Pass Function Handles from MATLAB to Simulink

38 ビュー (過去 30 日間)
Peter Fisher
Peter Fisher 2023 年 11 月 20 日
コメント済み: Peter Fisher 2024 年 2 月 23 日 23:04
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.

採用された回答

Peter Fisher
Peter Fisher 2024 年 2 月 11 日 20:43
編集済み: Peter Fisher 2024 年 2 月 11 日 20:49
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 件のコメント
Fangjun Jiang
Fangjun Jiang 2024 年 2 月 12 日 14:11
feval() sounds like a good fit for this. @Peter Fisher, I wonder if you could try this and update back.
Peter Fisher
Peter Fisher 2024 年 2 月 23 日 23:04
@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 件のコメント
Peter Fisher
Peter Fisher 2024 年 2 月 11 日 20:22
@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.
Peter Fisher
Peter Fisher 2024 年 2 月 11 日 20:44
Update: just posted my solution as a separate answer.

サインインしてコメントする。

カテゴリ

Help Center および File ExchangeSimulink Functions についてさらに検索

製品


リリース

R2023b

Community Treasure Hunt

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

Start Hunting!

Translated by