Matlab 函数处理工作区恶作剧 [英] Matlab function handle workspace shenanigans

查看:39
本文介绍了Matlab 函数处理工作区恶作剧的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

简而言之:有没有一种优雅的方法来限制匿名函数的范围,或者在这个例子中Matlab是不是坏掉了?

In short: is there an elegant way to restrict the scope of anonymous functions, or is Matlab broken in this example?

我有一个函数可以创建一个在管网求解器中使用的函数句柄.它将网络状态作为输入,其中包括有关管道及其连接(或边缘和顶点,如果必须的话)的信息,构造一个大字符串,该字符串在函数形式时将返回一个大矩阵,并评估"该字符串以创建句柄.

I have a function that creates a function handle to be used in a pipe network solver. It takes as input a Network state which includes information about the pipes and their connections (or edges and vertices if you must), constructs a large string which will return a large matrix when in function form and "evals" that string to create the handle.

function [Jv,...] = getPipeEquations(Network)
... %// some stuff happens here

Jv_str = ['[listConnected(~endNodes,:)',...
    ' .* areaPipes(~endNodes,:);\n',...
    anotherLongString,']'];

Jv_str = sprintf(Jv_str); %// This makes debugging the string easier

eval(['Jv = @(v,f,rho)', Jv_str, ';']);

此函数按预期工作,但每当我需要保存包含此函数句柄的后续数据结构时,它都需要荒谬的内存量 (150MB) - 巧合的是与整个 Matlab 一样多创建此函数时的工作空间 (~150MB).这个函数句柄需要从 getPipeEquations 工作区获取的变量并不是特别大,但更疯狂的是,当我检查函数句柄时:

This function works as intended, but whenever I need to save later data structures that contain this function handle, it requires a ridiculous amount of memory (150MB) - coincidentally about as much as the entire Matlab workspace at the time of this function's creation (~150MB). The variables that this function handle requires from the getPipeEquations workspace are not particularly large, but what's even crazier is that when I examine the function handle:

>> f = functions(Network.jacobianFun)
f = 

     function: [1x8323 char]
         type: 'anonymous'
         file: '...\pkg\+adv\+pipe\getPipeEquations.m'
    workspace: {2x1 cell}

...工作区字段包含 getPipeEquations 拥有的所有内容(顺便说一句,不是整个 Matlab 工作区).

...the workspace field contains everything that getPipeEquations had (which, incidentally is not the entire Matlab workspace).

如果我改为将 eval 语句移动到子函数以尝试强制作用域,则句柄将节省得更紧凑(~1MB):

If I instead move the eval statement to a sub-function in an attempt to force the scope, the handle will save much more compactly (~1MB):

function Jv = getJacobianHandle(Jv_str,listConnected,areaPipes,endNodes,D,L,g,dz)
eval(['Jv = @(v,f,rho)', Jv_str, ';']);

这是预期的行为吗?有没有更优雅的方式来限制这个匿名函数的作用域?

Is this expected behavior? Is there a more elegant way to restrict the scope of this anonymous function?

作为补充,当我多次运行包含此函数的模拟时,清除工作区变得非常缓慢,这可能与 Matlab 对函数及其工作区的处理有关,也可能无关.

推荐答案

我可以重现:对我来说,匿名函数正在捕获封闭工作区中所有变量的副本,而不仅仅是表达式中引用的那些匿名函数.

I can reproduce: anonymous functions for me are capturing copies of all variables in the enclosing workspace, not just those referenced in the expression of the anonymous function.

这是一个最小的重现.

function fcn = so_many_variables()
a = 1;
b = 2;
c = 3;
fcn = @(x) a+x;
a = 42;

事实上,它捕获了整个封闭工作区的副本.

And indeed, it captures a copy of the whole enclosing workspace.

>> f = so_many_variables;
>> f_info = functions(f);
>> f_info.workspace{1}
ans = 
    a: 1
>> f_info.workspace{2}
ans = 
    fcn: @(x)a+x
      a: 1
      b: 2
      c: 3

起初这让我感到惊讶.但是仔细想想也是有道理的:由于fevaleval 的存在,Matlab 在构造时实际上无法知道匿名函数实际运行的变量是什么结束引用.因此,它必须捕获范围内的所有内容,以防万一它们被动态引用,就像在这个人为的示例中一样.这使用了 foo 的值,但在调用返回的函数句柄之前,Matlab 不会知道这一点.

This was a surprise to me at first. But it makes sense when you think about it: because of the presence of feval and eval, Matlab can't actually know at construction time what variables the anonymous function is actually going to end up referencing. So it has to capture everything in scope just in case they get referenced dynamically, like in this contrived example. This uses the value of foo but Matlab won't know that until you invoke the returned function handle.

function fcn = so_many_variables()
a = 1;
b = 2;
foo = 42;
fcn = @(x) x + eval(['f' 'oo']);

您正在采取的解决方法 - 将函数构造隔离在具有最小工作空间的单独函数中 - 听起来是正确的解决方法.

The workaround you're doing - isolating the function construction in a separate function with a minimal workspace - sounds like the right fix.

这里有一种通用的方法可以让受限的工作区在其中构建您的匿名函数.

Here's a generalized way to get that restricted workspace to build your anonymous function in.

function eval_with_vars_out = eval_with_vars(eval_with_vars_expr, varargin)

% Assign variables to the local workspace so they can be captured
ewvo__reserved_names = {'varargin','eval_with_vars_out','eval_with_vars_expr','ewvo__reserved_names','ewvo_i'};
for ewvo_i = 2:nargin
    if ismember(inputname(ewvo_i), ewvo__reserved_names)
        error('variable name collision: %s', inputname(ewvo_i));
    end
    eval([ inputname(ewvo_i) ' = varargin{ewvo_i-1};']);
end
clear ewvo_i ewvo__reserved_names varargin;

% And eval the expression in that context
eval_with_vars_out = eval(eval_with_vars_expr);

这里的长变量名会影响可读性,但会降低与调用者变量发生冲突的可能性.

The long variable names here hurt readability, but reduce the likelihood of collision with the caller's variables.

您只需调用 eval_with_vars() 而不是 eval(),并将所有输入变量作为附加参数传入.这样您就不必为每个匿名函数构建器键入静态函数定义.只要您事先知道实际将引用哪些变量,这就会起作用,这与使用 getJacobianHandle 的方法相同.

You just call eval_with_vars() instead of eval(), and pass in all the input variables as additional arguments. Then you don't have to type up a static function definition for each of your anonymous function builders. This'll work as long as you know up front what variables are actually going to be referenced, which is the same limitation as the approach with getJacobianHandle.

Jv = eval_with_vars_out(['@(v,f,rho) ' Jv_str],listConnected,areaPipes,endNodes,D,L,g,dz);

这篇关于Matlab 函数处理工作区恶作剧的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆