midas-plotting 0.3.2__tar.gz → 0.4.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_plotting-0.3.2 → midas_plotting-0.4.0}/PKG-INFO +2 -2
- {midas_plotting-0.3.2 → midas_plotting-0.4.0}/README.md +1 -1
- {midas_plotting-0.3.2 → midas_plotting-0.4.0}/midas_plotting/__init__.py +1 -1
- {midas_plotting-0.3.2 → midas_plotting-0.4.0}/midas_plotting/ff.py +11 -3
- {midas_plotting-0.3.2 → midas_plotting-0.4.0}/midas_plotting/grains.py +40 -12
- {midas_plotting-0.3.2 → midas_plotting-0.4.0}/midas_plotting.egg-info/PKG-INFO +2 -2
- {midas_plotting-0.3.2 → midas_plotting-0.4.0}/pyproject.toml +1 -1
- {midas_plotting-0.3.2 → midas_plotting-0.4.0}/tests/test_ff.py +119 -13
- {midas_plotting-0.3.2 → midas_plotting-0.4.0}/midas_plotting/cli.py +0 -0
- {midas_plotting-0.3.2 → midas_plotting-0.4.0}/midas_plotting/ipf.py +0 -0
- {midas_plotting-0.3.2 → midas_plotting-0.4.0}/midas_plotting/laue.py +0 -0
- {midas_plotting-0.3.2 → midas_plotting-0.4.0}/midas_plotting/maps.py +0 -0
- {midas_plotting-0.3.2 → midas_plotting-0.4.0}/midas_plotting/mic.py +0 -0
- {midas_plotting-0.3.2 → midas_plotting-0.4.0}/midas_plotting/solutions.py +0 -0
- {midas_plotting-0.3.2 → midas_plotting-0.4.0}/midas_plotting.egg-info/SOURCES.txt +0 -0
- {midas_plotting-0.3.2 → midas_plotting-0.4.0}/midas_plotting.egg-info/dependency_links.txt +0 -0
- {midas_plotting-0.3.2 → midas_plotting-0.4.0}/midas_plotting.egg-info/entry_points.txt +0 -0
- {midas_plotting-0.3.2 → midas_plotting-0.4.0}/midas_plotting.egg-info/requires.txt +0 -0
- {midas_plotting-0.3.2 → midas_plotting-0.4.0}/midas_plotting.egg-info/top_level.txt +0 -0
- {midas_plotting-0.3.2 → midas_plotting-0.4.0}/setup.cfg +0 -0
- {midas_plotting-0.3.2 → midas_plotting-0.4.0}/tests/test_ipf.py +0 -0
- {midas_plotting-0.3.2 → midas_plotting-0.4.0}/tests/test_laue.py +0 -0
- {midas_plotting-0.3.2 → midas_plotting-0.4.0}/tests/test_mic.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: midas-plotting
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Standard plots for MIDAS reconstructions - near-field, far-field and Laue: IPF maps and legends, grain maps, pole figures, strain and size distributions, and Laue texture diagnostics against their chance levels.
|
|
5
5
|
Author-email: Hemant Sharma <hsharma@anl.gov>
|
|
6
6
|
License: BSD-3-Clause
|
|
@@ -88,7 +88,7 @@ Things the module will not let you get wrong:
|
|
|
88
88
|
* **Symmetry comes from the file.** `Grains.csv` states its space group in the
|
|
89
89
|
preamble; the plots use it. Defaulting to cubic would colour a hexagonal
|
|
90
90
|
sample with the wrong IPF triangle and produce a plausible, wrong figure.
|
|
91
|
-
* **Columns are read by name.** `Grains.csv` has
|
|
91
|
+
* **Columns are read by name.** `Grains.csv` has 53 columns (47 before 2026-08-21) and
|
|
92
92
|
`midas-fit-grain` 0.5.6 shipped a cyclic rotation of three of them; a
|
|
93
93
|
positional reader inherits that silently.
|
|
94
94
|
* **Euler angles are cross-checked against `O11..O33`.** They describe the same
|
|
@@ -67,7 +67,7 @@ Things the module will not let you get wrong:
|
|
|
67
67
|
* **Symmetry comes from the file.** `Grains.csv` states its space group in the
|
|
68
68
|
preamble; the plots use it. Defaulting to cubic would colour a hexagonal
|
|
69
69
|
sample with the wrong IPF triangle and produce a plausible, wrong figure.
|
|
70
|
-
* **Columns are read by name.** `Grains.csv` has
|
|
70
|
+
* **Columns are read by name.** `Grains.csv` has 53 columns (47 before 2026-08-21) and
|
|
71
71
|
`midas-fit-grain` 0.5.6 shipped a cyclic rotation of three of them; a
|
|
72
72
|
positional reader inherits that silently.
|
|
73
73
|
* **Euler angles are cross-checked against `O11..O33`.** They describe the same
|
|
@@ -48,7 +48,7 @@ from .solutions import (
|
|
|
48
48
|
)
|
|
49
49
|
from .mic import MicMap, read_mic
|
|
50
50
|
|
|
51
|
-
__version__ = "0.
|
|
51
|
+
__version__ = "0.4.0"
|
|
52
52
|
__all__ = [
|
|
53
53
|
"MicMap", "read_mic", "GrainList", "read_grains", "ff", "laue",
|
|
54
54
|
"LaueSolutions", "LaueSpots", "read_solutions", "read_spots",
|
|
@@ -318,9 +318,17 @@ def completeness_hist(grains, ax=None, *, bins: int = 30,
|
|
|
318
318
|
|
|
319
319
|
# ─── strain ─────────────────────────────────────────────────────────────────
|
|
320
320
|
def strain_scalar(grains, kind: str = "hydrostatic", *,
|
|
321
|
-
convention: str = "
|
|
321
|
+
convention: str = "ken") -> np.ndarray:
|
|
322
322
|
"""Reduce the per-grain strain tensor to one number per grain.
|
|
323
323
|
|
|
324
|
+
``convention`` defaults to **eKen**, matching
|
|
325
|
+
``midas_stress.io.read_grains_csv``'s ``strain`` key and its documented
|
|
326
|
+
recommendation, so a strain map from this module and a stress computed by
|
|
327
|
+
``midas_stress`` from the same Grains.csv are the same tensor. It used to
|
|
328
|
+
default to ``"fab"``; both sites labelled their choice, so nothing was
|
|
329
|
+
mis-read, but the two answers silently differed. Pass ``convention="fab"``
|
|
330
|
+
for the lattice-parameter form.
|
|
331
|
+
|
|
324
332
|
``kind``:
|
|
325
333
|
``hydrostatic`` trace/3
|
|
326
334
|
``vonmises`` von Mises equivalent of the deviatoric part
|
|
@@ -352,7 +360,7 @@ def strain_scalar(grains, kind: str = "hydrostatic", *,
|
|
|
352
360
|
|
|
353
361
|
|
|
354
362
|
def strain_map(grains, ax=None, *, kind: str = "hydrostatic",
|
|
355
|
-
convention: str = "
|
|
363
|
+
convention: str = "ken", plane: str = "xy", cmin: float = 0.0,
|
|
356
364
|
cmap: str = "coolwarm", vmin=None, vmax=None,
|
|
357
365
|
symmetric: bool = True, title: Optional[str] = None):
|
|
358
366
|
"""Grain map coloured by a strain scalar (µε).
|
|
@@ -392,7 +400,7 @@ def strain_map(grains, ax=None, *, kind: str = "hydrostatic",
|
|
|
392
400
|
return ax
|
|
393
401
|
|
|
394
402
|
|
|
395
|
-
def strain_distribution(grains, ax=None, *, convention: str = "
|
|
403
|
+
def strain_distribution(grains, ax=None, *, convention: str = "ken",
|
|
396
404
|
bins: int = 30, title: Optional[str] = None):
|
|
397
405
|
"""Histograms of the three normal strain components (µε)."""
|
|
398
406
|
import matplotlib.pyplot as plt
|
|
@@ -4,11 +4,16 @@ The FF analogue of :mod:`midas_plotting.mic`: one place that knows the column
|
|
|
4
4
|
layout so analysis scripts stop re-deriving it.
|
|
5
5
|
|
|
6
6
|
Columns are looked up **by name** from the ``%ID ...`` header line, never by
|
|
7
|
-
position. That matters more here than it looks: ``Grains.csv`` has
|
|
8
|
-
columns, and `midas-fit-grain` 0.5.6
|
|
9
|
-
``DiffPos`` / ``DiffOme`` / ``DiffAngle``
|
|
10
|
-
positional reader would silently inherit --
|
|
11
|
-
where the true value was 0.054°.
|
|
7
|
+
position. That matters more here than it looks: ``Grains.csv`` has been widened
|
|
8
|
+
repeatedly (19 -> 21 -> 23 -> 47 -> 53 columns), and `midas-fit-grain` 0.5.6
|
|
9
|
+
shipped a cyclic rotation of the ``DiffPos`` / ``DiffOme`` / ``DiffAngle``
|
|
10
|
+
columns (fixed in 0.5.7) that a positional reader would silently inherit --
|
|
11
|
+
one grain's ω residual read 223.87° where the true value was 0.054°.
|
|
12
|
+
|
|
13
|
+
Rows are split on **whitespace**, not on tabs: the C ``ProcessGrains`` writer
|
|
14
|
+
ends every data row with a tab, so a bare ``split("\t")`` produces a trailing
|
|
15
|
+
empty field and ``float("")`` raises on files that are otherwise perfectly
|
|
16
|
+
valid.
|
|
12
17
|
|
|
13
18
|
As a further guard, :func:`read_grains` recomputes the orientation matrix from
|
|
14
19
|
the Euler angles and compares it against the ``O11..O33`` columns. Both describe
|
|
@@ -125,8 +130,20 @@ class GrainList:
|
|
|
125
130
|
def n_grains(self) -> int:
|
|
126
131
|
return len(self)
|
|
127
132
|
|
|
128
|
-
def strain(self, convention: str = "
|
|
129
|
-
"""``(N, 3, 3)`` strain tensor in the requested convention.
|
|
133
|
+
def strain(self, convention: str = "ken") -> np.ndarray:
|
|
134
|
+
"""``(N, 3, 3)`` strain tensor in the requested convention.
|
|
135
|
+
|
|
136
|
+
Defaults to **eKen** to agree with ``midas_stress.io.read_grains_csv``,
|
|
137
|
+
whose plain ``strain`` key is eKen and which documents it as the
|
|
138
|
+
recommended form (it is fit directly from the per-reflection
|
|
139
|
+
``eps_ij g_i g_j = (d_obs - d_0)/d_0`` gauge equation, so it is tied to
|
|
140
|
+
the raw observables and has better noise properties than the
|
|
141
|
+
lattice-parameter form). These two packages are routinely pointed at
|
|
142
|
+
the same Grains.csv -- a strain map here and a stress calculation
|
|
143
|
+
there -- and while both label their choice, disagreeing on the default
|
|
144
|
+
meant the map and the stress were not the same tensor. Pass
|
|
145
|
+
``convention="fab"`` for the lattice-parameter (Fable-Beaudoin) form.
|
|
146
|
+
"""
|
|
130
147
|
c = convention.lower()
|
|
131
148
|
if c in ("fab", "fable", "efab"):
|
|
132
149
|
s = self.strain_fab
|
|
@@ -163,11 +180,22 @@ def _parse(path: Path):
|
|
|
163
180
|
if not line.strip():
|
|
164
181
|
continue
|
|
165
182
|
if not line.startswith("%"):
|
|
166
|
-
|
|
183
|
+
# Whitespace-split, NOT `split("\t")`. The C `ProcessGrains`
|
|
184
|
+
# terminates every data row with a tab, so a bare tab-split yields
|
|
185
|
+
# a spurious empty final field and `float("")` raises -- which is
|
|
186
|
+
# exactly what every file under midas_stress/dev/paper3/runs/ and
|
|
187
|
+
# the bundled GrainsSim.csv did. Splitting on whitespace also
|
|
188
|
+
# accepts the space-separated flavours (NF mic2grains seed files).
|
|
189
|
+
# No value in this format ever contains an internal space, and a
|
|
190
|
+
# collapsed empty field would be caught by the column-count check
|
|
191
|
+
# in read_grains() below.
|
|
192
|
+
rows.append(line.split())
|
|
167
193
|
continue
|
|
168
194
|
|
|
169
195
|
body = line[1:]
|
|
170
|
-
|
|
196
|
+
# Same rule for the header, so a space-separated file's column names
|
|
197
|
+
# are found too. Column names never contain spaces.
|
|
198
|
+
fields = body.split()
|
|
171
199
|
# Column header: names O11 (and therefore the whole grain record).
|
|
172
200
|
if "O11" in fields or (fields and fields[0] in ("ID", "GrainID")
|
|
173
201
|
and len(fields) > 5):
|
|
@@ -204,7 +232,7 @@ def read_grains(path, *, check_orientation: bool = True) -> GrainList:
|
|
|
204
232
|
if columns is None:
|
|
205
233
|
raise ValueError(
|
|
206
234
|
f"{path}: no column header found. Expected a line beginning "
|
|
207
|
-
"'%ID' or '%GrainID' listing
|
|
235
|
+
"'%ID' or '%GrainID' listing the column names.")
|
|
208
236
|
if not rows:
|
|
209
237
|
raise ValueError(f"{path}: header present but no grain rows.")
|
|
210
238
|
|
|
@@ -213,8 +241,8 @@ def read_grains(path, *, check_orientation: bool = True) -> GrainList:
|
|
|
213
241
|
if arr.shape[1] != len(columns):
|
|
214
242
|
raise ValueError(
|
|
215
243
|
f"{path}: {arr.shape[1]} data columns but {len(columns)} header "
|
|
216
|
-
"names -- the file is malformed or
|
|
217
|
-
"
|
|
244
|
+
"names -- the file is malformed (an empty field mid-row, or a "
|
|
245
|
+
"header that does not match the rows).")
|
|
218
246
|
|
|
219
247
|
def col(name, required=True):
|
|
220
248
|
if name not in idx:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: midas-plotting
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Standard plots for MIDAS reconstructions - near-field, far-field and Laue: IPF maps and legends, grain maps, pole figures, strain and size distributions, and Laue texture diagnostics against their chance levels.
|
|
5
5
|
Author-email: Hemant Sharma <hsharma@anl.gov>
|
|
6
6
|
License: BSD-3-Clause
|
|
@@ -88,7 +88,7 @@ Things the module will not let you get wrong:
|
|
|
88
88
|
* **Symmetry comes from the file.** `Grains.csv` states its space group in the
|
|
89
89
|
preamble; the plots use it. Defaulting to cubic would colour a hexagonal
|
|
90
90
|
sample with the wrong IPF triangle and produce a plausible, wrong figure.
|
|
91
|
-
* **Columns are read by name.** `Grains.csv` has
|
|
91
|
+
* **Columns are read by name.** `Grains.csv` has 53 columns (47 before 2026-08-21) and
|
|
92
92
|
`midas-fit-grain` 0.5.6 shipped a cyclic rotation of three of them; a
|
|
93
93
|
positional reader inherits that silently.
|
|
94
94
|
* **Euler angles are cross-checked against `O11..O33`.** They describe the same
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "midas-plotting"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.4.0"
|
|
8
8
|
description = "Standard plots for MIDAS reconstructions - near-field, far-field and Laue: IPF maps and legends, grain maps, pole figures, strain and size distributions, and Laue texture diagnostics against their chance levels."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "BSD-3-Clause" }
|
|
@@ -13,16 +13,38 @@ from midas_plotting.ipf import ( # noqa: E402
|
|
|
13
13
|
direction_rgb, ipf_rgb, ipf_rgb_from_matrix,
|
|
14
14
|
)
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
16
|
+
# The CURRENT width is 53: the six DiffPos/Ome/Angle Pre/Post diagnostics were
|
|
17
|
+
# appended on 2026-08-21. The fixture used to stop at 47, which is why nothing
|
|
18
|
+
# here noticed the widening.
|
|
19
|
+
_DATA_COLS = ([f"O{i}{j}" for i in (1, 2, 3) for j in (1, 2, 3)]
|
|
20
|
+
+ ["X", "Y", "Z", "a", "b", "c", "alpha", "beta", "gamma",
|
|
21
|
+
"DiffPos", "DiffOme", "DiffAngle",
|
|
22
|
+
"GrainRadius", "Confidence"]
|
|
23
|
+
+ [f"eFab{i}{j}" for i in (1, 2, 3) for j in (1, 2, 3)]
|
|
24
|
+
+ [f"eKen{i}{j}" for i in (1, 2, 3) for j in (1, 2, 3)]
|
|
25
|
+
+ ["RMSErrorStrain", "PhaseNr", "Eul0", "Eul1", "Eul2",
|
|
26
|
+
"DiffPosPre", "DiffOmePre", "DiffAnglePre",
|
|
27
|
+
"DiffPosPost", "DiffOmePost", "DiffAnglePost"])
|
|
28
|
+
_COLS = ["ID"] + _DATA_COLS
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _write_grains(tmp_path, n=4, sg=225, break_orientation=False,
|
|
32
|
+
*, trailing_tab=False, id_token="ID", sep="\t"):
|
|
33
|
+
"""A Grains.csv with the real preamble shape, incl. tab-indented phase info.
|
|
34
|
+
|
|
35
|
+
Parameters
|
|
36
|
+
----------
|
|
37
|
+
trailing_tab : bool
|
|
38
|
+
Terminate every data row with a tab, which is what the C
|
|
39
|
+
``ProcessGrains`` actually writes. A reader that does ``split("\t")``
|
|
40
|
+
then sees a spurious empty final field and ``float("")`` raises. Every
|
|
41
|
+
real file under ``midas_stress/dev/paper3/runs/*/data/*MPa/`` and the
|
|
42
|
+
bundled ``GrainsSim.csv`` is in this form, and this fixture's omission
|
|
43
|
+
of it is why the crash shipped.
|
|
44
|
+
id_token : "ID" or "GrainID"
|
|
45
|
+
Both spellings are written by live MIDAS writers.
|
|
46
|
+
sep : column separator for the header and data rows.
|
|
47
|
+
"""
|
|
26
48
|
from midas_stress.orientation import euler_to_orient_mat_batch
|
|
27
49
|
|
|
28
50
|
rng = np.random.default_rng(0)
|
|
@@ -38,7 +60,7 @@ def _write_grains(tmp_path, n=4, sg=225, break_orientation=False):
|
|
|
38
60
|
f"%\tSpaceGroup:{sg}",
|
|
39
61
|
"%\tLattice Parameter:4.078200\t4.078200\t4.078200\t"
|
|
40
62
|
"90.000000\t90.000000\t90.000000",
|
|
41
|
-
"%" +
|
|
63
|
+
"%" + sep.join([id_token] + _DATA_COLS),
|
|
42
64
|
]
|
|
43
65
|
for k in range(n):
|
|
44
66
|
row = [str(k + 1)] + [f"{v:.6f}" for v in om[k].reshape(-1)]
|
|
@@ -50,7 +72,9 @@ def _write_grains(tmp_path, n=4, sg=225, break_orientation=False):
|
|
|
50
72
|
row += [f"{v:.4f}" for v in rng.normal(200, 50, 9)] # eKen
|
|
51
73
|
row += ["400.0", "1"]
|
|
52
74
|
row += [f"{v:.9f}" for v in eul[k]]
|
|
53
|
-
|
|
75
|
+
row += ["151.0", "0.06", "0.09", "150.0", "0.05", "0.08"] # pre/post
|
|
76
|
+
assert len(row) == len(_COLS)
|
|
77
|
+
lines.append(sep.join(row) + ("\t" if trailing_tab else ""))
|
|
54
78
|
p = tmp_path / "Grains.csv"
|
|
55
79
|
p.write_text("\n".join(lines) + "\n")
|
|
56
80
|
return p, eul, om
|
|
@@ -111,6 +135,51 @@ def test_orientation_check_can_be_disabled(tmp_path):
|
|
|
111
135
|
read_grains(p, check_orientation=False)
|
|
112
136
|
|
|
113
137
|
|
|
138
|
+
@pytest.mark.parametrize("trailing_tab", [False, True])
|
|
139
|
+
@pytest.mark.parametrize("id_token", ["ID", "GrainID"])
|
|
140
|
+
def test_trailing_tab_and_both_id_spellings(tmp_path, trailing_tab, id_token):
|
|
141
|
+
"""The shipped crash: `line.split("\t")` on a tab-terminated row.
|
|
142
|
+
|
|
143
|
+
The C ``ProcessGrains`` ends every data row with a tab, so a tab-split
|
|
144
|
+
yields one extra empty field and ``float("")`` raised
|
|
145
|
+
``could not convert string to float: ''`` on every real file under
|
|
146
|
+
``midas_stress/dev/paper3/runs/*/data/*MPa/Grains.csv``.
|
|
147
|
+
"""
|
|
148
|
+
p, eul, om = _write_grains(tmp_path, n=4, trailing_tab=trailing_tab,
|
|
149
|
+
id_token=id_token)
|
|
150
|
+
g = read_grains(p)
|
|
151
|
+
assert len(g) == 4
|
|
152
|
+
assert len(g.columns) == 53
|
|
153
|
+
np.testing.assert_array_equal(g.ids, [1, 2, 3, 4])
|
|
154
|
+
assert np.allclose(g.euler, eul, atol=1e-6)
|
|
155
|
+
assert np.allclose(g.orient_mat, om, atol=1e-5)
|
|
156
|
+
assert np.allclose(g.radius, [40.0, 50.0, 60.0, 70.0])
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def test_space_separated_file_is_readable(tmp_path):
|
|
160
|
+
"""NF ``mic2grains`` seed files are space-separated, not tab-separated."""
|
|
161
|
+
p, eul, _ = _write_grains(tmp_path, n=3, sep=" ")
|
|
162
|
+
g = read_grains(p)
|
|
163
|
+
assert len(g) == 3 and len(g.columns) == 53
|
|
164
|
+
assert np.allclose(g.euler, eul, atol=1e-6)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def test_current_width_exposes_pre_post_residual_columns(tmp_path):
|
|
168
|
+
"""The 2026-08-21 widening: 47 -> 53. The extra columns must be readable
|
|
169
|
+
by name, and must not displace anything that was already there."""
|
|
170
|
+
p, _, _ = _write_grains(tmp_path, n=2)
|
|
171
|
+
g = read_grains(p)
|
|
172
|
+
idx = {c: i for i, c in enumerate(g.columns)}
|
|
173
|
+
for c in ("DiffPosPre", "DiffOmePre", "DiffAnglePre",
|
|
174
|
+
"DiffPosPost", "DiffOmePost", "DiffAnglePost"):
|
|
175
|
+
assert c in idx
|
|
176
|
+
np.testing.assert_allclose(g.raw[:, idx["DiffPosPre"]], 151.0)
|
|
177
|
+
np.testing.assert_allclose(g.raw[:, idx["DiffPosPost"]], 150.0)
|
|
178
|
+
# ...and the pre-existing block is untouched.
|
|
179
|
+
np.testing.assert_allclose(g.diff_pos, 150.0)
|
|
180
|
+
np.testing.assert_allclose(g.diff_ome, 0.05)
|
|
181
|
+
|
|
182
|
+
|
|
114
183
|
def test_missing_header_is_an_error(tmp_path):
|
|
115
184
|
p = tmp_path / "bad.csv"
|
|
116
185
|
p.write_text("1\t2\t3\n")
|
|
@@ -127,7 +196,7 @@ def test_strain_is_not_rescaled(tmp_path):
|
|
|
127
196
|
p, _, _ = _write_grains(tmp_path, n=4)
|
|
128
197
|
g = read_grains(p)
|
|
129
198
|
raw11 = g.strain_fab[:, 0, 0]
|
|
130
|
-
assert np.allclose(ff.strain_scalar(g, "11"), raw11)
|
|
199
|
+
assert np.allclose(ff.strain_scalar(g, "11", convention="fab"), raw11)
|
|
131
200
|
assert np.abs(ff.strain_scalar(g, "hydrostatic")).max() < 1e4
|
|
132
201
|
|
|
133
202
|
|
|
@@ -135,6 +204,9 @@ def test_strain_scalars(tmp_path):
|
|
|
135
204
|
p, _, _ = _write_grains(tmp_path, n=4)
|
|
136
205
|
g = read_grains(p)
|
|
137
206
|
e = g.strain("fab")
|
|
207
|
+
assert np.allclose(ff.strain_scalar(g, "hydrostatic", convention="fab"),
|
|
208
|
+
np.trace(e, axis1=1, axis2=2) / 3.0)
|
|
209
|
+
e = g.strain() # default convention, checked below
|
|
138
210
|
assert np.allclose(ff.strain_scalar(g, "hydrostatic"),
|
|
139
211
|
np.trace(e, axis1=1, axis2=2) / 3.0)
|
|
140
212
|
assert (ff.strain_scalar(g, "vonmises") >= 0).all()
|
|
@@ -246,3 +318,37 @@ def test_ff_detected_by_content_not_filename(tmp_path):
|
|
|
246
318
|
other.write_text("1 2 3 4 5\n")
|
|
247
319
|
assert not _looks_like_ff(other)
|
|
248
320
|
assert not _looks_like_ff(tmp_path / "missing.csv")
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
# ── strain convention default (regression) ──────────────────────────────────
|
|
324
|
+
def test_strain_default_convention_is_eken(tmp_path):
|
|
325
|
+
"""The default must be eKen, matching ``midas_stress.io``'s ``strain`` key.
|
|
326
|
+
|
|
327
|
+
midas_plotting defaulted to eFab while midas_stress.io mapped its plain
|
|
328
|
+
``strain`` key to eKen and documented eKen as the recommendation. Both
|
|
329
|
+
sites labelled their choice, so nothing raised and nothing was mislabelled
|
|
330
|
+
-- but a strain map from here and a stress from there, on the SAME
|
|
331
|
+
Grains.csv, were different tensors.
|
|
332
|
+
"""
|
|
333
|
+
p, _, _ = _write_grains(tmp_path, n=6)
|
|
334
|
+
g = read_grains(p)
|
|
335
|
+
# The fixture draws eFab and eKen independently, so a wrong default shows.
|
|
336
|
+
assert not np.allclose(g.strain_fab, g.strain_ken)
|
|
337
|
+
np.testing.assert_allclose(g.strain(), g.strain_ken)
|
|
338
|
+
for kind in ("hydrostatic", "vonmises", "11", "23"):
|
|
339
|
+
np.testing.assert_allclose(ff.strain_scalar(g, kind),
|
|
340
|
+
ff.strain_scalar(g, kind, convention="ken"))
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def test_strain_default_agrees_with_midas_stress(tmp_path):
|
|
344
|
+
"""Same file, same tensor, across the two packages.
|
|
345
|
+
|
|
346
|
+
midas_stress returns dimensionless strain (it divides the stored
|
|
347
|
+
microstrain by 1e6); midas_plotting keeps microstrain. That factor is the
|
|
348
|
+
ONLY difference the two defaults may have.
|
|
349
|
+
"""
|
|
350
|
+
ms_io = pytest.importorskip("midas_stress.io")
|
|
351
|
+
p, _, _ = _write_grains(tmp_path, n=6)
|
|
352
|
+
g = read_grains(p)
|
|
353
|
+
ms = ms_io.read_grains_csv(str(p))
|
|
354
|
+
np.testing.assert_allclose(g.strain(), ms["strain"] * 1e6, rtol=1e-9)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|