// Pareto — Collector block. // // Keeps the non-dominated set across the referenced scorer instances (the // Pareto frontier): a schedule survives unless another is at least as good // on every scorer and strictly better on at least one. Two or more scorers // are the sensible case; with a single scorer nothing dominates a co-best // schedule, so the frontier keeps every schedule tied at that scorer's best // value. // // The `over` entries are scorer instanceIds from the same pipeline, not // Scorer closures: they resolve to compiled scorers only at run-wiring time // (as `objective.lexicographic` does), so the emitted spec stays plain JSON. // // Collectors are a selection layer above the solver, so `toMiniZinc` emits // an explanatory comment rather than a constraint fragment. import { z } from "zod"; import type { ParetoCollector } from "../level-core/types.ts"; import type { CollectorBlock, MiniZincFragment } from "./types.ts"; export const ParetoInputSchema = z.object({ /** Scorer instanceIds spanning the objective space; at least one. */ over: z.array(z.string()).min(1), }); export type ParetoInput = z.infer; const ParetoOutputSchema: z.ZodType = z.object({ kind: z.literal("Pareto"), over: z.array(z.string()).min(1), }); export const ParetoBlock: CollectorBlock = { id: "Pareto", schema: { input: ParetoInputSchema, output: ParetoOutputSchema }, apply: ({ over }): ParetoCollector => ({ kind: "Pareto", over }), toMiniZinc: (): MiniZincFragment => ({ text: `% Pareto — selection layer; keeps the non-dominated set across the referenced scorers, emits no solver constraint.`, }), doc: { nl: "Keeps the non-dominated set across the referenced scorers — the Pareto frontier.", pseudocode: "candidates.filter(c => !candidates.some(o => dominates(o, c, over)))", }, };