Meep Socket-Susceptibility Solver
===================================
.. warning::
**Experimental feature and not yet production-ready.** This grid-level
coupling is under active development. For production simulations, refer to the standard Meep
coupling described in :doc:`meep`.
This feature couples **plain** `Meep `_
simulations to **MaxwellLink** molecular drivers directly at the C level of
the FDTD time-stepping loop.
.. note::
For a medium carrying ``MXLSocketSusceptibility``, Meep delegates the
polarization update at every active grid point :math:`\mathbf{r}_g` to a
molecular driver. At each FDTD time step, the C client transmits the
rescaled local electric field (converted to atomic units)
.. math::
\widetilde{\mathbf{E}}_{g}(t) = \gamma\, \mathbf{E}(\mathbf{r}_g, t)
to socket molecule :math:`g`. The driver then propagates its molecular model
for one FDTD time step and returns :math:`d\boldsymbol{\mu}_{g}/dt`, which
Meep deposits as the polarization-current density of the corresponding grid
cell (volume :math:`\Delta V`):
.. math::
\partial_t \mathbf{P}(\mathbf{r}_g, t) = \frac{\gamma}{\Delta V}\, \frac{d\boldsymbol{\mu}_{g}(t)}{dt}.
All Meep :math:`\leftrightarrow` atomic-unit conversions are handled
internally through ``time_units_fs``. The rescaling factor :math:`\gamma`
(``rescaling_factor``) is applied *symmetrically* to the outgoing field and
the returned response, which systematically amplifies or reduces the light–matter coupling.
Thus, a driver simulating :math:`N_{\mathrm{sim}}`
molecules faithfully represents the :math:`N_{\mathrm{phys}}` physical
molecules residing in its grid cell when
.. math::
\gamma = \sqrt{N_{\mathrm{phys}} / n_{\mathrm{sim}}},
which preserves the collective (bright-state) light–matter coupling in the
linear-response limit.
Comparison with the ``MeepSimulation`` route
--------------------------------------------
.. list-table::
:header-rows: 1
* -
- :doc:`meep`
- Socket susceptibility at grid level
* - Meep entry point
- ``mxl.MeepSimulation`` wrapper around ``meep.Simulation``
- Plain ``mp.Simulation`` (``fdtdbath-meep`` build)
* - Molecule placement
- Each ``mxl.Molecule`` carries ``center``/``size``/``sigma`` and spans
multiple grid points via a regularized kernel
- Every active grid point of the socket medium constitutes one socket
molecule; placement follows standard Meep geometry and material
assignment
* - Coupling level
- Python step function inserted between Meep time steps; molecules enter
as **current sources** :math:`\mathbf{J}_{\mathrm{mol}}`
- C-level susceptibility within Meep's material update; molecules enter
as **material polarization** :math:`\mathbf{P}`
* - Driver connection
- Socket (``SocketHub``/``AggregatedSocketHub``) or embedded drivers
- Socket only (``SusceptibilitySocketHub`` or
``AggregatedSusceptibilitySocketHub``)
* - Typical use cases
- Small numbers of emitters: spontaneous emission, resonance energy
transfer, superradiance
- Macroscopic ensembles: collective (vibrational) strong coupling with
realistic molecular matter filling a cavity or nanostructure
Requirements
------------
.. note::
This feature relies on a modified Meep FDTD code developed by the TEL Research Group `fdtdbath-meep `_. Users can install this modified Meep code following:
.. code-block:: bash
conda install --override-channels -c tel-research -c conda-forge "pymeep-fdtdbath * mpi_mpich_*"
This modified version supports Linux and MacOS M1 (osx-arm64) chips (Python ``3.11`` to ``3.13``). Only the MPI version is built for large-scale calculations.
- The modified Meep build `fdtdbath-meep
`_ must be installed; see above. To
verify a correct installation:
.. code-block:: python
import meep as mp
assert hasattr(mp, "MXLSocketSusceptibility")
- No additional dependency is required on the **MaxwellLink** side.
- Only TCP connections are supported: the Meep C client connects by host and
port, so UNIX domain sockets cannot be used between Meep and the hub.
Usage
-----
EM side: ``SusceptibilitySocketHub``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
:class:`~maxwelllink.sockets.susceptibility.SusceptibilitySocketHub` is the
recommended hub for most applications. A single TCP listener serves both the
Meep ranks and all molecular drivers directly.
.. code-block:: python
import meep as mp
import maxwelllink as mxl
# Publish the endpoint for the drivers (two lines: host, then port).
host, port = mxl.get_available_host_port(
localhost=False, save_to_file="tcp_host_port_info.txt"
)
hub = mxl.SusceptibilitySocketHub(
host=host,
port=port,
timeout=7200.0, # generous for HPC queueing
latency=0.01,
driver_count_file="num_socket_molecule", # written once Meep connects
)
# Attach the socket susceptibility to an ordinary Meep medium.
socket_susc = mp.MXLSocketSusceptibility(
rescaling_factor=8.89, # sqrt(N_phys / n_sim); see the note above
time_units_fs=3.33564, # 1 Meep time unit in fs (unit length as 1 um here)
hub=hub,
real_field_only=True,
)
molecular_medium = mp.Medium(
epsilon=1.7**2,
E_susceptibilities=[socket_susc],
)
# Every grid point inside this block becomes one socket molecule:
# a 0.08 x 0.08 um^2 patch at resolution 125 -> 10 x 10 = 100 drivers.
geometry = [
mp.Block(
material=molecular_medium,
size=mp.Vector3(0.08, 0.08, 0),
center=mp.Vector3(),
),
]
sim = mp.Simulation( # plain Meep simulation
cell_size=mp.Vector3(2, 2, 0),
geometry=geometry,
sources=[], # any native Meep sources
boundary_layers=[mp.PML(0.4)],
resolution=125,
)
sim.run(until=150)
All native Meep capabilities remain fully available.
Driver side
^^^^^^^^^^^
Any socket-mode **MaxwellLink** driver can serve a socket molecule, including
the Python ``mxl_driver`` models (:doc:`../drivers/index`), third-party drivers such as
the LAMMPS with ``fix mxl`` (:doc:`../drivers/lammps`).
Drivers connect to
the hub's TCP endpoint in the same manner as in the ``SocketHub`` workflows
(:doc:`../usage`); the only additional consideration is *how many* drivers to
launch.
As the number of grid points allocated is controlled by the Meep code itself,
the hub writes the total number of socket molecules requested by Meep
to ``driver_count_file`` (by default, ``num_socket_molecule``), which a SLURM
job array can read to size itself accordingly:
.. code-block:: bash
#!/bin/bash
#SBATCH --array=0-255 # >= expected number of socket molecules
# Wait until the EM job publishes its endpoint and driver count.
until [[ -s tcp_host_port_info.txt && -s num_socket_molecule ]]; do sleep 1; done
HOST=$(sed -n '1p' tcp_host_port_info.txt)
PORT=$(sed -n '2p' tcp_host_port_info.txt)
N=$(tr -d '[:space:]' < num_socket_molecule)
# Array tasks beyond the required driver count simply exit.
(( SLURM_ARRAY_TASK_ID >= N )) && exit 0
mxl_driver --model sho --address ${HOST} --port ${PORT} \
--param "omega=0.0906,mu0=187.0819866,orientation=2"
For molecular-dynamics ensembles, replace the final command with one LAMMPS
client per array task (each in its own working directory), where the LAMMPS
input couples through ``fix mxl``::
fix 1 all mxl HOST PORT reset_dipole
fix 2 all nve
As with any multi-job workflow, submit the EM job first and the driver array as
a dependent job (see :doc:`../usage`).
Validating against a classical Lorentzian medium
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
:meth:`~maxwelllink.sockets.susceptibility.SusceptibilitySocketHub.lorentzian_conversion`
provides a convenient route for validation by mapping a classical
``mp.LorentzianSusceptibility(frequency=..., sigma=...)`` onto equivalent SHO
socket drivers:
.. code-block:: python
conv = hub.lorentzian_conversion(
frequency=0.355, # Lorentzian resonance in meep units
sigma=0.01, # Lorentzian oscillator strength in meep units
resolution=125,
dimensions=2,
time_units_fs=3.33564,
orientation=2,
)
socket_susc = mp.MXLSocketSusceptibility(
rescaling_factor=conv["rescaling_factor"],
time_units_fs=3.33564,
hub=hub,
)
print(conv["driver_command"]) # launch this once per socket molecule
Parameters
----------
``mp.MXLSocketSusceptibility`` (Meep side):
.. list-table::
:header-rows: 1
* - Name
- Description
* - ``rescaling_factor``
- Symmetric bright-state coupling scale :math:`\lambda`, applied to both
the electric field transmitted to the driver and the returned
:math:`d\boldsymbol{\mu}/dt`. To represent :math:`N` physical molecules
per grid cell using a single driver simulating :math:`n` molecules, set
this to :math:`\sqrt{N/n}`. Default: ``1.0``.
* - ``time_units_fs``
- Number of femtoseconds corresponding to one Meep time unit (default:
``0.1``). This value must be consistent with any unit conversions
applied on the driver side.
* - ``hub``
- Required keyword argument: a
:class:`~maxwelllink.sockets.susceptibility.SusceptibilitySocketHub` or
:class:`~maxwelllink.sockets.aggregated_susceptibility.AggregatedSusceptibilitySocketHub`
instance that supplies the ``host``, ``port``, and ``timeout``
parameters for Meep's internal socket connection.
* - ``label``
- Optional Meep-side label used in molecule-map output and material
equivalence checks; never transmitted to **MaxwellLink**. Default:
``""``.
* - ``real_field_only``
- When ``True`` (default), only the real part of the electric field
drives the socket molecules. When ``False``, complex-field Meep runs
(e.g. cylindrical simulations with ``m != 0``, runs with a
``k_point``, or those using ``force_complex_fields=True``) create
**independent socket molecules for the real and imaginary field
components**, thereby doubling the driver count.
:class:`~maxwelllink.sockets.susceptibility.SusceptibilitySocketHub` (MaxwellLink side):
.. list-table::
:header-rows: 1
* - Name
- Description
* - ``host`` / ``port``
- TCP bind endpoint (default port: ``31415``; setting ``port=0`` requests
an OS-assigned ephemeral port). Use
:func:`~maxwelllink.sockets.sockets.get_available_host_port` with
``localhost=False`` for multi-node deployments.
* - ``timeout``
- Socket timeout in seconds applied to bound clients (default:
``60000.0``). This should be set longer than the maximum expected wait
time for all drivers to come online, accounting for factors such as
SLURM queueing delays.
* - ``latency``
- Polling interval in seconds for the accept/bind loops (default:
``0.05``).
* - ``driver_count_file``
- Path to a file that will receive the total socket-molecule count
requested by Meep, written once as a single integer (default:
``"num_socket_molecule"``). SLURM driver arrays typically read this
file to determine the appropriate array size. Set to ``None`` to
disable.
Returned data
-------------
- Standard Meep data channels remain unaffected.
- Molecular observables are stored by each individual driver process (e.g.
LAMMPS trajectory files and log output in each driver's working directory).
Because no ``mxl.Molecule`` objects exist in the Meep process, there is no
``additional_data_history`` available on the EM side.
Notes
-----
- The hub must be constructed **before** ``mp.Simulation`` begins execution.
- The total number of drivers equals the number of grid points within the
socket medium (doubled in complex-field runs when ``real_field_only=False``).
- Under MPI parallelism, each Meep rank opens its own connection to the hub
and requests drivers only for the grid points it owns, so this feature is compatible with MPI.
- Physically, each socket molecule represents *the molecular ensemble residing
within a single FDTD grid cell*. For example, coupling bulk water
(33.4 molecules/nm\ :sup:`3`) at an 8 nm grid spacing yields approximately
17,000 physical water molecules per cell. Simulating 216 water molecules per
LAMMPS driver then requires ``rescaling_factor = sqrt(17000/216)``.
.. seealso::
- :doc:`meep` — molecule-level Meep coupling
(``mxl.Molecule`` + ``mxl.MeepSimulation``).
- :doc:`../drivers/index` and :doc:`../drivers/lammps` for
molecular drivers capable of serving socket molecules.