|
FhSim
3.1.0
Marine systems simulation
|
#include <EnvironmentFacade.h>
Inheritance diagram for marenv::EnvironmentFacade:
Collaboration diagram for marenv::EnvironmentFacade:Public Member Functions | |
| EnvironmentFacade () | |
| void | SetWaves (std::shared_ptr< wave::WaveField > wave) |
| void | SetWaves (const wave::WaveEnergySpectrum::SpectrumParameters ¶ms, wave::WaveTheory waveTheory, int numWaves, uint32_t seed) |
| void | SetBathymetry (std::shared_ptr< seafloor::BathymetryField > bathymetry) |
| std::shared_ptr< wave::WaveField > | GetWaves () |
| double | MaxWaveElevation () const |
| std::shared_ptr< seafloor::BathymetryField > | GetBathymetry () |
| void | AddCurrentField (const std::string &id, std::shared_ptr< current::CurrentField > field) |
| void | RemoveCurrentField (const std::string &id) |
| bool | HasCurrentField (const std::string &id) const |
| std::shared_ptr< current::CurrentField > | GetCurrentField (const std::string &id) const |
| void | AddWakeField (const std::string &id, std::shared_ptr< wake::WakeField > field) |
| void | RemoveWakeField (const std::string &id) |
| bool | HasWakeField (const std::string &id) const |
| std::shared_ptr< wake::WakeField > | GetWakeField (const std::string &id) const |
| void | AddJetField (const std::string &id, std::shared_ptr< wake::JetField > field) |
| std::shared_ptr< wake::JetField > | GetJetField (const std::string &id) const |
| std::vector< std::string > | GetWakeFieldIds () const |
| std::vector< std::string > | GetJetFieldIds () const |
| std::vector< std::string > | GetCurrentFieldIds () const |
| Status | GetSubmergence (double time, const double pos[3], double &submergence) override |
| Status | GetSurfaceElevation (double time, const double pos[2], double &elevationOut) override |
| Status | GetSurfaceUnitNormal (double time, const double pos[2], double *unitNormalOut) override |
| Status | GetParticleVelocity (double time, const double pos[3], double *velOut) override |
| Status | GetParticleAcceleration (double time, const double pos[3], double *accOut) override |
| Status | GetDynamicPressure (double time, const double pos[3], double &pressureOut) override |
| Status | GetTotalPressure (double time, const double pos[3], double &pressureOut) override |
| Status | GetSeaDepth (const double pos[2], double &depthOut) override |
| Status | ComputeRayIntersection (const double origin[3], const double direction[3], double &distanceOut, double maxSearchDistance=100, double startSearchDistance=-1, double stepSize=1.0, double convergenceTolerance=0.01) override |
| Status | GetCurrentVelocity (double time, const double pos[3], double *velOut) override |
| Status | GetDensity (double time, const double pos[3], double &density) override |
| Status | PointEnvironmentQuery (double time, const double pos[3], EnvironmentQuery &queryResult) override |
| Status | GetWaterVelocity (double time, const double pos[3], WaterVelocityTerms terms, double velOut[3]) const |
| Status | GetWaterVelocity (double time, const double pos[3], const WaterVelocityParts &parts, double velOut[3]) const |
| Status | GetWaterVelocity (double time, const double pos[3], const WaterVelocityParts &parts, const wake::HeldWakes &held, std::size_t point, double velOut[3]) const |
| void | GetWaterVelocities (double time, std::span< const std::array< double, 3 > > positions, const WaterVelocityParts &parts, std::span< std::array< double, 3 > > velocities, std::span< Status > statuses) const |
| Status | GetWaterVelocity (double time, const double pos[3], const WaterVelocityParts &parts, const wake::WakeQuery &query, const wake::HeldWakes *held, std::size_t point, double velOut[3], WakedFlow *detail=nullptr) const |
| Status | GetWakedFlow (double time, const double pos[3], WakedFlow &out) const |
| Status | GetWakedFlow (double time, const double pos[3], const wake::WakeQuery &query, WakedFlow &out) const |
| wake::WakeSelection | SelectWakes (const wake::WakeQueryRegion ®ion, const wake::WakeQuery &query={}) const |
| std::uint64_t | GetWakeRevision () const |
| A number that grows whenever a wake or jet field is registered, removed or published again: wake::WakeRegistry::Revision(). | |
| bool | WakesStaticFrom (double t) const |
| Whether the wake and jet fields stay the same from t on until GetWakeRevision() changes: wake::WakeRegistry::StaticFrom(). | |
Public Member Functions inherited from marenv::Environment | |
| virtual | ~Environment ()=0 |
| Virtual destructor. | |
| virtual Status | GetSurfaceUnitNormal (double time, const double pos[2], double unitNormalOut[3])=0 |
| virtual Status | GetParticleVelocity (double time, const double pos[3], double velOut[3])=0 |
| virtual Status | GetParticleAcceleration (double time, const double pos[3], double accOut[3])=0 |
| virtual Status | GetCurrentVelocity (double time, const double pos[3], double velOut[3])=0 |
Protected Member Functions | |
| Status | ComputeTotalCurrent (double time, const double pos[3], double vel[3]) const |
Protected Member Functions inherited from marenv::Environment | |
| Environment ()=default | |
| The constructor is protected. | |
Protected Attributes | |
| std::shared_ptr< wave::WaveField > | m_waves |
| Wave field model providing wave-induced quantities. | |
| std::unordered_map< std::string, std::shared_ptr< current::CurrentField > > | m_currentFields |
| Registry of current fields indexed by unique identifier; velocities are summed for the total current. | |
| std::shared_ptr< seafloor::BathymetryField > | m_depths |
| Bathymetry field model providing seafloor geometry and properties. | |
| wake::WakeRegistry | m_wakes |
| The registered wake fields in registration order (MENV-0035: the composition order is the same on every platform), and their composition. | |
Defines an environment.
Some methods are at the moment simple stubs.
|
inline |
Default constructor. Initialises with null/default field implementations:
NullWaveField (no waves), FlatSeafloor (200 m flat bottom). No current fields are registered by default.
| void marenv::EnvironmentFacade::AddCurrentField | ( | const std::string & | id, |
| std::shared_ptr< current::CurrentField > | field | ||
| ) |
Adds a current field with the given identifier. The velocities of all registered current
| [in] | id | Unique identifier for this current field. |
| [in] | field | The current field to add. |
fields are summed to produce the total ambient current.
| std::invalid_argument | if a current field with the given id already exists. |
| void marenv::EnvironmentFacade::AddJetField | ( | const std::string & | id, |
| std::shared_ptr< wake::JetField > | field | ||
| ) |
Adds a jet field with the given identifier (ADR 0003).
The jets of every query that includes the wakes term (WaterVelocityTerms::Full, NoWaves) are summed and added after the deficit composition, u = u_amb + d_tot (u_s - u_amb) + sum Δu_j.
| [in] | id | Unique identifier among the wake and jet fields. |
| [in] | field | The jet field to add. |
| std::invalid_argument | if a wake or jet field with the given id already exists. |
| void marenv::EnvironmentFacade::AddWakeField | ( | const std::string & | id, |
| std::shared_ptr< wake::WakeField > | field | ||
| ) |
Adds a wake field with the given identifier.
| [in] | id | Unique identifier for this wake field. |
| [in] | field | The wake field to add. |
| std::invalid_argument | if a wake field with the given id already exists. |
|
overridevirtual |
Returns the distance a ray must travel before intercepting the seafloor: the first crossing along the ray (owner ruling R132; see seafloor::BathymetryField::ComputeRayIntersection). An origin at the seafloor or inside it is a hit at distance 0 (owner ruling R133).
| [in] | origin | Position (x,y,z) of the origin of the ray |
| [in] | direction | The unit direction (x,y,z) of the ray |
| [out] | distanceOut | The distance the beam travelled before reaching the seabed; -1 without an intersection. |
| [in] | maxSearchDistance | The maximum distance from the origin to search for an intersection. |
| [in] | startSearchDistance | The distance that anchors the search grid; negative for the flat-seabed estimate. It does not move the result away from the first crossing. |
| [in] | stepSize | The search step size. |
| [in] | convergenceTolerance | The vertical error tolerance between a computed point on the ray and a point on the seafloor to consider an intersection found. |
maxSearchDistance (owner ruling R134); marenv::Status::ERROR_NOT_SET for an invalid direction or range, or without a seafloor. Implements marenv::Environment.
|
protected |
Sums the velocities of the current fields whose status at pos is usable (IsUsable(): OK or OUT_OF_RANGE_INACCURATE, R122) into vel[3].
| std::shared_ptr< seafloor::BathymetryField > marenv::EnvironmentFacade::GetBathymetry | ( | ) |
Gets the bathymetry to be used.
| std::shared_ptr< current::CurrentField > marenv::EnvironmentFacade::GetCurrentField | ( | const std::string & | id | ) | const |
Returns the current field with the given identifier.
| [in] | id | Identifier of the current field. |
| std::invalid_argument | if the identifier does not exist. |
| std::vector< std::string > marenv::EnvironmentFacade::GetCurrentFieldIds | ( | ) | const |
Returns a list of all registered current field identifiers.
|
override |
Returns the water current velocity at a specific position.
| [in] | time | Current simulation time. |
| [in] | pos | Position (x,y,z) for which the current velocity is desired. |
| [out] | velOut | The current velocity at pos. |
Includes effects of only currents, not waves, modified by any registered wake fields (composition in EnvironmentFacade).
The WaterVelocityTerms::NoWaves terms: current and wakes, no waves, GetWaterVelocity(time, pos, WaterVelocityTerms::NoWaves, velOut); new code asks GetWaterVelocity() (owner ruling O3a). The wake composition is u = u_amb + d_tot (u_s - u_amb) (see GetWakedFlow()). It runs also when no current field is registered: then u_amb = 0 and a moving wake source gives u = d_tot u_s. u_amb is the sum of the current fields whose status at pos is usable (IsUsable(): Status::OK, or Status::OUT_OF_RANGE_INACCURATE for a value clamped at the edge of a depth profile, owner ruling R122); the first status of a current field that is not usable is returned, else Status::OUT_OF_RANGE_INACCURATE if a field returned it (MENV-0013, MENV-0053).
|
overridevirtual |
Returns the water density at a specific position
| [in] | time | Current simulation time. |
| [in] | pos | Position (x,y,z) for which the water current velocity producer is desired. |
| [out] | density | The water density at given position and time. |
Implements marenv::Environment.
|
overridevirtual |
Returns the dynamic pressure at a specific position.
| [in] | time | Current simulation time. |
| [in] | pos | Position (x,y,z) for which the pressure is desired. |
| [out] | pressureOut | The pressure at pos. |
Implements marenv::Environment.
| std::shared_ptr< wake::JetField > marenv::EnvironmentFacade::GetJetField | ( | const std::string & | id | ) | const |
Returns the jet field with the given identifier.
| [in] | id | Identifier of the jet field. |
| std::invalid_argument | if no jet field has the identifier. |
| std::vector< std::string > marenv::EnvironmentFacade::GetJetFieldIds | ( | ) | const |
Returns a list of all registered jet field identifiers (ADR 0003).
|
override |
Returns the particle acceleration at a specific position.
| [in] | time | Current simulation time. |
| [in] | pos | Position (x,y,z) for which the particle acceleration is desired. |
| [out] | accOut | The particle acceleration at pos. |
Includes effects of both current and waves.
|
override |
Returns the particle velocity at a specific position.
| [in] | time | Current simulation time. |
| [in] | pos | Position (x,y,z) for which the particle velocity is desired. |
| [out] | velOut | The particle velocity at pos. |
Includes effects of both current and waves. The current part is modified by any registered wake fields (composition in EnvironmentFacade), as in GetCurrentVelocity.
The WaterVelocityTerms::Full terms: current, wakes and waves, GetWaterVelocity(time, pos, WaterVelocityTerms::Full, velOut). The current part is the ambient current with the wake composition applied (see GetWakedFlow()); the wave particle velocity is not waked. Equals GetWakedFlow().waked.
|
overridevirtual |
Returns the sea depth at a specific position.
| [in] | pos | Position (x,y) for which sea depth is desired. |
| [out] | depthOut | The sea depth at pos. |
Implements marenv::Environment.
|
overridevirtual |
Calculates the submergence of a specific position.
| [in] | time | Time |
| [in] | pos | Position |
| [out] | submergence | The submergence of the position |
Implements marenv::Environment.
|
overridevirtual |
Returns the surface elevation at a specific position.
| [in] | time | Current simulation time. |
| [in] | pos | Position (x,y) for which surface elevation is desired. |
| [out] | elevationOut | Surface elevation at pos. |
Implements marenv::Environment.
|
override |
Returns the unit normal vector of the surface at a specific position.
| [in] | time | Current simulation time. |
| [in] | pos | Position (x,y) for which the unit normal surface vector is desired. |
| [out] | unitNormalOut | The unit normal surface vector at pos. |
|
overridevirtual |
Returns the total pressure at a specific position.
| [in] | time | Current simulation time. |
| [in] | pos | Position (x,y,z) for which the pressure is desired. |
| [out] | pressureOut | The pressure at pos. |
Includes both dynamic and static pressure.
Implements marenv::Environment.
| Status marenv::EnvironmentFacade::GetWakedFlow | ( | double | time, |
| const double | pos[3], | ||
| const wake::WakeQuery & | query, | ||
| WakedFlow & | out | ||
| ) | const |
Returns the ambient and the waked flow at a point, with one field excluded and one added (contract C5).
| [in] | time | Simulation time, s. |
| [in] | pos | Position (x,y,z) to query, m, NED. |
| [in] | query | The registered field to skip and the unregistered field to add. |
| [out] | out | Ambient velocity, waked velocity, combined deficit and source count. |
As GetWakedFlow(time, pos, out), except that the registered field query.exclude and the registered jet query.excludeJet are not sampled and the field query.own is sampled after the registered ones and multiplied onto their composition D, d_tot = 1 - (1 - D) (1 - d_own) (R88, R95): the own field is a net's private per-panel field, whose sources are normalised by their local, already shaded inflow (doc/wake.md section 4). out.deficit and out.numSources include it. query.own must not be registered, or it counts twice. GetWakedFlow(time, pos, out) and GetParticleVelocity() equal this call with WakeQuery{}.
| Status marenv::EnvironmentFacade::GetWakedFlow | ( | double | time, |
| const double | pos[3], | ||
| WakedFlow & | out | ||
| ) | const |
Returns the ambient and the waked flow at a point (contract C5).
| [in] | time | Simulation time, s. |
| [in] | pos | Position (x,y,z) to query, m, NED. |
| [out] | out | Ambient velocity, waked velocity, combined deficit and source count. |
Every registered wake field is sampled at pos. A field that returns a status other than Status::OK (Status::ERROR_NOT_SET outside the field) contributes nothing; a returned deficit is clamped to [0, 1], and a NaN deficit contributes nothing. The fields compose by the momentum-flux rule, d_tot (1 - d_tot) = sum d_i (1 - d_i) where that has a root below the branch deficit, else the capped linear sum or 1/2 (owner ruling R95, replacing R88's min(1, sum d_i); wake::MomentumFluxSum), with the source velocity u_s the deficit-weighted mean of the u_s,i, and the waked current is u = u_amb + d_tot (u_s - u_amb). Every registered jet field is sampled too, and the jets are added after the deficits, u = u_amb + d_tot (u_s - u_amb) + sum Δu_j (ADR 0003; out.numJets counts them).
out.waked equals GetParticleVelocity(). The wake composition runs whether or not a current field is registered; with none, the ambient current is zero.
| std::shared_ptr< wake::WakeField > marenv::EnvironmentFacade::GetWakeField | ( | const std::string & | id | ) | const |
Returns the wake field with the given identifier.
| [in] | id | Identifier of the wake field. |
| std::invalid_argument | if the identifier does not exist. |
| std::vector< std::string > marenv::EnvironmentFacade::GetWakeFieldIds | ( | ) | const |
Returns a list of all registered wake field identifiers.
| void marenv::EnvironmentFacade::GetWaterVelocities | ( | double | time, |
| std::span< const std::array< double, 3 > > | positions, | ||
| const WaterVelocityParts & | parts, | ||
| std::span< std::array< double, 3 > > | velocities, | ||
| std::span< Status > | statuses | ||
| ) | const |
GetWaterVelocity(time, pos, parts, vel) at many points, bit for bit, the wakes composed by wake::WakeRegistry::ComposeMany().
| [in] | time | Simulation time, s. |
| [in] | positions | The points, m, NED. |
| [in] | parts | The parts to sum. |
| [out] | velocities | One water velocity per point, m/s, NED; as many as positions. |
| [out] | statuses | One status per point; as many as positions. |
The way to sample a grid from several threads: the wake and jet fields are loaded once per call, not once per point (see wake::WakeField::SampleMany()).
| Status marenv::EnvironmentFacade::GetWaterVelocity | ( | double | time, |
| const double | pos[3], | ||
| const WaterVelocityParts & | parts, | ||
| const wake::HeldWakes & | held, | ||
| std::size_t | point, | ||
| double | velOut[3] | ||
| ) | const |
Returns the sum of the given parts of the water velocity at a point, with a held wake composition (owner rulings R46, O2a).
| [in] | time | Simulation time, s; the held composition is weighed at it (wake::HeldWakes::At()). |
| [in] | pos | Position (x,y,z) to query, m, NED. |
| [in] | parts | The parts to sum; the wakes and the jets come from the held composition, the current and the waves are live. |
| [in] | held | The compositions held at the caller's points. |
| [in] | point | The index of the point in held. |
| [out] | velOut | Water velocity, m/s, NED. |
With the composition the held selection gives at pos and time, bit for bit GetWaterVelocity(time, pos, parts, velOut). Thread-safe for different points (wake::HeldWakes::At()).
|
inline |
The one water-velocity query: the given parts at a point, with the wakes of a query composed live or held (plan E1).
| [in] | time | Simulation time, s; a held composition is weighed at it (wake::HeldWakes::At()). |
| [in] | pos | Position (x,y,z) to query, m, NED. |
| [in] | parts | The parts to sum (WaterVelocityParts); they alone decide the terms, the jets included. |
| [in] | query | The wake query of the live composition (wake::WakeRegistry::Compose()); not read with held, whose selection applied it. |
| [in] | held | The compositions held at the caller's points (wake::WakeSelection::Hold()), or nullptr to compose the wakes live. |
| [in] | point | The index of the point in held; not read without it. |
| [out] | velOut | Water velocity, m/s, NED. |
| [out] | detail | If not null, also the ambient current, velOut as waked, and the deficit and counts of the composition. Live, the composition holds only the asked parts; held, it holds what the take sampled (the velocity is the same, the counts may differ). |
Every other water-velocity call of the facade is this query: GetWaterVelocity() of terms or parts with WakeQuery{} and no held composition, GetWaterVelocity(time, pos, parts, held,
point, vel) with WakeQuery{}, GetWakedFlow() with every part. Only what the parts need is sampled: no wake field without parts.wakes, no jet without parts.jets. The current and the waves are always live. A held composition taken at pos and time gives the live query's velocity bit for bit. Thread-safe as GetWaterVelocity(time, pos, parts, velOut), and for different points of held (wake::HeldWakes::At()).
| Status marenv::EnvironmentFacade::GetWaterVelocity | ( | double | time, |
| const double | pos[3], | ||
| const WaterVelocityParts & | parts, | ||
| double | velOut[3] | ||
| ) | const |
Returns the sum of the given parts of the water velocity at a point.
| [in] | time | Simulation time, s. |
| [in] | pos | Position (x,y,z) to query, m, NED. |
| [in] | parts | The parts to sum (WaterVelocityParts). |
| [out] | velOut | Water velocity, m/s, NED. |
Only what the parts need is sampled: no wake field without wakes, no jet without jets. Each WaterVelocityTerms selection is the parts selection with the jets going with the wakes, bit for bit (Full: all four; NoWaves: no waves; NoWakes: current and waves; CurrentOnly: current). Thread-safe as the queries it makes: the wake fields as WakeField::Sample(), the current and wave fields as CurrentField::GetCurrent() and WaveField's queries.
| Status marenv::EnvironmentFacade::GetWaterVelocity | ( | double | time, |
| const double | pos[3], | ||
| WaterVelocityTerms | terms, | ||
| double | velOut[3] | ||
| ) | const |
Returns the water velocity of the given terms at a point (owner rulings O1a, O2a).
| [in] | time | Simulation time, s. |
| [in] | pos | Position (x,y,z) to query, m, NED. |
| [in] | terms | The terms to include: current, wakes and/or waves. |
| [out] | velOut | Water velocity, m/s, NED. |
The one rule: the water velocity is the live ambient current, with the composition of every registered wake field applied, plus the live waves; terms drops the waves and/or the wakes. Full equals GetParticleVelocity() and NoWaves equals GetCurrentVelocity(), bit for bit; NoWakes and CurrentOnly apply no wake. The query is exact and live: every wake field is sampled, nothing is held. A receiver that must leave its own wake out holds a selection (SelectWakes()) and asks GetWaterVelocity(time, pos, parts, held, point, vel).
| std::shared_ptr< wave::WaveField > marenv::EnvironmentFacade::GetWaves | ( | ) |
Gets the wave to be used.
| bool marenv::EnvironmentFacade::HasCurrentField | ( | const std::string & | id | ) | const |
Returns true if a current field with the given identifier exists.
| [in] | id | Identifier to check. |
| bool marenv::EnvironmentFacade::HasWakeField | ( | const std::string & | id | ) | const |
Returns true if a wake field with the given identifier exists.
| [in] | id | Identifier to check. |
| double marenv::EnvironmentFacade::MaxWaveElevation | ( | ) | const |
Returns an upper bound of |wave elevation| at every position and time (owner ruling R66).
A point at depth z > MaxWaveElevation() + h, h the half-size of a body around it, is submerged whatever the waves (GetSubmergence() > h), so a caller can skip the wave elevation there. The bound holds for the waves set and published at the call; SetWaves() or a blend of the wave components voids it.
0 means no waves: no wave elevation, particle velocity or particle acceleration anywhere (MENV-0064), so GetWaves()->ComputeParticleVelocity() and ComputeParticleAcceleration() need not be called.
|
overridevirtual |
Calculates multiple properties of the environment in one call.
| [in] | time | Simulation time. |
| [in] | pos | Position. |
| [out] | queryResult | The result of the query. |
With a wave field set (the default is a NullWaveField), current_velocity is the waked current, as GetCurrentVelocity() (WaterVelocityTerms::NoWaves), particle_velocity is the wave particle velocity plus the waked current, as GetParticleVelocity() (WaterVelocityTerms::Full), and the status is that of GetCurrentVelocity(). Without a wave field (SetWaves() with a null pointer) every member keeps its default (EnvironmentQuery::SetZero(): no current and no wave velocity, whatever the current fields) and the status is Status::ERROR_NOT_SET; use GetCurrentVelocity() or GetWakedFlow() for the current then (MENV-0054).
Implements marenv::Environment.
| void marenv::EnvironmentFacade::RemoveCurrentField | ( | const std::string & | id | ) |
Removes the current field with the given identifier.
| [in] | id | Identifier of the current field to remove. |
| std::invalid_argument | if no current field with the given id exists. |
| void marenv::EnvironmentFacade::RemoveWakeField | ( | const std::string & | id | ) |
Removes the wake field with the given identifier.
| [in] | id | Identifier of the wake field to remove. |
| std::invalid_argument | if no wake field with the given id exists. |
| wake::WakeSelection marenv::EnvironmentFacade::SelectWakes | ( | const wake::WakeQueryRegion & | region, |
| const wake::WakeQuery & | query = {} |
||
| ) | const |
Selects the registered wake fields and jets that may reach a region, for a query (owner decision R47): wake::WakeRegistry::Select().
A structure selects once per step, after the fields have been published for the step, and holds the selection at its points (wake::WakeSelection::Hold()); GetWaterVelocity(time, pos, parts, held, point, vel) applies a held composition to the live current and waves.
| void marenv::EnvironmentFacade::SetBathymetry | ( | std::shared_ptr< seafloor::BathymetryField > | bathymetry | ) |
Sets the bathymetry to be used.
| [in] | bathymetry | Bathymetry to be used. |
| void marenv::EnvironmentFacade::SetWaves | ( | const wave::WaveEnergySpectrum::SpectrumParameters & | params, |
| wave::WaveTheory | waveTheory, | ||
| int | numWaves, | ||
| uint32_t | seed | ||
| ) |
Creates the wave model based on spectrum parameters. Automatically selects between scalar and simd implementations for Airy waves.
| [in] | params | The spectrum parameters. |
| [in] | waveTheory | The wave theory to use. |
| [in] | numWaves | The number of waves to create. |
| [in] | seed | The random seed to use. |
| void marenv::EnvironmentFacade::SetWaves | ( | std::shared_ptr< wave::WaveField > | wave | ) |
Sets the wave to be used.
| [in] | wave | Wave to be used. |