aiida_pythonjob_ins.operations ============================== .. py:module:: aiida_pythonjob_ins.operations .. autoapi-nested-parse:: Atomic Euphonic operations, written as plain Python functions. These functions use only the **public** Euphonic API and know nothing about AiiDA, so they can be unit-tested directly and reused elsewhere. Crucially, this module's import chain is AiiDA-free (the package ``__init__`` is empty), so by-reference cloudpickling can target a lean remote environment (euphonic + seekpath + numpy, no aiida). They are turned into AiiDA PythonJobs by the helpers in :mod:`aiida_pythonjob_ins.pythonjobs`. The dispersion workflow is built from composable pieces, mirroring ``euphonic.cli.dispersion`` (https://euphonic.readthedocs.io/en/stable/cli.html) without relying on Euphonic's private ``_bands_from_force_constants`` helper: 1. :func:`band_path_qpoints` -- build a high-symmetry q-point path (seekpath). 2. :func:`interpolate_phonon_modes` -- Fourier-interpolate modes at those points. ``calculate_dispersion`` is a convenience that chains the two. The band path is turned into a native ``KpointsData`` by a parent-side ``calcfunction`` (see :func:`aiida_pythonjob_ins.workflows.dispersion.generate_band_path`), so these functions stay AiiDA-free and unit-testable. Attributes ---------- .. autoapisummary:: aiida_pythonjob_ins.operations.LOGGER Classes ------- .. autoapisummary:: aiida_pythonjob_ins.operations.QpointPath Functions --------- .. autoapisummary:: aiida_pythonjob_ins.operations.read_force_constants_from_castep aiida_pythonjob_ins.operations.read_force_constants_from_phonopy aiida_pythonjob_ins.operations.band_path_qpoints aiida_pythonjob_ins.operations.interpolate_phonon_modes aiida_pythonjob_ins.operations.calculate_dispersion aiida_pythonjob_ins.operations.default_energy_bins aiida_pythonjob_ins.operations.calculate_dos aiida_pythonjob_ins.operations.calculate_thermal_displacements aiida_pythonjob_ins.operations.calculate_scattering_q2 aiida_pythonjob_ins.operations.tosca_energy_bins aiida_pythonjob_ins.operations.broaden_tosca_spectrum aiida_pythonjob_ins.operations.interpolate_phonon_modes_on_grid aiida_pythonjob_ins.operations.calculate_tosca_spectrum Module Contents --------------- .. py:data:: LOGGER .. py:class:: QpointPath Bases: :py:obj:`NamedTuple` A high-symmetry q-point path: positions + labels + cell (no energies). A well-defined return type for :func:`band_path_qpoints`. It is a plain ``NamedTuple`` used purely on the Python side; the parent-side ``generate_band_path`` calcfunction turns it into a native ``KpointsData``. (Note: aiida-pythonjob would treat a ``NamedTuple`` *return annotation* as a structured multi-output spec, splitting it into one output port per field. That does not apply here because ``band_path_qpoints`` is not used as a PythonJob ``function`` -- if it were, the split into qpoints/labels/cell could even be desirable.) .. py:attribute:: qpoints :type: numpy.ndarray .. py:attribute:: labels :type: list[tuple[int, str]] .. py:attribute:: cell :type: numpy.ndarray .. py:function:: read_force_constants_from_castep(filename: str | pathlib.Path) -> euphonic.ForceConstants Read a CASTEP ``.castep_bin``/``.check`` file into ``ForceConstants``. A thin wrapper over ``ForceConstants.from_castep``. It exists because a PythonJob's ``function`` must be a plain module-level function: a bound classmethod (``ForceConstants.from_castep``) is a ``method``, not a ``FunctionType``, so aiida-pythonjob's ``build_function_data`` rejects it. The wrapper is also where we attach logging. ``filename`` is resolved relative to the working directory. When run as a PythonJob the CASTEP file is staged there via ``upload_files`` (see :func:`aiida_pythonjob_ins.pythonjobs.prepare_read_force_constants_inputs`). .. py:function:: read_force_constants_from_phonopy(summary_name: str = 'phonopy.yaml', fc_name: str = 'FORCE_CONSTANTS', born_name: str | None = None) -> euphonic.ForceConstants Read force constants from Phonopy output into ``ForceConstants``. Thin wrapper over ``ForceConstants.from_phonopy`` (requires euphonic's ``phonopy-reader`` extra, which this package installs by default). All names are resolved in the working directory; when run as a PythonJob the files are staged there via ``upload_files`` (see :func:`aiida_pythonjob_ins.pythonjobs.prepare_read_phonopy_inputs`). ``born_name`` is optional (Born charges for LO-TO splitting); pass ``None`` to skip it. .. py:function:: band_path_qpoints(cell: tuple[numpy.ndarray, numpy.ndarray, numpy.ndarray], q_spacing: float = 0.025, *, insert_gamma: bool = True) -> QpointPath Return a :class:`QpointPath` (q-points + labels + cell) for a band path. ``cell`` is a spglib-style ``(lattice, scaled_positions, numbers)`` tuple -- e.g. from ``euphonic.Crystal.to_spglib_cell()`` or an ASE ``Atoms``. Only the structure is needed; force constants are not. Pure/AiiDA-free: the parent-side ``generate_band_path`` calcfunction wraps this into a native ``KpointsData``. .. py:function:: interpolate_phonon_modes(force_constants: euphonic.ForceConstants, qpoints: numpy.ndarray, *, asr: str | None = 'reciprocal') -> euphonic.QpointPhononModes Fourier-interpolate phonon modes at the given fractional q-points. ``qpoints`` is an ``(N, 3)`` array in the crystal's reciprocal basis (as provided by an AiiDA ``KpointsData``). This is the core ``ForceConstants -> QpointPhononModes`` step. .. py:function:: calculate_dispersion(force_constants: euphonic.ForceConstants, q_spacing: float = 0.025, *, insert_gamma: bool = True, asr: str | None = 'reciprocal') -> euphonic.QpointPhononModes Convenience: build a band path and interpolate modes along it. .. py:function:: default_energy_bins(frequencies: pint.Quantity, energy_spacing: pint.Quantity, *, padding_fraction: float = 0.05) -> pint.Quantity Compute default bin edges with asymmetric padding and fixed physical spacing. Parameters ---------- frequencies Phonon frequencies as a dimensional Quantity. energy_spacing Width of each energy bin as a dimensional Quantity (e.g. ``1.0 * ureg('meV')``). padding_fraction Fractional padding based on the occupied frequency span (default: 0.05). Always added above the maximum frequency; added below the minimum frequency only if negative (imaginary modes present). Returns ------- pint.Quantity Bin edges with uniform spacing in the units of ``energy_spacing``. .. py:function:: calculate_dos(force_constants: euphonic.ForceConstants, q_spacing: float = 0.1, energy_spacing: float = 1.0, *, energy_unit: str = 'meV', adaptive: bool = True, asr: str | None = 'reciprocal') -> euphonic.Spectrum1D Compute a phonon density of states by sampling a Monkhorst-Pack grid. Parameters ---------- force_constants Interatomic force constants to interpolate from. q_spacing Target spacing of the sampling grid, in 1/Angstrom (finer -> denser grid). energy_spacing Width of the DOS energy bins, in ``energy_unit``. energy_unit Unit for the energy axis (e.g. ``"meV"``, ``"1/cm"``). adaptive Use adaptive broadening (per-mode widths from mode gradients) rather than fixed bins. Recommended; requires computing mode gradients. asr Acoustic sum rule applied during interpolation (``None`` to disable). Returns ------- Spectrum1D Density of states vs energy (bin edges in ``x_data``, values in ``y_data``; use ``get_bin_centres()`` for matching x/y lengths). .. py:function:: calculate_thermal_displacements(modes: euphonic.QpointPhononModes, temperature: float) -> tuple[abinslib.displacements.Displacements, pint.Quantity] Compute thermal mode/atomic displacements at a sample temperature. Thin wrapper over ``Displacements.from_modes`` and ``.to_atomic_displacements()``. The atomic displacements determine the Debye-Waller attenuation applied by the intensity calculations below. Parameters ---------- modes Phonon frequencies and eigenvectors. temperature Sample temperature in kelvin. Returns ------- tuple[Displacements, pint.Quantity] The per-mode displacement dataset and the derived per-atom displacement tensor (``Quantity``, shape ``(n_atoms, 3, 3)``). .. py:function:: calculate_scattering_q2(energy_transfer: pint.Quantity, detector_angle: float, final_energy: float, *, energy_unit: str = '1/cm') -> pint.Quantity Indirect-geometry Q² for a scattering angle and analyser final energy. Thin wrapper over ``abinslib.util.calculate_indirect_q2`` that takes the detector angle in degrees (the natural unit for describing a bank) rather than radians, and the final energy as a plain float in ``energy_unit`` (matching the other TOSCA operations' unit convention) rather than a pre-built ``Quantity``. Called once per detector bank, with ``energy_transfer`` set to the mode frequencies for the fundamental calculation and to the output bin centres for the combination-mode calculation -- the two kinematic evaluations the reference pipeline performs. .. py:function:: tosca_energy_bins(frequencies: pint.Quantity, energy_spacing: pint.Quantity, energy_max: pint.Quantity, *, max_quantum_order: int = 2) -> pint.Quantity Build the TOSCA energy axis: data-sized, then clipped to the instrument. Reuses :func:`default_energy_bins`' padding and bin-alignment rule, sized from the fundamental frequencies scaled by the highest quantum order to be computed (combination modes extend to roughly that multiple of the fundamental range), then clips to whichever of the data range or ``energy_max`` is tighter. The lower end is additionally clipped at zero. Unlike a density of states, where a negative energy indicates a real, physically meaningful soft mode, TOSCA measures only energy loss from the neutron to the sample: an energy transfer below zero (let alone below ``-final_energy``) is kinematically inaccessible, and evaluating the Q² relation there produces nonsense (``calculate_scattering_q2`` takes a square root that goes complex). A q-point mesh's small numerical noise around an acoustic mode at the zone centre is enough to trigger asymmetric padding on the negative side, so this clip is not merely a corner case. .. py:function:: broaden_tosca_spectrum(spectrum: euphonic.Spectrum1D | euphonic.Spectrum1DCollection, resolution_model: str = 'AbINS_v1') -> euphonic.Spectrum1D | euphonic.Spectrum1DCollection Apply TOSCA's energy-dependent resolution broadening to a spectrum. Broadens on the spectrum's own bin centres (``points == mesh``), so no rebinning takes place -- only the resolution-smeared intensity changes. Because ``resins``' broadening operator is linear, this gives the same result whether applied before or after summing lines (see ``group_spectra``/``broaden_spectra`` in :mod:`aiida_pythonjob_ins.workflows.tosca`, which rely on this). A collection is broadened line by line: the underlying ``resins`` model only accepts a single 1-D intensity array per call (see ``InstrumentModel.broaden``), not a 2-D stack of lines. .. py:function:: interpolate_phonon_modes_on_grid(force_constants: euphonic.ForceConstants, q_spacing: float = 0.1, *, asr: str | None = 'reciprocal') -> euphonic.QpointPhononModes Interpolate phonon modes on a Monkhorst-Pack grid (a powder average). Unlike :func:`interpolate_phonon_modes`, which evaluates a caller-supplied set of q-points (e.g. a high-symmetry band path), this samples the whole Brillouin zone the way :func:`calculate_dos` does -- the sampling :class:`ToscaFromForceConstantsWorkChain ` needs. The almost-isotropic incoherent approximation disregards actual q-point *positions* (see the reference pipeline's kinematic treatment in :func:`calculate_tosca_spectrum`), but still needs a representative *density* of modes across the zone, exactly as a DOS does. Parameters ---------- force_constants Interatomic force constants to interpolate from. q_spacing Target spacing of the sampling grid, in 1/Angstrom (finer -> denser grid). asr Acoustic sum rule applied during interpolation (``None`` to disable). .. py:function:: calculate_tosca_spectrum(modes: euphonic.QpointPhononModes, temperature: float = 10.0, energy_spacing: float = 10.0, energy_max: float = 4000.0, detector_angles: list[float] | None = None, final_energy: float = 32.0, energy_unit: str = '1/cm') -> euphonic.Spectrum1DCollection Compute the full, ungrouped TOSCA intensity line set from phonon modes. Combines fundamental (one-phonon) and combination (two-phonon) intensities in the almost-isotropic incoherent approximation, for every requested detector bank, into a single collection with one line per atom, quantum order and detector angle -- mirroring the reference pipeline's ``fundamentals + second_order`` sum, repeated per bank. Parameters ---------- modes Phonon frequencies and eigenvectors (e.g. from a molecular-crystal calculation; the almost-isotropic approximation assumes hydrogenous, largely incoherent scattering). temperature Sample temperature in kelvin, governing the Debye-Waller attenuation. energy_spacing, energy_max Energy axis bin width and instrument-range cutoff, in ``energy_unit``. See :func:`tosca_energy_bins`. detector_angles Scattering angles in degrees, one per detector bank to evaluate. Defaults to TOSCA's backward (135°) and forward (45°) banks. final_energy Analyser-fixed final neutron energy, in ``energy_unit``. energy_unit Unit for ``energy_spacing``, ``energy_max`` and ``final_energy``. TOSCA results are conventionally reported in wavenumbers. Returns ------- Spectrum1DCollection One line per (atom, quantum order, detector angle), each carrying that triple in its ``line_data`` metadata under ``atom_symbol``, ``quantum_order`` and ``detector_angle``. Not yet grouped or broadened.