Skip to content

Commit 574e7f2

Browse files
committed
multi-objectinge workflows
1 parent e337367 commit 574e7f2

42 files changed

Lines changed: 803 additions & 69 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ Optimum trial targeting for expensive black-box evaluations improves sample effi
77

88
Looptimum is a file-backed loop for optimum parameter targeting when each
99
trial is costly (time, compute, money, or operational risk).
10-
You provide a parameter space and a scalar objective; Looptimum suggests the
10+
You provide a parameter space and objective schema; Looptimum suggests the
1111
next trial, records decisions, and resumes cleanly after interruptions.
12-
Current stable release: `v0.3.3`.
12+
Current stable release: `v0.3.5`.
1313
For expensive black-box objectives, Looptimum starts with bounded exploration
1414
and then shifts to surrogate-guided suggestion ranking to reduce wasted trials.
1515
Its key differentiator is operational: a file-backed, resumable workflow that
@@ -25,8 +25,9 @@ For a spec-style contract summary, use
2525
- Private contact: [contact@looptimum.com](mailto:contact@looptimum.com)
2626
- Start here: [`PILOT.md`](PILOT.md), [`intake.md`](intake.md),
2727
[`docs/pilot-checklist.md`](docs/pilot-checklist.md)
28-
- Best initial fit: bounded numeric parameters, one scalar objective, and
29-
expensive evaluations in client-controlled environments
28+
- Best initial fit: bounded parameter spaces, one scalar objective or explicit
29+
scalarization rule, and expensive evaluations in client-controlled
30+
environments
3031
- Scope and delivery are tailored to the project; contact for scope
3132

3233
## Common Triggers
@@ -41,7 +42,7 @@ For a spec-style contract summary, use
4142

4243
Looptimum replaces ad hoc sweep loops with a small, explicit workflow:
4344

44-
1. Define parameter bounds and objective direction.
45+
1. Define parameter bounds, objective schema, and optional constraints.
4546
2. `suggest` one trial.
4647
3. Run that trial in your environment.
4748
4. `ingest` the result and repeat.
@@ -200,7 +201,8 @@ expanded stub in
200201
- Each evaluation is expensive enough that sample efficiency matters.
201202
- Your evaluator runs as external jobs and you want a thin outer loop above
202203
training/evaluation infrastructure.
203-
- You can define one scalar objective (`minimize` or `maximize`).
204+
- You can define one scalar objective or an explicit scalarization /
205+
lexicographic rule for multiple objectives.
204206
- You have a bounded parameter set (commonly small-to-moderate dimensional).
205207
- You want resumable, file-backed operation in local/offline/restricted
206208
environments.
@@ -217,9 +219,11 @@ expanded stub in
217219

218220
### Inputs
219221

220-
- Parameter space definition (`float` and `int` currently supported in public
221-
templates).
222-
- Objective schema (name + direction).
222+
- Parameter space definition (`float`, `int`, `bool`, and `categorical` in
223+
public templates; numeric params can also declare `scale`, and params may use
224+
`when` for conditional activation).
225+
- Objective schema (required `primary_objective`, optional
226+
`secondary_objectives`, optional `scalarization` policy).
223227
- Trial budget and seed/config settings.
224228

225229
### `suggest` Output
@@ -236,8 +240,8 @@ Each suggestion includes:
236240
- `trial_id` (must match a pending trial)
237241
- `params` (must match suggested params exactly)
238242
- `objectives`:
239-
- `status: ok` -> primary objective must be numeric and finite
240-
- non-`ok` status -> primary objective must be `null`
243+
- `status: ok` -> all configured objective values must be numeric and finite
244+
- non-`ok` status -> all configured objective values must be `null`
241245
- `status`: `ok`, `failed`, `killed`, `timeout`
242246

243247
### `ingest` Optional Fields
@@ -259,8 +263,11 @@ Each suggestion includes:
259263

260264
Best ranking rule:
261265

262-
- `best` is computed only from `status: "ok"` observations and their primary
263-
objective values.
266+
- `best` is computed only from `status: "ok"` observations.
267+
- Single-objective campaigns rank by the primary objective value.
268+
- Multi-objective campaigns rank by the configured scalarization or
269+
lexicographic policy while preserving raw objective vectors in status,
270+
manifests, and reports.
264271
- `penalty_objective` is never used to rank `best`.
265272

266273
### Local State Files
@@ -271,7 +278,9 @@ Best ranking rule:
271278
- `state/acquisition_log.jsonl`: append-only decision trace.
272279
- `state/event_log.jsonl`: append-only lifecycle/operations trace.
273280
- `state/trials/trial_<id>/manifest.json`: per-trial audit manifest.
274-
- `state/report.json` and `state/report.md`: explicit report outputs from `report`.
281+
- `state/report.json` and `state/report.md`: explicit report outputs from
282+
`report`, including objective-config and Pareto summaries for multi-objective
283+
campaigns.
275284

276285
### Compatibility Notes
277286

@@ -280,10 +289,6 @@ Best ranking rule:
280289
`terminal_reason`.
281290
- For non-`ok` outcomes with no reason provided, ingest synthesizes
282291
`terminal_reason` as `status=<status>`.
283-
- Legacy `v0.2.x` non-`ok` payloads with numeric primary objective are
284-
accepted in `v0.3.x`, normalized to
285-
`objective: null` + `penalty_objective`, and emit a deprecation warning.
286-
- Sentinel primary-objective compatibility is planned for removal in `v0.4.0`.
287292
- `v0.2.x` state without `schema_version` (or with `0.2.x`) upgrades in-memory
288293
to `0.3.0` and persists on next mutating command.
289294
- Earlier `v0.3.x` state versions load transparently in `v0.3.x`.
@@ -295,7 +300,7 @@ Best ranking rule:
295300
compatibility.
296301
- Breaking changes are allowed only on `0.x` major-line increments (for
297302
example `0.3 -> 0.4`) and require explicit compatibility notes.
298-
- Current patch tag in this line: `v0.3.3` (see `CHANGELOG.md`).
303+
- Current patch tag in this line: `v0.3.5` (see `CHANGELOG.md`).
299304
- Full policy: [`docs/stability-guarantees.md`](docs/stability-guarantees.md).
300305

301306
### Duplicate Ingest Behavior
@@ -335,6 +340,8 @@ The `examples/` folder shows integration patterns, not benchmark leaderboards.
335340
- `examples/toy-objectives/02_subprocess_cli/`: subprocess/CLI wrapper pattern
336341
- `examples/toy_objectives/03_tiny_quadratic_loop/`: dedicated tiny end-to-end
337342
objective (`suggest -> evaluate -> ingest -> status`, typically under one minute)
343+
- `docs/examples/multi_objective/`: generated multi-objective report/state pack
344+
with weighted-sum and lexicographic objective-schema examples
338345

339346
Run the tiny end-to-end objective from repo root:
340347

client_harness_template/tests/test_phase6_assets.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
CI_WORKFLOW = REPO_ROOT / ".github" / "workflows" / "ci.yml"
1111
PYPROJECT = REPO_ROOT / "pyproject.toml"
1212
TYPE_SAFETY_DOC = REPO_ROOT / "docs" / "type-safety.md"
13+
MULTI_OBJECTIVE_EXAMPLE = REPO_ROOT / "docs" / "examples" / "multi_objective"
1314

1415

1516
def test_golden_acquisition_log_has_expected_shape_and_timestamps() -> None:
@@ -76,3 +77,35 @@ def test_mypy_scope_and_type_safety_doc_are_present() -> None:
7677
doc_text = TYPE_SAFETY_DOC.read_text(encoding="utf-8")
7778
assert "Type-checking tool: `mypy`." in doc_text
7879
assert "Initial blocking CI gate scope" in doc_text
80+
81+
82+
def test_multi_objective_example_pack_has_expected_artifacts() -> None:
83+
assert MULTI_OBJECTIVE_EXAMPLE.exists(), (
84+
f"missing multi-objective example pack: {MULTI_OBJECTIVE_EXAMPLE}"
85+
)
86+
87+
readme_path = MULTI_OBJECTIVE_EXAMPLE / "README.md"
88+
weighted_schema = MULTI_OBJECTIVE_EXAMPLE / "objective_schema.json"
89+
lexicographic_schema = MULTI_OBJECTIVE_EXAMPLE / "objective_schema_lexicographic.json"
90+
status_path = MULTI_OBJECTIVE_EXAMPLE / "status_after_ingest.json"
91+
report_path = MULTI_OBJECTIVE_EXAMPLE / "state" / "report.json"
92+
manifest_path = MULTI_OBJECTIVE_EXAMPLE / "state" / "trials" / "trial_1" / "manifest.json"
93+
94+
for path in (
95+
readme_path,
96+
weighted_schema,
97+
lexicographic_schema,
98+
status_path,
99+
report_path,
100+
manifest_path,
101+
):
102+
assert path.exists(), f"missing multi-objective example artifact: {path}"
103+
104+
report_payload = json.loads(report_path.read_text(encoding="utf-8"))
105+
assert report_payload["objective_config"]["objective_names"] == ["loss", "throughput"]
106+
assert report_payload["pareto_front"]["trial_ids"] == [1, 2]
107+
assert report_payload["best"]["objective_vector"] == {"loss": 0.3, "throughput": 2.0}
108+
109+
manifest_payload = json.loads(manifest_path.read_text(encoding="utf-8"))
110+
assert manifest_payload["scalarization_policy"] == "weighted_sum"
111+
assert manifest_payload["objective_vector"] == {"loss": 0.3, "throughput": 2.0}

docs/README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@ Included files:
2525
state persistence, contamination controls, safe parallelism, and robust-best policy
2626
- `stability-guarantees.md`: public compatibility guarantees, deprecation
2727
policy, and breaking-change rules for `v0.3.x` (current patch:
28-
`v0.3.3`)
28+
`v0.3.5`)
2929
- `feedback-loop.md`: post-release feedback intake, issue-triage workflow,
3030
and backlog synchronization rules
3131
- `search-space.md`: supported parameter types, constraints framing, and
32-
multi-objective boundaries
32+
multi-objective handling
3333
- `constraints.md`: hard-constraint contract semantics, troubleshooting, and
3434
example authoring guidance
3535
- `decision-trace.md`: acquisition log schema and decision metadata guidance
@@ -46,6 +46,7 @@ Reference artifacts:
4646

4747
- `../PILOT.md`
4848
- `examples/README.md`
49+
- `examples/multi_objective/README.md`
4950
- `examples/state_snapshots/README.md`
5051
- `examples/decision_trace/README.md`
5152
- `examples/constraints/README.md`

docs/ci-knob-tuning.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ Default policy: top-k + median-of-repeats.
113113

114114
Recommended procedure:
115115

116-
1. rank candidate configs by primary objective
116+
1. rank candidate configs by the configured objective policy
117117
2. select top-k (for example `k=3` or `k=5`)
118118
3. run each candidate for `n` repeats (for example `n=3`)
119119
4. choose winner by median objective

docs/examples/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ Integration pattern note:
1010

1111
Included:
1212

13+
- `multi_objective/`: generated weighted-sum / lexicographic example pack with
14+
`status`, `report`, and trial-manifest outputs
1315
- `state_snapshots/`: sample state/log/CSV snapshots captured from a temp run
1416
of `templates/bo_client_demo`
1517
- snapshots include both `status: "ok"` and non-`ok` ingest examples
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Multi-Objective Example Pack
2+
3+
Generated reference artifacts for a small multi-objective run captured from
4+
`templates/bo_client_demo`.
5+
6+
This pack shows the current public multi-objective contract surface:
7+
8+
- `objective_schema.json`: weighted-sum example with `primary_objective`,
9+
`secondary_objectives`, and `scalarization`
10+
- `objective_schema_lexicographic.json`: alternative lexicographic objective
11+
contract using the same raw objective names
12+
- `suggestion_1.json` / `suggestion_2.json`: canonical suggestion payloads
13+
- `result_1.json` / `result_2.json`: ingest payloads with complete
14+
`objectives` maps
15+
- `status_after_ingest.json`: `best` with scalarized ranking metadata and raw
16+
`objective_vector`
17+
- `state/report.json` / `state/report.md`: report outputs with
18+
`objective_config`, scalarized ranking data, and `pareto_front`
19+
- `state/trials/trial_<id>/manifest.json`: trial manifests with
20+
`objective_vector` and `scalarization_policy`
21+
22+
The weighted-sum capture uses:
23+
24+
- primary objective: `loss` (`minimize`)
25+
- secondary objective: `throughput` (`maximize`)
26+
- scalarization policy: `weighted_sum`
27+
28+
The generated `report.json` shows both observed trials on the Pareto front, and
29+
`status_after_ingest.json` shows `best.objective_name = "scalarized"` with the
30+
raw objective vector preserved.
31+
32+
These files are documentation examples, not benchmark claims.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"seed": 17,
3+
"max_trials": 40,
4+
"initial_random_trials": 6,
5+
"candidate_pool_size": 600,
6+
"surrogate": {
7+
"type": "rbf_proxy",
8+
"length_scale": 0.22
9+
},
10+
"acquisition": {
11+
"type": "ucb",
12+
"kappa": 1.8,
13+
"xi": 0.01
14+
},
15+
"feature_flags": {
16+
"enable_botorch_gp": false,
17+
"fallback_to_proxy_if_unavailable": true,
18+
"enable_service_api_preview": false,
19+
"enable_dashboard_preview": false,
20+
"enable_auth_preview": false
21+
},
22+
"batch_size": 1,
23+
"stopping": {
24+
"min_improvement": 0.0001,
25+
"patience_trials": 10
26+
},
27+
"paths": {
28+
"state_file": "state/bo_state.json",
29+
"observations_csv": "state/observations.csv",
30+
"acquisition_log_file": "state/acquisition_log.jsonl",
31+
"constraints_schema_file": "../_shared/schemas/constraints.schema.json",
32+
"ingest_schema_file": "../_shared/schemas/ingest_payload.schema.json",
33+
"objective_schema_schema_file": "../_shared/schemas/objective_schema.schema.json",
34+
"search_space_schema_file": "../_shared/schemas/search_space.schema.json",
35+
"suggestion_schema_file": "../_shared/schemas/suggestion_payload.schema.json"
36+
}
37+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"primary_objective": {
3+
"name": "loss",
4+
"direction": "minimize"
5+
},
6+
"secondary_objectives": [
7+
{
8+
"name": "throughput",
9+
"direction": "maximize"
10+
}
11+
],
12+
"scalarization": {
13+
"policy": "weighted_sum",
14+
"weights": {
15+
"loss": 1.0,
16+
"throughput": 1.0
17+
}
18+
}
19+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"primary_objective": {
3+
"name": "loss",
4+
"direction": "minimize"
5+
},
6+
"secondary_objectives": [
7+
{
8+
"name": "throughput",
9+
"direction": "maximize"
10+
}
11+
],
12+
"scalarization": {
13+
"policy": "lexicographic"
14+
}
15+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"parameters": [
3+
{
4+
"name": "x1",
5+
"type": "float",
6+
"bounds": [0.0, 1.0],
7+
"description": "First controllable factor"
8+
},
9+
{
10+
"name": "x2",
11+
"type": "float",
12+
"bounds": [0.0, 1.0],
13+
"description": "Second controllable factor"
14+
}
15+
]
16+
}

0 commit comments

Comments
 (0)