Skip to content

Add T-3_01 Sobol batch print key (ID-to-spec mapping + as-printed .3mf) - #102

Open
sgbaird wants to merge 15 commits into
mainfrom
claude/issue-98-20260821-0103
Open

Add T-3_01 Sobol batch print key (ID-to-spec mapping + as-printed .3mf)#102
sgbaird wants to merge 15 commits into
mainfrom
claude/issue-98-20260821-0103

Conversation

@sgbaird

@sgbaird sgbaird commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

Adds the key linking each T-3_01 print ID to its Sobol specimen and parameters, recovered from the plate names in the as-printed .3mf uploaded in #98. Includes the slicer project, a per-print key CSV (mass, RH%, defects, geometry), the batch design table from PR #35 commit 32addaf, and a labeled visual key. Flags the Spec 08 official-print discrepancy (dea4ls per .3mf vs bag26v per issue comment).

Closes nothing; supports #98.

Generated with Claude Code

…sual key

The .3mf uploaded in issue #98 names each plate with its print ID and
carries one Sobol specimen per plate, recovering the print-to-parameters
mapping needed for parsing drop data. Includes the batch design table
from PR #35 (32addaf) and flags the Spec 08 official-print discrepancy
(dea4ls per .3mf plate label vs bag26v per issue comment).

Co-authored-by: Marcus Madsen <265197858+me-madsen@users.noreply.github.com>
@sgbaird

sgbaird commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator Author

@claude summarize where we left off from the issue

@claude

claude Bot commented Aug 21, 2026

Copy link
Copy Markdown

Claude finished @sgbaird's task in 1m 5s —— View job


Where we left off on #98 (T-3_01 Prints)

Printing: complete

All 9 Sobol specimens plus the S0 reference prism now have acceptable-to-test prints (announced 8/19). Documented IDs, mapped to specs via the plate names in the as-printed .3mf:

Spec Print ID(s) Mass (g) Notes
00 9hhbkp 21.62 no defects
01 6lhxfy 18.50 slight strut misprint, tendon divot
02 autv5r 22.04 minor tendon misalignment at top
03 ebdna8 19.61 strings peeled off two bottom tendons
04 nvxsrv 20.66 misalignment ring after printer pause
05 6nheas 21.73 strings off two bottom tendons
06 1zm8rv 18.50 stringing on diagonal tendons
07 ajhby6 20.53 filament line pulled from bottom struts
08 dea4ls / bag26v / ghmj4y 22.29 / 21.42 / 22.10 see discrepancy below
S0 ref bpx68c 20.23 not part of the Sobol batch; TPU-tuning reference

This PR (#102) commits that key to bo/: the print-key CSV, the as-printed .3mf, the Sobol design table from PR #35, a visual key, and a README with provenance.

Testing: campaign underway (tracked in PR #86, not #98)

The drop campaign started 8/19 (60 inch drop, 1/2 inch polyurethane mat, 101 drops per specimen, 3 slow-motion videos each). As of the 8/21 analysis on PR #86, 8/9 specimens (942 drops) are analyzed with a BO-ready campaign summary, and the mass-variation question was answered there (the constant-mass constraint holds solid CAD volume constant, not printed grams, so PLA infill vs. near-solid TPU makes printed mass vary by design). BO implementation is with @sgbaird in a separate thread.

Open items on #98

  1. Spec 08 official print is ambiguous. The .3mf plate label says official_dea4ls, but the 8/12 comment marks bag26v as official. @me-madsen (or @achris0520) needs to confirm which one is the test article; the committed key records both claims.
  2. Per-print .3mf placeholders. The two earliest comments still say "to be updated by @achris0520 with the .3mf files." The batch-level slicer project is now in this PR, which may cover it, but the placeholders were never filled.
  3. Missing template fields. RH% is unknown for the five earliest prints (bag26v, dea4ls, ghmj4y, ebdna8, bpx68c; the rest are ~8%), and no comment recorded the TPU or PLA infill % that the issue template asks for.
  4. This PR is open and unmerged, so the key only lives on branch claude/issue-98-20260821-0103 until merged.
    Branch

@sgbaird

sgbaird commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator Author

@claude bring in the BO script for ingesting the results from the Sobol sampling #86 which would have been run in #35 I think. Use Honegumi (pip install and also the online tutorials) with "existing data" and "batch", and "fully Bayesian" and "multi-objective" and "visualization" (assuming there were actually multiple objectives at this stage based on the data and discussions in #59 #60 #94 #97 #99). Ingest the results, 8 so far, attaching that as existing data, and set it up to return and record the next suggested experiments. Send a link to the file so I can review it.

@claude

claude Bot commented Aug 21, 2026

Copy link
Copy Markdown

Claude finished @sgbaird's task in 15m 37s —— View job


Honegumi SAASBO campaign script: 8 results ingested, round-2 batch suggested ✅


The file to review

bo/t3_prism_bo_campaign.py — Honegumi-templated Ax Service API script (template options: objective=Multi, model=Fully Bayesian → Models.SAASBO, existing_data=True, synchrony=Batch, visualize=True). Three deviations from the rendered template, each documented in the docstring: (1) the Sobol GenerationStep is dropped because the initialization already ran physically as PR #35's printed batch; (2) the inline X_train/y_train is replaced by CSV ingestion; (3) the loop runs once per physical round since the "objective function" is a print + 101-drop session. Template pins ax-platform 0.4.3; the script runs on 0.5.0 (same Service API, resolves cleanly on Python 3.12/numpy 2.x).

Choices you'll want to sanity-check

Round-2 suggestions (batch of 9, recorded to t3-prism-bo-suggestions-round1.csv)

trial R (mm) H (mm) twist (°) strut Ø (mm) cable Ø (mm) pred. t180 pred. e_rebound
10 25.0 60.0 40.0 12.0 3.0 0.905 ± 0.089 0.033 ± 0.017
11 25.0 60.0 80.0 12.0 5.5 0.903 ± 0.083 0.036 ± 0.016
12 40.0 60.0 80.0 12.0 5.5 0.885 ± 0.084 0.045 ± 0.018
13 25.0 84.3 40.0 12.0 5.5 0.958 ± 0.082 0.025 ± 0.018
14 25.0 60.0 80.0 8.2 3.0 0.942 ± 0.048 0.032 ± 0.011
15 25.0 60.0 62.8 12.0 3.0 0.884 ± 0.069 0.037 ± 0.014
16 40.0 110.0 40.0 12.0 5.5 0.984 ± 0.079 0.026 ± 0.017
17 40.0 60.0 40.0 12.0 3.0 0.890 ± 0.085 0.046 ± 0.016
18 40.0 60.0 40.0 12.0 5.5 0.915 ± 0.093 0.038 ± 0.018

The batch leans hard into thick struts (8 of 9 at the 12 mm bound; 6lhxfy's 10.9 mm strut was the standout) and short prisms (7 of 9 at H = 60), spreading across the twist/cable/R dimensions where the SAAS model is uncertain. Boundary-seeking is expected for qNEHVI with 7 points in 5D; if 12 mm struts are unprintable or the corners look too aggressive for a physical batch, that's a bounds/constraint conversation before printing.

Everything committed (4e26bb2)

Not done / for later: re-run once amdjwm is identified and the 9th specimen lands (one command: python bo/t3_prism_bo_campaign.py --round 2); objective thresholds were left to Ax's inference (it chose t180 ≤ 1.023, e_rebound ≤ 0.053); no manufacturability constraints (envelope ≤ 250 cm³, cable-bridge check) are wired into the acquisition yet, since round 1 printed violating designs anyway.
Branch

…suggest round-2 batch

Multi-objective (t180, e_rebound), fully Bayesian, batch, existing-data
Honegumi template adapted for the physical campaign: Sobol init step
dropped (PR #35 batch was the init), results ingested from the PR #86
campaign_summary.csv snapshot, specs 03/06/07 attached as pending
trials, amdjwm skipped until its spec mapping is resolved. Records 9
suggested base-space designs with posterior predictions, the AxClient
state, and a Pareto + parameter-space figure.

Co-authored-by: Sterling G. Baird <45469701+sgbaird@users.noreply.github.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@sgbaird

sgbaird commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator Author
  • Objectives: minimize t180 and minimize e_rebound, per the BO hand-off section of the campaign analysis doc and the Energy-absorption measurability review + drop-tower metrics primer #97 energy-absorption framing. The multi-objective assumption holds in the data: they genuinely trade off (best attenuator 6lhxfy hops hardest at e_rebound 0.050), so the Pareto front is informative. Noise is passed as per-drop SEM; the ~2% print-to-print floor (n = 1 article per design) is noted but not modeled.

@claude verify this with Edison scientific. Socratic method or something? Whatever you did before where you didn't just ask it to confirm or deny

@claude

claude Bot commented Aug 21, 2026

Copy link
Copy Markdown

Claude encountered an error after 19m 42s —— View job


Verifying the BO objectives claim with Edison Scientific

  • Found the precedent: Edison task d9092c5a (PR Add drop-test protocol, Edison synthesis, and first-data analysis #86, commits b43fb02/b6a296e) was an adversarial review: Edison got the raw data and was asked to attack the analysis's underpinnings and recompute independently, not confirm it (that one overturned the PU arrangement B recommendation). Reused that structure, plus a Socratic opening: Edison must derive its own objective set from the numeric files and commit to it in writing before reading our analysis docs or what we chose, then record any post-reading change of mind as an explicit diff.
  • Gathered the claim's underpinnings into a 23-file bundle across three branches: per-drop campaign_metrics.json (all 942 drops), the 10 TP4 series tables, campaign analysis doc + script, the e_rebound = g·t_second/(2·Δv) definition, the Energy-absorption measurability review + drop-tower metrics primer #97 energy-absorption review, the print-defect ~2% floor study, and the BO script itself (noise line: t180_sd/sqrt(n))
  • Submitted Edison ANALYSIS task 3e398131-d960-423d-97a2-32aa4db61ac1; scripts + task id committed and pushed in 467a4f9 (submit / fetch)
  • Poll to completion (foreground, checking this thread for new comments each cycle)
  • Fetch and commit all artifacts
  • Summarize Edison's independent verdict vs. the claim

What Edison is being asked, without a confirm/deny framing: Section A (Socratic, data-only): pick your own objectives, decide single vs multi-objective, and state the noise magnitude you'd hand a GP, before seeing our choice. Then attack sections: B) what e_rebound physically is (it looks like a velocity ratio labeled as energy), whose hop it measures (specimen vs carriage/mat restitution), and whether minimizing it double-counts against the energy-diversion mechanism that made 6lhxfy the best attenuator; C) whether the trade-off survives dropping single points at n = 8, and whether it's a real Pareto front vs a 1-D compliance coupling; D) what a 50x-understated noise floor does to SAASBO/qNEHVI and whether that explains the round-2 batch collapsing onto the strut/height bounds; E) whether the 16.8% design spread settles the earlier rigid-body objection to T; F) what's missing (absolute output peak, t1000 constraint, the excluded amdjwm); G) verdict + the concrete corrected round-2 formulation.

--- • Branch

@sgbaird

sgbaird commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator Author

@claude make sure to account for the fact that the actual mass is different for each print in terms of the objectives (and therefore the BO suggestions)

@claude

claude Bot commented Aug 21, 2026

Copy link
Copy Markdown

Claude encountered an error after 14m 53s —— View job


Per-print mass now enters the objectives and the suggestions ✅

  • Gather context: campaign doc section 7 (constant solid volume, not constant grams; PLA ~57% effective density vs TPU ~solid) and the earlier "carry mass as a feature or normalize by it" hand-off
  • Decide and document the mass treatment (docstring deviation 4 + objective rationale)
  • Update bo/t3_prism_bo_campaign.py to ingest each tested article's measured mass
  • Re-run (3m 48s); regenerated suggestions CSV, AxClient snapshot, and figure
  • Update bo/README.md; commit + push (7a048ee, rebased on top of the Edison-verification commit 467a4f9)

What changed and why

The mass concern is real in the data: corr(mass, t180) = 0.83 across the 7 mapped articles (masses 18.50 to 22.04 g, CV 5.9%). Three changes in bo/t3_prism_bo_campaign.py:

  1. Objective 2 is now e_reb_mJ, the absolute rebound energy returned to the payload per drop: e_rebound × m_printed × g × h (h = 60 in, each article's own weighed mass). The raw e_rebound is a fraction of an impact energy that itself varies ±9% with printed mass, so equal fractions on an 18.5 g and a 22.3 g article differ by ~20% in delivered millijoules. Minimizing the millijoules compares designs on what the payload actually receives and re-penalizes grams a design adds. Its noise now folds in the print-to-print mass scatter (sd 0.457 g, measured from the spec-08 triplicate dea4ls/bag26v/ghmj4y, the only design printed three times), so the n-of-1-article mass channel is modeled rather than just footnoted.
  2. t180 deliberately stays a ratio. It is already normalized by the measured input peak, and payload peak acceleration is a damage criterion that doesn't scale with specimen mass. The r = 0.83 correlation is confounded with geometry by construction (light articles are the PLA-heavy thick-strut/thin-cable corner), so regressing mass out of t180 with n = 7 would delete the very signal the campaign is after. Flagging this as the choice most worth your review, along with the multiply-vs-divide direction on objective 2 (I penalize heavy articles via absolute energy; a per-gram efficiency framing would reward them).
  3. mass_g is an Ax tracking metric, so the SAAS model learns printed mass as a function of the base coordinates, and every suggested design now reports a predicted as-printed mass. This is the "carry mass as a feature" option from the campaign doc addendum, without making mass a fake controllable parameter.

Regenerated round-2 batch (t3-prism-bo-suggestions-round1.csv)

trial R H twist strut Ø cable Ø pred t180 pred e_reb (mJ) pred mass (g)
10 25 60 80 12.0 3.0 0.873 ± 0.053 11.3 ± 4.3 18.3 ± 1.2
11 40 60 80 12.0 5.5 0.885 ± 0.075 12.2 ± 5.7 19.4 ± 1.7
12 25 60 40 12.0 3.0 0.926 ± 0.095 9.9 ± 5.0 19.8 ± 2.1
13 25 60 80 7.4 3.0 0.948 ± 0.046 8.3 ± 3.2 19.4 ± 1.1
14 25 60 40 12.0 5.5 0.953 ± 0.094 9.0 ± 5.6 20.5 ± 2.0
15 40 60 40 12.0 3.0 0.905 ± 0.091 13.0 ± 5.1 20.1 ± 1.9
16 25 60 80 12.0 5.5 0.910 ± 0.073 10.1 ± 5.2 19.2 ± 1.6
17 40 110 80 12.0 5.5 0.955 ± 0.071 10.0 ± 5.3 19.8 ± 1.6
18 25 60 80 6.0 5.5 0.987 ± 0.066 8.4 ± 4.3 20.5 ± 1.6

Versus the pre-mass-aware batch: still short prisms (8/9 at H = 60) and mostly 12 mm struts (7/9), but the batch now leans high-twist (5/9 at 80° vs 3/9 before, chasing 6lhxfy's 77.4°) and probes two mid/thin struts (7.4 and 6.0 mm) it previously ignored, because the energy objective values the low-rebound corner that bpx68c/9hhbkp occupy. On the mass-aware Pareto front, 6lhxfy is still the best attenuator, but its rebound-energy penalty (13.9 mJ, worst in the batch even at the lightest mass) is now explicit, and the S0 reference bpx68c anchors the low-energy end at 6.2 mJ.

Also updated: bo/README.md, bo/t3-prism-bo-ax-client-round1.json (snapshot now carries the mass metric). Spec 08 enters as bag26v at 21.42 g (the tested article); amdjwm is still skipped, and now needs a mass as well as a spec mapping before it can be attached. The Edison verification of the objectives claim (previous comment) is running separately; note its claim under test predates this rename (e_rebound fraction vs e_reb_mJ), so its verdict will speak to the framing, not the exact column.

--- • Branch

github-actions Bot and others added 2 commits August 21, 2026 05:52
Task 3e398131: Edison derives its own objective set from the per-drop
campaign data before reading ours, then attacks the three legs of the
hand-off claim (t180+e_rebound objective pair, the n=8 trade-off/Pareto
framing, and the per-drop-SEM noise model that ignores the ~2% print floor).
Bundle: 23 files across three branches (per-drop metrics, series tables,
analysis doc+script, print-defect floor study, #97 energy review, BO script).
Follows the d9092c5a precedent from PR #86.

Co-authored-by: Sterling G. Baird <45469701+sgbaird@users.noreply.github.com>
…_g tracking metric, regenerated round-1 suggestions

Co-authored-by: Sterling G. Baird <45469701+sgbaird@users.noreply.github.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@sgbaird

sgbaird commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator Author

@claude nice plot on the left. Let's use that. We don't need the parallel coordinates one on the right. For the one on the left: make a presentation-ready version of this. Increase font size. Make the unique IDs gray (de-emphasize). No need for the masses. Get me links to the images for the ones on the Pareto front. Put the legend outside of the plot. For the axes, we only need 6 to 14 and 0.8 to 1.1 (or similar) for the axes ticks and we don't need the gridlines. Remove the "objective space" title.

@claude

claude Bot commented Aug 21, 2026

Copy link
Copy Markdown

Claude finished @sgbaird's task in 4m 53s —— View job


Presentation-ready Pareto figure ✅

  • Read the current plotting code in bo/t3_prism_bo_campaign.py
  • Rewrite the figure: left panel only, larger fonts, gray IDs, no masses, legend outside, ticks 0.8-1.1 / 6-14, no gridlines, no title
  • Regenerate and inspect the figure
  • Identify the Pareto-front articles and link their print photos
  • Commit + push (b1130f6)

Full size: bo/figures/t3-prism-bo-round1-pareto.png (rendered at 220 dpi, 9 x 5.6 in, white background, so it drops onto a slide at full width without upscaling).

What changed

Ask Done
Drop the parallel-coordinates panel Removed from the script, not just the figure
Bigger fonts Axis labels 17 pt, ticks 15 pt, legend 14 pt, ID labels 12.5 pt (were 8 to 6.5 pt)
Gray, de-emphasized IDs #83827d; masses dropped from the labels
No masses Gone from both the tested-point labels and the orange suggestion labels
Legend outside Right of the axes, no frame
Ticks 0.8-1.1 and 6-14 x at 0.8/0.9/1.0/1.1, y at 6/8/10/12/14
No gridlines Grid off, top and right spines removed
No "Objective space" title Both that and the T-3_01 SAASBO round 1 suptitle are gone (the slide carries the title)

Two things worth knowing. The front drawn is now the non-dominated set of the observed points rather than Ax's model-predicted front. For round 1 those pick out the same three articles, and plotting observed points means every labeled marker is a physical specimen you can hold up. And the script gained --plot-only, which redraws the figure from the recorded CSVs with only pandas and matplotlib (Ax imports moved into main()): no model refit, ~1 s, and the committed round-2 suggestions are untouched by a restyle.

python bo/t3_prism_bo_campaign.py --plot-only

Mass has not left the analysis: it is still inside e_reb_mJ, still a mass_g tracking metric, and still a predicted column in t3-prism-bo-suggestions-round1.csv. It is only off the plot.

Photos of the three Pareto-front articles

Print ID Spec t180 Rebound (mJ) Role on the front Photo
6lhxfy 01 0.893 13.9 best attenuator, worst rebound photo (in context)
6nheas 05 0.997 13.1 interior knee point photo (in context)
bpx68c S0 ref 1.011 6.2 lowest rebound energy photo (in context)

Left to right: 6lhxfy, 6nheas, bpx68c. All three were photographed by @me-madsen in issue #98. Note bpx68c is the S0 reference prism, not a Sobol specimen, so if the slide is meant to be "the Sobol batch" it is the one point that needs a caption.

Not done: the round-2 suggestions themselves are unchanged (this was a restyle, not a re-run), and the Edison verification of the objectives claim is still pending from the earlier comment.
Branch

…print-ID labels, legend outside, --plot-only redraw

Drops the parallel-coordinates panel and both titles, removes the mass
annotations, raises font sizes, moves the legend outside the axes, sets
0.8-1.1 / 6-14 ticks and removes the gridlines (PR #102 review). The Ax
imports move into main() so --plot-only redraws the figure from the
recorded CSVs with only pandas and matplotlib, no model refit and no
change to the recorded round-2 suggestions.

Co-authored-by: Sterling G. Baird <45469701+sgbaird@users.noreply.github.com>
@sgbaird

sgbaird commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator Author

@claude #102 (comment), y-axis should be horizontal

@claude

claude Bot commented Aug 21, 2026

Copy link
Copy Markdown

Claude finished @sgbaird's task in 1m 3s —— View job


Horizontal y-axis label ✅

  • Rotate the y-axis label to horizontal and re-lay-out the figure
  • Regenerate with --plot-only and inspect
  • Commit + push (147c420)

Full size: bo/figures/t3-prism-bo-round1-pareto.png

The label is now horizontal and parked above the y-axis, left-aligned to the axis line, rather than rotated 90°. Rotating in place would have made a two-line label spill into the tick numbers, so t3_prism_bo_campaign.py:283 sets rotation=0, ha="left", va="bottom" and pins it with yaxis.set_label_coords(-0.035, 1.04). Wording changed only to fit the new shape: "Rebound energy to payload / (mJ per drop, lower is better)".

Nothing else moved. Same data, same round-2 suggestions, same tick range and legend placement.

Co-authored-by: Sterling G. Baird <45469701+sgbaird@users.noreply.github.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@sgbaird

sgbaird commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator Author

Note for us for later (after next week probably), we'll want to adjust the BO script to keep the intended mass constant based on actual infill, you'll probably need to use the bambu lab CLI slicer to check what this actually is. We'll still keep measuring the mass and keeping the objectives normalized by mass. Noting that this requires a special kwarg to be set somewhere within Ax to allow for points outside the design space to be brought in for fitting, even though they are outside the constraints of the search space. A highly thorough search through Ax GitHub would show where this is. Probably something where I've commented on (searching commenter:sgbaird)

@claude comment on above

@claude

claude Bot commented Aug 21, 2026

Copy link
Copy Markdown

Claude finished @sgbaird's task in 3m 42s —— View job


Notes on the constant-intended-mass plan

  • Re-read how mass is handled today in bo/t3_prism_bo_campaign.py
  • Find the Ax kwarg for fitting out-of-design points (searched the Ax repo, including commenter:sgbaird)
  • Check what the Bambu Studio CLI can actually report for per-design printed mass
  • Write it up

Agreed on all three parts, and the ordering matters: the slicer work has to land before the BO change, because the search space you generate under is only meaningful once the mass constraint is the one you actually intend.

1. What "constant intended mass" changes

Today the constraint is constant solid CAD volume, which is not constant grams. PLA struts print at roughly 57% effective density (infill plus walls), TPU cables print near solid, and the PLA:TPU volume split moves with strut diameter, cable diameter, R and H. That is exactly why round 1 spans 18.50 to 22.29 g (CV 5.9%) with corr(mass, t180) = 0.83. So a slice of the projection that "held mass constant" was really holding a proxy constant, and the objectives partly rank designs by how many grams they happen to carry.

The fix is to make the projection solve for the scale factor such that

m_pred = rho_PLA * V_PLA * f_PLA(infill, walls, geometry) + rho_TPU * V_TPU * f_TPU equals a target in grams,

where the f terms are what the slicer knows and the CAD does not. Two ways to get them, and I would do the first:

(a) Calibrate once, then no slicer in the loop. We already have the paired dataset: the as-printed project bo/slices/t3-prism-bo-batch.H2D-MM-PLAstruts-TPUcables.as-printed.3mf plus 10 weighed articles in bo/t3-prism-bo-batch-print-key.csv. Slice each plate, regress predicted against weighed grams, and you get a closed-form mass model plus an honest residual. Invert that inside the projection and slicing never enters the BO loop.

(b) Slicer in the loop, root-finding the scale factor per candidate. Exact, but seconds to tens of seconds per candidate and a hard dependency on a working headless slicer. Only worth it if (a)'s residuals exceed the 0.457 g print-to-print scatter we measured from the spec-08 triplicate, in which case the model is not good enough to trust anyway.

CLI mechanics, for whoever picks this up: bambu-studio --slice 0 --load-settings "machine.json;process.json" --load-filaments "f1.json;f2.json" --export-3mf out.gcode.3mf in.3mf. The exported .gcode.3mf is a zip; per-plate, per-filament weight and length live in Metadata/slice_info.config, and the plate gcode carries the same totals in its footer comments. On a headless runner it still tries to open a display, so xvfb-run it. (CLI reference, BambuStudio wiki, 3mf structure)

Two things that will bite on the calibration if we are not careful. Flush and prime tower: this is a multi-material H2D plate, so a real fraction of extruded filament is purge, and it is reported in the same totals. Per-object mass has to exclude it or every prediction runs high. And flow calibration bias: slicer grams are an estimate from extruded volume, so expect a few percent systematic offset against the scale. The regression absorbs it, which is another argument for (a) over (b).

2. Objectives stay normalized by measured mass

Agreed, and that is already the shape of the current script: e_reb_mJ = e_rebound * m_weighed * g * h uses each article's own weighed mass, t180 stays a ratio because it is already normalized by the input peak, and mass_g rides along as a tracking metric.

One consequence worth writing down now so nobody over-reads it later: once intended mass is constant, the mass channel narrows from 3.8 g of design-driven spread down to roughly the 0.46 g print scatter. That is good (it is the point), but it means e_reb_mJ and the raw e_rebound fraction become close to collinear, and mass_g stops being a design descriptor and becomes a print-quality QC channel. The two-objective structure does not change; the interpretation of the mass metric does.

3. The Ax kwarg, and the part it does not solve

Found it, plus your own comments on the thread. GenerationStep(model=Models.SAASBO, model_kwargs={"fit_out_of_design": True}), established in facebook/Ax#768 (lena-kashtelyan's answer, and your snippet pairing it with immutable_search_space_and_opt_config=False and two clients with different bounds), reaffirmed by mgarrard in #2584.

Three caveats before we copy that forward:

  • The kwarg only affects fitting, not attaching. In our pinned 0.5.0, AxClient.attach_trial calls Experiment.attach_trial, which calls search_space.validate_membership and raises (ax/core/experiment.py:1568 at tag 0.5.0) long before a model exists. So out-of-bounds round-1 data has to come in through a permissive search space that is narrowed for generation, or through the second-client pattern in your #768 comment. fit_out_of_design alone will look like it does nothing.
  • It is deprecated on newer Ax. DataLoaderConfig.__post_init__ on main warns "deprecated after Ax 1.1.2, can be removed with Ax 1.3.0". The live mechanism there is Adapter(expand_model_space=True) (already the default): RangeParameter bounds are soft and expand to the data range for fitting while gen still respects the real search space. So the 2022 snippet is correct for 0.5.0 and wrong for 1.x, and this is a decision to make at the same time as the pin.
  • Known side effect: facebook/Ax#1568, out-of-design trials can cause repeated suggestions. Worth a duplicate check on the round-2 batch.

The bigger risk here is not bounds, it is definitions. If we keep the same five base coordinates and only change the projection, round-1 points stay nominally in design while the map from coordinates to physical article silently changes underneath them, and no fit_out_of_design flag will tell you. That is the stale-data case, and the honest options are to fit on as-printed geometry rather than nominal coordinates (which is exactly your #3577 planned vs executed parameters, still unimplemented upstream), or to carry a projection-version task parameter and let a multi-task model decide how much round 1 transfers. My preference is the former, since we already record as-printed geometry.

Checklist for when we pick this up

Nothing committed this round: this is a note for later, so I only commented. Say the word if you want the checklist parked in bo/README.md or opened as a tracking issue so it does not live only in this thread. Still outstanding from earlier: the Edison verification of the objectives claim was submitted (task 3e398131) but never fetched, and amdjwm still has no spec mapping.
Branch

@sgbaird

sgbaird commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator Author

@claude I just made the following:

image

Update your code to mimic this, since we'll need a later figure that shows what the new Pareto front is after running the next batch, and showing what the predicted vs. actual was (we'll have the orange diamonds shift to the actual measured in a linear path and convert themselves to unfilled black circles), which I'd like you to prototype briefly (noting this is dummy data! We haven't gotten those new measurements yet)

@claude

claude Bot commented Aug 21, 2026

Copy link
Copy Markdown

Claude finished @sgbaird's task in 7m 31s —— View job


Figure restyled to your version, plus a round-2 prototype ✅

  • Fetch and study your uploaded figure
  • Read the current plotting code
  • Restyle to match yours
  • Prototype the round-2 predicted-vs-measured figure (dummy data)
  • Regenerate, inspect, commit, push (bd7be73)

The restyled round-1 figure

Full size: bo/figures/t3-prism-bo-round1-pareto.png (11 x 7 in at 200 dpi).

What I copied from yours:

Element Before Now
Series names legend box outside the axes leader-line callouts in the plot area, colored to the series they name
Front points blue line, blue markers blue fill with black outline, matching the open black circles
Suggested points orange diamonds with white edge plain orange diamonds
Spines box corner at the origin left and bottom only, pushed 14 pt outward, ending at the outermost tick
Type 15 to 17 pt 20 to 22 pt, humanist sans preferred (Source Sans, then Open Sans, Lato, Helvetica, and so on down to DejaVu)

One thing I did differently from a literal copy. Your figure has hand-placed ID labels; I put a greedy declutterer in (t3_prism_bo_campaign.py:318) that tries ten offsets per label and takes the first clear of every other label and marker. Hand-tuned offsets would break the moment the point cloud moves, which it will every round, and moving points is the whole reason you want this figure again later.

Round-2 prototype: predictions travel to measurements

Dummy data. Nothing in round 2 has been printed or dropped. The "measured" points are draws from the model's own posterior for the suggested designs, at 0.3 sd (a full-sd draw scatters the batch clean off the panel, which would make the prototype about the noise rather than the layout).

Full size: bo/figures/t3-prism-bo-round2-predicted-vs-actual-PROTOTYPE.png

The grammar, exactly as you described it:

  • Each orange diamond fades to 38% opacity (it is a prediction that has been superseded) and a straight arrow runs from it to where that article actually landed.
  • The landing point is an open black circle, identical to every other tested article, so a round-2 specimen stops looking special once it has been measured.
  • The front is recomputed over both rounds, and the round-1 front stays dashed and pale underneath, so the improvement is the thing your eye lands on.
  • Round-2 articles carry r2-<trial> labels until they have real print IDs.

Draw it with:

python bo/t3_prism_bo_campaign.py --prototype-next-round

When the real numbers land, synthesize_round2_outcomes is the one function to replace: point it at the round-2 campaign summary and everything downstream is unchanged. The dummy outcomes it currently produces are written to t3-prism-bo-round2-outcomes-PROTOTYPE-dummy.csv so the figure is reproducible and the file name says what it is.

Two honest limits of the prototype, both from the dummy data rather than the code. Six of nine synthetic points land on the new front, which is optimistic: a real round rarely sweeps the front like that, and the callouts sit where they do because of where these fake points fell. And r2-11 and r2-15 happen to land on top of each other, so the declutterer stacks their labels; with real data spread that resolves itself, and if it does not I will add a small repulsion pass.

Also updated: bo/README.md documents both figures and flags the prototype as synthetic.

Nothing else moved: same round-1 data, same round-2 suggestions, same objectives. Still outstanding from earlier in this thread: the Edison verification of the objectives claim (task 3e398131) was submitted but never fetched, and amdjwm still has no spec mapping.
Branch

claude Bot pushed a commit that referenced this pull request Aug 21, 2026
…er analogue, sim-vs-measured correlation study, repeat-seed BO campaign

- print_infill.py: sub-100% PLA infill (0.565 solid fraction, refit 0.556 from
  the committed CSVs), effective strut density/modulus (Gibson-Ashby bracket),
  CAD-vs-model geometry factors, and the PR #35 constant-mass projection.
  Predicted as-printed mass lands within 0.7 g of the scale readings.
- drop_tower_sim.py: MuJoCo analogue of the 60 in / PU-mat drop in the bench's
  own objectives (CFC-180 transmissibility t180, rebound energy e_reb_mJ), with
  the mat calibrated to the measured input pulse (208.4 G / 4.08 ms).
- pr102_correlation.py: every simulated observable in this directory vs the
  seven mapped tested articles. Volumetric SEA tracks measured t180 at
  rho = -0.93; the purpose-built t180 analogue only manages +0.46; nothing
  predicts e_reb_mJ yet.
- pr102_sim_campaign.py: closed-loop simulation-only analogue of PR #102's
  SAASBO campaign, one run per seed, with cross-seed mean +/- 1 sd figures.
- workflows-staged/sim-bo-pr102-matrix.yml: parallel seed matrix for Actions
  (staged outside .github/workflows, which this app cannot write to).

Co-authored-by: Sterling G. Baird <45469701+sgbaird@users.noreply.github.com>
@sgbaird

sgbaird commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator Author

@claude also do some Ax visualization to help with interpretability. Feature importance plot (inverse lengthscales), LOOCV plot, you might have to look at old Ax docs (0.5.0) and also Honegumi for some of these, also maybe Ax GitHub issues, and maybe if there's some kind of plot you could do also that would show the contributions, positive or negative, of each parameter (i.e., things like "in general, increasing x1 leads to a net positive effect on y1")

@claude

claude Bot commented Aug 21, 2026

Copy link
Copy Markdown

Claude finished @sgbaird's task in 23m 7s —— View job


Ax interpretability diagnostics: feature importance, LOOCV, signed parameter effects ✅

  • Read the campaign script; rebased onto the two figure commits that landed while this ran (bd7be73, b98be38)
  • Install Ax 0.5.0 + BoTorch 0.13 + Honegumi 0.4.3; introspected the installed package rather than trusting docs (the 0.5.0 docs URL 404s, and web search on these API names returns scikit-learn)
  • Feature importance from SAAS inverse lengthscales
  • Leave-one-out cross-validation
  • Signed parameter-effect plots (partial dependence curves + net-effect tornado)
  • Run, inspect all four figures, commit + push (02f5266)

New file: bo/t3_prism_bo_diagnostics.py. It refits the round-1 SAASBO model from the committed AxClient snapshot, so it describes the same data and model class that produced the committed round-2 suggestions. About 6 min for a full run; --skip-cv drops the expensive part and --plot-only redraws everything from the recorded CSVs in ~1 s with only pandas and matplotlib.

python bo/t3_prism_bo_diagnostics.py             # all four figures
python bo/t3_prism_bo_diagnostics.py --plot-only # restyle, no refit

Where each came from: Honegumi's visualize=True block is the Pareto scatter and nothing else (checked the installed main.py.jinja), so 1 and 2 are Ax 0.5.0's own API and 3 is built on the model posterior because Ax has no continuous-space equivalent.


1. Feature importance (inverse lengthscales)

TorchModelBridge.feature_importances(metric) takes the median ARD lengthscale over the SAAS MCMC draws, inverts it, and normalizes the five parameters to sum to 1 (ax/models/torch/botorch.py:546). Ax's MBM_X_trans maps the space onto the unit cube before fitting, so those numbers are comparable across parameters and read as share of the model's sensitivity. The whiskers are the interquartile range across the individual draws, which the single-number API throws away; I added them because at this sample size they are the finding.

parameter t180 rebound (mJ) mass (g)
R 0.167 0.210 0.237
H 0.292 0.225 0.149
twist 0.163 0.192 0.270
strut Ø 0.254 0.158 0.194
cable Ø 0.124 0.215 0.150

The honest reading: SAAS has not identified a sparse subspace. Every value sits between 0.12 and 0.29 against an equal share of 0.20, and every interquartile band except cable Ø on t180 straddles that line. The sparsity prior needs data to shrink dimensions with, and 7 points in 5-D is not enough. The one thing you can take from it is the t180 ordering, H and strut Ø on top and cable Ø at the bottom, which is at least consistent with the effects below. The rebound panel is flat: the model does not think any single coordinate governs it.

2. Leave-one-out cross-validation

cross_validate(model, folds=-1) then compute_diagnostics, full table in t3-prism-bo-round1-loocv-diagnostics.json.

metric MAPE r rank corr
t180 3.0% 0.57 0.64
e_reb_mJ 34.9% -0.17 -0.14
mass_g 4.5% 0.23 0.25

This is the result worth arguing about. The model has real (if weak) out-of-sample skill on t180, and none at all on e_reb_mJ: negative rank correlation means held-out ordering is no better than chance, and it misses both high-rebound articles (6lhxfy, 6nheas) badly low. Since e_reb_mJ is half the optimization, qNEHVI is currently trading off against a dimension it cannot predict. That does not invalidate the round-2 batch (a GP with no signal mostly reverts to the prior and explores, which is not a bad thing on round 2 of 9 points), but it does mean the predicted rebound column in the suggestions CSV should not be read as a forecast, and it is the strongest argument yet for the round-3 conversation about whether rebound belongs as an objective, a constraint, or a tracking metric. Mass predicts about as well as its own print scatter, which is roughly the ceiling.

One gotcha found on the way in, and fixed: BoTorchModel defaults refit_on_cv=False in Ax 0.5.0 (model.py:95). Out of the box, a CV fold reconditions on the held-out training set while keeping hyperparameters that were fitted on all the data, which leaks the held-out point into the kernel. That version ran in under a second and reported better numbers. The script passes refit_on_cv=True and pays for a genuine NUTS rerun per fold, which is essentially the whole 6 min.

3. Signed parameter effects

This is the "increasing x1 has a net positive effect on y1" plot. Ax 0.5.0 has nothing for it on a continuous space: ax.plot.marginal_effects is factorial-only, and plot_slice pins the other four parameters at one arbitrary point, which in 5-D with 7 observations gives you a different answer depending on where you pin them. So the script computes model-based partial dependence: sweep one parameter across its range while averaging the posterior mean over quasi-random draws of the other four.

Read the two together: the tornado compresses each curve to one number, and a curve that turns mid-range gets an asterisk so you go look at it. Both objectives are minimized, so on the tornado a blue (leftward) bar is an improvement.

  • Strut Ø is the free lunch and explains the batch. Raising it 6 to 12 mm lowers t180 by 0.099, the biggest single move on the panel, at a rebound cost of only +1.8 mJ. That is why 7 of 9 round-2 suggestions pin strut Ø at the 12 mm bound. It is a model statement, not a manufacturability one.
  • H and twist are where the trade-off actually lives. Taller lowers rebound energy by 3.8 mJ but raises t180 by 0.089; more twist lowers t180 by 0.036 but raises rebound by 2.7 mJ. The Pareto front is not an artifact of the mass normalization, it is these two coordinates pulling opposite ways.
  • R only matters to rebound (+3.6 mJ across its range, one of the two largest effects there) and barely touches t180.
  • On mass, twist is the dominant lever (-1.6 g over its range, more than strut Ø at -1.2 g). That is directly relevant to the constant-intended-mass plan from your earlier note: if the projection is going to target grams, twist is the coordinate whose mass coupling has to be modeled correctly, and it is not the one you would have guessed.

Note the apparent tension with panel 1: importance ranks H above strut Ø for t180 while the tornado ranks strut Ø first. Not a contradiction. An inverse lengthscale measures how fast the function wiggles in that direction, a net effect measures end-to-end signed change, and they only agree for a monotonic, evenly-sloped parameter. H's t180 curve is the steeper one but concentrated in its upper half.

Caveat that governs all of the above: the shaded bands are ±1 posterior sd, and most curves move by less than their own band. Directions and rankings, not decimals.


Committed (02f5266)

File What
bo/t3_prism_bo_diagnostics.py the script
bo/figures/t3-prism-bo-round1-feature-importance.png + .csv with the per-draw quartiles
bo/figures/t3-prism-bo-round1-loocv.png + loocv.csv, loocv-diagnostics.json
bo/figures/t3-prism-bo-round1-parameter-effects.png + partial-dependence.csv
bo/figures/t3-prism-bo-round1-parameter-net-effects.png + parameter-net-effects.csv
bo/README.md new "Model interpretability" section with the API provenance and both gotchas
.gitignore __pycache__/

Not done: the round-2 suggestions are untouched (this was diagnostics, not a re-run), and I did not act on what the LOOCV found about e_reb_mJ since changing an objective is your call. Still outstanding from earlier threads: the Edison verification of the objectives claim was submitted (task 3e398131) and never fetched, and amdjwm still has no spec mapping so it stays out of the fit.
Branch

…dicted-vs-measured view

Restyles the objective-space panel to the reference posted on PR #102:
leader-line callouts instead of a legend box, detached left/bottom spines,
open black circles for tested articles with blue-filled front points,
larger type, greedy label decluttering in place of hand-tuned offsets.

Adds --prototype-next-round, which draws the post-round-2 layout: each
predicted diamond joined by a straight path to where the article actually
landed (drawn as an open circle), the front recomputed over both rounds,
and the round-1 front left dashed underneath. Round-2 outcomes are
SYNTHETIC placeholders; synthesize_round2_outcomes is the single function
to swap for the measured summary.

Co-authored-by: Sterling G. Baird <45469701+sgbaird@users.noreply.github.com>
@sgbaird

sgbaird commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator Author

@claude the dummy data plot in #102 (comment) was meant to be a short mp4 or GIF

@claude

claude Bot commented Aug 21, 2026

Copy link
Copy Markdown

Claude finished @sgbaird's task in 7m 31s —— View job


Round-2 prototype, animated ✅

  • Read the current prototype plotting code
  • Check the runner's animation writers (installed ffmpeg; matplotlib 3.11)
  • Animate the predicted-to-measured grammar
  • Render GIF + MP4, inspect frames, fix layout and callout collisions
  • Commit + push (b98be38)

Still synthetic. Nothing in round 2 has been printed or dropped; the landing points are the same 0.3-sd draws from the model's own posterior as the still figure used.

GIF (683 KB, 980 px, 12 fps, loops) and MP4 (206 KB, 1100 x 700, 25 fps, h.264 yuv420p so it plays in PowerPoint and Keynote as well as a browser). Drop the MP4 on a slide; the GIF is for the thread and the README.

How it plays, 7.5 s

Phase Duration What happens
Hold 1.2 s Round-1 articles, round-1 front, the nine orange diamonds. Reads exactly like the round-1 slide.
Travel 2.6 s Each diamond eases along a straight path to its measurement, staggered so it reads as nine articles rather than one swarm. A trail grows behind it, and over the last 40% of its own path the diamond fades out while the open black circle fades in, so a measured article stops looking special. The arrowhead only lands once travel is over, so nothing overshoots.
Front 1.1 s Front recomputed over both rounds, drawn in. The round-1 front drops to a pale dashed line, round-1 points that lost the front lose their blue fill, the new r2-* IDs fade in, and the callouts cross-fade from "Suggested points" to "Predicted / Measured".
Hold 2.6 s Final state, which is pixel-for-pixel the layout of the still PNG, so the two artifacts agree and the loop restart is not jarring.

Faded diamonds stay at the predicted positions throughout, so predicted-versus-measured is still readable in a freeze frame.

Code

One new function, render_prediction_animation, in bo/t3_prism_bo_campaign.py. It runs off the same command as before, and takes about 25 s:

python bo/t3_prism_bo_campaign.py --prototype-next-round              # still + GIF + MP4
python bo/t3_prism_bo_campaign.py --prototype-next-round --no-animation  # still only

Three things worth knowing for when the real numbers land. synthesize_round2_outcomes is still the only function to replace: the animation reads the same frames the still does. Labels are laid out once against the final frame rather than per frame, so nothing shuffles mid-clip. And the GIF is built from the MP4 through an ffmpeg palette pass rather than frame-by-frame from matplotlib, which is what keeps it under a megabyte; without ffmpeg on PATH the script falls back to a Pillow GIF and skips the MP4.

I also moved two callouts to fixed positions in the animated version ("Existing data" and "Round-1 front"), because the auto-placed ones sat on top of the bpx68c label in the opening frame. The still PNG is unchanged.

One cosmetic limit, from the dummy data rather than the code: r2-10's ID label lands partly under a marker because two synthetic points nearly coincide. Real spread should resolve it; if it does not, the declutterer needs a repulsion pass.

Also updated: bo/README.md.

Still outstanding from earlier in this thread, unchanged: the Ax interpretability plots (feature importances, LOOCV, parameter effects) from your previous comment, the Edison verification of the objectives claim (task 3e398131, submitted but never fetched), and amdjwm's missing spec mapping.
Branch

claude Bot pushed a commit that referenced this pull request Aug 21, 2026
…ions

Generate the first model-based batch (PR #102 comment 5365706779, Ax trials
10-18) as a printable H2D IDEX project. bo/t3_prism_sobol_batch.py grows
--designs-csv (read the five swept columns from any design table, e.g. a
t3-prism-bo-suggestions-roundN.csv emitted by the campaign script) and
--out-prefix (so a round lands beside the pinned Sobol batch instead of
overwriting it). Each row's Ax trial_index is carried into the batch table
as source_trial.

Artifacts: bo/t3-prism-bo-round1.{csv,json,scad,stl}, -struts/-cables STLs,
plate/iso previews, 9 per-specimen STL pairs, and
bo/slices/t3-prism-bo-round1.H2D-MM-PLAstruts-TPUcables.3mf (9 objects, each
with a PLA struts part on extruder 1 and a TPU cables part on extruder 2,
filament_map_mode=Manual, supports off for manual painting).

All 9 hold m* = 30.95 g within tolerance and clear the 250 cm^3 envelope cap;
specimens 00/02/05 (trials 10/12/15) drop to 2.6-2.7 mm as-printed cable
diameter and are flagged cable_bridge_ok=False.

Co-authored-by: Sterling G. Baird <45469701+sgbaird@users.noreply.github.com>
@sgbaird

sgbaird commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator Author

@claude #102 (comment) this looks good, but you need to hide the original items afterwards and show just the final figure

@claude

claude Bot commented Aug 21, 2026

Copy link
Copy Markdown

Claude finished @sgbaird's task in 5m 49s —— View job


Animation now clears its own scaffolding ✅

  • Read render_prediction_animation and the still-figure code
  • Add a clean-up phase that fades the prediction layer out
  • Render a matching still for the resting state
  • Re-render GIF + MP4, inspect the last frame
  • Commit + push (1bc10c8)

Still synthetic, same 0.3-sd draws as before. GIF (826 KB) and MP4 (257 KB), now 9.7 s instead of 7.5 s.

What happens at the end

Phase Duration What happens
Hold 1.2 s round-1 articles, round-1 front, nine orange diamonds
Travel 2.6 s diamonds ease to their measurements, handing off to open black circles
Front 1.1 s front recomputed over both rounds, round-1 front drops to pale dashed
Hold 1.5 s read predicted versus measured
Clean 0.9 s diamonds, travel paths, arrowheads and the round-1 front fade out, and the callouts that named them ("Predicted", "Measured", "Round-1 front", "Existing data") go with them
Hold 2.4 s the round-2 figure alone: one front, one set of articles

Everything the clean-up removes exists only to explain how the batch got there, so it all leaves on one shared fade rather than in stages. "Existing data (round 1)" goes too: once round 2 is measured, every point on the panel is existing data.

The resting frame, as a committed still

New file bo/figures/t3-prism-bo-round2-front-final-PROTOTYPE.png. render_prediction_vs_actual_figure gained show_predictions=False (t3_prism_bo_campaign.py:658) rather than a second plotting function, so the animation's last frame and this PNG are the same drawing code and cannot drift apart. The same run writes both, plus the predicted-vs-measured still:

python bo/t3_prism_bo_campaign.py --prototype-next-round

One fix that fell out of this: with the diamonds gone, the "Pareto front after round 2" callout was sitting on the front line and striking through the r2-14 label. It now anchors into the empty left-middle of the panel.

Also updated: bo/README.md.

Nothing else moved: same round-2 suggestions, same objectives, same round-1 figure. Still outstanding from earlier in this thread: Edison task 3e398131 (objectives review) was submitted but never fetched, amdjwm has no spec mapping, and PR #35's generator still solves for solid mass rather than printed grams.
Branch

The clip now ends by clearing its own scaffolding. After a beat on
predicted-versus-measured, the orange diamonds, travel paths, arrowheads,
round-1 front and the callouts that named them fade out together, so the
last ~2.4 s is the round-2 Pareto figure alone.

render_prediction_vs_actual_figure gains show_predictions=False, which
draws that resting state as a still (t3-prism-bo-round2-front-final-
PROTOTYPE.png), so the animation's last frame and a committed PNG agree.
Also moved the 'Pareto front after round N' callout off the front line,
where it collided with the r2-14 label once the diamonds were gone.

Co-authored-by: Sterling G. Baird <45469701+sgbaird@users.noreply.github.com>
@sgbaird

sgbaird commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator Author

@claude there's a lot of text, and overlapping text as well. It's effective, but just ever too much overlapping at once. For example, redrawing the Pareto front should be it's own step after the points shift over and turn into black dots, and maybe the original Pareto front should be hidden before shifting the points

@claude

claude Bot commented Aug 21, 2026

Copy link
Copy Markdown

Claude finished @sgbaird's task in 15m 7s —— View job


Re-choreographed: one idea per beat, and the text thinned out ✅

  • Read render_prediction_animation and the callout/label layout code
  • Restructure the timeline: retire the round-1 front before the points travel, redraw the new front as its own step after they have all landed and turned black
  • Thin the on-screen text so at most one callout is live while anything is moving
  • Fix the label placer so IDs dodge callouts, leaders, the front line and the travel arrows
  • Re-render and inspect every phase boundary
  • Commit + push (011d1fa)

Still synthetic, same 0.3-sd draws. GIF (663 KB) and MP4 (213 KB), 11 s.

The new order

# Beat s What happens, and nothing else
1 Hold 1.3 the round-1 figure exactly as the slide already shows it
2 Retire 0.9 the round-1 front and its blue point fills fade out, taking "Pareto front" and "Existing data" with them. Nothing moves.
3 Travel 2.6 the diamonds ease to their measurements and hand off to open black circles, on a panel with no front to read across
4 Hold 1.7 predicted versus measured, named by one labeled pair
5 Clean 0.9 the prediction layer and those two callouts fade out, leaving black circles only
6 Front 1.3 the new front is redrawn as its own step, wiping in along the polyline and filling each article as the line reaches it
7 Hold 2.4 the round-2 figure alone

Both of your suggestions, and they turned out to depend on each other: once the round-1 front leaves in beat 2, there is no dashed remnant left to compete with the new front in beat 6, so the "Round-1 front" callout could go entirely. Text budget is now three callouts in the opening frame (which is just the round-1 slide), at most one while anything is in motion, and one at rest.

The overlapping-text fix

That was a real bug, not just crowding. The label placer tried ten offsets and, if none was clear, silently took whichever happened to be last in the list. It also only knew about markers and other labels, so a series name or its leader line was invisible to it. Now (t3_prism_bo_campaign.py:520) it scores every candidate against the callout text, the callout leaders, the front polyline and the travel arrows, and takes the least-weighted-overlap one, with words weighted well above hairlines so a label would rather cross a thin line than sit on top of text. Nothing is hand-positioned; the point cloud moves every round.

The round-2 front callout is anchored below-left of the front, which is a property of a Pareto front (nothing can dominate into that region) rather than a hand-placement that breaks with new data.

The two stills are now the two rest points

Instead of one still that stacked the travel layer and the front on top of each other, --prototype-next-round writes the beat-4 frame and the beat-7 frame:

Beat 4: predicted vs measured Beat 7: the new front

render_prediction_vs_actual_figure takes stage="travel" or stage="front" in place of the old show_predictions flag. Same command:

python bo/t3_prism_bo_campaign.py --prototype-next-round

The round-1 figure got the label fix too (t3-prism-bo-round1-pareto.png, nvxsrv no longer sits across the front line), as did the LOOCV panel, which shares the same placer.

Nothing else moved: same data, same round-2 suggestions, same objectives. bo/README.md documents the beat list. Still outstanding from earlier in this thread: Edison task 3e398131 (objectives review) was submitted but never fetched, amdjwm has no spec mapping, and PR #35's generator still solves for solid mass rather than printed grams.
Branch

The round-2 prototype clip had too much moving and too much text on screen
at once (PR #102 review). Re-choreographed so each beat carries one idea:
the round-1 front is retired before anything moves, the diamonds then travel
on an otherwise clean panel, and the new front is redrawn as its own step,
after every article has landed and turned into an open black circle, wiping
in along the polyline and filling each article as it reaches it.

Text is thinned to match: the Round-1 front callout is gone, at most one
callout is lit while anything is in motion, and point labels now dodge the
callout text, the callout leaders, the front line and the travel arrows as
well as the markers, choosing least-weighted-overlap rather than falling
back to whichever candidate offset happened to be last.

The two stills become the two rest points of the same story
(stage="travel" and stage="front") instead of one still with the front
layered on top of the travel layer.

Co-authored-by: Sterling G. Baird <45469701+sgbaird@users.noreply.github.com>
claude Bot pushed a commit that referenced this pull request Aug 21, 2026
…ol seed

The repeats shared round 0: every seed attached the nine physically printed
articles as completed trials, so the seed reached nothing but the
surrogate's own randomness and the seeds agreed to under 2 percent.

--init sobol (now the default) draws each repeat's own nine-point round 0
from that repeat's seed; --init printed keeps the PR #102-exact behaviour.
The hypervolume reference point moves off the seed's own round 0 onto a
fixed one derived from the printed articles, so a repeat that draws a bad
initial batch is not handed a generous reference point for it.

--jobs runs repeats concurrently, one process per seed, with the numeric
libraries pinned to one thread each.

Co-authored-by: Sterling G. Baird <45469701+sgbaird@users.noreply.github.com>
claude Bot pushed a commit that referenced this pull request Aug 21, 2026
Four batches of 9 per repeat, the same per-seed budget as the earlier
three-seed run, ten repeats in 1457 s on four cores.

The independent draws start far apart and finish close: hypervolume after
round 0 spans 13.8 percent of its mean, and 17.50 +/- 0.26 (1.51 percent)
after four batches.  All ten corner R, H, twist and cable_d identically and
leave strut_d loose across the whole box.

Co-authored-by: Sterling G. Baird <45469701+sgbaird@users.noreply.github.com>
@sgbaird

sgbaird commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator Author

Noting, VERY IMPORTANTLY, that the batch we ran in #35 is #102 (comment) (without the "real" constant mass constraint applied)

@sgbaird

sgbaird commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator Author

@claude #102 (comment) is good, but I think it would be good to hide all of the IDs prior to continuing the animation (8 IDs at first isn't overwhelming, but with so much more text after, it does get overwhelming)

@claude

claude Bot commented Aug 21, 2026

Copy link
Copy Markdown

Claude finished @sgbaird's task in 4m 47s —— View job


IDs retired with the round-1 front, back only at rest ✅

  • Read the animation timeline and the label-placement code
  • Fade every print ID out in beat 2, alongside the round-1 front and its callouts
  • Bring all IDs back in one beat, after the new front is drawn
  • Re-render GIF + MP4 + both stills, inspect every phase boundary
  • Update bo/README.md, commit + push (be4382a)

Still synthetic, same 0.3-sd draws. GIF (705 KB) and MP4 (243 KB), still 11 s: the change is what is on screen, not how long anything takes.

Where the IDs live now

# Beat s IDs on screen
1 Hold, the round-1 figure 1.3 the 8 round-1 IDs
2 Retire 0.9 all 8 fade out with the front, its blue fills and both callouts. Nothing moves.
3 Travel 2.6 none
4 Hold, predicted versus measured 1.7 none
5 Clean 0.9 none
6 Front redrawn 1.3 none
7 Rest 2.4 all 17 fade in together, once nothing is moving

The rule the code now follows is simply that nothing is labeled while anything is in motion. That also removes the thing that made the middle busiest: the round-2 IDs used to arrive one at a time as each article landed, so nine labels were appearing during the same beat the diamonds were traveling and the round-1 IDs were still sitting there.

Beat 2, everything retired at once:

One consequence worth confirming

The predicted-versus-measured still lost its IDs too, because it is the beat-4 frame and the two are supposed to register. That still is about the arrows, and it was carrying seventeen labels across nine travel paths, which was the most crowded artifact in the set. Identity comes back on the resting still, t3-prism-bo-round2-front-final-PROTOTYPE.png, which is unchanged:

Say the word if you want the travel still to keep its IDs even though the animation frame it matches does not.

Code

Same command, no new flags:

python bo/t3_prism_bo_campaign.py --prototype-next-round

In t3_prism_bo_campaign.py the labels split into a round-1 and a round-2 group; round-1 alpha is max(1 - retire, rest) and round-2 alpha is rest, where rest only starts ticking after the front wipe finishes. Placement is still solved once, against the final frame, so nothing shuffles mid-clip. Since no label is ever lit while a travel path is on the panel, the arrows are no longer obstacles for the placer; the suggestion diamonds are, via a new _marker_boxes helper, because they sit under the round-1 IDs in the opening frame.

Nothing else moved: same data, same round-2 suggestions, same objectives, same round-1 figure. Still outstanding from earlier in this thread: Edison task 3e398131 (objectives review) was submitted but never fetched, amdjwm has no spec mapping, and PR #35's generator still solves for solid mass rather than printed grams.
Branch

@sgbaird

sgbaird commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator Author

Also noting that instead of "lower is better", using an '"arrow pointing down" is better' might work out better

…ck at rest

Eight IDs on the opening frame is fine; carrying them through the travel
while nine round-2 IDs arrive on top of them is not (PR #102 review). The
IDs now leave in beat 2 with the round-1 front and its callouts, nothing
is labeled while anything is moving, and every ID fades back in once the
figure has come to rest in beat 7. The travel still drops its IDs to
match the beat it holds.

Co-authored-by: Sterling G. Baird <45469701+sgbaird@users.noreply.github.com>
@sgbaird

sgbaird commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator Author

@claude nice, but I need at higher res and I also need an identically sized image of the first frame and an identically sized image of the second frame so that I can line them up on three consecutive powerpoint slides and transition from one to the next

@claude

claude Bot commented Aug 21, 2026

Copy link
Copy Markdown

Claude finished @sgbaird's task in 6m 52s —— View job


Three registered stills at 2200 x 1400, and the video to match ✅

  • Read the current animation and still-rendering code
  • Raise the render resolution (MP4 + GIF + stills)
  • Export the opening frame and the predicted-vs-measured frame as stills at the exact video pixel size
  • Verify all three are pixel-identical in size and register on top of each other
  • Update bo/README.md, commit + push (cdff1e4)

Everything is 2200 x 1400 px now, stills and video alike (was 1100 x 700). Same 11 x 7 in canvas at 200 dpi instead of 100, so nothing about the layout changed, there is just twice the linear resolution. At 11 in wide that is 200 dpi on a full-bleed slide.

The three slides, in order

Slide 1 Slide 2 Slide 3
-start-PROTOTYPE.png -predicted-vs-actual-PROTOTYPE.png -front-final-PROTOTYPE.png
What it holds round-1 front, its IDs, the orange suggestions predicted joined to measured, no front, no IDs the front over both rounds, every ID back
Animation beat 1 4 7

Slide 1 is new. The other two file names are unchanged, so anything already pointing at them still works.

Why they line up

They are frames of the same figure, not three drawings. --prototype-next-round builds the animation figure once, calls the update function at frames 0, t_clean - 1 and n_frames - 1, and saves each with an explicit dpi and no bbox_inches="tight" (which is what made the old stills different sizes: it crops to the ink, and the ink changes every beat).

Checked rather than assumed: the axis furniture is byte-for-byte identical across the three PNGs.

x-axis spine + ticks + label : max abs pixel diff, 1v2 = 0, 1v3 = 0
y-axis spine + ticks         : max abs pixel diff, 1v2 = 0, 1v3 = 0

Every print ID that appears on both slide 1 and slide 3 (6lhxfy, 6nheas, bpx68c, nvxsrv, autv5r, 9hhbkp, bag26v) is at the same pixel in both, because label placement is solved once against the final frame for the whole set. So a Morph or a cross-fade between consecutive slides moves only the things that are meant to move.

One thing to know before you place them

Slide 1 is not the same panel as t3-prism-bo-round1-pareto.png. Its y-axis runs to 4 rather than 6, because the axis limits have to cover where the round-2 articles land or the points would walk off the panel in slide 2. Registration across the three is the whole point, so the limits are shared. If you also want the standalone round-1 slide ahead of these three, expect the axis to jump at that one transition; using slide 1 as the round-1 slide avoids it entirely.

Also worth flagging: the nvxsrv label sits close to the round-1 front line on slide 1. The placer solves against the final frame, where that line does not exist, and it takes the least-overlapping option available. Hand-moving it would break the registration that makes the sequence work, so I left it.

The animation

Same 11 s and the same beats, now 2200 x 1400 h.264 at 25 fps, crf 18 instead of 20: MP4 (563 KB). Its last frame is slide 3 and its opening frame is slide 1, so the clip and the three slides are interchangeable: use the clip if you want it to play, the three slides if you want to click through it.

The GIF went from 980 to 1280 px wide (1.06 MB), but it is still for the thread and the README. Put the MP4 on the slide.

Code

Same command, ~1 min:

python bo/t3_prism_bo_campaign.py --prototype-next-round                 # 3 stills + MP4 + GIF
python bo/t3_prism_bo_campaign.py --prototype-next-round --no-animation  # 3 stills, ~4 s

render_prediction_vs_actual_figure is gone and render_prediction_animation is now render_round2_prototype, returning (stills, gif, mp4). That removed ~190 lines that drew the same two panels a second way, and with them the possibility of the stills and the video drifting apart. --no-animation now means stills-only rather than one-still-only.

Still synthetic, unchanged: the round-2 landing points are 0.3-sd draws from the model's own posterior, and synthesize_round2_outcomes is the single function to replace when the real numbers land.

Not done: the standalone round-1 figure is still 200 dpi with a tight bounding box (say the word if you want it on the shared canvas too), and, still open from earlier in this thread, Edison task 3e398131 was submitted but never fetched, amdjwm has no spec mapping, and PR #35's generator still solves for solid mass rather than printed grams. Your note about swapping "lower is better" for a down-arrow glyph is not in this commit either; tell me if you want it and I will sweep both axis labels.
Branch

…ive slides

The three rest points of the round-2 story are now exported as frames of
the animation figure rather than drawn by a second plotting function, so
they are identical in pixel size and every element that survives a beat
sits at the same pixel in all three. That is what lets them go on three
consecutive PowerPoint slides and be transitioned between.

- ANIM_DPI 100 -> 200: the MP4 and the stills are 2200 x 1400.
- New still: -start-PROTOTYPE.png, the round-1 figure on the round-2
  canvas, so slide 1 registers with slides 2 and 3.
- render_prediction_vs_actual_figure removed; render_prediction_animation
  becomes render_round2_prototype and returns (stills, gif, mp4).
  --no-animation now means stills only, not still-only.
- GIF widened to 1280 px with a 128-colour palette; MP4 crf 20 -> 18.

Co-authored-by: Sterling G. Baird <45469701+sgbaird@users.noreply.github.com>
@sgbaird

sgbaird commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator Author

@claude needs to be 300 dpi. See also #102 (comment)

@claude

claude Bot commented Aug 21, 2026

Copy link
Copy Markdown

Claude finished @sgbaird's task in 6m 17s —— View job


300 dpi throughout, and the down-arrow glyph ✅

  • Read the figure-rendering code in bo/t3_prism_bo_campaign.py and bo/t3_prism_bo_diagnostics.py
  • Raise every figure to 300 dpi (stills, video, GIF, diagnostics)
  • Replace "lower is better" with a down-arrow glyph on both axis labels, and sweep the repo for the phrase
  • Re-render, inspect, verify the three stills still register pixel-for-pixel
  • Commit + push (25997b9)

The three registered stills, now 3300 x 2100

Slide 1 Slide 2 Slide 3
-start-PROTOTYPE.png -predicted-vs-actual-PROTOTYPE.png -front-final-PROTOTYPE.png

Same 11 x 7 in canvas, now at 300 dpi instead of 200. Layout did not move at all, because matplotlib sizes type and line weights in points: only the pixel count changed, 2200 x 1400 to 3300 x 2100. The MP4 went with them (3300 x 2100 h.264, 827 KB, still 11 s and the same beats), so the clip and the three slides are still interchangeable.

Two things checked rather than assumed:

  • They still register. Axis furniture is byte-for-byte identical across the three PNGs (max abs pixel diff 0 for both the x-axis and y-axis bands, 1v2 and 1v3), so Morph or a cross-fade still moves only what is meant to move.
  • The dpi is written into the file, not just implied by the pixel count. Every PNG now carries a pHYs chunk of 11811 px/m, so PowerPoint and Word place it at 11 x 7 in rather than guessing 96 dpi and dropping in a 34-inch-wide image.

The down-arrow glyph

From your other note. Both objective axes now read:

  • Shock transmissibility t180 (↓ is better)
  • Rebound energy to payload (mJ per drop, ↓ is better)

Set once as ARROW_DOWN at t3_prism_bo_campaign.py:413 so both labels and every figure that inherits them stay in step. I checked glyph coverage rather than trusting it: U+2193 is in Lato, Liberation Sans and DejaVu Sans on this runner, and matplotlib falls through the font.sans-serif list per character, so a machine whose preferred face lacks it gets the arrow from the next one rather than a tofu box.

The tornado caption in the diagnostics said "left is better" for the same reason, so it got ← by the same argument. That was the only other instance in the repo; the grep is clean.

Diagnostics figures came along

They were the stragglers at 200 dpi (and 180 for the parameter-sweep grid), so all four are re-rendered from the committed CSVs at 300:

Figure Now
feature importance 3718 x 1966, 277 KB
LOOCV 3759 x 2123, 377 KB
parameter effects 6180 x 3271, 667 KB
net effects 4122 x 2023, 359 KB
round-1 Pareto 2817 x 2348, 288 KB

It is one number now: FIGURE_DPI = 300 in t3_prism_bo_campaign.py, imported by the diagnostics, so a future change is one edit rather than seven.

Redraw

No model refit needed for any of this, ~1 min for the whole set:

python bo/t3_prism_bo_campaign.py --plot-only              # round-1 panel
python bo/t3_prism_bo_campaign.py --prototype-next-round   # 3 stills + MP4 + GIF
python bo/t3_prism_bo_diagnostics.py --plot-only           # the four diagnostics

The GIF is deliberately still 1280 px (951 KB): it is for this thread and the README. Put the MP4 on the slide.

Still synthetic, unchanged: the round-2 landing points are the same 0.3-sd draws from the model's own posterior. Nothing about the data, the objectives or the round-2 suggestions moved in this commit.

Not done, all carried from earlier in this thread: Edison task 3e398131 (objectives review) was submitted but never fetched, amdjwm has no spec mapping so it stays out of the fit, and PR #35's generator still solves for solid mass rather than printed grams.
Branch

claude Bot pushed a commit that referenced this pull request Aug 21, 2026
…nce front

The PR #102 simulation-only campaign reported a hypervolume trace with
nothing to compare it against.  Adds pr102_baselines.py: four baselines at
the campaign's own 36-design budget over the same ten seeds, plus a
reference optimum from a 65,536-design Sobol sweep and a Nelder-Mead polish
of 21 weightings.

BO reaches 97.1 % of the reference hypervolume, compass search 80.4 %,
Sobol 65.6 %, LHS 64.7 %, random 60.3 %; every baseline is completely
separated from the BO over ten seeds (Mann-Whitney p = 9.1e-5, the floor at
n = 10).  The front sits on box bounds, with cable_d and twist pinned low
along its whole length.

Also fixes the missing running-best e_reb_mJ panel: the column was computed
and saved but never plotted, in either the per-seed or the aggregate figure.
Every evaluation from every run is written to CSV, including the full
reference cloud, so plots can be redone without re-running anything.

Co-authored-by: Sterling G. Baird <45469701+sgbaird@users.noreply.github.com>
Every figure written by t3_prism_bo_campaign.py and t3_prism_bo_diagnostics.py
now renders at a single FIGURE_DPI = 300 (was 200, and 180 for the parameter
sweep grid), with the dpi written into each PNG so PowerPoint places it at its
intended physical size. The three registered prototype stills and the MP4 are
now 3300 x 2100; layout is unchanged, since matplotlib sizes type in points.

Both objective axes mark the good direction with U+2193 rather than the words
"lower is better", and the net-effects caption uses U+2190 for the same reason.

Co-authored-by: Sterling G. Baird <45469701+sgbaird@users.noreply.github.com>
@sgbaird

sgbaird commented Aug 22, 2026

Copy link
Copy Markdown
Collaborator Author

@claude which ones were actually printed in #35? This is very important. Did we use predictions where mass was accounted for somehow? Such as a scaling of an objective by mass.

Link to the exact BO script that produced the ones that are actually being printed right now.

@claude

claude Bot commented Aug 22, 2026

Copy link
Copy Markdown

Claude finished @sgbaird's task in 3m 21s —— View job


Which designs were actually printed, and did mass enter them

  • Identify the exact generator script + commit behind the printed articles
  • Confirm what each printed batch's mass treatment actually was
  • Check whether any mass-scaled objective fed the designs on the printer right now
  • Answer with exact links

Short answer: two different batches came out of #35, and only the second one is model-based. The one on the printer right now came from bo/t3_prism_bo_campaign.py at 7a048ee, the mass-aware-objective version. Yes, an objective was scaled by measured mass. No, mass was not held constant in the geometry.


1. The batch that was printed and drop-tested (Sobol, no model)

The 9 articles in the print key plus the S0 reference. Not a BO script at all: bo/t3_prism_sobol_batch.py, design table bo/t3-prism-bo-batch.csv, last regenerated in cf39fe0 ("Regenerate first Sobol batch with latest sensor housings + constant-mass / max-envelope constraints"). Quasi-random draw, no model fit, no objectives, no predictions. The copy committed on this PR (bo/t3-prism-bo-batch.csv) is byte-identical to that one (md5 prefix 73350c03 at cf39fe0, 32addaf, and here).

Its constraint is Route A constant solid mass, m* = 30.95 g of rho_PLA·V_PLA + rho_TPU·V_TPU. Not printed grams, which is why those 9 articles weigh 18.50 to 22.29 g on the scale.

2. The batch on the printer right now (model-based)

Added yesterday: 76e88bf "BO round-1 plate: H2D multi-material .3mf from the mass-aware suggestions", then @achris0520's manual-support version committed as 8809b25. Production file: bo/slices/t3-prism-bo-round1.H2D-MM-PLAstruts-TPUcables_manual-supports.3mf. Design table: bo/t3-prism-bo-round1.csv.

These are Ax trials 10 to 18, exactly the batch in your comment 5365706779.

3. The exact BO script that produced them

bo/t3_prism_bo_campaign.py at commit 7a048ee ("Account for per-print mass in BO objectives: e_reb_mJ objective, mass_g tracking metric, regenerated round-1 suggestions").

Established by matching the file, not by reading commit messages: the t3-prism-bo-suggestions-round1.csv that 76e88bf committed onto the #35 branch and fed to the plate generator is byte-identical to the one at 7a048ee on this branch, and differs from both the earlier and the later versions.

suggestions CSV version md5 prefix is it the plate?
4e26bb2 (first, mass-blind) 1db798e9 no
7a048ee (mass-scaled objective) 810c9a24 yes
2f1ca2e (constant printed mass, 6th parameter) ab9f3da8 no
the file on the #35 plate branch 810c9a24 matches 7a048ee

The plate itself was built by bo/t3_prism_sobol_batch.py at 76e88bf, which grew --designs-csv / --out-prefix for the purpose:

python3 bo/t3_prism_sobol_batch.py \
    --designs-csv bo/t3-prism-bo-suggestions-round1.csv \
    --out-prefix t3-prism-bo-round1

4. Did mass enter the predictions? Yes, in the objective. No, in the geometry.

Both are true at once, and the split is the thing worth knowing:

Where mass was used (in 7a048ee, so it is in the designs on the printer):

  • Objective 2 is scaled by each article's weighed mass. e_reb_mJ = e_rebound × m_weighed × g × h (line ~179). So yes, the acquisition was optimizing a mass-scaled objective when it picked trials 10 to 18.
  • Its noise folds in the print-to-print mass scatter (sd 0.457 g, spec-08 triplicate).
  • mass_g was an Ax tracking metric, so the model learned printed mass from the five coordinates and each suggestion carried a pred_mass_g_mean (18.3 to 20.5 g).
  • t180 was deliberately left as a ratio, not mass-corrected.

Where mass was not used:

  • Mass was not a parameter. The search space was the same 5 base coordinates (R, H, twist, strut Ø, cable Ø). pred_mass_g_mean was a readout, not a constraint.
  • The projection to physical geometry still held constant solid mass, m* = 30.95 g, identical to the Sobol batch. The README section that 76e88bf added says so explicitly: "same constant-mass projection onto m* = 30.95 g".

The consequence, which is the part I would flag. Because the plate re-projected onto solid mass, the printed masses the model predicted are not what will come off the printer, and the spread is if anything wider than round 1. Running this PR's calibrated mass model (i = 0.236, w = 1.18 mm, f_TPU = 0.744) over the nine plate designs:

spec trial strut Ø printed cable Ø printed predicted printed mass
00 10 10.53 2.63 17.4 g
01 11 9.04 4.14 19.3 g
02 12 10.78 2.70 17.3 g
03 13 8.08 3.28 19.9 g
04 14 9.79 4.49 18.7 g
05 15 10.47 2.62 17.6 g
06 16 9.63 4.42 18.7 g
07 17 8.08 3.70 20.0 g
08 18 6.01 5.51 22.5 g

17.3 to 22.5 g, CV 8.6%, against round 1's 18.50 to 22.29 g at CV 5.9%. So this round's articles will vary in printed mass more than the Sobol round did, and e_reb_mJ will again be partly ranking designs by grams. That is your note "without the real constant mass constraint applied", quantified.

Also on that plate: specimens 00, 02 and 05 (trials 10, 12, 15) come out at 2.6 to 2.7 mm as-printed cable diameter, flagged cable_bridge_ok=False, below the 3.0 mm TPU self-bridging floor. Round 1 printed two such violations (6lhxfy, 1zm8rv) and both showed tendon defects.

5. What is not on the printer

The 2f1ca2e batch, the one with mass_printed_g as a 6th parameter pinned at 20.23 g and fit_out_of_design wired up. Those are different designs (its trial 10 is 25 / 60 / 40° / 12.0 / 5.5 versus the printed 25 / 60 / 80° / 12.0 / 3.0), and no plate was ever generated from them. If you want the constant-printed-mass designs on a plate, that is a second --designs-csv run against bo/t3-prism-bo-suggestions-round1.csv as it stands on this branch now, plus re-pointing the projection at printed grams rather than solid.

One correction while I am here: the 7a048ee docstring said the projection "holds solid CAD volume constant". It holds solid mass constant. Fixed in 2f1ca2e, but the script that generated the printed plate carries the wrong phrasing.

Nothing committed this round; this is an answer, not a change.
Branch

claude Bot pushed a commit that referenced this pull request Aug 22, 2026
…inted mass

e_reb_mJ = e_rebound * m * g * h is an absolute energy, so it is
proportional to the article's printed mass. evaluate_pr102 was projecting
onto PR #35's constant-*solid*-mass manifold (30.95 g), which leaves
printed mass free: over the committed 68,944-design reference sweep,
printed mass spans 32 %, simulated e_rebound spans 0.34 %, and
rho(e_reb_mJ, mass_g) = 0.99993. The objective was the mass.

PR #102 fixed the same hole on the bench side in 2f1ca2e by projecting
onto constant *printed* mass and carrying mass_printed_g as a sixth BO
parameter in a narrow slab (target +/- 0.457 g, the spec-08 triplicate
scatter). This ports that projection (pr102_mass_model.py, recalibrated
from the CSVs already committed here: residual sd 0.378 g over 12 weighed
articles against 0.927 g for the flat two-density fit) and wires it in.

Also: simulate() can now pin the article's total mass, so the simulated
inertia agrees with the scale; the correlation study scores each article
at its own weighed mass; the mat is recalibrated on the new manifold
(S0 input peak 208.2 G, pulse 4.08 ms, both exact).

Co-authored-by: Sterling G. Baird <45469701+sgbaird@users.noreply.github.com>
@sgbaird

sgbaird commented Aug 22, 2026

Copy link
Copy Markdown
Collaborator Author

Those are different designs (its trial 10 is 25 / 60 / 40° / 12.0 / 5.5 versus the printed 25 / 60 / 80° / 12.0 / 3.0), and no plate was ever generated from them.

Noting that, yes, that is what I thought. This option is probably what I would have preferred if we weren't so time constrained, and I knew that I would need to check the accuracy of this latter one because of what I would expect from the difficulty of implementing it with the API. It's nuanced.

claude Bot pushed a commit that referenced this pull request Aug 22, 2026
…und objective, article-level noise; submitted on PR #102, previously unfetched

Co-authored-by: Sterling G. Baird <45469701+sgbaird@users.noreply.github.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
claude Bot pushed a commit that referenced this pull request Aug 22, 2026
…33 #94 #97 #85 #86 #98 #101)

Pivot from planned-methods SEA/eta_c framing to the executed campaign:
drop-tower objectives t180 (filtered peak-acceleration ratio) and rebound
energy per drop, SAASBO round 1 on the printed Sobol seed batch (real
results table, real Pareto/feature-importance/LOOCV figures with labels
regenerated for naming consistency), round-2 batch in fabrication,
constant-solid-mass projection + printability screens, as-printed
fabrication record (manual painted supports, TPU dry box, high-flow
nozzle), corrected J211 filter provenance, simulation screening ladder
from PR #33 with honest scorecard, metal-analog metric switched to t180,
Edison adversarial objective review reflected in Discussion/round-3 plan.
SI rewritten: print key, drop-tower protocol/rig characterization,
printed-mass model, simulation ladder. Em-dash sweep per style guide.
Rebuilt all four PDFs.

Co-authored-by: Sterling G. Baird <45469701+sgbaird@users.noreply.github.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
claude Bot added a commit that referenced this pull request Aug 22, 2026
Co-authored-by: Marcus Madsen <265197858+me-madsen@users.noreply.github.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant