Source code for maxwelllink.measurements.dummy_measurement
# --------------------------------------------------------------------------------------#
# Copyright (c) 2026 MaxwellLink #
# This file is part of MaxwellLink. Repository: https://github.com/TaoELi/MaxwellLink #
# If you use this code, always credit and cite arXiv:2512.06173. #
# See AGENTS.md and README.md for details. #
# --------------------------------------------------------------------------------------#
import numpy as np
from ..units import wavelength_nm_from_omega
[docs]
class DummyMeasurement:
"""
A dummy light-induced measurement for demonstration purposes.
This class serves as a template for implementing light-induced measurements,
which excite an EM solver (plus its molecules) with light pulses and turn
the recorded response into user-facing observables.
Every measurement splits into three steps:
1. ``reference()``: the excitation baseline, computed analytically
(e.g. the spectrum of a known laser pulse) or by a molecule-free
reference simulation (e.g. the FDTD normalization run);
2. ``signal_run()``: excite the full system and collect the raw response signals;
3. ``postprocess(reference, signals)``: combine both into the observable arrays.
``run()`` chains the three steps and is the single user-facing entry point.
"""
[docs]
def __init__(self, omega_min, omega_max, units="cm-1", nfreq=200, molecules=None):
"""
Initialize the necessary attributes of a light-induced measurement.
Notes
-----
This method *should be* overridden by subclasses to store their
solver-specific inputs; call
``super().__init__(omega_min, omega_max, units, nfreq, molecules)``
first.
Parameters
----------
omega_min, omega_max : float
Spectral window of the measurement in ``units``.
units : str, default: "cm-1"
Units of the window: "cm-1", "eV", "au", "nm", or "um".
nfreq : int, default: 200
Number of frequency points of the observables.
molecules : sequence or None, optional
Molecules probed by the measurement (may be empty).
"""
# spectral window in cm^-1
a = 1.0e7 / wavelength_nm_from_omega(omega_min, units)
b = 1.0e7 / wavelength_nm_from_omega(omega_max, units)
self.omega_min_cminv, self.omega_max_cminv = sorted((a, b))
if self.omega_max_cminv <= self.omega_min_cminv:
raise ValueError("omega_min and omega_max must span a nonzero window.")
self.units = str(units)
self.nfreq = int(nfreq)
if self.nfreq < 2:
raise ValueError("nfreq must be at least 2.")
self.molecules = list(molecules) if molecules is not None else []
# the frequency axes of the returned observables
self.omega_cminv = np.linspace(
self.omega_min_cminv, self.omega_max_cminv, self.nfreq
)
self.wavelength_nm = 1.0e7 / self.omega_cminv
# -------------- the three measurement steps (must be overridden) --------------
[docs]
def reference(self):
"""
Return the excitation baseline of the measurement.
Depending on the EM solver, this is computed analytically (e.g. the
Fourier transform of a known laser pulse) or by a molecule-free
reference simulation (e.g. the FDTD normalization run).
Notes
-----
This method *must be* overridden by subclasses.
"""
raise NotImplementedError("This method should be overridden by subclasses.")
[docs]
def signal_run(self):
"""
Excite the full system and return the raw response signals.
Notes
-----
This method *must be* overridden by subclasses.
"""
raise NotImplementedError("This method should be overridden by subclasses.")
[docs]
def postprocess(self, reference, signals):
"""
Combine the reference and the raw signals into observable arrays.
Notes
-----
This method *must be* overridden by subclasses. Implementations end
with ``return self._assemble_result(omega_cminv, **observables)``.
Parameters
----------
reference : object
The return value of ``reference()``.
signals : object
The return value of ``signal_run()``.
"""
raise NotImplementedError("This method should be overridden by subclasses.")
# -------------- measurement driver (no need to override) --------------
[docs]
def run(self):
"""
Run the measurement: the reference first, then the signal run, then the
combination. Subclasses may pass state between the steps via
attributes (e.g. fields recorded in the reference run).
Notes
-----
This method should *not* be overridden by subclasses.
Returns
-------
dict
The frequency axes plus the observables of the measurement.
"""
reference = self.reference()
signals = self.signal_run()
return self.postprocess(reference, signals)
def _assemble_result(self, omega_cminv, **observables):
"""
Return the standard result dict of every measurement: the frequency
axes (``omega_cminv`` in cm^-1) plus the named observable arrays.
"""
omega_cminv = np.asarray(omega_cminv, dtype=float)
result = {
"omega_cminv": omega_cminv,
"wavelength_nm": 1.0e7 / omega_cminv,
}
result.update(observables)
return result