Usage Guide
===========
This guide walks through **three** ways to couple :class:`~maxwelllink.molecule.molecule.Molecule` with
EM solvers using a single TLS molecule as the working example. Users can
check tutorials under :doc:`tutorials/index` for more detailed examples and
explanations.
.. note::
**MaxwellLink** ships a legacy :class:`~maxwelllink.molecule.molecule_legacy.SocketMolecule` API
used throughout ``tests/test_tls``. The patterns below focus on the unified
:class:`~maxwelllink.molecule.molecule.Molecule` interface, which works for both socket and
embedded (non-socket) drivers.
When using `Meep `_ FDTD as the EM solver, below we will introduce three ways
to run self-consistent light-matter simulations with a TLS molecule.
Single process (no sockets)
---------------------------
The simplest setup instantiates the TLS driver inside the `Meep `_ process. This
avoids socket traffic entirely and is ideal for prototyping or small-scale
benchmarks.
.. code-block:: python
import meep as mp
import maxwelllink as mxl
tls = mxl.Molecule(
driver="tls",
center=mp.Vector3(0, 0, 0),
size=mp.Vector3(1, 1, 1),
sigma=0.1,
dimensions=2,
driver_kwargs=dict(
omega=0.242, # TLS frequency (a.u.)
mu12=187.0, # dipole moment (a.u.)
orientation=2, # Ez
pe_initial=1e-3, # initial excited population
),
)
sim = mxl.MeepSimulation(
molecules=[tls],
time_units_fs=0.1,
cell_size=mp.Vector3(8, 8, 0),
geometry=[],
sources=[],
boundary_layers=[mp.PML(3.0)],
resolution=10,
)
sim.run(until=90)
Within the same interpreter, you can analyze the TLS diagnostics through
``tls.additional_data_history``.
Local multi-process run (UNIX socket)
-------------------------------------
When we want `Meep `_ and the molecular driver to run as separate processes on the
same machine, use a UNIX domain socket. The hub listens on ``/tmp/socketmxl_``
and the driver connects with ``--unix``.
`Meep `_ script:
.. code-block:: python
import meep as mp
import maxwelllink as mxl
hub = mxl.SocketHub(unixsocket="tls_demo", timeout=10.0, latency=1e-4)
tls = mxl.Molecule(
hub=hub,
center=mp.Vector3(0, 0, 0),
size=mp.Vector3(1, 1, 1),
sigma=0.1,
dimensions=2,
)
sim = mxl.MeepSimulation(
hub=hub,
molecules=[tls],
time_units_fs=0.1,
cell_size=mp.Vector3(8, 8, 0),
boundary_layers=[mp.PML(3.0)],
resolution=10,
)
sim.run(until=90)
Driver command (same laptop/workstation):
.. code-block:: bash
mxl_driver --model tls --unix --address tls_demo \
--param "omega=0.242, mu12=187, orientation=2, pe_initial=1e-3"
UNIX sockets avoid port collisions and usually takes less time for communication.
**MaxwellLink** waits for the driver to connect before advancing the simulation.
Distributed run (TCP socket)
----------------------------
For multi-node deployments (e.g., `Meep `_ on one node and ``mxl_driver`` on another),
use a TCP socket. Let the OS pick a free port to prevent clashes.
Meep script:
.. code-block:: python
import meep as mp
import maxwelllink as mxl
from maxwelllink import sockets as mxs
_, port = mxs.get_available_host_port()
hub = mxl.SocketHub(host="", port=port, timeout=30.0, latency=1e-4)
print(f"SocketHub listening on port {port}. Share the host/IP with the driver.")
tls = mxl.Molecule(
hub=hub,
center=mp.Vector3(0, 0, 0),
size=mp.Vector3(1, 1, 1),
sigma=0.1,
dimensions=2,
)
sim = mxl.MeepSimulation(
hub=hub,
molecules=[tls],
time_units_fs=0.1,
cell_size=mp.Vector3(8, 8, 0),
boundary_layers=[mp.PML(3.0)],
resolution=10,
)
sim.run(until=90)
Setting ``host=\"\"`` binds the hub to all interfaces (equivalent to ``0.0.0.0``).
We need to share the public hostname or IP of the `Meep `_ node and ``port``
with the driver.
Driver command (run on the remote node):
.. code-block:: bash
mxl_driver --model tls --address --port \
--param "omega=0.242, mu12=187, orientation=2, pe_initial=1e-3"
Replace ```` with the reachable address of the `Meep `_ node. Open
firewall ports if required by your cluster configuration.
Distributed run in HPC (TCP socket)
------------------------------------
For HPC users, if the EM solver and molecular driver run on separate nodes of a SLURM HPC system, the MaxwellLink
job can be submitted as a two-step dependent SLRUM job as follows:
.. code-block:: bash
job_main_id=$(sbatch submit_main.sh | awk '{print $4}')
sbatch --dependency=after:${job_main_id} submit_driver.sh
Here, two SLURM submission scripts
(``submit_main.sh`` and ``submit_driver.sh``) launch the EM solver main process and the driver process, respectively.
In the ``submit_main.sh`` bash script, the main MaxwellLink Python input (say, ``em_run.py``) contains the
following code to automatically find an available host and port for the TCP socket hub:
.. code-block:: python
import maxwelllink as mxl
from maxwelllink import sockets as mxs
host, port = mxs.get_available_host_port(localhost=False, save_to_file="tcp_host_port_info.txt")
# time out for HPC environments may need to be relatively long
hub = mxl.SocketHub(host=host, port=port, timeout=200.0, latency=1e-4)
The available host and port will be saved to a text file (``tcp_host_port_info.txt`` here)
that can be read by the driver.
In the ``submit_driver.sh`` bash script, the driver can read the host and port information from the text file and connect to the hub as follows:
.. code-block:: bash
# wait for the main job to start and write the host and port info
sleep 10s
HOST_PORT_FILE="tcp_host_port_info.txt"
if [[ ! -f "$HOST_PORT_FILE" ]]; then
echo "Error: Host and port info file '$HOST_PORT_FILE' not found!"
exit 1
fi
HOST=$(sed -n '1p' "$HOST_PORT_FILE")
PORT=$(sed -n '2p' "$HOST_PORT_FILE")
mxl_driver --model tls --address $HOST --port $PORT --param "..."
Then, only after the main job starts and writes the host and port information to the text file, the
driver job will be submitted and started to connect to the hub.
For large-scale simulations, multiple drivers can be launched on different nodes. For example, the following
input bash script launches one main job and two driver jobs. The two driver jobs will start only after the main job starts.
.. code-block:: bash
job_main_id=$(sbatch submit_main.sh | awk '{print $4}')
sbatch --dependency=after:${job_main_id} submit_driver.sh
sbatch --dependency=after:${job_main_id} submit_driver.sh
Inspecting TLS output
---------------------
In all the above workflows, after ``sim.run`` completes, the code below recovers the excited-state
population and the simulation time in atomic units.
.. code-block:: python
import numpy as np
population = np.array([entry["Pe"] for entry in tls.additional_data_history])
time_au = np.array([entry["time_au"] for entry in tls.additional_data_history])
We can then compare the electronic excited-state trajectory to the analytical golden-rule result as shown in
:doc:`tutorials/notebook/meep_tls_spontaneous_emission`.
MPI execution
-------------
**MaxwellLink** detects MPI automatically. Only rank 0 (the master) communicates with drivers
while field integrals and returned molecular response are broadcast to worker ranks via
``mpi4py``. We can launch a MPI run with:
.. code-block:: bash
mpirun -np 4 python run_tls.py
Driver restarts
---------------
If a driver disconnects unexpectedly, the hub pauses the EM solver time loop and
waits for the driver to reconnect. Enabling ``checkpoint=true`` and
``restart=true`` in the driver parameters lets expensive molecular dynamics
recover from transient failures without restarting the EM simulation.
Single-mode cavity emulator
---------------------------
For quick prototyping without launching `Meep `_, use
:class:`~maxwelllink.em_solvers.single_mode_cavity.SingleModeSimulation`. It models the EM field as one damped
harmonic oscillator in atomic units and couples to the same ``Molecule``
objects. Example:
.. 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-3),
)
sim = mxl.SingleModeSimulation(
dt_au=0.05,
frequency_au=0.242,
damping_au=1e-3,
molecules=[tls],
drive=0.0,
coupling_strength=1.0,
)
sim.run(steps=500)
print(tls.additional_data_history[-1]["Pe"])