LifFreqConversionStage

LifFreqConversionStage is the hardware-type base class for one crystal or compensator node in the LIF frequency-conversion topology — a doubler, a sum-frequency mixer, a difference-frequency mixer. It is a direct HardwareObject child, a sibling of LifLaser rather than a subtype of it, registered like any other hardware type (see Adding a New Hardware Type, which uses this class as a worked example of a type whose device-identity settings are pinned by concrete drivers). The base class owns only the generic contract every conversion node shares: the registered conversion operation (conversionOp()) and harmonic order (harmonicOrder()) — both snapshot-visible via SettingsStorage so a GUI or data-layer caller can read them without touching a live threaded device — a verify flag/tolerance pair, and the setPosition()/readPosition() dispatch slots that move to and confirm a local input-beam wavenumber (cm⁻¹). The DAG wiring itself (input references, the FINAL marker) is per-experiment state owned by LifConversionSnapshot and LifConfig, not by the stage — see Key invariants on LIF Acquisition and Visualization for the full op/harmonic-versus-wiring split.

A concrete driver such as SirahFcu pins conversionOp() to a hardware-frozen constant (a doubler is NHG by device identity, not a free choice) while leaving the registered op setting in place, and overrides setHarmonicOrder() to route a harmonic change through the device before persisting it — the canonical example of the gated setting pattern described on Hardware Configuration. VirtualLifFreqConversionStage and FixedLifFreqConversionStage are the uncontrolled/CI implementations.

Three free functions declared alongside the class join a LifConversionSnapshot’s wiring with each stage’s snapshot-read op/harmonic into an assembled LifConversion, without ever touching a live threaded device: lifConversionNodesFromSnapshot builds the joined node list, assembleLifConversion assembles it, and assembleCurrentLifConversion is the convenience wrapper that resolves the current loadout’s current LifPreset, falling back to the identity conversion when none is selected. These are the building blocks behind the prep-time and connection-complete assembly paths described on LIF Acquisition and Visualization (Frequency conversionData flow by moment).

API Reference

class LifFreqConversionStage : public HardwareObject

Base class for a LIF frequency-conversion stage (FCU): a crystal or compensator node in the optical conversion topology between the tunable LifLaser fundamental and the FINAL output beam.

A direct child of HardwareObject (sibling of LifLaser), so it earns its own hwType. The base owns the generic contract shared by every conversion node: the registered device-identity settings (conversionOp(), harmonicOrder() — op/harmonic order, snapshot-visible so a GUI/data-layer caller can read them without touching the threaded device), a per-device verify flag, and the setPosition()/readPosition() dispatch slots that move to and confirm a local input-beam wavenumber (cm⁻¹). The DAG wiring (input refs, FINAL marker) is per-experiment state owned by LifConversionSnapshot/LifConfig, not by the stage. Structured calibration settings (crystal/compensator angle-vs-wavelength polynomials, mount addresses) differ by driver and are not declared here.

A stage emits no output-position update to the display; only LifLaser::laserPosUpdate drives the axis.

Subclassed by FixedLifFreqConversionStage, SirahFcu, VirtualLifFreqConversionStage

Public Functions

LifFreqConversionStage(const QString &impl, const QString &label, QObject *parent = nullptr)
~LifFreqConversionStage() override
virtual BC::LifConv::Op conversionOp() const

This stage’s conversion operation (NHG/SFG/DFG).

Base implementation reads the registered op setting. A doubler or mixer driver is that operation by device identity — not a free choice — so concrete drivers override this to a constant; the registered setting itself stays declared on the base for every stage (concrete drivers pin its default) so it remains snapshot-visible even where the driver treats it as fixed (see the C-6 assembly join, which reads settings snapshots rather than calling this virtual on a live threaded device).

inline int harmonicOrder() const

Harmonic order N for an NHG stage (the registered harmonic setting; ignored for SFG/DFG).

virtual bool setHarmonicOrder(int n)

Driver hook for a gated harmonic-order change.

Base implementation persists n to the registered harmonic setting and returns true. A unit that can retune its harmonic output in firmware overrides this to issue the hardware command (and update the setting on success), so a harmonic change always passes through the device rather than being a raw setting poke.

Returns:

Whether the change succeeded.

Public Slots

bool setPosition(double localCm1)

Dispatch target: move to this stage’s PRIMARY input-beam wavenumber (cm⁻¹).

localCm1 is already computed by the caller from the assembled conversion topology (LifConversion::stageInput); the stage itself needs no topology. Maps the requested wavenumber to a phase-match motor position via the driver’s calibration, moves, then verifies via readPos(). A negative readPos() is a hard communication-error sentinel, not a value the stage actually reported, so it always emits hardwareFailure() and returns false regardless of the verify flag. For an in-range readback that simply misses the requested wavenumber, the verify flag (BC::Key::LifConvStage::verify) decides the outcome: off logs a warning and returns true (best-effort); on emits hardwareFailure() and returns false.

Returns:

Whether the move (and, if enabled, its verification) succeeded.

double readPosition()

Verify hook: this stage’s achieved local input-beam wavenumber (cm⁻¹), or <0 on error.

Private Functions

virtual void setPos(double localCm1) = 0

Driver hook: move to the given local input-beam wavenumber (cm⁻¹).

virtual double readPos() = 0

Driver hook: read the achieved local input-beam wavenumber (cm⁻¹), or a negative sentinel on error.

std::vector<BC::LifConv::Node> lifConversionNodesFromSnapshot(const LifConversionSnapshot &snap)

Join snap’s wiring with op/n read from each stage’s active hardware settings snapshot into a node list, without touching any threaded device.

For every BC::LifConv::StageWiring entry, builds a SettingsStorage snapshot directly on the stage’s hardware key (never a live device) to read conversionOp()/harmonicOrder(). Shared by assembleLifConversion() and by callers that need the joined node list itself (e.g. the conversion table model, when seeding wiring from a preset snapshot).

Returns:

The joined node list; empty when snap has no wiring.

LifConversion::AssemblyResult assembleLifConversion(const LifConversionSnapshot &snap)

Assemble a LIF frequency-conversion topology by joining snap’s wiring with op/n read from each stage’s active hardware settings snapshot, without touching any threaded device.

Builds the node list via lifConversionNodesFromSnapshot() and assembles it with LifConversion::assemble(). Snapshot-only, so it is safe to call from GUI or data-layer threads.

Returns:

The assembly result: ok and conversion on success, or ok == false and errorString on a malformed topology.

LifConversion::AssemblyResult assembleCurrentLifConversion()

Convenience wrapper: assemble the current LIF preset’s conversion wiring for the current loadout.

Resolves LoadoutManager::instance().currentLoadoutName() and that loadout’s current LIF preset. When no loadout or no LIF preset is selected, returns the identity AssemblyResult (ok == true, identity LifConversion) rather than an error, matching the tolerant fallback used by GUI callers (config page, live laser widget) that need the active topology to compute display ranges but must stay responsive when the topology is not yet configured.

Returns:

The assembly result for the current preset, or the identity result when no preset is selected.