simcon-toolkit 0.1.0__py3-none-any.whl
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.
- simcon_toolkit/__init__.py +40 -0
- simcon_toolkit/__main__.py +7 -0
- simcon_toolkit/_kit/LICENSE +202 -0
- simcon_toolkit/_kit/NOTICE +37 -0
- simcon_toolkit/_kit/assets/parts/clip_frame.stl +0 -0
- simcon_toolkit/_kit/assets/parts/simple_plate.stl +0 -0
- simcon_toolkit/_kit/packages/.ruff.toml +10 -0
- simcon_toolkit/_kit/packages/cadmould_cloud/__init__.py +8 -0
- simcon_toolkit/_kit/packages/cadmould_cloud/auth.py +681 -0
- simcon_toolkit/_kit/packages/cadmould_cloud/client.py +235 -0
- simcon_toolkit/_kit/packages/cadmould_geometry/__init__.py +5 -0
- simcon_toolkit/_kit/packages/cadmould_geometry/mesh.py +210 -0
- simcon_toolkit/_kit/packages/cadmould_geometry/stl.py +168 -0
- simcon_toolkit/_kit/packages/cadmould_results/__init__.py +30 -0
- simcon_toolkit/_kit/packages/cadmould_results/loader.py +288 -0
- simcon_toolkit/_kit/packages/cadmould_scoring/__init__.py +7 -0
- simcon_toolkit/_kit/packages/cadmould_scoring/metrics.py +519 -0
- simcon_toolkit/_kit/pyproject.toml +232 -0
- simcon_toolkit/_kit/templates/_shared/AGENTS.base.md +101 -0
- simcon_toolkit/_kit/templates/gate-study/.gitignore +18 -0
- simcon_toolkit/_kit/templates/gate-study/AGENTS.md +46 -0
- simcon_toolkit/_kit/templates/gate-study/GATING_STUDY_PLAYBOOK.md +219 -0
- simcon_toolkit/_kit/templates/gate-study/INITIAL_PROMPT.md +26 -0
- simcon_toolkit/_kit/templates/gate-study/README.md +137 -0
- simcon_toolkit/_kit/templates/gate-study/main.py +344 -0
- simcon_toolkit/_kit/templates/gate-study/pipeline.py +281 -0
- simcon_toolkit/_kit/templates/process-window/.gitignore +20 -0
- simcon_toolkit/_kit/templates/process-window/AGENTS.md +49 -0
- simcon_toolkit/_kit/templates/process-window/METHOD.md +155 -0
- simcon_toolkit/_kit/templates/process-window/README.md +176 -0
- simcon_toolkit/_kit/templates/process-window/configs/simple-plate.yaml +116 -0
- simcon_toolkit/_kit/templates/process-window/doe_spec.schema.md +249 -0
- simcon_toolkit/_kit/templates/process-window/main.py +82 -0
- simcon_toolkit/_kit/templates/process-window/process_window/__init__.py +5 -0
- simcon_toolkit/_kit/templates/process-window/process_window/centre.py +298 -0
- simcon_toolkit/_kit/templates/process-window/process_window/design.py +144 -0
- simcon_toolkit/_kit/templates/process-window/process_window/economics.py +367 -0
- simcon_toolkit/_kit/templates/process-window/process_window/emit.py +591 -0
- simcon_toolkit/_kit/templates/process-window/process_window/guardrails.py +153 -0
- simcon_toolkit/_kit/templates/process-window/process_window/harness.py +360 -0
- simcon_toolkit/_kit/templates/process-window/process_window/identity.py +92 -0
- simcon_toolkit/_kit/templates/process-window/process_window/inspect_part.py +184 -0
- simcon_toolkit/_kit/templates/process-window/process_window/kpis.py +355 -0
- simcon_toolkit/_kit/templates/process-window/process_window/material_card.py +163 -0
- simcon_toolkit/_kit/templates/process-window/process_window/probe_proxy.py +169 -0
- simcon_toolkit/_kit/templates/process-window/process_window/run_confirm.py +403 -0
- simcon_toolkit/_kit/templates/process-window/process_window/run_epsilon_floor.py +198 -0
- simcon_toolkit/_kit/templates/process-window/process_window/run_feedback.py +322 -0
- simcon_toolkit/_kit/templates/process-window/process_window/run_refine.py +279 -0
- simcon_toolkit/_kit/templates/process-window/process_window/run_screening.py +370 -0
- simcon_toolkit/_kit/templates/process-window/process_window/run_sweep.py +166 -0
- simcon_toolkit/_kit/templates/process-window/process_window/setup_campaign.py +312 -0
- simcon_toolkit/_kit/templates/process-window/process_window/surrogate.py +201 -0
- simcon_toolkit/_kit/templates/process-window/process_window/test_centre.py +169 -0
- simcon_toolkit/_kit/templates/process-window/process_window/test_design.py +113 -0
- simcon_toolkit/_kit/templates/process-window/process_window/test_guardrails.py +157 -0
- simcon_toolkit/_kit/templates/process-window/process_window/test_surrogate.py +127 -0
- simcon_toolkit/_kit/templates/process-window/process_window/units.py +152 -0
- simcon_toolkit/_kit/templates/quoting/.gitignore +24 -0
- simcon_toolkit/_kit/templates/quoting/AGENTS.md +58 -0
- simcon_toolkit/_kit/templates/quoting/INTERVIEW.md +147 -0
- simcon_toolkit/_kit/templates/quoting/METHOD.md +256 -0
- simcon_toolkit/_kit/templates/quoting/PROMPT.md +46 -0
- simcon_toolkit/_kit/templates/quoting/QUOTING_PLAYBOOK.md +245 -0
- simcon_toolkit/_kit/templates/quoting/README.md +158 -0
- simcon_toolkit/_kit/templates/quoting/main.py +484 -0
- simcon_toolkit/_kit/templates/quoting/parts/.gitkeep +0 -0
- simcon_toolkit/_kit/templates/quoting/quoting/__init__.py +11 -0
- simcon_toolkit/_kit/templates/quoting/quoting/costing.py +725 -0
- simcon_toolkit/_kit/templates/quoting/quoting/geometry.py +398 -0
- simcon_toolkit/_kit/templates/quoting/quoting/shop.py +193 -0
- simcon_toolkit/_kit/templates/quoting/quoting/state.py +260 -0
- simcon_toolkit/_kit/templates/quoting/quoting/study.py +577 -0
- simcon_toolkit/_kit/templates/quoting/quoting/toolkit.py +50 -0
- simcon_toolkit/_kit/templates/quoting/shop/README.md +43 -0
- simcon_toolkit/_kit/templates/quoting/shop/commercial.md +86 -0
- simcon_toolkit/_kit/templates/quoting/shop/lessons.md +94 -0
- simcon_toolkit/_kit/templates/quoting/shop/machines.md +68 -0
- simcon_toolkit/_kit/templates/quoting/shop/materials.md +92 -0
- simcon_toolkit/_kit/templates/quoting/shop/shop-profile.md +87 -0
- simcon_toolkit/_kit/templates/quoting/shop/tooling.md +145 -0
- simcon_toolkit/_kit/templates/run-one-simulation/.gitignore +16 -0
- simcon_toolkit/_kit/templates/run-one-simulation/AGENTS.md +41 -0
- simcon_toolkit/_kit/templates/run-one-simulation/README.md +133 -0
- simcon_toolkit/_kit/templates/run-one-simulation/main.py +216 -0
- simcon_toolkit/_kit/templates.toml +83 -0
- simcon_toolkit/choices.py +11 -0
- simcon_toolkit/cli.py +381 -0
- simcon_toolkit/generate.py +590 -0
- simcon_toolkit/instructions.py +152 -0
- simcon_toolkit/manifest.py +86 -0
- simcon_toolkit/project.py +356 -0
- simcon_toolkit/wizard.py +160 -0
- simcon_toolkit-0.1.0.dist-info/METADATA +48 -0
- simcon_toolkit-0.1.0.dist-info/RECORD +97 -0
- simcon_toolkit-0.1.0.dist-info/WHEEL +4 -0
- simcon_toolkit-0.1.0.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
"""Read and slice the AI-Solver injection-molding prediction (HDF5).
|
|
2
|
+
|
|
3
|
+
The prediction file is a 2.5D filling simulation produced by the Simcon AI
|
|
4
|
+
solver. Everything lives in a single ``.h5`` file with this layout::
|
|
5
|
+
|
|
6
|
+
/ root attrs: geometry, material, model_id, ...
|
|
7
|
+
/TimeStep_0001 ... _0053 one group per time step
|
|
8
|
+
Time, dt, filling_level (group attrs)
|
|
9
|
+
Degree_of_filling (N,) per-node fill fraction (NaN = not filled yet)
|
|
10
|
+
Pressure (N,) per-node cavity pressure [bar]
|
|
11
|
+
Temperature (N, L) per-node, per-layer melt temperature [K]
|
|
12
|
+
Log(1 + Shear_rate)(N, L) per-node, per-layer log1p of shear rate
|
|
13
|
+
/geometry3DF/Coordinates (N, 3) midplane mesh node coordinates [mm]
|
|
14
|
+
/supporting_points/NodePoints (N, L, 3) through-thickness point cloud [mm]
|
|
15
|
+
/flowrate0 (53,) global injection curve (time, flowrate, p, switch)
|
|
16
|
+
/input process settings + gate location(s)
|
|
17
|
+
/material_data PP material model (pvt / viscosity / thermal)
|
|
18
|
+
|
|
19
|
+
Here ``N`` = number of mesh nodes (6201) and ``L`` = number of layers across
|
|
20
|
+
the wall thickness (41). ``Pressure`` and ``Degree_of_filling`` are stored per
|
|
21
|
+
node only; ``Temperature`` and the shear field additionally resolve the wall
|
|
22
|
+
thickness, so layer 0 is one surface, layer ``L-1`` the other, and the middle
|
|
23
|
+
index is the core.
|
|
24
|
+
|
|
25
|
+
The class below is the one object you work with. It opens the file lazily and
|
|
26
|
+
hands you NumPy arrays via small, well-named slicing methods ("chunks") so the
|
|
27
|
+
template scripts never touch h5py directly.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
from dataclasses import dataclass
|
|
33
|
+
from pathlib import Path
|
|
34
|
+
|
|
35
|
+
import h5py
|
|
36
|
+
import numpy as np
|
|
37
|
+
|
|
38
|
+
# --- canonical field names as stored in each TimeStep group -----------------
|
|
39
|
+
DEGREE_OF_FILLING = "Degree_of_filling"
|
|
40
|
+
PRESSURE = "Pressure"
|
|
41
|
+
TEMPERATURE = "Temperature"
|
|
42
|
+
SHEAR_LOG = "Log(1 + Shear_rate)" # legacy: log-encoded shear (de-log with expm1)
|
|
43
|
+
SHEAR_RATE = "Shear_rate" # current: straight shear rate [1/s]
|
|
44
|
+
|
|
45
|
+
#: fields stored as (N,) -> one value per node
|
|
46
|
+
NODE_FIELDS = (DEGREE_OF_FILLING, PRESSURE)
|
|
47
|
+
#: fields stored as (N, L) -> one value per node and wall layer (both shear
|
|
48
|
+
#: spellings listed so membership tests work whichever the file carries)
|
|
49
|
+
LAYERED_FIELDS = (TEMPERATURE, SHEAR_LOG, SHEAR_RATE)
|
|
50
|
+
ALL_FIELDS = NODE_FIELDS + LAYERED_FIELDS
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass(frozen=True)
|
|
54
|
+
class ProcessInputs:
|
|
55
|
+
"""Process settings the prediction was run with (from /input attrs)."""
|
|
56
|
+
|
|
57
|
+
filling_time_s: float
|
|
58
|
+
flowrate: float
|
|
59
|
+
melt_temperature_c: float
|
|
60
|
+
wall_temperature_c: float
|
|
61
|
+
gate_xyz: np.ndarray # (G, 3) gate location(s) in mm
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class PredictionResult:
|
|
65
|
+
"""A read-only view over one AI-Solver prediction file.
|
|
66
|
+
|
|
67
|
+
Use it as a context manager so the underlying HDF5 handle is closed::
|
|
68
|
+
|
|
69
|
+
with PredictionResult("part_prediction.h5") as res:
|
|
70
|
+
snap = res.snapshot(step=29) # all fields at one time step
|
|
71
|
+
p_gate = res.node_history(PRESSURE, node=res.gate_node)
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
def __init__(self, path: str | Path) -> None:
|
|
75
|
+
"""Open a prediction file and read its time axis.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
path: The prediction ``.h5`` to open. It stays open until ``close``.
|
|
79
|
+
"""
|
|
80
|
+
self.path = Path(path)
|
|
81
|
+
self._f = h5py.File(self.path, "r")
|
|
82
|
+
|
|
83
|
+
# Ordered list of TimeStep group names (the file is already sorted, but
|
|
84
|
+
# never rely on dict order in HDF5 -- sort explicitly).
|
|
85
|
+
self.timestep_names: list[str] = sorted(k for k in self._f if k.startswith("TimeStep_"))
|
|
86
|
+
|
|
87
|
+
# The geometry group name has a trailing space in the source file; find
|
|
88
|
+
# it robustly instead of hard-coding the quirk.
|
|
89
|
+
self._geom_key = next(k for k in self._f if k.strip() == "geometry3DF")
|
|
90
|
+
|
|
91
|
+
# Time axis (seconds), one entry per time step.
|
|
92
|
+
self.times: np.ndarray = np.array(
|
|
93
|
+
[float(self._f[name].attrs["Time"]) for name in self.timestep_names],
|
|
94
|
+
dtype=np.float64,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
# -- lifecycle -----------------------------------------------------------
|
|
98
|
+
def close(self) -> None:
|
|
99
|
+
"""Close the underlying HDF5 file."""
|
|
100
|
+
self._f.close()
|
|
101
|
+
|
|
102
|
+
def __enter__(self) -> PredictionResult:
|
|
103
|
+
"""Enter a ``with`` block; the file is already open."""
|
|
104
|
+
return self
|
|
105
|
+
|
|
106
|
+
def __exit__(self, *exc: object) -> None:
|
|
107
|
+
"""Leave a ``with`` block, closing the file. Exceptions propagate."""
|
|
108
|
+
self.close()
|
|
109
|
+
|
|
110
|
+
# -- basic dimensions / metadata ----------------------------------------
|
|
111
|
+
@property
|
|
112
|
+
def meta(self) -> dict:
|
|
113
|
+
"""Root attributes: geometry name, material, solver/model id, ..."""
|
|
114
|
+
return {k: _decode(v) for k, v in self._f.attrs.items()}
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
def n_timesteps(self) -> int:
|
|
118
|
+
"""How many time steps the file recorded."""
|
|
119
|
+
return len(self.timestep_names)
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def n_nodes(self) -> int:
|
|
123
|
+
"""How many midplane mesh nodes the geometry has."""
|
|
124
|
+
return self._f[self._geom_key]["Coordinates"].shape[0]
|
|
125
|
+
|
|
126
|
+
@property
|
|
127
|
+
def n_layers(self) -> int:
|
|
128
|
+
"""How many through-thickness layers each layered field carries."""
|
|
129
|
+
return self._f[self.timestep_names[0]][TEMPERATURE].shape[1]
|
|
130
|
+
|
|
131
|
+
@property
|
|
132
|
+
def coordinates(self) -> np.ndarray:
|
|
133
|
+
"""Midplane mesh node coordinates, shape (N, 3), in mm."""
|
|
134
|
+
return np.asarray(self._f[self._geom_key]["Coordinates"][()], dtype=np.float32)
|
|
135
|
+
|
|
136
|
+
@property
|
|
137
|
+
def node_points(self) -> np.ndarray:
|
|
138
|
+
"""Through-thickness point cloud, shape (N, L, 3), in mm."""
|
|
139
|
+
return np.asarray(self._f["supporting_points"]["NodePoints"][()], dtype=np.float32)
|
|
140
|
+
|
|
141
|
+
@property
|
|
142
|
+
def has_inputs(self) -> bool:
|
|
143
|
+
"""Whether the file carries an `/input` group (process settings + gate location).
|
|
144
|
+
|
|
145
|
+
Cloud results omit it unless augmented — see the run-one-simulation template.
|
|
146
|
+
"""
|
|
147
|
+
return "input" in self._f
|
|
148
|
+
|
|
149
|
+
@property
|
|
150
|
+
def inputs(self) -> ProcessInputs:
|
|
151
|
+
"""The process settings and gate location the run was submitted with.
|
|
152
|
+
|
|
153
|
+
Raises:
|
|
154
|
+
KeyError: If the file carries no ``/input`` group; check ``has_inputs``.
|
|
155
|
+
"""
|
|
156
|
+
g = self._f["input"]
|
|
157
|
+
gates = np.asarray(g["gate_locations"][()])
|
|
158
|
+
gate_xyz = np.column_stack([gates["x"], gates["y"], gates["z"]]).astype(np.float32)
|
|
159
|
+
return ProcessInputs(
|
|
160
|
+
filling_time_s=float(g.attrs["filling_time"]),
|
|
161
|
+
flowrate=float(g.attrs["flowrate"]),
|
|
162
|
+
melt_temperature_c=float(g.attrs["melt_temperature"]),
|
|
163
|
+
wall_temperature_c=float(g.attrs["wall_temperature"]),
|
|
164
|
+
gate_xyz=gate_xyz,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
@property
|
|
168
|
+
def gate_node(self) -> int:
|
|
169
|
+
"""Index of the mesh node closest to the (first) gate location."""
|
|
170
|
+
gate = self.inputs.gate_xyz[0]
|
|
171
|
+
d = np.linalg.norm(self.coordinates - gate, axis=1)
|
|
172
|
+
return int(np.argmin(d))
|
|
173
|
+
|
|
174
|
+
@property
|
|
175
|
+
def flowrate_curve(self) -> np.ndarray:
|
|
176
|
+
"""Global injection curve, as a structured array.
|
|
177
|
+
|
|
178
|
+
Its fields are ``time``, ``flowrate_calculated``, ``pressure`` and ``switch``.
|
|
179
|
+
"""
|
|
180
|
+
return np.asarray(self._f["flowrate0"][()])
|
|
181
|
+
|
|
182
|
+
def field_unit(self, field: str) -> str:
|
|
183
|
+
"""Unit string stored on a field (e.g. 'bar', 'Pa', 'K', '1/s', 'relative')."""
|
|
184
|
+
ds = self._f[self.timestep_names[0]][field]
|
|
185
|
+
return _decode(ds.attrs.get("unit", ""))
|
|
186
|
+
|
|
187
|
+
def has_field(self, field: str) -> bool:
|
|
188
|
+
"""True if ``field`` exists in the time-step groups (the result format varies)."""
|
|
189
|
+
return field in self._f[self.timestep_names[0]]
|
|
190
|
+
|
|
191
|
+
def shear_name(self) -> str | None:
|
|
192
|
+
"""Name of the shear field this file carries, or None if it carries neither.
|
|
193
|
+
|
|
194
|
+
Either ``Shear_rate`` (current, straight 1/s) or ``Log(1 + Shear_rate)`` (legacy,
|
|
195
|
+
log-encoded).
|
|
196
|
+
"""
|
|
197
|
+
for n in (SHEAR_RATE, SHEAR_LOG):
|
|
198
|
+
if self.has_field(n):
|
|
199
|
+
return n
|
|
200
|
+
return None
|
|
201
|
+
|
|
202
|
+
def pressure_series_bar(self) -> np.ndarray:
|
|
203
|
+
"""Pressure over all steps in **bar**, regardless of stored unit (Pa or bar)."""
|
|
204
|
+
s = self.field_series(PRESSURE)
|
|
205
|
+
return s / 1.0e5 if self.field_unit(PRESSURE).strip().lower() == "pa" else s
|
|
206
|
+
|
|
207
|
+
# -- time indexing -------------------------------------------------------
|
|
208
|
+
def step_at_time(self, t_seconds: float) -> int:
|
|
209
|
+
"""Return the time-step index whose Time is closest to ``t_seconds``."""
|
|
210
|
+
return int(np.argmin(np.abs(self.times - t_seconds)))
|
|
211
|
+
|
|
212
|
+
def _group(self, step: int) -> h5py.Group:
|
|
213
|
+
return self._f[self.timestep_names[step]]
|
|
214
|
+
|
|
215
|
+
# -- CHUNK 1: a single time step (a "snapshot") --------------------------
|
|
216
|
+
def field_at(self, field: str, step: int) -> np.ndarray:
|
|
217
|
+
"""One field at one time step.
|
|
218
|
+
|
|
219
|
+
Shape is (N,) for node fields and (N, L) for layered fields.
|
|
220
|
+
Unfilled nodes are NaN. float16 storage is promoted to float32.
|
|
221
|
+
"""
|
|
222
|
+
return np.asarray(self._group(step)[field][()], dtype=np.float32)
|
|
223
|
+
|
|
224
|
+
def snapshot(self, step: int) -> dict:
|
|
225
|
+
"""All fields at one time step, plus that step's time/fill metadata."""
|
|
226
|
+
g = self._group(step)
|
|
227
|
+
data = {f: self.field_at(f, step) for f in ALL_FIELDS if self.has_field(f)}
|
|
228
|
+
data["time"] = float(g.attrs["Time"])
|
|
229
|
+
data["filled_fraction"] = float(np.isfinite(data[DEGREE_OF_FILLING]).mean())
|
|
230
|
+
return data
|
|
231
|
+
|
|
232
|
+
# -- CHUNK 2: a field across the whole simulation ------------------------
|
|
233
|
+
def field_series(self, field: str) -> np.ndarray:
|
|
234
|
+
"""Stack one field over every time step.
|
|
235
|
+
|
|
236
|
+
Shape is (T, N) for node fields and (T, N, L) for layered fields.
|
|
237
|
+
"""
|
|
238
|
+
return np.stack([self.field_at(field, s) for s in range(self.n_timesteps)])
|
|
239
|
+
|
|
240
|
+
# -- CHUNK 3: the time history at a single node (a virtual sensor) -------
|
|
241
|
+
def node_history(self, field: str, node: int, layer: int | None = None) -> np.ndarray:
|
|
242
|
+
"""Time history of ``field`` at one mesh node, shape (T,).
|
|
243
|
+
|
|
244
|
+
For layered fields pass ``layer`` (defaults to the core, L//2).
|
|
245
|
+
"""
|
|
246
|
+
if field in LAYERED_FIELDS:
|
|
247
|
+
if layer is None:
|
|
248
|
+
layer = self.n_layers // 2
|
|
249
|
+
return np.array([self.field_at(field, s)[node, layer] for s in range(self.n_timesteps)])
|
|
250
|
+
return np.array([self.field_at(field, s)[node] for s in range(self.n_timesteps)])
|
|
251
|
+
|
|
252
|
+
# -- CHUNK 4: one through-thickness layer over time ----------------------
|
|
253
|
+
def layer_series(self, field: str, layer: int) -> np.ndarray:
|
|
254
|
+
"""A single wall layer of a layered field over time, shape (T, N)."""
|
|
255
|
+
if field not in LAYERED_FIELDS:
|
|
256
|
+
raise ValueError(f"{field!r} has no layers; it is stored per node only.")
|
|
257
|
+
return np.stack([self.field_at(field, s)[:, layer] for s in range(self.n_timesteps)])
|
|
258
|
+
|
|
259
|
+
# -- derived helpers -----------------------------------------------------
|
|
260
|
+
def fill_progress(self) -> np.ndarray:
|
|
261
|
+
"""Fraction of nodes reached by the flow front at each step, shape (T,)."""
|
|
262
|
+
return np.array([np.isfinite(self.field_at(DEGREE_OF_FILLING, s)).mean() for s in range(self.n_timesteps)])
|
|
263
|
+
|
|
264
|
+
def shear_rate(self, step: int) -> np.ndarray:
|
|
265
|
+
"""Shear rate at a step, regardless of how the file stores it.
|
|
266
|
+
|
|
267
|
+
Current files carry a straight ``Shear_rate`` field [1/s] -> returned as-is.
|
|
268
|
+
Legacy files carry ``Log(1 + Shear_rate)`` -> de-logged with ``expm1`` (the
|
|
269
|
+
legacy unit label is '100s', i.e. units of 100 1/s).
|
|
270
|
+
"""
|
|
271
|
+
name = self.shear_name()
|
|
272
|
+
if name is None:
|
|
273
|
+
raise KeyError("no shear field (Shear_rate / Log(1 + Shear_rate)) in this file")
|
|
274
|
+
s = self.field_at(name, step)
|
|
275
|
+
return np.expm1(s) if name == SHEAR_LOG else s
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def _decode(v: object) -> object:
|
|
279
|
+
"""Turn HDF5 bytes/0-d arrays into plain Python values for printing.
|
|
280
|
+
|
|
281
|
+
The input is an arbitrary HDF5 attribute value, so the body narrows by type rather
|
|
282
|
+
than assuming one.
|
|
283
|
+
"""
|
|
284
|
+
if isinstance(v, bytes):
|
|
285
|
+
return v.decode("utf-8", "replace")
|
|
286
|
+
if isinstance(v, np.ndarray) and v.shape == ():
|
|
287
|
+
return v.item()
|
|
288
|
+
return v
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""Score a filling result: pressure, fill evenness and weld-line detection.
|
|
2
|
+
|
|
3
|
+
from cadmould_scoring import metrics
|
|
4
|
+
|
|
5
|
+
Nothing is imported here on purpose, so a file that wants one module pays for one
|
|
6
|
+
module. See ``cadmould_scoring.metrics`` for the documented measures.
|
|
7
|
+
"""
|