确保 MATLAB 不会重新计算符号表达式

Make sure MATLAB does not recalculate symbolic expression

我正在构建(我的第一个...)MatLab 程序,它需要对一个方程进行符号微分,然后多次使用该解(使用不同的数字输入)。

我不希望它每次需要输入一组新数值时都重新计算符号微分。这可能会大大增加 运行 这个程序所花费的时间(考虑到它的性质,一个数字优化器,可能已经是几个小时了)。

我的问题是如何构建我的程序,使其不会重新计算符号微分?

有问题的 class 是:

function [ result ] = GradOmega(numX, numY, numZ, numMu)
syms x y z mu
omega = 0.5*(x^2+y^2+z^2) + (1-mu)/((x+mu)^2+y^2+z^2)^0.5 + mu/((x+mu-1)^2+y^2+z^2)^0.5;
symGradient = gradient(omega);
%//Substitute the given numeric values back into the funtion
result = subs(symGradient, {x,y,z,mu}, {numX, numY, numZ, numMu});
end

我知道我可以象征性地计算导数,然后将其复制粘贴到代码中,例如

gradX = x + ((2*mu + 2*x)*(mu - 1))/(2*((mu + x)^2 + y^2 + z^2)^(3/2)) - (mu*(2*mu + 2*x - 2))/(2*((mu + x - 1)^2 + y^2 + z^2)^(3/2));
gradY = y - (mu*y)/((mu + x - 1)^2 + y^2 + z^2)^(3/2) + (y*(mu - 1))/((mu + x)^2 + y^2 + z^2)^(3/2);
gradZ = z - (mu*z)/((mu + x - 1)^2 + y^2 + z^2)^(3/2) + (z*(mu - 1))/((mu + x)^2 + y^2 + z^2)^(3/2);

但是我的代码有点晦涩,这是共享项目中的问题。 这里有一个相关的查询:http://uk.mathworks.com/matlabcentral/answers/53542-oop-how-to-avoid-recalculation-on-dependent-properties-i-hope-a-mathwork-developer-could-give-me-a 但恐怕我无法遵循代码。 此外,我对 Java 和 Python 更加熟悉,如果这有助于解释任何事情的话。

您可以将您的函数包装到某种函数工厂中,它不是 return 数值结果,而是一个可以计算的函数:

(我不得不将调用 syms 替换为 sym('mu'),因为出于某种原因它一直在行 omega = ... 中调用 mutools 函数。我也进行了更改调用 gradient 以确保参数顺序正确,mu 将被视为常量。)

function GradOmega = GradOmegaFactory()
x = sym('x');
y = sym('y');
z = sym('z');
mu = sym('mu');
omega = 0.5*(x^2+y^2+z^2) + (1-mu)/((x+mu)^2+y^2+z^2)^0.5 + mu/((x+mu-1)^2+y^2+z^2)^0.5;
symGradient = gradient(omega,{'x','y','z'});
GradOmega = matlabFunction(symGradient, 'vars', {'x','y','z','mu'});
end

然后您可以通过以下方式调用它:

GradOmega = GradOmegaFactory();
result1 = GradOmega(numX1, numY1, numZ1, numMu1);
result2 = GradOmega(numX2, numY2, numZ2, numMu2);
result3 = GradOmega(numX3, numY3, numZ3, numMu3);
...

更好:

您可以更花哨地使用包装函数 GradOmega,它在内部构建这样一个函数并使其成为 persistent,以获得与您最初的方法相同的界面。第一次调用函数 GradOmega 时,会计算符号表达式,但在每次连续调用时,您只需计算生成的函数句柄,这意味着它应该与硬编码一样快。

function result = GradOmega(numX, numY, numZ, numMu)
persistent numericalGradOmega;
if isempty(numericalGradOmega)
    numericalGradOmega = GradOmegaFactory();
end
result = numericalGradOmega(numX, numY, numZ, numMu);
end

像使用原始版本一样使用它

result = GradOmega(numX, numY, numZ, numMu);

只需将这两个函数复制并粘贴到一个 GradOmega.m 文件中。 (GradOmega 应该是文件中的第一个函数。)


另一个提示:您甚至可以使用向量计算此函数。您可以通过使用行向量的调用 GradOmega([1,5], [2,6], [3,7], [4,8]) 来节省时间开销,而不是之后调用 GradOmega(1,2,3,4)GradOmega(5,6,7,8)

另一个提示:要进一步清理您的代码,您还可以将第一行放入单独的 symOmega.m 文件中。

function omega = symOmega()
x = sym('x');
y = sym('y');
z = sym('z');
mu = sym('mu');
omega = 0.5*(x^2+y^2+z^2) + (1-mu)/((x+mu)^2+y^2+z^2)^0.5 + mu/((x+mu-1)^2+y^2+z^2)^0.5;

这样您就不必在使用它的每个文件中都有此符号表达式的副本。如果您还想评估 Omega 本身,这可能是有益的,因为您可以使用此答案中列出的相同工厂方法。您最终会得到以下文件:symOmega.mOmega.mGradOmega.m,其中只有文件 symOmega.m 具有实际的数学公式,其他两个文件使用 symOmega.m.