loadpath 0.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- loadpath/README.md +294 -0
- loadpath/SKILL.md +584 -0
- loadpath/__init__.py +17 -0
- loadpath/__main__.py +1202 -0
- loadpath/am.py +316 -0
- loadpath/amgfast.py +326 -0
- loadpath/assembly.py +985 -0
- loadpath/benchmarks/__init__.py +0 -0
- loadpath/benchmarks/beams.py +266 -0
- loadpath/benchmarks/cases/ge-bracket.json +380 -0
- loadpath/benchmarks/evaluate.py +172 -0
- loadpath/benchmarks/mechanisms.py +416 -0
- loadpath/benchmarks/problems.py +140 -0
- loadpath/benchmarks/reference/bridge-3d.top3d.npz +0 -0
- loadpath/benchmarks/reference/cantilever-3d.top3d.npz +0 -0
- loadpath/benchmarks/reference/lbracket-2d.top88-ft2.npz +0 -0
- loadpath/benchmarks/reference/mbb-2d.top88-ft1.npz +0 -0
- loadpath/benchmarks/reference/mbb-2d.top88-ft2.npz +0 -0
- loadpath/benchmarks/reference.py +138 -0
- loadpath/benchmarks/run.py +301 -0
- loadpath/brep.py +1470 -0
- loadpath/bucklingopt.py +300 -0
- loadpath/buildsim.py +1490 -0
- loadpath/cadimport.py +904 -0
- loadpath/calibration.py +885 -0
- loadpath/cavities.py +211 -0
- loadpath/checks/__init__.py +0 -0
- loadpath/checks/dfm.py +354 -0
- loadpath/checks/exchanger.py +7 -0
- loadpath/checks/fea.py +212 -0
- loadpath/checks/fit.py +475 -0
- loadpath/checks/networks.py +260 -0
- loadpath/checks/opening.py +67 -0
- loadpath/checks/sdf_kit.py +7 -0
- loadpath/conjugate.py +267 -0
- loadpath/constrained.py +329 -0
- loadpath/contact.py +691 -0
- loadpath/coopt.py +342 -0
- loadpath/cores.py +163 -0
- loadpath/crease.py +1138 -0
- loadpath/critic.py +212 -0
- loadpath/crosscheck.py +1107 -0
- loadpath/cutcell.py +510 -0
- loadpath/data/lattice_buckling.json +439 -0
- loadpath/data/lattice_tensors.json +27449 -0
- loadpath/data/published_experiments.json +71 -0
- loadpath/decimate.py +177 -0
- loadpath/delivery.py +190 -0
- loadpath/directions.py +262 -0
- loadpath/domain.py +630 -0
- loadpath/drains.py +615 -0
- loadpath/durability.py +413 -0
- loadpath/editor.html +3135 -0
- loadpath/editor.py +610 -0
- loadpath/escape.py +354 -0
- loadpath/evals/__init__.py +0 -0
- loadpath/evals/baselines/coarse.json +1543 -0
- loadpath/evals/baselines/full.json +1497 -0
- loadpath/evals/brief_kinds/__init__.py +1 -0
- loadpath/evals/brief_kinds/heatx.py +387 -0
- loadpath/evals/brief_kinds/lattice.py +571 -0
- loadpath/evals/brief_kinds/multimat.py +593 -0
- loadpath/evals/briefs/assets/bracket.step +1201 -0
- loadpath/evals/briefs/assets/desk_arm.urdf +22 -0
- loadpath/evals/briefs/assets/elbow-gearbox.stl +0 -0
- loadpath/evals/briefs/assets/motor-body.stl +0 -0
- loadpath/evals/briefs/assets/sketch.png +0 -0
- loadpath/evals/briefs/base-adapter-fdm.json +492 -0
- loadpath/evals/briefs/bell-crank-lpbf.json +180 -0
- loadpath/evals/briefs/belt-tensioner-fdm.json +151 -0
- loadpath/evals/briefs/camera-boom-mjf.json +339 -0
- loadpath/evals/briefs/camera-mount-pla.json +203 -0
- loadpath/evals/briefs/coolant-isolator-petg.json +100 -0
- loadpath/evals/briefs/elbow-bearing-bracket-sls.json +300 -0
- loadpath/evals/briefs/elbow-gearbox-forearm-mjf.json +558 -0
- loadpath/evals/briefs/elbow-link-fatigue-fdm.json +251 -0
- loadpath/evals/briefs/elbow-stop-pad-tpu.json +39 -0
- loadpath/evals/briefs/forearm-fdm.json +146 -0
- loadpath/evals/briefs/forearm-servo-link-fdm.json +327 -0
- loadpath/evals/briefs/grip-pad-finger-mm.json +354 -0
- loadpath/evals/briefs/gripper-bracket-creep-fdm.json +340 -0
- loadpath/evals/briefs/gripper-finger-fdm.json +155 -0
- loadpath/evals/briefs/gripper-jaw-fatigue-mjf.json +328 -0
- loadpath/evals/briefs/gripper-palm-fdm.json +715 -0
- loadpath/evals/briefs/hot-stepper-mount-asa-fdm.json +471 -0
- loadpath/evals/briefs/hpu-oil-cooler-lpbf.json +121 -0
- loadpath/evals/briefs/isolated-camera-mount-mm.json +675 -0
- loadpath/evals/briefs/kart-charge-cooler-lpbf.json +93 -0
- loadpath/evals/briefs/lighten-step-bracket.json +220 -0
- loadpath/evals/briefs/nema17-mount-stl.json +429 -0
- loadpath/evals/briefs/pillow-block-sls.json +220 -0
- loadpath/evals/briefs/polyjet-fingertip-mm.json +305 -0
- loadpath/evals/briefs/rod-hook-pla.json +154 -0
- loadpath/evals/briefs/sensor-arm-resin.json +150 -0
- loadpath/evals/briefs/shelf-bracket-pla.json +158 -0
- loadpath/evals/briefs/shoulder-riser-mjf.json +487 -0
- loadpath/evals/briefs/sketch-clevis.json +229 -0
- loadpath/evals/briefs/spring-bracket-pacf.json +294 -0
- loadpath/evals/briefs/tool-flange-ti64.json +280 -0
- loadpath/evals/briefs/tray-arm-lattice-fdm.json +260 -0
- loadpath/evals/briefs/turret-fdm.json +281 -0
- loadpath/evals/briefs/upper-arm-sls.json +179 -0
- loadpath/evals/briefs/urdf-forearm.json +233 -0
- loadpath/evals/briefs/wrist-link-mjf.json +278 -0
- loadpath/evals/briefs/wrist-yoke-mjf.json +245 -0
- loadpath/evals/briefs.py +393 -0
- loadpath/evals/cases/arm-link-cast.json +157 -0
- loadpath/evals/cases/arm-link-driven.json +171 -0
- loadpath/evals/cases/arm-link-sandwich.json +315 -0
- loadpath/evals/cases/arm-link.json +111 -0
- loadpath/evals/cases/bell-crank.json +116 -0
- loadpath/evals/cases/grip-turret.json +479 -0
- loadpath/evals/cases/grip-upper-arm.json +253 -0
- loadpath/evals/cases/grip-wrist-link-mjf.json +929 -0
- loadpath/evals/cases/grip-wrist-yoke.json +831 -0
- loadpath/evals/cases/lpbf-intercooler-core.json +59 -0
- loadpath/evals/cases/mbb-beam.json +61 -0
- loadpath/evals/cases/motor-body.stl +0 -0
- loadpath/evals/cases/motor-mount.json +265 -0
- loadpath/evals/cases/printed-hook.json +77 -0
- loadpath/evals/cases/wall-bracket-fasteners.json +125 -0
- loadpath/evals/cases/wall-bracket.json +126 -0
- loadpath/evals/corpus.py +179 -0
- loadpath/evals/pareto.py +90 -0
- loadpath/evals/run.py +860 -0
- loadpath/examples/arm-link.json +25 -0
- loadpath/examples/bumpered-bracket-compression.json +225 -0
- loadpath/examples/bumpered-bracket-lattice.json +204 -0
- loadpath/examples/bumpered-bracket.json +34 -0
- loadpath/examples/gripper-finger-mm.json +185 -0
- loadpath/examples/heat-exchanger-counterflow.json +21 -0
- loadpath/examples/heat-exchanger-crossflow-tuned.json +48 -0
- loadpath/examples/heat-exchanger-crossflow.json +20 -0
- loadpath/examples/pedal-lattice.json +23 -0
- loadpath/examples/pressure-plenum.json +12 -0
- loadpath/examples/shelf-bracket.json +27 -0
- loadpath/exchanger.py +831 -0
- loadpath/fasteners.py +199 -0
- loadpath/fem.py +668 -0
- loadpath/femops.py +70 -0
- loadpath/fieldjit.py +981 -0
- loadpath/fieldops.py +119 -0
- loadpath/fields.py +3697 -0
- loadpath/fieldtorch.py +464 -0
- loadpath/flow.py +984 -0
- loadpath/flowtopo.py +300 -0
- loadpath/gpu.py +521 -0
- loadpath/heatx.py +1067 -0
- loadpath/heatx_thermal.py +432 -0
- loadpath/homogenized.py +886 -0
- loadpath/ibrep.py +552 -0
- loadpath/impact.py +693 -0
- loadpath/infill.py +376 -0
- loadpath/interface_library.py +340 -0
- loadpath/interlock.py +186 -0
- loadpath/isolator.py +149 -0
- loadpath/knn.py +204 -0
- loadpath/lattice.py +1065 -0
- loadpath/lattice3mf.py +760 -0
- loadpath/lattice_buckling.py +245 -0
- loadpath/lattice_grade.py +325 -0
- loadpath/lattice_orient.py +595 -0
- loadpath/lattice_print.py +159 -0
- loadpath/lattice_tensors.py +558 -0
- loadpath/latticestep.py +1210 -0
- loadpath/manufacture.py +446 -0
- loadpath/material_library.py +489 -0
- loadpath/materials.py +144 -0
- loadpath/mcp_server.py +786 -0
- loadpath/mechanism.py +218 -0
- loadpath/mechanism_nl.py +811 -0
- loadpath/meshops.py +452 -0
- loadpath/mg.py +853 -0
- loadpath/mma.py +214 -0
- loadpath/modal.py +210 -0
- loadpath/multimat.py +1788 -0
- loadpath/narrowband.py +418 -0
- loadpath/nlkernel.py +241 -0
- loadpath/nonlinear.py +1095 -0
- loadpath/orient.py +334 -0
- loadpath/porous.py +994 -0
- loadpath/pressure.py +171 -0
- loadpath/preview.py +335 -0
- loadpath/printcheck.py +263 -0
- loadpath/printsetup.py +207 -0
- loadpath/processcheck.py +376 -0
- loadpath/quick.py +113 -0
- loadpath/recall.py +406 -0
- loadpath/reconstruct.py +797 -0
- loadpath/render.py +436 -0
- loadpath/resolved.py +189 -0
- loadpath/schema.py +1701 -0
- loadpath/sdf_kit.py +843 -0
- loadpath/selfx.py +292 -0
- loadpath/shapes.py +584 -0
- loadpath/sharp.py +717 -0
- loadpath/simp.py +1170 -0
- loadpath/sizing.py +312 -0
- loadpath/slicer.py +321 -0
- loadpath/spec.py +1072 -0
- loadpath/step.py +806 -0
- loadpath/struts.py +639 -0
- loadpath/study.py +1937 -0
- loadpath/tasks.py +822 -0
- loadpath/thermal.py +1691 -0
- loadpath/threemf.py +487 -0
- loadpath/tune.py +437 -0
- loadpath/validation.py +145 -0
- loadpath/verify.py +1215 -0
- loadpath/viewer.html +694 -0
- loadpath/viewer.py +446 -0
- loadpath/viscoelastic.py +106 -0
- loadpath/voxelprint.py +582 -0
- loadpath/voxels.py +402 -0
- loadpath-0.1.1.dist-info/METADATA +319 -0
- loadpath-0.1.1.dist-info/RECORD +220 -0
- loadpath-0.1.1.dist-info/WHEEL +5 -0
- loadpath-0.1.1.dist-info/entry_points.txt +2 -0
- loadpath-0.1.1.dist-info/licenses/LICENSE +21 -0
- loadpath-0.1.1.dist-info/top_level.txt +1 -0
loadpath/evals/corpus.py
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Procedurally generated eval cases, so tuning stops fitting GRIP.
|
|
3
|
+
|
|
4
|
+
Every hand-written case is a GRIP-like part, and a fix tuned on them can win
|
|
5
|
+
there and lose everywhere else without anyone noticing. A generated case is
|
|
6
|
+
drawn from a seed: a family (link, bracket, tri-mount), random proportions,
|
|
7
|
+
bore sizes, axis directions, load directions, material and target. The same
|
|
8
|
+
seed always gives the same case, so a corpus is a fixed, reproducible
|
|
9
|
+
held-out set, and a fresh seed range is a part nobody has tuned on.
|
|
10
|
+
|
|
11
|
+
Loads are sized from a beam estimate so the solid design space would carry
|
|
12
|
+
them at several times the target safety factor: every case is feasible and
|
|
13
|
+
needs real material removal. Expectations are the generic ones every part
|
|
14
|
+
must meet (one body, nothing in keep-outs, preserved geometry kept, bores
|
|
15
|
+
open, SF met); the score that matters is the Pareto massAtTarget, compared
|
|
16
|
+
run to run.
|
|
17
|
+
|
|
18
|
+
python -m loadpath.evals.run --corpus 8 # seeds 0-7
|
|
19
|
+
python -m loadpath.evals.run --corpus 100:108 # a fresh range
|
|
20
|
+
python -m loadpath.evals.corpus 3 # print seed 3's study
|
|
21
|
+
"""
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import json
|
|
25
|
+
import sys
|
|
26
|
+
|
|
27
|
+
import numpy as np
|
|
28
|
+
|
|
29
|
+
FAMILIES = ("link", "bracket", "tri-mount")
|
|
30
|
+
MATERIALS = ("pla", "petg", "pa12", "asa")
|
|
31
|
+
_AX = {"x": 0, "y": 1, "z": 2}
|
|
32
|
+
# solid design space carries the load at this many times the target SF
|
|
33
|
+
_HEADROOM = (4.0, 8.0)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _r(x, nd=1):
|
|
37
|
+
return float(round(float(x), nd))
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _bore(rid, center, axis, bore_r, boss_r, length, through=40.0):
|
|
41
|
+
"""A preserved boss ring round a bore keep-out, both along `axis`."""
|
|
42
|
+
c = [_r(v) for v in center]
|
|
43
|
+
return ({"id": f"{rid}-boss", "shape": {"type": "cylinder", "center": c, "axis": axis,
|
|
44
|
+
"length": _r(length), "r": _r(boss_r)}},
|
|
45
|
+
{"id": f"{rid}-bore", "shape": {"type": "cylinder", "center": c, "axis": axis,
|
|
46
|
+
"length": _r(length + through), "r": _r(bore_r)}})
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _force(rng, size, mat, sf, span, section):
|
|
50
|
+
"""N for a load whose cantilever bending stress on the solid section is
|
|
51
|
+
strength / (sf * headroom)."""
|
|
52
|
+
b, h = sorted(section)
|
|
53
|
+
k = rng.uniform(*_HEADROOM)
|
|
54
|
+
return mat.strength * b * h * h / (6.0 * span * sf * k)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def generate(seed: int, family: str | None = None, max_elements: int = 8000) -> dict:
|
|
58
|
+
from ..materials import material_from_spec
|
|
59
|
+
|
|
60
|
+
rng = np.random.default_rng(seed)
|
|
61
|
+
family = family or FAMILIES[seed % len(FAMILIES)]
|
|
62
|
+
mat_name = MATERIALS[int(rng.integers(len(MATERIALS)))]
|
|
63
|
+
mat = material_from_spec(mat_name)
|
|
64
|
+
sf = float(rng.choice([2.0, 2.5, 3.0]))
|
|
65
|
+
min_member = _r(rng.uniform(2.5, 3.5))
|
|
66
|
+
preserve, keep, supports, loads = [], [], [], []
|
|
67
|
+
|
|
68
|
+
if family == "link":
|
|
69
|
+
# two bosses on a bar; bore axes both z (a planar link) or crossed
|
|
70
|
+
L = rng.uniform(70, 150)
|
|
71
|
+
W = rng.uniform(26, 44)
|
|
72
|
+
T = rng.uniform(16, 28)
|
|
73
|
+
r1 = rng.uniform(3.5, 7.0)
|
|
74
|
+
r2 = rng.uniform(3.0, 6.5)
|
|
75
|
+
crossed = rng.random() < 0.35
|
|
76
|
+
lo, hi = [-W / 2 - 2, -W / 2, -T / 2], [L + W / 2 + 2, W / 2, T / 2]
|
|
77
|
+
a2 = "y" if crossed else "z"
|
|
78
|
+
len2 = W if crossed else T
|
|
79
|
+
for rid, c, ax, br, ln in (("a", [0, 0, 0], "z", r1, T), ("b", [L, 0, 0], a2, r2, len2)):
|
|
80
|
+
p, k_ = _bore(rid, c, ax, br, min(br + rng.uniform(3.5, 6.0), 0.5 * (W if ax == "z" else T)), ln)
|
|
81
|
+
preserve.append(p)
|
|
82
|
+
keep.append(k_)
|
|
83
|
+
supports.append({"region": "a-bore"})
|
|
84
|
+
d = rng.normal(size=3)
|
|
85
|
+
d[0] *= 0.3 # mostly across the link: bending, not tension
|
|
86
|
+
d /= np.linalg.norm(d)
|
|
87
|
+
F = _force(rng, None, mat, sf, L, (W, T))
|
|
88
|
+
loads.append({"region": "b-bore", "force": [_r(F * v) for v in d]})
|
|
89
|
+
symmetry = ["z"] if not crossed and abs(d[2]) < 0.15 else []
|
|
90
|
+
elif family == "bracket":
|
|
91
|
+
# a plate bolted to a wall at x=0 (2-4 bolts), loaded at a boss out at +x
|
|
92
|
+
L = rng.uniform(60, 120)
|
|
93
|
+
W = rng.uniform(50, 80)
|
|
94
|
+
H = rng.uniform(50, 80)
|
|
95
|
+
n = int(rng.integers(2, 5))
|
|
96
|
+
lo, hi = [0, -W / 2, -H / 2], [L, W / 2, H / 2]
|
|
97
|
+
br = rng.uniform(1.6, 2.75)
|
|
98
|
+
ax = "y" if rng.random() < 0.5 else "z" # the load boss's bore
|
|
99
|
+
ey, ez = W / 2 - 7, H / 2 - 7
|
|
100
|
+
if n == 2:
|
|
101
|
+
# two bolts either side of the load boss, not in line with it
|
|
102
|
+
holes = [(0.0, -ez), (0.0, ez)] if ax == "y" else [(-ey, 0.0), (ey, 0.0)]
|
|
103
|
+
else:
|
|
104
|
+
holes = [(-ey, -ez), (ey, ez), (-ey, ez), (ey, -ez)][:n]
|
|
105
|
+
for i, (y, z) in enumerate(holes):
|
|
106
|
+
p, k_ = _bore(f"bolt{i}", [3, y, z], "x", br, br + rng.uniform(2.5, 4.0), 6)
|
|
107
|
+
preserve.append(p)
|
|
108
|
+
keep.append(k_)
|
|
109
|
+
# head access out to the far side: nothing may cover the bolt head
|
|
110
|
+
keep.append({"id": f"bolt{i}-head", "shape": {"type": "cylinder", "p0": [6, _r(y), _r(z)],
|
|
111
|
+
"p1": [_r(L + 1), _r(y), _r(z)], "r": _r(2 * br + 1)}})
|
|
112
|
+
supports.append({"region": f"bolt{i}-bore"})
|
|
113
|
+
lr = rng.uniform(3.0, 5.0)
|
|
114
|
+
c = [L - lr - 5, 0, 0]
|
|
115
|
+
p, k_ = _bore("load", c, ax, lr, lr + rng.uniform(3.5, 4.5), W if ax == "y" else H)
|
|
116
|
+
preserve.append(p)
|
|
117
|
+
keep.append(k_)
|
|
118
|
+
d = rng.normal(size=3)
|
|
119
|
+
d[0] = -abs(d[0]) * 0.5
|
|
120
|
+
d /= np.linalg.norm(d)
|
|
121
|
+
# the bolts and the load boss carry it, not the whole section: a
|
|
122
|
+
# sixth of the beam estimate (seeds 1, 4 missed SF at 67% fill)
|
|
123
|
+
F = _force(rng, None, mat, sf, L, (W, H)) / 6.0
|
|
124
|
+
loads.append({"region": "load-bore", "force": [_r(F * v) for v in d]})
|
|
125
|
+
symmetry = []
|
|
126
|
+
elif family == "tri-mount":
|
|
127
|
+
# three bosses on a triangle in a plate: two hold, one is loaded
|
|
128
|
+
R = rng.uniform(35, 70)
|
|
129
|
+
T = rng.uniform(14, 26)
|
|
130
|
+
ang = np.sort(rng.uniform(0, 2 * np.pi, 3))
|
|
131
|
+
ang = ang[0] + np.array([0, 2.1, 4.2]) + rng.uniform(-0.3, 0.3, 3)
|
|
132
|
+
pts = [[R * np.cos(a), R * np.sin(a), 0.0] for a in ang]
|
|
133
|
+
lo = [min(p[0] for p in pts) - 14, min(p[1] for p in pts) - 14, -T / 2]
|
|
134
|
+
hi = [max(p[0] for p in pts) + 14, max(p[1] for p in pts) + 14, T / 2]
|
|
135
|
+
for i, c in enumerate(pts):
|
|
136
|
+
br = rng.uniform(2.5, 5.5)
|
|
137
|
+
p, k_ = _bore(f"m{i}", c, "z", br, br + rng.uniform(3.5, 6.0), T)
|
|
138
|
+
preserve.append(p)
|
|
139
|
+
keep.append(k_)
|
|
140
|
+
supports += [{"region": "m0-bore"}, {"region": "m1-bore"}]
|
|
141
|
+
d = rng.normal(size=3)
|
|
142
|
+
d /= np.linalg.norm(d)
|
|
143
|
+
span = float(np.linalg.norm(np.array(pts[2]) - 0.5 * (np.array(pts[0]) + np.array(pts[1]))))
|
|
144
|
+
F = _force(rng, None, mat, sf, max(span, 20.0), (hi[1] - lo[1], T)) / 3.0
|
|
145
|
+
loads.append({"region": "m2-bore", "force": [_r(F * v) for v in d]})
|
|
146
|
+
symmetry = []
|
|
147
|
+
else:
|
|
148
|
+
raise ValueError(f"unknown family {family!r}: {FAMILIES}")
|
|
149
|
+
|
|
150
|
+
name = f"corpus-{family}-{seed}"
|
|
151
|
+
return {
|
|
152
|
+
"name": name,
|
|
153
|
+
"description": f"Generated ({family}, seed {seed}): not tuned on. Load sized so the solid design "
|
|
154
|
+
f"space has several times the target SF.",
|
|
155
|
+
"designSpace": [{"type": "box", "min": [_r(v) for v in lo], "max": [_r(v) for v in hi]}],
|
|
156
|
+
"preserve": preserve, "keepOut": keep, "supports": supports,
|
|
157
|
+
"loadCases": [{"name": "load", "loads": loads}],
|
|
158
|
+
"material": mat_name,
|
|
159
|
+
"goal": {"safetyFactor": sf, "volumeFraction": 0.3},
|
|
160
|
+
"minMember": min_member,
|
|
161
|
+
**({"symmetry": symmetry} if symmetry else {}),
|
|
162
|
+
"resolution": {"maxElements": int(max_elements)},
|
|
163
|
+
"expect": {"minSafetyFactor": sf, "bodies": 1, "maxKeepOutIntrusion": 2.0,
|
|
164
|
+
"maxPreserveMissing": 2.0, "maxClosedBores": 0, "minMassReduction": 0.3},
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def seeds(spec: str) -> list[int]:
|
|
169
|
+
"""'8' -> 0..7, '100:108' -> 100..107, '3,5,9' -> those."""
|
|
170
|
+
if ":" in spec:
|
|
171
|
+
a, b = spec.split(":")
|
|
172
|
+
return list(range(int(a), int(b)))
|
|
173
|
+
if "," in spec:
|
|
174
|
+
return [int(s) for s in spec.split(",")]
|
|
175
|
+
return list(range(int(spec)))
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
if __name__ == "__main__":
|
|
179
|
+
print(json.dumps(generate(int(sys.argv[1]), *(sys.argv[2:3] or [None])), indent=1))
|
loadpath/evals/pareto.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pareto scoring: how good the mass / safety-factor trade-off is, not just
|
|
3
|
+
whether the pick cleared a line.
|
|
4
|
+
|
|
5
|
+
Pass/fail hides progress. A case that gets 15% lighter at the same safety
|
|
6
|
+
factor scores the same as before, and a case that misses its target by 2%
|
|
7
|
+
scores the same as one that misses by 60%. Every outcome a study tries is a
|
|
8
|
+
point (mass, SF); the lightest-for-its-SF points form a front, and the front
|
|
9
|
+
says how much mass the target costs whatever the search happened to pick.
|
|
10
|
+
|
|
11
|
+
massAtTarget g, the front interpolated to the goal's safety factor
|
|
12
|
+
(lower is better; the number to watch). Between the
|
|
13
|
+
heaviest failing and the lightest passing front point,
|
|
14
|
+
linear in SF. With no failing point below it, it is the
|
|
15
|
+
lightest passing mass (an upper bound: "bracketed" false).
|
|
16
|
+
hypervolume area the front dominates in (1 - mass / solid mass,
|
|
17
|
+
min(SF / target, 2)) from (0, 0): 0..2, higher is better.
|
|
18
|
+
Rewards the whole curve, so a search that explores well
|
|
19
|
+
and finds a better trade-off away from the target still
|
|
20
|
+
moves it.
|
|
21
|
+
front the non-dominated points, lightest first.
|
|
22
|
+
"""
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
SF_CAP = 2.0 # SF / target beyond this buys nothing
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def front(points):
|
|
29
|
+
"""Non-dominated (mass, sf) points: no other point is at most as heavy and
|
|
30
|
+
at least as safe (strictly better in one). Lightest first."""
|
|
31
|
+
pts = sorted({(float(m), float(s)) for m, s in points if m is not None and s is not None},
|
|
32
|
+
key=lambda p: (p[0], -p[1]))
|
|
33
|
+
out, best_sf = [], float("-inf")
|
|
34
|
+
for m, s in pts:
|
|
35
|
+
if s > best_sf:
|
|
36
|
+
out.append((m, s))
|
|
37
|
+
best_sf = s
|
|
38
|
+
return out
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def mass_at(fr, target):
|
|
42
|
+
"""(mass, bracketed) where the front reaches `target` SF, or (None, False)
|
|
43
|
+
when no front point reaches it."""
|
|
44
|
+
passing = [p for p in fr if p[1] >= target]
|
|
45
|
+
if not passing:
|
|
46
|
+
return None, False
|
|
47
|
+
hi = min(passing, key=lambda p: p[0])
|
|
48
|
+
below = [p for p in fr if p[1] < target and p[0] <= hi[0]]
|
|
49
|
+
if not below:
|
|
50
|
+
return hi[0], False
|
|
51
|
+
lo = max(below, key=lambda p: p[0])
|
|
52
|
+
if hi[1] - lo[1] <= 1e-12:
|
|
53
|
+
return hi[0], True
|
|
54
|
+
t = (target - lo[1]) / (hi[1] - lo[1])
|
|
55
|
+
return lo[0] + t * (hi[0] - lo[0]), True
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def hypervolume(fr, solid_mass, target):
|
|
59
|
+
"""Area dominated by the front in (saving, SF/target) from (0, 0), with
|
|
60
|
+
saving = 1 - mass / solid_mass and SF/target capped at SF_CAP."""
|
|
61
|
+
if not fr or not solid_mass or not target:
|
|
62
|
+
return None
|
|
63
|
+
# in (saving up, safety up) both are maximized; sort by saving descending
|
|
64
|
+
pts = sorted(((1.0 - m / solid_mass, min(s / target, SF_CAP)) for m, s in fr), key=lambda p: -p[0])
|
|
65
|
+
area, prev_y = 0.0, 0.0
|
|
66
|
+
for x, y in pts:
|
|
67
|
+
if y > prev_y and x > 0:
|
|
68
|
+
area += x * (y - prev_y)
|
|
69
|
+
prev_y = y
|
|
70
|
+
return area
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def score(outcomes, target):
|
|
74
|
+
"""Pareto summary of a study's outcomes (Outcome.summary() dicts or
|
|
75
|
+
anything with mass, safetyFactor, massReduction)."""
|
|
76
|
+
pts = [(o.get("mass"), o.get("safetyFactor")) for o in outcomes]
|
|
77
|
+
fr = front(pts)
|
|
78
|
+
solid = None
|
|
79
|
+
for o in outcomes:
|
|
80
|
+
m, r = o.get("mass"), o.get("massReduction")
|
|
81
|
+
if m and r is not None and r < 1.0:
|
|
82
|
+
solid = m / (1.0 - r)
|
|
83
|
+
break
|
|
84
|
+
out = {"points": len([p for p in pts if None not in p]), "front": [[m, s] for m, s in fr],
|
|
85
|
+
"solidMass": solid}
|
|
86
|
+
if target:
|
|
87
|
+
m, br = mass_at(fr, target)
|
|
88
|
+
out.update(target=target, massAtTarget=m, bracketed=br,
|
|
89
|
+
hypervolume=hypervolume(fr, solid, target))
|
|
90
|
+
return out
|