|
|
Native external function examples: Dynamic positioning system |
This topic is intended to be read in conjunction with the main external function documentation, and the example source code.
This ZIP file contains all of the native external function examples, with each example contained in the appropriately named sub-folder.
This dynamic positioning (DP) example is a very simple external function DLL used to apply a force and moment to a vessel. The force and moment act to keep the vessel on a specified target position and heading.
The force and moment returned by the external function to OrcaFlex are directly proportional to the difference between the target position and the actual instantaneous position obtained from OrcaFlex. In other words the effect of the applied loads is to apply simple linear translational and rotational springs.
Linear restoring loads are not actually very good at keeping the vessel on target, but the purpose of the example is to demonstrate the framework of calls required to use an external function to apply a force and moment that is some function of vessel position and other values. A more realistic model would perhaps be an external function DLL that was used to apply multiple thruster applied loads to a vessel, where the thruster loads depended on both the position and velocity of the vessel and were determined by a sophisticated control system algorithm.
The OrcaFlex data file for this example is DynamicPositioningSystem.dat. In this model the 'DP Vessel' (yellow) is the vessel to which the externally-calculated DP loads are applied. The Target Position (red) vessel is a fixed vessel whose purpose is to mark the target position that has been specified for the 'DP Vessel'. The 'no DP Vessel' (green) is identical to the 'DP Vessel' but with the DP system not enabled, showing how the vessel would have drifted due to wave, current and wind effects.
On the Applied loads page of the DP Vessel data form, a global applied load is specified. This load uses the external function variable data item called Thruster to calculate the components of applied force in the global X and Y directions, and the applied moment about the global Z direction.
Notice that the same external function variable data item (Thruster) is specified for the X and Y applied force components, and also for the Z component of applied moment. You might therefore expect that all the return values for these load components slots would be the same, but that is not the case, since the external function receives the name (and row number) of the applied load component when it is called, so it can return the appropriate load component. This will be explained in the Calculate section below.
The configuration data for the external function (the target position and heading, and the stiffness constants) are specified in the object tags for the DP Vessel. During the initialisation phase the numerical values are extracted. The following parameters are used, each of which is a floating point value:
TargetX
TargetY
TargetHeading
kf
km
The parameter values must be specified in the same units as those used by the OrcaFlex model, and they are used as follows:
TargetX and TargetY are the global coordinates of the point where you want the DP system to position the vessels origin.TargetHeading is the target vessel heading in degrees.kf is the force constant. The force applied in the X or Y direction is equal to the difference between the target and actual coordinates multiplied by kf.km is the moment constant. The moment applied around the vessels Z axis is equal to the difference between the target and actual headings multiplied by km.Multiple instances of this external function are created for the different load components applied to the vessel. The configuration input from the model is shared data that should be available to all instances, and we only want to expend the effort to obtain this data once.
In order to achieve the sharing of data, we use the CallerLong value that OrcaFlex objects make available. Once the data is initialised, its location in memory can be stored in CallerLong for the vessel. Other external function instances on the vessel can retrieve the data from this pointer.
The sharing mechanism also allows us to detect whether the shared ThrusterData structure has already been initialised. The Initialise routine will be called once for each data item in the OrcaFlex model for which the Thruster external function is specified, but we only want to initialise ThrusterData once. So the Initialise routine first asks for the caller's CallerLong. If the CallerLong is NULL then this must be the first call to Initialise, so the routine allocates ThrusterData, sets the data values in it, and then stores a pointer to it in CallerLong. If it is not NULL then this means that it has already been initialised and its address has now been obtained.
The ThrusterData structure contains the target position and applied stiffness constants. These are read from the object tags during this initialisation. It also contains the applied load results, which are filled in during the Calculate phase.
It is important to note that the shared data has no dependence on the history of the simulation. The configuration data is constant, and the applied load results can be calculated from instantaneous values at each time step. This means that the dynamic positioning calculations can be re-started for a resumed simulation or for an extension of dynamics by making a new call to the initialisation action.
If the data used in calculation was dependent on historical values, then we would refer to this external function as having state. The management of state for the benefit of partially completed simulations requires that current external function state be stored and retrieved using the OrcaFlex simulation file. This is possible, and the PID controller example demonstrates this behaviour.
Finalisation is called for each external function instance, so again we need to make sure that we only delete the shared ThrusterData once. The further calls to Finalise for the other applied load components will find that ThrusterData is already NULL, and so nothing further is needed.
When Calculate is called, we first get the shared ThrusterData structure.
This example uses a single external function to calculate more than one applied load component. We refer to this as the externally-calculated data items being coupled, and we can only assume certain things about the order in which the function will be called – see External Function Calling Order.
For externally-calculated applied load the calling order rules mean that the X-component will always be asked for first. We can calculate all the applied load components together when the X-component is asked for, and we don't need to repeat the calculation when the other components are asked for. We therefore check if it's the X-component being asked for, and if so calculate all the components of the applied load and store them in the shared ThrusterData structure.
Now that we have all the thruster values, we need to return the correct value depending on which component of the applied load this call to the external function is for. This can be found by looking at the info.lpDataName parameter, since this contains the table column name and row index of the calling external function instance, i.e. GlobalAppliedForceX or GlobalAppliedMomentZ, etc. The first part of the name is defined as a constant in the OrcFxAPI.h file and the number in brackets is the row number in the table. In this simple example we are only using one row so we ignore the row part of the name.
The load components calculated in our external function are registered as user defined results to be available from any model object that uses this external function. Function RegisterResult is provided in the Utils source files.
For results registration, you must define an ID number for each result. Those results also need names and units that appear in the OrcaFlex user interface. Here we are making use of special units strings such as FF, which are automatically converted to the units of the OrcaFlex model. Duplicate attempts to register the same result are benign, and are ignored.
In log result, the log data pointer within info is directed to the existing memory within the shared data. No new memory is assigned for results logging in this example. The shared data is freed in the finalise action, as noted above.
Results are also made available from our external stress result example. In that code, the allocation of memory occurs within the log result actions. If results are of particular interest, it might be useful to also refer to that example.
Based on the requested result ID, the correct load component is retrieved from the log data pointer, and returned to OrcaFlex. Note that info.Value is used for the returning result, just as it is used for return from actual calculation.