FhSim  3.1.0
Marine systems simulation
Loading...
Searching...
No Matches
SimObject methods

A SimObject can implement a range of methods. Some of these are optional, some are required. Their calling sequence is given in the figure below.

The FhSim call sequence.

Constructor

 SimObject(const string& simObjectName);
Parameters
[in]simObjectName-> The name of the simobject

The constructor performs all initial setup for a SimObject. It is responsible for reading the parameters belonging to the SimObject from the input file and defining the interface of the SimObject, in terms of:

  • Input ports
  • Output ports
  • States It should also perform any additional 'one time only' resource setup.

A list of methods used in the constructor is found in SimObject construction methods.

InitialConditionSetup

 void InitialConditionSetup(const double T, const double* const currentIC, double* const updatedIC, ISimObjectCreator* const creator);
Parameters
[in]TThe time at the beginning of the simulation. Usually but not necessarily zero
[in]currentICArray of initial conditions. If the value is QNAN, the value is not set yet and can be set by the SimObject, otherwise the value is set, and can not be changed. Trying to change an already set value will trigger an error
[out]updatedICArray of ONAN. Any initial condition that are ready to be set, should be written to this array.
[in]creatorPointer to creator class

Method for SimObjects that wants to set it's own initial conditions based on the value of it's input ports. SimObjects that wants to use this feature overrides the method, and it will be called if the SimObject has unspecified initial conditions after the values from the SimObject constructor, and the input file are set.

The following service contract applies to SimObjects that wishes to override this method:

  • All or some of the input ports may not be ready yet, for instance if other SimObjects also has unfinished states. In the case where a port is not yet ready, it will return an array of QNAN. In the case where a state is not ready it will contain a QNAN. The method should detect this and act appropriately. See also the is_qNaN function in SimObject
  • The method are expected to set as many states as it can based on which input port are ready or not.
  • If due to unfinished states, the SimObject itself has output ports that are not yet ready to return a legal value, these ports must be reported as unfinished. Note that if an output port is dependent on an unfinished input port, this in itself does not warrant a report. Only dependence on unfinished states are grounds for report.
  • If, after the method returns, the SimObject were unable to set all of it's states, the method will be called again until all states are set.
  • The solution is always growing. This entails that states that are once set, cannot be unset or changed. Input ports that were once ready, will continue to be ready. Output ports that during previous calls were not reported as unfinished are assumed ready and cannot be reported as unfinished later.
  • In all these matters the SimObject are expected to tell the truth, the whole truth, and nothing but the truth.
  • Some or all states that were suggested by the SimObject in the constructor, may have been overwritten by values from input files or other.
  • The SimObject may assume that multi element states and input ports are either set completely, or not at all. This means that if one element is QNAN the rest of the elements will be QNAN, and if one element is set all elements will be set. The exceptions from this rule are when the SimObject itself, either in the constructor or the InitialConditionSetup- method, specified the state with incomplete elements. In this case the SimObject are expected to know about it and take the proper precautions.

FinalSetup

 virtual void FinalSetup(double T, const double* X, ISimObjectCreator* creator);
Parameters
[in]T-> Current simulation time
[in]X-> Current simulation state
[in]creator-> Retrieve shared resources

Make final preparations before simulation starts. Mainly for retrieving shared resources registered during construction phase. Note: Called before RenderInit. Simobjects that wish to exchange shared resources through GetSharedResource/SetSharedResource may not retrieve said resources inside the constructor due to unpredictable order of construction.

RenderInit

 void RenderInit(Ogre::Root* ogreRoot, ISimObjectCreator* creator);
Parameters
[in]ogreRoot-> Register visualization resources
[in]creator-> Retrieve parameters.

Sets up visualization resources.

PreOdeFcn

 void PreOdeFcn(double T, const double* X, IStateUpdater* updater);
Parameters
[in]T-> Current simulation time
[in]X-> Current simulation state
[in]updater-> Callback class for updating states

Pre ode-function pass for handling discontinuous/discrete states. Called once before the start of every major timestep.

OdeFcn

 void OdeFcn(double T, const double* X, double* XDot) const = 0;
Parameters
[in]T-> Current simulation time
[in]X-> Current simulation state
[out]XDot-> State derivatives

This method is responsible for calculating the derivative of the states of the SimObject as a function of time, states and input ports.

OdeFcn is const and should contain derivative computations only. Use AcceptedStep() for once-per-accepted-step side effects.

AcceptedStep

 void AcceptedStep(double T, const double* X);
Parameters
[in]T-> Accepted simulation time
[in]X-> Accepted simulation state

Called once after each accepted integration step.

Typical uses:

  • Once-per-step bookkeeping
  • Event/logging hooks
  • Side effects that previously lived in if (isMajorTimeStep) branches

Default implementation is empty.

HasJacobians

 bool HasJacobians() const;
Returns
true if this SimObject provides an analytical Jacobian via OdeJacobian()

Returns whether this SimObject can provide analytical Jacobian information. The default implementation returns false. Override this method to return true if you have implemented OdeJacobian().

This method is used by implicit integration methods (e.g., SUNDIALS CVODES with BDF, ARKODE with DIRK, or the Engine's Euler1imp method) to determine whether to use analytical or numerically approximated Jacobians. Providing analytical Jacobians improves both performance and numerical stability, especially for stiff systems.

Note
If any SimObject with states returns false from HasJacobians(), the simulation will fall back to hybrid or numerical Jacobian approximation depending on the <Jacobian Policy> setting. See Implicit Integration with Analytical Jacobians for details.
See also
OdeJacobian(), GetJacobianSparsity(), HasPortJacobians(), Implicit Integration with Analytical Jacobians

OdeJacobian

 void OdeJacobian(double T, const double* X, double* J, int nStates);
Parameters
[in]T-> Current simulation time
[in]X-> Current simulation state
[out]J-> Jacobian matrix stored in row-major order (nStates x nStates)
[in]nStates-> Number of states (matrix dimension)

Computes the analytical Jacobian matrix J = dF/dX at the given time and state, where F is the right-hand side function defined by OdeFcn().

The Jacobian matrix is stored in row-major order, meaning element J[i][j] (the partial derivative of state derivative i with respect to state j) is stored at index i * nStates + j in the array.

This method is only called if HasJacobians() returns true. The default implementation does nothing.

Example

For a 2-state system with:

dX[0]/dt = -2*X[0] + X[1]
dX[1]/dt = X[0] - 3*X[1]

The Jacobian is constant:

J = [ -2 1 ]
[ 1 -3 ]

Implementation:

void MyObject::OdeJacobian(double T, const double* X, double* J, int nStates)
{
J[0] = -2.0; // dF[0]/dX[0]
J[1] = 1.0; // dF[0]/dX[1]
J[2] = 1.0; // dF[1]/dX[0]
J[3] = -3.0; // dF[1]/dX[1]
}
See also
HasJacobians(), GetJacobianSparsity(), Implicit Integration with Analytical Jacobians

GetJacobianSparsity

 int GetJacobianSparsity(int nStates, int* rowPtr, int* colIdx);
Parameters
[in]nStates-> Number of states
[out]rowPtr-> Row pointer array (size nStates+1), may be nullptr for query
[out]colIdx-> Column index array, may be nullptr for query
Returns
Number of non-zero elements, or -1 if the Jacobian is dense

Returns the sparsity pattern of the Jacobian matrix in Compressed Sparse Row (CSR) format. This is used by SUNDIALS to construct sparse Jacobian matrices for large systems, reducing memory usage and computation time.

The CSR format consists of two arrays:

  • rowPtr[i] gives the starting index in colIdx for row i
  • colIdx[p] gives the column index of the p-th non-zero element

Return -1 to indicate a dense Jacobian (the default). For small systems (typically fewer than ~50 states), dense Jacobians are usually more efficient than sparse ones.

Query Pattern

The method may be called with rowPtr = nullptr and colIdx = nullptr to query the number of non-zeros. In this case, only the return value is used.

Example

For a 2x2 dense Jacobian (all 4 elements non-zero):

int MyObject::GetJacobianSparsity(int nStates, int* rowPtr, int* colIdx)
{
if (nStates != 2) return -1;
if (rowPtr != nullptr && colIdx != nullptr) {
rowPtr[0] = 0; rowPtr[1] = 2; rowPtr[2] = 4;
colIdx[0] = 0; colIdx[1] = 1;
colIdx[2] = 0; colIdx[3] = 1;
}
return 4;
}

For a tridiagonal 3x3 Jacobian (7 non-zeros):

int MyObject::GetJacobianSparsity(int nStates, int* rowPtr, int* colIdx)
{
if (rowPtr != nullptr && colIdx != nullptr) {
rowPtr[0] = 0;
rowPtr[1] = 2; // row 0: cols 0,1
rowPtr[2] = 4; // row 1: cols 0,1,2 -> but col 0 already counted
rowPtr[3] = 7; // row 2: cols 1,2
colIdx[0] = 0; colIdx[1] = 1;
colIdx[2] = 0; colIdx[3] = 1; colIdx[4] = 2;
colIdx[5] = 1; colIdx[6] = 2;
}
return 7;
}
Note
For most SimObjects, returning -1 (dense) is sufficient. Sparse Jacobians are primarily beneficial for large systems with many states.
See also
HasJacobians(), OdeJacobian(), Implicit Integration with Analytical Jacobians

HasPortJacobians

 bool HasPortJacobians() const;
Returns
true if this SimObject provides analytical port Jacobians via OutputPortJacobian() and InputPortJacobian()

Returns whether this SimObject can provide analytical Jacobian information for its input and output ports. The default implementation returns false. Override this method to return true if you have implemented both OutputPortJacobian() and InputPortJacobian().

This method is used during Jacobian assembly to determine whether off-diagonal blocks (from port connections between stateful SimObjects) can be computed analytically via the chain rule, or must be approximated numerically.

Note
Both OutputPortJacobian() and InputPortJacobian() must be implemented for this to return true. If only one is implemented, the port connection will fall back to numerical approximation.
See also
OutputPortJacobian(), InputPortJacobian(), GetPortDependencyPolicy(), Cross-Object Port Jacobians

PortDependencyPolicy

 enum class PortDependencyPolicy { All, UserSpecified };

Policy for determining which local states an output port depends on. Used to build structural sparsity patterns for off-diagonal Jacobian blocks.

Value Description
All Conservative: assumes the port depends on all local states. Safe but may result in more numerical perturbations than necessary.
UserSpecified The SimObject author provides exact dependency information via GetPortStateDependency().

The default policy is All. Override GetPortDependencyPolicy() to return UserSpecified and implement GetPortStateDependency() for tighter sparsity.

See also
GetPortDependencyPolicy(), GetPortStateDependency(), Structural Sparsity & Port Dependencies

GetPortDependencyPolicy

 PortDependencyPolicy GetPortDependencyPolicy() const;
Returns
The port dependency policy for this SimObject

Returns the policy used to determine which local states each output port depends on. The default implementation returns All (conservative). Override to return UserSpecified if you implement GetPortStateDependency().

See also
PortDependencyPolicy, GetPortStateDependency(), Structural Sparsity & Port Dependencies

GetPortStateDependency

 std::vector<int> GetPortStateDependency(const std::string& portName) const;
Parameters
[in]portName-> Name of the output port
Returns
Vector of local state indices this port depends on

Returns the list of local state indices that a specific output port depends on. Only called when GetPortDependencyPolicy() returns UserSpecified.

Return an empty vector to fall back to "all states" for that port.

Example

std::vector<int> MyObject::GetPortStateDependency(const std::string& portName) const override
{
if (portName == "force") {
return {0, 1}; // Force output depends only on states 0 and 1
}
if (portName == "velocity") {
return {2, 3, 4}; // Velocity depends on states 2, 3, 4
}
return {}; // Unknown port: fall back to all states
}
See also
GetPortDependencyPolicy(), Structural Sparsity & Port Dependencies

OutputPortJacobian

 void OutputPortJacobian(const std::string& portName, double T, const double* X,
                         double* dPort_dX, int portSize, int nStates, int index = -1);
Parameters
[in]portName-> Name of the output port
[in]T-> Current simulation time
[in]X-> Current simulation state (local to this SimObject)
[out]dPort_dX-> Jacobian matrix (row-major, portSize x nStates)
[in]portSize-> Size of the port signal
[in]nStates-> Number of local states
[in]index-> Port index for indexed ports, -1 for standard ports

Computes the Jacobian of an output port with respect to the SimObject's local states: \( \frac{\partial (\text{port\_value})}{\partial X_{local}} \).

The result is stored in row-major order with dimensions [portSize x nStates]. Element (i, j) at index i * nStates + j represents the partial derivative of the i-th port element with respect to the j-th local state.

Note
If the port output depends on CommonComputations, ensure they are executed before calling this method. The caller is responsible for invoking CommonComputations beforehand.
This method provides the C matrix (d(output)/dX) used in directional derivative computation. In forward mode, C is applied directly. In adjoint mode, its transpose Cᵀ is used to propagate sensitivities backwards.

Example

For a 2-state system with a scalar output port force = 3*X[0] + 2*X[1]:

void MyObject::OutputPortJacobian(const std::string& portName, double T,
const double* X, double* dPort_dX, int portSize, int nStates, int index)
{
if (portName == "force") {
dPort_dX[0] = 3.0; // d(force)/dX[0]
dPort_dX[1] = 2.0; // d(force)/dX[1]
}
}
See also
HasPortJacobians(), InputPortJacobian(), Cross-Object Port Jacobians

InputPortJacobian

 void InputPortJacobian(const std::string& portName, double T, const double* X,
                      double* dF_dInput, int portSize, int nStates);
Parameters
[in]portName-> Name of the input port
[in]T-> Current simulation time
[in]X-> Current simulation state (local to this SimObject)
[out]dF_dInput-> Sensitivity matrix (row-major, nStates x portSize)
[in]portSize-> Size of the port signal
[in]nStates-> Number of local states

Computes the sensitivity of the SimObject's ODE derivatives with respect to an input port value: \( \frac{\partial F}{\partial (\text{input\_port})} \).

The result is stored in row-major order with dimensions [nStates x portSize]. Element (i, j) at index i * portSize + j represents the partial derivative of the i-th state derivative with respect to the j-th input port element.

Example

For a 2-state system with a scalar input port u:

dX[0]/dt = ... + 5*u
dX[1]/dt = ... - 1*u
void MyObject::InputPortJacobian(const std::string& portName, double T,
const double* X, double* dF_dInput, int portSize, int nStates)
{
if (portName == "force") {
dF_dInput[0] = 5.0; // dF[0]/du
dF_dInput[1] = -1.0; // dF[1]/du
}
}
See also
HasPortJacobians(), OutputPortJacobian(), Cross-Object Port Jacobians
Note
This method provides the B matrix (dF/d(input)) used in directional derivative computation. In forward mode, B is applied to input perturbations. In adjoint mode, its transpose Bᵀ is used to map adjoint sensitivities to inputs.

RenderUpdate

 void RenderUpdate(double T, const double* X);
Parameters
[in]T-> Current simulation time
[in]X-> Current simulation state

Updates visualization resources to reflect current state