Pass Function Handles from MATLAB to Simulink

I'm trying to implement control barrier functions in Simulink, and it's decided to make my life pretty difficult. I'm also a bit of a beginner in Simulink, though, so I'm hoping someone can help me with what I'm trying to do.
The problem:
I have a MATLAB script that sets up a large number of variables and then opens and runs a Simulink model with a control loop in it. Included in that control loop is a large quadratic program to be solved at every time step, where the constraints of that quadratic program are calculated using several control barrier functions and their first few time derivatives. The bottom line is that I need to make between 20 and 30 custom functions that each take in a vector input and return a scalar output.
First of all, my MATLAB script needs to have access to all of these functions because I need to check that hyperparameters defined in the script satisfy some requirements with the initial condition with the model. So, I need to write each of these functions in the MATLAB script regardless. And second of all, it's just so, so much easier to write these functions in MATLAB. As MATLAB functions, they're all one-liners. As Simulink functions, they all require several blocks.
What I'm currently doing:
Currently, I just have every one of the functions I need implemented in both MATLAB and Simulink. So, the bottom of my MATLAB script looks like
function h = h_x_lower(state, x_min)
h = state(1) - x_min;
end
function hdot = hdot_x_lower(state)
hdot = state(7);
end
% (I'm omitting a lot of the functions here for brevity)
function hddot = hddot_obs(state, a, b, g)
hddot = 2*(state(7)^2 + g*(state(1) - a)*state(4) + state(8)^2 - g*(state(2) - b)*state(5));
end
function hdddot = hdddot_obs(state, a, b, g)
hdddot = 2*g*(3*state(4)*state(7) + (state(1) - a)*state(10) - 3*state(5)*state(8) - (state(2) - b)*state(11));
end
and then I also have all of these Simulink functon blocks:
The issue is that this isn't a sustainable solution. I've just discovered that I want to significantly revise my design, which means that I have to remake all of these functions and add several more besides. I don't want to have to do all that work in two places every time I revise my design, and I'm really, really hoping there's a way around it.
Potential solution I've considered (1):
First, I'm aware of the existence of Interpreted MATLAB Function blocks, which seem to be exactly what I'm looking for. However, I've also read that they're extremely slow and will be removed in future versions of Simulink. So it sounds like I should avoid them if possible.
Potential solution I've considered (2):
The other solution I've considered is defining each of these MATLAB functions in the script (as I've already done) and then calling them in MATLAB Function blocks. The issue is that I'm not sure how to get the MATLAB Function block to acknowledge the existence of the functions as I've written them above. I do realize that Simulink can access parameters stored in the MATLAB workspace, so one thing I've tried is storing function handles in the MATLAB workspace - something like
a = 1; b = 1; g = 9.8;
hdddot_obs = @(state) hdddot_obs(state, a, b, g);
- and then defining a MATLAB Function block that takes the function handle as an argument and giving the block a mask so that I can put the function handle in as a parameter. The contents of the block look like
function hdddot = hdddot_obs(state, hdddot_obs_handle)
hdddot = hdddot_obs_handle(state);
I then added the following mask:
and I changed the argument hdddot_obs_handle to a parameter as below:
Finally, I passed the function handle to the block as below:
and connected it up to my model very simply as below:
However, when I try to run my model (which otherwise works fine) with this function block added in, I get the following error:
Expression 'hdddot_obs_handle' for initial value of data 'hdddot_obs_handle' must evaluate to logical or supported numeric type.
Conclusion:
Evidently, it's not possible to pass function handles as parameters to Simulink, at least not in the way I'm trying to do it. (Or perhaps I'm just doing something wrong.) I'm sure there should be a way to do generally what I'm trying to do. (I'm a bit frustrated that Interpreted MATLAB Function blocks are broken because they're exactly what I'm looking for.) As I said before, I'm kind of new to Simulink -- the whole masking thing took me several hours and multiple help docs to figure out. If anyone could give me some pointers for what I'm trying to do, I would greatly appreciate it.

 采纳的回答

I completely forgot to follow up on this and it's now several months after the fact. But here's what I ended up doing. It's not the most elegant or efficient solution (I'm sure someone better at MATLAB could make it better), but it works.
The key piece of info I was missing is the fact that the first function in a .m file is the main function and the rest are local functions (many thanks to @Fangjun Jiang for that knowledge). Given that fact, I created a separate .m file containing all of my functions. Then, at the top of the file, I made a selector function to be able to call any of the subsequent functions.
Here's some code, generalized so that people don't get confused by variable names. Suppose that you have functions func_1(a, b, c), func_2(a, b), func_3(a, b, c), and func_4(a, b, c, d) (i.e. all functions have different numbers of arguments), and you don't want to make four separate .m files. Then, you could do this:
% function_selector.m
output = selector(function_name, args)
% function_name should be a string, args should be a cell array
if strcmp(function_name, 'func_1')
[a, b, c] = args{:};
output = func_1(a, b, c);
end
elseif strcmp(function_name, 'func_2')
[a, b] = args{:};
output = func_2(a, b);
end
elseif strcmp(function_name, 'func_3')
[a, b, c] = args{:};
output = func_3(a, b, c);
end
elseif strcmp(function_name, 'func_4')
[a, b, c, d] = args{:};
output = func_4(a, b, c, d);
end
else
assert(false, ["selector() received unknown function name '", function_name, "'"])
end
end
out_1 = func_1(a, b, c)
% Body of func_1
end
out_2 = func_2(a, b)
% Body of func_2
end
out_3 = func_3(a, b, c)
% Body of func_3
end
out_4 = func_4(a, b, c, d)
% Body of func_4
end
Then, wherever you want to use any of the functions in Simulink, you can use a MATLAB Function block. Just include the line
coder.extrinsic("function_selector") % or whatever you named your .m file
at the top of the block (but inside the function). Then, in that block, you can call any of your functions wherever you want to, but with slightly different syntax than normal. Instead of writing
output = func_3(x, y, z);
you would write
output = selector('func_3', {x, y, z});
Note that this solution does require you to write out a pretty extensive if/else tree if you have a lot of functions. Like I said, it's not very elegant. Perhaps if you have a lot of functions and you can number them in an obvious way, you could put them all in an array and then instead of passing the function name as a string into selector(), you could just pass the index of the desired function and then index into the array.

3 个评论

I don't know if this approach with the selector function is the best way, but if using this approach it's possible the selector function could be very much simplified. I haven't verified that this will work, but I think it should.
The function_selector .m file would look like this
function output = function_selector(function_name,varargin)
output = feval(function_name,varargin{:})
end
out_1 = func_1(a, b, c)
% Body of func_1
end
out_2 = func_2(a, b)
% Body of func_2
end
out_3 = func_3(a, b, c)
% Body of func_3
end
out_4 = func_4(a, b, c, d)
% Body of func_4
end
This function can be called from Matlab itself with the usual signature (i.e., no need to wrap the arguments in a cell array)
output = function_selector('func_2',a,b)
and you can add more local functions or remove ones you don't need. I guess one difference is that you can't assert for the function_name, but if it can't be found an error will be thrown anyway (though you could maintain a list of supported local functions at the top of function_selector and check against that before calling feval).
In Simulink, the Matlab Function block would look like what you already have, but w/o the cell array wrapping of the arguments.
function outputs = matfunction(inputs)
coder.extrinsic("function_selector")
% do some stuff
out2 = function_selector('func2',a,b);
% do some more stuff
end
feval() sounds like a good fit for this. @Peter Fisher, I wonder if you could try this and update back.
@Paul Aha, I was wondering whether MATLAB had something like that. At the time I was working on this, I just needed a quick solution and didn't really have time to look into it too much. But I just tried it and yes, your approach works great and is much more elegant than what I had. Thank you so much for the suggestion!

请先登录,再进行评论。

更多回答(1 个)

Fangjun Jiang
Fangjun Jiang 2023-11-20
编辑:Fangjun Jiang 2023-11-20
Design all those functions as Simulink Functions. A Simulink Function can be called in Simulink and/or inside a MATLAB Function block.
Put the "scripts to check that hyperparameters defined in the script satisfy some requirements with the initial condition with the model" inside a MATLAB Function block and put the MATLAB Function block inside the Initialize subsystem. This MATLAB Function block can call all those Simulink Functions. It is executed only once at the initialization of the simulation.
The way you put all the functions in one file makes the first function as the main function but the rest as "local functions". Local functions can only be called within the same file.
The other approach would require making one file for each function, which is not desirable.

5 个评论

My apologies, I forgot to reply to this earlier, but I did end up solving my problem. Thank you so much for your response! It was extremely helpful to know that the first function in a file is the main function and all the rest are local functions. I did not end up taking the approach of making each function as a Simulink function block -- I think you underestimate how much I dislike clicking and dragging blocks around. I'll post a separate answer to my question shortly with what I actually ended up doing.
Hello Peter.
Did you manage to figure out how to pass function handles (generated in a m script) as parameters to Simulink (matlab function blocks) in the end?
I am trying to solve the same issue and would be curious if there is a solution
Many thanks
There is no way to pass a function handle to Simulink. In order to pass such a thing, there would have to be a signal type that could hold function handles, but there is not: https://www.mathworks.com/help/simulink/ug/signal-types.html
@Gregor Ah, whoops, I completely forgot to follow up on this with what I actually did. My apologies. I ended up making a separate .m file containing all of my functions, with the first function in the file being a selector function which allows me to call any one of the subsequent functions using if/else statements. In Simulink, I then called the selector function as many times as I needed to in a MATLAB Function block. Give me a few minutes -- I'll post my code in a separate answer to the question.
Update: just posted my solution as a separate answer.

请先登录,再进行评论。

类别

帮助中心File Exchange 中查找有关 Programming 的更多信息

产品

版本

R2023b

Community Treasure Hunt

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

Start Hunting!

Translated by