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,116 @@
|
|
|
1
|
+
# Frontloaded DOE — runnable configuration against the sample part shipped with this repo.
|
|
2
|
+
#
|
|
3
|
+
# Paths are relative to this template's root (the folder containing main.py and configs/).
|
|
4
|
+
#
|
|
5
|
+
# This config points at the sample part in `sample/`, so the pipeline can be executed end to
|
|
6
|
+
# end without any private input. Replace the `part:` block to run it on your own geometry.
|
|
7
|
+
#
|
|
8
|
+
# Expect a modest answer: run
|
|
9
|
+
# python main.py inspect
|
|
10
|
+
# first. A thick, generously-proportioned plate fills long before its walls freeze, so the
|
|
11
|
+
# feasibility gate will very likely never bite and the whole box will come back green. That is a
|
|
12
|
+
# true result, not a broken run: it is what a thick-walled part looks like, and it is the signal
|
|
13
|
+
# to try a thinner-walled one.
|
|
14
|
+
|
|
15
|
+
part:
|
|
16
|
+
name: Simple Plate
|
|
17
|
+
stl: "sample/simple_plate.stl"
|
|
18
|
+
scale: 1.0 # 1.0 = already in mm
|
|
19
|
+
|
|
20
|
+
material:
|
|
21
|
+
# NOT SHIPPED. A .plb is a licensed Cadmould material file and is not in this repository.
|
|
22
|
+
#
|
|
23
|
+
# It is only needed for the G4 numerical decks (SimulationConfig.Builder wants a Material
|
|
24
|
+
# object, and PlbReader is the only loader). G0-G3 do not need it: the cloud catalogue's
|
|
25
|
+
# material version carries no_flow_temperature_K, the thermal data and the processing window,
|
|
26
|
+
# and they agree with the local card exactly.
|
|
27
|
+
#
|
|
28
|
+
# Point this at your own .plb export, or drop the G4 stage.
|
|
29
|
+
plb: "PP Generic.plb"
|
|
30
|
+
catalogue_name: "PP Generic"
|
|
31
|
+
is_placeholder: true
|
|
32
|
+
|
|
33
|
+
gates:
|
|
34
|
+
# `suggest` asks the SDK for an on-mesh gate. The surrogate requires the gate to be a real mesh
|
|
35
|
+
# node — never an off-mesh point, never (0,0,0).
|
|
36
|
+
mode: suggest
|
|
37
|
+
# positions_mm: [[x, y, z]] # set this to override, snapped to the nearest node
|
|
38
|
+
|
|
39
|
+
variables:
|
|
40
|
+
# The only three variables the AI filling solver can see. Bounds are DERIVED:
|
|
41
|
+
# melt, wall -> the material card's full processing window
|
|
42
|
+
# flow -> nominal +/- 50 %
|
|
43
|
+
# Temperature bounds are not a percentage: temperature is an interval scale, so "+/-50 %" of it
|
|
44
|
+
# is not a well-defined instruction. The card's own processing window is the honest wide box.
|
|
45
|
+
melt:
|
|
46
|
+
from: material_card
|
|
47
|
+
wall:
|
|
48
|
+
from: material_card
|
|
49
|
+
flow:
|
|
50
|
+
from: sdk_suggestion # suggest_fill_profile + estimate_fill_time, never a guessed time
|
|
51
|
+
rel_span: 0.50
|
|
52
|
+
|
|
53
|
+
# Switchover is NOT swept: the AI endpoint has no switchover input. It is a read-out axis on
|
|
54
|
+
# every run, and a swept input only in the G4 numerical decks.
|
|
55
|
+
switchover_readout_phi: [0.90, 0.95, 0.98, 0.99]
|
|
56
|
+
|
|
57
|
+
packing_cooling:
|
|
58
|
+
from: sdk_suggestion
|
|
59
|
+
|
|
60
|
+
feasibility_gate:
|
|
61
|
+
primary: no_flow_margin_end_K # absolute superheat above no-flow, in K
|
|
62
|
+
fill_completeness:
|
|
63
|
+
use_as_gate: auto # only if the residual is ~0 AND flow-independent
|
|
64
|
+
residual_tolerance: 0.005
|
|
65
|
+
report_always: true
|
|
66
|
+
|
|
67
|
+
budget:
|
|
68
|
+
# Deliberately small so a first run finishes quickly.
|
|
69
|
+
# Sobol is incremental, so raising this later reuses every run already done.
|
|
70
|
+
epsilon_floor_runs: 17
|
|
71
|
+
sweep_points: 256
|
|
72
|
+
refine_points: 96
|
|
73
|
+
confirmation_points: 14
|
|
74
|
+
fake_real_anchors: 3
|
|
75
|
+
|
|
76
|
+
solver:
|
|
77
|
+
num_timesteps: 50 # 50-64 safe; ~100 returns 502 from the gateway
|
|
78
|
+
htc_W_m2K: 1000.0 # fixed, not swept
|
|
79
|
+
max_concurrency: 4 # the endpoint sheds load above this
|
|
80
|
+
retry:
|
|
81
|
+
attempts: 4
|
|
82
|
+
backoff_s: 2.0
|
|
83
|
+
on_status: [429, 500, 502, 503, 504]
|
|
84
|
+
|
|
85
|
+
cloud:
|
|
86
|
+
rest_base_url: "https://api.simcon.ai/api/v1"
|
|
87
|
+
# Driven over REST rather than the SDK: the SDK cannot set num_timesteps (which controls both
|
|
88
|
+
# the phi resolution the KPIs depend on and the result file size) or pin model_version.
|
|
89
|
+
model_version: "v0.1.0"
|
|
90
|
+
project: "DOE Frontloading"
|
|
91
|
+
group_per_stage: true
|
|
92
|
+
|
|
93
|
+
storage:
|
|
94
|
+
# Results are 25-220 MB each. Score, write a ~2 kB summary, then delete the raw file — except
|
|
95
|
+
# where the run is worth keeping. A per-part subfolder is appended automatically.
|
|
96
|
+
#
|
|
97
|
+
# Under cache/ because it is ours and safe to delete: everything here can be recomputed,
|
|
98
|
+
# it only costs time and solver runs. The campaign's answer lands in output/.
|
|
99
|
+
runs_dir: "cache/runs"
|
|
100
|
+
keep_h5:
|
|
101
|
+
- boundary
|
|
102
|
+
- anomaly
|
|
103
|
+
- anchor
|
|
104
|
+
|
|
105
|
+
machine_limits:
|
|
106
|
+
# Applied as a final feasibility ANNOTATION only, never as a screening criterion: a machine
|
|
107
|
+
# limit is a constraint, and guardrail 1 forbids absolute pressure thresholds in criteria.
|
|
108
|
+
max_injection_pressure_bar: 1500.0
|
|
109
|
+
max_clamp_force_kN: 1000.0
|
|
110
|
+
applied_as: annotation_only
|
|
111
|
+
|
|
112
|
+
stated_limitations:
|
|
113
|
+
- "sample geometry, not a production part"
|
|
114
|
+
- "material is PP Generic as a PLACEHOLDER"
|
|
115
|
+
- "no runner system in the AI model, so any hot runner is absent rather than approximated"
|
|
116
|
+
- "AI solver under-predicts absolute pressure on multi-gate parts (~0.3x on 8 gates)"
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
# `doe_spec.yaml` — schema
|
|
2
|
+
|
|
3
|
+
No schema was supplied, so this is ours. Designed against one question: **can the process
|
|
4
|
+
engineer in Arbeitsvorbereitung take this file to the machine tomorrow morning, and can an
|
|
5
|
+
auditor later reconstruct why every number in it is what it is?**
|
|
6
|
+
|
|
7
|
+
Three rules shaped it:
|
|
8
|
+
|
|
9
|
+
1. **The answer comes first.** `centre_point` and `factors` are at the top, because that is
|
|
10
|
+
what the engineer actually needs: which parameters to vary, where to start, how wide. The
|
|
11
|
+
provenance lives below and is for the auditor, not the operator.
|
|
12
|
+
2. **Every bound carries its basis.** No number appears without `*_basis` saying where it came
|
|
13
|
+
from — material card, derived, measured, or judgement. A bound whose basis is "judgement"
|
|
14
|
+
is allowed; a bound with no basis is not.
|
|
15
|
+
3. **Absolute values are quarantined.** Any field whose value depends on the solver's absolute
|
|
16
|
+
pressure scale sits under a key marked `absolute: true` and carries `caveats`. The
|
|
17
|
+
guardrail lint (`process_window/guardrails.py`) walks this file and fails if an absolute pressure
|
|
18
|
+
appears inside `acceptance_criteria`.
|
|
19
|
+
|
|
20
|
+
Temperatures are emitted in **both** °C and K. The engineer works in °C, the API works in K,
|
|
21
|
+
and the whole project's worst failure mode is a silent confusion between them — so the file
|
|
22
|
+
states both rather than making a reader convert.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Structure
|
|
27
|
+
|
|
28
|
+
```yaml
|
|
29
|
+
schema: simcon.doe_spec/1
|
|
30
|
+
generated_at: 2026-08-04T14:22:07+02:00
|
|
31
|
+
generated_by: "frontloaded-doe POC, commit <sha>"
|
|
32
|
+
status: draft | confirmed # confirmed only after G4 has run
|
|
33
|
+
|
|
34
|
+
# ---------------------------------------------------------------- the answer
|
|
35
|
+
centre_point:
|
|
36
|
+
definition: chebyshev_centre_shrunk # deepest interior point of the green region,
|
|
37
|
+
# shrunk inward by the safety margin.
|
|
38
|
+
# NOT the survivor mean, NOT the default settings.
|
|
39
|
+
melt: {value_C: 245.0, value_K: 518.15, basis: derived}
|
|
40
|
+
wall: {value_C: 40.0, value_K: 313.15, basis: derived}
|
|
41
|
+
flow: {value_cm3_s: 9.4, basis: derived}
|
|
42
|
+
safety_margin: # in VARIABLE space, not KPI space
|
|
43
|
+
melt_C: 6.2
|
|
44
|
+
wall_C: 3.1
|
|
45
|
+
flow_cm3_s: 0.7
|
|
46
|
+
basis: >
|
|
47
|
+
KPI uncertainty band divided by the local gradient of that KPI from the G2
|
|
48
|
+
surrogate. Band = derived noise floor (+ AI-to-numerical disagreement once G4
|
|
49
|
+
has run).
|
|
50
|
+
band_source: {kpi: p80, band: 1.8, unit: bar, floor_run_ids: [...]}
|
|
51
|
+
|
|
52
|
+
factors: # what to vary, ranked by effect share
|
|
53
|
+
- name: melt_temperature
|
|
54
|
+
unit_engineer: degC
|
|
55
|
+
unit_api: K
|
|
56
|
+
role: control # control | inert | readout | uncertainty
|
|
57
|
+
centre_C: 245.0
|
|
58
|
+
low_C: 232.0
|
|
59
|
+
high_C: 258.0
|
|
60
|
+
bounds_basis: derived # from the green region, inset by the margin
|
|
61
|
+
effect_share: 0.54 # normalised, per KPI below
|
|
62
|
+
effect_share_per_kpi: {p80: 0.61, weld_front_temp: 0.88, hotspot: 0.42}
|
|
63
|
+
rank: 1
|
|
64
|
+
above_noise_floor: true
|
|
65
|
+
|
|
66
|
+
- name: injection_rate
|
|
67
|
+
unit_engineer: cm3_s
|
|
68
|
+
role: control
|
|
69
|
+
centre_cm3_s: 9.4
|
|
70
|
+
low_cm3_s: 7.1
|
|
71
|
+
high_cm3_s: 11.8
|
|
72
|
+
bounds_basis: derived
|
|
73
|
+
effect_share: 0.39
|
|
74
|
+
rank: 2
|
|
75
|
+
above_noise_floor: true
|
|
76
|
+
|
|
77
|
+
fixed_factors: # held, with the reason it is held
|
|
78
|
+
- name: mould_wall_temperature
|
|
79
|
+
value_C: 40.0
|
|
80
|
+
value_K: 313.15
|
|
81
|
+
reason: inert # inert | tool_property | out_of_scope | judgement
|
|
82
|
+
detail: >
|
|
83
|
+
Entire effect across the card window sits below the derived floor for every KPI.
|
|
84
|
+
Declared inert and fixed, not ranked low (guardrail 2).
|
|
85
|
+
floor_comparison: {kpi: p80, effect: 0.9, floor: 1.8, unit: bar}
|
|
86
|
+
|
|
87
|
+
- name: htc
|
|
88
|
+
value_W_m2K: 1000.0
|
|
89
|
+
reason: out_of_scope
|
|
90
|
+
detail: "Melt-to-mould contact property, not a machine setting. Not swept (user directive)."
|
|
91
|
+
|
|
92
|
+
- name: gate_positions_mm
|
|
93
|
+
value: [[...]]
|
|
94
|
+
reason: tool_property
|
|
95
|
+
detail: "Gate layout is tool design, not a machine parameter. Fixed across the DOE."
|
|
96
|
+
|
|
97
|
+
readouts: # free axes, not swept inputs
|
|
98
|
+
- name: switchover_point
|
|
99
|
+
unit: phi # injected-volume fraction
|
|
100
|
+
values: [0.90, 0.95, 0.98, 0.99]
|
|
101
|
+
method: >
|
|
102
|
+
Field state read at fill fraction phi from runs already paid for. This is the state
|
|
103
|
+
AT switchover, not the effect OF switching there — the effect is in packing and is
|
|
104
|
+
swept in numerical_confirmation below.
|
|
105
|
+
caveats: [incompressible_melt_phi]
|
|
106
|
+
|
|
107
|
+
# ---------------------------------------------------------------- what was ruled out
|
|
108
|
+
excluded_regions: # "which 90%", and specifically why each
|
|
109
|
+
- id: X1
|
|
110
|
+
bounds: {melt_C: [220.0, 231.0]}
|
|
111
|
+
reason: no_flow_margin
|
|
112
|
+
detail: "Melt within <8 K of the card's no-flow temperature at the last-filled region."
|
|
113
|
+
kpi: no_flow_margin
|
|
114
|
+
evidence_run_ids: [...]
|
|
115
|
+
absolute: false
|
|
116
|
+
- id: X2
|
|
117
|
+
bounds: {flow_cm3_s: [13.1, 13.5], melt_C: [220.0, 236.0]}
|
|
118
|
+
reason: hotspot_rank
|
|
119
|
+
detail: "Thermal heterogeneity in the worst decile across the sweep."
|
|
120
|
+
kpi: hotspot
|
|
121
|
+
evidence_run_ids: [...]
|
|
122
|
+
absolute: false
|
|
123
|
+
|
|
124
|
+
# ---------------------------------------------------------------- how it was decided
|
|
125
|
+
acceptance_criteria: # MUST be rank / ratio / comparison only
|
|
126
|
+
- name: pressure_ordering
|
|
127
|
+
kind: rank
|
|
128
|
+
expression: "p80_rank <= 0.70" # keep the best 70% by rank
|
|
129
|
+
kpi: p80
|
|
130
|
+
absolute: false
|
|
131
|
+
note: "Ordering only. p80 never compared to a threshold in bar (guardrail 1)."
|
|
132
|
+
- name: no_flow_guard
|
|
133
|
+
kind: ratio
|
|
134
|
+
expression: "(T_min_at_front - no_flow_temp) / (melt_C - no_flow_temp) >= 0.15"
|
|
135
|
+
kpi: no_flow_margin
|
|
136
|
+
absolute: false
|
|
137
|
+
|
|
138
|
+
noise_floor: # derived here, never imported
|
|
139
|
+
method: epsilon_input_micro_experiment
|
|
140
|
+
mechanism: >
|
|
141
|
+
Solver is deterministic, so this is float16 storage quantization plus model
|
|
142
|
+
conditioning — not run-to-run scatter. Replicates carry zero information.
|
|
143
|
+
per_kpi:
|
|
144
|
+
p80: {floor: 1.8, unit: bar, n_runs: 16}
|
|
145
|
+
weld_front_temp: {floor: 0.6, unit: K, n_runs: 16}
|
|
146
|
+
hotspot: {floor: 0.02, unit: "-", n_runs: 16}
|
|
147
|
+
|
|
148
|
+
feasibility_gate:
|
|
149
|
+
primary: no_flow_margin # physics-based freeze-off guard
|
|
150
|
+
fill_completeness:
|
|
151
|
+
used_as_gate: false | true
|
|
152
|
+
residual_unfilled_at_best_point: 0.0
|
|
153
|
+
flow_dependence: none | present
|
|
154
|
+
detail: >
|
|
155
|
+
Fill completeness is used as a gate only if the residual unfilled fraction at the
|
|
156
|
+
best point is ~0 and shows no flow-rate dependence. A flow-dependent residual is a
|
|
157
|
+
known model artifact, not physics, and must not drive exclusion.
|
|
158
|
+
|
|
159
|
+
# ---------------------------------------------------------------- the confirmation set
|
|
160
|
+
numerical_confirmation:
|
|
161
|
+
solver: cadmould_numerical
|
|
162
|
+
mesh: same_as_ai # same .cfex — apples to apples
|
|
163
|
+
points:
|
|
164
|
+
- id: C0
|
|
165
|
+
role: centre # centre | face | probe
|
|
166
|
+
melt_C: 245.0
|
|
167
|
+
wall_C: 40.0
|
|
168
|
+
flow_cm3_s: 9.4
|
|
169
|
+
switchover_phi: 0.97
|
|
170
|
+
holding_pressure_bar: 420.0
|
|
171
|
+
holding_time_s: 4.5
|
|
172
|
+
cooling_time_s: 12.0
|
|
173
|
+
pack_cool_basis: sdk_suggestion
|
|
174
|
+
rm1: "cache/runs/<part-key>/G4_confirmation/C0.rm1"
|
|
175
|
+
rm1_validated: true # Rm1Writer -> Rm1Reader round trip, field-checked
|
|
176
|
+
submit: {tier: 1, endpoint: "POST /simulations"}
|
|
177
|
+
status: prepared | submitted | done
|
|
178
|
+
rule: >
|
|
179
|
+
Where a numerical result disagrees with the AI-solver ordering, step the bounds
|
|
180
|
+
INWARD, never outward.
|
|
181
|
+
|
|
182
|
+
# ---------------------------------------------------------------- provenance / audit
|
|
183
|
+
provenance:
|
|
184
|
+
part: {name: clip_frame, geometry_id: ..., cfex_sha256: ..., nodes: 6201,
|
|
185
|
+
volume_cm3: ..., max_wall_mm: ...}
|
|
186
|
+
material: {catalogue_name: "PP Generic", material_id: ..., version_id: ...,
|
|
187
|
+
plb_sha256: ..., no_flow_temp_C: ...,
|
|
188
|
+
window: {melt_C: [.., ..], mold_C: [.., ..]}}
|
|
189
|
+
machine_limits:
|
|
190
|
+
max_injection_pressure_bar: 1500.0
|
|
191
|
+
max_clamp_force_kN: 1000.0
|
|
192
|
+
applied_as: annotation_only # a constraint, never a screening criterion
|
|
193
|
+
absolute: true
|
|
194
|
+
caveats: [multi_gate_pressure_underprediction, cavities_1_of_32, no_runner_system]
|
|
195
|
+
wide_box:
|
|
196
|
+
melt_C: {low: .., high: .., basis: material_card}
|
|
197
|
+
mold_C: {low: .., high: .., basis: material_card}
|
|
198
|
+
flow_cm3_s: {low: .., high: .., basis: nominal_ratio, rel_span: 0.5}
|
|
199
|
+
ai_runs:
|
|
200
|
+
stages:
|
|
201
|
+
- {stage: G1, group_id: ..., design: sobol, seed: 0, n_planned: 1024, n_completed: ...}
|
|
202
|
+
- {stage: G3, group_id: ..., design: adaptive_boundary, n_completed: ...}
|
|
203
|
+
total_completed: ...
|
|
204
|
+
determinism_note: "Identical request returns identical result; no replicates run."
|
|
205
|
+
surrogate:
|
|
206
|
+
type: rbf | quadratic_rs
|
|
207
|
+
loo_error_per_kpi: {p80: 0.016, weld_front_temp: 0.008}
|
|
208
|
+
self_diagnosis:
|
|
209
|
+
verdict: green | warn | alarm
|
|
210
|
+
thresholds_basis: derived_from_anchor_spread
|
|
211
|
+
anchors: [...]
|
|
212
|
+
fake_real_source: ai_with_hidden_deviation | numerical_with_hidden_deviation
|
|
213
|
+
limitation: >
|
|
214
|
+
AI-vs-AI-with-hidden-deviation measures sensitivity to input misspecification,
|
|
215
|
+
not the AI-to-physics offset.
|
|
216
|
+
|
|
217
|
+
guardrail_report:
|
|
218
|
+
absolute_pressure_in_criteria: pass # lint fails the run otherwise
|
|
219
|
+
noise_floor_derived_not_imported: pass
|
|
220
|
+
caveats_attached_to_absolute_claims: pass
|
|
221
|
+
|
|
222
|
+
caveats: # auto-attached to absolute-valued claims
|
|
223
|
+
cavities_1_of_32: "1 of 32 cavities modelled, single gate."
|
|
224
|
+
no_runner_system: "No runner system in the model; the hot runner is absent, not approximated."
|
|
225
|
+
no_process_description: "Almost no description of the real process beyond the material."
|
|
226
|
+
multi_gate_pressure_underprediction: >
|
|
227
|
+
AI solver under-predicts absolute pressure on multi-gate parts (~0.3x on an 8-gate
|
|
228
|
+
part). Every criterion is therefore a rank, ratio or comparison.
|
|
229
|
+
weld_temp_circularity: >
|
|
230
|
+
Weld-front temperature ordered points well (Spearman ~0.95, roughly constant offset)
|
|
231
|
+
but was tested against a melt perturbation while melt drives that KPI roughly 1:1.
|
|
232
|
+
incompressible_melt_phi: >
|
|
233
|
+
The phi axis equates injected with filled volume. Sub-percent during fill, worst as
|
|
234
|
+
phi -> 1, which is where switchover lives.
|
|
235
|
+
fill_race_not_transferable: >
|
|
236
|
+
Fill-race metrics measured not to transfer (Spearman 0.10-0.23). Computed as an
|
|
237
|
+
anomaly detector only, never as a screening criterion.
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
## Field conventions
|
|
241
|
+
|
|
242
|
+
| Convention | Rule |
|
|
243
|
+
|---|---|
|
|
244
|
+
| `*_C` / `*_K` | Temperatures are always emitted in both. |
|
|
245
|
+
| `*_basis` | `material_card` \| `derived` \| `measured` \| `sdk_suggestion` \| `judgement` \| `user_directive`. Required next to every bound. |
|
|
246
|
+
| `absolute: true` | Marks a value that depends on the solver's absolute pressure scale. Requires non-empty `caveats`. Forbidden inside `acceptance_criteria`. |
|
|
247
|
+
| `role` | `control` (swept, kept) \| `inert` (below floor, fixed) \| `readout` (free axis) \| `uncertainty` (robustness axis). |
|
|
248
|
+
| `unset` | Explicit, not omitted — so the dossier can state that a limit was unavailable rather than silently skipping it. |
|
|
249
|
+
| run ids | Every exclusion and every threshold cites the run ids it rests on. |
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""Find the process window for a part, before you book machine time.
|
|
2
|
+
|
|
3
|
+
One campaign, run a step at a time:
|
|
4
|
+
|
|
5
|
+
python main.py --list what the steps are, in order
|
|
6
|
+
python main.py inspect is this part worth a campaign at all?
|
|
7
|
+
python main.py sweep configs/simple-plate.yaml
|
|
8
|
+
|
|
9
|
+
Each step is resumable and writes what the next one reads, so they run in the order
|
|
10
|
+
`--list` prints. Judgement belongs between them, which is why there is no command that
|
|
11
|
+
runs the whole campaign: a sweep costs thousands of solver runs, and you decide whether
|
|
12
|
+
the result is worth refining before paying for the next stage.
|
|
13
|
+
|
|
14
|
+
Everything after the step name belongs to that step — `python main.py refine --help`
|
|
15
|
+
prints the refinement step's own options. The steps do not share a flag set on purpose:
|
|
16
|
+
`--grid` means one thing to `refine` and another to `economics`, and folding them into
|
|
17
|
+
one option here would silently change what a campaign ran.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import importlib
|
|
23
|
+
import sys
|
|
24
|
+
|
|
25
|
+
# Step name -> the module that implements it, in the order a campaign runs them.
|
|
26
|
+
# The name is the verb a reader types; the module keeps the name the campaign uses
|
|
27
|
+
# for the stage internally.
|
|
28
|
+
STEPS: dict[str, tuple[str, str]] = {
|
|
29
|
+
"inspect": ("inspect_part", "G0 — geometry, wall, L/t. Free; run it first"),
|
|
30
|
+
"epsilon-floor": ("run_epsilon_floor", "G1a — this part's resolution floor"),
|
|
31
|
+
"probe": ("probe_proxy", "G1b — does the feasibility gate discriminate?"),
|
|
32
|
+
"sweep": ("run_sweep", "G1 — space-filling sweep, resumable"),
|
|
33
|
+
"screen": ("run_screening", "G2 — rank the variables, map what is excluded"),
|
|
34
|
+
"refine": ("run_refine", "G3 — adaptive boundary refinement + centre point"),
|
|
35
|
+
"confirm": ("run_confirm", "G4 — numerical confirmation decks"),
|
|
36
|
+
"feedback": ("run_feedback", "G6 — hidden-deviation self-diagnosis"),
|
|
37
|
+
"emit": ("emit", "G5 — write doe_spec.yaml and dossier.md"),
|
|
38
|
+
"economics": ("economics", "cycle time vs robustness"),
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def print_steps() -> None:
|
|
43
|
+
width = max(len(name) for name in STEPS)
|
|
44
|
+
print("Campaign steps, in the order they run:\n")
|
|
45
|
+
for name, (_module, blurb) in STEPS.items():
|
|
46
|
+
print(f" {name:<{width}} {blurb}")
|
|
47
|
+
print("\nRun one with: python main.py <step> [its own options]")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def main(argv: list[str] | None = None) -> int:
|
|
51
|
+
argv = list(sys.argv[1:] if argv is None else argv)
|
|
52
|
+
|
|
53
|
+
if not argv or argv[0] in {"-h", "--help"}:
|
|
54
|
+
print(__doc__)
|
|
55
|
+
print_steps()
|
|
56
|
+
return 0
|
|
57
|
+
if argv[0] in {"-l", "--list"}:
|
|
58
|
+
print_steps()
|
|
59
|
+
return 0
|
|
60
|
+
|
|
61
|
+
step, rest = argv[0], argv[1:]
|
|
62
|
+
if step not in STEPS:
|
|
63
|
+
print(f"unknown step: {step}", file=sys.stderr)
|
|
64
|
+
print(f"expected one of: {', '.join(STEPS)}", file=sys.stderr)
|
|
65
|
+
return 2
|
|
66
|
+
|
|
67
|
+
module_name, _blurb = STEPS[step]
|
|
68
|
+
# Imported here rather than at module level, so listing the steps and reading this
|
|
69
|
+
# help never needs the licensed cadmould wheel. Every step reaches it — one directly,
|
|
70
|
+
# the rest through the module that builds the campaign — so an eager command table
|
|
71
|
+
# would make `--list` impossible without a licence.
|
|
72
|
+
module = importlib.import_module(f"process_window.{module_name}")
|
|
73
|
+
|
|
74
|
+
# Re-present the command line as though the step had been run directly: each step
|
|
75
|
+
# owns its own parser, and this is what keeps `main.py refine --grid 41` identical
|
|
76
|
+
# to what that step has always accepted.
|
|
77
|
+
sys.argv = [f"main.py {step}", *rest]
|
|
78
|
+
return int(module.main() or 0)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
if __name__ == "__main__":
|
|
82
|
+
raise SystemExit(main())
|