|
FhSim
3.1.0
Marine systems simulation
|
Conductivity, temperature and depth instrument mounted on a moving body.
Collaboration diagram for CTD sensor:A CTD reports the temperature, the salinity and the depth of the water at the point where its sensor head actually is, which on an undulating survey AUV is neither the vehicle origin nor a fixed depth. This class queries the two named scalar fields of the shared Environment resource at the mounted sensor position, passes each channel through a first-order instrument lag, and samples the result at a fixed rate with a bias, Gaussian noise and an optional quantisation.
It is the smallest complete example of every pattern the other AUV sensors use: a SensorFrame for the mounting geometry, a SampleClock for the fixed rate, a SensorNoise for portable random draws, field handles resolved once in FinalSetup, a const OdeFcn that does nothing but write derivatives, and all the imperfection in AcceptedStep.
The three lags are integrator states, not a filter advanced in AcceptedStep. A real CTD's thermistor and conductivity cell have genuine continuous-time thermal and flushing time constants, and on a vehicle diving at 0.4 m/s a 0.07 s lag is a real 3 cm offset in where a reading belongs. Making them states means the integrator resolves them with the same error control as the vehicle dynamics, and it makes this a genuine dynamic SimObject rather than a sampler.
\[ \dot{x}_T = \frac{T_{field}(p_{sensor}) - x_T}{\texttt{TemperatureTimeConstant}} \]
and likewise for salinity and for depth. A time constant of zero means no lag at all: the derivative is then zero, the state is frozen at its initial value and the sampled value is taken straight from the field. That is the configuration to use when the point of the run is to read the field exactly.
The states are set in InitialConditionSetup to the field values at the start position, so a run does not open with a spurious transient from zero. They are only set if the input file did not set them, so an explicit <InitialCondition Ctd.LagT="..."/> still wins.
Every SamplePeriod, in AcceptedStep only:
and the result is held on the output ports until the next sample. Nothing random and nothing held is touched in OdeFcn, which is const and is re-entered at rejected trial states: a sensor that drew a random number there would give results that depended on which steps the integrator threw away.
NoiseSeed = 0 means deterministic and noise free: no bias is added either, no random number is drawn, and the output is the exact lagged field value. That is both an exact regression baseline and the "perfect sensor" to debug a model against. See SensorNoise.
<FileOutput> interpolates linearly between its output times, so a logged trace of a held output shows ramps between samples rather than steps. Log at TOutput equal to SamplePeriod if the staircase matters. No state is added to fake a zero-order hold.StepMax. SampleClock warns once if a sample is taken more than a quarter of a period late.Depth is the NED z coordinate of the sensor head, that is the depth below the mean water level; the wave surface elevation is not subtracted, because a real CTD measures pressure and reports depth against a fixed datum in exactly the same way.
Pressure is a nominal conversion, p = 1.0055 z dbar, which is the usual rule of thumb for temperate sea water at mid latitudes. It is offered for convenience and for logging against instruments that report pressure; it is not a full UNESCO/TEOS-10 depth-pressure relation, and it carries no latitude or density-profile dependence. Do not use it where a fraction of a decibar matters.
There are deliberately no TimeDelay, LossProbability or OutlierProbability parameters here. fhsim_base's Signal/Samplifier already models those well, and the Measurement outport exists to be chained into one:
Measurement is [Salinity, Temperature, Depth], in that order, so one three-element telemetry channel carries the whole instrument. The order is the conventional CTD ordering and not the port ordering below; it is written out on the port so that nobody has to guess.
fhsim_base is not in this package's Conan closure, see 0028 — Input files require the `base` SimObject library, which the package does not depend on, so the excerpt above is documentation rather than a fixture in this repository.The two scalar field handles are resolved once, in FinalSetup, with EnvironmentProvider::FindScalarField, and every step then calls GetScalarValue(handle, ...). That is the documented idiom and it keeps string hashing out of the step path. A field name that is not registered is a fatal error at FinalSetup: a CTD wired to a temperature field that does not exist is a configuration mistake, and silently reporting 0 degC is worse than refusing to run.
EnvironmentProvider::GetScalarValue is not const, and it is reached from the const OdeFcn through the provider pointer. That is safe only because a ScalarField is pure; the @note on ScalarField::GetValue says so.| Name | Width | Description @addoption{Pos, 3, The carrier body origin in NED, m. Held by the SensorFrame. } @addoption{Quater, 4, The carrier body attitude, scalar first [q0 q1 q2 q3]. Held by the SensorFrame. } |
|---|
| Name | Width | Description @addoption{Temperature, 1, The measured water temperature, degC. Held between samples. } @addoption{Salinity, 1, The measured salinity, PSU. Held between samples. } @addoption{Depth, 1, The measured depth of the sensor head below the mean water level, m. Held between samples. } @addoption{Pressure, 1, The measured pressure, dbar, from the nominal conversion 1.0055 times the measured depth. } @addoption{Measurement, 3, The three measurements as one vector in the conventional CTD order [Salinity Temperature Depth], for logging and for chaining into a Signal/Samplifier. } @addoption{Position, 3, The NED position at which the held sample was actually taken, m. Post-processing a survey needs this and cannot recover it from the vehicle track alone. } |
|---|
| Name | Width | Description |
|---|---|---|
| TemperatureFieldName | 1 | The name of the registered scalar field to read the temperature from. Default SeaTemperature. A name that is not registered is a fatal error. |
| SalinityFieldName | 1 | The name of the registered scalar field to read the salinity from. Default SeaSalinity. A name that is not registered is a fatal error. @addoption{MountOffset, 3, The sensor head position in the body frame, m. Default 0 0 0. } @addoption{MountDirection, 3, The beam direction in the body frame. A CTD has no beam, so this is unused and is only read because the SensorFrame is shared with the sensors that do have one. } @addoption{SamplePeriod, 1, The time between samples, s. Default 0.0625, that is 16 Hz, the rate of an SBE49 FastCAT. } @addoption{TemperatureTimeConstant, 1, The thermistor time constant, s. Default 0.07. Zero means no lag and the field is read directly. } @addoption{ConductivityTimeConstant, 1, The conductivity cell time constant, s. Default 0.06. Zero means no lag. } @addoption{DepthTimeConstant, 1, The pressure sensor time constant, s. Default 0.02. Zero means no lag. } @addoption{TemperatureBias, 1, A constant offset added at every sample, degC. Default 0.002. } @addoption{SalinityBias, 1, A constant offset added at every sample, PSU. Default 0.005. } @addoption{DepthBias, 1, A constant offset added at every sample, m. Default 0.05. } @addoption{TemperatureNoiseStd, 1, The standard deviation of the Gaussian noise added at every sample, degC. Default 0.0005. } @addoption{SalinityNoiseStd, 1, The standard deviation of the Gaussian noise added at every sample, PSU. Default 0.002. } @addoption{DepthNoiseStd, 1, The standard deviation of the Gaussian noise added at every sample, m. Default 0.02. } @addoption{TemperatureResolution, 1, The quantisation step of the reported temperature, degC. Default 0, which means no quantisation. } @addoption{SalinityResolution, 1, The quantisation step of the reported salinity, PSU. Default 0, which means no quantisation. } @addoption{DepthResolution, 1, The quantisation step of the reported depth, m. Default 0, which means no quantisation. } @addoption{NoiseSeed, 1, The random seed. Default 0, which means deterministic and noise free, with no bias either. Any other value seeds the generator and enables the bias, the noise and the quantisation. } |
| Name | Width | Description @addoption{LagT, 1, The lagged temperature, degC. Defaults to the temperature field at the start position. } @addoption{LagS, 1, The lagged salinity, PSU. Defaults to the salinity field at the start position. } @addoption{LagD, 1, The lagged depth, m. Defaults to the depth of the sensor head at the start position. } |
|---|
This SimObject is referred to as Auv/CTD