Commit Graph
5 Commits
Author SHA1 Message Date
swaits 4a59041d1a style: apply rustfmt drift across the crate 2026-05-05 11:40:14 -06:00
swaits cf3b6acd10 fix(examples): jiggly mean_presses now counts every daily press
The Python tune_runtime.py treats the morning boot press and the 13:00
post-lunch re-tap as 'free' and only counts extra warning-phase taps.
That undercounts what the user actually presses each day and breaks
any comparison against a stated 'presses/day' comfort cap.

Updated `simulate_one` to count every press the user makes:
- boot press at workday start (always +1)
- 13:00 re-login press when the workday continues past lunch (+1)
- per-minute Bernoulli warning-phase presses (already counted)
- death-restart press: each time the device transitions running→dead
  during workday and the user is at-desk (not at lunch), the user
  presses to restart the cycle (warning press and death-restart for
  the same cycle are mutually exclusive — extending via warning press
  prevents that cycle's death)

With baseline now ~2 presses/day already mandatory, the hinge/cap
shift up too: PRESS_HINGE_LOW = 2.5/d (full reward up to baseline +
half a warning press) and PRESS_COMFORT_CAP = 3.5/d (rejected above).

Output 'Why' bullet now reports the total directly and notes the
component breakdown so the number is interpretable against the
new thresholds.
2026-05-05 09:51:10 -06:00
swaits e42514087c feat(examples): personalize jiggly weights with hinge press term and balance bonus
Tweak the a-posteriori scoring to match the user's stated preferences:

- Reweight: lunch_sleep 30%, after_hours 25%, work_fail 20%,
  presses 15% (with hinge below), balance 10%.
- Press term is now a hinge instead of a normalized minimize:
    * <= 2 presses/day → score 1.0 (no penalty)
    * 2 → 3 presses/day → linear ramp from 1.0 to 0.0
    * > 3 presses/day → -inf (excluded; comfort cap)
- New balance term: bonus for longer warning phases. Computed as
  min(YA - RA, RA - FRA), saturated at 10 minutes. So a 5/5/X split
  scores 0.5, an 8/8/X split scores 0.8, and 10/10/X or wider saturates
  at 1.0.

Constants moved to module scope so the printout in main and the
scoring function stay in sync.
2026-05-05 09:51:10 -06:00
swaits 5bd8b571e7 feat(examples): rank the jiggly Pareto front and recommend a pick
Adds an a-posteriori decision step to the jiggly example. After NSGA-III
produces the Pareto front, we apply a weighted-sum score over each
objective normalized to [0, 1] across the front (best→1, worst→0,
direction-aware), and report the top three plus a clear recommendation.

The weights are stated explicitly with rationale, not buried in code:

  work_fail    45%  — screen sleeping mid-meeting is the worst failure
  lunch_sleep  30%  — the actual design goal
  presses      15%  — UX friction the user feels
  after_hours  10%  — minor, mostly screen burn

This is the standard structure for picking a single answer out of a
Pareto set without losing the front itself: someone with different
weights can read the front and pick differently, but we surface a
specific recommendation with reasoning rather than leaving the user
to stare at 84 incomparable rows. Identical normalization could be
swapped for TOPSIS or knee-point detection later if useful.
2026-05-04 20:22:19 -06:00
swaits f70a12010d feat(examples): add jiggly USB-jiggler runtime tuning problem
Port of `scripts/tune_runtime.py` from ~/Code/jiggly: optimize the four
lifecycle constants of a USB mouse-jiggler firmware so the screen sleeps
during the user's lunch hour rather than failing during work.

The Python script grid-searches against a single composite score that
linearly combines several genuinely conflicting goals — a workaround
for the fact that grid search needs one number to rank by. heuropt has
the actual right tool, so this example is structured as a 4-objective
NSGA-III run that surfaces the Pareto front of legitimate tradeoffs:

1. minimize work-time failures (mean_work_sleep)
2. maximize lunch sleep (mean_lunch)
3. minimize human button presses (mean_presses)
4. minimize after-hours waste (mean_after)

Decision: 4-element `Vec<f64>` for (RT, YA, RA, FRA), continuous-relaxed
and rounded to integer minutes inside `evaluate`. The firmware ordering
constraint YA > RA > FRA > 0 is encoded as `constraint_violation` so
heuropt's feasible-beats-infeasible logic handles it for free.

Solver: NSGA-III with M=4, H=6 → 84 reference points, matching the
population size. Each evaluate runs a 1,000-workday Monte Carlo, so
the example is also a deliberately meaty evaluator that benefits from
`--features parallel`.

Output is in jiggly's native units — RT as Xh00m, thresholds in plain
minutes, sleep durations as Xh00m / Mm, probabilities as percentages —
and contrasts the Pareto front against:
- the four extreme single-axis winners (most lunch / fewest work fails /
  fewest presses / least after-hours)
- the firmware's currently-shipping defaults (which sit inside the
  front as a balanced compromise)
2026-05-04 20:16:59 -06:00