"""High-level CIF analysis pipeline (loads structure, prints distance tables, returns crystal + couplings)."""
from __future__ import annotations
import logging
from typing import Optional, Sequence
from pymatgen.core import Structure
from mag4.constants import DIST_EQUIV_TOL, CrystalData
from mag4.io_cif import (
crystal_from_cif,
get_mm_distances,
group_inequivalent,
)
from mag4.reports import print_inequivalent, print_mm_distances
log = logging.getLogger(__name__)
[docs]
def run_cif_analysis(
cif_path: str,
element: str,
cutoff: float,
tol: float = DIST_EQUIV_TOL,
*,
ligand_elements: Optional[Sequence[str]] = None,
bond_cutoff: float = 2.5,
angle_tol: float = 1.0,
ll_cutoff: float = 0.0,
signed_dihedral: bool = False,
) -> tuple[CrystalData, list]:
"""Print distance / J tables and return ``(crystal, couplings)``.
``couplings`` is a list of items that can be fed straight into
:func:`mag4.dimers.find_dimers`:
* Without ``ligand_elements``: ``[[label, distance], ...]`` (legacy form,
distance is the only discriminator).
* With ``ligand_elements`` set: ``[[label, distance, fingerprint], ...]``
where ``fingerprint`` is an
:data:`mag4.geometry.AngleFingerprint` carrying the M–L–M bridging
geometry — used by ``find_dimers`` as a secondary discriminator so that
pairs at the same distance but different bridging angles end up in
different J shells (``J1a``, ``J1b``, …).
"""
print(f"\nLoading structure from CIF: {cif_path}")
structure = Structure.from_file(cif_path)
print(f" Formula : {structure.composition.reduced_formula}")
print(f" Space grp: {structure.get_space_group_info()}")
a, b, c = structure.lattice.a, structure.lattice.b, structure.lattice.c
al, be, ga = (structure.lattice.alpha,
structure.lattice.beta,
structure.lattice.gamma)
print(f" Lattice : a={a:.4f} b={b:.4f} c={c:.4f} Å")
print(f" α={al:.3f}° β={be:.3f}° γ={ga:.3f}°")
# Formula units per primitive cell — used to normalise the multiplicity
# column ("dimers per formula unit") in the J shell table.
_reduced_formula, z_formula_units = structure.composition.get_reduced_formula_and_factor()
results = get_mm_distances(cif_path, element, cutoff)
print_mm_distances(results, element, cutoff, structure)
if ligand_elements:
# Geometry-aware grouping: distance THEN bridging-angle fingerprint.
from mag4.geometry import group_inequivalent_geom
groups = group_inequivalent_geom(
results, structure,
ligand_elements=ligand_elements,
bond_cutoff=bond_cutoff,
dist_tol=tol,
angle_tol=angle_tol,
ll_cutoff=ll_cutoff,
signed_dihedral=signed_dihedral,
)
print_inequivalent(groups, element, tol,
ligand_elements=ligand_elements,
bond_cutoff=bond_cutoff,
angle_tol=angle_tol,
ll_cutoff=ll_cutoff,
z_formula_units=int(z_formula_units))
couplings = [[g["label"], g["distance"], g["fingerprint"]] for g in groups]
else:
groups = group_inequivalent(results, tol)
print_inequivalent(groups, element, tol,
z_formula_units=int(z_formula_units))
couplings = [[g["label"], g["distance"]] for g in groups]
crystal = crystal_from_cif(cif_path, element)
return crystal, couplings