aiida_pythonjob_ins.operations

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 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. band_path_qpoints() – build a high-symmetry q-point path (seekpath).

  2. 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 aiida_pythonjob_ins.workflows.dispersion.generate_band_path()), so these functions stay AiiDA-free and unit-testable.

Attributes

Classes

QpointPath

A high-symmetry q-point path: positions + labels + cell (no energies).

Functions

read_force_constants_from_castep(→ euphonic.ForceConstants)

Read a CASTEP .castep_bin/.check file into ForceConstants.

read_force_constants_from_phonopy(...)

Read force constants from Phonopy output into ForceConstants.

band_path_qpoints(→ QpointPath)

Return a QpointPath (q-points + labels + cell) for a band path.

interpolate_phonon_modes(→ euphonic.QpointPhononModes)

Fourier-interpolate phonon modes at the given fractional q-points.

calculate_dispersion(→ euphonic.QpointPhononModes)

Convenience: build a band path and interpolate modes along it.

default_energy_bins(→ pint.Quantity)

Compute default bin edges with asymmetric padding and fixed physical spacing.

calculate_dos(→ euphonic.Spectrum1D)

Compute a phonon density of states by sampling a Monkhorst-Pack grid.

calculate_thermal_displacements(...)

Compute thermal mode/atomic displacements at a sample temperature.

calculate_scattering_q2(→ pint.Quantity)

Indirect-geometry Q² for a scattering angle and analyser final energy.

tosca_energy_bins(→ pint.Quantity)

Build the TOSCA energy axis: data-sized, then clipped to the instrument.

broaden_tosca_spectrum(...)

Apply TOSCA's energy-dependent resolution broadening to a spectrum.

interpolate_phonon_modes_on_grid(...)

Interpolate phonon modes on a Monkhorst-Pack grid (a powder average).

calculate_tosca_spectrum(→ euphonic.Spectrum1DCollection)

Compute the full, ungrouped TOSCA intensity line set from phonon modes.

Module Contents

aiida_pythonjob_ins.operations.LOGGER
class aiida_pythonjob_ins.operations.QpointPath[source]

Bases: NamedTuple

A high-symmetry q-point path: positions + labels + cell (no energies).

A well-defined return type for 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.)

qpoints: numpy.ndarray
labels: list[tuple[int, str]]
cell: numpy.ndarray
aiida_pythonjob_ins.operations.read_force_constants_from_castep(filename: str | pathlib.Path) euphonic.ForceConstants[source]

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 aiida_pythonjob_ins.pythonjobs.prepare_read_force_constants_inputs()).

aiida_pythonjob_ins.operations.read_force_constants_from_phonopy(summary_name: str = 'phonopy.yaml', fc_name: str = 'FORCE_CONSTANTS', born_name: str | None = None) euphonic.ForceConstants[source]

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 aiida_pythonjob_ins.pythonjobs.prepare_read_phonopy_inputs()).

born_name is optional (Born charges for LO-TO splitting); pass None to skip it.

aiida_pythonjob_ins.operations.band_path_qpoints(cell: tuple[numpy.ndarray, numpy.ndarray, numpy.ndarray], q_spacing: float = 0.025, *, insert_gamma: bool = True) QpointPath[source]

Return a 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.

aiida_pythonjob_ins.operations.interpolate_phonon_modes(force_constants: euphonic.ForceConstants, qpoints: numpy.ndarray, *, asr: str | None = 'reciprocal') euphonic.QpointPhononModes[source]

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.

aiida_pythonjob_ins.operations.calculate_dispersion(force_constants: euphonic.ForceConstants, q_spacing: float = 0.025, *, insert_gamma: bool = True, asr: str | None = 'reciprocal') euphonic.QpointPhononModes[source]

Convenience: build a band path and interpolate modes along it.

aiida_pythonjob_ins.operations.default_energy_bins(frequencies: pint.Quantity, energy_spacing: pint.Quantity, *, padding_fraction: float = 0.05) pint.Quantity[source]

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.

aiida_pythonjob_ins.operations.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[source]

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).

aiida_pythonjob_ins.operations.calculate_thermal_displacements(modes: euphonic.QpointPhononModes, temperature: float) tuple[abinslib.displacements.Displacements, pint.Quantity][source]

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)).

aiida_pythonjob_ins.operations.calculate_scattering_q2(energy_transfer: pint.Quantity, detector_angle: float, final_energy: float, *, energy_unit: str = '1/cm') pint.Quantity[source]

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.

aiida_pythonjob_ins.operations.tosca_energy_bins(frequencies: pint.Quantity, energy_spacing: pint.Quantity, energy_max: pint.Quantity, *, max_quantum_order: int = 2) pint.Quantity[source]

Build the TOSCA energy axis: data-sized, then clipped to the instrument.

Reuses 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.

aiida_pythonjob_ins.operations.broaden_tosca_spectrum(spectrum: euphonic.Spectrum1D | euphonic.Spectrum1DCollection, resolution_model: str = 'AbINS_v1') euphonic.Spectrum1D | euphonic.Spectrum1DCollection[source]

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 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.

aiida_pythonjob_ins.operations.interpolate_phonon_modes_on_grid(force_constants: euphonic.ForceConstants, q_spacing: float = 0.1, *, asr: str | None = 'reciprocal') euphonic.QpointPhononModes[source]

Interpolate phonon modes on a Monkhorst-Pack grid (a powder average).

Unlike 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 calculate_dos() does – the sampling ToscaFromForceConstantsWorkChain needs. The almost-isotropic incoherent approximation disregards actual q-point positions (see the reference pipeline’s kinematic treatment in 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).

aiida_pythonjob_ins.operations.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[source]

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 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.