Geometry: the published open-stent design by Craig Bonsignore (Nitinol Devices & Components, 2010), CC-BY-SA 3.0 license. This is real CAD, not a re-imagined illustration. Every number on this page is computed from this project's own model, not read out of the CAD file. Full attribution: public/reference/ATTRIBUTION.md. (The button below toggles the same physical part crimped/expanded — 15.230mm shrinking to 13.644mm, a 10.4% foreshortening measured directly from the real CAD, not from this project's scoring model.)
Capability Map & Verification Status
R&D Pipeline Map — 3 Verification Tiers
Each stage in the Nitinol stent R&D pipeline is transparently color-coded by independent reproducibility and verification level:
Reference Geometry from Real CAD
Bonsignore 2010 CAD reference; geometry benchmarked.
Analytical Scoring Model
score_design() runs stably and is self-reproducible; Python and TypeScript match to 14 significant digits. Radial force used to be mislabeled from the hoop force variable (missing 2π factor, case 14) — root cause identified AND fixed 2026-08-12 (RF_trf = 2π × RF_hoop, propagated to the web). Kept yellow because this is a lesson worth keeping as evidence, not because it's still broken.
NSGA-II Optimization Loop
5 independent seeds; 132-point trade-off Pareto front.
3D Direct Hex Mesh Generation
12,800 hex elements, median quality 0.979 is strong, BUT 1.3% of elements at crown apex are severely distorted due to offset self-intersection — root cause identified, UNFIXED.
Nitinol FEA Simulation
Hysteresis loop runs; 391-point mesh is still coarse (not qualified for green).
Experimental Validation
Future objective; physical specimens & test rigs pending.
Standard Compliance Dossier
Targeted to ASME V&V 40 standard; not executed.
Unit-cell — built directly from one real Pareto point
Self-expanding Nitinol stent — single ring (unit-cell)
The mesh below and the 4 numbers next to it are computed from the SAME geometric parameters (N_CROWNS=17, AMPLITUDE=1.0355mm, STRUT_WIDTH=0.1541mm) — not a separate illustration with numbers attached afterward.
Bấm vào một chấm trên hình để xem vì sao vị trí đó đáng chú ý.
Geometry note: the mesh draws a ROUND cross-section (0.1541mm diameter) for the strut, while the real strut is RECTANGULAR (0.1541 x 0.15mm) — the depicted shape is about 1.03x thicker than reality in the radial direction (nearly matched, since strut width and wall thickness happen to be close at this point). No need to fix the shape, just know it's simplified.
The model currently covers a SINGLE ring only (N_col=1, N_bridges=0) — it does not yet simulate flexibility between rings (which depends on connecting bridges the model doesn't have). See 07_campaigns/2026-08-10-stent-ai-design-loop/ for the full scope notes.
A real trade-off surface, not one point picked to show off
132 optimized points found — the reference design still stands out of reach
Each dot is one of 132 designs on the Pareto front (NSGA-II, 5 independent seeds, after fixing a tube-diameter bug — see the 3 limitations below the chart). Horizontal axis is true radial force RF_trf (N/cm), vertical axis is fatigue safety factor N_sf, color encodes mass (light = light, dark = heavy). The white-ringed circle is the reference design itself — a real, published Nitinol engineer's example (Craig Bonsignore), NOT produced by this loop — rescored with the SAME model for a fair comparison.
Bấm vào một điểm có tên (hoặc nhãn bên dưới) để xem số của nó.
The plain truth: of these 132 points, NOT ONE beats the reference design on all 3 objectives simultaneously (RF_trf AND N_sf AND mass — measured with this same model on the reference design itself: RF_trf=2.77 N/cm, N_sf=2.71, mass=2.52mg). Relaxing to just 2 objectives (dropping mass), 9/132 points beat both RF_trf and N_sf — but all weigh 3-5x more. The point presented above picks a more balanced trade-off direction: 4.69x the radial force (12.99 N/cm vs 2.77 N/cm) AND 17.4% LIGHTER (2.09mg vs 2.52mg), at the cost of a fatigue safety factor down to 0.81x (2.19 vs 2.71) and a foreshortening margin that shrinks to a thin 4.93% (only about 1.4% left before violating the <5% constraint). This is a readable trade-off, NOT an 'the algorithm found a better design than the engineer' achievement. The upper-left corner of the chart (RF near 0, N_sf hitting the 100 cap) exists mathematically but is clinically useless: a stent with near-zero radial force provides no resistance to vessel recoil, however good its fatigue number looks. A related, milder observation: the loop has no objective that rewards metal coverage or crush resistance (POA is computed in scoring.py but never used as an objective) — that's part of why the Pareto surface drifts toward few-long-strut designs: nothing penalizes a design with only a handful of struts. (RF_trf = 2π × RF_hoop — the hoop force read directly from the original formula, see case 14 in the catalog below; fixed 2026-08-12. Do NOT say 'matches the published real-world range' — no physical specimen or measurement device has confirmed that.)
3 limitations that make this NOT a final conclusion
- The model fixes wall_thickness=0.15mm across the whole optimization loop, while the reference design uses 0.17mm — not the same variable, so the comparison is only relative.
- The adapter sets the crown-apex width (w_apex) equal to the strut width — scoring.py's own docstring flags this as an OPTIMISTIC assumption at the crown apex, exactly where fatigue strain concentrates most, so the real N_sf may be lower than the displayed number.
- The loop has EXACTLY 3 free variables (N_CROWNS, AMPLITUDE, STRUT_WIDTH) — wall_thickness and tube diameter are both fixed. With this simplified model and 3 variables, the loop did NOT find a design that dominates the reference design.
Drag it yourself, see the trade-off yourself — no need to read about it
Drag 4 parameters, watch 4 numbers pull against each other in real time
The 4 sliders below plug DIRECTLY into the same formula used everywhere on this page (scoreStentDesign() in lib/stentCalculator.ts) — not a separate illustration. Tube diameter stays fixed at 1.92mm (changing it changes the problem, not the design for the same vessel). Drag the strut wider and watch the mass climb with it — that's the same trade-off shown in the chart above, now felt by hand instead of read about.
Bấm vào một chấm trên hình để xem vì sao vị trí đó đáng chú ý.
A stent design cannot be separated from the vessel it serves
Same design, different target vessel — the fatigue safety factor changes by nearly 2.6x
The service parameters (D_set/D_ves) were carried over unchanged from the open-stent reference: a 1.92mm Nitinol tube self-expanding to 8.0mm for a 6.5mm vessel. The reference does NOT state a specific clinical indication — it's a general formula set for Nitinol stents, not tied to any particular vessel (the original README: "arteries, veins, or other vessels"). So this page doesn't name a vessel either — only the diameter. What can be stated with confidence: this is NOT a coronary vessel (coronary arteries run 2.25–4.0mm) and not the balloon-expandable technology common in coronary stents. The table below recomputes the SAME geometry for a smaller target vessel — nothing else changed.
| Metric | Reference scenario — 6.5mm vessel (D_set=8.0mm, D_ves=6.5mm) | 2.75mm vessel (D_set=3.0mm, D_ves=2.75mm) |
|---|---|---|
| RF_trf | 12.99 N/cm | 11.19 N/cm |
| N_sf | 2.19 | 5.69 |
| Mass | 2.085 mg | 2.085 mg |
| Foreshortening | 4.93% | 0.16% |
This is the SAME design measured in two different vessels — NOT a redesign for the smaller vessel (only D_set/D_ves changed, the optimization loop was not re-run). Mass is unchanged since it doesn't depend on the target vessel. The fatigue safety factor rises nearly 2.6x (2.19 -> 5.69) for the smaller vessel, showing that a stent design cannot be separated from the vessel it serves — that's worth stating plainly, not hiding.
Section W5
Where I Was Wrong
Not "what did I get running" — but "a number that looks reasonable doesn't mean it's correct, and here is how I found out." Most of the cases below raised no exception, no warning, nothing in red — just one perfectly reasonable, wrong number.
1. Rewriting a formula in another language — 2.44x off
Symptom: The site showed a radial force of 182.212 when the real value is 74.678.
Root cause: Silently simplified away a term from the vendor formula (+w_apex_raw), and used the raw wall thickness instead of the thickness after electropolish material loss.
Caught by: A 5-point cross-check running both language versions in parallel, compared to full float precision.
After the fix: Exact match — 74.67824159957114 on both sides.
2. A calculation returning garbage instead of an error
Symptom: Mass came out negative.
Root cause: The same computation behaves oppositely across two languages when given an infeasible geometry — one raises a domain error, the other silently returns NaN, which propagates downstream into nonsense.
Caught by: A golden-test boundary point (n_crowns/amplitude at the lower design limit).
After the fix: Now throws an explicit error on infeasible geometry, matching the Python behavior.
3. A results file contradicting itself
Symptom: Taking the parameters recorded in the file and recomputing produces a number that differs from the result printed right next to it — off in the second decimal place.
Root cause: Two rounding standards in the same file: the input parameters were rounded to 4 decimals for readability, but the result was computed from the full, unrounded value — the file could not reproduce itself.
Caught by: A discrepancy in the fifth decimal digit was almost dismissed as 'probably just rounding when typing the message' — following it to the end revealed a real bug, not a triviality.
After the fix: Increased export precision to 8 decimals; the file now correctly reproduces itself.
4. CAD software reporting 'valid' for a hollow, zero-volume solid
Symptom: OpenCASCADE returned solids=1, faces=432, is_valid=True — but volume = 6.9e-18.
Root cause: The solid self-intersected; the signed volume contributions canceled each other out.
Caught by: Cross-checking absolute volume instead of trusting the is_valid flag alone.
5. A volume-error check reporting 'best' exactly when the mesh is most broken
Symptom: At the finest mesh resolution, 1,184 elements were inverted (scaled Jacobian = -1.0) — while the volume error was the best of all resolutions tested (-0.030%).
Root cause: Volume is signed; inverted elements contribute negatively and cancel out the error from healthy elements.
Caught by: Keeping an element-quality check (scaled Jacobian) running alongside the volume check, instead of trusting volume alone.
6. Regenerating data destroys the ability to verify an old finding
Symptom: pareto_front_latest.json was regenerated after fixing bug #3 — technically correct, but it erased the original file that an earlier finding ('dropping RF_hoop loses 0/39 points') depended on.
Root cause: Changed the source data's format/precision without keeping the old version with a timestamp.
Caught by: Trying to re-verify an old claim and discovering the original data no longer existed.
After the fix: New rule: when changing the format of source data, keep the old version alongside a timestamp.
7. Choosing the wrong metric led to the exact opposite conclusion — the subtlest case
Symptom: Not a broken tool — the tool ran correctly. But two metrics on the SAME data produced two completely opposite conclusions about whether the RF_hoop objective was redundant.
Root cause: Counting how many Pareto points get dominated treats a point at the edge of the front the same as a point in the middle — but losing an edge point can erase an entire region of trade-off space.
Caught by: Hypervolume (the industry-standard metric) plus re-running on an independent random seed to see which metric was stable.
| Metric | Conclusion about RF_hoop |
|---|---|
| Count of dominated Pareto points | Dropping RF_hoop only loses 2/18 (11%) → "contributes least, looks redundant" |
| Hypervolume (industry standard) | Dropping RF_hoop loses 97% of covered volume → "the single most important objective" |
A "correlation > 0.95 ⇒ objective is redundant" threshold, written earlier in an independent audit — unverified, stated with confidence — is refuted by this very data: corr(RF_hoop, N_sf) = 0.9716, above that threshold, yet RF_hoop is the most important axis. High correlation does not mean redundancy. The metric that stays stable across independent runs (here: hypervolume, which kept the same ranking RF_hoop > mass > N_sf across two different random seeds, while the point-count metric jumped from 11% to 22%) is the one worth trusting.
8. “Exact match” — Communication failure and blind trust across verification layers
Symptom: The phrase 'exact match' was used to claim Python and TS versions agree, but the N_sf output actually differed at the last decimal place (10.618709906158351 vs 10.618709906158376).
Root cause: The developer (claude9) rushed to a conclusion, the gatekeeper (claude5) trusted it without verifying the digits and relayed it to Batin, and the writer (agy5) put it into the book draft as a fact.
Caught by: Cross-checking individual decimal digits at the final acceptance stage.
After the fix: Matches up to 14 significant digits, discrepancy at the 15th digit is due to float64 rounding, not a porting logic bug.
9. Caught BEFORE it was said — Inactive phenomenon in the superelastic model
Symptom: FEA simulation converged successfully with the Nitinol superelastic card, returning a reasonable peak stress of 89.422 MPa, but the material never activated its superelastic behavior.
Root cause: The 89 MPa stress was far below the phase transformation start threshold (400–500 MPa); the material was still operating entirely in its ordinary linear elastic region.
Caught by: Asking 'has this number exceeded the physical limits' instead of 'did the software crash', and running a diagnostic test with 10x load followed by unloading to confirm the hysteresis loop.
Do not correlate the 391-node coarse mesh from the Learning Edition (a tool constraint of 1,000 nodes, not a technical choice) to any fatigue durability metrics. Fatigue safety factor fluctuates up to an order of magnitude based on mesh density. The model used linear tetrahedral elements (C3D4) which systematically underpredict stress; industry standard recommends hexahedral mesh.
10. Self-written quality checker reports GREEN exactly when the mesh is severely broken — with a written justification for the bad checker
Symptom: A raw STL file of 127,042 faces / 2.2MB was decimated to 30k faces for web rendering. After decimation, the mesh shattered from 1 watertight solid into 384 disconnected fragments, destroying geometric continuity.
Root cause: The custom quality checker measured only the bounding-box discrepancy, reporting 0.0143% (green) because the outer shell bounds remained unchanged while the internal thin strut structures (0.13mm wide struts on an 8mm body) were ripped apart. A 77% volume deviation was logged but dismissed as 'reference only, not a primary check', followed by a written justification in the code comments explaining why the bounding box was a 'more accurate' metric.
Caught by: Checking the topological invariants of thin-walled stent structures: the number of disconnected shells must remain exactly 1, and the mesh must remain watertight (watertight = true) — rather than trusting bounding boxes or thumbnail renders.
The final kicker: the raw 2.2MB STL compressed to just 790KB under gzip, making decimation completely unnecessary. We created a non-existent problem, solved it incorrectly, and then wrote a justification to defend the wrong solution. Lesson: before optimizing, measure if optimization is actually needed.
11. Filling a source documentation gap with a 'reasonable' speculation — two people guessed two different answers, both wrong
Symptom: The web app originally labeled the design as a 'coronary stent' (wrong, since the target vessel diameter of 6.5mm is far larger than a real coronary artery: 2.25–4.0mm). It was then corrected to 'iliac artery' (reasonable based on diameter), while another developer independently proposed 'carotid artery' (equally reasonable based on stroke expertise). Both were fabricated.
Root cause: The original open-stent README only specifies 'arteries, veins, or other vessels' without any specific clinical indication. The D_ves=6.5mm was merely a placeholder parameter in the reference model.
Caught by: Asking 'does the source DOCUMENT state this' instead of 'does this sound reasonable'. Speculation is detected when two independent readers fill the same information gap with two different, yet highly plausible, fabrications.
After the fix: Removed all anatomy names, reporting only the diameter. Stated clearly: the source does not specify clinical indication, so the app remains name-neutral.
More dangerous than the original obvious 'coronary' error (which was easy to catch via mismatched numbers), the 'iliac/carotid' guesses looked researched and plausible, making them much harder to detect without checking the source.
12. Search space fails to contain its own reference starting point
Symptom: After correcting the tube diameter (3.0→1.92mm), the GA loop converged smoothly to a nice Pareto front of 109 points. However, the best RF_hoop was only 4.24 N/cm (17.6x lower than the old incorrect value), and 64/109 points (nearly 60%) pinned hard against the upper bound of the search space, a classic sign of a restricted boundary.
Root cause: The search space N_crowns=[4,16] was defined when the tube diameter was incorrectly set to 3.0mm. However, the actual physical reference design (Craig Bonsignore's open-stent) has n_crowns=21. The search space did not even contain the baseline comparison point.
Caught by: Evaluating the baseline reference design directly within the model and checking if its parameters fall inside the bounds — rather than judging by a clean-looking front. A well-behaved GA loop can easily optimize within an empty box.
After expanding the search space to [4,32] (pinning dropped to 1-2%), amplitude still pinned at 20-23% despite expansions. This was not a bounding issue but a physical degeneracy (RF_hoop→0, N_sf→100 sanity cap as struts grow too long). Further expansion would not converge due to this asymptote. Result: 0/132 points on the front dominated the reference design on all three objectives simultaneously.
13. A minSJ quality check magnifies a local defect into a global disaster — the opposite twin of Case 5
Symptom: Generating a hex mesh for the official design (n_crowns=17) yielded minSJ=-1.0000 across all 4 refinement levels, implying the mesh was completely broken and unusable for FEA.
Root cause: Analyzing the distribution instead of just the minimum: only 166/12,800 elements (1.3%) had SJ≤-0.999; the median SJ was 0.9788 (excellent); and 94.3% of elements had SJ>0. The 1.3% bad elements always located at the outermost edges of the strut cross-section at the peak of every crown. Direct measurement of the centerline minimum radius of curvature yielded 0.06731mm (resampled) and 0.07451mm (raw, measured by claude5), both smaller than half the strut width (0.077045mm), causing the offset curve to self-intersect.
Caught by: Inspecting the full distribution (min, median, percentage of bad elements) and measuring the physical radius of curvature rather than stopping at the 'insufficient crown radius' assumption.
Hypothesis 1 (node numbering reversal at the periodic seam) was rejected (minSJ remained -1.0000 after 3 reversal tests). Hypothesis 2 (insufficient crown radius) also failed: sweeping crown_r from 0.08 to 0.40mm yielded identical mesh metrics because the radius was capped by half-period geometric constraints. ⚠️ Note on statistics: the percentage of points below the threshold is unstable (36% after resampling vs 84.6% raw due to sampling resolution), so we never quote these percentages as hard facts. Case 13 is the opposite twin of Case 5: Case 5 used a cumulative metric (volume) to HIDE a local defect; Case 13 used an extreme metric (minSJ) to MAGNIFY a local defect of 1.3% into a global failure. Solution: look at min, median, AND percentage of bad elements.
14. "Radial resistive force" label mapped onto "hoop force" — a 2pi error slip-through because both numbers were physically reasonable
Symptom: The RF_hoop variable was mapped by default as 'Radial Resistive Force' with a reference value of 0.441 N/cm, rendering on plots, websites, and float64 regression tests without triggering any errors or warnings.
Root cause: The range of hoop force RF_hoop (0.1–4.0 N/cm) is completely reasonable for small coronary stents. Since both numbers (hoop force RF_hoop and true radial force RF_trf = 2 * pi * RF_hoop) were within physically plausible limits, the label mismatch of the original author slipped through all automated cross-checks.
Caught by: Asking outside (Gemini Deep Research). It didn't hand over a number to trust — it handed over a checkable mechanical argument: an 8mm stent cut from a 1.92mm tube with a 0.17mm wall should have a moment of inertia that gives far more force than 0.441 N/cm. Didn't take its word for it — opened the original source and read it directly, and found lines 143-144 of stent-calculator.py sitting right next to each other: RF_hoop and RF_trf = 2π × RF_hoop. Measurable beats trusted.
⚠️ Deep Research DID cite a reference RRF range (2.4-10.7 N/cm), but that range has NOT been independently verified by this project, and no source publishes a measurement specific to this exact open-stent design — do NOT cite that range as a verified fact, and do NOT attribute it to 'this specific open-stent design' (a misattribution that had briefly slipped into this very case — itself a repeat of case 11's pattern — now corrected). The 2π bug has been traced to its root AND fixed (2026-08-12): RF_trf = 2π × RF_hoop is now computed live across scoring.py, ga_loop.py, and the web.
Rules learned
- Ask two questions, not one — 'do the two versions agree with each other' is different from 'does the result reproduce from the way it was generated'.
- Never wave away a small discrepancy with 'probably just rounding' — that phrase is exactly where bugs hide.
- Keep the buggy version. Don't delete it for tidiness — the wrong file is the evidence that real checking happened.
- Porting a formula means porting its error-handling behavior too, not just the arithmetic.
- Don't trust a single check — a green gate can be hiding exactly what it was supposed to catch.
- The person who builds and the person who verifies must be two different parties — not because one is smarter, but because the person who wrote the code can't ask the question their own assumptions already ruled out.
- Ask 'has this number exceeded the physical limits of the material/model yet', not just 'did the software throw an error' — a simulation that converges with reasonable numbers and correct models can still be completely short of activating the phenomenon you want to prove, and the software will never flag it.
- Ask 'does the source DOCUMENT state this' instead of 'does this sound reasonable' — a reasonable guess based on context is still a guess; if the source is silent, do not write it as verified.
- After modifying a foundational parameter, verify if the search/experimental space still contains the baseline physical reference point — do not just look at a smooth, error-free convergence front.
- Never judge a distribution by a single metric (either extreme values like min/max, or cumulative values like sum/average). Cumulative metrics can HIDE local defects, and extreme metrics can MAGNIFY tiny local defects into global failures. Always inspect min, median, AND the percentage of bad elements.
- Ask 'does this metric align with independent published data / external experimental data', instead of just self-auditing internal discrepancies — when a wrong number is physically plausible, self-checking between code versions will never catch it. An outward-facing check is required to break the audit ceiling.