diff --git a/docs/BRINGUP.md b/docs/BRINGUP.md index 5baaead..90f3a60 100644 --- a/docs/BRINGUP.md +++ b/docs/BRINGUP.md @@ -246,7 +246,56 @@ hardware I/O — host testing). old formula had to be re-derived** — which is how the flow check below got rebuilt. - **Coolant flow verification (rebuilt, live-verified both ways).** + **Coolant flow verification — REBUILT ON A 60-RUN DESIGN MATRIX + (2026-08-02 overnight).** Everything below supersedes the earlier + ΔT-based designs; the tools are `scripts/bench/flow_matrix.py` + (+`flow_sampler.py` on the board), `flow_sustained.py`, + `flow_warm_validate.py`, `flow_recheck_char.py`. + - **Duty is the decisive parameter.** Below ~40 % the stagnant + loop sheds the heater's output by natural convection well enough + to **mimic flow**: at 30 %/50 s the five pump-stopped trials read + 8.15, 8.69, 8.78, 12.25, 13.33 °C while flow never exceeded 9.08 + — three of five dead-pump cases looked *healthier* than a working + pump. At 40 % heat input outruns convection (flow ≤11.46, + no-flow ≥16.04, d′ 8.4) and it is also the cheapest viable + option (~0.8 °C of loop heating per check vs ~2.0 °C at 50 %). + - **Operating point: 40 % duty, 50 s window, threshold 14.4 °C** + (balanced midpoint of 17 flow observations peaking at 12.75 and + 8 no-flow observations bottoming at 16.04). + - **Periodic re-checks every 150 s** (`GFCOOL_RECHECK_S`), because + a stopped pump is undetectable any other way — absolute + temperature only tracks a *circulating* loop, and "coolant + should warm while cutting" is ambiguous (a light engrave may add + no measurable heat). Sustained 40-minute run: zero false faults, + and **no thermal accumulation** — with cut-profile fans the loop + *cooled* 2 °C while being interrogated throughout. + - **Settle gate (safety-critical).** The check measures a rise + from a baseline; capturing that baseline while the loop is still + cooling from earlier heat produces garbage and was bench-proven + to **miss** (reported flow with the pump stopped). Checks are now + requested, and start only once the sensors agree **and** the + downstream reading is stationary. Stationarity uses a + **split-half mean difference**, not peak-to-peak: measured noise + on a settled loop is 0.52 °C p-p (0.70 worst) but only 0.11 °C + split-half (0.21 worst), so any p-p threshold tight enough to + catch drift sits *below the noise floor* and the gate never + opens. + - **Record: 25/25 correct classifications at 40 %**, plus all three + settle cases (settled/flow, settled/no-flow, and the unsettled + no-flow case that previously missed → now defers, then faults). + - **NOT YET VALIDATED (first-light commissioning items):** all + baselines were 19–23 °C (an overnight-cool room; the loop + equilibrates near ambient and the heater cannot reach a + cutting-session loop temperature — 100 % duty drives the + downstream sensor past 50 °C in 30 s while the bulk barely + moves). Behaviour at 27–32 °C baselines, and under real laser + heating, must be characterized at first light. Physics argues + the dependence is weak — with forced flow ΔT = P/(ṁ·c), which + carries no absolute-temperature term — but that is reasoning, + not measurement. + + *(Superseded earlier text kept below for context.)* + **Coolant flow verification (first attempt, live-verified both ways).** Continuous 10 % heating was never viable on the corrected curve: flow ΔT ≤3.69 vs no-flow ΔT ≥3.74 — a 0.04 °C gap against ~0.9 °C of sensor noise. At 30 % the ΔT bands separate (≤9.32 / ≥10.99) diff --git a/scripts/bench/README.md b/scripts/bench/README.md index d0dbe6e..7c76139 100644 --- a/scripts/bench/README.md +++ b/scripts/bench/README.md @@ -13,6 +13,10 @@ target board (dev image, python3 present) unless noted. | `fire_test.py` | FIRE drop-timing scope test (runs on the board): A = latch locked (expects nothing on FIRE/LASER_ON), B = latch unlocked / normal end-of-data, U = true underrun. Duty 0 throughout; refuses to unlock if HV reports good. All gates PASSED with it 2026-08-02 (2.0000 s pulses exact, both paths). | | `fan_test.py` | Fan/coolant bench (Windows-side): snapshots fan PWMs/tachs/temps, drives M8 → cut fans, M9 → cooldown → idle, verifying via tach readbacks. All-green 2026-08-02. | | `flow_characterize.py` | Coolant flow characterization using the factory temperature curve: baseline → flow → no-flow → recovery, printing the ΔT bands and their separation. Takes the heater duty as an argument (`flow_characterize.py 30`); aborts if downstream passes 45 °C. This is what showed the 10 % scheme was unusable (0.04 °C gap) and sized the 30 % check. | +| `flow_matrix.py` | **The flow-detection design matrix** (with `flow_sampler.py`, which lives on the board at `/data/`): duty × duration × flow/no-flow, every run from a common cooled baseline, interleaved repeats. One heating trace yields the metric at every candidate duration, so cost and precision come from the same 60 runs. Prints a cost table, a precision table (mean±sd, worst-case margin, d′) and a ranked shortlist. Env: `FM_DUTIES`, `FM_REPEATS`, `FM_RESULTS`. | +| `flow_sustained.py` | Long-run test of the real re-check cadence via M8: counts verdicts/false faults and tracks whether the loop accumulates heat. 40 min ⇒ 0 false faults, loop *cooled* 2 °C. | +| `flow_warm_validate.py` | Runs the real check from a heater-warmed baseline. Note the ceiling: 100 % duty pushes the downstream sensor past 50 °C in 30 s while the bulk barely moves, so warm-loop validation above ~23 °C needs the laser, not the heater. | +| `flow_recheck_char.py` | Characterizes short in-run re-checks and the differential metric; documents two dead ends (over-temp cannot see a stopped pump; passive warming trends are ambiguous). | | `temp_calibrate.py` | Coolant temperature calibration helper (`watch` / `point ` / `fit`) — pairs a measured temperature with averaged raw ADC readings and fits the line. Used to sanity-check the factory curve against a thermometer. | | `build-glowforge.sh` | Cross-compiles **grblHAL-glowforge** (the canonical driver repo, `../../../grblHAL-glowforge`) in the forge-yocto WSL distro. Run: `wsl -d forge-yocto -- bash /build-glowforge.sh` (from PowerShell; Git Bash mangles /mnt/c paths). This is the production controller build. | | `build-feeder.sh` | Cross-compiles `feeder.c` the same way. | diff --git a/scripts/bench/flow_matrix.py b/scripts/bench/flow_matrix.py new file mode 100644 index 0000000..6c73f58 --- /dev/null +++ b/scripts/bench/flow_matrix.py @@ -0,0 +1,270 @@ +#!/usr/bin/env python3 +"""Coolant flow-detection design matrix: heating cost AND detection +precision across heater duty cycles and check durations. + +Method. For every (duty, case) the loop is first cooled with the +cut-profile fans back to a common baseline, so every run starts from the +same thermal state and the measured rises are comparable. Then the +heater runs at the given duty while both sensors are sampled at 1 Hz. +Because a single heating trace contains the rise at every elapsed time, +one run yields the metric for ALL candidate check durations at once. + +Two questions answered from the same data: + 1. COST - how much does the check heat the loop? (upstream/bulk rise + in the flow case, per duty per duration) + 2. PRECISION - how well does it discriminate? (downstream rise, flow + vs no-flow: separation, worst-case margin, and d' = + separation / pooled standard deviation) + +Repeats are interleaved (all conditions in round 1, then round 2, ...) +so slow ambient drift spreads across conditions instead of confounding +any single one. + +Safety: aborts a run if downstream passes ABORT_C; heater off and pump +on at every exit path. The controller is stopped for the duration so it +cannot touch the fans or heater, and restarted at the end. + +Output: incremental JSON to flow_matrix_results.json (so partial runs +are still usable) and a summary table at the end. +""" +import json +import math +import os +import statistics +import subprocess +import sys +import time + +HOST = '172.16.1.97' +HERE = os.path.dirname(os.path.abspath(__file__)) +RESULTS = os.path.join(HERE, os.environ.get('FM_RESULTS', 'flow_matrix_results.json')) + +# Factory B-equation conversion (kernel-module-glowforge/UAPI.md). +F = 1024.0 * 1.3 +RD, BETA = 10000.0, 3380.0 +RINF = 10000.0 * math.exp(-3380.0 / 298.15) + +DUTIES = [int(x) for x in os.environ.get('FM_DUTIES', '10,15,20,30,40,50').split(',')] +REPEATS = int(os.environ.get('FM_REPEATS', '5')) +RUN_S = 75 +SAMPLE_IV = 1.0 +DURATIONS = [15, 20, 25, 30, 40, 50, 60, 75] +ABORT_C = 48.0 # below the factory 50 C idle ceiling +BASE_TOL_C = 0.5 # cooldown target: base + this +COOL_MAX_S = 300 + +FANS_RUN = ('echo 65535 > /sys/glowforge/thermal/exhaust_pwm; ' + 'echo 43278 > /sys/glowforge/thermal/intake_pwm; ' + 'echo 204 > /sys/glowforge/head/air_assist_pwm') + + +def degc(raw): + raw = float(raw) + if raw <= 0 or raw >= F: + return float('nan') + r = RD / (F / raw - 1.0) + return BETA / math.log(r / RINF) - 273.15 + + +def board(cmd, timeout=120): + r = subprocess.run(['wsl', '-d', 'forge-yocto', '--', 'ssh', + '-o', 'PreferredAuthentications=none', + 'root@' + HOST, cmd], + capture_output=True, text=True, timeout=timeout) + return r.stdout + + +def temps(): + o = board('cat /sys/glowforge/pic/water_temp_1 /sys/glowforge/pic/water_temp_2').split() + return degc(o[0]), degc(o[1]) + + +def heater(pct): + board('echo %d > /sys/glowforge/thermal/heater_pwm' % int(65535 * pct / 100)) + + +def pump(on): + board('echo %d > /sys/glowforge/thermal/water_pump_on' % (1 if on else 0)) + + +def safe_state(): + heater(0) + pump(True) + + +def cool_to(base, log): + """Cool with cut-profile fans until upstream is back near base.""" + safe_state() + board(FANS_RUN) + t0 = time.time() + while time.time() - t0 < COOL_MAX_S: + d, u = temps() + if u <= base + BASE_TOL_C: + return u, time.time() - t0 + time.sleep(10) + d, u = temps() + log(' (cooldown timeout at %.2f C, target %.2f)' % (u, base + BASE_TOL_C)) + return u, time.time() - t0 + + +def run_case(duty, flow, log): + """One heating run. Returns dict of rises at each candidate duration.""" + pump(flow) + time.sleep(3) + d0, u0 = temps() + heater(duty) + raw = board('python3 /data/flow_sampler.py %d %.1f' % (RUN_S, SAMPLE_IV), + timeout=RUN_S + 60) + heater(0) + pump(True) + + series = [] + for line in raw.strip().splitlines(): + try: + el, r1, r2 = line.split(',') + series.append((float(el), degc(r1), degc(r2))) + except ValueError: + continue + + aborted_at = None + for el, d, u in series: + if d >= ABORT_C: + aborted_at = el + break + + out = {'duty': duty, 'flow': flow, 'start_down': d0, 'start_up': u0, + 'aborted_at': aborted_at, 'samples': len(series), 'at': {}} + for target in DURATIONS: + if aborted_at is not None and target > aborted_at: + continue + near = [s for s in series if s[0] <= target] + if not near: + continue + el, d, u = near[-1] + if abs(el - target) > 4: # no sample close enough + continue + out['at'][str(target)] = {'t': el, 'down_rise': d - d0, 'up_rise': u - u0, + 'diff_rise': (d - d0) - (u - u0), 'down_abs': d} + return out + + +def summarize(runs, log): + log('') + log('=== COST: bulk (upstream) rise during a check, flow case, degrees C') + log(' duty ' + ''.join('%8s' % ('%ds' % t) for t in DURATIONS)) + for duty in DUTIES: + cells = [] + for t in DURATIONS: + vals = [r['at'][str(t)]['up_rise'] for r in runs + if r['duty'] == duty and r['flow'] and str(t) in r['at']] + cells.append('%8s' % ('%.2f' % statistics.mean(vals) if vals else '-')) + log(' %4d%%' % duty + ''.join(cells)) + + log('') + log('=== PRECISION: downstream-rise discrimination (flow vs no-flow)') + log(' duty dur flow mean+-sd noflow mean+-sd sep worst d-prime') + best = [] + for duty in DUTIES: + for t in DURATIONS: + fv = [r['at'][str(t)]['down_rise'] for r in runs + if r['duty'] == duty and r['flow'] and str(t) in r['at']] + nv = [r['at'][str(t)]['down_rise'] for r in runs + if r['duty'] == duty and not r['flow'] and str(t) in r['at']] + if len(fv) < 2 or len(nv) < 2: + continue + fm, fsd = statistics.mean(fv), statistics.stdev(fv) + nm, nsd = statistics.mean(nv), statistics.stdev(nv) + sep = nm - fm + worst = min(nv) - max(fv) + pooled = math.sqrt((fsd ** 2 + nsd ** 2) / 2) or 1e-9 + dprime = sep / pooled + cost = statistics.mean([r['at'][str(t)]['up_rise'] for r in runs + if r['duty'] == duty and r['flow'] and str(t) in r['at']]) + best.append((dprime, worst, duty, t, fm, fsd, nm, nsd, sep, cost)) + log(' %4d%% %4ds %6.2f+-%4.2f %6.2f+-%4.2f %5.2f %+5.2f %5.1f' + % (duty, t, fm, fsd, nm, nsd, sep, worst, dprime)) + + log('') + log('=== RANKED by d-prime (separation in pooled standard deviations)') + log(' rank duty dur d-prime worst-margin bulk-cost threshold') + for i, b in enumerate(sorted(best, reverse=True)[:12], 1): + dprime, worst, duty, t, fm, fsd, nm, nsd, sep, cost = b + log(' %4d %4d%% %4ds %7.1f %+11.2f %8.2f %8.2f' + % (i, duty, t, dprime, worst, cost, (fm + nm) / 2)) + log('') + log(' (worst-margin = min(no-flow) - max(flow); positive means every') + log(' observed no-flow run exceeded every observed flow run.') + log(' bulk-cost = degrees C added to the loop per check.)') + + +def main(): + t_start = time.time() + log_path = os.path.join(HERE, 'flow_matrix_log.txt') + logf = open(log_path, 'a') + + def log(msg): + print(msg, flush=True) + logf.write(msg + '\n') + logf.flush() + + log('=== flow matrix started %s' % time.strftime('%Y-%m-%d %H:%M:%S')) + log('duties=%s repeats=%d run=%ds durations=%s' % (DUTIES, REPEATS, RUN_S, DURATIONS)) + + board('pkill -x grblHAL_glowfor; sleep 1; true') + log('controller stopped for the experiment') + board(FANS_RUN) + safe_state() + + log('initial settle (180 s with fans)...') + time.sleep(180) + d, u = temps() + base = u + log('baseline: down=%.2f up=%.2f (target for every run: <= %.2f)' + % (d, u, base + BASE_TOL_C)) + + runs = [] + if os.path.exists(RESULTS): + try: + runs = json.load(open(RESULTS)).get('runs', []) + log('resuming with %d existing runs' % len(runs)) + except Exception: + runs = [] + + total = REPEATS * len(DUTIES) * 2 + n = 0 + try: + for rep in range(REPEATS): + for duty in DUTIES: + for flow in (True, False): + n += 1 + start_t, cool_s = cool_to(base, log) + r = run_case(duty, flow, log) + r['rep'] = rep + r['cooldown_s'] = round(cool_s) + r['base_at_start'] = start_t + runs.append(r) + json.dump({'base': base, 'runs': runs}, open(RESULTS, 'w'), indent=1) + at30 = r['at'].get('30') or r['at'].get('25') or {} + log(' [%2d/%2d] rep%d duty%3d%% %-7s start=%.2f down_rise@%s=%s %s' + % (n, total, rep + 1, duty, 'FLOW' if flow else 'NO-FLOW', + start_t, '30s' if '30' in r['at'] else '25s', + ('%.2f' % at30['down_rise']) if at30 else 'n/a', + ('ABORT@%.0fs' % r['aborted_at']) if r['aborted_at'] else '')) + log(' elapsed %.1f h' % ((time.time() - t_start) / 3600)) + except KeyboardInterrupt: + log('interrupted - summarizing what we have') + finally: + safe_state() + board('echo 0 > /sys/glowforge/thermal/exhaust_pwm; ' + 'echo 0 > /sys/glowforge/thermal/intake_pwm') + + summarize(runs, log) + + board('cd /data && GFSINK=/dev/glowforge nohup grblHAL_glowforge -p 23 ' + '-e /data/EEPROM-glowforge.DAT > /data/glowforge.log 2>&1 & sleep 2; true') + log('controller restarted; total elapsed %.2f h' % ((time.time() - t_start) / 3600)) + logf.close() + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/scripts/bench/flow_matrix_log.txt b/scripts/bench/flow_matrix_log.txt new file mode 100644 index 0000000..375dee1 --- /dev/null +++ b/scripts/bench/flow_matrix_log.txt @@ -0,0 +1,205 @@ +=== flow matrix started 2026-08-02 20:20:59 +duties=[10, 15, 20, 30, 40, 50] repeats=5 run=75s durations=[15, 20, 25, 30, 40, 50, 60, 75] +controller stopped for the experiment +initial settle (180 s with fans)... +baseline: down=22.62 up=22.46 (target for every run: <= 22.96) + [ 1/60] rep1 duty 10% FLOW start=22.77 down_rise@30s=3.93 + elapsed 0.1 h + [ 2/60] rep1 duty 10% NO-FLOW start=22.38 down_rise@30s=3.10 + elapsed 0.1 h + [ 3/60] rep1 duty 15% FLOW start=22.85 down_rise@30s=4.73 + elapsed 0.1 h + [ 4/60] rep1 duty 15% NO-FLOW start=22.38 down_rise@30s=3.45 + elapsed 0.2 h + [ 5/60] rep1 duty 20% FLOW start=22.77 down_rise@30s=3.64 + elapsed 0.2 h + [ 6/60] rep1 duty 20% NO-FLOW start=22.46 down_rise@30s=6.82 + elapsed 0.2 h + [ 7/60] rep1 duty 30% FLOW start=22.23 down_rise@30s=8.41 + elapsed 0.2 h + [ 8/60] rep1 duty 30% NO-FLOW start=22.30 down_rise@30s=11.03 + elapsed 0.3 h + [ 9/60] rep1 duty 40% FLOW start=22.93 down_rise@30s=11.46 + elapsed 0.3 h + [10/60] rep1 duty 40% NO-FLOW start=22.46 down_rise@30s=15.27 + elapsed 0.3 h + [11/60] rep1 duty 50% FLOW start=22.23 down_rise@30s=14.75 + elapsed 0.3 h + [12/60] rep1 duty 50% NO-FLOW start=22.54 down_rise@30s=18.74 + elapsed 0.4 h + [13/60] rep2 duty 10% FLOW start=21.84 down_rise@30s=3.14 + elapsed 0.4 h + [14/60] rep2 duty 10% NO-FLOW start=22.30 down_rise@30s=2.29 + elapsed 0.4 h + [15/60] rep2 duty 15% FLOW start=22.15 down_rise@30s=3.46 + elapsed 0.5 h + [16/60] rep2 duty 15% NO-FLOW start=22.77 down_rise@30s=4.08 + elapsed 0.5 h + [17/60] rep2 duty 20% FLOW start=22.15 down_rise@30s=4.69 + elapsed 0.5 h + [18/60] rep2 duty 20% NO-FLOW start=22.77 down_rise@30s=5.51 + elapsed 0.5 h + [19/60] rep2 duty 30% FLOW start=21.99 down_rise@30s=7.17 + elapsed 0.6 h + [20/60] rep2 duty 30% NO-FLOW start=22.93 down_rise@30s=7.10 + elapsed 0.6 h + [21/60] rep2 duty 40% FLOW start=22.62 down_rise@30s=8.82 + elapsed 0.6 h + [22/60] rep2 duty 40% NO-FLOW start=22.62 down_rise@30s=13.46 + elapsed 0.6 h + [23/60] rep2 duty 50% FLOW start=22.30 down_rise@30s=12.66 + elapsed 0.7 h + [24/60] rep2 duty 50% NO-FLOW start=22.54 down_rise@30s=18.18 + elapsed 0.7 h + [25/60] rep3 duty 10% FLOW start=22.07 down_rise@30s=2.43 + elapsed 0.7 h + [26/60] rep3 duty 10% NO-FLOW start=22.07 down_rise@30s=3.40 + elapsed 0.7 h + [27/60] rep3 duty 15% FLOW start=22.62 down_rise@30s=4.40 + elapsed 0.8 h + [28/60] rep3 duty 15% NO-FLOW start=21.76 down_rise@30s=4.62 + elapsed 0.8 h + [29/60] rep3 duty 20% FLOW start=22.85 down_rise@30s=4.35 + elapsed 0.8 h + [30/60] rep3 duty 20% NO-FLOW start=21.68 down_rise@30s=4.79 + elapsed 0.8 h + [31/60] rep3 duty 30% FLOW start=22.30 down_rise@30s=9.11 + elapsed 0.9 h + [32/60] rep3 duty 30% NO-FLOW start=22.15 down_rise@30s=7.28 + elapsed 0.9 h + [33/60] rep3 duty 40% FLOW start=21.91 down_rise@30s=10.00 + elapsed 0.9 h + [34/60] rep3 duty 40% NO-FLOW start=22.85 down_rise@30s=14.55 + elapsed 0.9 h + [35/60] rep3 duty 50% FLOW start=22.62 down_rise@30s=12.71 + elapsed 1.0 h + [36/60] rep3 duty 50% NO-FLOW start=22.62 down_rise@30s=18.19 + elapsed 1.0 h + [37/60] rep4 duty 10% FLOW start=22.54 down_rise@30s=2.75 + elapsed 1.0 h + [38/60] rep4 duty 10% NO-FLOW start=20.75 down_rise@30s=2.92 + elapsed 1.1 h + [39/60] rep4 duty 15% FLOW start=22.15 down_rise@30s=4.17 + elapsed 1.1 h + [40/60] rep4 duty 15% NO-FLOW start=22.62 down_rise@30s=4.71 + elapsed 1.1 h + [41/60] rep4 duty 20% FLOW start=21.37 down_rise@30s=5.48 + elapsed 1.1 h + [42/60] rep4 duty 20% NO-FLOW start=22.23 down_rise@30s=4.77 + elapsed 1.2 h + [43/60] rep4 duty 30% FLOW start=22.46 down_rise@30s=6.41 + elapsed 1.2 h + [44/60] rep4 duty 30% NO-FLOW start=22.62 down_rise@30s=6.84 + elapsed 1.2 h + [45/60] rep4 duty 40% FLOW start=22.54 down_rise@30s=10.50 + elapsed 1.2 h + [46/60] rep4 duty 40% NO-FLOW start=22.69 down_rise@30s=14.56 + elapsed 1.3 h + [47/60] rep4 duty 50% FLOW start=22.38 down_rise@30s=13.75 + elapsed 1.3 h + [48/60] rep4 duty 50% NO-FLOW start=22.23 down_rise@30s=18.61 + elapsed 1.3 h + [49/60] rep5 duty 10% FLOW start=22.38 down_rise@30s=2.67 + elapsed 1.3 h + [50/60] rep5 duty 10% NO-FLOW start=22.07 down_rise@30s=4.10 + elapsed 1.4 h + [51/60] rep5 duty 15% FLOW start=21.45 down_rise@30s=3.94 + elapsed 1.4 h + [52/60] rep5 duty 15% NO-FLOW start=22.15 down_rise@30s=3.19 + elapsed 1.4 h + [53/60] rep5 duty 20% FLOW start=21.91 down_rise@30s=4.26 + elapsed 1.4 h + [54/60] rep5 duty 20% NO-FLOW start=22.46 down_rise@30s=5.25 + elapsed 1.5 h + [55/60] rep5 duty 30% FLOW start=22.23 down_rise@30s=7.34 + elapsed 1.5 h + [56/60] rep5 duty 30% NO-FLOW start=22.77 down_rise@30s=11.06 + elapsed 1.5 h + [57/60] rep5 duty 40% FLOW start=22.93 down_rise@30s=9.65 + elapsed 1.5 h + [58/60] rep5 duty 40% NO-FLOW start=22.38 down_rise@30s=13.48 + elapsed 1.6 h + [59/60] rep5 duty 50% FLOW start=22.38 down_rise@30s=12.59 + elapsed 1.6 h + [60/60] rep5 duty 50% NO-FLOW start=21.68 down_rise@30s=20.05 + elapsed 1.6 h + +=== COST: bulk (upstream) rise during a check, flow case, degrees C + duty 15s 20s 25s 30s 40s 50s 60s 75s + 10% 0.34 0.62 0.67 0.33 0.30 0.58 0.09 0.34 + 15% 0.03 0.76 0.50 0.67 0.53 0.69 0.39 0.64 + 20% -0.08 -0.17 0.23 0.09 0.19 0.23 0.02 0.50 + 30% 0.79 0.73 1.39 0.83 1.11 1.17 1.42 1.22 + 40% 0.55 0.36 1.05 1.06 0.95 0.76 1.25 1.53 + 50% 1.26 1.83 1.72 1.59 2.03 2.03 2.19 2.69 + +=== PRECISION: downstream-rise discrimination (flow vs no-flow) + duty dur flow mean+-sd noflow mean+-sd sep worst d-prime + 10% 15s 2.68+-0.45 2.03+-0.71 -0.65 -2.20 -1.1 + 10% 20s 2.90+-0.69 2.81+-0.37 -0.09 -1.65 -0.2 + 10% 25s 2.76+-0.69 3.10+-0.60 0.33 -1.80 0.5 + 10% 30s 2.98+-0.59 3.16+-0.66 0.18 -1.64 0.3 + 10% 40s 2.90+-0.86 3.30+-0.42 0.40 -1.08 0.6 + 10% 50s 3.05+-0.85 3.88+-0.44 0.83 -1.09 1.2 + 10% 60s 2.37+-0.54 3.61+-0.54 1.24 -0.21 2.3 + 10% 75s 2.60+-0.83 4.12+-0.54 1.52 -0.36 2.2 + 15% 15s 2.93+-0.63 2.93+-0.36 -0.01 -1.21 -0.0 + 15% 20s 3.97+-0.33 3.54+-0.35 -0.42 -1.31 -1.2 + 15% 25s 3.89+-0.86 3.95+-0.45 0.06 -1.46 0.1 + 15% 30s 4.14+-0.48 4.01+-0.68 -0.13 -1.55 -0.2 + 15% 40s 4.11+-0.79 4.43+-0.65 0.32 -1.54 0.4 + 15% 50s 4.17+-0.78 5.01+-0.34 0.83 -0.24 1.4 + 15% 60s 4.17+-0.52 5.09+-0.48 0.92 -0.21 1.8 + 15% 75s 4.21+-0.41 5.75+-0.45 1.54 +0.37 3.6 + 20% 15s 3.84+-0.78 3.57+-0.83 -0.27 -2.08 -0.3 + 20% 20s 4.18+-0.53 4.14+-0.83 -0.03 -1.36 -0.0 + 20% 25s 4.71+-0.92 4.90+-1.03 0.19 -1.91 0.2 + 20% 30s 4.48+-0.67 5.43+-0.84 0.95 -0.71 1.2 + 20% 40s 4.84+-0.58 5.79+-1.16 0.96 -0.61 1.0 + 20% 50s 4.98+-0.39 6.41+-0.79 1.43 +0.22 2.3 + 20% 60s 4.53+-0.66 6.67+-1.39 2.14 -0.44 2.0 + 20% 75s 4.95+-0.65 7.34+-1.02 2.39 +0.64 2.8 + 30% 15s 6.32+-0.81 5.63+-1.72 -0.69 -3.43 -0.5 + 30% 20s 7.13+-1.31 6.86+-2.24 -0.27 -3.67 -0.1 + 30% 25s 7.65+-0.92 7.73+-1.93 0.08 -2.63 0.1 + 30% 30s 7.69+-1.07 8.66+-2.18 0.97 -2.27 0.6 + 30% 40s 7.69+-0.90 9.96+-2.65 2.27 -1.00 1.1 + 30% 50s 8.15+-0.75 10.24+-2.37 2.09 -0.93 1.2 + 30% 60s 8.20+-0.70 10.77+-2.34 2.57 -0.35 1.5 + 30% 75s 8.27+-1.28 10.86+-2.26 2.59 -0.82 1.4 + 40% 15s 8.06+-0.87 10.14+-0.65 2.08 +0.37 2.7 + 40% 20s 8.93+-1.07 12.07+-0.48 3.13 +1.06 3.8 + 40% 25s 9.61+-1.22 13.47+-0.98 3.86 +0.96 3.5 + 40% 30s 10.09+-0.98 14.26+-0.78 4.18 +2.00 4.7 + 40% 40s 10.29+-1.41 16.01+-0.88 5.72 +2.77 4.9 + 40% 50s 10.14+-0.87 16.95+-0.74 6.81 +4.58 8.4 + 40% 60s 10.71+-1.22 17.27+-0.96 6.56 +3.72 6.0 + 40% 75s 10.46+-1.22 17.88+-0.77 7.42 +5.38 7.2 + 50% 15s 10.73+-0.65 13.02+-0.71 2.29 +0.22 3.3 + 50% 20s 12.71+-0.89 15.58+-0.85 2.87 +0.55 3.3 + 50% 25s 12.71+-0.96 17.10+-0.78 4.39 +1.66 5.0 + 50% 30s 13.29+-0.95 18.75+-0.76 5.46 +3.43 6.4 + 50% 40s 14.04+-0.80 20.38+-1.16 6.34 +3.48 6.3 + 50% 50s 14.38+-0.57 21.61+-0.93 7.23 +5.08 9.4 + 50% 60s 14.24+-0.48 22.14+-1.02 7.90 +5.94 9.9 + 50% 75s 14.18+-0.78 23.17+-1.10 8.99 +6.51 9.4 + +=== RANKED by d-prime (separation in pooled standard deviations) + rank duty dur d-prime worst-margin bulk-cost threshold + 1 50% 60s 9.9 +5.94 2.19 18.19 + 2 50% 75s 9.4 +6.51 2.69 18.68 + 3 50% 50s 9.4 +5.08 2.03 18.00 + 4 40% 50s 8.4 +4.58 0.76 13.54 + 5 40% 75s 7.2 +5.38 1.53 14.17 + 6 50% 30s 6.4 +3.43 1.59 16.02 + 7 50% 40s 6.3 +3.48 2.03 17.21 + 8 40% 60s 6.0 +3.72 1.25 13.99 + 9 50% 25s 5.0 +1.66 1.72 14.91 + 10 40% 40s 4.9 +2.77 0.95 13.15 + 11 40% 30s 4.7 +2.00 1.06 12.18 + 12 40% 20s 3.8 +1.06 0.36 10.50 + + (worst-margin = min(no-flow) - max(flow); positive means every + observed no-flow run exceeded every observed flow run. + bulk-cost = degrees C added to the loop per check.) +controller restarted; total elapsed 1.63 h diff --git a/scripts/bench/flow_matrix_results.json b/scripts/bench/flow_matrix_results.json new file mode 100644 index 0000000..507b662 --- /dev/null +++ b/scripts/bench/flow_matrix_results.json @@ -0,0 +1,4145 @@ +{ + "base": 22.459370242678233, + "runs": [ + { + "duty": 10, + "flow": true, + "start_down": 22.070385781761104, + "start_up": 22.148125674399637, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.05, + "down_rise": 3.4521165180688627, + "up_rise": 0.9351786560037567, + "diff_rise": 2.516937862065106, + "down_abs": 25.522502299829966 + }, + "20": { + "t": 19.06, + "down_rise": 4.088061549762813, + "up_rise": 1.4828252166111042, + "diff_rise": 2.6052363331517085, + "down_abs": 26.158447331523917 + }, + "25": { + "t": 24.08, + "down_rise": 3.92879724358653, + "up_rise": 1.6396069480256301, + "diff_rise": 2.2891902955609, + "down_abs": 25.999183025347634 + }, + "30": { + "t": 29.09, + "down_rise": 3.92879724358653, + "up_rise": 1.1696813208146182, + "diff_rise": 2.7591159227719118, + "down_abs": 25.999183025347634 + }, + "40": { + "t": 39.12, + "down_rise": 4.088061549762813, + "up_rise": 1.4044873630488723, + "diff_rise": 2.6835741867139404, + "down_abs": 26.158447331523917 + }, + "50": { + "t": 49.15, + "down_rise": 4.487063750161724, + "up_rise": 1.6396069480256301, + "diff_rise": 2.8474568021360938, + "down_abs": 26.557449531922828 + }, + "60": { + "t": 59.19, + "down_rise": 2.9770373302504254, + "up_rise": 1.0914805579342328, + "diff_rise": 1.8855567723161926, + "down_abs": 25.04742311201153 + }, + "75": { + "t": 74.23, + "down_rise": 3.8492356020589114, + "up_rise": 1.6396069480256301, + "diff_rise": 2.2096286540332812, + "down_abs": 25.919621383820015 + } + }, + "rep": 0, + "cooldown_s": 0, + "base_at_start": 22.771088546071724 + }, + { + "duty": 10, + "flow": false, + "start_down": 24.1017307953295, + "start_up": 23.005202442288976, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.04, + "down_rise": 2.2959715888885057, + "up_rise": 0.5474105951595334, + "diff_rise": 1.7485609937289723, + "down_abs": 26.397702384218007 + }, + "20": { + "t": 19.06, + "down_rise": 3.0164209486147797, + "up_rise": 0.4691075792852075, + "diff_rise": 2.547313369329572, + "down_abs": 27.11815174394428 + }, + "25": { + "t": 24.07, + "down_rise": 3.096728295939613, + "up_rise": 0.7825301801362912, + "diff_rise": 2.314198115803322, + "down_abs": 27.198459091269115 + }, + "30": { + "t": 29.09, + "down_rise": 3.096728295939613, + "up_rise": 0.6257484487217653, + "diff_rise": 2.4709798472178477, + "down_abs": 27.198459091269115 + }, + "40": { + "t": 39.12, + "down_rise": 3.096728295939613, + "up_rise": 0.7041215176278683, + "diff_rise": 2.3926067783117446, + "down_abs": 27.198459091269115 + }, + "50": { + "t": 49.15, + "down_rise": 3.902785959321477, + "up_rise": 1.4898725793573249, + "diff_rise": 2.4129133799641522, + "down_abs": 28.00451675465098 + }, + "60": { + "t": 59.18, + "down_rise": 3.57970001889845, + "up_rise": 1.4111264481245485, + "diff_rise": 2.1685735707739013, + "down_abs": 27.68143081422795 + }, + "75": { + "t": 74.22, + "down_rise": 4.064670556224996, + "up_rise": 1.8052550805690544, + "diff_rise": 2.259415475655942, + "down_abs": 28.166401351554498 + } + }, + "rep": 0, + "cooldown_s": 0, + "base_at_start": 22.381515612920623 + }, + { + "duty": 15, + "flow": true, + "start_down": 22.225893698005905, + "start_up": 21.837331209719537, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.05, + "down_rise": 3.138075138642421, + "up_rise": 0.2330545720415671, + "diff_rise": 2.9050205666008537, + "down_abs": 25.363968836648326 + }, + "20": { + "t": 19.06, + "down_rise": 4.411503760409175, + "up_rise": 1.402275022614333, + "diff_rise": 3.0092287377948423, + "down_abs": 26.63739745841508 + }, + "25": { + "t": 24.08, + "down_rise": 4.97256539326321, + "up_rise": 1.0117637133643598, + "diff_rise": 3.96080167989885, + "down_abs": 27.198459091269115 + }, + "30": { + "t": 29.09, + "down_rise": 4.731800690129319, + "up_rise": 1.1678712325694391, + "diff_rise": 3.5639294575598797, + "down_abs": 26.957694388135224 + }, + "40": { + "t": 39.12, + "down_rise": 5.213807105982141, + "up_rise": 1.402275022614333, + "diff_rise": 3.8115320833678084, + "down_abs": 27.439700803988046 + }, + "50": { + "t": 49.15, + "down_rise": 4.97256539326321, + "up_rise": 2.107327034638729, + "diff_rise": 2.865238358624481, + "down_abs": 27.198459091269115 + }, + "60": { + "t": 59.18, + "down_rise": 4.49150179940591, + "up_rise": 1.4804757854947184, + "diff_rise": 3.0110260139111915, + "down_abs": 26.717395497411815 + }, + "75": { + "t": 74.23, + "down_rise": 4.49150179940591, + "up_rise": 1.1678712325694391, + "diff_rise": 3.3236305668364707, + "down_abs": 26.717395497411815 + } + }, + "rep": 0, + "cooldown_s": 32, + "base_at_start": 22.849094923083896 + }, + { + "duty": 15, + "flow": false, + "start_down": 25.284767845082115, + "start_up": 22.070385781761104, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.04, + "down_rise": 2.638892658718021, + "up_rise": 1.7173468406641632, + "diff_rise": 0.9215458180538576, + "down_abs": 27.923660503800136 + }, + "20": { + "t": 19.06, + "down_rise": 3.368695096479769, + "up_rise": 1.9527901816463782, + "diff_rise": 1.4159049148333906, + "down_abs": 28.653462941561884 + }, + "25": { + "t": 24.07, + "down_rise": 3.939701839541044, + "up_rise": 2.1099373397934187, + "diff_rise": 1.8297644997476255, + "down_abs": 29.22446968462316 + }, + "30": { + "t": 29.09, + "down_rise": 3.4500818926642864, + "up_rise": 1.4822272556874054, + "diff_rise": 1.967854636976881, + "down_abs": 28.7348497377464 + }, + "40": { + "t": 39.12, + "down_rise": 4.0215258384907315, + "up_rise": 1.7173468406641632, + "diff_rise": 2.3041789978265683, + "down_abs": 29.306293683572846 + }, + "50": { + "t": 49.15, + "down_rise": 4.760887173301285, + "up_rise": 2.5034747827869523, + "diff_rise": 2.2574123905143324, + "down_abs": 30.0456550183834 + }, + "60": { + "t": 59.18, + "down_rise": 4.513834765136494, + "up_rise": 2.1099373397934187, + "diff_rise": 2.403897425343075, + "down_abs": 29.79860261021861 + }, + "75": { + "t": 74.22, + "down_rise": 5.091249537822534, + "up_rise": 3.6902513396877907, + "diff_rise": 1.4009981981347437, + "down_abs": 30.37601738290465 + } + }, + "rep": 0, + "cooldown_s": 0, + "base_at_start": 22.381515612920623 + }, + { + "duty": 20, + "flow": true, + "start_down": 23.317806995214255, + "start_up": 22.615168696173612, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.04, + "down_rise": 3.9610124436977685, + "up_rise": 0.07794456483503609, + "diff_rise": 3.8830678788627324, + "down_abs": 27.278819438912024 + }, + "20": { + "t": 19.06, + "down_rise": 4.041426208584426, + "up_rise": -0.1557984534953789, + "diff_rise": 4.197224662079805, + "down_abs": 27.35923320379868 + }, + "25": { + "t": 24.08, + "down_rise": 5.417042742532146, + "up_rise": 0.7026382990406432, + "diff_rise": 4.714404443491503, + "down_abs": 28.7348497377464 + }, + "30": { + "t": 29.09, + "down_rise": 3.6398873929209685, + "up_rise": 0.1559198498981118, + "diff_rise": 3.4839675430228567, + "down_abs": 26.957694388135224 + }, + "40": { + "t": 39.12, + "down_rise": 4.202415663470674, + "up_rise": 0.7026382990406432, + "diff_rise": 3.499777364430031, + "down_abs": 27.52022265868493 + }, + "50": { + "t": 49.15, + "down_rise": 4.605853508585881, + "up_rise": 0.7026382990406432, + "diff_rise": 3.9032152095452375, + "down_abs": 27.923660503800136 + }, + "60": { + "t": 59.18, + "down_rise": 3.8003447487300264, + "up_rise": -0.1557984534953789, + "diff_rise": 3.9561432022254053, + "down_abs": 27.11815174394428 + }, + "75": { + "t": 74.23, + "down_rise": 5.8249024842558015, + "up_rise": 1.0157821948371293, + "diff_rise": 4.809120289418672, + "down_abs": 29.142709479470057 + } + }, + "rep": 0, + "cooldown_s": 11, + "base_at_start": 22.771088546071724 + }, + { + "duty": 20, + "flow": false, + "start_down": 23.55261303744851, + "start_up": 22.771088546071724, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.04, + "down_rise": 4.938257940846029, + "up_rise": 0.6249529202519852, + "diff_rise": 4.3133050205940435, + "down_abs": 28.490870978294538 + }, + "20": { + "t": 19.06, + "down_rise": 5.426765623323888, + "up_rise": 0.9382354138451205, + "diff_rise": 4.488530209478768, + "down_abs": 28.979378660772397 + }, + "25": { + "t": 24.07, + "down_rise": 6.4930419809348905, + "up_rise": 0.31221578433166997, + "diff_rise": 6.1808261966032205, + "down_abs": 30.0456550183834 + }, + "30": { + "t": 29.09, + "down_rise": 6.82340434545614, + "up_rise": 1.173569698286542, + "diff_rise": 5.649834647169598, + "down_abs": 30.37601738290465 + }, + "40": { + "t": 39.12, + "down_rise": 7.821354852110346, + "up_rise": 2.0393689767863066, + "diff_rise": 5.78198587532404, + "down_abs": 31.373967889558855 + }, + "50": { + "t": 49.15, + "down_rise": 7.570874105124801, + "up_rise": 1.6452403443418007, + "diff_rise": 5.9256337607830005, + "down_abs": 31.12348714257331 + }, + "60": { + "t": 59.18, + "down_rise": 8.91484766495347, + "up_rise": 2.751413753758243, + "diff_rise": 6.163433911195227, + "down_abs": 32.46746070240198 + }, + "75": { + "t": 74.22, + "down_rise": 9.084287531902817, + "up_rise": 3.4670622538930616, + "diff_rise": 5.617225278009755, + "down_abs": 32.636900569351326 + } + }, + "rep": 0, + "cooldown_s": 11, + "base_at_start": 22.459370242678233 + }, + { + "duty": 30, + "flow": true, + "start_down": 22.459370242678233, + "start_up": 22.459370242678233, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.05, + "down_rise": 6.031500735616305, + "up_rise": 0.5458321996107429, + "diff_rise": 5.485668536005562, + "down_abs": 28.490870978294538 + }, + "20": { + "t": 19.06, + "down_rise": 7.833952386032308, + "up_rise": 0.5458321996107429, + "diff_rise": 7.2881201864215654, + "down_abs": 30.29332262871054 + }, + "25": { + "t": 24.08, + "down_rise": 7.999412117826807, + "up_rise": 1.1715806483325082, + "diff_rise": 6.827831469494299, + "down_abs": 30.45878236050504 + }, + "30": { + "t": 29.09, + "down_rise": 8.414306912881102, + "up_rise": 1.4852880016800327, + "diff_rise": 6.929018911201069, + "down_abs": 30.873677155559335 + }, + "40": { + "t": 39.12, + "down_rise": 7.9166471402264165, + "up_rise": 1.406807014729111, + "diff_rise": 6.5098401254973055, + "down_abs": 30.37601738290465 + }, + "50": { + "t": 49.15, + "down_rise": 9.081963970045933, + "up_rise": 1.328362379747034, + "diff_rise": 7.753601590298899, + "down_abs": 31.541334212724166 + }, + "60": { + "t": 59.18, + "down_rise": 8.580773023064296, + "up_rise": 1.563805720729249, + "diff_rise": 7.016967302335047, + "down_abs": 31.04014326574253 + }, + "75": { + "t": 74.23, + "down_rise": 8.998242564740565, + "up_rise": 1.7995830815011686, + "diff_rise": 7.198659483239396, + "down_abs": 31.457612807418798 + } + }, + "rep": 0, + "cooldown_s": 21, + "base_at_start": 22.225893698005905 + }, + { + "duty": 30, + "flow": false, + "start_down": 23.317806995214255, + "start_up": 22.148125674399637, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.05, + "down_rise": 7.306717877700692, + "up_rise": 1.4044873630488723, + "diff_rise": 5.902230514651819, + "down_abs": 30.624524872914947 + }, + "20": { + "t": 19.06, + "down_rise": 9.403938637105114, + "up_rise": 1.9536051209298648, + "diff_rise": 7.450333516175249, + "down_abs": 32.72174563231937 + }, + "25": { + "t": 24.08, + "down_rise": 9.82943623777237, + "up_rise": 2.1108276497797647, + "diff_rise": 7.718608587992605, + "down_abs": 33.147243232986625 + }, + "30": { + "t": 29.09, + "down_rise": 11.032586771371257, + "up_rise": 2.583425754058794, + "diff_rise": 8.449161017312463, + "down_abs": 34.35039376658551 + }, + "40": { + "t": 39.12, + "down_rise": 12.695374979640576, + "up_rise": 4.01032165712428, + "diff_rise": 8.685053322516296, + "down_abs": 36.01318197485483 + }, + "50": { + "t": 49.15, + "down_rise": 12.254266210883031, + "up_rise": 4.489271784015443, + "diff_rise": 7.764994426867588, + "down_abs": 35.572073206097286 + }, + "60": { + "t": 59.18, + "down_rise": 12.695374979640576, + "up_rise": 4.649318383860248, + "diff_rise": 8.046056595780328, + "down_abs": 36.01318197485483 + }, + "75": { + "t": 74.23, + "down_rise": 12.872554797995406, + "up_rise": 5.211107529399044, + "diff_rise": 7.661447268596362, + "down_abs": 36.19036179320966 + } + }, + "rep": 0, + "cooldown_s": 11, + "base_at_start": 22.303690221175657 + }, + { + "duty": 40, + "flow": true, + "start_down": 22.459370242678233, + "start_up": 23.23960623233387, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.04, + "down_rise": 8.165154630236714, + "up_rise": -0.39051130924997324, + "diff_rise": 8.555665939486687, + "down_abs": 30.624524872914947 + }, + "20": { + "t": 19.06, + "down_rise": 10.43231839021297, + "up_rise": 0.15643523398983916, + "diff_rise": 10.27588315622313, + "down_abs": 32.8916886328912 + }, + "25": { + "t": 24.07, + "down_rise": 9.754548524215124, + "up_rise": 0.3913446586768714, + "diff_rise": 9.363203865538253, + "down_abs": 32.21391876689336 + }, + "30": { + "t": 29.09, + "down_rise": 11.459280572458908, + "up_rise": 1.4130796723769095, + "diff_rise": 10.046200900081999, + "down_abs": 33.91865081513714 + }, + "40": { + "t": 39.12, + "down_rise": 12.151198704366095, + "up_rise": 0.7050520120243959, + "diff_rise": 11.4461466923417, + "down_abs": 34.61056894704433 + }, + "50": { + "t": 49.15, + "down_rise": 11.459280572458908, + "up_rise": 0.07820076288038535, + "diff_rise": 11.381079809578523, + "down_abs": 33.91865081513714 + }, + "60": { + "t": 59.18, + "down_rise": 12.325128934434531, + "up_rise": 0.4697177275829745, + "diff_rise": 11.855411206851556, + "down_abs": 34.784499177112764 + }, + "75": { + "t": 74.22, + "down_rise": 11.545443566991537, + "up_rise": 1.1767226580796546, + "diff_rise": 10.368720908911882, + "down_abs": 34.00481380966977 + } + }, + "rep": 0, + "cooldown_s": 21, + "base_at_start": 22.927132764254054 + }, + { + "duty": 40, + "flow": false, + "start_down": 22.537254480322474, + "start_up": 22.148125674399637, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.05, + "down_rise": 10.609988752664151, + "up_rise": 0.46704302177397494, + "diff_rise": 10.142945730890176, + "down_abs": 33.147243232986625 + }, + "20": { + "t": 19.06, + "down_rise": 12.771391125897765, + "up_rise": 1.718051583007707, + "diff_rise": 11.053339542890058, + "down_abs": 35.30864560622024 + }, + "25": { + "t": 24.08, + "down_rise": 14.815442954927164, + "up_rise": 2.6623318484583933, + "diff_rise": 12.15311110646877, + "down_abs": 37.35269743524964 + }, + "30": { + "t": 29.09, + "down_rise": 15.267614086961771, + "up_rise": 3.9306658663753637, + "diff_rise": 11.336948220586407, + "down_abs": 37.804868567284245 + }, + "40": { + "t": 39.12, + "down_rise": 17.293684071072846, + "up_rise": 4.649318383860248, + "diff_rise": 12.644365687212598, + "down_abs": 39.83093855139532 + }, + "50": { + "t": 49.15, + "down_rise": 18.04622204488851, + "up_rise": 5.372096984285292, + "diff_rise": 12.674125060603217, + "down_abs": 40.58347652521098 + }, + "60": { + "t": 59.18, + "down_rise": 18.616543568755958, + "up_rise": 6.099304882156503, + "diff_rise": 12.517238686599455, + "down_abs": 41.15379804907843 + }, + "75": { + "t": 74.23, + "down_rise": 19.19213667870116, + "up_rise": 6.668171987900621, + "diff_rise": 12.52396469080054, + "down_abs": 41.729391159023635 + } + }, + "rep": 0, + "cooldown_s": 21, + "base_at_start": 22.459370242678233 + }, + { + "duty": 50, + "flow": true, + "start_down": 21.52696332098617, + "start_up": 22.225893698005905, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.05, + "down_rise": 11.705637597451982, + "up_rise": 1.2484163235682786, + "diff_rise": 10.457221273883704, + "down_abs": 33.23260091843815 + }, + "20": { + "t": 19.06, + "down_rise": 13.95719876409754, + "up_rise": 2.269181323640396, + "diff_rise": 11.688017440457145, + "down_abs": 35.48416208508371 + }, + "25": { + "t": 24.08, + "down_rise": 14.133123734852063, + "up_rise": 1.9544294235486177, + "diff_rise": 12.178694311303445, + "down_abs": 35.66008705583823 + }, + "30": { + "t": 29.09, + "down_rise": 14.752148654124312, + "up_rise": 1.7187645463523609, + "diff_rise": 13.033384107771951, + "down_abs": 36.27911197511048 + }, + "40": { + "t": 39.12, + "down_rise": 15.376450920094783, + "up_rise": 2.033059626173497, + "diff_rise": 13.343391293921286, + "down_abs": 36.90341424108095 + }, + "50": { + "t": 49.15, + "down_rise": 15.286933496980396, + "up_rise": 1.7187645463523609, + "diff_rise": 13.568168950628035, + "down_abs": 36.813896817966565 + }, + "60": { + "t": 59.18, + "down_rise": 14.929972716824238, + "up_rise": 1.4834302619109394, + "diff_rise": 13.446542454913299, + "down_abs": 36.45693603781041 + }, + "75": { + "t": 74.23, + "down_rise": 15.286933496980396, + "up_rise": 3.138075138642421, + "diff_rise": 12.148858358337975, + "down_abs": 36.813896817966565 + } + }, + "rep": 0, + "cooldown_s": 32, + "base_at_start": 22.225893698005905 + }, + { + "duty": 50, + "flow": false, + "start_down": 23.474310021574183, + "start_up": 23.161438802330906, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.05, + "down_rise": 11.922043064742411, + "up_rise": 0.3128712192432772, + "diff_rise": 11.609171845499134, + "down_abs": 35.396353086316594 + }, + "20": { + "t": 19.06, + "down_rise": 15.705262540411411, + "up_rise": 1.806954173329757, + "diff_rise": 13.898308367081654, + "down_abs": 39.179572561985594 + }, + "25": { + "t": 24.08, + "down_rise": 15.797919675415983, + "up_rise": 1.649018720527124, + "diff_rise": 14.148900954888859, + "down_abs": 39.272229696990166 + }, + "30": { + "t": 29.09, + "down_rise": 18.73890311015134, + "up_rise": 3.6360052559289784, + "diff_rise": 15.102897854222363, + "down_abs": 42.213213131725524 + }, + "40": { + "t": 39.12, + "down_rise": 20.513802440643076, + "up_rise": 5.085991754225233, + "diff_rise": 15.427810686417843, + "down_abs": 43.98811246221726 + }, + "50": { + "t": 49.15, + "down_rise": 21.727924864406646, + "up_rise": 5.817939858441491, + "diff_rise": 15.909985005965154, + "down_abs": 45.20223488598083 + }, + "60": { + "t": 59.18, + "down_rise": 22.035568189081857, + "up_rise": 6.4727978226123355, + "diff_rise": 15.562770366469522, + "down_abs": 45.50987821065604 + }, + "75": { + "t": 74.23, + "down_rise": 22.241608705780948, + "up_rise": 7.380179230505007, + "diff_rise": 14.86142947527594, + "down_abs": 45.71591872735513 + } + }, + "rep": 0, + "cooldown_s": 21, + "base_at_start": 22.537254480322474 + }, + { + "duty": 10, + "flow": true, + "start_down": 22.459370242678233, + "start_up": 21.914988917022924, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.05, + "down_rise": 2.667125132253659, + "up_rise": 0.934106006060972, + "diff_rise": 1.733019126192687, + "down_abs": 25.126495374931892 + }, + "20": { + "t": 19.06, + "down_rise": 2.9045985939700927, + "up_rise": 0.934106006060972, + "diff_rise": 1.9704925879091206, + "down_abs": 25.363968836648326 + }, + "25": { + "t": 24.08, + "down_rise": 2.114490321869823, + "up_rise": 1.1683154133804692, + "diff_rise": 0.946174908489354, + "down_abs": 24.573860564548056 + }, + "30": { + "t": 29.09, + "down_rise": 3.1424653181324516, + "up_rise": 1.0902135252660514, + "diff_rise": 2.0522517928664, + "down_abs": 25.601835560810684 + }, + "40": { + "t": 39.12, + "down_rise": 3.1424653181324516, + "up_rise": 0.8560996290487992, + "diff_rise": 2.2863656890836523, + "down_abs": 25.601835560810684 + }, + "50": { + "t": 49.15, + "down_rise": 2.825397602403882, + "up_rise": 0.7781243439857235, + "diff_rise": 2.0472732584181585, + "down_abs": 25.284767845082115 + }, + "60": { + "t": 59.18, + "down_rise": 1.9569586477352914, + "up_rise": 0.23313675737671247, + "diff_rise": 1.723821890358579, + "down_abs": 24.416328890413524 + }, + "75": { + "t": 74.23, + "down_rise": 2.667125132253659, + "up_rise": 1.1683154133804692, + "diff_rise": 1.49880971887319, + "down_abs": 25.126495374931892 + } + }, + "rep": 1, + "cooldown_s": 32, + "base_at_start": 21.837331209719537 + }, + { + "duty": 10, + "flow": false, + "start_down": 23.787732622425267, + "start_up": 22.459370242678233, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.04, + "down_rise": 1.338762752506625, + "up_rise": -0.6220390329586962, + "diff_rise": 1.9608017854653212, + "down_abs": 25.126495374931892 + }, + "20": { + "t": 19.06, + "down_rise": 2.450418177539518, + "up_rise": 0.38972468040566355, + "diff_rise": 2.0606934971338546, + "down_abs": 26.238150799964785 + }, + "25": { + "t": 24.07, + "down_rise": 2.131888761394748, + "up_rise": -0.15568002150257598, + "diff_rise": 2.287568782897324, + "down_abs": 25.919621383820015 + }, + "30": { + "t": 29.09, + "down_rise": 2.2910589183497336, + "up_rise": 0.6239340877251607, + "diff_rise": 1.667124830624573, + "down_abs": 26.078791540775 + }, + "40": { + "t": 39.12, + "down_rise": 3.0097114358346175, + "up_rise": 0.5458321996107429, + "diff_rise": 2.4638792362238746, + "down_abs": 26.797444058259885 + }, + "50": { + "t": 49.15, + "down_rise": 3.651968181562779, + "up_rise": 1.1715806483325082, + "diff_rise": 2.480387533230271, + "down_abs": 27.439700803988046 + }, + "60": { + "t": 59.18, + "down_rise": 2.7697169094975607, + "up_rise": 1.2499537172386113, + "diff_rise": 1.5197631922589494, + "down_abs": 26.557449531922828 + }, + "75": { + "t": 74.23, + "down_rise": 3.4910868164867566, + "up_rise": 0.8584367525360221, + "diff_rise": 2.6326500639507344, + "down_abs": 27.278819438912024 + } + }, + "rep": 1, + "cooldown_s": 0, + "base_at_start": 22.303690221175657 + }, + { + "duty": 15, + "flow": true, + "start_down": 22.615168696173612, + "start_up": 22.303690221175657, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.05, + "down_rise": 2.1952888266844184, + "up_rise": -0.23330443941455314, + "diff_rise": 2.4285932660989715, + "down_abs": 24.81045752285803 + }, + "20": { + "t": 19.06, + "down_rise": 3.7825336880443956, + "up_rise": 0.5454047019082395, + "diff_rise": 3.237128986136156, + "down_abs": 26.397702384218007 + }, + "25": { + "t": 24.08, + "down_rise": 3.7825336880443956, + "up_rise": 0.4673983248960667, + "diff_rise": 3.315135363148329, + "down_abs": 26.397702384218007 + }, + "30": { + "t": 29.09, + "down_rise": 3.463622844601389, + "up_rise": 0.23356425914681722, + "diff_rise": 3.2300585854545716, + "down_abs": 26.078791540775 + }, + "40": { + "t": 39.12, + "down_rise": 3.3044526876464033, + "up_rise": 0.23356425914681722, + "diff_rise": 3.070888428499586, + "down_abs": 25.919621383820015 + }, + "50": { + "t": 49.15, + "down_rise": 3.3044526876464033, + "up_rise": 0.15568002150257598, + "diff_rise": 3.1487726661438273, + "down_abs": 25.919621383820015 + }, + "60": { + "t": 59.18, + "down_rise": 4.262374855177484, + "up_rise": -0.3887013041527325, + "diff_rise": 4.651076159330216, + "down_abs": 26.877543551351096 + }, + "75": { + "t": 74.23, + "down_rise": 4.182275362086273, + "up_rise": 0.7796141092277367, + "diff_rise": 3.402661252858536, + "down_abs": 26.797444058259885 + } + }, + "rep": 1, + "cooldown_s": 11, + "base_at_start": 22.148125674399637 + }, + { + "duty": 15, + "flow": false, + "start_down": 24.73155142845843, + "start_up": 21.682095410064733, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.04, + "down_rise": 2.708149375529615, + "up_rise": 1.7139460562589761, + "diff_rise": 0.994203319270639, + "down_abs": 27.439700803988046 + }, + "20": { + "t": 19.06, + "down_rise": 3.434849923096067, + "up_rise": 1.8705176273837765, + "diff_rise": 1.5643322957122905, + "down_abs": 28.166401351554498 + }, + "25": { + "t": 24.07, + "down_rise": 3.759319549836107, + "up_rise": 2.0272285498521114, + "diff_rise": 1.7320909999839955, + "down_abs": 28.490870978294538 + }, + "30": { + "t": 29.09, + "down_rise": 4.084746233841827, + "up_rise": 1.6357115851495223, + "diff_rise": 2.4490346486923045, + "down_abs": 28.816297662300258 + }, + "40": { + "t": 39.12, + "down_rise": 4.329461191847997, + "up_rise": 2.419635385264769, + "diff_rise": 1.9098258065832283, + "down_abs": 29.06101262030643 + }, + "50": { + "t": 49.15, + "down_rise": 4.820601514825114, + "up_rise": 2.184081847342611, + "diff_rise": 2.636519667482503, + "down_abs": 29.552152943283545 + }, + "60": { + "t": 59.18, + "down_rise": 4.9848349331965665, + "up_rise": 2.3410805533427492, + "diff_rise": 2.6437543798538172, + "down_abs": 29.716386361654997 + }, + "75": { + "t": 74.22, + "down_rise": 5.892973444456516, + "up_rise": 3.0494560183936983, + "diff_rise": 2.8435174260628173, + "down_abs": 30.624524872914947 + } + }, + "rep": 1, + "cooldown_s": 0, + "base_at_start": 22.771088546071724 + }, + { + "duty": 20, + "flow": true, + "start_down": 23.39604146632371, + "start_up": 22.927132764254054, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.05, + "down_rise": 3.321354031088106, + "up_rise": -0.07803784117015766, + "diff_rise": 3.3993918722582634, + "down_abs": 26.717395497411815 + }, + "20": { + "t": 19.06, + "down_rise": 3.321354031088106, + "up_rise": -0.5456171513334311, + "diff_rise": 3.866971182421537, + "down_abs": 26.717395497411815 + }, + "25": { + "t": 24.08, + "down_rise": 3.321354031088106, + "up_rise": -0.4677625215758212, + "diff_rise": 3.789116552663927, + "down_abs": 26.717395497411815 + }, + "30": { + "t": 29.09, + "down_rise": 4.689388761298005, + "up_rise": 0.39067423096020093, + "diff_rise": 4.298714530337804, + "down_abs": 28.085430227621714 + }, + "40": { + "t": 39.12, + "down_rise": 4.366076493006403, + "up_rise": -0.4677625215758212, + "diff_rise": 4.8338390145822245, + "down_abs": 27.762117959330112 + }, + "50": { + "t": 49.15, + "down_rise": 5.257421475238175, + "up_rise": 0.07806967803492171, + "diff_rise": 5.179351797203253, + "down_abs": 28.653462941561884 + }, + "60": { + "t": 59.18, + "down_rise": 4.12418119236122, + "up_rise": -0.7790070898544172, + "diff_rise": 4.903188282215638, + "down_abs": 27.52022265868493 + }, + "75": { + "t": 74.23, + "down_rise": 4.12418119236122, + "up_rise": -0.4677625215758212, + "diff_rise": 4.591943713937042, + "down_abs": 27.52022265868493 + } + }, + "rep": 1, + "cooldown_s": 11, + "base_at_start": 22.148125674399637 + }, + { + "duty": 20, + "flow": false, + "start_down": 25.284767845082115, + "start_up": 22.693113261008648, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.05, + "down_rise": 3.6130393108580847, + "up_rise": 0.702928205315061, + "diff_rise": 2.9101111055430238, + "down_abs": 28.8978071559402 + }, + "20": { + "t": 19.06, + "down_rise": 4.349468779861127, + "up_rise": 0.46832554132225823, + "diff_rise": 3.8811432385388684, + "down_abs": 29.63423662494324 + }, + "25": { + "t": 24.08, + "down_rise": 5.174014515422925, + "up_rise": 1.094619361416619, + "diff_rise": 4.079395154006306, + "down_abs": 30.45878236050504 + }, + "30": { + "t": 29.09, + "down_rise": 5.505786111870009, + "up_rise": 1.2515449833496177, + "diff_rise": 4.254241128520391, + "down_abs": 30.790553956952124 + }, + "40": { + "t": 39.12, + "down_rise": 5.339757027832832, + "up_rise": 1.0162106989081963, + "diff_rise": 4.323546328924635, + "down_abs": 30.624524872914947 + }, + "50": { + "t": 49.15, + "down_rise": 6.844799646719025, + "up_rise": 1.9595726437021312, + "diff_rise": 4.885227003016894, + "down_abs": 32.12956749180114 + }, + "60": { + "t": 59.18, + "down_rise": 6.676338085722875, + "up_rise": 2.433382113923244, + "diff_rise": 4.242955971799631, + "down_abs": 31.96110593080499 + }, + "75": { + "t": 74.23, + "down_rise": 7.26737125461932, + "up_rise": 3.0675238604402466, + "diff_rise": 4.1998473941790735, + "down_abs": 32.552139099701435 + } + }, + "rep": 1, + "cooldown_s": 0, + "base_at_start": 22.771088546071724 + }, + { + "duty": 30, + "flow": true, + "start_down": 23.787732622425267, + "start_up": 22.070385781761104, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.05, + "down_rise": 5.5185610611475795, + "up_rise": 0.7787091413227927, + "diff_rise": 4.739851919824787, + "down_abs": 29.306293683572846 + }, + "20": { + "t": 19.06, + "down_rise": 6.753885410410646, + "up_rise": 1.4039242398130796, + "diff_rise": 5.349961170597567, + "down_abs": 30.541618032835913 + }, + "25": { + "t": 24.08, + "down_rise": 6.422965005850301, + "up_rise": 1.2474212134531513, + "diff_rise": 5.17554379239715, + "down_abs": 30.210697628275568 + }, + "30": { + "t": 29.09, + "down_rise": 7.169140808820487, + "up_rise": 1.4039242398130796, + "diff_rise": 5.765216569007407, + "down_abs": 30.956873431245754 + }, + "40": { + "t": 39.12, + "down_rise": 7.753601590298899, + "up_rise": 1.4039242398130796, + "diff_rise": 6.34967735048582, + "down_abs": 31.541334212724166 + }, + "50": { + "t": 49.15, + "down_rise": 7.669880184993531, + "up_rise": 2.031345013568398, + "diff_rise": 5.638535171425133, + "down_abs": 31.457612807418798 + }, + "60": { + "t": 59.18, + "down_rise": 7.335754520148043, + "up_rise": 1.0129185486422898, + "diff_rise": 6.322835971505754, + "down_abs": 31.12348714257331 + }, + "75": { + "t": 74.22, + "down_rise": 7.002821334526857, + "up_rise": 0.23330443941455314, + "diff_rise": 6.769516895112304, + "down_abs": 30.790553956952124 + } + }, + "rep": 1, + "cooldown_s": 11, + "base_at_start": 21.99267365190036 + }, + { + "duty": 30, + "flow": false, + "start_down": 27.600799188253063, + "start_up": 23.23960623233387, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.04, + "down_rise": 4.697551863404897, + "up_rise": 0.7835697310736123, + "diff_rise": 3.9139821323312844, + "down_abs": 32.29835105165796 + }, + "20": { + "t": 19.06, + "down_rise": 4.866661514148916, + "up_rise": 0.23470378924031365, + "diff_rise": 4.631957724908602, + "down_abs": 32.46746070240198 + }, + "25": { + "t": 24.07, + "down_rise": 6.404014621416707, + "up_rise": 1.8078168796776595, + "diff_rise": 4.596197741739047, + "down_abs": 34.00481380966977 + }, + "30": { + "t": 29.09, + "down_rise": 7.096686478023514, + "up_rise": 1.9660039230356574, + "diff_rise": 5.130682554987857, + "down_abs": 34.69748566627658 + }, + "40": { + "t": 39.12, + "down_rise": 7.532733613857431, + "up_rise": 2.521030889115025, + "diff_rise": 5.011702724742406, + "down_abs": 35.133532802110494 + }, + "50": { + "t": 49.15, + "down_rise": 8.147405055466038, + "up_rise": 2.839185308441131, + "diff_rise": 5.308219747024907, + "down_abs": 35.7482042437191 + }, + "60": { + "t": 59.18, + "down_rise": 8.767170657113013, + "up_rise": 4.119626971464811, + "diff_rise": 4.647543685648202, + "down_abs": 36.367969845366076 + }, + "75": { + "t": 74.22, + "down_rise": 9.932415429624825, + "up_rise": 4.441824581894082, + "diff_rise": 5.4905908477307435, + "down_abs": 37.53321461787789 + } + }, + "rep": 1, + "cooldown_s": 0, + "base_at_start": 22.927132764254054 + }, + { + "duty": 40, + "flow": true, + "start_down": 24.416328890413524, + "start_up": 22.693113261008648, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.04, + "down_rise": 7.125005322310642, + "up_rise": 1.2515449833496177, + "diff_rise": 5.873460338961024, + "down_abs": 31.541334212724166 + }, + "20": { + "t": 19.06, + "down_rise": 8.475359742477679, + "up_rise": 0.6246937342056071, + "diff_rise": 7.850666008272071, + "down_abs": 32.8916886328912 + }, + "25": { + "t": 24.07, + "down_rise": 9.502321924723617, + "up_rise": 1.330062702398834, + "diff_rise": 8.172259222324783, + "down_abs": 33.91865081513714 + }, + "30": { + "t": 29.09, + "down_rise": 8.816272028024628, + "up_rise": 0.46832554132225823, + "diff_rise": 8.34794648670237, + "down_abs": 33.23260091843815 + }, + "40": { + "t": 39.12, + "down_rise": 8.64564342226629, + "up_rise": 0.3901910693947457, + "diff_rise": 8.255452352871544, + "down_abs": 33.06197231267981 + }, + "50": { + "t": 49.15, + "down_rise": 9.330270414745598, + "up_rise": 0.7811967605655354, + "diff_rise": 8.549073654180063, + "down_abs": 33.74659930515912 + }, + "60": { + "t": 59.18, + "down_rise": 9.158581185830087, + "up_rise": 1.4086175343208538, + "diff_rise": 7.749963651509233, + "down_abs": 33.57491007624361 + }, + "75": { + "t": 74.22, + "down_rise": 9.416250608384246, + "up_rise": 1.7232156294048764, + "diff_rise": 7.6930349789793695, + "down_abs": 33.83257949879777 + } + }, + "rep": 1, + "cooldown_s": 11, + "base_at_start": 22.615168696173612 + }, + { + "duty": 40, + "flow": false, + "start_down": 24.2589533241794, + "start_up": 22.070385781761104, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.04, + "down_rise": 9.832115716181136, + "up_rise": 1.79579147564624, + "diff_rise": 8.036324240534896, + "down_abs": 34.09106904036054 + }, + "20": { + "t": 19.06, + "down_rise": 11.4892509195397, + "up_rise": 2.5823001229496754, + "diff_rise": 8.906950796590024, + "down_abs": 35.7482042437191 + }, + "25": { + "t": 24.07, + "down_rise": 12.913688973948922, + "up_rise": 3.293583054887222, + "diff_rise": 9.6201059190617, + "down_abs": 37.17264229812832 + }, + "30": { + "t": 29.09, + "down_rise": 13.455245927339377, + "up_rise": 3.6902513396877907, + "diff_rise": 9.764994587651586, + "down_abs": 37.71419925151878 + }, + "40": { + "t": 39.12, + "down_rise": 14.920619237806193, + "up_rise": 5.047765962183178, + "diff_rise": 9.872853275623015, + "down_abs": 39.179572561985594 + }, + "50": { + "t": 49.15, + "down_rise": 16.041282908900598, + "up_rise": 5.611045032466848, + "diff_rise": 10.43023787643375, + "down_abs": 40.30023623308 + }, + "60": { + "t": 59.18, + "down_rise": 16.041282908900598, + "up_rise": 6.096015569793394, + "diff_rise": 9.945267339107204, + "down_abs": 40.30023623308 + }, + "75": { + "t": 74.22, + "down_rise": 17.954259807546123, + "up_rise": 6.827421374179096, + "diff_rise": 11.126838433367027, + "down_abs": 42.213213131725524 + } + }, + "rep": 1, + "cooldown_s": 11, + "base_at_start": 22.615168696173612 + }, + { + "duty": 50, + "flow": true, + "start_down": 23.709323959916844, + "start_up": 22.381515612920623, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.05, + "down_rise": 10.641069806668668, + "up_rise": 0.8580906194132467, + "diff_rise": 9.782979187255421, + "down_abs": 34.35039376658551 + }, + "20": { + "t": 19.06, + "down_rise": 11.511715084182072, + "up_rise": 1.4846616444867209, + "diff_rise": 10.02705343969535, + "down_abs": 35.221039044098916 + }, + "25": { + "t": 24.08, + "down_rise": 12.215427126449981, + "up_rise": 1.406217009504644, + "diff_rise": 10.809210116945337, + "down_abs": 35.924751086366825 + }, + "30": { + "t": 29.09, + "down_rise": 12.658645885449232, + "up_rise": 1.406217009504644, + "diff_rise": 11.252428875944588, + "down_abs": 36.367969845366076 + }, + "40": { + "t": 39.12, + "down_rise": 13.283719897207902, + "up_rise": 2.271170291790156, + "diff_rise": 11.012549605417746, + "down_abs": 36.993043857124746 + }, + "50": { + "t": 49.15, + "down_rise": 13.733573974441583, + "up_rise": 1.7988075086338995, + "diff_rise": 11.934766465807684, + "down_abs": 37.44289793435843 + }, + "60": { + "t": 59.18, + "down_rise": 14.277240678118176, + "up_rise": 2.58687736274004, + "diff_rise": 11.690363315378136, + "down_abs": 37.98656463803502 + }, + "75": { + "t": 74.23, + "down_rise": 13.194090281164108, + "up_rise": 2.271170291790156, + "diff_rise": 10.922919989373952, + "down_abs": 36.90341424108095 + } + }, + "rep": 1, + "cooldown_s": 21, + "base_at_start": 22.303690221175657 + }, + { + "duty": 50, + "flow": false, + "start_down": 23.161438802330906, + "start_up": 22.615168696173612, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.04, + "down_rise": 13.117673172779575, + "up_rise": 1.0157821948371293, + "diff_rise": 12.101890977942446, + "down_abs": 36.27911197511048 + }, + "20": { + "t": 19.06, + "down_rise": 14.91615397824745, + "up_rise": 1.7224530898047306, + "diff_rise": 13.19370088844272, + "down_abs": 38.07759278057836 + }, + "25": { + "t": 24.07, + "down_rise": 17.422037722880077, + "up_rise": 3.224937519533512, + "diff_rise": 14.197100203346565, + "down_abs": 40.58347652521098 + }, + "30": { + "t": 29.09, + "down_rise": 18.18362882855064, + "up_rise": 3.7825336880443956, + "diff_rise": 14.401095140506243, + "down_abs": 41.345067630881545 + }, + "40": { + "t": 39.12, + "down_rise": 18.85778368178171, + "up_rise": 4.342525691961612, + "diff_rise": 14.5152579898201, + "down_abs": 42.01922248411262 + }, + "50": { + "t": 49.15, + "down_rise": 21.02724578143409, + "up_rise": 5.7944962404234275, + "diff_rise": 15.232749541010662, + "down_abs": 44.188684583764996 + }, + "60": { + "t": 59.18, + "down_rise": 21.531763198211365, + "up_rise": 6.609300988449547, + "diff_rise": 14.922462209761818, + "down_abs": 44.69320200054227 + }, + "75": { + "t": 74.22, + "down_rise": 23.59636634808146, + "up_rise": 7.6781539325369295, + "diff_rise": 15.91821241554453, + "down_abs": 46.757805150412366 + } + }, + "rep": 1, + "cooldown_s": 21, + "base_at_start": 22.537254480322474 + }, + { + "duty": 10, + "flow": true, + "start_down": 22.303690221175657, + "start_up": 22.927132764254054, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.05, + "down_rise": 2.3489956835351222, + "up_rise": -0.07803784117015766, + "diff_rise": 2.42703352470528, + "down_abs": 24.65268590471078 + }, + "20": { + "t": 19.07, + "down_rise": 2.5857143545545114, + "up_rise": -0.3119640680804423, + "diff_rise": 2.8976784226349537, + "down_abs": 24.88940457573017 + }, + "25": { + "t": 24.08, + "down_rise": 2.5857143545545114, + "up_rise": 0.0, + "diff_rise": 2.5857143545545114, + "down_abs": 24.88940457573017 + }, + "30": { + "t": 29.1, + "down_rise": 2.427861207282774, + "up_rise": -1.2450373541893214, + "diff_rise": 3.6728985614720955, + "down_abs": 24.73155142845843 + }, + "40": { + "t": 39.13, + "down_rise": 3.139523302303644, + "up_rise": -0.5456171513334311, + "diff_rise": 3.685140453637075, + "down_abs": 25.4432135234793 + }, + "50": { + "t": 49.16, + "down_rise": 2.981077623906458, + "up_rise": -0.3119640680804423, + "diff_rise": 3.2930416919869003, + "down_abs": 25.284767845082115 + }, + "60": { + "t": 59.19, + "down_rise": 2.3489956835351222, + "up_rise": -0.5456171513334311, + "diff_rise": 2.8946128348685534, + "down_abs": 24.65268590471078 + }, + "75": { + "t": 74.23, + "down_rise": 2.822805153756235, + "up_rise": 0.23430603807685202, + "diff_rise": 2.588499115679383, + "down_abs": 25.126495374931892 + } + }, + "rep": 2, + "cooldown_s": 32, + "base_at_start": 22.070385781761104 + }, + { + "duty": 10, + "flow": false, + "start_down": 23.23960623233387, + "start_up": 21.682095410064733, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.05, + "down_rise": 1.255468789312431, + "up_rise": 0.6215948111109242, + "diff_rise": 0.6338739782015068, + "down_abs": 24.4950750216463 + }, + "20": { + "t": 19.06, + "down_rise": 2.839185308441131, + "up_rise": 1.0889931360069909, + "diff_rise": 1.75019217243414, + "down_abs": 26.078791540775 + }, + "25": { + "t": 24.08, + "down_rise": 3.2379450772875202, + "up_rise": 1.2450373541893214, + "diff_rise": 1.9929077230981989, + "down_abs": 26.47755130962139 + }, + "30": { + "t": 29.09, + "down_rise": 3.3977912260812104, + "up_rise": 1.4012089203386608, + "diff_rise": 1.9965823057425496, + "down_abs": 26.63739745841508 + }, + "40": { + "t": 39.12, + "down_rise": 3.317843299588958, + "up_rise": 1.0110178509439152, + "diff_rise": 2.306825448645043, + "down_abs": 26.557449531922828 + }, + "50": { + "t": 49.15, + "down_rise": 3.8785455116104117, + "up_rise": 1.6357115851495223, + "diff_rise": 2.2428339264608894, + "down_abs": 27.11815174394428 + }, + "60": { + "t": 59.18, + "down_rise": 3.718088155801354, + "up_rise": 2.184081847342611, + "diff_rise": 1.5340063084587428, + "down_abs": 26.957694388135224 + }, + "75": { + "t": 74.23, + "down_rise": 4.039213206578154, + "up_rise": 2.3410805533427492, + "diff_rise": 1.6981326532354046, + "down_abs": 27.278819438912024 + } + }, + "rep": 2, + "cooldown_s": 0, + "base_at_start": 22.070385781761104 + }, + { + "duty": 15, + "flow": true, + "start_down": 21.914988917022924, + "start_up": 21.75970016295645, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.04, + "down_rise": 3.84564820442597, + "up_rise": 0.6996700797217841, + "diff_rise": 3.145978124704186, + "down_abs": 25.760637121448895 + }, + "20": { + "t": 19.06, + "down_rise": 4.084194108324709, + "up_rise": 0.855468533217163, + "diff_rise": 3.2287255751075463, + "down_abs": 25.999183025347634 + }, + "25": { + "t": 24.07, + "down_rise": 4.482713467195083, + "up_rise": 1.0893947601274476, + "diff_rise": 3.3933187070676354, + "down_abs": 26.397702384218007 + }, + "30": { + "t": 29.09, + "down_rise": 4.4029134324053985, + "up_rise": 0.855468533217163, + "diff_rise": 3.5474448991882355, + "down_abs": 26.317902349428323 + }, + "40": { + "t": 39.12, + "down_rise": 4.642460614899903, + "up_rise": 1.0893947601274476, + "diff_rise": 3.5530658547724556, + "down_abs": 26.557449531922828 + }, + "50": { + "t": 49.15, + "down_rise": 4.722408541392156, + "up_rise": 0.6218154499641741, + "diff_rise": 4.100593091427982, + "down_abs": 26.63739745841508 + }, + "60": { + "t": 59.18, + "down_rise": 4.722408541392156, + "up_rise": 0.855468533217163, + "diff_rise": 3.8669400081749927, + "down_abs": 26.63739745841508 + }, + "75": { + "t": 74.22, + "down_rise": 4.722408541392156, + "up_rise": 0.31068561880465495, + "diff_rise": 4.411722922587501, + "down_abs": 26.63739745841508 + } + }, + "rep": 2, + "cooldown_s": 11, + "base_at_start": 22.615168696173612 + }, + { + "duty": 15, + "flow": false, + "start_down": 23.866177257407344, + "start_up": 22.381515612920623, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.04, + "down_rise": 3.493055946391337, + "up_rise": 0.7799231894102832, + "diff_rise": 2.7131327569810537, + "down_abs": 27.35923320379868 + }, + "20": { + "t": 19.06, + "down_rise": 3.9766837900703536, + "up_rise": 0.3115976480880249, + "diff_rise": 3.6650861419823286, + "down_abs": 27.842861047477697 + }, + "25": { + "t": 24.07, + "down_rise": 4.705959576997486, + "up_rise": 0.7799231894102832, + "diff_rise": 3.926036387587203, + "down_abs": 28.57213683440483 + }, + "30": { + "t": 29.09, + "down_rise": 4.624693720887194, + "up_rise": 1.1710974245278862, + "diff_rise": 3.453596296359308, + "down_abs": 28.490870978294538 + }, + "40": { + "t": 39.12, + "down_rise": 5.276532222062713, + "up_rise": 1.406217009504644, + "diff_rise": 3.8703152125580687, + "down_abs": 29.142709479470057 + }, + "50": { + "t": 49.15, + "down_rise": 5.522004668180273, + "up_rise": 1.7202151824088787, + "diff_rise": 3.801789485771394, + "down_abs": 29.388181925587617 + }, + "60": { + "t": 59.18, + "down_rise": 5.031629898532856, + "up_rise": 1.8774377112587786, + "diff_rise": 3.1541921872740772, + "down_abs": 28.8978071559402 + }, + "75": { + "t": 74.22, + "down_rise": 5.685975685876201, + "up_rise": 1.8774377112587786, + "diff_rise": 3.8085379746174226, + "down_abs": 29.552152943283545 + } + }, + "rep": 2, + "cooldown_s": 0, + "base_at_start": 21.75970016295645 + }, + { + "duty": 20, + "flow": true, + "start_down": 22.849094923083896, + "start_up": 22.537254480322474, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.04, + "down_rise": 4.269056820860385, + "up_rise": 0.23383406574924948, + "diff_rise": 4.0352227551111355, + "down_abs": 27.11815174394428 + }, + "20": { + "t": 19.06, + "down_rise": 4.671127735601033, + "up_rise": 0.23383406574924948, + "diff_rise": 4.437293669851783, + "down_abs": 27.52022265868493 + }, + "25": { + "t": 24.07, + "down_rise": 4.832335891144055, + "up_rise": 0.7805525148917809, + "diff_rise": 4.051783376252274, + "down_abs": 27.68143081422795 + }, + "30": { + "t": 29.09, + "down_rise": 4.349364168185218, + "up_rise": -0.6222655632995497, + "diff_rise": 4.971629731484768, + "down_abs": 27.198459091269115 + }, + "40": { + "t": 39.12, + "down_rise": 4.751704265169167, + "up_rise": -0.15573886740185117, + "diff_rise": 4.907443132571018, + "down_abs": 27.600799188253063 + }, + "50": { + "t": 49.15, + "down_rise": 4.59060588090415, + "up_rise": 0.0, + "diff_rise": 4.59060588090415, + "down_abs": 27.439700803988046 + }, + "60": { + "t": 59.18, + "down_rise": 4.59060588090415, + "up_rise": 0.5460498500809194, + "diff_rise": 4.04455603082323, + "down_abs": 27.439700803988046 + }, + "75": { + "t": 74.23, + "down_rise": 5.3173064284706015, + "up_rise": 0.5460498500809194, + "diff_rise": 4.771256578389682, + "down_abs": 28.166401351554498 + } + }, + "rep": 2, + "cooldown_s": 11, + "base_at_start": 22.849094923083896 + }, + { + "duty": 20, + "flow": false, + "start_down": 25.999183025347634, + "start_up": 21.914988917022924, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.04, + "down_rise": 2.7356667123987677, + "up_rise": 1.559321104551259, + "diff_rise": 1.1763456078475087, + "down_abs": 28.7348497377464 + }, + "20": { + "t": 19.06, + "down_rise": 3.3071106582252128, + "up_rise": 0.7001797791506874, + "diff_rise": 2.6069308790745254, + "down_abs": 29.306293683572846 + }, + "25": { + "t": 24.07, + "down_rise": 3.7172033363073638, + "up_rise": 1.7943350428939198, + "diff_rise": 1.922868293413444, + "down_abs": 29.716386361654997 + }, + "30": { + "t": 29.09, + "down_rise": 4.79137093160449, + "up_rise": 1.4810525493007844, + "diff_rise": 3.3103183823037057, + "down_abs": 30.790553956952124 + }, + "40": { + "t": 39.12, + "down_rise": 5.374784864211222, + "up_rise": 1.7159619739878167, + "diff_rise": 3.658822890223405, + "down_abs": 31.373967889558855 + }, + "50": { + "t": 49.15, + "down_rise": 5.793779283980712, + "up_rise": 2.5013399733906, + "diff_rise": 3.2924393105901117, + "down_abs": 31.792962309328345 + }, + "60": { + "t": 59.18, + "down_rise": 6.214735741545724, + "up_rise": 3.0534040586377387, + "diff_rise": 3.161331682907985, + "down_abs": 32.21391876689336 + }, + "75": { + "t": 74.22, + "down_rise": 6.4682776770543455, + "up_rise": 3.84564820442597, + "diff_rise": 2.6226294726283754, + "down_abs": 32.46746070240198 + } + }, + "rep": 2, + "cooldown_s": 0, + "base_at_start": 21.682095410064733 + }, + { + "duty": 30, + "flow": true, + "start_down": 21.75970016295645, + "start_up": 21.371932016053336, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.05, + "down_rise": 7.46476952166671, + "up_rise": 1.867674216280534, + "diff_rise": 5.597095305386176, + "down_abs": 29.22446968462316 + }, + "20": { + "t": 19.06, + "down_rise": 8.533622465754092, + "up_rise": 1.7113723143500579, + "diff_rise": 6.822250151404035, + "down_abs": 30.29332262871054 + }, + "25": { + "t": 24.08, + "down_rise": 8.781917869879464, + "up_rise": 2.1023780055208476, + "diff_rise": 6.679539864358617, + "down_abs": 30.541618032835913 + }, + "30": { + "t": 29.09, + "down_rise": 9.113976992602886, + "up_rise": 2.1023780055208476, + "diff_rise": 7.0115989870820385, + "down_abs": 30.873677155559335 + }, + "40": { + "t": 39.12, + "down_rise": 8.533622465754092, + "up_rise": 1.3211812449553122, + "diff_rise": 7.21244122079878, + "down_abs": 30.29332262871054 + }, + "50": { + "t": 49.15, + "down_rise": 8.699082197548591, + "up_rise": 0.853961681952569, + "diff_rise": 7.845120515596022, + "down_abs": 30.45878236050504 + }, + "60": { + "t": 59.18, + "down_rise": 9.113976992602886, + "up_rise": 2.6512439473541463, + "diff_rise": 6.46273304524874, + "down_abs": 30.873677155559335 + }, + "75": { + "t": 74.23, + "down_rise": 9.697912644462349, + "up_rise": 2.494245241354008, + "diff_rise": 7.203667403108341, + "down_abs": 31.457612807418798 + } + }, + "rep": 2, + "cooldown_s": 21, + "base_at_start": 22.303690221175657 + }, + { + "duty": 30, + "flow": false, + "start_down": 27.762117959330112, + "start_up": 23.083304330403394, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.05, + "down_rise": 4.030844349998233, + "up_rise": 0.4693087070451156, + "diff_rise": 3.5615356429531175, + "down_abs": 31.792962309328345 + }, + "20": { + "t": 19.06, + "down_rise": 5.299854353349701, + "up_rise": 1.0970187911511289, + "diff_rise": 4.2028355621985725, + "down_abs": 33.06197231267981 + }, + "25": { + "t": 24.08, + "down_rise": 6.156532855807029, + "up_rise": 1.8850886452572695, + "diff_rise": 4.27144421054976, + "down_abs": 33.91865081513714 + }, + "30": { + "t": 29.09, + "down_rise": 7.284008325911145, + "up_rise": 2.280664506244932, + "diff_rise": 5.003343819666213, + "down_abs": 35.04612628524126 + }, + "40": { + "t": 39.12, + "down_rise": 8.428243833879549, + "up_rise": 2.995487210371607, + "diff_rise": 5.432756623507942, + "down_abs": 36.19036179320966 + }, + "50": { + "t": 49.15, + "down_rise": 8.694818078480296, + "up_rise": 3.314398053814614, + "diff_rise": 5.380420024665682, + "down_abs": 36.45693603781041 + }, + "60": { + "t": 59.18, + "down_rise": 9.14129628175084, + "up_rise": 4.034847413540888, + "diff_rise": 5.106448868209952, + "down_abs": 36.90341424108095 + }, + "75": { + "t": 74.23, + "down_rise": 8.873077981186555, + "up_rise": 4.19551510850863, + "diff_rise": 4.677562872677925, + "down_abs": 36.63519594051667 + } + }, + "rep": 2, + "cooldown_s": 0, + "base_at_start": 22.148125674399637 + }, + { + "duty": 40, + "flow": true, + "start_down": 23.317806995214255, + "start_up": 22.459370242678233, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.05, + "down_rise": 8.896111771679102, + "up_rise": 0.936671223645476, + "diff_rise": 7.959440548033626, + "down_abs": 32.21391876689336 + }, + "20": { + "t": 19.06, + "down_rise": 7.972591971184443, + "up_rise": 0.233743018330415, + "diff_rise": 7.738848952854028, + "down_abs": 31.290398966398698 + }, + "25": { + "t": 24.08, + "down_rise": 9.31909357413707, + "up_rise": 0.936671223645476, + "diff_rise": 8.382422350491595, + "down_abs": 32.636900569351326 + }, + "30": { + "t": 29.1, + "down_rise": 10.000238910316398, + "up_rise": 1.1715806483325082, + "diff_rise": 8.82865826198389, + "down_abs": 33.31804590553065 + }, + "40": { + "t": 39.13, + "down_rise": 10.171392946930666, + "up_rise": 1.406807014729111, + "diff_rise": 8.764585932201555, + "down_abs": 33.48919994214492 + }, + "50": { + "t": 49.16, + "down_rise": 10.085771737846244, + "up_rise": 0.8584367525360221, + "diff_rise": 9.227334985310222, + "down_abs": 33.4035787330605 + }, + "60": { + "t": 59.19, + "down_rise": 10.859610072454643, + "up_rise": 1.4852880016800327, + "diff_rise": 9.37432207077461, + "down_abs": 34.1774170676689 + }, + "75": { + "t": 74.24, + "down_rise": 10.085771737846244, + "up_rise": 1.7209528788762896, + "diff_rise": 8.364818858969954, + "down_abs": 33.4035787330605 + } + }, + "rep": 2, + "cooldown_s": 21, + "base_at_start": 21.914988917022924 + }, + { + "duty": 40, + "flow": false, + "start_down": 23.709323959916844, + "start_up": 22.225893698005905, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.05, + "down_rise": 10.037275345242278, + "up_rise": 1.875837097323597, + "diff_rise": 8.161438247918682, + "down_abs": 33.74659930515912 + }, + "20": { + "t": 19.06, + "down_rise": 11.862749246180442, + "up_rise": 2.1117280879724376, + "diff_rise": 9.751021158208005, + "down_abs": 35.572073206097286 + }, + "25": { + "t": 24.08, + "down_rise": 13.104572858049721, + "up_rise": 2.4267922067048744, + "diff_rise": 10.677780651344847, + "down_abs": 36.813896817966565 + }, + "30": { + "t": 29.1, + "down_rise": 14.550688816945637, + "up_rise": 2.9797164573636223, + "diff_rise": 11.570972359582015, + "down_abs": 38.26001277686248 + }, + "40": { + "t": 39.13, + "down_rise": 16.308923022666818, + "up_rise": 4.892258045938377, + "diff_rise": 11.416664976728441, + "down_abs": 40.01824698258366 + }, + "50": { + "t": 49.16, + "down_rise": 16.590912273163156, + "up_rise": 4.97256539326321, + "diff_rise": 11.618346879899946, + "down_abs": 40.30023623308 + }, + "60": { + "t": 59.19, + "down_rise": 17.731600216494428, + "up_rise": 5.859536529615809, + "diff_rise": 11.872063686878619, + "down_abs": 41.44092417641127 + }, + "75": { + "t": 74.24, + "down_rise": 17.34905934983209, + "up_rise": 6.264977280288633, + "diff_rise": 11.084082069543456, + "down_abs": 41.05838330974893 + } + }, + "rep": 2, + "cooldown_s": 11, + "base_at_start": 22.849094923083896 + }, + { + "duty": 50, + "flow": true, + "start_down": 22.771088546071724, + "start_up": 21.99267365190036, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.04, + "down_rise": 10.889621135107348, + "up_rise": 2.1090571434291405, + "diff_rise": 8.780563991678207, + "down_abs": 33.66070968117907 + }, + "20": { + "t": 19.06, + "down_rise": 12.977115697647378, + "up_rise": 1.7950589705249058, + "diff_rise": 11.182056727122472, + "down_abs": 35.7482042437191 + }, + "25": { + "t": 24.07, + "down_rise": 12.275037739169534, + "up_rise": 1.3251333433138939, + "diff_rise": 10.94990439585564, + "down_abs": 35.04612628524126 + }, + "30": { + "t": 29.09, + "down_rise": 12.713073539011987, + "up_rise": 1.0906306785030324, + "diff_rise": 11.622442860508954, + "down_abs": 35.48416208508371 + }, + "40": { + "t": 39.12, + "down_rise": 14.042808271894842, + "up_rise": 2.030502311507121, + "diff_rise": 12.012305960387721, + "down_abs": 36.813896817966565 + }, + "50": { + "t": 49.15, + "down_rise": 14.4015537520566, + "up_rise": 2.1090571434291405, + "diff_rise": 12.29249660862746, + "down_abs": 37.17264229812832 + }, + "60": { + "t": 59.18, + "down_rise": 13.596881299294353, + "up_rise": 1.716650308016483, + "diff_rise": 11.88023099127787, + "down_abs": 36.367969845366076 + }, + "75": { + "t": 74.22, + "down_rise": 14.491523900569177, + "up_rise": 2.975719323760302, + "diff_rise": 11.515804576808875, + "down_abs": 37.2626124466409 + } + }, + "rep": 2, + "cooldown_s": 21, + "base_at_start": 22.615168696173612 + }, + { + "duty": 50, + "flow": false, + "start_down": 24.023175963407482, + "start_up": 22.771088546071724, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.05, + "down_rise": 12.790720854559083, + "up_rise": 0.5467184491425314, + "diff_rise": 12.244002405416552, + "down_abs": 36.813896817966565 + }, + "20": { + "t": 19.06, + "down_rise": 14.511386690160293, + "up_rise": 2.3554068288601684, + "diff_rise": 12.155979861300125, + "down_abs": 38.534562653567775 + }, + "25": { + "t": 24.08, + "down_rise": 17.03520734634145, + "up_rise": 2.830747014738961, + "diff_rise": 14.20446033160249, + "down_abs": 41.05838330974893 + }, + "30": { + "t": 29.09, + "down_rise": 18.190037168318042, + "up_rise": 4.106455005279372, + "diff_rise": 14.08358216303867, + "down_abs": 42.213213131725524 + }, + "40": { + "t": 39.12, + "down_rise": 19.56586402253498, + "up_rise": 4.749134112613206, + "diff_rise": 14.816729909921776, + "down_abs": 43.58903998594246 + }, + "50": { + "t": 49.15, + "down_rise": 20.36678221195905, + "up_rise": 6.289924074234705, + "diff_rise": 14.076858137724344, + "down_abs": 44.38995817536653 + }, + "60": { + "t": 59.18, + "down_rise": 20.873089851136058, + "up_rise": 6.781064397211821, + "diff_rise": 14.092025453924236, + "down_abs": 44.89626581454354 + }, + "75": { + "t": 74.23, + "down_rise": 21.796049900538947, + "up_rise": 6.781064397211821, + "diff_rise": 15.014985503327125, + "down_abs": 45.81922586394643 + } + }, + "rep": 2, + "cooldown_s": 11, + "base_at_start": 22.615168696173612 + }, + { + "duty": 10, + "flow": true, + "start_down": 22.537254480322474, + "start_up": 22.537254480322474, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.04, + "down_rise": 2.510168631689055, + "up_rise": -0.46686869856137037, + "diff_rise": 2.9770373302504254, + "down_abs": 25.04742311201153 + }, + "20": { + "t": 19.06, + "down_rise": 2.431138495338189, + "up_rise": 0.3118404427614223, + "diff_rise": 2.1192980525767666, + "down_abs": 24.968392975660663 + }, + "25": { + "t": 24.07, + "down_rise": 2.747513364759641, + "up_rise": 0.3118404427614223, + "diff_rise": 2.4356729219982185, + "down_abs": 25.284767845082115 + }, + "30": { + "t": 29.09, + "down_rise": 2.747513364759641, + "up_rise": 0.0, + "diff_rise": 2.747513364759641, + "down_abs": 25.284767845082115 + }, + "40": { + "t": 39.12, + "down_rise": 2.273203042535556, + "up_rise": -0.15573886740185117, + "diff_rise": 2.428941909937407, + "down_abs": 24.81045752285803 + }, + "50": { + "t": 49.15, + "down_rise": 2.273203042535556, + "up_rise": -0.07788423764424124, + "diff_rise": 2.3510872801797973, + "down_abs": 24.81045752285803 + }, + "60": { + "t": 59.18, + "down_rise": 2.8267143563258514, + "up_rise": -0.23356425914681722, + "diff_rise": 3.0602786154726687, + "down_abs": 25.363968836648326 + }, + "75": { + "t": 74.22, + "down_rise": 1.8790744100910501, + "up_rise": -1.242801235760453, + "diff_rise": 3.121875645851503, + "down_abs": 24.416328890413524 + } + }, + "rep": 3, + "cooldown_s": 32, + "base_at_start": 22.537254480322474 + }, + { + "duty": 10, + "flow": false, + "start_down": 23.083304330403394, + "start_up": 22.303690221175657, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.05, + "down_rise": 2.3599091930759073, + "up_rise": 0.6234425430783972, + "diff_rise": 1.73646664999751, + "down_abs": 25.4432135234793 + }, + "20": { + "t": 19.06, + "down_rise": 2.439197969426573, + "up_rise": 0.07782539174496605, + "diff_rise": 2.361372577681607, + "down_abs": 25.522502299829966 + }, + "25": { + "t": 24.08, + "down_rise": 3.2345980190249293, + "up_rise": 0.15568002150257598, + "diff_rise": 3.0789179975223533, + "down_abs": 26.317902349428323 + }, + "30": { + "t": 29.09, + "down_rise": 2.91587869494424, + "up_rise": 0.6234425430783972, + "diff_rise": 2.292436151865843, + "down_abs": 25.999183025347634 + }, + "40": { + "t": 39.12, + "down_rise": 3.075143001120523, + "up_rise": 1.0141167740385981, + "diff_rise": 2.061026227081925, + "down_abs": 26.158447331523917 + }, + "50": { + "t": 49.15, + "down_rise": 3.3942469792179963, + "up_rise": 0.389423039832991, + "diff_rise": 3.0048239393850054, + "down_abs": 26.47755130962139 + }, + "60": { + "t": 59.18, + "down_rise": 3.714139727856491, + "up_rise": 0.8577485811552492, + "diff_rise": 2.8563911467012417, + "down_abs": 26.797444058259885 + }, + "75": { + "t": 74.23, + "down_rise": 4.034847413540888, + "up_rise": 1.1706198003985264, + "diff_rise": 2.8642276131423614, + "down_abs": 27.11815174394428 + } + }, + "rep": 3, + "cooldown_s": 0, + "base_at_start": 20.752756284325415 + }, + { + "duty": 15, + "flow": true, + "start_down": 22.303690221175657, + "start_up": 22.381515612920623, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.04, + "down_rise": 2.5067673016823733, + "up_rise": -0.2333899385209861, + "diff_rise": 2.7401572402033594, + "down_abs": 24.81045752285803 + }, + "20": { + "t": 19.06, + "down_rise": 4.014212128252666, + "up_rise": 0.4675793101632735, + "diff_rise": 3.5466328180893925, + "down_abs": 26.317902349428323 + }, + "25": { + "t": 24.07, + "down_rise": 2.822805153756235, + "up_rise": 0.07785462975760993, + "diff_rise": 2.744950523998625, + "down_abs": 25.126495374931892 + }, + "30": { + "t": 29.09, + "down_rise": 4.173861088445733, + "up_rise": 0.38957293315110064, + "diff_rise": 3.7842881552946324, + "down_abs": 26.47755130962139 + }, + "40": { + "t": 39.12, + "down_rise": 3.615931162644358, + "up_rise": -0.3111298311595192, + "diff_rise": 3.9270609938038774, + "down_abs": 25.919621383820015 + }, + "50": { + "t": 49.15, + "down_rise": 3.377523481222852, + "up_rise": -0.3111298311595192, + "diff_rise": 3.688653312382371, + "down_abs": 25.68121370239851 + }, + "60": { + "t": 59.18, + "down_rise": 3.377523481222852, + "up_rise": -0.9320803597239546, + "diff_rise": 4.3096038409468065, + "down_abs": 25.68121370239851 + }, + "75": { + "t": 74.23, + "down_rise": 3.695492804171977, + "up_rise": 0.5456171513334311, + "diff_rise": 3.1498756528385456, + "down_abs": 25.999183025347634 + } + }, + "rep": 3, + "cooldown_s": 11, + "base_at_start": 22.148125674399637 + }, + { + "duty": 15, + "flow": false, + "start_down": 24.023175963407482, + "start_up": 21.371932016053336, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.05, + "down_rise": 3.0949757805367994, + "up_rise": 0.7761936583463012, + "diff_rise": 2.3187821221904983, + "down_abs": 27.11815174394428 + }, + "20": { + "t": 19.06, + "down_rise": 3.8196850840702155, + "up_rise": 2.024109450270373, + "diff_rise": 1.7955756337998423, + "down_abs": 27.842861047477697 + }, + "25": { + "t": 24.08, + "down_rise": 3.8196850840702155, + "up_rise": 2.3373919438635085, + "diff_rise": 1.482293140206707, + "down_abs": 27.842861047477697 + }, + "30": { + "t": 29.09, + "down_rise": 4.711673774338919, + "up_rise": 1.9458749791609193, + "diff_rise": 2.765798795178, + "down_abs": 28.7348497377464 + }, + "40": { + "t": 39.12, + "down_rise": 4.874631192532718, + "up_rise": 2.4158006063719313, + "diff_rise": 2.4588305861607864, + "down_abs": 28.8978071559402 + }, + "50": { + "t": 49.15, + "down_rise": 5.201293721215677, + "up_rise": 2.57272622830493, + "diff_rise": 2.628567492910747, + "down_abs": 29.22446968462316 + }, + "60": { + "t": 59.18, + "down_rise": 5.857709865577874, + "up_rise": 2.8083911055011868, + "diff_rise": 3.0493187600766873, + "down_abs": 29.880885828985356 + }, + "75": { + "t": 74.23, + "down_rise": 6.352841419497167, + "up_rise": 3.2019285484947204, + "diff_rise": 3.150912871002447, + "down_abs": 30.37601738290465 + } + }, + "rep": 3, + "cooldown_s": 0, + "base_at_start": 22.615168696173612 + }, + { + "duty": 20, + "flow": true, + "start_down": 22.927132764254054, + "start_up": 22.693113261008648, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.05, + "down_rise": 2.8335043571948404, + "up_rise": -0.7781243439857235, + "diff_rise": 3.611628701180564, + "down_abs": 25.760637121448895 + }, + "20": { + "t": 19.06, + "down_rise": 4.512568039733992, + "up_rise": 0.0, + "diff_rise": 4.512568039733992, + "down_abs": 27.439700803988046 + }, + "25": { + "t": 24.08, + "down_rise": 4.351686674657969, + "up_rise": 0.2340195032454062, + "diff_rise": 4.117667171412563, + "down_abs": 27.278819438912024 + }, + "30": { + "t": 29.09, + "down_rise": 5.482532172342985, + "up_rise": 0.07797528506307572, + "diff_rise": 5.4045568872799095, + "down_abs": 28.40966493659704 + }, + "40": { + "t": 39.13, + "down_rise": 5.401385509757517, + "up_rise": 0.07797528506307572, + "diff_rise": 5.323410224694442, + "down_abs": 28.32851827401157 + }, + "50": { + "t": 49.16, + "down_rise": 4.996527739546082, + "up_rise": -0.15585878068617376, + "diff_rise": 5.152386520232255, + "down_abs": 27.923660503800136 + }, + "60": { + "t": 59.19, + "down_rise": 4.593089894430875, + "up_rise": 0.0, + "diff_rise": 4.593089894430875, + "down_abs": 27.52022265868493 + }, + "75": { + "t": 74.23, + "down_rise": 4.834985195076058, + "up_rise": 1.094619361416619, + "diff_rise": 3.740365833659439, + "down_abs": 27.762117959330112 + } + }, + "rep": 3, + "cooldown_s": 11, + "base_at_start": 21.371932016053336 + }, + { + "duty": 20, + "flow": false, + "start_down": 25.522502299829966, + "start_up": 22.381515612920623, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.05, + "down_rise": 3.212347437916435, + "up_rise": 1.1710974245278862, + "diff_rise": 2.0412500133885487, + "down_abs": 28.7348497377464 + }, + "20": { + "t": 19.06, + "down_rise": 4.111734325113275, + "up_rise": 0.7799231894102832, + "diff_rise": 3.331811135702992, + "down_abs": 29.63423662494324 + }, + "25": { + "t": 24.08, + "down_rise": 4.605639613783524, + "up_rise": 1.3278083469962212, + "diff_rise": 3.2778312667873024, + "down_abs": 30.12814191361349 + }, + "30": { + "t": 29.09, + "down_rise": 4.770820328880575, + "up_rise": 1.5631426314376426, + "diff_rise": 3.207677697442932, + "down_abs": 30.29332262871054 + }, + "40": { + "t": 39.12, + "down_rise": 4.853515083074683, + "up_rise": 1.5631426314376426, + "diff_rise": 3.2903724516370403, + "down_abs": 30.37601738290465 + }, + "50": { + "t": 49.15, + "down_rise": 5.684403247241903, + "up_rise": 2.271170291790156, + "diff_rise": 3.413232955451747, + "down_abs": 31.20690554707187 + }, + "60": { + "t": 59.18, + "down_rise": 5.10202257308498, + "up_rise": 2.428941909937407, + "diff_rise": 2.673080663147573, + "down_abs": 30.624524872914947 + }, + "75": { + "t": 74.23, + "down_rise": 6.860362558794179, + "up_rise": 3.1409866869093435, + "diff_rise": 3.7193758718848358, + "down_abs": 32.382864858624146 + } + }, + "rep": 3, + "cooldown_s": 0, + "base_at_start": 22.225893698005905 + }, + { + "duty": 30, + "flow": true, + "start_down": 23.474310021574183, + "start_up": 22.459370242678233, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.04, + "down_rise": 5.750159663048976, + "up_rise": 0.5458321996107429, + "diff_rise": 5.204327463438233, + "down_abs": 29.22446968462316 + }, + "20": { + "t": 19.06, + "down_rise": 5.097826812830647, + "up_rise": -0.3889844609171291, + "diff_rise": 5.486811273747776, + "down_abs": 28.57213683440483 + }, + "25": { + "t": 24.07, + "down_rise": 7.06730801126173, + "up_rise": 1.2499537172386113, + "diff_rise": 5.817354294023119, + "down_abs": 30.541618032835913 + }, + "30": { + "t": 29.09, + "down_rise": 6.406575807411173, + "up_rise": -0.6220390329586962, + "diff_rise": 7.028614840369869, + "down_abs": 29.880885828985356 + }, + "40": { + "t": 39.12, + "down_rise": 6.159926603369058, + "up_rise": 0.936671223645476, + "diff_rise": 5.223255379723582, + "down_abs": 29.63423662494324 + }, + "50": { + "t": 49.15, + "down_rise": 7.233193333895088, + "up_rise": 1.1715806483325082, + "diff_rise": 6.061612685562579, + "down_abs": 30.70750335546927 + }, + "60": { + "t": 59.18, + "down_rise": 7.732595525497686, + "up_rise": 0.936671223645476, + "diff_rise": 6.79592430185221, + "down_abs": 31.20690554707187 + }, + "75": { + "t": 74.23, + "down_rise": 6.819012607136358, + "up_rise": 0.3117183033934907, + "diff_rise": 6.507294303742867, + "down_abs": 30.29332262871054 + } + }, + "rep": 3, + "cooldown_s": 11, + "base_at_start": 22.459370242678233 + }, + { + "duty": 30, + "flow": false, + "start_down": 27.68143081422795, + "start_up": 21.914988917022924, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.05, + "down_rise": 4.4481366775731885, + "up_rise": 2.422632868955418, + "diff_rise": 2.0255038086177706, + "down_abs": 32.12956749180114 + }, + "20": { + "t": 19.06, + "down_rise": 5.5511701042102, + "up_rise": 2.5013399733906, + "diff_rise": 3.0498301308196005, + "down_abs": 33.23260091843815 + }, + "25": { + "t": 24.08, + "down_rise": 6.409638226132586, + "up_rise": 2.974415658707244, + "diff_rise": 3.435222567425342, + "down_abs": 34.09106904036054 + }, + "30": { + "t": 29.09, + "down_rise": 6.842317626623583, + "up_rise": 3.3697789280591905, + "diff_rise": 3.4725386985643922, + "down_abs": 34.523748440851534 + }, + "40": { + "t": 39.12, + "down_rise": 8.154994567882397, + "up_rise": 4.004632466797091, + "diff_rise": 4.150362101085307, + "down_abs": 35.83642538211035 + }, + "50": { + "t": 49.16, + "down_rise": 8.775505223582456, + "up_rise": 4.4029134324053985, + "diff_rise": 4.372591791177058, + "down_abs": 36.45693603781041 + }, + "60": { + "t": 59.19, + "down_rise": 9.401355508556094, + "up_rise": 4.80240658038889, + "diff_rise": 4.598948928167204, + "down_abs": 37.082786322784045 + }, + "75": { + "t": 74.23, + "down_rise": 8.953765126288715, + "up_rise": 4.482713467195083, + "diff_rise": 4.471051659093632, + "down_abs": 36.63519594051667 + } + }, + "rep": 3, + "cooldown_s": 0, + "base_at_start": 22.615168696173612 + }, + { + "duty": 40, + "flow": true, + "start_down": 22.303690221175657, + "start_up": 21.21699857403314, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.04, + "down_rise": 8.903215325896213, + "up_rise": 1.1645170388874817, + "diff_rise": 7.738698287008731, + "down_abs": 31.20690554707187 + }, + "20": { + "t": 19.06, + "down_rise": 9.657415709629333, + "up_rise": 0.5427015889233076, + "diff_rise": 9.114714120706026, + "down_abs": 31.96110593080499 + }, + "25": { + "t": 24.07, + "down_rise": 11.442909083983466, + "up_rise": 2.963324547521381, + "diff_rise": 8.479584536462085, + "down_abs": 33.74659930515912 + }, + "30": { + "t": 29.09, + "down_rise": 10.502984590555684, + "up_rise": 1.6320963490507552, + "diff_rise": 8.870888241504929, + "down_abs": 32.80667481173134 + }, + "40": { + "t": 39.12, + "down_rise": 11.185509720969264, + "up_rise": 2.1008084211811138, + "diff_rise": 9.08470129978815, + "down_abs": 33.48919994214492 + }, + "50": { + "t": 49.14, + "down_rise": 10.418055411143712, + "up_rise": 1.6320963490507552, + "diff_rise": 8.785959062092957, + "down_abs": 32.72174563231937 + }, + "60": { + "t": 59.18, + "down_rise": 11.271219855067955, + "up_rise": 2.335614463415368, + "diff_rise": 8.935605391652587, + "down_abs": 33.57491007624361 + }, + "75": { + "t": 74.22, + "down_rise": 11.960168233396018, + "up_rise": 2.5707340483921257, + "diff_rise": 9.389434185003893, + "down_abs": 34.263858454571675 + } + }, + "rep": 3, + "cooldown_s": 21, + "base_at_start": 22.537254480322474 + }, + { + "duty": 40, + "flow": false, + "start_down": 23.787732622425267, + "start_up": 21.837331209719537, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.05, + "down_rise": 9.274239690254547, + "up_rise": 1.3241075926113695, + "diff_rise": 7.950132097643177, + "down_abs": 33.06197231267981 + }, + "20": { + "t": 19.06, + "down_rise": 12.225449352429564, + "up_rise": 2.5002905762588057, + "diff_rise": 9.725158776170758, + "down_abs": 36.01318197485483 + }, + "25": { + "t": 24.08, + "down_rise": 12.402629170784394, + "up_rise": 2.264399585609965, + "diff_rise": 10.138229585174429, + "down_abs": 36.19036179320966 + }, + "30": { + "t": 29.09, + "down_rise": 14.563673418251312, + "up_rise": 4.0822901741004785, + "diff_rise": 10.481383244150834, + "down_abs": 38.35140604067658 + }, + "40": { + "t": 39.12, + "down_rise": 15.856437692415511, + "up_rise": 4.560371174498471, + "diff_rise": 11.29606651791704, + "down_abs": 39.64417031484078 + }, + "50": { + "t": 49.15, + "down_rise": 17.175381522206976, + "up_rise": 5.200565771412641, + "diff_rise": 11.974815750794335, + "down_abs": 40.96311414463224 + }, + "60": { + "t": 59.18, + "down_rise": 16.890439109165527, + "up_rise": 5.6828914489653926, + "diff_rise": 11.207547660200134, + "down_abs": 40.678171731590794 + }, + "75": { + "t": 74.23, + "down_rise": 17.557335008456278, + "up_rise": 6.978966452580721, + "diff_rise": 10.578368555875556, + "down_abs": 41.345067630881545 + } + }, + "rep": 3, + "cooldown_s": 11, + "base_at_start": 22.693113261008648 + }, + { + "duty": 50, + "flow": true, + "start_down": 22.615168696173612, + "start_up": 21.682095410064733, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.04, + "down_rise": 10.532074536813013, + "up_rise": 0.6215948111109242, + "diff_rise": 9.91047972570209, + "down_abs": 33.147243232986625 + }, + "20": { + "t": 19.06, + "down_rise": 12.430957589067646, + "up_rise": 1.9488554809460084, + "diff_rise": 10.482102108121637, + "down_abs": 35.04612628524126 + }, + "25": { + "t": 24.07, + "down_rise": 13.221256685936737, + "up_rise": 2.0272285498521114, + "diff_rise": 11.194028136084626, + "down_abs": 35.83642538211035 + }, + "30": { + "t": 29.09, + "down_rise": 13.752801149192464, + "up_rise": 2.1056372123605342, + "diff_rise": 11.64716393683193, + "down_abs": 36.367969845366076 + }, + "40": { + "t": 39.12, + "down_rise": 13.575193097036049, + "up_rise": 1.9488554809460084, + "diff_rise": 11.62633761609004, + "down_abs": 36.19036179320966 + }, + "50": { + "t": 49.15, + "down_rise": 14.377875160951135, + "up_rise": 2.3410805533427492, + "diff_rise": 12.036794607608385, + "down_abs": 36.993043857124746 + }, + "60": { + "t": 59.18, + "down_rise": 14.28824554490734, + "up_rise": 3.0494560183936983, + "diff_rise": 11.238789526513642, + "down_abs": 36.90341424108095 + }, + "75": { + "t": 74.22, + "down_rise": 14.109322238274785, + "up_rise": 2.8917651544833234, + "diff_rise": 11.217557083791462, + "down_abs": 36.7244909344484 + } + }, + "rep": 3, + "cooldown_s": 32, + "base_at_start": 22.381515612920623 + }, + { + "duty": 50, + "flow": false, + "start_down": 22.070385781761104, + "start_up": 21.604516584730902, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.04, + "down_rise": 13.677818461957997, + "up_rise": 2.104807375185942, + "diff_rise": 11.573011086772055, + "down_abs": 35.7482042437191 + }, + "20": { + "t": 19.06, + "down_rise": 16.281020258915476, + "up_rise": 1.7132904104833528, + "diff_rise": 14.567729848432123, + "down_abs": 38.35140604067658 + }, + "25": { + "t": 24.07, + "down_rise": 17.85413907062849, + "up_rise": 3.363876390929761, + "diff_rise": 14.49026267969873, + "down_abs": 39.924524852389595 + }, + "30": { + "t": 29.09, + "down_rise": 18.60778594982969, + "up_rise": 3.601093570638625, + "diff_rise": 15.006692379191065, + "down_abs": 40.678171731590794 + }, + "40": { + "t": 39.12, + "down_rise": 21.320137794862603, + "up_rise": 5.674302854181121, + "diff_rise": 15.645834940681482, + "down_abs": 43.39052357662371 + }, + "50": { + "t": 49.15, + "down_rise": 22.319572393605426, + "up_rise": 6.319143919069234, + "diff_rise": 16.000428474536193, + "down_abs": 44.38995817536653 + }, + "60": { + "t": 59.18, + "down_rise": 22.825880032782436, + "up_rise": 7.2932905712092975, + "diff_rise": 15.532589461573139, + "down_abs": 44.89626581454354 + }, + "75": { + "t": 74.22, + "down_rise": 24.372771856894474, + "up_rise": 8.771500798173747, + "diff_rise": 15.601271058720727, + "down_abs": 46.44315763865558 + } + }, + "rep": 3, + "cooldown_s": 21, + "base_at_start": 22.225893698005905 + }, + { + "duty": 10, + "flow": true, + "start_down": 22.537254480322474, + "start_up": 22.148125674399637, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.04, + "down_rise": 2.431138495338189, + "up_rise": 0.3891288059228373, + "diff_rise": 2.0420096894153517, + "down_abs": 24.968392975660663 + }, + "20": { + "t": 19.06, + "down_rise": 2.510168631689055, + "up_rise": 0.7009692486842596, + "diff_rise": 1.8091993830047954, + "down_abs": 25.04742311201153 + }, + "25": { + "t": 24.07, + "down_rise": 2.431138495338189, + "up_rise": 0.2333899385209861, + "diff_rise": 2.197748556817203, + "down_abs": 24.968392975660663 + }, + "30": { + "t": 29.09, + "down_rise": 2.668355675047053, + "up_rise": 0.6229628716720867, + "diff_rise": 2.045392803374966, + "down_abs": 25.205610155369527 + }, + "40": { + "t": 39.12, + "down_rise": 1.8790744100910501, + "up_rise": -0.07773989263853309, + "diff_rise": 1.9568143027295832, + "down_abs": 24.416328890413524 + }, + "50": { + "t": 49.15, + "down_rise": 2.668355675047053, + "up_rise": 0.857076767889339, + "diff_rise": 1.811278907157714, + "down_abs": 25.205610155369527 + }, + "60": { + "t": 59.18, + "down_rise": 1.7216988438569274, + "up_rise": -0.07773989263853309, + "diff_rise": 1.7994387364954605, + "down_abs": 24.2589533241794 + }, + "75": { + "t": 74.22, + "down_rise": 1.8003673056558682, + "up_rise": -0.07773989263853309, + "diff_rise": 1.8781071982944013, + "down_abs": 24.337621785978342 + } + }, + "rep": 4, + "cooldown_s": 33, + "base_at_start": 22.381515612920623 + }, + { + "duty": 10, + "flow": false, + "start_down": 22.381515612920623, + "start_up": 21.914988917022924, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.04, + "down_rise": 2.903252232161492, + "up_rise": 0.8560996290487992, + "diff_rise": 2.0471526031126928, + "down_abs": 25.284767845082115 + }, + "20": { + "t": 19.06, + "down_rise": 3.299698089477886, + "up_rise": 0.4665266958976986, + "diff_rise": 2.833171393580187, + "down_abs": 25.68121370239851 + }, + "25": { + "t": 24.07, + "down_rise": 3.7769317186032936, + "up_rise": 0.6222655632995497, + "diff_rise": 3.154666155303744, + "down_abs": 26.158447331523917 + }, + "30": { + "t": 29.09, + "down_rise": 4.096035696700767, + "up_rise": 0.6222655632995497, + "diff_rise": 3.473770133401217, + "down_abs": 26.47755130962139 + }, + "40": { + "t": 39.12, + "down_rise": 4.016186771297384, + "up_rise": 0.0, + "diff_rise": 4.016186771297384, + "down_abs": 26.397702384218007 + }, + "50": { + "t": 49.15, + "down_rise": 4.576178775214601, + "up_rise": 1.0121438472311297, + "diff_rise": 3.564034927983471, + "down_abs": 26.957694388135224 + }, + "60": { + "t": 59.18, + "down_rise": 4.255881845494457, + "up_rise": 0.934106006060972, + "diff_rise": 3.321775839433485, + "down_abs": 26.63739745841508 + }, + "75": { + "t": 74.22, + "down_rise": 4.977717590878058, + "up_rise": 1.8727437054023426, + "diff_rise": 3.104973885475715, + "down_abs": 27.35923320379868 + } + }, + "rep": 4, + "cooldown_s": 0, + "base_at_start": 22.070385781761104 + }, + { + "duty": 15, + "flow": true, + "start_down": 22.381515612920623, + "start_up": 22.148125674399637, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.04, + "down_rise": 2.9824532237277026, + "up_rise": -0.3107944646801002, + "diff_rise": 3.293247688407803, + "down_abs": 25.363968836648326 + }, + "20": { + "t": 19.06, + "down_rise": 3.538105770899392, + "up_rise": 0.544987586609011, + "diff_rise": 2.993118184290381, + "down_abs": 25.919621383820015 + }, + "25": { + "t": 24.07, + "down_rise": 3.3791215085282715, + "up_rise": -0.1554520224992757, + "diff_rise": 3.5345735310275472, + "down_abs": 25.760637121448895 + }, + "30": { + "t": 29.09, + "down_rise": 3.9363867365077, + "up_rise": 0.7009692486842596, + "diff_rise": 3.2354174878234403, + "down_abs": 26.317902349428323 + }, + "40": { + "t": 39.12, + "down_rise": 3.7769317186032936, + "up_rise": 0.2333899385209861, + "diff_rise": 3.5435417800823075, + "down_abs": 26.158447331523917 + }, + "50": { + "t": 49.15, + "down_rise": 4.496027938430473, + "up_rise": 0.857076767889339, + "diff_rise": 3.6389511705411337, + "down_abs": 26.877543551351096 + }, + "60": { + "t": 59.18, + "down_rise": 4.016186771297384, + "up_rise": 0.9351786560037567, + "diff_rise": 3.0810081152936277, + "down_abs": 26.397702384218007 + }, + "75": { + "t": 74.22, + "down_rise": 3.9363867365077, + "up_rise": 0.3891288059228373, + "diff_rise": 3.5472579305848626, + "down_abs": 26.317902349428323 + } + }, + "rep": 4, + "cooldown_s": 11, + "base_at_start": 21.44943525319667 + }, + { + "duty": 15, + "flow": false, + "start_down": 24.4950750216463, + "start_up": 22.070385781761104, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.05, + "down_rise": 2.703384069622814, + "up_rise": 0.6227274792475441, + "diff_rise": 2.08065659037527, + "down_abs": 27.198459091269115 + }, + "20": { + "t": 19.06, + "down_rise": 3.1057241666067625, + "up_rise": 0.544782914412508, + "diff_rise": 2.5609412521942545, + "down_abs": 27.600799188253063 + }, + "25": { + "t": 24.08, + "down_rise": 3.509441733004678, + "up_rise": 1.169220450572766, + "diff_rise": 2.340221282431912, + "down_abs": 28.00451675465098 + }, + "30": { + "t": 29.09, + "down_rise": 3.186355792581651, + "up_rise": 0.8567469824929503, + "diff_rise": 2.3296088100887005, + "down_abs": 27.68143081422795 + }, + "40": { + "t": 39.12, + "down_rise": 3.6713263299081973, + "up_rise": 1.5605651092496373, + "diff_rise": 2.11076122065856, + "down_abs": 28.166401351554498 + }, + "50": { + "t": 49.15, + "down_rise": 4.729394662976858, + "up_rise": 1.9527901816463782, + "diff_rise": 2.77660448133048, + "down_abs": 29.22446968462316 + }, + "60": { + "t": 59.18, + "down_rise": 5.057077921637244, + "up_rise": 2.031345013568398, + "diff_rise": 3.0257329080688464, + "down_abs": 29.552152943283545 + }, + "75": { + "t": 74.23, + "down_rise": 5.7156226066292675, + "up_rise": 2.9770373302504254, + "diff_rise": 2.738585276378842, + "down_abs": 30.210697628275568 + } + }, + "rep": 4, + "cooldown_s": 0, + "base_at_start": 22.148125674399637 + }, + { + "duty": 20, + "flow": true, + "start_down": 22.381515612920623, + "start_up": 22.225893698005905, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.05, + "down_rise": 4.816943478348492, + "up_rise": 0.15562191491471822, + "diff_rise": 4.6613215634337735, + "down_abs": 27.198459091269115 + }, + "20": { + "t": 19.06, + "down_rise": 4.335879884491192, + "up_rise": -0.3885624882863681, + "diff_rise": 4.72444237277756, + "down_abs": 26.717395497411815 + }, + "25": { + "t": 24.08, + "down_rise": 5.623001141730356, + "up_rise": -0.07776802360626789, + "diff_rise": 5.700769165336624, + "down_abs": 28.00451675465098 + }, + "30": { + "t": 29.09, + "down_rise": 4.255881845494457, + "up_rise": 0.46721956300274314, + "diff_rise": 3.788662282491714, + "down_abs": 26.63739745841508 + }, + "40": { + "t": 39.12, + "down_rise": 5.461345434557074, + "up_rise": 0.7793087442830711, + "diff_rise": 4.682036690274003, + "down_abs": 27.842861047477697 + }, + "50": { + "t": 49.15, + "down_rise": 5.461345434557074, + "up_rise": 0.5451948480658189, + "diff_rise": 4.916150586491256, + "down_abs": 27.842861047477697 + }, + "60": { + "t": 59.18, + "down_rise": 5.542144890879513, + "up_rise": 0.46721956300274314, + "diff_rise": 5.07492532787677, + "down_abs": 27.923660503800136 + }, + "75": { + "t": 74.23, + "down_rise": 4.656381368211555, + "up_rise": 0.3113607823165694, + "diff_rise": 4.345020585894986, + "down_abs": 27.037896981132178 + } + }, + "rep": 4, + "cooldown_s": 11, + "base_at_start": 21.914988917022924 + }, + { + "duty": 20, + "flow": false, + "start_down": 25.205610155369527, + "start_up": 22.615168696173612, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.04, + "down_rise": 3.366526679035303, + "up_rise": 0.780872770150097, + "diff_rise": 2.585653908885206, + "down_abs": 28.57213683440483 + }, + "20": { + "t": 19.06, + "down_rise": 3.5292395823768743, + "up_rise": 0.23392622691028464, + "diff_rise": 3.2953133554665897, + "down_abs": 28.7348497377464 + }, + "25": { + "t": 24.07, + "down_rise": 4.51077620628547, + "up_rise": 0.46813563422978177, + "diff_rise": 4.042640572055689, + "down_abs": 29.716386361654997 + }, + "30": { + "t": 29.09, + "down_rise": 5.253172205135513, + "up_rise": 1.0941552637432324, + "diff_rise": 4.159016941392281, + "down_abs": 30.45878236050504 + }, + "40": { + "t": 39.12, + "down_rise": 5.584943801582597, + "up_rise": 1.4865620991558899, + "diff_rise": 4.098381702426707, + "down_abs": 30.790553956952124 + }, + "50": { + "t": 49.15, + "down_rise": 6.168357734189328, + "up_rise": 1.8011601942399125, + "diff_rise": 4.367197539949416, + "down_abs": 31.373967889558855 + }, + "60": { + "t": 59.18, + "down_rise": 6.41952244474794, + "up_rise": 2.3532242794870513, + "diff_rise": 4.066298165260889, + "down_abs": 31.625132600117468 + }, + "75": { + "t": 74.22, + "down_rise": 7.00830861152383, + "up_rise": 2.828044827305689, + "diff_rise": 4.180263784218141, + "down_abs": 32.21391876689336 + } + }, + "rep": 4, + "cooldown_s": 0, + "base_at_start": 22.459370242678233 + }, + { + "duty": 30, + "flow": true, + "start_down": 22.459370242678233, + "start_up": 22.303690221175657, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.05, + "down_rise": 6.846923440894614, + "up_rise": 0.23356425914681722, + "diff_rise": 6.613359181747796, + "down_abs": 29.306293683572846 + }, + "20": { + "t": 19.06, + "down_rise": 7.421515586307123, + "up_rise": 0.389423039832991, + "diff_rise": 7.032092546474132, + "down_abs": 29.880885828985356 + }, + "25": { + "t": 24.08, + "down_rise": 7.999412117826807, + "up_rise": 1.1706198003985264, + "diff_rise": 6.828792317428281, + "down_abs": 30.45878236050504 + }, + "30": { + "t": 29.09, + "down_rise": 7.339232367540376, + "up_rise": -0.23330443941455314, + "diff_rise": 7.572536806954929, + "down_abs": 29.79860261021861 + }, + "40": { + "t": 39.12, + "down_rise": 8.08224779015768, + "up_rise": 0.4673983248960667, + "diff_rise": 7.614849465261614, + "down_abs": 30.541618032835913 + }, + "50": { + "t": 49.15, + "down_rise": 8.08224779015768, + "up_rise": 0.4673983248960667, + "diff_rise": 7.614849465261614, + "down_abs": 30.541618032835913 + }, + "60": { + "t": 59.18, + "down_rise": 8.248133112791038, + "up_rise": 0.9359160111582128, + "diff_rise": 7.312217101632825, + "down_abs": 30.70750335546927 + }, + "75": { + "t": 74.23, + "down_rise": 8.831028723720465, + "up_rise": 1.2489228162728523, + "diff_rise": 7.5821059074476125, + "down_abs": 31.290398966398698 + } + }, + "rep": 4, + "cooldown_s": 22, + "base_at_start": 22.225893698005905 + }, + { + "duty": 30, + "flow": false, + "start_down": 22.771088546071724, + "start_up": 21.914988917022924, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.05, + "down_rise": 7.687693814433317, + "up_rise": 0.7001797791506874, + "diff_rise": 6.987514035282629, + "down_abs": 30.45878236050504 + }, + "20": { + "t": 19.06, + "down_rise": 9.190017384733267, + "up_rise": 1.1683154133804692, + "diff_rise": 8.021701971352798, + "down_abs": 31.96110593080499 + }, + "25": { + "t": 24.08, + "down_rise": 9.865812023279602, + "up_rise": 1.1683154133804692, + "diff_rise": 8.697496609899133, + "down_abs": 32.636900569351326 + }, + "30": { + "t": 29.09, + "down_rise": 11.061490952726047, + "up_rise": 3.2115064579089676, + "diff_rise": 7.849984494817079, + "down_abs": 33.83257949879777 + }, + "40": { + "t": 39.12, + "down_rise": 12.977115697647378, + "up_rise": 4.004632466797091, + "diff_rise": 8.972483230850287, + "down_abs": 35.7482042437191 + }, + "50": { + "t": 49.15, + "down_rise": 13.33063012290728, + "up_rise": 4.004632466797091, + "diff_rise": 9.32599765611019, + "down_abs": 36.101718668979004 + }, + "60": { + "t": 59.18, + "down_rise": 13.864107394444943, + "up_rise": 4.88245514123696, + "diff_rise": 8.981652253207983, + "down_abs": 36.63519594051667 + }, + "75": { + "t": 74.23, + "down_rise": 13.685847491738684, + "up_rise": 4.243458414500992, + "diff_rise": 9.442389077237692, + "down_abs": 36.45693603781041 + } + }, + "rep": 4, + "cooldown_s": 11, + "base_at_start": 22.771088546071724 + }, + { + "duty": 40, + "flow": true, + "start_down": 24.180323121554522, + "start_up": 23.23960623233387, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.05, + "down_rise": 7.193644768004333, + "up_rise": -0.23440379004489387, + "diff_rise": 7.428048558049227, + "down_abs": 31.373967889558855 + }, + "20": { + "t": 19.06, + "down_rise": 8.118027930103437, + "up_rise": 0.23470378924031365, + "diff_rise": 7.883324140863124, + "down_abs": 32.29835105165796 + }, + "25": { + "t": 24.08, + "down_rise": 8.033595645338835, + "up_rise": -0.39051130924997324, + "diff_rise": 8.424106954588808, + "down_abs": 32.21391876689336 + }, + "30": { + "t": 29.09, + "down_rise": 9.652256377243248, + "up_rise": 0.6265710250734742, + "diff_rise": 9.025685352169774, + "down_abs": 33.83257949879777 + }, + "40": { + "t": 39.13, + "down_rise": 9.308876820590399, + "up_rise": 0.15643523398983916, + "diff_rise": 9.15244158660056, + "down_abs": 33.48919994214492 + }, + "50": { + "t": 49.16, + "down_rise": 9.394586954689089, + "up_rise": 0.4697177275829745, + "diff_rise": 8.924869227106115, + "down_abs": 33.57491007624361 + }, + "60": { + "t": 59.19, + "down_rise": 9.910745918806015, + "up_rise": 0.5481263900913973, + "diff_rise": 9.362619528714617, + "down_abs": 34.09106904036054 + }, + "75": { + "t": 74.23, + "down_rise": 9.308876820590399, + "up_rise": 0.4697177275829745, + "diff_rise": 8.839159093007424, + "down_abs": 33.48919994214492 + } + }, + "rep": 4, + "cooldown_s": 11, + "base_at_start": 22.927132764254054 + }, + { + "duty": 40, + "flow": false, + "start_down": 23.23960623233387, + "start_up": 22.303690221175657, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.05, + "down_rise": 10.937810835335029, + "up_rise": 1.4056337387411872, + "diff_rise": 9.532177096593841, + "down_abs": 34.1774170676689 + }, + "20": { + "t": 19.06, + "down_rise": 11.981432811765046, + "up_rise": 1.6409680231826087, + "diff_rise": 10.340464788582437, + "down_abs": 35.221039044098916 + }, + "25": { + "t": 24.08, + "down_rise": 14.113091202915768, + "up_rise": 3.139523302303644, + "diff_rise": 10.973567900612125, + "down_abs": 37.35269743524964 + }, + "30": { + "t": 29.09, + "down_rise": 13.484884702114527, + "up_rise": 2.90191993419387, + "diff_rise": 10.582964767920657, + "down_abs": 36.7244909344484 + }, + "40": { + "t": 39.12, + "down_rise": 15.662770135806852, + "up_rise": 4.734206759956521, + "diff_rise": 10.92856337585033, + "down_abs": 38.90237636814072 + }, + "50": { + "t": 49.15, + "down_rise": 16.872499495828663, + "up_rise": 5.377740593052295, + "diff_rise": 11.494758902776368, + "down_abs": 40.11210572816253 + }, + "60": { + "t": 59.18, + "down_rise": 17.06063000074613, + "up_rise": 5.5391708263020405, + "diff_rise": 11.52145917444409, + "down_abs": 40.30023623308 + }, + "75": { + "t": 74.23, + "down_rise": 17.343870292877114, + "up_rise": 5.5391708263020405, + "diff_rise": 11.804699466575073, + "down_abs": 40.58347652521098 + } + }, + "rep": 4, + "cooldown_s": 11, + "base_at_start": 22.381515612920623 + }, + { + "duty": 50, + "flow": true, + "start_down": 23.161438802330906, + "start_up": 22.070385781761104, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.04, + "down_rise": 9.900533510348907, + "up_rise": 1.4822272556874054, + "diff_rise": 8.418306254661502, + "down_abs": 33.06197231267981 + }, + "20": { + "t": 19.06, + "down_rise": 12.674986579779443, + "up_rise": 1.6389381781557404, + "diff_rise": 11.036048401623702, + "down_abs": 35.83642538211035 + }, + "25": { + "t": 24.07, + "down_rise": 11.710171258466744, + "up_rise": 1.8742724625971618, + "diff_rise": 9.835898795869582, + "down_abs": 34.87161006079765 + }, + "30": { + "t": 29.09, + "down_rise": 12.586765441388195, + "up_rise": 1.6389381781557404, + "diff_rise": 10.947827263232455, + "down_abs": 35.7482042437191 + }, + "40": { + "t": 39.12, + "down_rise": 13.92134752045314, + "up_rise": 1.8742724625971618, + "diff_rise": 12.047075057855977, + "down_abs": 37.082786322784045 + }, + "50": { + "t": 49.15, + "down_rise": 14.101173644309995, + "up_rise": 2.1885675424182978, + "diff_rise": 11.912606101891697, + "down_abs": 37.2626124466409 + }, + "60": { + "t": 59.18, + "down_rise": 14.101173644309995, + "up_rise": 2.1099373397934187, + "diff_rise": 11.991236304516576, + "down_abs": 37.2626124466409 + }, + "75": { + "t": 74.22, + "down_rise": 13.83160505479384, + "up_rise": 2.1885675424182978, + "diff_rise": 11.643037512375543, + "down_abs": 36.993043857124746 + } + }, + "rep": 4, + "cooldown_s": 21, + "base_at_start": 22.381515612920623 + }, + { + "duty": 50, + "flow": false, + "start_down": 22.070385781761104, + "start_up": 22.148125674399637, + "aborted_at": null, + "samples": 75, + "at": { + "15": { + "t": 14.05, + "down_rise": 13.589701274077129, + "up_rise": 1.3261843471745465, + "diff_rise": 12.263516926902582, + "down_abs": 35.66008705583823 + }, + "20": { + "t": 19.06, + "down_rise": 16.46417687180667, + "up_rise": 2.8992974376118923, + "diff_rise": 13.56487943419478, + "down_abs": 38.534562653567775 + }, + "25": { + "t": 24.08, + "down_rise": 17.38755029139719, + "up_rise": 2.8992974376118923, + "diff_rise": 14.488252853785298, + "down_abs": 39.457936073158294 + }, + "30": { + "t": 29.09, + "down_rise": 20.04575445352168, + "up_rise": 4.809568713735587, + "diff_rise": 15.236185739786094, + "down_abs": 42.116140235282785 + }, + "40": { + "t": 39.12, + "down_rise": 21.61816587327661, + "up_rise": 6.099304882156503, + "diff_rise": 15.518860991120107, + "down_abs": 43.68855165503771 + }, + "50": { + "t": 49.15, + "down_rise": 22.622816218781168, + "up_rise": 6.99458380507042, + "diff_rise": 15.628232413710748, + "down_abs": 44.69320200054227 + }, + "60": { + "t": 59.18, + "down_rise": 23.439492428894937, + "up_rise": 7.404027268883908, + "diff_rise": 16.03546516001103, + "down_abs": 45.50987821065604 + }, + "75": { + "t": 74.23, + "down_rise": 23.852339995222223, + "up_rise": 8.227891708505013, + "diff_rise": 15.624448286717211, + "down_abs": 45.92272577698333 + } + }, + "rep": 4, + "cooldown_s": 21, + "base_at_start": 21.682095410064733 + } + ] +} \ No newline at end of file diff --git a/scripts/bench/flow_recheck_char.py b/scripts/bench/flow_recheck_char.py new file mode 100644 index 0000000..7e8a16c --- /dev/null +++ b/scripts/bench/flow_recheck_char.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python3 +"""Characterize a SHORT periodic flow re-check for use DURING a job. + +Why a separate design from the job-start check: with the laser firing +and coolant flowing, tube heat raises BOTH sensors, inflating a plain +downstream-rise metric toward a false fault. With the pump stopped that +heat never reaches the sensors at all (stagnant loop), so the no-flow +signature stays at its idle value. The two signatures therefore +converge during a cut. The differential rise + + (downstream_end - downstream_start) - (upstream_end - upstream_start) + +cancels that common-mode heating and is the metric this measures. + +TWO DEAD ENDS, recorded so they are not re-invented: + +1. Absolute over-temperature does NOT detect a failed pump. The + sensors read the water at their own location; with no circulation + the tube's heat stays in the tube and never reaches them. The loop + can read perfectly comfortable while the tube cooks. Over-temp + monitoring detects a hot CIRCULATING loop - a different failure. + +2. "The coolant should warm up while cutting" is NOT a usable + indicator either. A low-duty engrave (say 5% duty at 30% power) can + put so little heat in that the cooling system absorbs it with no + measurable rise, so a flat trend is equally consistent with a light + load working correctly and with a dead pump. Ambiguous evidence is + worse than none - it invites false confidence. + +Hence: periodic ACTIVE interrogation with the heater, which creates a +known stimulus instead of waiting for one, is the only valid method on +this hardware (there is no pump tach or pump current sense anywhere in +the machine). + +Usage: flow_recheck_char.py [heater_pct] [window_s] (default 50 30) +Runs both flow and no-flow cases from a comparable loop state and +prints the differential separation. Aborts if downstream passes 45 C. +""" +import math +import subprocess +import sys +import time + +HOST = '172.16.1.97' +F = 1024.0 * 1.3 +RD, BETA = 10000.0, 3380.0 +RINF = 10000.0 * math.exp(-3380.0 / 298.15) +DOWN_ABORT_C = 45.0 + + +def degc(raw): + r = RD / (F / raw - 1.0) + return BETA / math.log(r / RINF) - 273.15 + + +def board(cmd): + r = subprocess.run(['wsl', '-d', 'forge-yocto', '--', 'ssh', + '-o', 'PreferredAuthentications=none', + 'root@' + HOST, cmd], + capture_output=True, text=True, timeout=30) + return r.stdout.strip() + + +def sample(): + o = board('cat /sys/glowforge/pic/water_temp_1 /sys/glowforge/pic/water_temp_2').split() + return degc(int(o[0])), degc(int(o[1])) + + +def settle(seconds=100): + board('echo 1 > /sys/glowforge/thermal/water_pump_on; echo 0 > /sys/glowforge/thermal/heater_pwm') + time.sleep(seconds) + + +def check(tag, pump_on, pct, window): + board('echo %d > /sys/glowforge/thermal/water_pump_on' % (1 if pump_on else 0)) + time.sleep(2) + d0, u0 = sample() + board('echo %d > /sys/glowforge/thermal/heater_pwm' % int(65535 * pct / 100)) + t0 = time.time() + d, u = d0, u0 + while time.time() - t0 < window: + time.sleep(5) + d, u = sample() + print(' %-8s t=%2.0fs down=%5.2f (%+5.2f) up=%5.2f (%+5.2f) diff=%+5.2f' + % (tag, time.time() - t0, d, d - d0, u, u - u0, (d - d0) - (u - u0)), flush=True) + if d >= DOWN_ABORT_C: + print(' abort: downstream at safety limit', flush=True) + break + board('echo 0 > /sys/glowforge/thermal/heater_pwm; echo 1 > /sys/glowforge/thermal/water_pump_on') + return (d - d0) - (u - u0), d - d0 + + +pct = int(sys.argv[1]) if len(sys.argv) > 1 else 50 +window = int(sys.argv[2]) if len(sys.argv) > 2 else 30 + +print('=== periodic re-check characterization: %d%% heater, %d s window' % (pct, window)) +settle() +print(' flow case:') +flow_diff, flow_rise = check('flow', True, pct, window) +settle() +print(' no-flow case:') +noflow_diff, noflow_rise = check('noflow', False, pct, window) +settle(60) + +print() +print('differential rise: flow %+.2f no-flow %+.2f separation %.2f C' + % (flow_diff, noflow_diff, noflow_diff - flow_diff)) +print('plain down rise: flow %+.2f no-flow %+.2f separation %.2f C' + % (flow_rise, noflow_rise, noflow_rise - flow_rise)) +print('suggested differential threshold: %.2f C' % ((flow_diff + noflow_diff) / 2)) diff --git a/scripts/bench/flow_sampler.py b/scripts/bench/flow_sampler.py new file mode 100644 index 0000000..273a575 --- /dev/null +++ b/scripts/bench/flow_sampler.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +"""Board-side coolant sampler: prints 'elapsed,raw_down,raw_up' lines. +Usage: flow_sampler.py +Kept on the board so sampling cadence is not at the mercy of ssh +round-trip latency.""" +import sys +import time + +dur = float(sys.argv[1]) +iv = float(sys.argv[2]) +t0 = time.time() + +while True: + el = time.time() - t0 + if el > dur: + break + with open('/sys/glowforge/pic/water_temp_1') as f: + r1 = f.read().strip() + with open('/sys/glowforge/pic/water_temp_2') as f: + r2 = f.read().strip() + print('%.2f,%s,%s' % (el, r1, r2), flush=True) + time.sleep(iv) diff --git a/scripts/bench/flow_sustained.py b/scripts/bench/flow_sustained.py new file mode 100644 index 0000000..5a2df9d --- /dev/null +++ b/scripts/bench/flow_sustained.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python3 +"""Sustained-load test for periodic flow re-checks. + +Question: what does repeated interrogation cost thermally over a long +job, and does the loop reach equilibrium or climb without bound? Runs +the driver's real check cadence (M8 held for the duration) with the +cut-profile fans, logging bulk coolant temperature and every verdict. + +Usage: flow_sustained.py [minutes] (default 30) +""" +import math +import re +import socket +import subprocess +import sys +import time + +HOST = '172.16.1.97' +F = 1024.0 * 1.3 +RD, BETA = 10000.0, 3380.0 +RINF = 10000.0 * math.exp(-3380.0 / 298.15) + + +def degc(raw): + r = RD / (F / float(raw) - 1.0) + return BETA / math.log(r / RINF) - 273.15 + + +def board(cmd): + r = subprocess.run(['wsl', '-d', 'forge-yocto', '--', 'ssh', + '-o', 'PreferredAuthentications=none', + 'root@' + HOST, cmd], + capture_output=True, text=True, timeout=30) + return r.stdout.strip() + + +def temps(): + o = board('cat /sys/glowforge/pic/water_temp_1 /sys/glowforge/pic/water_temp_2').split() + return degc(o[0]), degc(o[1]) + + +minutes = float(sys.argv[1]) if len(sys.argv) > 1 else 30 + +s = socket.create_connection((HOST, 23), timeout=5) +s.settimeout(0.3) +time.sleep(0.5) +try: + while s.recv(4096): + pass +except socket.timeout: + pass + +d0, u0 = temps() +print('start: down=%.2f up=%.2f running M8 for %.0f min' % (d0, u0, minutes)) +s.sendall(b'M8\n') + +t0 = time.time() +verdicts = [] +peak_up = u0 +buf = '' +while time.time() - t0 < minutes * 60: + try: + buf += s.recv(4096).decode('ascii', 'replace') + except socket.timeout: + pass + for line in buf.split('\n'): + if 'flow' in line and ('verified' in line or 'FAULT' in line): + m = re.search(r'rise ([\d.]+) C', line) + if m and (not verdicts or verdicts[-1][1] != float(m.group(1))): + el = (time.time() - t0) / 60 + verdicts.append((el, float(m.group(1)), 'FAULT' in line)) + d, u = temps() + peak_up = max(peak_up, u) + print(' %5.1f min rise=%5.2f %-8s loop down=%.2f up=%.2f (%+.2f from start)' + % (el, float(m.group(1)), 'FAULT' if 'FAULT' in line else 'ok', d, u, u - u0), + flush=True) + buf = buf[-2000:] + time.sleep(5) + +s.sendall(b'M9\n') +time.sleep(1) +s.close() + +d, u = temps() +print() +print('checks run: %d over %.0f min' % (len(verdicts), minutes)) +if verdicts: + rises = [v[1] for v in verdicts] + print('rise values: min=%.2f max=%.2f mean=%.2f (threshold 13.7)' + % (min(rises), max(rises), sum(rises) / len(rises))) + print('false faults: %d' % sum(1 for v in verdicts if v[2])) +print('loop temperature: start %.2f -> end %.2f (%+.2f C), peak %.2f' + % (u0, u, u - u0, peak_up)) +print('cadence: %.1f min between checks' % (minutes / max(1, len(verdicts)))) diff --git a/scripts/bench/flow_warm_log.txt b/scripts/bench/flow_warm_log.txt new file mode 100644 index 0000000..5251724 --- /dev/null +++ b/scripts/bench/flow_warm_log.txt @@ -0,0 +1,96 @@ +=== warm-baseline validation 2026-08-02 22:43:41 duty=40% window=50s threshold=13.7 + cycle 1/3 FLOW + warmed to down=21.84 up=21.91 in 1.3 min + base_up=22.07 down_rise=12.18 -> ok CORRECT (peak down 34.4 C) + cycle 1/3 NO-FLOW + warmed to down=22.46 up=21.84 in 1.0 min + base_up=21.84 down_rise=18.16 -> FAULT CORRECT (peak down 40.4 C) + cycle 2/3 FLOW + warmed to down=21.84 up=22.07 in 1.0 min + base_up=21.29 down_rise=12.75 -> ok CORRECT (peak down 34.5 C) + cycle 2/3 NO-FLOW + warmed to down=22.07 up=21.91 in 1.0 min + base_up=21.45 down_rise=18.15 -> FAULT CORRECT (peak down 40.5 C) + cycle 3/3 FLOW + warmed to down=22.07 up=21.91 in 1.0 min + base_up=21.60 down_rise=10.95 -> ok CORRECT (peak down 34.3 C) + cycle 3/3 NO-FLOW + warmed to down=22.30 up=22.30 in 1.0 min + base_up=21.76 down_rise=17.85 -> FAULT CORRECT (peak down 40.4 C) + +=== warm-baseline results (baselines 21.3 - 22.1 C) + flow n=3 mean 11.96 sd 0.92 max 12.75 + no-flow n=3 mean 18.06 sd 0.17 min 17.85 + worst-case margin: +5.11 C threshold 13.7 sits +0.95 above flow max, +4.15 below no-flow min + misclassified: 0 of 6 +controller restarted +=== warm-baseline validation 2026-08-02 22:56:44 duty=40% window=50s threshold=13.7 + cycle 1/3 FLOW + warmed to down=22.30 up=22.07 in 1.3 min + base_up=22.23 down_rise=12.62 -> ok CORRECT (peak down 34.0 C) + cycle 1/3 NO-FLOW + warmed to down=22.23 up=21.84 in 1.0 min + base_up=21.76 down_rise=18.71 -> FAULT CORRECT (peak down 40.6 C) + cycle 2/3 FLOW + warmed to down=22.30 up=22.07 in 1.0 min + base_up=21.76 down_rise=11.03 -> ok CORRECT (peak down 34.3 C) + cycle 2/3 NO-FLOW + warmed to down=22.15 up=21.84 in 1.0 min + base_up=21.37 down_rise=18.26 -> FAULT CORRECT (peak down 40.1 C) + cycle 3/3 FLOW + warmed to down=22.54 up=22.15 in 1.0 min + base_up=21.99 down_rise=11.08 -> ok CORRECT (peak down 34.3 C) + cycle 3/3 NO-FLOW + warmed to down=22.07 up=21.84 in 1.0 min + base_up=21.91 down_rise=18.42 -> FAULT CORRECT (peak down 40.5 C) + +=== warm-baseline results (baselines 21.4 - 22.2 C) + flow n=3 mean 11.58 sd 0.91 max 12.62 + no-flow n=3 mean 18.46 sd 0.23 min 18.26 + worst-case margin: +5.63 C threshold 13.7 sits +1.08 above flow max, +4.56 below no-flow min + misclassified: 0 of 6 +controller restarted +=== warm-baseline validation 2026-08-02 23:10:04 duty=40% window=50s threshold=13.7 + cycle 1/2 FLOW + warm abort: downstream 51.95 at 0.5 min + warmed to down=22.15 up=21.84 in 1.3 min +=== warm-baseline validation 2026-08-02 23:11:51 duty=40% window=50s threshold=13.7 + cycle 1/2 FLOW + base_up=22.23 down_rise=15.53 -> FAULT *** WRONG *** (peak down 37.7 C) + cycle 1/2 NO-FLOW + warm abort: downstream 49.34 at 0.3 min + warm target reached: up=25.84 at 0.8 min + warmed to down=21.84 up=21.60 in 1.0 min + warmed to down=22.15 up=22.07 in 1.6 min + base_up=21.37 down_rise=10.69 -> ok *** WRONG *** (peak down 34.1 C) + cycle 2/2 FLOW + base_up=20.91 down_rise=11.48 -> ok CORRECT (peak down 34.4 C) + cycle 1/2 NO-FLOW + warming: 0.8 min down=37.53 up=23.71 + warming: 0.8 min down=38.17 up=24.65 + warming: 1.8 min down=37.80 up=23.94 + warming: 1.8 min down=37.99 up=24.50 + (warming plateaued at 24.18 C after 2.6 min) + warming: 2.9 min down=26.56 up=23.47 + (warming plateaued at 23.47 C after 2.9 min) + warmed to down=21.68 up=21.60 in 3.4 min + warmed to down=28.57 up=21.84 in 3.6 min + base_up=21.37 down_rise=12.18 -> ok *** WRONG *** (peak down 34.1 C) + cycle 2/2 FLOW + base_up=21.91 down_rise=3.76 -> ok CORRECT (peak down 35.8 C) + cycle 2/2 NO-FLOW + warm abort: downstream 48.90 at 0.3 min + warm target reached: up=25.68 at 0.5 min + warmed to down=22.23 up=21.84 in 1.0 min + warmed to down=21.29 up=21.68 in 1.3 min + base_up=21.45 down_rise=11.58 -> ok *** WRONG *** (peak down 34.4 C) + base_up=21.06 down_rise=11.30 -> ok CORRECT (peak down 34.2 C) + cycle 2/2 NO-FLOW + +=== warm-baseline results (baselines 21.4 - 22.2 C) + flow n=2 mean 9.65 sd 8.32 max 15.53 + no-flow n=2 mean 11.13 sd 0.63 min 10.69 + worst-case margin: -4.84 C threshold 13.7 sits -1.83 above flow max, -3.01 below no-flow min + misclassified: 3 of 4 +controller restarted + warming: 0.8 min down=37.71 up=24.50 diff --git a/scripts/bench/flow_warm_results.json b/scripts/bench/flow_warm_results.json new file mode 100644 index 0000000..f68af1e --- /dev/null +++ b/scripts/bench/flow_warm_results.json @@ -0,0 +1,32 @@ +[ + { + "flow": true, + "base_up": 20.90741361785507, + "base_down": 21.837331209719537, + "down_rise": 11.480714695811116, + "up_rise": 2.254025184475836, + "peak_down": 34.43702357179029, + "t": 49.15, + "cycle": 0 + }, + { + "flow": false, + "base_up": 21.371932016053336, + "base_down": 21.914988917022924, + "down_rise": 12.176080123337613, + "up_rise": 1.5552007482007184, + "peak_down": 34.09106904036054, + "t": 49.17, + "cycle": 0 + }, + { + "flow": true, + "base_up": 21.06216007856807, + "base_down": 21.75970016295645, + "down_rise": 11.302272149723365, + "up_rise": 2.6471638813487743, + "peak_down": 34.1774170676689, + "t": 49.16, + "cycle": 1 + } +] \ No newline at end of file diff --git a/scripts/bench/flow_warm_validate.py b/scripts/bench/flow_warm_validate.py new file mode 100644 index 0000000..cb382b2 --- /dev/null +++ b/scripts/bench/flow_warm_validate.py @@ -0,0 +1,205 @@ +#!/usr/bin/env python3 +"""Validate the flow check at a WARM loop baseline - the condition a +real job actually presents. + +Every design-matrix run started near ambient (21-23 C). During a cut the +loop sits warmer, where heat loss to ambient is larger, which shrinks +the measured rise in BOTH the flow and no-flow cases and could compress +the margin the threshold depends on. This warms the loop with its own +heater (which plateaus near 28-29 C - the most it can reach unaided), +then runs the real check at 40% / 50 s with the cut-profile fans, and +alternates flow / no-flow so both cases see the same conditions. + +Usage: flow_warm_validate.py [cycles_per_case] (default 3) +""" +import json +import math +import os +import statistics +import subprocess +import sys +import time + +HOST = '172.16.1.97' +HERE = os.path.dirname(os.path.abspath(__file__)) +RESULTS = os.path.join(HERE, 'flow_warm_results.json') + +F = 1024.0 * 1.3 +RD, BETA = 10000.0, 3380.0 +RINF = 10000.0 * math.exp(-3380.0 / 298.15) + +DUTY = 40 +CHECK_S = 50 +THRESHOLD = 13.7 +WARM_TARGET_C = 25.5 # what a ~19-20 C room permits at 50% duty +WARM_MAX_S = 780 +ABORT_C = 48.0 + +FANS_RUN = ('echo 65535 > /sys/glowforge/thermal/exhaust_pwm; ' + 'echo 43278 > /sys/glowforge/thermal/intake_pwm') +FANS_OFF = ('echo 0 > /sys/glowforge/thermal/exhaust_pwm; ' + 'echo 0 > /sys/glowforge/thermal/intake_pwm') + + +def degc(raw): + raw = float(raw) + if raw <= 1.0 or raw >= F: + return float('nan') + r = RD / (F / raw - 1.0) + return BETA / math.log(r / RINF) - 273.15 + + +def board(cmd, timeout=120): + r = subprocess.run(['wsl', '-d', 'forge-yocto', '--', 'ssh', + '-o', 'PreferredAuthentications=none', + 'root@' + HOST, cmd], + capture_output=True, text=True, timeout=timeout) + return r.stdout + + +_last_good = (22.0, 22.0) + + +def temps(): + """Sanity-guarded read. A single glitched sysfs value used to map to + ~109 C and trip the safety abort, silently ending the warm phase + after one iteration - which wasted two whole validation runs.""" + global _last_good + o = board('cat /sys/glowforge/pic/water_temp_1 /sys/glowforge/pic/water_temp_2').split() + if len(o) != 2: + return _last_good + d, u = degc(o[0]), degc(o[1]) + if any(x != x or x < 5.0 or x > 60.0 for x in (d, u)): + return _last_good + _last_good = (d, u) + return d, u + + +def heater(pct): + board('echo %d > /sys/glowforge/thermal/heater_pwm' % int(65535 * pct / 100)) + + +def pump(on): + board('echo %d > /sys/glowforge/thermal/water_pump_on' % (1 if on else 0)) + + +def warm_to(target, log): + """Warm with the loop heater, pump circulating, fans off.""" + pump(True) + board(FANS_OFF) + # 50% is the practical ceiling: the downstream sensor sits AT the + # heater element, so 100% drives it past 50 C within 30 s while the + # bulk has barely moved. At 50% it plateaus near 41 C, leaving the + # bulk free to climb for as long as we let it. + heater(50) + t0 = time.time() + # Plateau detection compares against the reading two minutes back: + # with the pump circulating, the bulk climbs only ~0.5-0.8 C/min, so + # a per-sample threshold mistakes normal warming for a plateau (it + # did exactly that on the first attempt and the whole run executed + # at ambient). + hist = [] + while time.time() - t0 < WARM_MAX_S: + d, u = temps() + el = (time.time() - t0) / 60 + if u >= target: + log(' warm target reached: up=%.2f at %.1f min' % (u, el)) + break + if d >= ABORT_C: + log(' warm abort: downstream %.2f at %.1f min' % (d, el)) + break + hist.append(u) + if len(hist) % 4 == 0: + log(' warming: %.1f min down=%.2f up=%.2f' % (el, d, u)) + if len(hist) > 8 and u - hist[-9] < 0.3: + log(' (warming plateaued at %.2f C after %.1f min)' % (u, el)) + break + time.sleep(15) + heater(0) + pump(True) + time.sleep(45) # mix so the bulk is uniform before measuring + d, u = temps() + log(' warmed to down=%.2f up=%.2f in %.1f min' % (d, u, (time.time() - t0) / 60)) + return u + + +def check(flow, log): + """The real check: fans at cut profile, 40% heater, 50 s.""" + board(FANS_RUN) + pump(flow) + time.sleep(3) + d0, u0 = temps() + heater(DUTY) + raw = board('python3 /data/flow_sampler.py %d 1.0' % CHECK_S, timeout=CHECK_S + 60) + heater(0) + pump(True) + + series = [] + for line in raw.strip().splitlines(): + try: + el, r1, r2 = line.split(',') + series.append((float(el), degc(r1), degc(r2))) + except ValueError: + continue + if not series: + return None + el, d, u = series[-1] + return {'flow': flow, 'base_up': u0, 'base_down': d0, 'down_rise': d - d0, + 'up_rise': u - u0, 'peak_down': max(s[1] for s in series), 't': el} + + +def main(): + cycles = int(sys.argv[1]) if len(sys.argv) > 1 else 3 + logf = open(os.path.join(HERE, 'flow_warm_log.txt'), 'a') + + def log(msg): + print(msg, flush=True) + logf.write(msg + '\n') + logf.flush() + + log('=== warm-baseline validation %s duty=%d%% window=%ds threshold=%.1f' + % (time.strftime('%Y-%m-%d %H:%M:%S'), DUTY, CHECK_S, THRESHOLD)) + board('pkill -x grblHAL_glowfor; sleep 1; true') + + runs = [] + try: + for c in range(cycles): + for flow in (True, False): + log(' cycle %d/%d %s' % (c + 1, cycles, 'FLOW' if flow else 'NO-FLOW')) + warm_to(WARM_TARGET_C, log) + r = check(flow, log) + if r: + r['cycle'] = c + runs.append(r) + verdict = 'FAULT' if r['down_rise'] > THRESHOLD else 'ok' + correct = (verdict == 'FAULT') == (not flow) + log(' base_up=%.2f down_rise=%.2f -> %-5s %s (peak down %.1f C)' + % (r['base_up'], r['down_rise'], verdict, + 'CORRECT' if correct else '*** WRONG ***', r['peak_down'])) + json.dump(runs, open(RESULTS, 'w'), indent=1) + finally: + heater(0) + pump(True) + board(FANS_OFF) + + fv = [r['down_rise'] for r in runs if r['flow']] + nv = [r['down_rise'] for r in runs if not r['flow']] + log('') + log('=== warm-baseline results (baselines %.1f - %.1f C)' + % (min(r['base_up'] for r in runs), max(r['base_up'] for r in runs))) + if len(fv) >= 2 and len(nv) >= 2: + log(' flow n=%d mean %.2f sd %.2f max %.2f' % (len(fv), statistics.mean(fv), statistics.stdev(fv), max(fv))) + log(' no-flow n=%d mean %.2f sd %.2f min %.2f' % (len(nv), statistics.mean(nv), statistics.stdev(nv), min(nv))) + log(' worst-case margin: %+.2f C threshold %.1f sits %+.2f above flow max, %+.2f below no-flow min' + % (min(nv) - max(fv), THRESHOLD, THRESHOLD - max(fv), min(nv) - THRESHOLD)) + wrong = [r for r in runs if ((r['down_rise'] > THRESHOLD) != (not r['flow']))] + log(' misclassified: %d of %d' % (len(wrong), len(runs))) + + board('cd /data && GFSINK=/dev/glowforge nohup grblHAL_glowforge -p 23 ' + '-e /data/EEPROM-glowforge.DAT > /data/glowforge.log 2>&1 & sleep 2; true') + log('controller restarted') + logf.close() + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/scripts/bench/temp_calibration.json b/scripts/bench/temp_calibration.json new file mode 100644 index 0000000..c1c1bbf --- /dev/null +++ b/scripts/bench/temp_calibration.json @@ -0,0 +1,11 @@ +{ + "points": [ + { + "measured_c": 25.67, + "raw1": 672.4, + "raw2": 672.6, + "note": "IR on reservoir+hoses 78.2F, ambient 78.0F, idle 1h, pump on, heater off", + "when": "2026-08-02 18:13:41" + } + ] +} \ No newline at end of file