Skip to content

v1.8.0

Choose a tag to compare

@github-actions github-actions released this 09 Oct 08:29

Changed

  • The initial design is now a real Sobol design in every backend. draw_sobol_samples(n=1, q=N) drew one point of an N·d-dimensional Sobol sequence and reshaped it — no better spread than independent uniform draws (mean centered L2 discrepancy, d=2, N=6, 200 seeds: 0.064 vs 0.061 for i.i.d. uniform and 0.019 for a real Sobol design). BoTorch, MOBO, DBO and MetaTAF now draw N points of one d-dimensional sequence with the same call (n=N, q=1), so all of them share the initial design for a given seed — MetaTAF previously used a different generator, so its participants started from other designs than the BoTorch control condition. CABOP's own Sobol draw is fixed separately (below). Initial designs differ from earlier versions: do not upgrade within a running study.
  • CSV logs keep 10 significant digits instead of 3 decimals. Small ranges were erased (0.000274 on [0, 0.004] logged as 0.0; two objective values 0.0035 and 0.0041 both as 0.004), so the logs could not reproduce the evaluated design, and FinalDesignSelector, warm starts and Meta-TAF sources worked from different values than the participant experienced. 10 digits keep every bit of the float32 values Unity exchanges while hiding float64 round-trip noise (6.0, not 5.999999999999999).
  • BoTorch/MOBO suggestions are 2.5–3× faster with identical candidates: the 1024 raw samples are scored 128 at a time (init_batch_limit) instead of 5 at a time (measured: single objective 1.72 s → 0.57 s, two objectives 11.0 s → 4.4 s per suggestion).
  • The single-objective backends (bo.py, DBO) run PyTorch on one thread: 1.4–1.6× faster per suggestion with identical suggestions at study sizes (measured d = 2–8, n = 12–80; e.g. DBO d=2: 2.26 s → 1.40 s, bo.py d=8, n=80: 0.79 s → 0.55 s), and the other cores stay free for Unity. The environment variable BO_TORCH_THREADS overrides it. MOBO and MetaTAF keep PyTorch's default, because their hypervolume computations do benefit from threads (MOBO d=2: 3.9 s on 16 threads vs 7.3 s on one).
  • MetaTAF records its provenance in a new MetaRunState.json, written atomically before the first trial (and on an aborted start, with abort_reason): library versions, how openbo is installed (pip's direct_url.json) plus SHA-256 hashes of the openbo modules in use (open-bo keeps version 0.1.0 across behaviour changes), the exact MOTAFConfig, the frame and its digest, the seed and init configuration, and the loaded, dropped and frame-rejected sources with their provenance stamps. The runtime now pins the MOTAFConfig fields it used to leave to openbo's defaults (source_reference_mode="front", source_reference_quantile=0.9, min_informative_pairs=1, no meta-features — identical in every openbo that passes the TAF-R probe), so an upstream default change (e.g. to the newer target_incumbent reference) cannot silently alter a running study, and the weights log reads openbo's attributes directly, so a renamed attribute fails loudly instead of logging invented weights.
  • meta_train.py publishes each artifact only after its self-check passes, and atomically: a failed or skipped source leaves nothing behind (the artifact used to be written before the check and stayed after a failure).
  • One iteration axis for observation and metric logs: every row of BestObjectivePerEvaluation.csv / HypervolumePerEvaluation.csv now carries the same Iteration as its evaluation's row in ObservationsPerEvaluation.csv, and warm-start runs log their baseline at the iteration of the last current-context warm-start row. Under warm start, BoTorch had dropped the baseline row entirely since 1.7.1 (its metric list was one entry short) and numbered live rows 1, 2, … while its observations said k+1, k+2, …; MOBO logged the baseline as 0. Joining the files on Iteration paired metrics with the wrong evaluations.
  • DBO backend now runs dbo-torch 0.2.0 (vendored from M-Colley/dbo-torch main @ 1a268cd, adding _base.py and PROVENANCE.md). Changes that affect studies: each optimizer owns its random-number generator, so a run is reproducible from the inspector Seed alone and unaffected by anything else drawing from torch's global RNG; inputs are normalized against the fixed domain rather than the observed range, so fitted lengthscales keep their meaning between iterations; hyperparameters are fitted from three starting lengthscales, keeping the most likely fit; PredictedCost/PredictedSd are now predictions for the iteration at which the point is evaluated (previously the acquisition time, one iteration earlier by default); the incumbent and continuous-validation searches use BoTorch's optimize_acqf instead of hand-written loops. Results for the same seed differ from 0.1.0 — do not mix versions within one study.
  • New per-run DboRunState.json: the optimizer's own record (dbo_torch/torch/botorch/gpytorch versions, full configuration, RNG state, every observation with its prediction), rewritten atomically after every evaluation, so each run is attributable to the library version that produced it and restorable with DynamicBO.load() for offline analysis. The dbo_torch version is also printed at startup.
  • DBO validation scheduling is configured up front instead of being switched on after the sampling phase; dbo-torch never schedules a validation iteration while seed points are unspent, so the schedule is unchanged.

Fixed

  • DBO Initial Alpha = 1 silently produced a stationary run labelled as DBO. Alpha can never be fitted away from exactly 1 (its gradient vanishes there): with dbo-torch 0.1.0 the fitted alpha stayed at 1.000000 on strongly drifting data where a 0.99 start fitted 0.982, and with 0.2.0 the same setting would crash at the first model fit, after the participant had completed the sampling phase. The inspector slider now stops at 0.999 and shows an error for 1, the launcher refuses to start, and the backend rejects it at init. Pin alpha = 1 with DBO Stationary Baseline instead.
  • DBO BestObjectivePerEvaluation.csv used a different iteration axis from every other backend: one row 0 after sampling, then optimization steps 1..M, while BoTorch/MOBO/MetaTAF have logged one row per evaluation on the global index (sampling included) since 1.7.1. DBO-vs-BoTorch curves were shifted by the number of sampling iterations; DBO now writes the same rows as bo.py.
  • Measurements could be recorded against a design the participant never saw. Every questionnaire in the scene finds the manager and calls OptimizationStart() when it finishes, which sent the current objective values (the scale midpoint for objectives without values) even when no evaluation was in progress — e.g. a pre-study questionnaire finished after the optimizer was ready, or a second questionnaire finished after the next design had arrived. Python stored these as real observations of a design that was never shown. OptimizationStart() now only reports an evaluation in progress and otherwise logs a warning and does nothing.
  • One non-numeric sub-measure (e.g. an unanswered optional item, submitted as NaN) replaced the whole objective with the scale midpoint, discarding the answered items; the objective is now the mean of its finite sub-measures (midpoint only when none is finite, as before).
  • Values on a decimal bound crashed the backend. Bounds were sent as doubles widened from the inspector's floats (0.7f → 0.699999988079071) while values were sent as floats (0.7), so a value clamped to, or rated at, such a bound failed the backends' bounds check and ended the session; warm-start rows on such a bound were rejected the same way, and MetaTAF frame fingerprints never matched a frame.json written with the typed bounds. Bounds are now sent as floats (0.7), identical to the values.
  • Non-ASCII keys or IDs (e.g. Größe, Jürgen) crashed the BoTorch/MOBO/MetaTAF backends at the end of the sampling phase: Python wrote the CSV logs in the Windows code page but reads them back as UTF-8. The backend now runs in Python's UTF-8 mode (PYTHONUTF8=1), and its console output is decoded as UTF-8.
  • Python dependency setup could hang forever ("Installing Python dependencies…") once pip wrote more to stderr than the pipe buffer holds, because stdout was read to the end before stderr; both are now drained concurrently.
  • Backend tracebacks (stderr) are now written to output.txt as well, and the process-exit log no longer dereferences a process object that shutdown has already disposed.
  • CABOP: a fixed Seed did not reproduce a run. The GP's hyperparameter restarts drew from numpy's global RNG; they now draw from the run's seed, and repeated runs with the same seed propose bit-identical designs.
  • CABOP: the "Sobol" initial design was i.i.d. uniform — every point came from a freshly scrambled Sobol engine. The initial design is now one Sobol sequence per run (so initial designs differ from earlier versions for the same seed).
  • CABOP update rule Both entered every unsnapped observation twice, so the GP treated noisy ratings as nearly exact; an intended design is now added only when snapping actually moved it.
  • CABOP: a parameter whose lower and upper bounds are equal crashed the first GP fit after the sampling phase; it is now rejected at startup. A negative Seed (allowed by Unity) crashed the backend at startup; it is now wrapped into numpy's seed range. Group cost entries whose group name differed from the parameters' group only in letter case were silently replaced by default costs — Unity matches groups case-insensitively, and the backend now does too.
  • MetaTAF could still silently run as the no-transfer control. Meta Require Sources checked the sources staged after frame validation, but openbo warns and skips artifacts it cannot use (an unreadable or half-synced trajectory, malformed hyperparameters, a front that never dominates the reference point). With every staged source skipped, the run continued as plain qLogNEHVI while the log still said "using N population model(s)" and MetaSourcesUsed/ archived sources that were never used. The check now applies to what openbo actually loaded: dropped sources are reported with openbo's reason and removed from MetaSourcesUsed/, and the run aborts before the first trial when none is left.
  • MetaTAF's IsPareto flagged every copy of a duplicated non-dominated objective vector, MOBO only the first; with tied (e.g. Likert) ratings MetaTAF reported more Pareto rows — and gave FinalDesignSelector a larger candidate pool — than BoTorch for identical data. Both now use the same semantics.
  • meta_train.py: parameter values outside the frame's bounds that happened to lie in [0,1] were silently read as already normalized, so a run recorded under different bounds became a distorted source that the frame stamp then certified; such runs are now refused with the column and offending values. Restarts with a non-finite likelihood or hyperparameters are discarded (a NaN score on the first restart used to stay "best"), and frame.json bounds must be finite with low ≤ high.
  • Contextual optimization crashed whenever a configured context had no observations — README 8.13's main use case (warm start from earlier participants, a new current participant) at startup, and any contextual run with two or more contexts and no warm start after the participant had finished the sampling phase. LCEMGP inherits MultiTaskGP.eval(), which in BoTorch 0.18.1 initialises unobserved tasks through an IndexKernel that LCEMGP does not have; the backends now skip that step, which is exact for LCEMGP (posteriors are unchanged when every context is observed). Note that with Learned embeddings a new context's embedding is untrained; use manual or image embeddings for transfer to new participants.
  • MOBO crashed with exactly one current-context observation (a one-row warm start, or a new participant's first evaluation): NumPy 2 arrays also have .device, so NumPy input got a torch mask, and a one-element torch bool mask indexes as the integer 1.
  • IDs were rewritten in the observation log. BoTorch, MOBO, MetaTAF and CABOP rewrite ObservationsPerEvaluation.csv after every evaluation through pandas, whose type inference turned 007/01/NA into 7/1/an empty cell in every row but the newest. FinalDesignSelector matches IDs as exact strings, so final-design selection found no candidates, and joins with the questionnaire logs broke. Logs are now read back as text; zero-padded warm-start Context keys load correctly too.
  • A multi-byte UTF-8 character split across two socket reads was corrupted in every backend (Größe → Gr��ße, then a missing-key error); messages are now decoded incrementally.
  • Parameter/objective keys that collide with fixed log columns (Phase, Iteration, IsBest, IsPareto, UserID, …) crashed BoTorch/MOBO/MetaTAF after the sampling phase and made the DBO/CABOP logs ambiguous; they are now rejected at startup.
  • Warm-start parameter files: a raw value a hair outside its bounds (e.g. logged at 3 decimals) silently switched the whole column to the "already normalized" reading, remapping every value. Values within 0.05% of the range outside the bounds are now read as raw, and both the raw-vs-normalized choice (when both fit) and the normalized fallback are announced. CABOP now shares this detection with the other backends.
  • Perfect-rating stop: the last sampling round's rating could start the "perfect twice in a row" streak although ratings from initial rounds were excluded (the check used the already-advanced iteration counter), and a perfect rating after a gap reset the streak instead of starting a new one, so stopping then took three perfect ratings in a row.

Security

  • The optimizer backends listened on every network interface (port 56001, no authentication), so any machine that could reach the lab PC could connect before Unity did and drive or read the study. All five backends now bind to 127.0.0.1 only — Unity always connects there.
  • On Windows, SO_REUSEADDR let a second backend bind port 56001 while a leftover one (e.g. after a Unity crash) was still listening, so Unity's connection could land on the stale process. The backends now use SO_EXCLUSIVEADDRUSE on Windows (a second backend fails at startup with a message naming the leftover process; immediate restarts still work), keep SO_REUSEADDR on macOS/Linux where it only bridges TIME_WAIT, and stop listening as soon as Unity has connected.

Licensing

  • The CABOP optimizer is vendored from the authors' reference implementation (aalto-ui/CABOP, MIT); its licence notice was missing and now ships as cabop/LICENSE, with the local changes listed in cabop/__init__.py and credited in README 8.7.

Tests

  • test_contextual_bo_loop_with_warm_start failed on main since 1.7.1 (the dropped warm-start baseline row); it passes again. New real-stack tests run the BoTorch and MOBO contextual loops for a new participant whose context has no data (fails with the stock LCEMGP), check that metric and observation logs share the iteration axis, and that a one-row NumPy Pareto mask stays NumPy. New stub tests (CI): the initial design is drawn as n=N, q=1; IDs 007/01/NA survive the log rewrite; a UTF-8 character split across reads decodes intact; keys colliding with log columns are rejected; warm-start parameter scale detection (rounding tolerance, announced fallback); log precision.
  • Every backend: listens on 127.0.0.1 only, with SO_EXCLUSIVEADDRUSE on Windows (SO_REUSEADDR elsewhere), and closes its listener before the session starts; a taken port is explained. Checked against the real DBO backend: a second instance now fails at startup instead of sharing the port, and the port does not answer on the machine's network address.
  • MetaTAF: 16 new tests, all failing against the previous code — loaded-vs-staged fail-fast (partial drop; all dropped aborts before any evaluation), MetaRunState.json contents and pinned configuration, the exact Sobol call, IDs surviving the log rewrite, non-ASCII keys, a UTF-8 character split across reads, IsPareto duplicate semantics against the real moocore, and meta_train.py's strict bounds, NaN restarts and atomic publishing. The openbo test stub now mirrors MOTAFConfig field for field and openbo's warn-and-skip loader contract. BO_TORCH_THREADS is validated and applied.
  • CABOP: one Sobol sequence across sequential draws; same seed gives identical designs whatever numpy's global RNG holds; Both adds only moved intended designs; case-insensitive group costs; degenerate range rejected at startup; IDs such as 007/01/NA survive the log rewrite.
  • New tests/test_dbo_runtime.py (real torch/botorch; skipped on CI): initial alpha 1 rejected before any evaluation (accepted for the stationary baseline); two runs with torch's global RNG disturbed in between propose identical designs; validation iterations land on the expected global iterations; DboRunState.json records the vendored version and config, and DynamicBO.load() of it proposes exactly what the live optimizer proposes next.
  • tests/dbo_protocol_check.py additionally verifies DboRunState.json (version, observation count, validation schedule). The standalone bridge's 11 tests pass against 0.2.0 unchanged.

Documentation

  • MetaTAF docs: the meta_train.py command in MetaSources/README.md lacked the required --source-type/--y-calibration flags and failed as written. The student guide no longer calls MetaTAF "always safe" (weighting and decay limit, but do not remove, the risk of a population that does not fit the participant), no longer calls a source-less MetaTAF run "the same optimizer" as the BoTorch control (same initial design and target model; the acquisition optimizer settings differ), documents MetaRunState.json, and has troubleshooting rows for the new messages.
  • Student guide + inspector tooltip: keep the population-weight decay enabled — Meta Decay Rate = 0 is an ablation setting, not a study setting. In simulation benchmarks, no-decay TAF finished below plain multi-objective BO at the final checkpoint (sources keep a constant-size say while the converging participant model's improvement signal shrinks, so late iterations never personalize — the effect Liao et al.'s decay γ(t) exists to fix). This holds for every weight mode. The Unity path always sent the inspector's decay values (default d1=2, d2=0.3); openbo's run_mobo_taf simulation entry point, which previously defaulted to "never decay", now defaults to the same (2, 0.3).