2011-12-29 43 views
11

總之:是有限制的匿名功能範圍優雅的方式,或者是Matlab的在這個例子中破?MATLAB函數手柄工作區惡作劇

我有一個創建一個功能手柄管網解算器要使用的功能。它將網絡狀態作爲輸入,其中包括關於管道及其連接(或者必要時的邊和頂點)的信息,構造一個大的字符串,該字符串在函數形式中返回一個大矩陣,然後「evals」該字符串以創建句柄。

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工作區需要的變量不是特別大,但什麼是更瘋狂的是,當我檢查功能手柄:

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

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

...工作區字段包含一切getPipeEquations有(其中,順便說一句,是而不是整個Matlab工作區)。

如果我移動而不是eval語句的子功能,企圖迫使範圍,手柄會節省更多的緊湊(〜1MB):

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

這是預期的行爲?有沒有更好的方法來限制這個匿名函數的範圍?

作爲附錄,當我多次運行包含此函數的模擬時,清除工作區變得非常緩慢,這可能與Matlab的函數及其工作空間的處理有關,也可能不相關。

+0

你有沒有試過evalin('base',...)'?這有什麼區別嗎? – 2011-12-29 19:20:53

+0

我沒有,但工作區應該已經被限制在getPipeEquations的範圍內。 – 2011-12-29 20:57:40

回答

7

我可以重現:對於我來說匿名函數捕獲所有變量的副本,而不僅僅是匿名函數表達式中引用的那些變量。

這裏有一個最小的攝製。

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

實際上,它捕獲整個封閉工作區的副本。

>> 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將不會知道,直到您調用返回的函數句柄。

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

你正在做的解決方法 - 隔離在最小的工作空間的獨立功能的功能建設 - 聽起來像是正確的修補程序。

這裏有一個通用的方法來獲取有限的工作空間建立在匿名函數。

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); 

的長變量名在這裏受傷的可讀性,但減少與調用者的變量發生碰撞的可能性。

您只需調用eval_with_vars()而不是eval(),並將所有輸入變量作爲附加參數傳遞。然後,您不必爲每個匿名函數構建器輸入一個靜態函數定義。只要你知道哪些變量實際上將被引用,這將起作用,這與getJacobianHandle的方法相同。

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

你可能在開始時使用inputname來檢查碰撞,但可能沒有必要。 – 2011-12-30 19:08:01

+0

好主意 - 碰撞不太可能發生,但如果發生這種情況,您可能會得到一個沉默的,難以診斷的錯誤。我修改了代碼以添加碰撞檢查。 – 2011-12-30 19:43:49

2

匿名函數的範圍內,捕捉一切,並把它們存儲在功能工作區。參見anonymous functions

特別MATLAB文件:

「中的變量的表達式的主體中指定MATLAB捕獲這些變量和保持它們在整個功能句柄的生存期內保持不變

後者變量必須。在構建一個使用它們的匿名函數時,它們有一個賦值給它們的值,在構造時,MATLAB捕獲該函數體中指定的每個變量的當前值,函數將繼續將該值與變量相關聯,即使該值應該在工作區中改變或超出範圍。「

+1

您摘錄和鏈接到的文檔不會像您在此處所述的那樣廣泛。 doco說它捕獲表達式主體中引用的變量,但是海報說它不僅捕獲這些變量,而且還捕獲封閉工作空間中的_all_變量。 – 2011-12-29 20:27:45

0

另一種解決方法,你的問題,是使用MATLAB的save功能,可以用來保存你所需要的特定變量的事實。我遇到了與save函數有關的問題,它可以節省太多數據(與您的上下文有很大的不同),但是一些司法命名約定和變量列表中通配符的使用使我的所有問題都消失了。