Passing C++ Handles in Simulink

3 views (last 30 days)
Taylor
Taylor on 15 Jan 2023
Answered: Taylor on 16 Jan 2023
Hi All,
I need a method to generate random numbers from within a Simulink foreach block for a set of simuated sensors. It is extremely cumbersome to seed each noise block individually, so I'm looking for a way to create a persistent random engine and pass a pointer to the user blocks. I originally created an s-function with the following class, registered he "get()" and "getWhite()" functions, and was calling them with the function caller block, but this method fails when I try to run the simulation in a compiled accelerator mode.
class RandomGenerator {
private:
unsigned long rand_seed;
std::default_random_engine engine;
std::uniform_real_distribution<double> distribution;
public:
double get();
double getWhite(double mu, double sigma);
RandomGenerator(unsigned long n);
};
Is there a clean s-function method that would allow me to pass a pointer to the get or getWhite function and subsequently call the function?
Any suggestions would be extremely helpful.
Thanks,
Taylor

Accepted Answer

Taylor
Taylor on 16 Jan 2023
Playing around with it a bit, I came up with a fairly messy way to imlement this by passing a pointer to the pwork vector in the model outputs function. It seems like there must be a safer solution in Simulink though...
#define S_FUNCTION_NAME sfun_RandomStream
#define S_FUNCTION_LEVEL 2
#include "simstruc.h"
#include <random>
#include "RandomNumberStream.h"
enum params {SEED_idx, NPARAMS};
enum outputs {STREAMPTR_idx, NOUT};
static int tmp = 0;
static void mdlInitializeSizes(SimStruct *S)
{
int_T status;
DTypeId id;
tmp = 0;
ssSetNumSFcnParams(S, params::NPARAMS); //seed
if (ssGetNumSFcnParams(S) != ssGetSFcnParamsCount(S)) {
return; /* Parameter mismatch will be reported by Simulink */
}
if (!ssSetNumInputPorts(S, 0)) return;
if (!ssSetNumOutputPorts(S,outputs::NOUT)) return;
ssSetOutputPortWidth(S, outputs::STREAMPTR_idx, 1);
id = ssRegisterDataType(S, "voidPtr");
if(id == INVALID_DTYPE_ID) return;
status = ssSetDataTypeSize(S, id, sizeof(voidPtr));
if(status == 0) return;
ssSetOutputPortDataType(S, outputs::STREAMPTR_idx, id);
ssSetNumSampleTimes(S, 1);
ssSetNumPWork(S,1);
ssSetOperatingPointCompliance(S, USE_DEFAULT_OPERATING_POINT);
ssSetOptions(S,
SS_OPTION_WORKS_WITH_CODE_REUSE |
SS_OPTION_EXCEPTION_FREE_CODE);
}
#define MDL_START
static void mdlStart(SimStruct *S)
{
double seed = (double) mxGetScalar(ssGetSFcnParam(S, SEED_idx));
//printf("SEED: %i\n", seed);
RandomGenerator *generator = new RandomGenerator(seed);
(ssGetPWork(S))[0] = (void *)(generator);
}
static void mdlInitializeSampleTimes(SimStruct *S)
{
ssSetSampleTime(S, 0, INHERITED_SAMPLE_TIME);
ssSetOffsetTime(S, 0, 0.0);
ssSetModelReferenceSampleTimeDefaultInheritance(S);
}
static void mdlOutputs(SimStruct *S, int_T tid)
{
voidPtr pwork = ssGetPWork(S)[0];
voidPtr *out = (voidPtr *)ssGetOutputPortSignal(S, 0);
//void *tmp = (void *) out[0];
out[0] = pwork;
// printf("randStream mdlOutputs address: %p\n", out[0]);
}
static void mdlTerminate(SimStruct *S)
{
delete ( RandomGenerator *)ssGetPWorkValue(S,0);
}
#ifdef MATLAB_MEX_FILE /* Is this file being compiled as a MEX-file? */
#include "simulink.c" /* MEX-file interface mechanism */
#else
#include "cg_sfun.h" /* Code generation registration function */
#endif
%implements "sfun_RandomStream" "C"
%function BlockTypeSetup(block, system) void
%% The Target Language must be C
%if ::GenCPP!=1 && !IsModelReferenceSimTarget()
%<LibReportFatalError("Random Number Generator must be implemented in c++.")>
%endif
%<LibAddToCommonIncludes("RandomNumberStream.h")>
%endfunction %% BlockTypeSetup
%function Start(block, system) Output
/* %<Type> Block: %<Name> */
%assign seed = LibBlockParameterValue(block.Parameter[0], 0)
RandomGenerator *stream = new RandomGenerator((double) %<seed>);
%<LibBlockPWork(0, "", "", 0)> = stream;
%endfunction
%function Outputs(block, system) Output
/* %<Type> Block: %<Name> */
%assign pwork = LibBlockPWork(0, "", "", 0)
%assign out = LibBlockOutputSignal(0, "", "", 0)
%<out> = %<pwork>;
%endfunction
%function Terminate(block, system) Output
%switch SFunctionType
%case "TLC"
/* %<Type> Block: %<Name> */
%assign u = LibBlockPWork(0, "", "", 0)
RandomGenerator *stream = (RandomGenerator *)%<u>;
delete stream;
%break
%endswitch
%endfunction
#define S_FUNCTION_NAME sfun_NormalDist
#define S_FUNCTION_LEVEL 2
#include "simstruc.h"
#include <random>
#include "RandomNumberStream.h"
enum params {TS_idx, SIGMA_idx, MU_idx, NPARAMS};
enum outputs {VAL_idx, NOUT};
enum inputs {STREAM_idx, NINP};
static void mdlInitializeSizes(SimStruct *S)
{
int_T status;
DTypeId id;
ssSetNumSFcnParams(S, params::NPARAMS); /* Number of expected parameters */
if (ssGetNumSFcnParams(S) != ssGetSFcnParamsCount(S)) {return; }
ssSetNumContStates(S, 0);
ssSetNumDiscStates(S, 0);
if (!ssSetNumInputPorts(S, inputs::NINP)) return;
ssSetInputPortWidth(S, inputs::STREAM_idx, 1);
//DTypeId id = ssGetDataTypeId(S, "voidPtr");
id = ssRegisterDataType(S, "voidPtr");
if(id == INVALID_DTYPE_ID) return;
status = ssSetDataTypeSize(S, id, sizeof(voidPtr));
if(status == 0) return;
if(id != ssSetInputPortDataType(S, inputs::STREAM_idx, id)) return;
ssSetInputPortDirectFeedThrough(S, inputs::STREAM_idx, 1);
if (!ssSetNumOutputPorts(S, outputs::NOUT)) return;
ssSetOutputPortWidth(S, outputs::VAL_idx, 1);
ssSetNumSampleTimes(S, 1);
ssSetNumRWork(S, 0);
ssSetNumIWork(S, 0);
ssSetNumPWork(S, 0);
ssSetNumModes(S, 0);
ssSetNumNonsampledZCs(S, 0);
ssSetOperatingPointCompliance(S, USE_DEFAULT_OPERATING_POINT);
//ssSetRuntimeThreadSafetyCompliance(S, RUNTIME_THREAD_SAFETY_COMPLIANCE_TRUE);
//ssSetOptions(S, SS_OPTION_EXCEPTION_FREE_CODE);
ssSupportsMultipleExecInstances(S, true);
}
static void mdlInitializeSampleTimes(SimStruct *S)
{
double ts = (double) mxGetScalar(ssGetSFcnParam(S, TS_idx));
ssSetSampleTime(S, 0, ts);
ssSetOffsetTime(S, 0, 0.0);
}
static void mdlOutputs(SimStruct *S, int_T tid)
{
voidPtr **inPtr = ((voidPtr**) ssGetInputPortSignal(S, inputs::STREAM_idx));
if(inPtr == NULL)
return;
voidPtr in = inPtr[0][0];
RandomGenerator *engine = static_cast<RandomGenerator*>(in);
if(engine == NULL) //deal with sample time issues...
return;
double sigma = (double) mxGetScalar(ssGetSFcnParam(S, SIGMA_idx));
double mu = (double) mxGetScalar(ssGetSFcnParam(S, MU_idx));
std::normal_distribution<double> distribution(mu, sigma);
double val = distribution(engine->getEngine());
double *out = (double*) ssGetOutputPortSignal(S, outputs::VAL_idx);
out[0] = val;
}
static void mdlTerminate(SimStruct *S)
{
}
/*=============================*
* Required S-function trailer *
*=============================*/
#ifdef MATLAB_MEX_FILE /* Is this file being compiled as a MEX-file? */
#include "simulink.c" /* MEX-file interface mechanism */
#else
#include "cg_sfun.h" /* Code generation registration function */
#endif
%implements "sfun_NormalDist" "C"
%function BlockTypeSetup(block, system) void
%% The Target Language must be C
%if ::GenCPP!=1 && !IsModelReferenceSimTarget()
%<LibReportFatalError("Random Number Generator must be implemented in c++.")>
%endif
%<LibAddToCommonIncludes("RandomNumberStream.h")>
%endfunction %% BlockTypeSetup
%function Outputs(block, system) Output
/* %<Type> Block: %<Name> */
%assign pu = LibBlockInputSignalAddr(0, "", "", 0)
%assign y = LibBlockOutputSignal(0, "", "", 0)
RandomGenerator *engine = (RandomGenerator *)(*%<pu>);
%assign sigma = LibBlockParameterValue(block.Parameter[1], 0)
%assign mu = LibBlockParameterValue(block.Parameter[2], 0)
std::normal_distribution<double> distribution((double) %<mu>, (double) %<sigma>);
double val = distribution(engine->getEngine());
%<y> = val;
%endfunction

More Answers (0)

Categories

Find more on Prepare Model Inputs and Outputs in Help Center and File Exchange

Products


Release

R2021b

Community Treasure Hunt

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

Start Hunting!