Single-Mode Cavity
==================

The :mod:`maxwelllink.em_solvers.single_mode_cavity` module provides a
lightweight electromagnetic solver that replaces a full FDTD grid with a single
damped harmonic oscillator. It evolves entirely in atomic units while
supporting the same :class:`~maxwelllink.molecule.molecule.Molecule` abstraction used by the Meep
backend, making it well suited for rapid prototyping. Multiple molecular dipole
components can be coupled simultaneously by supplying composite axes such as
``"xy"``.

.. note::

  The cavity replaces the full EM grid with canonical coordinate :math:`\mathbf{q}_c` and momentum :math:`\mathbf{p}_c = \dot{\mathbf{q}}_c` obeying the component-wise equation

  .. math::

     \ddot{\mathbf{q}}_c = -\omega_c^{2} \mathbf{q}_c - \varepsilon \sum_{m} \boldsymbol{\mu}_{m} - \kappa \dot{\mathbf{q}}_c + D(t),

  where :math:`\omega_c` is ``frequency_au``, :math:`\kappa` is ``damping_au``, :math:`\varepsilon = 1/\sqrt{\epsilon_0 V}` is ``coupling_strength``, and :math:`D(t)` is the optional external drive. The drive enters the cavity equation only when ``excite_ph=True`` (default). The sum runs over the dipole components selected by ``coupling_axis`` for each coupled molecule. The effective electric field returned to the drivers is

  .. math::

     \mathbf{E}(t) = -\varepsilon\, \mathbf{q}_c(t) - \delta_{\mathrm{DSE}} \frac{\varepsilon^{2}}{\omega_c^{2}}\, \boldsymbol{\mu}(t) + \delta_{\mathrm{exc,mol}}\, D(t)\,\hat{\mathbf{e}},

  with :math:`\boldsymbol{\mu}(t)` the summed molecular dipole restricted to the requested axes, :math:`\delta_{\mathrm{DSE}} = 1` only when ``include_dse=True``, and :math:`\delta_{\mathrm{exc,mol}} = 1` only when ``excite_mol=True`` (with :math:`\hat{\mathbf{e}}` the unit vector along ``coupling_axis``).

  When ``temperature_au > 0`` and ``initializer="maxwell_boltzmann"``, the initial cavity coordinate and momentum are resampled from a Maxwell-Boltzmann distribution at temperature :math:`T` (atomic units),

  .. math::

     q_c \sim \mathcal{N}\!\left(0,\, \sqrt{T}/\omega_c\right), \qquad p_c \sim \mathcal{N}\!\left(0,\, \sqrt{T}\right),

  overriding any provided ``qc_initial`` / ``pc_initial``. If ``langevin_tau_au`` is also supplied, a Langevin thermostat with damping time :math:`\tau` is applied to the cavity momentum every step,

  .. math::

     p_c \leftarrow p_c\, e^{-\Delta t/\tau} + \xi,\qquad \xi \sim \mathcal{N}\!\left(0,\, \sqrt{T\,(1 - e^{-2\Delta t/\tau})}\right).

Requirements
------------

- No additional dependencies beyond the **MaxwellLink** Python stack are required.

Usage
-----

Socket mode
^^^^^^^^^^^

.. code-block:: python

   import maxwelllink as mxl

   hub = mxl.SocketHub(host="127.0.0.1", port=31415, timeout=10.0, latency=1e-5)
   molecule = mxl.Molecule(hub=hub)

   sim = mxl.SingleModeSimulation(
       dt_au=0.5,
       frequency_au=0.242,
       damping_au=0.0,
       molecules=[molecule],
       coupling_strength=1e-4,
       qc_initial=[0, 0, 1e-5],
       coupling_axis="z",
       hub=hub,
       record_history=True,
   )

   sim.run(steps=4000)

Launch :command:`mxl_driver --model tls --port 31415 ...` (or another driver)
after running this script.

Non-socket mode
^^^^^^^^^^^^^^^

.. code-block:: python

   import maxwelllink as mxl

   tls = mxl.Molecule(
       driver="tls",
       driver_kwargs=dict(
           omega=0.242,
           mu12=187.0,
           orientation=2,
           pe_initial=1e-4,
       ),
   )

   sim = mxl.SingleModeSimulation(
       dt_au=0.5,
       frequency_au=0.242,
       damping_au=0.0,
       molecules=[tls],
       coupling_strength=1e-4,
       qc_initial=[0, 0, 1e-5],
       coupling_axis="z",
       record_history=True,
   )

   sim.run(steps=4000)

Parameters
----------

.. list-table::
   :header-rows: 1

   * - Name
     - Description
   * - ``dt_au``
     - Integration time step in atomic units. Must be positive.
   * - ``frequency_au``
     - Cavity angular frequency :math:`\omega_c` (a.u.).
   * - ``damping_au``
     - Linear damping coefficient :math:`\kappa` applied to the cavity momentum (a.u.).
   * - ``molecules``
     - Iterable of :class:`~maxwelllink.molecule.molecule.Molecule` instances. Socket and non-socket
       molecules can be mixed in the same simulation.
   * - ``drive``
     - Constant float or callable ``drive(time_au)`` returning the external drive term.
       Defaults to ``0.0``.
   * - ``coupling_strength``
     - Scalar prefactor :math:`g` for the molecular polarization feedback. Default: ``1.0``.
   * - ``coupling_axis``
     - One or more dipole components to couple (case-insensitive union of ``"x"``, ``"y"``, ``"z"``, e.g. ``"xy"``). Default: ``"xy"``.
   * - ``hub``
     - Optional :class:`~maxwelllink.sockets.sockets.SocketHub` shared by all socket-mode molecules.
       The simulation infers the hub from the first socket molecule when omitted.
   * - ``qc_initial``
     - Initial cavity coordinate vector (sequence of three floats or scalar applied to each coupled axis). Default: ``[0.0, 0.0, 0.0]``.
   * - ``pc_initial``
     - Initial cavity momentum vector (a.u.). Default: ``[0.0, 0.0, 0.0]``.
   * - ``mu_initial``
     - Initial total molecular dipole vector prior to axis masking (a.u.). Default: ``[0.0, 0.0, 0.0]``.
   * - ``dmudt_initial``
     - Initial time derivative of the total molecular dipole vector (a.u.). Default: ``[0.0, 0.0, 0.0]``.
   * - ``temperature_au``
     - Cavity temperature in atomic units (Hartree), used by the optional initializer and Langevin thermostat. Default: ``0.0``.
   * - ``langevin_tau_au``
     - Langevin thermostat relaxation time :math:`\tau` (a.u.). When supplied, a Langevin thermostat is applied to the cavity momentum each step. Leave as ``None`` for an NVE-like cavity. Default: ``None``.
   * - ``initializer``
     - Optional initial-condition sampler. Set to ``"maxwell_boltzmann"`` with ``temperature_au > 0`` to resample ``qc_initial`` and ``pc_initial`` from a Maxwell-Boltzmann distribution. Leave as ``None`` to use the provided initial coordinates and momenta. Default: ``None``.
   * - ``random_seed``
     - Seed for the RNG that drives the optional Maxwell-Boltzmann sampling and Langevin kicks. Use a fixed integer for reproducibility. Default: ``None``.
   * - ``shift_dipole_baseline``
     - When ``True`` subtract the initial dipole so the simulation starts from a zero baseline (helps with large permanent dipoles). Default: ``False``.
   * - ``molecule_half_step``
     - When ``True`` extrapolate molecular responses from half-step data (use ``False`` for full-step drivers). Default: ``False``.
   * - ``record_history``
     - When ``True`` store histories for time, field, momentum, drive, and net molecular response.
       Default: ``True``.
   * - ``include_dse``
     - When ``True`` add the dipole self-energy correction to the field returned to the molecules and account for it in the cavity energy. Default: ``False``.
   * - ``excite_ph``
     - When ``True`` the external ``drive`` term is applied to the cavity equation of motion. Set to ``False`` to suppress the drive in the photonic equation. Default: ``True``.
   * - ``excite_mol``
     - When ``True`` the external ``drive`` term is added to the effective electric field returned to the molecules along ``coupling_axis`` (direct molecular excitation). Default: ``False``. Setting both ``excite_ph`` and ``excite_mol`` to ``True`` drives both subsystems and triggers a console warning.

Returned data
-------------

Calling `simu`=:class:`~maxwelllink.em_solvers.single_mode_cavity.SingleModeSimulation` with ``record_history=True``
populates:

- :attr:`simu.time_history` – time stamps in atomic units.
- :attr:`simu.qc_history` – cavity coordinate :math:`q_c(t)`.
- :attr:`simu.pc_history` – cavity momentum :math:`\dot{q}_c(t)`.
- :attr:`simu.drive_history` – external drive values.
- :attr:`simu.molecule_response_history` – summed molecular response along ``coupling_axis``.
- :attr:`simu.energy_history` – total energy of the cavity and coupled molecules (requires ``record_history=True``).

Each :class:`~maxwelllink.molecule.molecule.Molecule` keeps
:attr:`~maxwelllink.molecule.molecule.Molecule.additional_data_history`, which records driver
data (e.g., TLS populations, energies, timestamps). Socket drivers may
append extra JSON payloads through the hub; embedded drivers populate the same
history via :meth:`append_additional_data`.

Notes
-----

- Provide either ``steps`` or ``until`` to :meth:`SingleModeSimulation.run`, not both.
- Socket-mode molecules must all bind to the same :class:`~maxwelllink.sockets.sockets.SocketHub`;
  the simulation waits until every driver acknowledges initialization.
- Setting ``record_history=False`` avoids list allocations for throughput-critical runs.
