Closed SKittan closed 3 years ago
12.9.3 in the specification says:
An external function is not allowed to internally change the inputs (even if they are restored before the end of the function)
The specification does say that the record is passed by reference, but that doesn't say anything about the lifetime of the record that is passed to the function.
If you need to store internal memory between calls to the external function you might instead want to use an external object.
Another way would be to return a new record from the external function.
The specification does say that the record is passed by reference, but that doesn't say anything about the lifetime of the record that is passed to the function.
This is an output though. So it is returned. The problem is outputs are not initialized even if you add an initialization to the record:
structTest_threeValues_construct(threadData, _abc);
tmp2._a = 3.0;
tmp2._b = 4.0;
tmp2._c = 5.0;
tmp1 = tmp2;
structTest_threeValues_copy(tmp1, _abc);;
add_c(&_abc_ext); // _abc is not passed here !!!
_abc = (structTest_threeValues)_abc_ext;
I had problems returning the data as new record. My solution for now is to manage my data in the C functions via malloc and pointer.
#pragma once
#include <stdlib.h>
typedef struct
{
double a, b, c;
}threeValues;
size_t init_data(double, double, double);
void add_c(size_t);
double get_a(size_t);
double get_b(size_t);
double get_c(size_t);
void free_data(size_t);
size_t init_data(double a, double b, double c){
threeValues* abc = (threeValues*)malloc(sizeof(threeValues));
abc->a = a;
abc->b = b;
abc->c = c;
return (size_t)abc;
}
void add_c(size_t in){
threeValues* out = (threeValues*)in;
out->a += 2.;
out->b += 2.;
out->c += 2.;
}
double get_a(size_t ptData){
threeValues* data = (threeValues*)ptData;
return data->a;
}
double get_b(size_t ptData){
threeValues* data = (threeValues*)ptData;
return data->b;
}
double get_c(size_t ptData){
threeValues* data = (threeValues*)ptData;
return data->c;
}
void free_data(size_t ptData){
threeValues* data = (threeValues*)ptData;
free(data);
}
Another way would be to return a new record from the external function.
This could cause huge memory leaks if the memory for the new record is malloc'd each time the function is called.
My understanding from the old days when we wrote ExternalMedia is that record (and array) external function outputs are passed by reference, meaning that the simulation runtime allocates the memory and passes a pointer to the function, that uses the pointer to fill it in.
The only case when this doesn't work is for String outputs, because the amount of memory is not known a priori. In this case the Specification define ad-hoc functions to be called, see Section 12.9.6.1. In this case the Specification specifically advises not to use malloc, because a Modelica environment may have a different memory allocation mechanism, e.g. stack-based.
Do I miss something?
@SKittan, in your example, the Modelica function add()
has only an output, no inputs. So, you can't pass any value to it, to increase them by some amounts. Modelica functions are memoryless. If you want to achieve that semantics, you need to write something like
model structTest
threeValues abc(a(start=1.), b(start=2.), c(start=3.));
record threeValues
Real a, b, c;
annotation(
Icon(coordinateSystem(extent = {{-200, -200}, {200, 200}})),
Diagram(coordinateSystem(extent = {{-200, -200}, {200, 200}})));
end threeValues;
function add
input threeValues abc_in;
output threeValues abc;
external "C"
add_c(abc_in, abc) annotation(
IncludeDirectory = "modelica://structTest",
Include = "#include \"add_two.c\"");
annotation(
Icon(coordinateSystem(extent = {{-200, -200}, {200, 200}})),
Diagram(coordinateSystem(extent = {{-200, -200}, {200, 200}})));
end add;
algorithm
abc := add(abc_in);
annotation(
Icon(coordinateSystem(extent = {{-200, -200}, {200, 200}})),
Diagram(coordinateSystem(extent = {{-200, -200}, {200, 200}})));
end structTest;
and the C code should be something like
#include <stdio.h>
typedef struct
{
double a, b, c;
}threeValues;
void add_c(threeValues*, threeValues*);
void add_c(threeValues* in, threeValues* out){
//printf("In Pointer Address: %i\n", in);
out->a = in->a+2;
out->b = in->b+2;
out->c = in->c+2;
}
Thank you for your remark. For now I use the malloc only in the init function. This is invoked only once during initialization phase of a simulation. So from my understanding there should be no memory leak:
...
algorithm
if initial() then // Construct
pt2abc := init(abc);
elseif terminal() then // Destruct
free(pt2abc);
else // Calculate
add(pt2abc);
end if;
abc.a := getA(pt2abc);
abc.b := getB(pt2abc);
abc.c := getC(pt2abc);
annotation(
Icon(coordinateSystem(extent = {{-200, -200}, {200, 200}})),
Diagram(coordinateSystem(extent = {{-200, -200}, {200, 200}})));
end structTest;
But I prefer a solution, where Modelica manages the memory. Hence, I will try your recommendation.
You shouldn't need to have a malloc in the unit function either. Just pass the output by reference as an input argument to the function and fill in the values.
I have now this variant:
model structTest
threeValues abc(a(start=1.), b(start=2.), c(start=3.));
record threeValues
Real a, b, c;
annotation(
Icon(coordinateSystem(extent = {{-200, -200}, {200, 200}})),
Diagram(coordinateSystem(extent = {{-200, -200}, {200, 200}})));
end threeValues;
function add
input threeValues abc_in;
output threeValues abc_out;
external "C"
add_c(abc_in, abc_out) annotation(
IncludeDirectory = "modelica://structTest",
Include = "#include \"add_two.c\"");
annotation(
Icon(coordinateSystem(extent = {{-200, -200}, {200, 200}})),
Diagram(coordinateSystem(extent = {{-200, -200}, {200, 200}})));
end add;
algorithm
abc := add(abc);
annotation(
Icon(coordinateSystem(extent = {{-200, -200}, {200, 200}})),
Diagram(coordinateSystem(extent = {{-200, -200}, {200, 200}})));
end structTest;
With the C-Code:
typedef struct
{
double a, b, c;
}threeValues;
void add_c(void*, void*);
void add_c(void* in, void* out){
threeValues* in_typed = (threeValues*)in;
threeValues* out_typed = (threeValues*)out;
out_typed->a = in_typed->a + 2.;
out_typed->b = in_typed->b + 2.;
out_typed->c = in_typed->c + 2.;
}
The Simulation result (1s with 500 steps):
@SKittan, this result is expected. Remember that Modelica is a declarative equation-based modelling language, not an imperative language like C or Python. A continuous-time Modelica model (with no events) as the one you wrote is conceptually equivalent to a bunch of equations. Even algorithms in models are conceptually equivalent to equations. There is absolutely no concept of "time step" in there.
For continuous variables, as abc
is in your example, the specification mandates that each time the algorithm is executed, the left-hand-side variables are always initialized to their start values, precisely in order to avoid introducing unwanted memory effects. If you want memory, you need to use discrete variables and when-equations, see below.
This means that at each time step, abc
is set to {1,2,3}
, then each component is added a value of 2, so you always get the same result.
If you want to have some discrete-time dynamics, you have to describe it explicitly with discrete variables and when-equations, that are triggered at some event times, e.g.
model structTest
discrete threeValues abc(a(start=1.), b(start=2.), c(start=3.));
record threeValues
Real a, b, c;
annotation(
Icon(coordinateSystem(extent = {{-200, -200}, {200, 200}})),
Diagram(coordinateSystem(extent = {{-200, -200}, {200, 200}})));
end threeValues;
function add
input threeValues abc_in;
output threeValues abc_out;
external "C"
add_c(abc_in, abc_out) annotation(
IncludeDirectory = "modelica://structTest",
Include = "#include \"add_two.c\"");
annotation(
Icon(coordinateSystem(extent = {{-200, -200}, {200, 200}})),
Diagram(coordinateSystem(extent = {{-200, -200}, {200, 200}})));
end add;
algorithm
when sample(0,0.1) then
abc := add(abc);
end when;
annotation(
Icon(coordinateSystem(extent = {{-200, -200}, {200, 200}})),
Diagram(coordinateSystem(extent = {{-200, -200}, {200, 200}})));
end structTest;
Now abc
is a discrete variable, so the rules for algorithm semantics are different: each time the when clause is triggered, all LHS variables are initialized with their pre()
value, i.e. the value immediately before the event is processed. Every 0.1 seconds an event is triggered, and abc
is updated based on the previous value.
Of course events can also be triggered by other means than sample()
, any boolean condition can be put in the when statement.
I guess this was what you were looking for?
@casella Thank you for this explination. I think with this information I can adapt the test case to my real application.
If you need more help feel free to use this ticket. I closed it for our records, since there is nothing wrong with OMC as I understand.
Description
I've created a small example to test records with external "C" code. Therefor I create a simple record with three Real values in Modelica. The external C function is getting this Record and adding a two on each value.
Steps to Reproduce
Just run the attached example.
Code to reproduce:
Modelica Model:
C-File:
Expected Behavior
I would expect, that the initial values raise each time step by two. But the values in Modelica are nan (see attached Screenshot). Additionally I tracked the Pointer address of the c struct via print function. I can see, that this address is changing. I didn't expect this, since the Modelica Documentation says, that records are handed over by reference. Hence I would assume, that the record data is just created once and then held by Modelica (without any copies).
A side effect of the print function is, that the data displayed by Modelica is two for each value and time step. Is this an indicate that a new struct is created in every time step and initialised with 0?
Screenshots
Results with activated print
Version and OS