IO

Input readers and validated records for experiment boundaries.

class diffBloch.io.AdpRecord(*, kind: tuple[Literal['Uiso', 'Uani', 'missing'], ...], u_iso: FloatArray, u_iso_su: FloatArray, uij_cif: FloatArray, uij_cif_su: FloatArray)[source][source]

Bases: BaseModel

Atomic displacement parameters, preserving CIF Uiso/Uani semantics and SUs.

model_config = {'arbitrary_types_allowed': True, 'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

kind: tuple[Literal['Uiso', 'Uani', 'missing'], ...]
u_iso: FloatArray
u_iso_su: FloatArray
uij_cif: FloatArray
uij_cif_su: FloatArray
class diffBloch.io.ExperimentalRecord(*, source_path: Path | None = None, unit_cell: FloatArray, cell_parameters: FloatArray, cell_parameters_su: FloatArray, wavelength: float, data_collection_geometry: Literal['continuous_rotation', 'precession'] = 'continuous_rotation', dstar_max: float | None = None, mosaicity_degrees: float | None = None, rotation_axis_position_degrees: float | None = None, ub_matrix: FloatArray, zone_axis_ids: IntArray, zone_axes: FloatArray, precession_angles: FloatArray, alphas: FloatArray, betas: FloatArray, omegas: FloatArray, scales: FloatArray, hkl: IntArray, intensities: FloatArray, sigmas: FloatArray, reflection_zone_axis_ids: IntArray)[source][source]

Bases: BaseModel

Experimental PETS reflection data keyed by rotation/zone-axis id.

model_config = {'arbitrary_types_allowed': True, 'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

source_path: Path | None
unit_cell: FloatArray
cell_parameters: FloatArray
cell_parameters_su: FloatArray
wavelength: float
data_collection_geometry: Literal['continuous_rotation', 'precession']
dstar_max: float | None
mosaicity_degrees: float | None
rotation_axis_position_degrees: float | None
ub_matrix: FloatArray
zone_axis_ids: IntArray
zone_axes: FloatArray
precession_angles: FloatArray
alphas: FloatArray
betas: FloatArray
omegas: FloatArray
scales: FloatArray
hkl: IntArray
intensities: FloatArray
sigmas: FloatArray
reflection_zone_axis_ids: IntArray
property integration_semiangle: float

Angular integration semi-angle recorded consistently across the PETS virtual frames.

property n_rotations: int

Number of PETS zone-axis rows.

property n_reflections: int

Number of measured reflections.

class diffBloch.io.ParsedInput(record: T, diagnostics: tuple[ParseDiagnostic, ...] = ())[source][source]

Bases: Generic

An IO record plus non-fatal diagnostics collected while parsing it.

record: T
diagnostics: tuple[ParseDiagnostic, ...] = ()
class diffBloch.io.ParseDiagnostic(code: ParseDiagnosticCode, input_kind: InputKind, source_path: Path | None, message: str, details: Mapping[str, ~diffBloch.io.diagnostics.ParseDetail]=<factory>)[source][source]

Bases: object

A non-fatal input parse decision worth surfacing at the app boundary.

channel: ClassVar[str] = 'input parse'
code: ParseDiagnosticCode
input_kind: InputKind
source_path: Path | None
message: str
details: Mapping[str, ParseDetail]
property step: int | None
property measurements: Mapping[str, float]
class diffBloch.io.StructureRecord(*, source_path: Path | None = None, unit_cell: FloatArray, cell_parameters: FloatArray, cell_parameters_su: FloatArray, spacegroup_hm: str, spacegroup_number: int | None = None, symops_R: FloatArray, symops_t: FloatArray, labels: tuple[str, ...], numbers: IntArray, frac_positions: FloatArray, frac_positions_su: FloatArray, occupancies: FloatArray, occupancies_su: FloatArray, adp: AdpRecord)[source][source]

Bases: BaseModel

Asymmetric-unit structure data in CIF convention.

Nominal arrays feed downstream kernels. The corresponding *_su arrays preserve CIF standard uncertainties as provenance metadata for reporting or later optional penalties.

model_config = {'arbitrary_types_allowed': True, 'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

source_path: Path | None
unit_cell: FloatArray
cell_parameters: FloatArray
cell_parameters_su: FloatArray
spacegroup_hm: str
spacegroup_number: int | None
symops_R: FloatArray
symops_t: FloatArray
labels: tuple[str, ...]
numbers: IntArray
frac_positions: FloatArray
frac_positions_su: FloatArray
occupancies: FloatArray
occupancies_su: FloatArray
adp: AdpRecord
property n_atoms: int

Number of asymmetric-unit atom sites.

property n_symops: int

Number of symmetry operations provided by the source file.

property uij_cif: FloatArray

Anisotropic ADP matrices; non-Uani rows are NaN.

diffBloch.io.parse_experimental_block(block: Block, *, source_path: str | Path | None = None) ExperimentalRecord[source][source]

Parse a Gemmi PETS CIF-like block into a validated ExperimentalRecord.

diffBloch.io.parse_cif_number(value: Any) CifNumber[source][source]

Parse a CIF number and optional standard uncertainty.

A parenthesized SU is expressed in units of the final significant digit of the mantissa, so 0.0144(8) has SU 0.0008 and 42(3) has SU 3.

diffBloch.io.parse_structure_block(block: Block, *, source_path: str | Path | None = None, load_hydrogens: bool = False) StructureRecord[source][source]

Parse a Gemmi CIF block into a validated StructureRecord.

Parameters:
  • block – Parsed Gemmi CIF block.

  • source_path – Optional source path to retain in the record.

  • load_hydrogens – Include hydrogen atom sites when present. The default mirrors electron diffraction refinement practice where H sites are usually excluded from this boundary.

diffBloch.io.read_experimental_data(path: str | Path) ExperimentalRecord[source][source]

Read a PETS .cif_pets file into a validated ExperimentalRecord.

diffBloch.io.read_experimental_data_with_diagnostics(path: str | Path) ParsedInput[ExperimentalRecord][source][source]

Read a PETS .cif_pets file and report non-fatal parser decisions.

diffBloch.io.read_structure(path: str | Path, *, load_hydrogens: bool = False) StructureRecord[source][source]

Read a structure CIF into a validated StructureRecord.

Parameters:
  • path – CIF path.

  • load_hydrogens – Include hydrogen atom sites when present. The default mirrors electron diffraction refinement practice where H sites are usually excluded from this boundary.

diffBloch.io.read_structure_with_diagnostics(path: str | Path, *, load_hydrogens: bool = False) ParsedInput[StructureRecord][source][source]

Read a structure CIF and report non-fatal parser decisions.

diffBloch.io.symmetry_constraints(record: StructureRecord, *, symprec: float = 1e-3) SymmetryConstraints[source][source]

Return special-position constraint data for a validated structure record.

For each asymmetric-unit atom the site stabilizer S = {(R, t) : R @ x0 + t == x0 (mod 1)} is collected from the record’s symmetry operators, and the projector onto the allowed-displacement subspace is the Reynolds average P = (1/|S|) * sum_{R in S} R with offset (I - P) @ x0. P is idempotent and its image is exactly the free degrees of freedom, so P @ raw + offset keeps the atom on its site for any raw. The projector is built from the record’s symmetry operators (no diffpy); the ADP (Uij) equalities are extracted from diffpy SymmetryConstraints.Ueqns keyed on the record’s H-M symbol.

Validated IO records.

The core simulation code should receive arrays with stable shapes and units, not parser objects. These records are the trust boundary between file formats and differentiable kernels.

Record classes are pydantic models when they cross an IO/config boundary and need validation. Parser-local helpers can stay as lighter Python value objects, but public records should keep this validated boundary shape.

Standard uncertainties are stored as shape-aligned *_su arrays next to their nominal values, with NaN meaning absent from the source file. Keep that parallel-array convention until an uncertainty group needs behavior beyond validation, covariance storage, or multiple competing representations. ADPs already use a grouped record because CIF Uiso, Uani, and missing ADPs have different semantics.

uij_cif names the CIF-source ADP convention at the IO boundary. Constrained tensors currently reuse that field name for ASU ADPs produced from raw parameters; downstream scattering code must explicitly convert to reciprocal-space U* if that is the representation it consumes.

class diffBloch.io.record.AdpRecord(*, kind: tuple[Literal['Uiso', 'Uani', 'missing'], ...], u_iso: FloatArray, u_iso_su: FloatArray, uij_cif: FloatArray, uij_cif_su: FloatArray)[source][source]

Bases: BaseModel

Atomic displacement parameters, preserving CIF Uiso/Uani semantics and SUs.

model_config = {'arbitrary_types_allowed': True, 'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

kind: tuple[Literal['Uiso', 'Uani', 'missing'], ...]
u_iso: FloatArray
u_iso_su: FloatArray
uij_cif: FloatArray
uij_cif_su: FloatArray
class diffBloch.io.record.StructureRecord(*, source_path: Path | None = None, unit_cell: FloatArray, cell_parameters: FloatArray, cell_parameters_su: FloatArray, spacegroup_hm: str, spacegroup_number: int | None = None, symops_R: FloatArray, symops_t: FloatArray, labels: tuple[str, ...], numbers: IntArray, frac_positions: FloatArray, frac_positions_su: FloatArray, occupancies: FloatArray, occupancies_su: FloatArray, adp: AdpRecord)[source][source]

Bases: BaseModel

Asymmetric-unit structure data in CIF convention.

Nominal arrays feed downstream kernels. The corresponding *_su arrays preserve CIF standard uncertainties as provenance metadata for reporting or later optional penalties.

model_config = {'arbitrary_types_allowed': True, 'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

source_path: Path | None
unit_cell: FloatArray
cell_parameters: FloatArray
cell_parameters_su: FloatArray
spacegroup_hm: str
spacegroup_number: int | None
symops_R: FloatArray
symops_t: FloatArray
labels: tuple[str, ...]
numbers: IntArray
frac_positions: FloatArray
frac_positions_su: FloatArray
occupancies: FloatArray
occupancies_su: FloatArray
adp: AdpRecord
property n_atoms: int

Number of asymmetric-unit atom sites.

property n_symops: int

Number of symmetry operations provided by the source file.

property uij_cif: FloatArray

Anisotropic ADP matrices; non-Uani rows are NaN.

class diffBloch.io.record.ExperimentalRecord(*, source_path: Path | None = None, unit_cell: FloatArray, cell_parameters: FloatArray, cell_parameters_su: FloatArray, wavelength: float, data_collection_geometry: Literal['continuous_rotation', 'precession'] = 'continuous_rotation', dstar_max: float | None = None, mosaicity_degrees: float | None = None, rotation_axis_position_degrees: float | None = None, ub_matrix: FloatArray, zone_axis_ids: IntArray, zone_axes: FloatArray, precession_angles: FloatArray, alphas: FloatArray, betas: FloatArray, omegas: FloatArray, scales: FloatArray, hkl: IntArray, intensities: FloatArray, sigmas: FloatArray, reflection_zone_axis_ids: IntArray)[source][source]

Bases: BaseModel

Experimental PETS reflection data keyed by rotation/zone-axis id.

model_config = {'arbitrary_types_allowed': True, 'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

source_path: Path | None
unit_cell: FloatArray
cell_parameters: FloatArray
cell_parameters_su: FloatArray
wavelength: float
data_collection_geometry: Literal['continuous_rotation', 'precession']
dstar_max: float | None
mosaicity_degrees: float | None
rotation_axis_position_degrees: float | None
ub_matrix: FloatArray
zone_axis_ids: IntArray
zone_axes: FloatArray
precession_angles: FloatArray
alphas: FloatArray
betas: FloatArray
omegas: FloatArray
scales: FloatArray
hkl: IntArray
intensities: FloatArray
sigmas: FloatArray
reflection_zone_axis_ids: IntArray
property integration_semiangle: float

Angular integration semi-angle recorded consistently across the PETS virtual frames.

property n_rotations: int

Number of PETS zone-axis rows.

property n_reflections: int

Number of measured reflections.

CIF structure reader backed by gemmi.

diffBloch.io.cif.read_structure(path: str | Path, *, load_hydrogens: bool = False) StructureRecord[source][source]

Read a structure CIF into a validated StructureRecord.

Parameters:
  • path – CIF path.

  • load_hydrogens – Include hydrogen atom sites when present. The default mirrors electron diffraction refinement practice where H sites are usually excluded from this boundary.

diffBloch.io.cif.read_structure_with_diagnostics(path: str | Path, *, load_hydrogens: bool = False) ParsedInput[StructureRecord][source][source]

Read a structure CIF and report non-fatal parser decisions.

diffBloch.io.cif.parse_structure_block(block: Block, *, source_path: str | Path | None = None, load_hydrogens: bool = False) StructureRecord[source][source]

Parse a Gemmi CIF block into a validated StructureRecord.

Parameters:
  • block – Parsed Gemmi CIF block.

  • source_path – Optional source path to retain in the record.

  • load_hydrogens – Include hydrogen atom sites when present. The default mirrors electron diffraction refinement practice where H sites are usually excluded from this boundary.

PETS CIF-like experimental data reader.

diffBloch.io.pets.read_experimental_data(path: str | Path) ExperimentalRecord[source][source]

Read a PETS .cif_pets file into a validated ExperimentalRecord.

diffBloch.io.pets.read_experimental_data_with_diagnostics(path: str | Path) ParsedInput[ExperimentalRecord][source][source]

Read a PETS .cif_pets file and report non-fatal parser decisions.

diffBloch.io.pets.parse_experimental_block(block: Block, *, source_path: str | Path | None = None) ExperimentalRecord[source][source]

Parse a Gemmi PETS CIF-like block into a validated ExperimentalRecord.