Core API Reference
The public core abstractions, experiment runner, and results store. These are
re-exported at the package top level (from dp_testing_platform import ...); the
canonical definitions live in dp_testing_platform.core.
Tasks and algorithms
dp_testing_platform.core.task_spec
Task
register_instance
Register an instance generator class for this task (use as decorator).
register_algorithm
Register an algorithm class for this task (use as decorator).
get_algorithms
Algorithms registered directly to this task.
get_instance_generators
Instance generators registered directly to this task.
get_all_default_instances
Yield default instances for this task (all param grid combinations).
dp_testing_platform.core.algorithm
The algorithm base class every DP algorithm inherits from.
:class:BaseAlgorithm fixes the contract a task's algorithms share: a single
:meth:BaseAlgorithm.run over instances in the algorithm's own task format, plus
the HYPERPARAM_INFO introspection surface from which the DP hyperparameter
tuners derive search grids and default values. Cross-task reuse is not handled
here — a parent-task algorithm is presented to a child task as a lifted class
(see core.lifted), never adapted inside run.
BaseAlgorithm
Bases: ABC
Abstract base class for an algorithm native to a specific :class:Task.
Subclasses implement the single abstract method :meth:run, which consumes
an instance in self.TASK format and returns that task's output. All
hyperparameters are declared once in HYPERPARAM_INFO; the base derives
the search grid and default values from it, so the DP hyperparameter tuners
introspect an algorithm without any per-algorithm wiring.
Attributes:
| Name | Type | Description |
|---|---|---|
TASK |
Task
|
The task this algorithm is native to (set when the class is
registered on a task). |
HYPERPARAM_INFO |
dict[str, dict[str, Any]]
|
Per-hyperparameter metadata keyed by parameter name. Recognized fields per entry:
|
hyperparams
property
Get the current hyperparameter values for this algorithm instance.
__init__
__init__(name: str | None = None, description: str | None = None, metadata: dict[str, Any] | None = None)
Initialize the algorithm.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str | None
|
Display name for the algorithm. |
None
|
description
|
str | None
|
Brief description of the algorithm. |
None
|
metadata
|
dict[str, Any] | None
|
Arbitrary metadata dict (e.g., tuning config). |
None
|
get_hyperparam_keys
classmethod
Get the hyperparameter keys for this algorithm.
get_default_hyperparam_grid
classmethod
Get the default hyperparameter grid for this algorithm.
default_grid (an explicit candidate list, required for categorical
parameters) takes precedence over default_range, mirroring
default_search_grid.
default_search_grid
classmethod
Build a discrete search grid for the DP private-selection tuners.
Reads HYPERPARAM_INFO only (no data or problem metadata), so the
grid is data-independent and safe to hand to a DP selector as
alg_hyperparam_grid. For each tunable parameter:
"default_grid"(an explicit candidate list), when present, is used verbatim;- otherwise a numeric
"default_range"[min, max]is discretized inton_pointsvalues -- geometrically spaced when"scale" == "log", evenly spaced otherwise. Integer-typed parameters are rounded to ints and de-duplicated (so a coarse integer range yields fewer points); - a
"default_range"holding non-numeric (categorical) values cannot be interpolated and is enumerated verbatim.
Parameters with neither default_grid nor default_range, and any
marked "tunable": False, are omitted -- the latter guards against a
tuner selecting an unsound configuration (e.g. ObjectivePerturbation's
output_budget_frac).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n_points
|
int
|
Number of grid points per continuous (range-based) parameter. |
7
|
Returns:
| Type | Description |
|---|---|
dict[str, list[Any]]
|
Maps each included parameter name to its list of candidate values. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If a |
get_default_hyperparams
classmethod
Get the default hyperparameters for this algorithm.
run
abstractmethod
Run the algorithm on an instance of its own task.
Data is guaranteed to be in self.TASK format; a cross-task algorithm
is exposed as a native lifted class of the target task (see
core.lifted) rather than adapted here.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
metadata
|
TMetadata
|
Problem specification in self.TASK format. |
required |
input
|
TInput
|
The private input data in self.TASK format. |
required |
budget
|
DPBudget
|
Privacy allowance for this run. |
required |
seed
|
int
|
Seed for reproducibility. |
required |
Returns:
| Type | Description |
|---|---|
TOutput
|
The algorithm's output in self.TASK format. |
from_grid
classmethod
Yield algorithm objects for all combinations of parameters.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
**param_grid
|
list[Any]
|
Maps parameter names to lists of values. |
{}
|
Yields:
| Type | Description |
|---|---|
BaseAlgorithm
|
One algorithm per parameter combination, with auto-generated name. |
dp_testing_platform.core.lifted
Factory for lifted-algorithm classes.
A lifted algorithm presents a parent-task algorithm as a native algorithm of a
child task. The factory forwards the inner algorithm's introspection surface
(HYPERPARAM_INFO and the derived search grids) verbatim,
so the DP hyperparameter tuners and the result registry treat a lifted class
exactly like a hand-written algorithm of the child task: it instantiates with
cls(**hyperparams), exposes flat per-parameter grids, and serializes to an
importable module:ClassName path.
The child->parent adaptation is baked in at import time: the lift functions and
the inner class are captured in a module-level class, hyperparameters bind at
construction, and only data moves at run time (lift input -> inner.run ->
push output, per call). Depth-2 chains compose by wrapping an already-lifted
class (its run performs its own inner lift).
Privacy invariant (checked by the privacy review): lift_input MUST be
per-row -- record i of the child input maps to record i of the parent
input, with no cross-row statistics. Under that condition add/remove-one
neighbors map to add/remove-one neighbors, the inner privacy guarantee transfers
verbatim to the child data, and push_output is post-processing
(privacy-free).
Authoring note (import discipline): a task's lifted.py imports the donor
algorithm classes it wraps from their leaf modules
(...m_estimation.algorithms.dp_gradient_descent), never through a package
__init__ re-export. Leaf modules depend only on core and their own siblings,
so wrapping across tasks stays free of package-initialization import cycles.
make_lifted_algorithm
make_lifted_algorithm(inner_cls: type[BaseAlgorithm], *, task: Task, lift_metadata: Callable[[ChildMetadata], ParentMetadata], lift_input: Callable[[ChildInput], ParentInput], push_output: Callable[[ParentOutput, ChildMetadata], ChildOutput], name: str) -> type[BaseAlgorithm]
Build a class running inner_cls as a native algorithm of task.
The returned class carries TASK = task and forwards inner_cls's
HYPERPARAM_INFO (so grids match the inner exactly); it records the donor
task on LIFTED_FROM and the donor
class on INNER. Its __init__ accepts the inner algorithm's
hyperparameters (plus name/description/metadata), builds an inner
instance, and mirrors the resolved hyperparameters onto self so the
hyperparams property and registry identity match. run lifts the
child metadata/input to the parent format, runs the inner algorithm, and
pushes the parent output back to the child format.
__module__/__qualname__/__name__ are stamped to the calling
module and name so get_function_path yields an importable path -- the
class must be bound to a module-level global of the same name for the
path to re-import.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
inner_cls
|
type[BaseAlgorithm]
|
The parent-task algorithm class to wrap (may itself be a lifted class, giving a composed depth-2 lift). |
required |
task
|
Task
|
The child task the lifted class runs as a native algorithm of. |
required |
lift_metadata
|
Callable[[ChildMetadata], ParentMetadata]
|
Map child metadata to the inner algorithm's task metadata. |
required |
lift_input
|
Callable[[ChildInput], ParentInput]
|
Map child input to the inner algorithm's task input; MUST be per-row (privacy invariant). |
required |
push_output
|
Callable[[ParentOutput, ChildMetadata], ChildOutput]
|
Map the inner algorithm's output back to the child output, given the child instance metadata (enables metadata-dependent projections, e.g. selecting a coefficient of interest). |
required |
name
|
str
|
The class name (e.g. |
required |
Returns:
| Type | Description |
|---|---|
type[BaseAlgorithm]
|
A |
dp_testing_platform.core.instance_generator
InstanceGenerator
Bases: ABC
params
property
Get the current parameter values for this generator instance.
generate
abstractmethod
Generate an instance for the task.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
seed
|
int
|
Seed for random number generation (may be ignored by some generators). |
required |
Returns:
| Type | Description |
|---|---|
tuple[TMetadata, TInput, Metric]
|
A (metadata, input_data, metric) tuple for the task. |
get_param_keys
classmethod
Get the parameter keys for this instance generator class.
get_default_param_grid
classmethod
Get the default parameter grid for this instance generator class.
get_default_params
classmethod
Get the default parameters for this instance generator class.
MixtureInstanceGenerator
Bases: InstanceGenerator
Instance generator that samples from a weighted mixture of generator classes.
Each generator spec provides a class (or importable path), a parameter grid, and an optional weight. At generation time, a generator class is sampled according to the weights, then parameters are sampled uniformly from the grid. This avoids combinatorial explosion over all generator/parameter combinations.
__init__
Initialize from a list of generator specifications.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
generator_specs
|
Sequence[dict[str, Any]]
|
Each dict must have 'generator_class' (or 'path') and optionally 'params' (grid dict) and 'weight' (float). |
required |
generate
Generate an instance by sampling a generator class and parameters from the mixture.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
seed
|
int
|
Seed for reproducible generator/parameter selection and data generation. |
required |
Returns:
| Type | Description |
|---|---|
tuple[TMetadata, TInput, Metric]
|
A (metadata, input_data, metric) tuple from the sampled generator. |
dp_testing_platform.core.metrics
Metric
dataclass
Concrete metric with callable and bound arguments.
__call__
Evaluate the metric on the given output.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
output
|
TOutput
|
The algorithm output to evaluate. |
required |
**kwargs
|
Any
|
Additional arguments merged with bound args. |
{}
|
Returns:
| Type | Description |
|---|---|
float
|
The computed metric value. |
MetricSpec
dataclass
Specification for a metric, optionally allowing custom functions.
A DP quality metric (registered in a task's dp_quality_metrics and used
for private hyperparameter selection) is a cost: lower is better, because
the DP selectors take the argmin. Expose a utility-like quantity as its cost
complement (e.g. error rate, not accuracy).
create_metric
Create a concrete Metric instance from this spec.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fn
|
TMetricFn | None
|
Custom metric function. Uses the spec's default if not provided. |
None
|
**override_args
|
Any
|
Override default_args for this instance. |
{}
|
Returns:
| Type | Description |
|---|---|
Metric
|
A bound Metric ready to evaluate outputs. |
serialize_for_json
Serialize the metric specification for JSON storage.
dp_testing_platform.core.domain
Domain
Bases: ABC
Abstract base class for domain constraints.
Domains specify constraints on data points and provide methods for computing diameters (used in sensitivity calculations) and projecting points onto the domain.
l2_diameter
abstractmethod
Compute the L2 diameter of the domain.
Returns:
| Type | Description |
|---|---|
float
|
The L2 diameter (max L2 distance between any two points). |
l1_diameter
abstractmethod
Compute the L1 diameter of the domain.
Returns:
| Type | Description |
|---|---|
float
|
The L1 diameter (max L1 distance between any two points). |
project
abstractmethod
Project point(s) onto the domain.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
ndarray
|
Point(s) to project. Can be a single point (1D) or multiple points (2D with points as rows). |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
The projected point(s) with the same shape as input. |
l2_norm_upper_bound
Sound upper bound on the L2 norm of any point in the domain.
Returns an upper bound on max_{x in domain} ||x||_2, or None when no
sound L2 bound can be derived for this domain type. The base implementation
returns None (the safe default): callers that tighten a norm-clipping
threshold with this bound keep their own computed clip when None is
returned, which is the conservative choice (more noise, never less).
This is privacy-load-bearing: it must be a true UPPER bound. Clipping rows to
min(computed_clip, l2_norm_upper_bound()) and calibrating the mechanism noise to
that same value is privacy-safe precisely because the clip is what enforces
the sensitivity; an over-estimate only forgoes a tightening, while an
under-estimate is never produced here. Subclasses override only when they can
prove the bound sound.
Returns:
| Type | Description |
|---|---|
float | None
|
A sound L2 upper bound, or |
BoxDomain
dataclass
Bases: Domain
Axis-aligned bounding box domain.
A box domain is defined by per-dimension lower and upper bounds. The bounds array has shape (d, 2) where d is the dimension, bounds[:, 0] are lower bounds, and bounds[:, 1] are upper bounds.
Attributes:
| Name | Type | Description |
|---|---|---|
bounds |
Array2D
|
Array of shape (d, 2) with lower and upper bounds. |
l2_norm_upper_bound
Max L2 norm over the box: sqrt(sum_i max(lo_i^2, hi_i^2)).
Each coordinate independently attains its largest magnitude at whichever
endpoint is farther from 0, so max_i x_i^2 = max(lo_i^2, hi_i^2) and the
worst-case squared L2 norm is their sum. This is a sound (in fact tight)
upper bound on the L2 norm of any point in the box.
LpBallDomain
dataclass
Bases: Domain
Lp ball domain: {x : ||x - center||_p <= radius}.
An Lp ball is defined by a norm type p, a radius, and optionally a center point. If center is None, the ball is centered at the origin.
Attributes:
| Name | Type | Description |
|---|---|---|
p |
LpNorm
|
The Lp norm (1, 2, or np.inf). |
radius |
float
|
Radius of the ball. |
dim |
int
|
Dimension of the space. |
center |
Array1D | None
|
Center of the ball. None means origin. |
l2_norm_upper_bound
Max L2 norm over the ball -- sound only for the L2 ball (p == 2).
For p == 2 every point satisfies ||x - center||_2 <= radius, so by the
triangle inequality ||x||_2 <= radius + ||center||_2 (just radius when
the ball is centred at the origin). A sound upper bound.
For p != 2 (L1 / Linf balls) we return None rather than convert a
different-norm radius into an L2 bound here, so callers conservatively keep
their own clip. (A sound L2 bound does exist -- e.g. an L1 ball gives
||x||_2 <= ||x||_1 <= radius -- but is intentionally out of scope for this
audited helper.)
tighten_clip_to_domain
Tighten an L2 norm-clipping threshold using a domain bound when one exists.
Returns min(computed_clip, domain.l2_norm_upper_bound()) when domain supplies a
sound L2 upper bound (see :meth:Domain.l2_norm_upper_bound), otherwise computed_clip
unchanged. There is no point clipping rows looser than the domain already
guarantees, and a tighter clip means a smaller calibrated sensitivity (less
noise).
Privacy note: the returned value is the clip that MUST also be passed to the
mechanism's noise calibration. Clipping rows to it enforces exactly that L2
sensitivity, so the tightening is always sound; because the domain bound is a
sound UPPER bound, min never relaxes the clip an algorithm would otherwise
have used. Behaviour-preserving (returns computed_clip) when domain is
None or yields no bound -- the common case.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
computed_clip
|
float
|
The clip threshold an algorithm computed from its
hyperparameters (e.g. |
required |
domain
|
Domain | None
|
The public metadata domain bounding the clipped quantity, or
|
required |
Returns:
| Type | Description |
|---|---|
float
|
The clip threshold to both clip to and calibrate noise against. |
dp_testing_platform.core.exceptions
Custom exceptions for the DP Testing Platform.
NoRelease
Bases: Exception
Raised when a DP algorithm outputs "no result" (the failure outcome ⊥).
Contract: raising this is a RELEASE, not a crash. The failure event is an outcome of the mechanism itself, and the algorithm's charged (eps, delta) budget must cover it. Consumers may therefore observe and post-process the failure freely — the engine records an honest "no_release" status row, and the DP selection tuners score a failed candidate +inf without extra budget.
Two compliant kinds of raiser:
- Data-independent infeasibility: the failure is a deterministic function of public quantities (metadata, hyperparameters, eps/delta), so the failure bit carries no information about the private data. Example: a smoothing parameter admitting no valid noise dilation at the requested eps.
- Accounted data-dependent failure: the failure probability depends on the private data, and the raiser's privacy analysis covers the joint release over outputs-plus-⊥. Example: a PTR (Propose-Test-Release) check failure. Such a raiser must state that argument at the raise site.
A failure that is NOT part of the accounted release — a solver that could not certify its tolerance, non-convergence, numerical breakdown — must use an ordinary exception (e.g. RuntimeError) instead: those are execution errors, recorded as "error" rows, and never fed back into DP selection.
Distinct from ValueError (invalid inputs/configuration).
Experiments and results
dp_testing_platform.core.experiment
Sweep orchestration: evaluate DP algorithms across a privacy-parameter grid.
:func:run_experiment is the engine entry point. It runs every
(algorithm, instance, budget, seed) cell of a sweep, each in a supervised
child process, so a cell that times out or breaches its memory cap is recorded as
a failure while the rest of the sweep continues (failure isolation). All
persistence lives under one workdir: the results store and both registries
are written there, each cell is committed as it completes, and the default resume
mode recomputes only the cells missing from a prior run.
load_existing_result_keys
Return canonical keys for every stored result in results_dir.
Reads the union of results.csv and any in-flight shard files, so
cells persisted by an interrupted run are seen as done.
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If a results file exists but cannot be parsed (corrupt file or unexpected schema). Failing loudly prevents silently re-running or overwriting results that deduplication could not see. |
run_experiment
run_experiment(task: Task, algs: Sequence[BaseAlgorithm], instance_generators: Sequence[InstanceGenerator], budgets: Sequence[DPBudget] = _UNSET, *, alg_seeds: int | Sequence[int] = _UNSET, instance_generator_seeds: int | Sequence[int] = _UNSET, workdir: Path | str | None = _UNSET, mode: str = _UNSET, time_limit: float | None = _UNSET, memory_limit_gb: float | None = _UNSET, n_jobs: int = _UNSET, verbose: bool = _UNSET, config: ExperimentConfig | None = None) -> pd.DataFrame
Run an experiment across concrete budgets, algorithms, and instances.
All persistence lives under workdir: the engine creates, loads, and
saves both registry files (algorithm_registry.json /
instance_generator_registry.json) and the results store
(results.csv + in-flight shards/) directly in that directory —
user code never constructs or saves a registry. Reload a complete,
labelled result set later with load_results(workdir).
Each completed cell is persisted immediately (commit per cell): an
interrupted run loses at most the cells that were in flight, and the
default mode="resume" means a re-run computes only the missing
cells — finished work is never silently recomputed. A completed run
consolidates everything into a single results.csv.
The sweep is a parameter grid over budget and instance_seed.
Concrete typed budgets= are the only privacy-axis input. Settings can
equivalently be supplied as a validated
:class:ExperimentConfig via config= (mutually exclusive with the
individual settings keywords).
Every cell runs in a supervised child process (unless both
time_limit and memory_limit_gb are None): the instance is
generated, the algorithm run, and the metric evaluated inside the
child, so instance data never accumulates in the parent and a cell
that exceeds its memory cap is killed and recorded as a
"memory_limit_exceeded" failure while the sweep continues.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
task
|
Task
|
The task to evaluate. |
required |
algs
|
Sequence[BaseAlgorithm]
|
Algorithm instances to evaluate on every cell. Per-instance
tuning is expressed by including a |
required |
instance_generators
|
Sequence[InstanceGenerator]
|
Instance generators to produce test data. |
required |
budgets
|
Sequence[DPBudget]
|
Concrete privacy allowances (grid axis |
_UNSET
|
alg_seeds
|
int | Sequence[int]
|
Algorithm seeds. An int N means the deterministic
seeds |
_UNSET
|
instance_generator_seeds
|
int | Sequence[int]
|
Seeds for instance generation (grid axis
|
_UNSET
|
workdir
|
Path | str | None
|
Directory owning all persistence for the sweep.
|
_UNSET
|
mode
|
str
|
|
_UNSET
|
time_limit
|
float | None
|
Max seconds per cell. |
_UNSET
|
memory_limit_gb
|
float | None
|
Hard cap (in GB) on the resident memory of one
cell's process tree, enforced by a watchdog that kills the
cell on breach. |
_UNSET
|
n_jobs
|
int
|
Number of parallel worker processes (joblib/loky) over cells. 1 runs serially in-process; -1 uses all cores. |
_UNSET
|
verbose
|
bool
|
Render a per-completed-cell progress bar (tqdm; notebook or terminal automatically) and detailed logging. |
_UNSET
|
config
|
ExperimentConfig | None
|
A pre-built :class: |
None
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
Results for every cell of the requested grid — the union of |
DataFrame
|
previously stored rows and newly computed ones (so |
DataFrame
|
re-runs return the full result set, not just the new rows). When |
DataFrame
|
|
dp_testing_platform.core.experiment_config
Validated configuration for experiment sweeps over concrete DP budgets.
ExperimentConfig
Bases: BaseModel
Hold the validated settings for one experiment sweep.
budgets is the single privacy axis. Identical budgets are deduplicated
in caller order. JSON budget objects are converted to typed budgets at this
validation boundary; the engine never sees epsilon/delta configuration
sugar or a generic parameter grid.
grid_points
Enumerate scalar budget and instance-seed coordinates.
dp_testing_platform.core.result_manager
Durable, typed CSV storage for experiment results.
Results live in a results directory as one human-openable results.csv
plus, while a run is in flight, per-process shard files under shards/.
Each completed experiment cell is appended to a shard immediately (commit
per cell), so an interrupted run loses at most the in-flight cells. Loaders
always read the union of results.csv and any shards; a completing run
consolidates everything back into a single results.csv.
All reads apply an explicit dtype schema. Result identity is carried by the canonical typed-budget string; analysis floats retain round-trip formatting but never participate in identity.
OutputWriter
Durably persist each cell's full released output to the sidecar.
Stores the task's Output as a field dict ({field_name: value}): None
fields are omitted (the dataclass default refills them on reconstruct) and
scalar fields are coerced to 0-d arrays, so the store stays task-agnostic
(raw values, no Output type imported). Each field becomes one array in the
cell's NPZ shard under output_shards/ (array name f"{key}::{field}"),
the file named by a hash of the cell key (so re-running a cell overwrites its
own file). Every write is flushed and fsynced, so a committed output survives
a SIGKILL — mirroring :class:ShardWriter's durability. Instances are cheap
and picklable (they hold only the results directory), so one writer ships to
all parallel workers.
write_output
Append one cell's output field dict to its own sidecar shard.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
The cell's content-addressed output key (see :func: |
required |
fields
|
Mapping[str, Any]
|
The output dataclass as |
required |
ShardWriter
Durably append result rows to a per-process shard file.
Each writing process gets its own shard (named by run id + pid), so no
file ever sees concurrent appends — safe under joblib worker
processes. Every row is flushed and fsynced on write: once
:meth:write_row returns, the cell survives a SIGKILL of the whole
process tree.
Instances are cheap, stateless, and picklable (they hold only the results directory and run id), so one writer can be shipped to all parallel workers.
write_row
Append one result row to this process's shard, creating it if needed.
Rows that do not already carry the library_version /
library_commit provenance stamps get them filled in from the
writing process's runtime environment; rows without a runtime_s
measurement get NaN; rows without a privacy_sound verdict default
to True (no diagnostic modification recorded); rows without a
peak_rss_mb measurement get NaN.
Any keys beyond the fixed columns are task-derived key-metric columns: they are written (as %.17g floats) after the fixed columns. All rows from one process share a task, so they carry the same key-metric columns — the header (written once per shard) stays consistent.
results_frame
Build a results DataFrame with the fixed columns, dtypes, and any key metrics.
Fixed columns always appear in their canonical order; any extra keys carried by the rows are task-derived key-metric columns, appended after the fixed columns (in first-seen order) and typed float64.
has_shards
Return whether any in-flight/interrupted-run shard files exist.
load_result_rows
Load the typed union of results.csv and any in-flight shards.
Shard rows (from interrupted or in-flight runs) are appended after the
consolidated rows, so on duplicate keys the shard row is the later one.
This is the raw row loader (no name labelling) used by the engine; user
code usually wants :func:load_results.
Returns:
| Type | Description |
|---|---|
DataFrame
|
A DataFrame with the canonical result columns and dtypes; empty |
DataFrame
|
(but correctly typed) when no results exist. |
load_results
Load labelled results from an experiment workdir, fresh-process safe.
Reads the typed union of results.csv and any in-flight shards under
workdir and joins human-readable names from the persisted registry
files: an alg_name column (from algorithm_registry.json) and an
instance_name column (from instance_generator_registry.json,
dataset_id:converter_id for real datasets). Requires no in-memory
state — a fresh kernel can reload a complete, labelled result set from
disk alone. UUIDs without a registry entry keep the UUID as the name.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
workdir
|
Path | str
|
The directory passed as |
required |
Returns:
| Type | Description |
|---|---|
DataFrame
|
The results DataFrame with the canonical columns plus |
DataFrame
|
and |
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
If the workdir holds no results at all. Run
|
unregistered_result_uuids
Return result UUIDs absent from the persisted registry files.
The registries must cover every UUID appearing in the results:
:func:load_results labels rows by joining on them, so a missing entry
silently degrades a row to its raw content-addressed UUID. Checked at
consolidation time so a multi-call sweep that failed to union its
registries is caught immediately rather than surfacing later as
unresolvable names.
Returns:
| Type | Description |
|---|---|
dict[str, set[str]]
|
A dict with keys |
dict[str, set[str]]
|
of result UUIDs (from |
dict[str, set[str]]
|
entry in the corresponding registry file. Empty sets mean the |
dict[str, set[str]]
|
invariant (registry ⊇ every result UUID) holds. |
consolidate_results
Merge results.csv and all shards into one results.csv.
Deduplicates on the canonical budget-bearing key keeping the last occurrence
(shard rows win over previously consolidated rows, so re-running with
mode="overwrite" replaces old rows). The merged file is written
atomically (temp file + os.replace) and the shards/ directory is
removed — shards exist on disk only for in-flight/interrupted runs.
Returns:
| Type | Description |
|---|---|
DataFrame
|
The consolidated, typed DataFrame. |
result_key
result_key(inst_gen_uuid: str, alg_uuid: str, budget: BudgetString, alg_seed: int, instance_seed: int) -> ResultKey
Build the one canonical identity tuple for a result cell.
output_key
output_key(inst_gen_uuid: str, alg_uuid: str, budget: BudgetString, alg_seed: int, instance_seed: int) -> str
Build the output-sidecar key string for one result cell.
Uses the same canonical string identity as :func:result_key; callers
convert typed budgets once at their boundary.
load_outputs
Load the output sidecar as key -> {field name -> value}, fresh-process safe.
Reads the union of outputs.npz and any in-flight output_shards/ NPZ
files (the latter present only during or after an interrupted run); shard
entries win on duplicate keys. Each cell's value is the stored field dict of
its task's Output (raw field values — task-agnostic, no Output type imported);
None fields were omitted on write and scalar fields are 0-d arrays. Returns an
empty dict when no sidecar exists (for example, when every output exceeded
the size threshold), so callers degrade gracefully rather than crashing.
has_output_shards
Return whether any in-flight output-sidecar shard files exist.
consolidate_outputs
Merge outputs.npz and all sidecar shards into one outputs.npz.
Unions the previously consolidated sidecar with every in-flight shard
(shard entries win on duplicate composite keys, so mode="overwrite"
replaces old outputs), writes the result atomically, and removes
output_shards/. A no-op (no file written) when nothing was stored.
Returns:
| Type | Description |
|---|---|
dict[str, dict[str, Any]]
|
The consolidated |
dict[str, dict[str, Any]]
|
outputs exist). |
Registries
The engine owns these — it creates, loads, and saves both registries inside the experiment workdir, so user code never constructs one. They map algorithms and instance generators to the UUIDs that key the results store.
dp_testing_platform.core.algorithm_registry
AlgorithmRegistry
Registry for algorithm objects, with UUID management.
Two in-memory stores back the registry. _registry (uuid -> live
algorithm object) holds the objects that could be reconstructed, used for
identity matching and get_algorithm. _records (uuid -> serialized
JSON record) is the source of truth for what is written: it accumulates
the union of every entry ever loaded or registered, so a multi-call sweep
grows the on-disk registry and never overwrites entries from earlier calls
(even ones whose live object could not be rebuilt). On disk the file is a
JSON list of those records.
save
Save the algorithm registry to disk as the union of all known entries.
Writes _records (every entry loaded from disk plus everything
registered this session), refreshing the record of any entry whose live
object is present (so metadata merges from get_uuid are captured).
Entries from earlier run_experiment calls are therefore never
dropped.
get_uuid
Get or register a UUID for the given algorithm.
Uses class path and hyperparams for identity. If a duplicate is found, merges metadata (new keys/values overwrite old).
get_algorithm
Retrieve an algorithm object by its UUID.
Raises:
| Type | Description |
|---|---|
ValueError
|
If no algorithm with the given UUID is found. |
__getitem__
Get an algorithm by UUID using dictionary-like access.
find_by_name
Find the first algorithm matching the given name.
dp_testing_platform.core.instance_generator_registry
InstanceGeneratorRegistry
Registry for instance generators and their parameters, with UUID management.
Two in-memory stores back the registry. _registry (uuid -> live
generator object) is best-effort: it holds the objects that could be
reconstructed, used for identity matching and get_generator.
_records (uuid -> serialized JSON record) is the source of truth for
what is written: it accumulates the union of every entry ever loaded or
registered, so a multi-call sweep grows the on-disk registry and never
overwrites entries from earlier calls. On disk the file is a JSON list of
those records.
save
Save the instance registry to disk as the union of all known entries.
Writes _records (every entry loaded from disk plus everything
registered this session), refreshing the record of any entry whose
live object is present. Entries from earlier run_experiment calls
are therefore never dropped.
list_generators
Return a list of (uuid, generator) tuples for reconstructed generators.
get_uuid
Get or register a UUID for the given generator (class + params identity, no seed).
get_generator
Retrieve a generator object by its UUID.
Raises:
| Type | Description |
|---|---|
ValueError
|
If no generator with the given UUID exists. |
__getitem__
Get a generator by UUID using dictionary-like access.
Real-dataset discovery
dp_testing_platform.core.real_dataset_utils
get_real_datasets_for_task
get_real_datasets_for_task(task: Union[Task, str], raw_directory: Path | str = Path('datasets/raw'), modifications: Sequence[str] | None = None) -> dict[str, RealInstanceGenerator]
Get all available real datasets for a task.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
task
|
Union[Task, str]
|
Task object or its id |
required |
raw_directory
|
Path | str
|
Directory containing raw datasets with converters.
A |
Path('datasets/raw')
|
modifications
|
Sequence[str] | None
|
Optional ordered names of task-declared dataset
modifications applied to every dataset (looked up in the task's
|
None
|
Returns:
| Type | Description |
|---|---|
dict[str, RealInstanceGenerator]
|
Dict mapping the instance name to its RealInstanceGenerator. The key is |
dict[str, RealInstanceGenerator]
|
|
dict[str, RealInstanceGenerator]
|
applied modification (e.g. |
dict[str, RealInstanceGenerator]
|
the |
get_real_dataset_info_for_task
get_real_dataset_info_for_task(task: Union[Task, str], raw_directory: Path | str = Path('datasets/raw'), include_instance_metadata: bool = False) -> dict[str, dict[str, Any]]
Get metadata about all available real datasets for a task.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
task
|
Union[Task, str]
|
Task object or its id |
required |
raw_directory
|
Path | str
|
Directory containing raw datasets. A |
Path('datasets/raw')
|
include_instance_metadata
|
bool
|
If True, run converters to include instance metadata (of type task.Metadata) with key 'instance_metadata' |
False
|
Returns:
| Type | Description |
|---|---|
dict[str, dict[str, Any]]
|
Dict mapping "{dataset_id}:{converter_id}" to metadata dict |
load_dataset_info
Load the info.json for a specific raw dataset.
validate_dataset_exists
Check if a raw dataset exists and has required files.
list_raw_datasets
List all raw dataset IDs that have converters.
list_real_datasets
Summarize every raw dataset for human-readable discovery.
A lightweight discovery view: reads each dataset's info.json and the
converter registry, never the data arrays. Useful for browsing which
datasets exist, what they are called, and which tasks they support.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
raw_directory
|
Path | str
|
Directory containing raw datasets with converters.
A |
Path('datasets/raw')
|
Returns:
| Type | Description |
|---|---|
list[dict[str, Any]]
|
One dict per dataset (sorted by id) with keys |
list[dict[str, Any]]
|
|
list[dict[str, Any]]
|
dataset has converters for). |
list[dict[str, Any]]
|
|