Matlab function in Simulink - retain previous non-zero input value - matlab

I have a Matlab function block in Simulink that receives 2 inputs and processes it to generate an output. During the course of the simulation, at some time points, one of the inputs is zero. I'd like to use the most recent non-zero input to the function whenever that particular input value is zero. How can I achieve this? I tried creating a persisent variable that updates to most recent non-zero input value but that doesn't seem to work.
EDIT 1 (to include code):
function y = fcn(u)
persistent ref_val
if isEmpty(ref_val)
ref_val=10.0
end
if(u(1)<=25)
y=20.0
else
if(u(2)>0)
y=u(2)
ref_val=u(2)
else
y=ref_val
end
end
EDIT 2: For now, I fixed the issue by writing a C code that uses a static variable to retain the most recent non-zero input value. But I still welcome suggestions/solutions to realize this directly in a Matlab function.

Can't you use something like this in your simulation?
//Find the index of the last non-zero value in the input
[~,last_non_zero] = max(find(input(1:i) > 0))
//Call the function using this input
output = fnc(input(last_non_zero))

Related

MATLAB how to write a for loop with 2 conditions, one is "==", the other is "~="?

I am trying to write an S-Function in Simulink, with the inputs "t" for time, and "LIBs" for the amount of material entering the system. There are 2 outputs. The idea is like this in Simulink.
Simulink model concept
What I want to do is that, at a certain time iteration, the input "LIBs" goes to a different output. But during the iteration, there are some special points at which I don't want the input to go to any output.
The code is like this:
MATLAB
function [Batts_Spent, Batts_N_Spent] = BattsL6Y(t, LIBs)
for t = 2011:6:2035
if t ~= 2005:10:2035
Batts_Spent = LIBs;
end
end
for t = 2015:10:2035
if t ~= 2005:6:2035
Batts_N_Spent = LIBs;
end
end
for t = 2011:6:2035
if t == 2015:10:2035
Batts_Spent = LIBs;
end
end
end
I am sure that this code is not correct, but I don't know how to write it correctly.
And also, even if I have several input and output ports, the S-Function block in the Simulink project still remains only one input and output port. Should I change it to a MATLAB Function block?
In your case t is an input parameter, which makes it look like the function is called for each value of t. Then you have a loop iterating over all values of t. Considering your image, you don't need a loop at all. You can use if in combination with mod. For example:
function [Batts_Spent, Batts_N_Spent] = BattsL6Y(t, LIBs)
if mod(t-2005,6)==0 && t>=2005
Batts_Spent = LIBs;
end
end
This is only "Every 6th time the value of LIBs will be assigned to the first output". This isn't a finished solution but should be sufficient to get you started. You need a second if like this for the other output.

if statement block in Simulink accumulates last true value

If statement in Simulink is not like in a programming language, it accumulates last true value untill it occurs again.
As it can be seen here, when random value is lower than 0.5 (if condition) output dosn't give zero as one normally expects from if statement.
What is the proper way to use an if? (Where preferably I don't want to put saturation and matlab function)
The Out block within the If Action subsystem has a property to either hold or reset its output when disabled. The default is to hold; you want it to reset. You'll also need to specify 0 as the initial condition, which is what it'll reset to.
So far what I could do this to use an elementary matlab function;
function y = fcn(u)
if u>0
y = u;
else
y = 0;
end
end
but I wondered it would be a proper way of using if block.
Change out blocks property in If action subsystem to 'reset', it looks like it is 'held' in your current implementation

Too many input arguments error

I am trying to run my function. it shows
[root, ni]=value1(xu,xl,acceptable)
Error using value1
Too many input arguments.
function[root, ni]=value1(xu,xl,acceptable)
fu=HW10B(xu);
fl=HW10B(xl);
Err=1;
N=0;
if fu*fl>=0
end
while Err>=acceptable;
m=(xu+xl)/2;
fm=HW10B(m)
if fm*fu<0;
fl=m;
else fu=m;
Err=abs(xu-xl)/xu*100;
end
N=N+1;
end
function [ y] = HW10B( x)
%equation of x
y=3*x^3-8*x^2-4*x+9;
end
root=m;
ni=N;
end
function[m, N]=value1(xu,xl,acceptable)
y=#(x)3*x.^3-8.*x.^2-4.*x+9;%//Used anonymous function instead of private
fu=y(xu);%//Used above definition
fl=y(xl);
Err=1;%//Initialise error
N=0;%//Initialise counter
while Err>=acceptable && N<1e4;%//check for convergence
m=(fu+fl)/2;%//Get new midpoint
fm=y(m);%//Get value at midpoint
if fm*fu<0;%//Get left or right value to move
fl=m;
else
fu=m;
Err=abs(fu-fl)/fu*100;%//Calculate the error
end
N=N+1;%//Update iteration counter
end
end
Call it from the command line:
xu=15;xl=2;acceptable=1e-3;
[root, ni]=value1(xu,xl,acceptable)
root =
2.7554
ni =
29
As you can see I cleaned up your code quite a bit. Using the two separate storage variables at the end of the code was just taking up more space than necessary. The if statement fu*fl>0 did not do anything, thus I chucked it out. Finally, you needed to update your values in your functions, thus using the fl, fx and fm, not the xu and xl.
If you call the function exactly as I showed you from the command line (with your own values of course), it should not throw any errors.
What happens in your original code is that you calculate everything once for the input variables, get an error which is larger than acceptable and therefore executes again, taking the same input arguments, returning the same error as before, which is still larger than acceptable. This is what we call an infinite loop. I suggest you check for it using a maximum number of iterations, i.e.
while Err>=acceptable && N<1e4
and change the 1e4 to whatever maximum number of iterations you want to have. If you accidentally end up going infinite, this counter will kill it without having to resort to crtl-c or equivalents.

Change a constant in ODE calculations under particular conditions with a flag

I have an ODE for calculating how acidicity changes. Everything is working just fine, only I would like to change a constant whenever acidicity reaches a critical point. It is supposed to be some kind of irreversible effect I wish to simulate.
My constants are coming from a structure file (c) I load once in the ODE function.
[Time,Results] = ode15s(#(x, c) f1(x, c),[0 c.length],x0,options);
The main problem I have here is not telling Matlab to change the constant but remember if it happened already during the simulation once. so Matlab should take the irreversibly changed constant rather than the one I supply at the beginning.
I would like to write a flag that is saved while the ODE is running and an if condition, "if flag exists, change constant". How to do that?
UPDATE I:
PROBLEM SOLVED
Here a first own solution, it is not polished and requires a structure file approach. Which means, the constants which should suddenly changed upon an event, must be struct files which are handed in the ODE function into the function that should be evaluated (look above for the ODE syntax). The function accepts the inputs like this:
function [OUTPUT] = f1(t, x, c)
% So here, the constants all start with c. followed by the variable name. (because they are structs instead of globals)
%% write a flag when that event happens
if c.someODEevent <= 999 && exist ('flag.txt') == 0
dlmwrite ('flag.txt',1);
end
%% next iteration will either write the flag again or not. more importantly, if it exists, the constant will change because of this.
if exist ('flag.txt') == 2
c.changingconstant = c.changingconstant/2;
end
end
Please look into Horchlers kind answer where you have to take care that such a step may introduce inaccuracies and you have to be careful to check if your code does what it is supposed to do.
To do this accurately, you should use event detection within the ODE solver. I can't give you a specific answer because you've only provided the ode15s call it in your question, but you'll need to write an events function and then specify it via odeset. Something like this:
function acidity_main
% load c data
...
x0 = ...
options = odeset('Events',#events); % add any other options too
% integrate until critical value and stop
[Time1,Results1] = ode15s(#(x,c)f1(x,c),[0 c.length],x0,options);
x0 = Results(end,:); % set new initial conditions
% pass new parameters -it's not clear how you're passing parameters
% if desired, change options to turn off events for faster integration
[Time2,Results2] = ode15s(#(x,c)f1(x,c),[0 c.length],x0,options);
% append outputs, last of 1 is same as first of 2
Time = [Time1;Time2(2:end)];
Results = [Results1;Results2(2:end,:)];
...
function y=f1(x,c)
% your integration function
...
function [value,isterminal,direction] = events(x,c)
value = ... % crossing condition, evaluates to zero at event condition
isterminal = 1; % stop integration when event detected
direction = ... % see documentation
You'll want to use the events to integrate right to the point where the "acidicity reaches a critical point" and stop the integration. Then call ode15s again with the new value and continue the integration. This may seem crude, but it how this sort of thing can be done accurately.
You can see an example of basic event detection here. Type ballode in your command window to see the code for this. You can see a slightly more complex version of this demo in my answer here. Here's an example of using events to accurately change an ODE at specified times (rather than your case of specified state values).
Note: I find it strange that you're passing what you call "constants", c, as the second argument to ode15s. This function has strict input argument requirements: the first is the independent variable (often time), and the second is the array of state variables (same as your initial condition vector). Also if f1 only takes two arguments, #(x,c)f1(x,c) is superfluous – it's sufficient to pass in #f1.

Simulink return not-valid input

I have a model which takes the root of the input in an matlab function block. The input should always positive; however, sometimes simulink gives a negative number. The reason is that i use an implicit solver (ode15s due to other part of the system being stiff) and simulink have a invalid estimate. My question is: how can I tell simulink that the input is invalid which makes the solver take a smaller step (without stopping)? Can I return a special value (e.g., NaN) or throw an error (without stopping the simulation)?
You can return 0 in your MATLAB Function block if the input is negative:
if u<=0
y = 0;
else
y = sqrt(u);
end
where u is the input and y the output of the function.
Use the Hit Crossing Block to force the solver to take small time steps as your signal approaches zero. This will work assuming your model is correctly set up to force the signal to go no lower than zero (i.e. it works something like an Abs block, which will hit zero and then continue with a positive value).
My solution was to add another output, isInputValid. This is 0 if the input is invalid and 1 if the input is valid. This output is then integrated by a new integration block. It seems like the discontinuity produced by the boolean variable makes sure that the integrator takes smaller steps.
Example:
if ( u<0)
y = -realmax;
isInputValid = 0;
else
y = sqrt(u);
isInputValid = 1;
end
Then attach an integrator to the output of isInputValid.