Source code for solprop.driver.material_output

# SPDX-FileCopyrightText: 2026 John C. Travers and contributors
#
# SPDX-License-Identifier: GPL-3.0-or-later

"""Record the material and physical reconstruction data needed by saved-run analysis.

Two records are built here. :func:`material_record` and :func:`processing_record`
describe a run's mixture for the configuration and for processing after reload.
:func:`material_manifest` collects a run's **material manifest** (SEP 3, output schema
8): every optmat-backed object the run carries, wherever it sits -- the mixture, a
mode's fill, a wall, a cladding, a core, a general guide's material regions, a bulk
medium -- found by walking the run's own objects, with the selection and the policies
in force.
"""

import dataclasses
import enum
import functools
import hashlib
import json
import math
import types
from collections.abc import Iterator, Mapping, Sequence
from typing import Final, cast

import numpy as np
from jaxtyping import Float

from solprop.constants import EPS0, C
from solprop.grid import TimeGrid
from solprop.modes.fill import GasFillMixin
from solprop.modes.material import GasKerr, MaterialRegion
from solprop.modes.protocol import Mode
from solprop.nonlinear.gnlse import GnlseProblem
from solprop.nonlinear.optmat_raman import OptmatRamanResponse
from solprop.nonlinear.optmat_rates import OptmatRate, PeakFieldReport
from solprop.nonlinear.responses import DispersiveKerrResponse
from solprop.physdata import Mixture, _optmat
from solprop.physdata.glasses import GlassIndex, OptmatGlass
from solprop.physdata.mixtures import DILUTE_SCALING_THRESHOLD
from solprop.physdata.selection import (
    MaterialLog,
    MaterialManifest,
    MaterialRecord,
    MaterialSelection,
)
from solprop.physdata.sellmeier import GasSusceptibility, OptmatSusceptibility
from solprop.physdata.species import Species
from solprop.profiles import Constant
from solprop.translate.linop import power_weight_factors
from solprop.translate.problem import KernelProblem
from solprop.translate.responses import DispersiveKerrTerms

__all__ = [
    "MIXTURE_FILTER_RULE",
    "material_manifest",
    "material_record",
    "processing_record",
]

_LEAVES = (
    str,
    bytes,
    bool,
    int,
    float,
    complex,
    np.ndarray,
    np.generic,
    enum.Enum,
    type,
    types.ModuleType,
    types.FunctionType,
    types.BuiltinFunctionType,
)
"""Values the manifest's walk never descends into. A function, a lambda or a closure
is one: what it captures is not walked, so a material reached only through a callable's
closure is not found."""

_STOP_PACKAGES: Final = frozenset(
    {
        "optmat",
        "numpy",
        "scipy",
        "h5py",
        "builtins",
        "logging",
        "threading",
        "jaxtyping",
        "CoolProp",
        "pyfluids",
        "numba",
    }
)
"""Packages whose objects the walk does not enter. optmat's own objects are recorded
through the solprop adapters that hold them, never walked; the rest hold no material."""


[docs] def material_record(mixture: Mixture) -> dict[str, object]: """Record explicit composition units, paths and complete supplied species data. Parameters ---------- mixture : Mixture Ordered populations and temperature. Returns ------- dict Constructor data or declared path samples, together with every species model. """ return mixture.serialise() | { "species_data": tuple(component.species for component in mixture.components) }
[docs] def processing_record( problem: KernelProblem, grid: TimeGrid, mixture: Mixture, modes: Sequence[Mode] ) -> dict[str, object]: """Record compact data for species absorption and node fluence after reload. Parameters ---------- problem : KernelProblem Translated geometry and save positions, with no backend state. grid : TimeGrid Time and angular-frequency axes and temporal padding scale. mixture : Mixture The same ordered material used by the operator and responses. modes : sequence of Mode Capillary modes, or an empty sequence for spatial propagation. Returns ------- dict Density in m^-3 at requested planes, absorption cross sections in m^2 in the active band, and physical field synthesis with temporal padding removed. A replaced total loss is recorded as unavailable species attribution. """ positions = np.concatenate(([0.0], problem.stepper.save_planes)) transform = problem.transform geometry = transform.geometry omega = np.sort(grid.omega[grid.active_mask]) cross_sections = _cross_sections(mixture, modes, omega) # The weight at every plane, factored: the dense stack is n_z state-sized rows of # JSON in a hashed record (26 MB for 201 planes of four modes on 2049 bins). terms, basis = (None, None) if geometry is None: terms, basis = power_weight_factors(problem, positions) return { "labels": mixture.labels, "z_m": positions, "density_m-3": mixture.density(positions).T, "omega_rad_s": omega, "cross_section_m2": cross_sections, "absorption_definition": "rho_s omega Im(gamma_s)/c, first order in chi", "absorption_unavailable": None if cross_sections is not None else "callable total loss has no species attribution", "node_synthesis": transform.synthesis / grid.pad_scale, "power_weight_terms": terms, "power_weight_basis": basis, "n_nodes": transform.n_nodes, "node_polarisations": transform.n_pol * problem.shape[2], "taper": np.ones(positions.size) if transform.taper_scale is None else np.asarray([float(transform.taper_scale(float(z))) for z in positions]), "column_energy_weights": np.ones(problem.shape[1]) if geometry is None else EPS0 * C / 2 * geometry.spectral_weights, }
def _cross_sections( mixture: Mixture, modes: Sequence[Mode], omega: np.ndarray ) -> np.ndarray | None: """Factor the mode's first-order attribution into density-independent rows. Parameters ---------- mixture : Mixture Ordered material populations. modes : sequence of Mode Capillary modes, or an empty sequence for bulk propagation. omega : numpy.ndarray Positive angular frequencies in rad/s within the active band. Returns ------- numpy.ndarray or None Cross sections in m^2, shape (n_species, n_modes_or_one, n_omega), or None when the total loss policy supplies no species attribution. """ if not modes: return np.asarray( [ [ np.zeros_like(omega) if ( ( isinstance( component.species.susceptibility, GasSusceptibility ) and not component.species.susceptibility.absorbing ) or ( component.density.is_constant and float(component.density(np.array([0.0]))[0]) == 0.0 ) ) else omega * np.imag(component.species.susceptibility.susceptibility(omega)) / C ] for component in mixture.components ] ) if any(not isinstance(mode, GasFillMixin) or callable(mode.loss) for mode in modes): return None # An absent population must not demand optical data outside its table support. unit_density = mixture.with_densities( { component.label: Constant( 0.0 if component.density.is_constant and float(component.density(np.array([0.0]))[0]) == 0.0 else 1.0 ) for component in mixture.components } ) return np.stack( [ mode.with_mixture(unit_density).absorption_by_species(omega) for mode in modes if isinstance(mode, GasFillMixin) ], axis=1, ) def _children(value: object) -> list[object]: """Return what the manifest's walk visits next from one object. Parameters ---------- value : object A dataclass, a ``functools.partial``, a bound method, or any object with a ``__dict__``. Returns ------- list A dataclass's field values in declaration order; a partial's function, arguments and keywords; a bound method's instance; otherwise the instance attributes, none for an object that has none. """ if isinstance(value, functools.partial): return [value.func, value.args, value.keywords] if isinstance(value, types.MethodType): return [value.__self__] if dataclasses.is_dataclass(value): return [getattr(value, one.name, None) for one in dataclasses.fields(value)] return list(getattr(value, "__dict__", {}).values()) def _walk(roots: Sequence[object]) -> Iterator[object]: """Yield every object reachable from the roots, once each. The walk descends into mappings, sequences, dataclasses, partials, bound methods and any object with a ``__dict__`` -- solprop's own, a caller's subclass, a user's mode -- but never into an array, a number, a string, a function or an object of a package in :data:`_STOP_PACKAGES`. Each object is visited once, so a cycle ends, and the order is fixed by the objects themselves. Parameters ---------- roots : sequence The run's objects: its configuration, modes, responses, media and grids. Yields ------ object Each reachable object outside the stopped packages, in depth-first order. """ seen: set[int] = set() stack: list[object] = list(reversed(roots)) while stack: value = stack.pop() if value is None or isinstance(value, _LEAVES) or id(value) in seen: continue seen.add(id(value)) if isinstance(value, Mapping): stack.extend(reversed(list(value.values()))) continue if isinstance(value, (list, tuple, set, frozenset)): items = ( sorted(value, key=repr) if isinstance(value, (set, frozenset)) else value ) stack.extend(reversed(list(items))) continue if ( not isinstance(value, (functools.partial, types.MethodType)) and type(value).__module__.split(".")[0] in _STOP_PACKAGES ): continue yield value stack.extend(reversed(_children(value))) def _single_species_fills( found: Sequence[object], ) -> list[tuple[Species, GasFillMixin]]: """Return each distinct single-species fill among the run's modes. Parameters ---------- found : sequence The objects the walk reached. Returns ------- list of tuple ``(species, mode)`` for each gas fill without a mixture whose species carries optmat records, one per distinct species, density path and temperature: the modes of one guide share their fill. """ fills: list[tuple[Species, GasFillMixin]] = [] seen: list[tuple[Species, str, object]] = [] for value in found: if not isinstance(value, GasFillMixin): continue material = value.fill_species if material is None or not material.material_records: continue key = (material, repr(value.density), value.temperature) if key in seen: continue seen.append(key) fills.append((material, value)) return fills def _records(value: object) -> tuple[MaterialRecord, ...]: """Return an object's optmat records, or none for one that carries none. Parameters ---------- value : object Any of solprop's objects. Returns ------- tuple of MaterialRecord Its ``material_records``, where it has them. """ found = getattr(value, "material_records", ()) return tuple(found) if isinstance(found, tuple) else () def _solid_entry(glass: OptmatGlass, log: MaterialLog | None) -> dict[str, object]: """Return an optmat solid's manifest entry. Parameters ---------- glass : OptmatGlass The adapter. log : MaterialLog or None The run's evaluation log, whose dropped loss is the run's own; ``None`` takes what the adapter has dropped over its life. Returns ------- dict Its material, axis, model, digest, transparency policy and measurement. """ entry = glass.material_records[0] return { "material": glass.name, "axis": glass.axis, "model": entry.name, "digest": entry.digest, "transparent": glass.transparent, "absorption_bound": glass.absorption_bound if glass.transparent else None, "transparency": dict(glass.run_transparency(log)), } def _citation_entry(value: object) -> dict[str, object] | None: """Return the citation of a material object that is not optmat's, if any. Parameters ---------- value : object Any of solprop's objects. Returns ------- dict or None ``object``, ``name`` and ``citation`` for a glass, a gas susceptibility or a species that carries no optmat record; ``None`` otherwise. """ if not isinstance(value, GlassIndex | GasSusceptibility | Species) or _records( value ): return None return { "object": type(value).__name__, "name": str(getattr(value, "name", "") or getattr(value, "source", "")), "citation": str(getattr(value, "citation", "")), } def _window(omega: Float[np.ndarray, " n_omega"]) -> float | None: """Return the time window a frequency grid resolves. Parameters ---------- omega : numpy.ndarray Angular frequencies in rad/s, uniformly spaced in some order. Returns ------- float or None ``2 pi / d_omega`` in s, or ``None`` for fewer than two frequencies. """ spacing = np.diff(np.unique(np.asarray(omega, dtype=float))) return None if spacing.size == 0 else float(2.0 * np.pi / np.min(spacing)) def _raman_entry( response: OptmatRamanResponse, omega: Float[np.ndarray, " n_omega"] ) -> dict[str, object]: """Return what was measured about an optmat Raman response's assembly. Parameters ---------- response : OptmatRamanResponse The response. omega : numpy.ndarray The run's angular frequencies in rad/s, whose spacing fixes the window over which a held line shift's phase error is bounded. Returns ------- dict The assembly's record with the labels, and the shift's phase bound in rad over the run's window (``None`` without a held shift). """ assembly = response.assembly entry: dict[str, object] = {"labels": list(response.labels)} if assembly is None: return entry window = _window(omega) shift = assembly.shift entry |= assembly.to_dict() entry["window_s"] = window entry["shift_phase_bound_rad"] = ( None if shift is None or window is None else shift.phase_bound(window) ) return entry def _gnlse_policy(problems: Sequence[GnlseProblem]) -> dict[str, object] | None: """Return a GNLSE run's held-back Raman fraction and where it came from. Parameters ---------- problems : sequence of GnlseProblem The run's GNLSE mappings: none, or one. Returns ------- dict or None ``fraction``, its ``origin`` (``"user"``, ``"bundle"`` or ``"raman-off"``), whether a Raman response carries it, the silica ``bundle`` (or ``None``) and the origin of ``n2``; ``None`` for a run without a GNLSE mapping. """ if not problems: return None problem = problems[0] return { "fraction": problem.raman_fraction, "origin": problem.fraction_origin, "raman": problem.raman is not None, "bundle": None if problem.bundle is None else problem.bundle.name, "n2_origin": problem.n2_origin, "carrier_wavelength": 2.0 * math.pi * C / problem.omega0, } def _policies( selection: MaterialSelection | None, *, dilute: bool, carrier: float | None, gnlse: dict[str, object] | None, raman: Sequence[OptmatRamanResponse] = (), dispersive: dict[str, object] | None = None, ) -> dict[str, object]: """Return the approximation policies a run's materials are evaluated under. Parameters ---------- selection : MaterialSelection or None The run's selection. dilute : bool Whether a gas susceptibility from optmat is scaled linearly in density. carrier : float or None The carrier in m an optmat Kerr coefficient was evaluated at, or ``None`` when the run has no optmat Kerr model. gnlse : dict or None A GNLSE run's held-back fraction (:func:`_gnlse_policy`). raman : sequence of OptmatRamanResponse, optional The run's optmat Raman responses. dispersive : dict or None, optional The dispersive electronic law in force (:func:`_dispersive_policy`), which replaces the carrier law; ``None`` when the run has none. Returns ------- dict The policies; one a later phase resolves is recorded as selected. """ assemblies = [one.assembly for one in raman if one.assembly is not None] temperatures = sorted({assembly.temperature for assembly in assemblies}) return { "linear_density_scaling": { "law": "N alpha / eps0", "warning_threshold": DILUTE_SCALING_THRESHOLD, } if dilute else None, "electronic": None if selection is None else selection.electronic, "carrier_wavelength": None if selection is None else selection.carrier_wavelength, # SEP 3, section 4.3: one frozen coefficient per species, at the carrier, # for every process the cubic term drives. "electronic_response": dispersive if dispersive is not None else None if carrier is None else {"law": "carrier-kerr", "carrier_wavelength": carrier}, "gnlse_raman": gnlse, "raman_shift": None if selection is None else selection.raman_shift, # The temperature an optmat Raman response holds its populations and its # widths' temperature laws at (SEP 3, section 4.4): one per response. "raman_reference_temperature": None if not temperatures else temperatures[0] if len(temperatures) == 1 else temperatures, "raman_terms": [ { "material": assembly.material, "labels": list(one.labels), "groups": assembly.groups, "terms": assembly.terms, "shift": None if assembly.shift is None else assembly.shift.policy, } for one in raman if (assembly := one.assembly) is not None ], "energy_loss": None if selection is None else selection.energy_loss, } def _dispersive_label(response: DispersiveKerrResponse) -> str: """Return the population a dispersive response is recorded under. Parameters ---------- response : DispersiveKerrResponse The response. Returns ------- str Its component label, or its gas for a single-gas run. """ return response.model.material if response.species is None else response.species def _dispersive_entries( responses: Sequence[DispersiveKerrResponse], band: tuple[float, float] | None, *, vector: bool, ) -> list[dict[str, object]]: """Return the manifest's electronic entries of a run's dispersive responses. Parameters ---------- responses : sequence of DispersiveKerrResponse The run's dispersive responses. band : tuple of float or None The run's active band in rad/s, or ``None`` when it is not known. vector : bool Whether the run's field has two polarisation components at a node. Returns ------- list of dict Per response: its label, gas, evaluator, sector and tensor approximation, the model and support digests, the leg bound and both band edges in m^-1, and the regions of the band (:meth:`ElectronicRegions.to_dict`), in label order. """ entries = [] for response in responses: model = response.model regions = None if band is None else model.regions(*band, vector=vector) entries.append( { "label": _dispersive_label(response), "material": model.material, "model": model.material_records[0].name, "digest": model.model_digest, "support_digest": model.support_digest, "evaluator": response.evaluator, "sector": response.sector, "tensor": "isotropic-kleinman", "leg_bound": model.leg_bound, "band_edge_linear": model.band_edge_linear, "band_edge_any": model.band_edge_any, "regions": None if regions is None else regions.to_dict(), } ) return sorted(entries, key=lambda entry: str(entry["label"])) MIXTURE_FILTER_RULE: Final = ( "g_mix = [sum_s rho_s gamma0_s g_s^4 / sum_s rho_s gamma0_s]^(1/4), " "rebuilt from the densities at every stage" ) """The symmetric evaluator's filter of a mixture, as a run records it (SEP 6, 6).""" def _density_identities(found: Sequence[object]) -> dict[str, str]: """Return the identity of each population's density profile, by channel label. Parameters ---------- found : sequence of object The run's objects: its mixtures, and a general modal guide's regions, whose gas fills' channels are labelled ``"<region>:<population>"``. Returns ------- dict Per label, the SHA-256 of the canonical JSON of the profile's record -- its samples, breakpoints and declarations, as the run's configuration records a mixture (:meth:`solprop.physdata.mixtures.Mixture.serialise`). """ identities: dict[str, str] = {} def add(prefix: str, mixture: Mixture) -> None: """Record a mixture's populations under a label prefix. Parameters ---------- prefix : str Prepended to every population label. mixture : Mixture The mixture. """ record = cast("list[dict[str, object]]", mixture.serialise()["components"]) for component in record: identities.setdefault( f"{prefix}{component['label']}", hashlib.sha256( _canonical_entry( cast("Mapping[str, object]", component["density"]) ).encode() ).hexdigest(), ) for value in found: if isinstance(value, Mixture): add("", value) elif isinstance(value, MaterialRegion) and isinstance(value.gas, GasKerr): add(f"{value.name}:", value.gas.fill) return identities def _mixture_filters( leaf: DispersiveKerrTerms | None, densities: Mapping[str, str] ) -> list[dict[str, object]]: """Return the symmetric mixture filters a run's leaf builds, for its record. Parameters ---------- leaf : solprop.translate.responses.DispersiveKerrTerms or None The run's compiled dispersive channels. densities : Mapping of str to str Each label's density-profile identity (:func:`_density_identities`). Returns ------- list of dict One per group of several species under the symmetric evaluator (:meth:`~solprop.translate.responses.DispersiveKerrTerms.groups`): the rule, the general modal region it acts in (``None`` on every other route), and the composition it is built from -- each species' label and its density profile's identity. Empty for the polynomial and for a pure gas. """ if leaf is None or leaf.evaluator != "symmetric": return [] filters: list[dict[str, object]] = [] for group in leaf.groups(): if len(group) < 2: continue filters.append( { "rule": MIXTURE_FILTER_RULE, "region": None if leaf.regions is None else int(leaf.regions[group[0]]), "composition": [ { "label": leaf.labels[channel], "density": densities.get(leaf.labels[channel]), } for channel in group ], } ) return filters def _dispersive_policy( entries: Sequence[Mapping[str, object]], mixtures: Sequence[Mapping[str, object]] = (), ) -> dict[str, object]: """Return the dispersive electronic law a run's entries record, for its key. Parameters ---------- entries : sequence of Mapping :func:`_dispersive_entries` of the run. mixtures : sequence of Mapping, optional The symmetric evaluator's mixture filters (:func:`_mixture_filters`), written under ``mixture_filters`` only when there is one, so that a pure gas's record is what it was. Returns ------- dict The law, its evaluator, sector and tensor approximation, the per-leg bound rule, each population's support digest and regions, and each mixture filter with the composition it is built from: what the resume key covers, so that a changed model, support record, band or composition is refused. """ evaluator = str(entries[0]["evaluator"]) policy: dict[str, object] = { "law": "symmetric-kerr" if evaluator == "symmetric" else "gas-polynomial", "evaluator": evaluator, "sector": entries[0]["sector"], "tensor": "isotropic-kleinman", "leg_bound": "per-leg at sqrt(3 W2_max / 4)", "species": [ { "label": entry["label"], "support_digest": entry["support_digest"], "regions": entry["regions"], } for entry in entries ], } if mixtures: policy["mixture_filters"] = [dict(one) for one in mixtures] return policy def _rate_policies(found: Sequence[object]) -> list[dict[str, object]]: """Return the policies of a run's optmat ionisation rates. Parameters ---------- found : sequence of object The run's objects. Returns ------- list of dict :meth:`solprop.nonlinear.optmat_rates.OptmatRate.manifest_entry` of each rate, each once, in canonical order: the threshold kind, the reference field, the constant energy per event and its bound, the maximum field. """ entries = [ value.manifest_entry() for value in found if isinstance(value, OptmatRate) ] distinct = {_canonical_entry(entry): entry for entry in entries} return [distinct[key] for key in sorted(distinct)] def _canonical_entry(entry: Mapping[str, object]) -> str: """Return an entry's canonical JSON, for ordering and deduplication. Parameters ---------- entry : collections.abc.Mapping Plain values. Returns ------- str Sorted-key, compact JSON. """ return json.dumps(entry, sort_keys=True, separators=(",", ":"), default=str)
[docs] def material_manifest( # noqa: PLR0913 - the run's objects and its band, one each roots: Sequence[object], *, selection: MaterialSelection | None, omega: Float[np.ndarray, " n_omega"], log: MaterialLog | None = None, peak_fields: Sequence[PeakFieldReport] = (), carrier: float | None = None, active_band: tuple[float, float] | None = None, vector: bool = False, leaf: DispersiveKerrTerms | None = None, ) -> MaterialManifest: """Collect a run's material manifest from its objects. Every one of solprop's objects reachable from `roots` is visited once. Those that carry optmat records (a species, a mixture, a gas susceptibility or equation of state, a solid) contribute them; a mixture contributes its optmat populations, its named composition and its dilute-scaling check over `omega`, measured here once for the run; an optmat solid its axis and its transparent approximation; a species with optmat's Kerr model its coefficient at `carrier`, beside the third-harmonic one; a GNLSE mapping its held-back Raman fraction and its origin; and any other glass, gas susceptibility or species its citation. A run with no optmat object and no selection has the empty manifest, whatever else it carries. Parameters ---------- roots : sequence The run's objects: configuration, modes, responses, media, guide. selection : MaterialSelection or None The selection the run was built with. omega : numpy.ndarray Angular frequencies in rad/s of the run's band, collars included; the positive ones are checked. log : solprop.physdata.selection.MaterialLog or None, optional The run's evaluation log, whose out-of-interval bands are recorded. peak_fields : sequence of PeakFieldReport, optional The post-run diagnostic of each optmat rate: the largest field each population saw, against the model's maximum field and the constant energy (:func:`solprop.nonlinear.optmat_rates.peak_field_report`); a measurement. carrier : float or None, optional The run's carrier, its grid's reference wavelength in m, at which an optmat Kerr coefficient is recorded; ``None`` records none. active_band : tuple of float or None, optional The run's active band in rad/s, ``omega_min / 2`` to ``1.1 omega_max``, over which a dispersive electronic response's regions are classified (SEP 6, section 4.10); ``None`` records no regions. vector : bool, optional Whether the run's field has two polarisation components at a node, which takes the any-polarisation band edge. leaf : solprop.translate.responses.DispersiveKerrTerms or None, optional The run's compiled dispersive channels, whose groups name the symmetric evaluator's mixture filters: each is recorded, with the composition it is built from, in the electronic law's policy (and so in the resume key), and its species' electronic entries name it. ``None`` records none. Returns ------- solprop.physdata.selection.MaterialManifest The manifest, deduplicated and in canonical order. """ found = list(_walk(roots)) records = [entry for value in found for entry in _records(value)] if not records and selection is None: return MaterialManifest() mixtures = [value for value in found if isinstance(value, Mixture)] band = np.asarray(omega, dtype=float) # A fill without a mixture (a planar run's slab) carries its gas as one species, # whose records and dilute-scaling check are recorded as a population's are; the # modes of one guide share their fill, so it is recorded once (SEP 3, Q27). fills = _single_species_fills(found) species = [ { "label": component.label, "species": component.species.name, "records": [entry.digest for entry in component.species.material_records], } for mixture in mixtures for component in mixture.components if component.species.material_records ] + [ { "label": material.name, "species": material.name, "records": [entry.digest for entry in material.material_records], } for material, _ in fills if material.material_records ] compositions = [ { "name": mixture.composition, "labels": list(mixture.labels), "fractions": list(mixture.fractions or ()), } for mixture in mixtures if mixture.composition is not None ] checks = [ check.to_dict() for mixture in mixtures for check in mixture.dilute_scaling_checks(band[band > 0]) ] + [ check.to_dict() for _, mode in fills for check in mode._dilute_checks(band[band > 0]) ] citations = [entry for value in found if (entry := _citation_entry(value))] dispersive = _dispersive_entries( [value for value in found if isinstance(value, DispersiveKerrResponse)], active_band, vector=vector, ) mixture_filters = _mixture_filters(leaf, _density_identities(found)) # Each population of a symmetric mixture names the filter it belongs to. membership = { str(member["label"]): { "kind": "mixture", "labels": [one["label"] for one in composition], } for mixture_filter in mixture_filters for composition in [ cast("list[dict[str, object]]", mixture_filter["composition"]) ] for member in composition } dispersive = [ entry | ({"filter": membership[label]} if label in membership else {}) for entry in dispersive for label in [str(entry["label"])] ] # Under a dispersive law no carrier coefficient is used, so none is recorded. electronic = dispersive or [ {"label": component.label} | component.species.kerr.at_carrier(carrier).to_dict() for mixture in mixtures for component in mixture.components if component.species.kerr is not None and carrier is not None ] raman = [value for value in found if isinstance(value, OptmatRamanResponse)] return MaterialManifest( optmat_version=_optmat.optmat_version(), selection=None if selection is None else selection.to_dict(), records=tuple(records), species=tuple(species), compositions=tuple(compositions), solids=tuple( _solid_entry(value, log) for value in found if isinstance(value, OptmatGlass) ), policies={ **_policies( selection, dilute=any(isinstance(value, OptmatSusceptibility) for value in found), carrier=carrier if electronic and not dispersive else None, gnlse=_gnlse_policy( [value for value in found if isinstance(value, GnlseProblem)] ), raman=raman, dispersive=_dispersive_policy(dispersive, mixture_filters) if dispersive else None, ), "ionisation": _rate_policies(found), }, raman=tuple(_raman_entry(one, band) for one in raman), dilute_scaling=tuple(checks), ranges=() if log is None else log.range_entries(), citations=tuple(citations), electronic=tuple(electronic), peak_fields=tuple(report.to_dict() for report in peak_fields), )