Decoupled OSY example

This executable example applies qPOTS-Decoupled to the constrained OSY benchmark. OSY has six design variables, two objectives, and six inequality constraints, giving eight output oracles in total.

Why OSY is useful here

A coupled batch of two designs requires 16 scalar oracle evaluations. In qPOTS-Decoupled mode, the acquisition returns a task subset for each design. Values from tasks outside that subset are stored as NaN and excluded from the next multitask Gaussian-process fit.

Key configuration

config = QPOTSConfig(
    n_initial=60,
    iterations=50,
    batch_size=2,
    n_constraints=6,
    generations=20,
    multitask=True,
    partial_evaluations=True,
    correlation_threshold=1e-4,
    seed=1023,
)

The multitask model shares information across all eight outputs. partial_evaluations enables output selection, while correlation_threshold activates the total-correlation gate and mutual-information subset rule.

Complete script

decoupled_osy_example.py
 1"""Run decoupled qPOTS oracle selection on the constrained OSY benchmark."""
 2
 3from __future__ import annotations
 4
 5import argparse
 6from pathlib import Path
 7
 8import torch
 9
10from qpots import Function, QPOTSConfig, QPOTSRunner, RuntimeConfig
11
12
13def parse_args() -> argparse.Namespace:
14    """Parse example runtime and output settings."""
15    parser = argparse.ArgumentParser()
16    parser.add_argument("--n-initial", type=int, default=60)
17    parser.add_argument("--iterations", type=int, default=50)
18    parser.add_argument("--batch-size", type=int, default=2)
19    parser.add_argument("--generations", type=int, default=20)
20    parser.add_argument("--threshold", type=float, default=1e-4)
21    parser.add_argument("--seed", type=int, default=1023)
22    parser.add_argument("--output-dir", type=Path, default=Path("."))
23    parser.add_argument(
24        "--quick",
25        action="store_true",
26        help="Use a small CPU configuration for an end-to-end smoke test.",
27    )
28    return parser.parse_args()
29
30
31def report(result):
32    queried = (~torch.isnan(result.observed_values)).sum().item()
33    print(
34        f"Iteration {result.iteration}: queried "
35        f"{queried}/{result.observed_values.numel()} scalar oracles"
36    )
37
38
39def main() -> None:
40    """Run the example and save its partially observed training data."""
41    args = parse_args()
42    if args.quick:
43        args.n_initial = 12
44        args.iterations = 1
45        args.batch_size = 2
46        args.generations = 2
47
48    runtime = RuntimeConfig(device="cpu", dtype=torch.float64)
49    problem = Function("osy", dim=6, nobj=2, runtime=runtime)
50    config = QPOTSConfig(
51        n_initial=args.n_initial,
52        iterations=args.iterations,
53        batch_size=args.batch_size,
54        n_constraints=6,
55        generations=args.generations,
56        multitask=True,
57        partial_evaluations=True,
58        correlation_threshold=args.threshold,
59        seed=args.seed,
60    )
61    result = QPOTSRunner(problem, config, runtime=runtime, callbacks=[report]).run()
62    args.output_dir.mkdir(parents=True, exist_ok=True)
63    torch.save(result.train_x.cpu(), args.output_dir / "osy_train_x.pt")
64    torch.save(result.train_y.cpu(), args.output_dir / "osy_partial_train_y.pt")
65    queried = sum(int((~torch.isnan(step.observed_values)).sum()) for step in result.iterations)
66    possible = args.iterations * args.batch_size * result.train_y.shape[-1]
67    print(f"Selected {queried}/{possible} possible infill oracle evaluations")
68
69
70if __name__ == "__main__":
71    main()

The callback reports how many scalar outputs were retained from each possible 16-output batch. The saved osy_partial_train_y.pt tensor contains objective columns first and constraint columns second, with NaN at unobserved tasks.

Run the example

python -m examples.decoupled_osy_example

For a small end-to-end check on CPU, use:

python -m examples.decoupled_osy_example --quick --output-dir /tmp/qpots-osy

These commands assume a source checkout. With a PyPI-only installation, save the complete script above as main.py and replace python -m examples.decoupled_osy_example with python main.py. See Running the repository examples.

This example is substantially more expensive than the introductory examples because it refits a joint multitask Gaussian process after every partially observed batch. Reduce n_initial, iterations, and generations while validating a local environment.

The built-in OSY function is fully evaluated for benchmark bookkeeping, but only selected values enter model training. See Benchmarking versus real oracle calls for the distinction between this emulation and an external workflow that avoids unselected oracle calls entirely.

The saved output order is always two objectives followed by six constraints. When a complete row is available, it is feasible only if every constraint is greater than or equal to zero. NaN denotes an output that was not queried; it is not a feasibility value.