Skip to content

Decision summaries

Use existing results to see how choices change with preferences:

policy = PreferencePolicy(
    normalization="minmax",
    weights=[
        {"accuracy": weight, "cost": 1 - weight}
        for weight in (0.25, 0.5, 0.75)
    ],
    equivalence={"accuracy": 0.01, "cost": 5.0},
)
sweep = preference_sweep(results, observables, policy=policy, constraints=constraints)
frame = sweep.summary.to_dataframe(include_metadata=True)

Every preference vector is explicit, finite and nonnegative and is normalized by its sum; omitted objectives have zero weight. Observable.weight is replaced by these preference vectors. Minimized objectives contribute positively and maximized objectives negatively to the weighted loss; smaller loss wins. Session raw means in metadata override already-weighted objective columns. Raw replicated tables are aggregated by design point, with duplicate or incomplete replicate identities refused. The summary retains raw-unit means.

Choose normalization explicitly. minmax uses the finite feasible alternatives in this table, so changing that set can change rankings. reference uses caller-provided raw-unit reference_bounds and allows values beyond the anchors without clipping. none retains original units, making the weight ratios unit dependent. Zero-range objectives contribute equally to every eligible design. Outputs expose the selected policy, effective weights and actual anchors.

ranks shows each design's competition rank across preferences; ties share a rank. Summary metadata includes the best/worst/mean rank, maximum weighted-loss regret and selection fraction. Tied winners split selection credit, so fractions sum to one when eligible alternatives exist. These are fractions of the supplied preference scenarios, not posterior probabilities or sampling confidence. pareto identifies feasible nondominated alternatives regardless of preferences. Infeasible/non-finite designs have NaN ranks and zero selection credit; if none remain, the result reports no choice. Feasibility follows existing constraint rules, including required standard errors for confidence constraints.

equivalent[a, b] compares every objective's raw mean difference with the specified practical tolerance; omitted tolerances are zero. This is a pairwise relation and need not be transitive. It expresses practical similarity of point estimates, not statistical evidence that the designs are equivalent.

For raw data with matching replicate sets, supply paired_reference as a design point id or config selector to attach the existing paired comparisons. Only eligible designs are compared. The requested paired_confidence is split across observables and the existing Bonferroni adjustment across alternatives. Bootstrap coverage remains approximate; paired t assumptions remain unchanged. Choosing a reference after observing outcomes and optional stopping are not corrected. Unequal replication needs an explicitly chosen common replicate set before calling this API. Without raw replicate identities, paired uncertainty cannot be reconstructed from means and standard errors alone.

trade_study.PreferencePolicy(weights, normalization, reference_bounds=dict(), equivalence=dict(), paired_confidence=0.95, paired_method='bootstrap', n_boot=2000, seed=0) dataclass

Explicit normalization, preferences, equivalence, and paired assumptions.

Attributes:

Name Type Description
weights list[dict[str, float]]

Preference vectors by observable name; nonnegative weights are normalized to sum to one. Omitted objectives have zero weight.

normalization str

minmax over finite feasible design means, reference using reference_bounds, or none in raw units.

reference_bounds dict[str, tuple[float, float]]

Raw-unit lower/upper anchors for reference scaling.

equivalence dict[str, float]

Pairwise practical-equivalence tolerances in raw units; omitted observables require exact equality.

paired_confidence float

Nominal joint confidence across requested comparisons and observables. Bootstrap intervals are approximate.

paired_method str

Existing paired-comparison method, bootstrap or t.

n_boot int

Bootstrap resample count.

seed int

Bootstrap seed; preference vectors themselves are explicit.

trade_study.PreferenceSweep(summary, weights, utilities, ranks, feasible, pareto, equivalent, normalization_bounds, paired, policy) dataclass

Exportable decision summaries and rankings under explicit preferences.

Attributes:

Name Type Description
summary ResultsTable

One raw-mean row per design, with decision metadata; export using ResultsTable.to_dataframe(include_metadata=True).

weights NDArray[float64]

Effective normalized weights, preference by observable.

utilities NDArray[float64]

Direction-aware weighted losses, preference by design.

ranks NDArray[float64]

Competition ranks (ties share a rank); excluded designs are NaN.

feasible NDArray[bool_]

Finite designs meeting the supplied constraints.

pareto NDArray[bool_]

Feasible nondominated designs, independently of preferences.

equivalent NDArray[bool_]

Pairwise raw-unit practical-equivalence matrix.

normalization_bounds dict[str, tuple[float, float]]

Actual anchors; empty for raw-unit normalization.

paired dict[str, list[PairedDifference]]

Optional existing paired comparisons against a supplied reference.

policy PreferencePolicy

Copy of all requested preference and inference assumptions.

trade_study.preference_sweep(results, observables, *, policy, constraints=None, paired_reference=None)

Assess choices across explicit preferences without evaluating a simulator.

Parameters:

Name Type Description Default
results ResultsTable

Existing raw-replicate or per-design results. Session raw means in metadata override weighted score columns.

required
observables list[Observable]

Decision objectives with directions. Observable.weight is replaced by the explicit preference vectors, avoiding double weights.

required
policy PreferencePolicy

Explicit normalization, preferences and equivalence assumptions.

required
constraints list[Constraint] | None

Feasibility restrictions, using existing ResultsTable rules.

None
paired_reference int | dict[str, Any] | None

Optional raw design-point id or config selector for existing paired comparisons. Requires matching replicate sets.

None

Returns:

Type Description
PreferenceSweep

Per-design exportable summaries, ranks and regret across preferences,

PreferenceSweep

feasible Pareto alternatives, pairwise practical equivalence and optional

PreferenceSweep

paired uncertainty. Tied winners share selection credit. With no finite

PreferenceSweep

feasible alternatives, selection fractions are zero and ranks are NaN.

Raises:

Type Description
ValueError

If objective names, table shape, preferences, normalization, or paired inference assumptions are invalid.

Notes

Minmax anchors depend on this feasible set. Reference anchors do not clip values outside their range. Practical equivalence compares raw means, not statistical evidence. Paired bootstrap/t assumptions and approximate coverage remain those of existing paired comparisons; optional stopping and selecting a reference after seeing results are not corrected here.

Source code in src/trade_study/decision.py
def preference_sweep(
    results: ResultsTable,
    observables: list[Observable],
    *,
    policy: PreferencePolicy,
    constraints: list[Constraint] | None = None,
    paired_reference: int | dict[str, Any] | None = None,
) -> PreferenceSweep:
    """Assess choices across explicit preferences without evaluating a simulator.

    Args:
        results: Existing raw-replicate or per-design results. Session raw means
            in metadata override weighted score columns.
        observables: Decision objectives with directions. Observable.weight is
            replaced by the explicit preference vectors, avoiding double weights.
        policy: Explicit normalization, preferences and equivalence assumptions.
        constraints: Feasibility restrictions, using existing ResultsTable rules.
        paired_reference: Optional raw design-point id or config selector for
            existing paired comparisons. Requires matching replicate sets.

    Returns:
        Per-design exportable summaries, ranks and regret across preferences,
        feasible Pareto alternatives, pairwise practical equivalence and optional
        paired uncertainty. Tied winners share selection credit. With no finite
        feasible alternatives, selection fractions are zero and ranks are NaN.

    Raises:
        ValueError: If objective names, table shape, preferences, normalization,
            or paired inference assumptions are invalid.

    Notes:
        Minmax anchors depend on this feasible set. Reference anchors do not clip
        values outside their range. Practical equivalence compares raw means,
        not statistical evidence. Paired bootstrap/t assumptions and approximate
        coverage remain those of existing paired comparisons; optional stopping
        and selecting a reference after seeing results are not corrected here.
    """
    names = [o.name for o in observables]
    if (
        not names
        or len(set(names)) != len(names)
        or set(names) - set(results.observable_names)
    ):
        msg = "Decision objectives must be distinct names in the results"
        raise ValueError(msg)
    if results.scores.shape != (len(results.configs), len(results.observable_names)):
        msg = "Decision score matrix has an incompatible row/column shape"
        raise ValueError(msg)
    if (
        not 0 < policy.paired_confidence < 1
        or policy.paired_method not in {"bootstrap", "t"}
        or policy.n_boot < 1
    ):
        msg = "Invalid paired inference assumptions"
        raise ValueError(msg)
    policy = deepcopy(policy)
    summary, eligible = _decision_table(results, names, constraints or [])
    raw = np.asarray(summary.scores, dtype=np.float64)
    weights = _weights(policy, names)
    normalized, bounds = _normalize(raw, eligible, names, policy)
    utilities = _utilities(normalized, eligible, weights, observables)
    ranks, selection = _ranks(utilities, eligible)
    pareto, equivalent = _alternatives(raw, eligible, observables, policy)
    report = PreferenceSweep(
        summary,
        weights,
        utilities,
        ranks,
        eligible,
        pareto,
        equivalent,
        bounds,
        {}
        if paired_reference is None
        else _paired(results, summary, eligible, observables, paired_reference, policy),
        policy,
    )
    _decorate_summary(report, selection)
    return report