Can I use a struct in an anonymous function?

I am solving a PDE via finite volume methods. As a result, I am left with a set of ODEs which I solve with ode15s. I have a bunch of constants which I use in the function defining the derivative, and I insert these by the inclusion of a struct to the function. When I run the ode15s I obtain an error which pertains to the inclusion of the struct as an input to the function. I don't really want to include the constants within the function as that seems "messy", so I would prefer to do it via a struct.
%This is a code to solve the isothermal sintering equations.
%%---Physical parameters---
N = 251; %This is the number of cells-1
L = 1; %This is the initial length of the rod
%%---Initial conditions
%Length
X=linspace(0,L,N); %This is the partition of the initial length
%Density
rho_0_int=0.5+0.1*sin(6*pi*X);
rho_0=0.5*(rho_0_int(1:N-1)+rho_0_int(2:N));
%Amount of mass
h=cumtrapz(X,rho_0_int);
%Initial velocity
u0=zeros(N-1,1);
% Define the system of equations as a function
y0 = [1./rho_0'; u0];
% Finite Volume Method solution loop
tspan = [0 3]; % You can adjust this based on the time range you're interested in
[t, y] = ode15s(@ode_system, tspan, y0);
nu=y(:,1:N-1);
u=y(:,N:2*N-2);
Tspan=length(y(:,1));
for i=1:Tspan
plot(u(i,:))
pause(0.05);
end
function dydt = ode_system(t, y)
g = 9.81; %Acceleration due to gravity.
K = 1e-2; %This is the Laplace constant from the sintering potential.
eta_0 = 0.005; %The shear stress of the skeleton
T = 2.7; %This is the time of sintering.
rho_m = 1; %This is the density of the individual metallic particles.
N = 251; %This is the number of cells-1
L = 1; %Initial length of rod
X=linspace(0,L,N); %This is the partition of the initial length
%Density
rho_0_int=0.5+0.1*sin(6*pi*X);
rho_0=0.5*(rho_0_int(1:N-1)+rho_0_int(2:N));
%Amount of mass
h=cumtrapz(X,rho_0_int);
M=h(N);
U=M/(rho_m*T);
a_1=T/(U*M);
a_2=T*rho_m/M^2;
a_3=g*T/U;
dh=diff(h);
nu = y(1:N-1)'; % u(t)
u = y(N:2*N-2)'; % v(t)
%Compute the left and right specific volumes:
nu_left=15*nu(1)/8-5*nu(2)/4+3*nu(3)/8;
nu_right=15*nu(N-3)/8-5*nu(N-2)/5+3*nu(N-1)/8;
%Compute the fluxes on the interfaces
nu_half=NaN(1,N);
nu_half(2:N-1)=0.5*(nu(2:N-1)+nu(1:N-2));
nu_half(1)=nu_left;
nu_half(N)=nu_right;
%Fluxes for nu equation
nu_flux=zeros(N,1);
nu_flux(2:N-1)=0.5*(u(1:N-2)+u(2:N-1));
du_dh_left=-P_L(K,nu_left)*nu_left/zeta(nu_left);
du_dh_right=-P_L(K,nu_right)*nu_right/zeta(nu_right);
nu_flux(1)=-3*du_dh_left*dh(1)/8+9*u(1)/8-u(2)/8;
nu_flux(N)=3*du_dh_right*dh(N-1)/8-9*u(N-1)/8+u(N-2)/8;
%Fluxes for u equation
u_grad=2*(u(2:N-1)-u(1:N-2))./(dh(2:N-1)+dh(1:N-2));
u_flux=zeros(1,N);
u_flux(2:N-1)=a_1*P_L(K,nu_half(2:N-1))+(a_2*zeta(nu_half(2:N-1))./nu_half(2:N-1)).*u_grad;
% The system of equationsY
dnu_dt = nu_flux(2:N)-nu_flux(1:N-1);
du_dt =u_flux(2:N)'-u_flux(1:N-1)';
% Return the derivatives
dydt = [dnu_dt; du_dt];
end
function y=P_L(K,nu)
y=K./nu;
end
function y=zeta(nu)
y=(2/3)*(nu).^(-2)./(nu-1);
end

 Accepted Answer

When I run the ode15s I obtain an error which pertains to the inclusion of the struct as an input to the function.
Please show us the full and exact text of that error message, including all the text displayed in red in the Command Window (and if there are any warning messages displayed in orange, please show us those too.) The exact text may be useful and/or necessary to determine what's going on and how to avoid the warning and/or error.
At a quick glance, though, you're not actually including the struct as an input to your ODE function.
[t, y] = ode15s(@ode_system, tspan, y0);
function dydt = ode_system(t, y, S)
As you're calling it, ode15s will call your ode_system function with two input arguments, t and y. You have not told it that it should pass the struct S into that function; use one of the techniques shown on this documentation page to do so. [I suspect you expected that MATLAB would automatically "figure out" it should pass the struct from the caller's workspace into ode_system based on the name. It does not.]

17 Comments

Not enough input arguments.
Error in sintering_FV>ode_system (line 31)
S.M
Error in odearguments (line 92)
f0 = ode(t0,y0,args{:}); % ODE15I sets args{1} to yp0.
Error in ode15s (line 148)
odearguments(odeIsFuncHandle, odeTreatAsMFile, solver_name, ode, tspan, y0, options, varargin);
Error in sintering_FV (line 28)
[t, y] = ode15s(@ode_system, tspan, y0,S);
My "quick glance" was correct. Parameterize the function in your ode15s call using one of the techniques on the documentation page I linked.
The problem is that I can't really use an anonymous function. So the page you linked is irrelevant. Would I have to use a nested function? This seems a bit of a faff to my mind.
"The problem is that I can't really use an anonymous function"
What exactly is stopping you from using an anonymous function?
Isn't trivially obvious? The function isn't a one-liner. The whole idea suggested is flawed. Let me explain. The function I use has three inputs, t, y, and S, the struct of variables. That struct comes from the main program. It has to be inserted in the main function, so you still have the issue that issue that was mentioned, ode15s requires sometimg with two inputs, not three.
The other solution is to make all the constants I use global vaiables. That makes the problem go away.
To parameterize using an anonymous function in this case, replace
[t, y] = ode15s(@ode_system, tspan, y0);
with
[t, y] = ode15s(@(t,y)ode_system(t,y,S), tspan, y0);
which is analogous to the example from the documentation page:
You also can use anonymous functions to call more complicated objective functions that you define in a function file. For example, suppose you have a file named cubicpoly.m with this function definition:
function y = cubicpoly(x,b,c)
y = x^3 + b*x + c;
end
At the command line, define b and c, and then call fzero with an anonymous function that invokes cubicpoly:
b = 2;
c = 3.5;
x = fzero(@(x) cubicpoly(x,b,c),0)
However, you'll still run into errors because of problems in the ode_system function itself, which include:
(1) the line
nu_half=NaN(1:S.N);
% ^
should probably be
nu_half=NaN(1,S.N);
% ^
(2) N is undefined and should be S.N throughout the function I imagine
(3) the line
u_grad=2*(u(2:S.N-2)-u(1:S.N-2))./(dh(2:S.N-1)+dh(1:S.N-2));
% ^
will throw an error about incompatible array sizes. I'm guessing it should be
u_grad=2*(u(2:S.N-1)-u(1:S.N-2))./(dh(2:S.N-1)+dh(1:S.N-2));
% ^
(4) an error that the size of the initial conditions vector does not match the size of the results vector, and that's where I stopped trying to debug.
"Isn't trivially obvious?"
Not to me.
"The function isn't a one-liner"
Totally irrelevant.
"The whole idea suggested is flawed."
I doubt that, it has always worked for me.
"Let me explain. The function I use has three inputs, t, y, and S, the struct of variables. That struct comes from the main program. It has to be inserted in the main function, so you still have the issue that issue that was mentioned, ode15s requires sometimg with two inputs, not three."
That is exactly what function parameterization via an anonymous function achieves:
[t, y] = ode15s(@(t,y)ode_system(t,y,S), tspan, y0);
ODE15S calls a function with two input arguments and ODE_SYSTEM is called with three inputs, including the structure S from the calling workspace. So far I do not see what the problem is.
"The other solution is to make all the constants I use global vaiables. That makes the problem go away."
Do not use global variables for this. Parameterizing the function using an anonymous function is recommended.
@Mat "Isn't trivially obvious? The function isn't a one-liner. "
You then miss the main principal use case of anonymous function.
You must have a two level of functions; A main one that do the works with many lines of code as you like - a standard function then with extra parameters, then the anonymous serves only as a wrapper to your ode solver with standard calling interface as required by the ode solver but passes extra parameter(s) to the main long function.
You have TWO functions in cascade to work with: the short anonymous that invokes the long main function.
@Stephen23 What worked was put all the variables within the function. There are other issues but I have something that works
@Mat comments to Steven Lord:
Not relevant to question
Steven Lord's response appears to be entirely correct to me.
It takes no less than 6 MVPs to show Mat the light.
I suspect that was a response to a flag I had added to the original question after the code had been edited away.
The code has been restored to the question so I have removed my flag.
"What worked was put all the variables within the function. "
There is no need to do that. You wrote in your question that "I don't really want to include the constants within the function as that seems "messy", so I would prefer to do it via a struct", which seems a perfectly reasonable approach to me. The documented, recommended solution works, everyone here uses it. Several people have shown you the exact code to use. So far nothing in this thread indicates why it would not work for you.
In contrast you have not shown your modified code (so we have no idea what you attempted, or what you did wrong).
In any case, I would not define those parameters inside the function.
"There is no need to do that."
Turns out there was.
The issue is ode15s, where it uses 2 inputs rather than three as I origianally thought. The other solution is to use global variables.
Put the parameter variables in a single extra structure, then dispatche it using input structure within your inner function. This is less messy and more efficient than calling the anonymous function with a long list of parameters.
Advantage: When you want to add or remove parameters you only need to change the structure, not the calling interface.
You could do similar with a specific own object class and put parameters in properties when you instantiate the object, but if you are not comfortable with structure no need to work with OOP.
"Turns out there was."
Turns out there is not.
"The issue is ode15s, where it uses 2 inputs rather than three as I origianally thought...."
As everyone here keeps advising you, that is exactly what function parameterization is for:
Show us your current code and we will show you how.

Sign in to comment.

Categories

Find more on Programming in Help Center and File Exchange

Products

Release

R2024a

Asked:

Mat
on 2 Oct 2024

Commented:

on 10 Oct 2024

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!