plesty.pow_pl ============= .. py:module:: plesty.pow_pl .. autoapi-nested-parse:: Power-dependent PL experiment module. Submodules ---------- .. toctree:: :maxdepth: 1 /reference/plesty/pow_pl/__main__/index /reference/plesty/pow_pl/analysis/index /reference/plesty/pow_pl/experiment/index /reference/plesty/pow_pl/rig/index /reference/plesty/pow_pl/viz/index Attributes ---------- .. autoapisummary:: plesty.pow_pl.PlestyPowPlExperiment Classes ------- .. autoapisummary:: plesty.pow_pl.Experiment Functions --------- .. autoapisummary:: plesty.pow_pl.load_config Package Contents ---------------- .. py:class:: Experiment(devices: Optional[plesty.pow_pl.rig.PowPlRig] = None, config: Optional[dict[str, Any]] = None, run_root: str | pathlib.Path | None = None, **kwargs: Any) Bases: :py:obj:`plesty.lib.experiment.Experiment` Measure the power-dependent spectral series of every catalogued QD. Configuration comes from a plain dict (loaded from ``config/*.yaml`` by ``__main__``) and is frozen into the plan, so a resumed run refuses a changed configuration. Instruments are accessed exclusively through the :class:`~.rig.PowPlRig` composite passed at construction. Initialize the experiment. :param devices: The instrument rig (HWP stage, spectrometer, powermeter); may be ``None`` for plan-only inspection. :param config: Measurement configuration (see ``config/default.yaml``); recorded in the plan. :param run_root: Directory under which each run creates its data directory — :data:`RUN_ROOT` under ``PLESTY_DATA`` for a run started by the command line. :param \*\*kwargs: Forwarded to the base ``Experiment`` (``name``, ``max_retries``, ``retry_sleep``, ``data_root``). .. py:attribute:: rig :value: None .. py:attribute:: config .. py:attribute:: _announced_qd :type: Optional[str] :value: None .. py:attribute:: _configured :type: Optional[tuple] :value: None .. py:attribute:: _grating_center_nm :type: Optional[float] :value: None .. py:attribute:: _rows_done :value: 0 .. py:attribute:: _rows_total :value: 0 .. py:method:: build_plan() -> plesty.lib.experiment.Plan Expand the configuration into one atomic step per QD per HWP angle. Each step commands one absolute angle (absolute targets, so no drift accumulates over the series) and one simultaneous spectrometer + powermeter acquisition; resume granularity is the single angle. With a list of exposures, every angle gets one step per exposure, in the order listed, before the plate moves on. :returns: The frozen measurement schedule; step ids look like ``qd[QD-01]-hwp[004.00]``, or ``qd[QD-01]-hwp[004.00]-exp[0.500s]`` with several exposures. :raises KeyError: If a catalogue entry has no ``id``. :raises ValueError: If the HWP sweep configuration is invalid. .. py:method:: record_rig() -> dict[str, Any] Record the instrument identities for replayability. :returns: Each device's identity string (model, serial, firmware) and the UTC time it was recorded. :raises RuntimeError: If the experiment was constructed without a rig. .. py:method:: measure_row(sample: str, qd: str, x_um: float, y_um: float, angle_deg: float, exposure_s: float, frames: int, pm_wavelength_nm: Optional[float] = None, pm_averaging: Optional[int] = None, pm_delay_s: float = 0.4, settle_s: float = 0.0) -> dict[str, Any] Rotate the HWP to one angle, then acquire the row. With powermeter parameters (a plan from a config with a ``powermeter`` block) the spectrum and the power are acquired together; without them the row is the spectrum alone. Settings are pushed to the instruments once per process — on the first row, or whenever a step carries different values — so a resumed run configures itself without rewriting identical settings on every row. :param sample: Sample identifier, recorded in the result. :param qd: Quantum-dot identifier from the catalogue. :param x_um: Catalogued chip-frame X coordinate in micrometres. :param y_um: Catalogued chip-frame Y coordinate in micrometres. :param angle_deg: Commanded absolute HWP angle in degrees. :param exposure_s: Spectrometer exposure in seconds. :param frames: Frames stored per acquisition (row is frame 0). :param pm_wavelength_nm: Powermeter correction wavelength in nanometres (written once per configure); ``None`` measures the spectrum without a power reference. :param pm_averaging: Powermeter samples per reading (~3 ms each). :param pm_delay_s: Delay before the power sample so it lands inside the exposure window. :param settle_s: Pause after the completed step — breathing room for the instrument stacks between rows (defaults to 0 for plans frozen before the knob existed). :returns: identifiers, coordinates, commanded and reported HWP angle, the shared-disk path of the saved frame, the grating center wavelength, and — when measured — the simultaneous optical power. :rtype: A JSON-serializable record :raises RuntimeError: If the experiment was constructed without a rig, or if the plan carries power parameters but the rig has no powermeter. .. py:method:: analyze_qd(qd: str) -> dict[str, Any] Analyse one dot's power series and save the results beside the run. Reads the dot's spectra back from this run's records, then the two analyses of :mod:`.analysis`: the integrated counts and every line's fitted area against the power. The line fit is saved as ``analysis/_line_fit`` and the report as ``analysis/.report.json``, which the bench's run report shows without importing this module. :param qd: Quantum-dot identifier. :returns: Each line's position and exponent, the integrated exponent, the saturation power when the data constrain it, the line fit's warnings, and where the results were saved. :raises RuntimeError: If the run has not started. .. py:method:: _grating_center() -> Optional[float] Return the grating center wavelength, queried once per process. The spectrometer's LightField IPC link is the fragile part of the rig — static settings are read (and written) once, not per row. A value the spectrometer cannot report (empty or non-numeric) is recorded as ``None`` with one warning; metadata must not abort a sweep whose frames and power are fine. .. py:method:: _ensure_at_qd(qd: str, x_um: float, y_um: float) -> None Positioning placeholder — the dot is assumed to be localized. Logs once per dot, not per angle. The upcoming localization stage will move the AMC300 to the catalogue coordinates, verify via the DMK camera, and fine-steer the PI mirror before the sweep starts. .. py:method:: setup() -> None :async: Connect the instruments, route the frames into ``raw/spec``, home the wave plate. The frame directory is set through the device's own data path rather than by writing a vendor parameter: the spectrometer resolves it under its bench's folder on the Plesty share, creates it, and answers with that folder from the share, where each frame is recorded (:meth:`route`). Only the spectrometer is routed — the stage and the powermeter write nothing. :raises RuntimeError: If this machine is not told where the Plesty share is, so it could not read the frames back. .. py:method:: teardown() -> None :async: Park the wave plate at 0 deg (best effort), then disconnect. .. py:data:: PlestyPowPlExperiment .. py:function:: load_config() -> dict[str, Any] Load the packaged module configuration (constants and running defaults). :returns: The parsed ``config.yaml`` shipped with this module.