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.
- midas_process_grains-0.1.0/PKG-INFO +176 -0
- midas_process_grains-0.1.0/README.md +145 -0
- midas_process_grains-0.1.0/midas_process_grains/__init__.py +41 -0
- midas_process_grains-0.1.0/midas_process_grains/__main__.py +9 -0
- midas_process_grains-0.1.0/midas_process_grains/cli.py +145 -0
- midas_process_grains-0.1.0/midas_process_grains/compute/__init__.py +35 -0
- midas_process_grains-0.1.0/midas_process_grains/compute/c_parity.py +498 -0
- midas_process_grains-0.1.0/midas_process_grains/compute/c_parity_emit.py +653 -0
- midas_process_grains-0.1.0/midas_process_grains/compute/c_parity_run.py +286 -0
- midas_process_grains-0.1.0/midas_process_grains/compute/canonicalize.py +131 -0
- midas_process_grains-0.1.0/midas_process_grains/compute/cluster.py +515 -0
- midas_process_grains-0.1.0/midas_process_grains/compute/conflict.py +245 -0
- midas_process_grains-0.1.0/midas_process_grains/compute/geometry.py +116 -0
- midas_process_grains-0.1.0/midas_process_grains/compute/pass_a.py +218 -0
- midas_process_grains-0.1.0/midas_process_grains/compute/refine_cluster.py +347 -0
- midas_process_grains-0.1.0/midas_process_grains/compute/strain.py +608 -0
- midas_process_grains-0.1.0/midas_process_grains/compute/stress.py +86 -0
- midas_process_grains-0.1.0/midas_process_grains/compute/symmetry.py +189 -0
- midas_process_grains-0.1.0/midas_process_grains/compute/twins.py +180 -0
- midas_process_grains-0.1.0/midas_process_grains/device.py +60 -0
- midas_process_grains-0.1.0/midas_process_grains/io/__init__.py +42 -0
- midas_process_grains-0.1.0/midas_process_grains/io/binary.py +317 -0
- midas_process_grains-0.1.0/midas_process_grains/io/consolidated.py +142 -0
- midas_process_grains-0.1.0/midas_process_grains/io/csv.py +179 -0
- midas_process_grains-0.1.0/midas_process_grains/io/hkls.py +61 -0
- midas_process_grains-0.1.0/midas_process_grains/io/ids_hash.py +111 -0
- midas_process_grains-0.1.0/midas_process_grains/modes.py +77 -0
- midas_process_grains-0.1.0/midas_process_grains/params.py +214 -0
- midas_process_grains-0.1.0/midas_process_grains/pipeline.py +836 -0
- midas_process_grains-0.1.0/midas_process_grains/result.py +157 -0
- midas_process_grains-0.1.0/midas_process_grains.egg-info/PKG-INFO +176 -0
- midas_process_grains-0.1.0/midas_process_grains.egg-info/SOURCES.txt +54 -0
- midas_process_grains-0.1.0/midas_process_grains.egg-info/dependency_links.txt +1 -0
- midas_process_grains-0.1.0/midas_process_grains.egg-info/entry_points.txt +2 -0
- midas_process_grains-0.1.0/midas_process_grains.egg-info/requires.txt +14 -0
- midas_process_grains-0.1.0/midas_process_grains.egg-info/top_level.txt +1 -0
- midas_process_grains-0.1.0/pyproject.toml +59 -0
- midas_process_grains-0.1.0/setup.cfg +4 -0
- midas_process_grains-0.1.0/tests/test_c_parity.py +329 -0
- midas_process_grains-0.1.0/tests/test_canonicalize.py +95 -0
- midas_process_grains-0.1.0/tests/test_cluster_bucket.py +149 -0
- midas_process_grains-0.1.0/tests/test_cluster_phase1.py +136 -0
- midas_process_grains-0.1.0/tests/test_cluster_phase2.py +183 -0
- midas_process_grains-0.1.0/tests/test_conflict.py +123 -0
- midas_process_grains-0.1.0/tests/test_geometry.py +124 -0
- midas_process_grains-0.1.0/tests/test_ids_hash.py +55 -0
- midas_process_grains-0.1.0/tests/test_io_binary.py +106 -0
- midas_process_grains-0.1.0/tests/test_io_csv.py +65 -0
- midas_process_grains-0.1.0/tests/test_modes.py +82 -0
- midas_process_grains-0.1.0/tests/test_params.py +78 -0
- midas_process_grains-0.1.0/tests/test_pass_a.py +172 -0
- midas_process_grains-0.1.0/tests/test_pipeline.py +100 -0
- midas_process_grains-0.1.0/tests/test_strain.py +275 -0
- midas_process_grains-0.1.0/tests/test_stress.py +52 -0
- midas_process_grains-0.1.0/tests/test_symmetry.py +99 -0
- 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,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
|
+
]
|