Skip to main content

midas-ddd

Dislocation networks for MIDAS: ingest a discrete-dislocation-dynamics network (ExaDiS / ParaDiS) and turn it into the displacement fields that three MIDAS forward models consume.

q . u~(q) + Laue term  ->  small-angle scattering       (midas_saxs)
(G+q) . u~(q)          ->  near-Bragg diffuse / Huang   (midas_defect.huang)
real-space beta(r)     ->  DFXM contrast                (midas_dfxm)

Everything is torch-differentiable.

Elastic vs total — the distinction that bites first. midas_ddd.fourier works with the TOTAL displacement; midas_ddd.realspace (Mura line integral over finite segments) returns the ELASTIC distortion. Both are physical and they are not the same thing: DFXM images the elastic part, because the lattice is continuous across the cut surface for a perfect dislocation, while the scattering kernels need the total. The two are tied together quantitatively — their ball-averaged trace integrals are (2/3)(1-2nu)/(1-nu) and (1+nu)/(3(1-nu)) in units of dV, which sum to exactly 1 because the plastic eigendistortion on the cut contributes -dV. That relation is the cross-modality gate in tests/test_realspace.py. When the two kernels first appeared to disagree by a clean functional factor, this was why.

Scope differs between the two. u_tilde, the distortion field for near-Bragg work, handles CLOSED LOOPS only (a cut surface needs a closed circuit) and reports the line length it ignored. The total small-angle amplitude has no such limit: by Stokes it is a line integral, so line_small_angle_amplitude covers finite lines at every q, and lines that close only through a periodic cell's boundary get an intensity from the cell's reciprocal lattice (periodic_small_angle_intensity). The real-space kernel images any network in DFXM.

Quick start

from midas_ddd import read_paradis, validate_network, prismatic_loop
from midas_ddd import small_angle_amplitude, isotropic_stiffness
import torch

# A network from ExaDiS, via the file bridge that always works
net = read_paradis("net.data", b_magnitude_A=2.556)
print(validate_network(net, q_max_inv_A=0.1))

# Or build one here, with no ExaDiS installed
loop = prismatic_loop(radius_um=0.005, burgers=(0, 0, 1), n_segments=64)

# q in the loop plane, inverse micrometers. Along the normal, (0, 0, q), the
# small-angle amplitude is exactly zero.
q = torch.tensor([[1e-2, 0.0, 0.0]], dtype=torch.float64)
# Distortion PLUS Laue term. `q_dot_u_tilde` is the distortion term alone and
# is not the small-angle answer.
amp = small_angle_amplitude(loop, q, isotropic_stiffness(100.0, 75.0))

Three things worth knowing before you use it

1. Burgers magnitudes are physical; they are not normalised. A perfect dislocation has |b| = 1 in file units, but a junction from a dislocation reaction has |b| = sqrt(2), and that magnitude is what balances Burgers conservation at the junction node. The 5055-node FCC-Cu network in this repo has 54 such segments; normalising them made 60 nodes fail conservation with a residual of exactly sqrt(2) - 1.

2. For u_tilde, the cut surface is physical, not a gauge. Stokes gives the part of the surface form factor perpendicular to q from a cheap line integral, which looks like a way to avoid choosing a cut surface. For the displacement field it is not: for a prismatic loop probed along its own normal, where q . u~ is largest, the transverse projection returns exactly zero. The kernel integrates an explicit fan triangulation from each loop's centroid. Gated by test_transverse_gauge_would_destroy_the_signal.

3. Loops, finite lines and periodic lines are three different objects. A closed loop has a cut surface and a relaxation volume dV = b . A, and its amplitude is exact at every q. A finite line has neither, so it contributes nothing as q -> 0; at finite q its dilatation field scatters into a sheet perpendicular to the line (Thomson, Levine & Long, Acta Cryst. A 55, 433 (1999), Eq. 6; an isotropic screw gives exactly zero), and line_small_angle_amplitude computes that from the same line-integral form the loops use. A line that closes only through a periodic cell's boundary has an amplitude only on the cell's reciprocal lattice; periodic_small_angle_intensity shows it between lattice points at a stated resolution (default two lattice spacings), which is what a window of that width sees, and reports a low-q floor below which the cell's own periodicity dominates. An earlier version of this README said lines contribute nothing at small angle; that holds only as q -> 0. The periodic treatment is preregistered and under adversarial verification (2026-09-11), not established. u_tilde still takes closed loops only and reports the line length it ignored.

The gates

For isotropic elasticity and a prismatic loop (b || A || n) the small-q limit of the distortion term has an exact closed form, verified to 2e-15 against the tensor expression over 1200 random directions and four moduli:

q . u~(q -> 0) = i dV [ kappa + (1 - kappa) (n.qhat)^2 ]
    dV = b . A          (pi R^2 |b| for a circular loop)
    kappa = lambda / (lambda + 2 mu)

That is not the small-angle scattering amplitude. At small angle the scattering vector is the deviation itself, and the Laue term (scattering from the loop's own extra or missing atoms) is the same order as the distortion term (Ehrhart, Trinkaus & Larson, Phys. Rev. B 25 (1982) 834, Eq. 8a). The total, small_angle_amplitude, has the limit

q . u~ + Laue  ->  i dV (1 - kappa) sin^2(theta)       theta from the loop normal

verified to 7e-16 over 1500 directions and five moduli. The two laws sum to dV identically and point opposite ways: the total vanishes along the normal, where the distortion term alone is largest, and peaks in the loop plane. A void of the same volume is isotropic. Use small_angle_amplitude for SAXS; q_dot_u_tilde is the distortion building block.

That anisotropy is why a 2-D detector image is worth simulating instead of a radially averaged I(q) — but it is not a general loop-versus-void discriminator; see the known-limitations table below.

ExaDiS

ExaDiS needs a Kokkos/CMake build and can never be a pip dependency. Two routes:

  • File bridge (always works). Run ExaDiS anywhere, call its write_data(N, "net.data"), read it with read_paradis.
  • In-process (optional). midas_ddd.exadis lazily imports pyexadis and wraps generate_prismatic_config (irradiation loops, radius accepts [min, max]) and generate_line_config (deformation lines). Build with cmake .. -DEXADIS_PYTHON_BINDING=On; on the beamline, chiltepin is the only host with internet.

midas_ddd.generate provides small exact equivalents (prismatic_loop, straight_line, combine) so the whole test suite runs without ExaDiS.

The in-process bridge is exercised by tests/test_exadis.py, which skips without a build. Its load-bearing test takes BOTH routes into this package from one ExaDiS config — objects converted in memory, and ExaDiS's own write_data read back by read_paradis — and requires them to agree.

Resolution

A nodal DDD code discretises a 2 nm loop into a handful of segments, and SAXS at q ~ 1/R probes exactly that scale. resolution_report bounds the usable q at 2 pi / L_median and says so. The repo's FCC-Cu network has a median segment of 651 nm, giving q_max ~ 1e-3 1/A — a SAXS run at 0.1 1/A on it would be reporting the polyline, not the dislocations.

Relationship to midas_defect

The anisotropic-elasticity (Stroh) primitives here — cubic_stiffness, hexagonal_stiffness, the sextic solver, the slip-system tables — used to live in midas_defect.contrast_factor, which already carried a "do NOT re-port" contract because midas_dfxm imported them across a package boundary. They moved down when a third and fourth consumer appeared: a SAXS package should not depend on an FF-HEDM metrology package to build a stiffness matrix. midas_defect.contrast_factor re-exports every name, so no historical import path broke, and test_elasticity.py::test_reexport_* fails loudly if anyone re-ports a copy.

Known limitations — claims that were refuted, and must not come back

Each of these looked like a feature at some point in this package's development and was killed by adversarial review. They are recorded because the cheapest way to lose a week here is to re-derive one of them.

Claim Why it is wrong
The kappa law is a loop-vs-void discriminator A uniaxial plate precipitate is identical to a loop (6 of 6 directions agreeing to 1e-10), and randomly oriented loops are exactly degenerate with a void: the variant mean of n (x) n is exactly I/3 for every cubic family.
"elastic + total = 1" confirms both kernels against each other An algebraic tautology. The gate never called the Fourier kernel at all, and a sphere passes it identically because the sphere average is tr(P)/3. It is Eshelby restated (Lazar 2017, Eqs. 77/94/95).
An ExaDiS loop network is invisible to DFXM The 1e-4 rad figure matched no measured quantity — most likely the dimensionless eps = 1.4e-4 read as an angle. The right floor is per-pixel precision, 2 mdeg = 3.5e-5 rad, and single dislocations are routinely imaged (Jakobsen 2019; Borgi 2024/2025).
The kappa law / the small-angle anisotropy is novel Clouet 2018 Eq. 26; Dederichs 1973.
SAXS determines loop number density to 2.6 % from one frame REFUTED 2026-09-04. Restoring the mandated signed dV gives cond 2.77e16 and an unbounded SE; the data fix only n*dV^2. Full autopsy in the midas-saxs README. Loop radius at 0.6 % survived and may be stated, given q_max * R >~ 3.

What the refutations did not touch. Three independent lenses confirmed the Fourier kernel: the Laue-corrected small-q law was re-derived from a sum rule with no MIDAS code, agreeing to 5.55e-17; the Ehrhart-Trinkaus-Larson Eq. (8b) implementation is gauge-invariant; and an independent numpy forward reproduced the CRB to four digits. The code is sound. What was wrong, every time, was a claim about what the code measured.

One positive result from the same review. A discrete fcc {111} four-variant loop population is not degenerate with a void at zero selection (contrast 1.47, SE 11.8 %), because four discrete normals have anisotropic fourth moments. The <n (x) n> = I/3 degeneracy above is a second-moment statement about the amplitude and does not transfer to the incoherent <|A|^2>. This needs a single crystal or strong texture: a randomly textured polycrystal averages back to uniform (contrast 1.0004, SE 699 %).

Two facts that are load-bearing for anything built on this package. Loop character — interstitial versus vacancy — is unrecoverable from small-angle intensity, but IS reachable near Bragg through the structure-factor phase Phi = -1 on odd reflections for extrinsic loops (Ehrhart 1982). And open (line) dislocations carry no relaxation volume, so they contribute nothing as q -> 0; at finite q they do scatter (a sheet perpendicular to each edge or mixed line, nothing from an isotropic screw), and the SAXS forward now includes them (midas_saxs.simulate_frame, components lines and periodic_lines). An earlier version said they were absent from small angle entirely; that was the q -> 0 limit stated as a rule. They image perfectly well in DFXM.

Numerical traps, all of them found the hard way

Burgers magnitudes are not normalised (see above): 54 of the repo network's 5930 segments are sqrt(2) junctions, and normalising them broke conservation at 60 nodes by exactly sqrt(2) - 1.

The cut surface is subdivided RADIALLY, not just around the circumference. A plain fan from the centroid has an edge of length R running out to every vertex, so the quadrature parameter |q|h is set by the loop radius however finely the polygon is discretised. At qR = 10 — mid-detector for real SAXS — a plain fan is ~20 % wrong. u_tilde picks n_rings from q_max automatically.

Polygon shape is not quadrature error. With the quadrature converged to 1e-8, a 64-gon and a 256-gon still differ by ~1 % at qR = 10. That is the polygon, a modelling choice; only n_segments changes it.

Fixed-order quadrature fails the Mura line integral. The integrand falls as 1/R^2, so eight Gauss nodes spread over a long segment miss the peak entirely: a 400 um segment probed at 0.5 um came out ~1000x low and rising with r. realspace subdivides into panels sized by the closest approach.

Metadata

Release files for midas-ddd 0.1.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for midas-ddd 0.1.2
File Size Uploaded
midas_ddd-0.1.2.tar.gz 99.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for midas-ddd 0.1.2
File Interpreter ABI Platform
midas_ddd-0.1.2-py3-none-any.whl Python 3 none any Details

Total release size: 164.3 kB

Release files / midas_ddd-0.1.2.tar.gz

Download URL midas_ddd-0.1.2.tar.gz
Size 99.0 kB
Tags Source
SHA-256 checksum
How to use checksums
ecc95dc61c09690386170e06f9ee2bdab14cd4afc58dfb26fd6aaf9f28d9cda6
BLAKE2b-256 checksum
How to use checksums
ff3009cc008b61c06d4cca342d187f4350ec2b60fab4081cea3d2361427c5906
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 11, 2026.

Transparency log

Release files / midas_ddd-0.1.2-py3-none-any.whl

Download URL midas_ddd-0.1.2-py3-none-any.whl
Size 65.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d50adafa0e8c1744bec0ed67ded5e1a137b2e2f9b30abf8f89fdef29acf7ca86
BLAKE2b-256 checksum
How to use checksums
f70d3a56d0da00954de52d88440a1adfc1ae38379197922f4b1d0903202cbd95
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 11, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.2 This release

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page