midas-process-grains 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. midas_process_grains-0.1.0/PKG-INFO +176 -0
  2. midas_process_grains-0.1.0/README.md +145 -0
  3. midas_process_grains-0.1.0/midas_process_grains/__init__.py +41 -0
  4. midas_process_grains-0.1.0/midas_process_grains/__main__.py +9 -0
  5. midas_process_grains-0.1.0/midas_process_grains/cli.py +145 -0
  6. midas_process_grains-0.1.0/midas_process_grains/compute/__init__.py +35 -0
  7. midas_process_grains-0.1.0/midas_process_grains/compute/c_parity.py +498 -0
  8. midas_process_grains-0.1.0/midas_process_grains/compute/c_parity_emit.py +653 -0
  9. midas_process_grains-0.1.0/midas_process_grains/compute/c_parity_run.py +286 -0
  10. midas_process_grains-0.1.0/midas_process_grains/compute/canonicalize.py +131 -0
  11. midas_process_grains-0.1.0/midas_process_grains/compute/cluster.py +515 -0
  12. midas_process_grains-0.1.0/midas_process_grains/compute/conflict.py +245 -0
  13. midas_process_grains-0.1.0/midas_process_grains/compute/geometry.py +116 -0
  14. midas_process_grains-0.1.0/midas_process_grains/compute/pass_a.py +218 -0
  15. midas_process_grains-0.1.0/midas_process_grains/compute/refine_cluster.py +347 -0
  16. midas_process_grains-0.1.0/midas_process_grains/compute/strain.py +608 -0
  17. midas_process_grains-0.1.0/midas_process_grains/compute/stress.py +86 -0
  18. midas_process_grains-0.1.0/midas_process_grains/compute/symmetry.py +189 -0
  19. midas_process_grains-0.1.0/midas_process_grains/compute/twins.py +180 -0
  20. midas_process_grains-0.1.0/midas_process_grains/device.py +60 -0
  21. midas_process_grains-0.1.0/midas_process_grains/io/__init__.py +42 -0
  22. midas_process_grains-0.1.0/midas_process_grains/io/binary.py +317 -0
  23. midas_process_grains-0.1.0/midas_process_grains/io/consolidated.py +142 -0
  24. midas_process_grains-0.1.0/midas_process_grains/io/csv.py +179 -0
  25. midas_process_grains-0.1.0/midas_process_grains/io/hkls.py +61 -0
  26. midas_process_grains-0.1.0/midas_process_grains/io/ids_hash.py +111 -0
  27. midas_process_grains-0.1.0/midas_process_grains/modes.py +77 -0
  28. midas_process_grains-0.1.0/midas_process_grains/params.py +214 -0
  29. midas_process_grains-0.1.0/midas_process_grains/pipeline.py +836 -0
  30. midas_process_grains-0.1.0/midas_process_grains/result.py +157 -0
  31. midas_process_grains-0.1.0/midas_process_grains.egg-info/PKG-INFO +176 -0
  32. midas_process_grains-0.1.0/midas_process_grains.egg-info/SOURCES.txt +54 -0
  33. midas_process_grains-0.1.0/midas_process_grains.egg-info/dependency_links.txt +1 -0
  34. midas_process_grains-0.1.0/midas_process_grains.egg-info/entry_points.txt +2 -0
  35. midas_process_grains-0.1.0/midas_process_grains.egg-info/requires.txt +14 -0
  36. midas_process_grains-0.1.0/midas_process_grains.egg-info/top_level.txt +1 -0
  37. midas_process_grains-0.1.0/pyproject.toml +59 -0
  38. midas_process_grains-0.1.0/setup.cfg +4 -0
  39. midas_process_grains-0.1.0/tests/test_c_parity.py +329 -0
  40. midas_process_grains-0.1.0/tests/test_canonicalize.py +95 -0
  41. midas_process_grains-0.1.0/tests/test_cluster_bucket.py +149 -0
  42. midas_process_grains-0.1.0/tests/test_cluster_phase1.py +136 -0
  43. midas_process_grains-0.1.0/tests/test_cluster_phase2.py +183 -0
  44. midas_process_grains-0.1.0/tests/test_conflict.py +123 -0
  45. midas_process_grains-0.1.0/tests/test_geometry.py +124 -0
  46. midas_process_grains-0.1.0/tests/test_ids_hash.py +55 -0
  47. midas_process_grains-0.1.0/tests/test_io_binary.py +106 -0
  48. midas_process_grains-0.1.0/tests/test_io_csv.py +65 -0
  49. midas_process_grains-0.1.0/tests/test_modes.py +82 -0
  50. midas_process_grains-0.1.0/tests/test_params.py +78 -0
  51. midas_process_grains-0.1.0/tests/test_pass_a.py +172 -0
  52. midas_process_grains-0.1.0/tests/test_pipeline.py +100 -0
  53. midas_process_grains-0.1.0/tests/test_strain.py +275 -0
  54. midas_process_grains-0.1.0/tests/test_stress.py +52 -0
  55. midas_process_grains-0.1.0/tests/test_symmetry.py +99 -0
  56. midas_process_grains-0.1.0/tests/test_twins.py +87 -0
@@ -0,0 +1,176 @@
1
+ Metadata-Version: 2.4
2
+ Name: midas-process-grains
3
+ Version: 0.1.0
4
+ Summary: Pure-Python/PyTorch FF-HEDM grain-determination + strain pipeline (drop-in replacement for ProcessGrains)
5
+ Author-email: Hemant Sharma <hsharma@anl.gov>
6
+ License-Expression: BSD-3-Clause
7
+ Project-URL: Homepage, https://github.com/marinerhemant/MIDAS
8
+ Project-URL: Documentation, https://github.com/marinerhemant/MIDAS/tree/master/packages/midas_process_grains
9
+ Project-URL: Issues, https://github.com/marinerhemant/MIDAS/issues
10
+ Keywords: MIDAS,HEDM,grain-determination,strain,PyTorch,far-field,diffraction,crystallography,polycrystal,ProcessGrains
11
+ Classifier: Development Status :: 2 - Pre-Alpha
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Topic :: Scientific/Engineering :: Physics
16
+ Requires-Python: >=3.9
17
+ Description-Content-Type: text/markdown
18
+ Requires-Dist: numpy>=1.22
19
+ Requires-Dist: scipy>=1.9
20
+ Requires-Dist: torch>=2.0
21
+ Requires-Dist: h5py>=3.7
22
+ Requires-Dist: midas-stress<1.0,>=0.5.0
23
+ Requires-Dist: midas-hkls>=0.1.0
24
+ Requires-Dist: midas-diffract>=0.1.0
25
+ Requires-Dist: midas-transforms>=0.1.0
26
+ Requires-Dist: midas-index>=0.3.0
27
+ Provides-Extra: dev
28
+ Requires-Dist: pytest>=7.0; extra == "dev"
29
+ Requires-Dist: pytest-cov; extra == "dev"
30
+ Requires-Dist: pytest-benchmark; extra == "dev"
31
+
32
+ # midas-process-grains
33
+
34
+ Pure-Python (PyTorch) replacement for `FF_HEDM/src/ProcessGrains.c`. Reads the
35
+ binary outputs of the upstream MIDAS pipeline (`OrientPosFit.bin`, `Key.bin`,
36
+ `ProcessKey.bin`, `IndexBestFull.bin`, `FitBest.bin`) and emits the canonical
37
+ `Grains.csv` / `SpotMatrix.csv` / `GrainIDsKey.csv` files.
38
+
39
+ ## Scope: bit-level parity with the C reference
40
+
41
+ The package has one shippable mode: **`c_parity`**, which mirrors
42
+ `FF_HEDM/src/ProcessGrains.c` exactly. The Stage 1 cluster-build, the Pass A
43
+ position+orientation dedup, the confidence cut, and the 47-column
44
+ `Grains.csv` / 12-column `SpotMatrix.csv` / `GrainIDsKey.csv` writers all
45
+ follow the C source line-for-line.
46
+
47
+ On the `peakfit_hard` reference dataset (357 k seeds → 22 k grains), the
48
+ Python output is **bit-identical** to the C output for every column except
49
+ the Kenesei strain tensor — see "Parity verdict" below.
50
+
51
+ Earlier experimental modes (`legacy`, `paper_claim`, `spot_aware`) shipped in
52
+ v0.1 internal builds are still present in the source tree but are not
53
+ exposed through the supported public surface. They will be removed in a
54
+ future cleanup.
55
+
56
+ ## CLI
57
+
58
+ ```bash
59
+ midas-process-grains paramstest.txt 8 --mode c_parity --device cuda
60
+ ```
61
+
62
+ The CLI reads `paramstest.txt` from the run directory, writes the three
63
+ output files into `--out-dir` (defaulting to the run directory), and exits.
64
+ Use `--device cpu` if you do not have a CUDA-capable GPU.
65
+
66
+ ```bash
67
+ midas-process-grains paramstest.txt 8 \
68
+ --mode c_parity \
69
+ --device cuda \
70
+ --min-nr-spots 1 \
71
+ --out-dir ./output
72
+ ```
73
+
74
+ `--min-nr-spots` matches the `MinNrSpots` parameter in `paramstest.txt`
75
+ (Stage 1 cluster-size cutoff). Default is `1`, which keeps every cluster.
76
+
77
+ ## Library
78
+
79
+ ```python
80
+ from midas_process_grains.compute.c_parity_run import (
81
+ run_c_parity_pipeline_from_disk,
82
+ )
83
+
84
+ run_c_parity_pipeline_from_disk(
85
+ run_dir="/scratch/.../LayerNr_1",
86
+ out_dir="/scratch/.../LayerNr_1",
87
+ device="cuda", # or "cpu"
88
+ min_nr_spots=1,
89
+ )
90
+ ```
91
+
92
+ For lower-level access (run only Stage 1, only Pass A, only the writers,
93
+ etc.) see `midas_process_grains.compute.c_parity` and
94
+ `midas_process_grains.compute.c_parity_emit`.
95
+
96
+ ## Parity verdict (peakfit_hard, 22 k grains)
97
+
98
+ | Column | Python vs C max abs diff |
99
+ |---|---|
100
+ | `GrainID`, OM (9), `X`, `Y`, `Z`, lattice (6), `DiffPos`, `DiffOme`, `DiffAngle`, `GrainRadius`, `Confidence`, **Fable strain** (9), `Eul0`, `Eul1`, `Eul2` | **0** (bit-identical) |
101
+ | **Kenesei strain** (9 components) | ≤ 35 µε (NLOPT vs SciPy `lsq_linear` solver convergence) |
102
+ | `RMSErrorStrain` | ≤ 0.085 µε |
103
+
104
+ Cluster identity: 21,504 of 22,003 grains share the same `rep_pos` between
105
+ the C and Python runs. The remaining ~2 % is OMP `atomic_test_and_set`
106
+ non-determinism in the C source — running C on the same input twice produces
107
+ two outputs that disagree on **846 grains** (3.8 %). Python and a current C
108
+ rerun agree at **99.58 %** — closer than C agrees with itself across runs.
109
+
110
+ ## Performance
111
+
112
+ Wall time on a single peakfit_hard run (8-thread alleppey, NVIDIA H100 NVL):
113
+
114
+ | Pipeline | Wall | CPU time |
115
+ |---|---:|---:|
116
+ | C ProcessGrains, 8-thread OMP | 50 min | 396 min |
117
+ | Python `c_parity`, CPU 8-thread torch | 119 s | 676 s |
118
+ | Python `c_parity`, CUDA H100 | **113 s** | **125 s** |
119
+
120
+ Roughly **27× faster** wall-clock and **190× less CPU** on GPU. The biggest
121
+ wins are (a) Pass A's `O(N)` spatial-hash replacing C's `O(N²)` all-pairs,
122
+ (b) precomputing the misorientation graph for all spot-overlap candidates in
123
+ one batched torch call, and (c) batching all per-grain Kenesei solves into a
124
+ single `torch.linalg.solve` over a `(B, 6, 6)` stack.
125
+
126
+ ## Inputs
127
+
128
+ The pipeline reads the standard MIDAS run-directory layout:
129
+
130
+ ```
131
+ <run_dir>/
132
+ paramstest.txt
133
+ hkls.csv
134
+ IDsHash.csv
135
+ SpotsToIndex.csv
136
+ InputAllExtraInfoFittingAll.csv
137
+ Output/
138
+ IndexBestFull.bin
139
+ FitBest.bin
140
+ Results/
141
+ OrientPosFit.bin
142
+ Key.bin
143
+ ProcessKey.bin
144
+ ```
145
+
146
+ ## Outputs
147
+
148
+ ```
149
+ <out_dir>/
150
+ Grains.csv # 47 columns, C ProcessGrains layout
151
+ GrainIDsKey.csv # one line per kept grain
152
+ SpotMatrix.csv # 12 columns, C ProcessGrains layout
153
+ ```
154
+
155
+ ## Implementation notes
156
+
157
+ * Stage 1 (`FindInternalAngles` equivalent) does a recursive DFS over the
158
+ `ProcessKey`-defined spot-overlap candidate graph, filtered by misori
159
+ < `0.4°`. The misorientation for every candidate edge is precomputed in
160
+ one batched torch call before the DFS.
161
+ * Pass A (`misori < 0.1° AND |Δpos| < 5 µm` dedup) uses a 5 µm spatial hash
162
+ on rep positions to limit pairs to those within the position threshold,
163
+ then vectorised misori on the surviving pairs. Greedy outer-serial dedup
164
+ matches C's order.
165
+ * Confidence filter `OPF[26] >= 0.05` (matches C `OPs[ri][22] < 0.05` cut).
166
+ * Strain — Fable-Beaudoin from refined lattice (closed form), Kenesei from
167
+ per-spot lstsq (`scipy.optimize.lsq_linear` with the same ±0.01 bounds C
168
+ uses with NLOPT Nelder-Mead). Kenesei is solved in batch over all grains
169
+ in a single `torch.linalg.solve(GTG + λI, GTb)` call when running on GPU.
170
+ * Euler angles use C's exact `OrientMat2Euler` algorithm with the
171
+ `sin_cos_to_angle(s, c) = acos(c) if s ≥ 0 else 2π − acos(c)` helper.
172
+ Output is in **radians**, matching C.
173
+
174
+ See the docstrings in `compute/c_parity.py` and `compute/c_parity_emit.py`
175
+ for the full algorithm spec, with line-number references back to the C
176
+ source.
@@ -0,0 +1,145 @@
1
+ # midas-process-grains
2
+
3
+ Pure-Python (PyTorch) replacement for `FF_HEDM/src/ProcessGrains.c`. Reads the
4
+ binary outputs of the upstream MIDAS pipeline (`OrientPosFit.bin`, `Key.bin`,
5
+ `ProcessKey.bin`, `IndexBestFull.bin`, `FitBest.bin`) and emits the canonical
6
+ `Grains.csv` / `SpotMatrix.csv` / `GrainIDsKey.csv` files.
7
+
8
+ ## Scope: bit-level parity with the C reference
9
+
10
+ The package has one shippable mode: **`c_parity`**, which mirrors
11
+ `FF_HEDM/src/ProcessGrains.c` exactly. The Stage 1 cluster-build, the Pass A
12
+ position+orientation dedup, the confidence cut, and the 47-column
13
+ `Grains.csv` / 12-column `SpotMatrix.csv` / `GrainIDsKey.csv` writers all
14
+ follow the C source line-for-line.
15
+
16
+ On the `peakfit_hard` reference dataset (357 k seeds → 22 k grains), the
17
+ Python output is **bit-identical** to the C output for every column except
18
+ the Kenesei strain tensor — see "Parity verdict" below.
19
+
20
+ Earlier experimental modes (`legacy`, `paper_claim`, `spot_aware`) shipped in
21
+ v0.1 internal builds are still present in the source tree but are not
22
+ exposed through the supported public surface. They will be removed in a
23
+ future cleanup.
24
+
25
+ ## CLI
26
+
27
+ ```bash
28
+ midas-process-grains paramstest.txt 8 --mode c_parity --device cuda
29
+ ```
30
+
31
+ The CLI reads `paramstest.txt` from the run directory, writes the three
32
+ output files into `--out-dir` (defaulting to the run directory), and exits.
33
+ Use `--device cpu` if you do not have a CUDA-capable GPU.
34
+
35
+ ```bash
36
+ midas-process-grains paramstest.txt 8 \
37
+ --mode c_parity \
38
+ --device cuda \
39
+ --min-nr-spots 1 \
40
+ --out-dir ./output
41
+ ```
42
+
43
+ `--min-nr-spots` matches the `MinNrSpots` parameter in `paramstest.txt`
44
+ (Stage 1 cluster-size cutoff). Default is `1`, which keeps every cluster.
45
+
46
+ ## Library
47
+
48
+ ```python
49
+ from midas_process_grains.compute.c_parity_run import (
50
+ run_c_parity_pipeline_from_disk,
51
+ )
52
+
53
+ run_c_parity_pipeline_from_disk(
54
+ run_dir="/scratch/.../LayerNr_1",
55
+ out_dir="/scratch/.../LayerNr_1",
56
+ device="cuda", # or "cpu"
57
+ min_nr_spots=1,
58
+ )
59
+ ```
60
+
61
+ For lower-level access (run only Stage 1, only Pass A, only the writers,
62
+ etc.) see `midas_process_grains.compute.c_parity` and
63
+ `midas_process_grains.compute.c_parity_emit`.
64
+
65
+ ## Parity verdict (peakfit_hard, 22 k grains)
66
+
67
+ | Column | Python vs C max abs diff |
68
+ |---|---|
69
+ | `GrainID`, OM (9), `X`, `Y`, `Z`, lattice (6), `DiffPos`, `DiffOme`, `DiffAngle`, `GrainRadius`, `Confidence`, **Fable strain** (9), `Eul0`, `Eul1`, `Eul2` | **0** (bit-identical) |
70
+ | **Kenesei strain** (9 components) | ≤ 35 µε (NLOPT vs SciPy `lsq_linear` solver convergence) |
71
+ | `RMSErrorStrain` | ≤ 0.085 µε |
72
+
73
+ Cluster identity: 21,504 of 22,003 grains share the same `rep_pos` between
74
+ the C and Python runs. The remaining ~2 % is OMP `atomic_test_and_set`
75
+ non-determinism in the C source — running C on the same input twice produces
76
+ two outputs that disagree on **846 grains** (3.8 %). Python and a current C
77
+ rerun agree at **99.58 %** — closer than C agrees with itself across runs.
78
+
79
+ ## Performance
80
+
81
+ Wall time on a single peakfit_hard run (8-thread alleppey, NVIDIA H100 NVL):
82
+
83
+ | Pipeline | Wall | CPU time |
84
+ |---|---:|---:|
85
+ | C ProcessGrains, 8-thread OMP | 50 min | 396 min |
86
+ | Python `c_parity`, CPU 8-thread torch | 119 s | 676 s |
87
+ | Python `c_parity`, CUDA H100 | **113 s** | **125 s** |
88
+
89
+ Roughly **27× faster** wall-clock and **190× less CPU** on GPU. The biggest
90
+ wins are (a) Pass A's `O(N)` spatial-hash replacing C's `O(N²)` all-pairs,
91
+ (b) precomputing the misorientation graph for all spot-overlap candidates in
92
+ one batched torch call, and (c) batching all per-grain Kenesei solves into a
93
+ single `torch.linalg.solve` over a `(B, 6, 6)` stack.
94
+
95
+ ## Inputs
96
+
97
+ The pipeline reads the standard MIDAS run-directory layout:
98
+
99
+ ```
100
+ <run_dir>/
101
+ paramstest.txt
102
+ hkls.csv
103
+ IDsHash.csv
104
+ SpotsToIndex.csv
105
+ InputAllExtraInfoFittingAll.csv
106
+ Output/
107
+ IndexBestFull.bin
108
+ FitBest.bin
109
+ Results/
110
+ OrientPosFit.bin
111
+ Key.bin
112
+ ProcessKey.bin
113
+ ```
114
+
115
+ ## Outputs
116
+
117
+ ```
118
+ <out_dir>/
119
+ Grains.csv # 47 columns, C ProcessGrains layout
120
+ GrainIDsKey.csv # one line per kept grain
121
+ SpotMatrix.csv # 12 columns, C ProcessGrains layout
122
+ ```
123
+
124
+ ## Implementation notes
125
+
126
+ * Stage 1 (`FindInternalAngles` equivalent) does a recursive DFS over the
127
+ `ProcessKey`-defined spot-overlap candidate graph, filtered by misori
128
+ < `0.4°`. The misorientation for every candidate edge is precomputed in
129
+ one batched torch call before the DFS.
130
+ * Pass A (`misori < 0.1° AND |Δpos| < 5 µm` dedup) uses a 5 µm spatial hash
131
+ on rep positions to limit pairs to those within the position threshold,
132
+ then vectorised misori on the surviving pairs. Greedy outer-serial dedup
133
+ matches C's order.
134
+ * Confidence filter `OPF[26] >= 0.05` (matches C `OPs[ri][22] < 0.05` cut).
135
+ * Strain — Fable-Beaudoin from refined lattice (closed form), Kenesei from
136
+ per-spot lstsq (`scipy.optimize.lsq_linear` with the same ±0.01 bounds C
137
+ uses with NLOPT Nelder-Mead). Kenesei is solved in batch over all grains
138
+ in a single `torch.linalg.solve(GTG + λI, GTb)` call when running on GPU.
139
+ * Euler angles use C's exact `OrientMat2Euler` algorithm with the
140
+ `sin_cos_to_angle(s, c) = acos(c) if s ≥ 0 else 2π − acos(c)` helper.
141
+ Output is in **radians**, matching C.
142
+
143
+ See the docstrings in `compute/c_parity.py` and `compute/c_parity_emit.py`
144
+ for the full algorithm spec, with line-number references back to the C
145
+ source.
@@ -0,0 +1,41 @@
1
+ """midas-process-grains: pure-Python FF-HEDM grain-determination + strain.
2
+
3
+ Drop-in replacement for ``FF_HEDM/src/ProcessGrains.c``. Reads the binary
4
+ outputs of the upstream pipeline (``IndexBest{,Full}.bin``, ``FitBest.bin``,
5
+ ``Key.bin``, ``OrientPosFit.bin``, ``ProcessKey.bin``) and emits the canonical
6
+ MIDAS grain artefacts (``Grains.csv``, ``SpotMatrix.csv``, ``GrainIDsKey.csv``).
7
+
8
+ Three operating modes (`mode=` kwarg):
9
+
10
+ * ``"legacy"`` — bit-for-bit reproduce the current C ProcessGrains
11
+ output (used for regression tests during migration).
12
+ * ``"paper_claim"`` — the §3.6 spec from the MIDAS methodology paper that
13
+ the current C code does not actually enforce
14
+ (90% shared peaks, 0.01° misorientation, 15 µm pos).
15
+ * ``"spot_aware"`` — DEFAULT. Symmetry-aware row-aligned per-hkl SpotID
16
+ consistency, Jaccard pre-screen, union-of-cluster
17
+ emission, lstsq strain. No position gate.
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ __version__ = "0.1.0"
23
+
24
+ from .params import ProcessGrainsParams, read_paramstest_pg
25
+
26
+ __all__ = [
27
+ "__version__",
28
+ "ProcessGrainsParams",
29
+ "read_paramstest_pg",
30
+ ]
31
+
32
+
33
+ def __getattr__(name):
34
+ """Lazy import of pipeline-level symbols (avoid module cycles during build-up)."""
35
+ if name == "ProcessGrains":
36
+ from .pipeline import ProcessGrains
37
+ return ProcessGrains
38
+ if name == "ProcessGrainsResult":
39
+ from .result import ProcessGrainsResult
40
+ return ProcessGrainsResult
41
+ raise AttributeError(f"module 'midas_process_grains' has no attribute {name!r}")
@@ -0,0 +1,9 @@
1
+ """``python -m midas_process_grains`` shim."""
2
+ from __future__ import annotations
3
+
4
+ import sys
5
+
6
+ from .cli import main
7
+
8
+ if __name__ == "__main__":
9
+ sys.exit(main())
@@ -0,0 +1,145 @@
1
+ """CLI: ``midas-process-grains`` (and ``python -m midas_process_grains``).
2
+
3
+ Mirrors the C ``ProcessGrains`` invocation pattern (single positional arg:
4
+ the parameter file path) with optional flags to override mode, device,
5
+ dtype, and a couple of merge knobs.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import argparse
11
+ import sys
12
+ from pathlib import Path
13
+ from typing import List, Optional
14
+
15
+ from . import __version__
16
+
17
+
18
+ def _build_parser() -> argparse.ArgumentParser:
19
+ p = argparse.ArgumentParser(
20
+ prog="midas-process-grains",
21
+ description=(
22
+ "Pure-Python FF-HEDM grain-determination + strain pipeline "
23
+ "(drop-in for ProcessGrains)."
24
+ ),
25
+ )
26
+ p.add_argument(
27
+ "param_file",
28
+ type=Path,
29
+ help="Path to paramstest.txt (the same file IndexerOMP/FitPosOrStrains "
30
+ "consumed for this run).",
31
+ )
32
+ p.add_argument(
33
+ "num_procs", type=int, nargs="?", default=1,
34
+ help="CPU thread count (used only on cpu device). Default 1.",
35
+ )
36
+ p.add_argument(
37
+ "--mode", choices=("legacy", "paper_claim", "spot_aware", "c_parity"),
38
+ default="spot_aware",
39
+ help="Pipeline mode. Use 'c_parity' for a bit-level replica of the "
40
+ "C ProcessGrains pipeline (writes Grains.csv, GrainIDsKey.csv, "
41
+ "SpotMatrix.csv in C's exact format).",
42
+ )
43
+ p.add_argument(
44
+ "--min-nr-spots", type=int, default=None,
45
+ help="MinNrSpots threshold (Stage 1 cluster-size cutoff). C ProcessGrains "
46
+ "default is 1; the original peakfit_hard run used 3.",
47
+ )
48
+ p.add_argument("--device", choices=("cpu", "cuda", "mps"), default=None)
49
+ p.add_argument("--dtype", choices=("float32", "float64"), default=None)
50
+ p.add_argument("--misori-tol", type=float, default=None,
51
+ help="Override the Phase 1 misorientation tolerance (degrees).")
52
+ p.add_argument(
53
+ "--strain-method",
54
+ choices=(
55
+ "kenesei", "kenesei_unbounded", "fable_beaudoin", "both",
56
+ # backwards-compat aliases (resolved in params.validated())
57
+ "lstsq", "lattice",
58
+ ),
59
+ default=None,
60
+ help="Per-grain strain solver. Default: kenesei (bounded ±0.01, "
61
+ "matches C reference). Use fable_beaudoin for the lattice-"
62
+ "parameter route, or both to emit each.",
63
+ )
64
+ p.add_argument("--material", default=None,
65
+ help="Material name for stiffness lookup (e.g. Cu, Ni, Fe).")
66
+ p.add_argument("--stiffness-file", type=Path, default=None,
67
+ help="Path to a 6×6 stiffness matrix (CSV/TXT/NPY).")
68
+ p.add_argument("--out-dir", type=Path, default=None,
69
+ help="Where to write outputs. Default: param-file directory.")
70
+ p.add_argument("--no-h5", action="store_true",
71
+ help="Skip writing data_consolidated.h5.")
72
+ p.add_argument("--no-diagnostics-h5", action="store_true",
73
+ help="Skip writing processgrains_diagnostics.h5.")
74
+ p.add_argument("--max-seeds", type=int, default=None,
75
+ help="Process only the first N alive seeds (smoke / dev).")
76
+ p.add_argument("--version", action="version",
77
+ version=f"midas-process-grains {__version__}")
78
+ return p
79
+
80
+
81
+ def main(argv: Optional[List[str]] = None) -> int:
82
+ """CLI entry point. Returns process exit code."""
83
+ args = _build_parser().parse_args(argv)
84
+
85
+ from .device import apply_cpu_threads, resolve_device, resolve_dtype
86
+ from .pipeline import ProcessGrains
87
+
88
+ # ── c_parity mode: dispatch to the C-replica pipeline and return ────────
89
+ if args.mode == "c_parity":
90
+ from .compute.c_parity_run import run_c_parity_pipeline_from_disk
91
+ run_dir = args.param_file.parent
92
+ out_dir = args.out_dir if args.out_dir is not None else run_dir
93
+ device = resolve_device(args.device)
94
+ # torch device strings: "cpu" / "cuda" / "cuda:0" / "mps"
95
+ device_str = str(device) if not hasattr(device, "type") else (
96
+ device.type if device.index is None else f"{device.type}:{device.index}"
97
+ )
98
+ apply_cpu_threads(args.num_procs, device)
99
+ run_c_parity_pipeline_from_disk(
100
+ run_dir=run_dir,
101
+ out_dir=out_dir,
102
+ min_nr_spots=(args.min_nr_spots
103
+ if args.min_nr_spots is not None else 1),
104
+ device=device_str,
105
+ )
106
+ return 0
107
+
108
+ pg = ProcessGrains.from_param_file(
109
+ args.param_file,
110
+ device=args.device,
111
+ dtype=args.dtype,
112
+ )
113
+ apply_cpu_threads(args.num_procs, pg.device)
114
+
115
+ # CLI overrides on top of paramstest.
116
+ if args.misori_tol is not None:
117
+ pg.params.MisoriTol = float(args.misori_tol)
118
+ if args.strain_method is not None:
119
+ pg.params.StrainMethod = args.strain_method
120
+ if args.material is not None:
121
+ pg.params.MaterialName = args.material
122
+ if args.stiffness_file is not None:
123
+ pg.params.StiffnessFile = str(args.stiffness_file)
124
+ pg.params = pg.params.validated()
125
+
126
+ if args.max_seeds is not None:
127
+ pg.params.raw["__max_seeds__"] = [str(args.max_seeds)]
128
+
129
+ result = pg.run(mode=args.mode)
130
+ out_dir = args.out_dir if args.out_dir is not None else pg.run_dir
131
+ result.write(
132
+ out_dir,
133
+ h5=not args.no_h5,
134
+ diagnostics_h5=not args.no_diagnostics_h5,
135
+ )
136
+ print(
137
+ f"midas-process-grains {__version__}: "
138
+ f"{result.n_grains} grains written to {out_dir}",
139
+ file=sys.stderr,
140
+ )
141
+ return 0
142
+
143
+
144
+ if __name__ == "__main__":
145
+ sys.exit(main())
@@ -0,0 +1,35 @@
1
+ """Compute submodule.
2
+
3
+ Pure-tensor implementations of:
4
+ - symmetry table builders (24-op cubic / hexagonal / etc.)
5
+ - hkl-row permutation under each symmetry op
6
+ - cluster-level orientation canonicalisation
7
+ - misorientation graph + connected components (Phase 1)
8
+ - spot-aware sub-clustering (Phase 2)
9
+ - per-hkl SpotID conflict resolution (Phase 3)
10
+ - lstsq strain solver (Phase 4)
11
+ - Hooke's-law stress (Phase 5)
12
+ - twin post-processor
13
+
14
+ Design rule: every public function takes a ``device`` / ``dtype`` argument or
15
+ honours the caller's tensors' device + dtype, mirroring the conventions of
16
+ ``midas_index`` and ``midas_transforms``.
17
+ """
18
+
19
+ from .symmetry import (
20
+ SymmetryTable,
21
+ build_symmetry_table,
22
+ apply_sym_to_hkl_int,
23
+ )
24
+ from .canonicalize import (
25
+ pick_best_sym_op,
26
+ align_member_to_rep,
27
+ )
28
+
29
+ __all__ = [
30
+ "SymmetryTable",
31
+ "build_symmetry_table",
32
+ "apply_sym_to_hkl_int",
33
+ "pick_best_sym_op",
34
+ "align_member_to_rep",
35
+ ]