Artifact#
- class Artifact#
Read a committed package of optimization data from the Local Registry.
An Artifact groups problems, evaluated candidates, tables, and other data with metadata. It is optional: constructing, transforming, evaluating, or solving an
Instancedoes not require an Artifact. UseExperimentwhen you also want to organize recorded conditions and results into trials.Layers, descriptors, and relationships#
Each data item is a layer. A manifest lists the layers and their metadata. A layer’s
Descriptoridentifies its media type, byte size, content digest, and annotations. Typed methods such asadd_instance()andget_instance()choose and interpret the corresponding serialization format.The Artifact does not infer which problem produced a particular result merely because both are present. Record the relationships your application needs. For example, the complete exchange below records the problem and result layer digests in a JSON analysis note. When several layers have the same type, select their descriptors with
get_layer_descriptor()and pass them to the typed getters. The convenience propertiesinstanceandsolutionreturn the first layer of the requested type.Annotations describe a layer without decoding its data. For JSON, tables, and arrays, annotation keywords such as
title="analysis"are stored under the defaultorg.ommx.user.namespace. They are labels, not uniqueness constraints; the example deliberately assigns distinct titles.Save a problem and candidate, then read them in another process#
This example needs only the Python SDK and its pandas dependency. It evaluates an explicit candidate; it does not run a solver. The sender writes a problem, its evaluated candidate, an analysis note, and a table into one
.ommxfile. Build a mutableArtifactDraft, add the data, and commit its layer list into the Local Registry. Then save the committed Artifact as an archive; committing locally and exporting a transferable file are separate steps.>>> sender = """ ... import sys ... import pandas as pd ... from ommx import Instance ... from ommx.artifact import ArtifactDraft ... ... instance = Instance.maximize() ... x = instance.new_binary("x") ... y = instance.new_binary("y") ... instance.objective = 3 * x + 2 * y ... instance.add_constraint(x + y <= 1) ... solution = instance.evaluate({x.id: 1, y.id: 0}) ... assert solution.feasible and solution.objective == 3 ... ... draft = ArtifactDraft.new_anonymous() ... instance_layer = draft.add_instance(instance) ... solution_layer = draft.add_solution(solution) ... draft.add_json({ ... "instance_digest": instance_layer.digest, ... "solution_digest": solution_layer.digest, ... "note": "An evaluated candidate, not an optimality certificate.", ... }, title="analysis") ... draft.add_dataframe(pd.DataFrame({ ... "item": ["x", "y"], "selected": [True, False], ... }), title="selection") ... artifact = draft.commit() ... artifact.save(sys.argv[1]) ... """
The receiver uses only the transferred file. It retrieves the problem and result identified by the analysis note, reevaluates the saved state, and reads the table.
import_archive()imports the contents into the receiver’s Local Registry; it does not keep reading the sender’s registry or Python objects.>>> receiver = """ ... import sys ... from ommx.artifact import Artifact ... ... received = Artifact.import_archive(sys.argv[1]) ... by_title = { ... layer.annotations["org.ommx.user.title"]: layer ... for layer in received.layers ... if "org.ommx.user.title" in layer.annotations ... } ... analysis = received.get_json(by_title["analysis"]) ... instance = received.get_instance( ... received.get_layer_descriptor(analysis["instance_digest"]) ... ) ... solution = received.get_solution( ... received.get_layer_descriptor(analysis["solution_digest"]) ... ) ... checked = instance.evaluate(solution.state) ... assert checked.feasible == solution.feasible ... assert checked.objective == solution.objective ... selection = received.get_dataframe(by_title["selection"]) ... print(solution.feasible, solution.objective) ... print(selection.loc[selection["selected"], "item"].tolist()) ... """
Run these two programs in separate processes using the same SDK environment. The following executable harness creates two initially empty registries and a temporary shared file. It sets
OMMX_LOCAL_REGISTRY_ROOTbefore each child process starts, so it does not write to your normal registry. A real recipient can run the receiver program on the transferred archive in their own environment.>>> import os >>> import subprocess >>> import sys >>> from pathlib import Path >>> from tempfile import TemporaryDirectory >>> with TemporaryDirectory() as directory: ... root = Path(directory) ... sender_registry = root / "sender-registry" ... receiver_registry = root / "receiver-registry" ... sender_registry.mkdir() ... receiver_registry.mkdir() ... archive = root / "candidate.ommx" ... sent = subprocess.run( ... [sys.executable, "-c", sender, str(archive)], ... env={**os.environ, "OMMX_LOCAL_REGISTRY_ROOT": str(sender_registry)}, ... capture_output=True, ... text=True, ... ) ... assert sent.returncode == 0, sent.stderr ... assert not list(receiver_registry.iterdir()) ... received = subprocess.run( ... [sys.executable, "-c", receiver, str(archive)], ... env={**os.environ, "OMMX_LOCAL_REGISTRY_ROOT": str(receiver_registry)}, ... capture_output=True, ... text=True, ... ) ... assert received.returncode == 0, received.stderr ... print(received.stdout, end="") True 3.0 ['x']
The sender’s process has exited before the receiver starts, and the receiver’s registry begins empty. The archive is sufficient to recover these stored data. This does not prove optimality or reproduce the original computation: source code, solver settings, and environment details are present only if you recorded them explicitly.
Local storage, archives, and remote registries#
Destination
Operation
Meaning
Local Registry
Store or reopen a named Artifact on this machine
.ommxarchiveExport a file for transfer, or import its contents into the receiver’s registry
Remote OCI registry
Distribute an Artifact, or retrieve a missing or incomplete local copy
The Local Registry is the SDK’s persistent local storage. Choose a separate root with
OMMX_LOCAL_REGISTRY_ROOTwhen starting the process. Changing the root changes which stored data are visible; it does not move existing data.inspect_archive()reads only the manifest and descriptors without importing the archive. ItsArchiveManifestdoes not provide decoded layer contents.An OCI registry distributes data using container-registry protocols. An OMMX Artifact is a data package, not an executable container. Remote authentication and access control belong to the registry service. A local commit neither exports a file nor uploads data; those require
save()orpush()respectively.Names, tags, and content digests#
A reference such as
ghcr.io/my-team/my-project/candidates:trial-1identifies a registry, repository, and tag. A tag is a name that can later refer to different content. A digest identifies content: a manifest digest identifies the manifest, while each layer has its own digest. The two identify different data. An archive’s manifest digest is available frommanifest_digest.load()uses a complete local copy without contacting the remote registry. If the reference or any required manifest, config, or layer data is missing, it attempts a fresh remote pull. A tag with a complete local copy is not refreshed from the remote server. Use the distributor’s digest reference when you need a specific published Artifact.new_anonymous()generates a local name containing.ommx.local; it is suitable for starting without a distribution name and for exporting archives, but is not a remote publishing destination. Usenew()orfor_github()when choosing a remote name. Anonymous entries remain in local storage until managed explicitly; seeprune_anonymous()andgc()for cleanup.Data format and SDK versions#
The Python SDK version, the Message wire-schema version, and the Artifact type are separate. For example,
v1inapplication/org.ommx.v1.artifactdoes not mean Python SDK version 1. Typed layer methods handle the payload formats supported by the installed SDK.For another language or tool, consult the Artifact specification and Message schemas.
save_mps()instead exports a supported mathematical model for MPS-compatible tools; it does not preserve all OMMX labels, evaluation metadata, and recorded data as an Artifact does.- get_blob(descriptor: Descriptor) bytes#
Get raw bytes of a blob by Descriptor.
Registry and storage failures, including a missing CAS blob referenced by the Descriptor, raise
RuntimeError.
- get_dataframe(descriptor: Descriptor) Any#
Get a pandas DataFrame from an artifact layer stored by
add_dataframe().A mismatched media type raises
ValueError; registry and storage failures raiseRuntimeError. pandas exceptions are preserved.
- get_instance(descriptor: Optional[Descriptor] = None) Instance#
Get an instance from the artifact.
If
descriptorisNone, returns the first instance layer.If
descriptoris given, returns the instance for that specific layer.
Raises
ValueErrorif no instance layer is found, if the Descriptor has the wrong media type, or if the payload is malformed. Registry and storage failures raiseRuntimeError.
- get_json(descriptor: Descriptor) Any#
Get a JSON object from an artifact layer stored by
add_json().A mismatched media type raises
ValueError; registry and storage failures raiseRuntimeError. JSON decoder exceptions are preserved.
- get_layer(descriptor: Descriptor) Any#
Get the layer object corresponding to the descriptor.
Dynamically dispatched based on
media_type:OMMX instance payloads return
InstanceOMMX parametric instance payloads return
ParametricInstanceOMMX solution payloads return
SolutionOMMX sample-set payloads return
SampleSetapplication/vnd.numpyreturns a numpy array
Raises
ValueErrorfor an unsupported media type. Registry and storage failures raiseRuntimeError; exceptions raised by Python-backed decoders are preserved.
- get_layer_descriptor(digest: str) Descriptor#
Look up a layer descriptor by digest.
Raises
ValueErrorwhendigestis malformed andKeyErrorwhen the digest is valid but is not present in the Artifact manifest.
- get_ndarray(descriptor: Descriptor) Any#
Get a numpy array from an artifact layer stored by
add_ndarray().A mismatched media type raises
ValueError; registry and storage failures raiseRuntimeError. NumPy exceptions are preserved.
- get_parametric_instance(descriptor: Optional[Descriptor] = None) ParametricInstance#
Get a parametric instance from the artifact.
If
descriptorisNone, returns the first parametric instance layer.If
descriptoris given, returns the parametric instance for that specific layer.
Raises
ValueErrorif no parametric instance layer is found, if the Descriptor has the wrong media type, or if the payload is malformed. Registry and storage failures raiseRuntimeError.
- get_sample_set(descriptor: Optional[Descriptor] = None) SampleSet#
Get a sample set from the artifact.
If
descriptorisNone, returns the first sample set layer.If
descriptoris given, returns the sample set for that specific layer.
Raises
ValueErrorif no sample set layer is found, if the Descriptor has the wrong media type, or if the payload is malformed. Registry and storage failures raiseRuntimeError.
- get_solution(descriptor: Optional[Descriptor] = None) Solution#
Get a solution from the artifact.
If
descriptorisNone, returns the first solution layer.If
descriptoris given, returns the solution for that specific layer.
Raises
ValueErrorif no solution layer is found, if the Descriptor has the wrong media type, or if the payload is malformed. Registry and storage failures raiseRuntimeError.
- import_archive(path: str | PathLike | Path) Artifact#
Import an artifact from a
.ommxOCI archive file (or an OCI Image Layout directory) into the user’s v3 SQLite Local Registry, and return a handle to the imported registry entry.Side effect (intentional): archive / directory contents are permanently written into the SQLite Local Registry under the default root (
$XDG_DATA_HOME/ommx/on Linux,$HOME/Library/Application Support/org.ommx.ommx/on macOS, or$OMMX_LOCAL_REGISTRY_ROOTwhen set). SubsequentArtifact.load(image_name)calls resolve from SQLite without re-importing.For a side-effect-free read that just surfaces the manifest / layer descriptors without writing into the registry, use
Artifact.inspect_archive()instead. TheArtifactexample creates and imports a complete archive in separate processes with isolated registries.If the input lacks an
org.opencontainers.image.ref.nameannotation, v3 synthesizes an anonymous Local Registry ref and imports the content under that name. This keeps v2 unnamed archives importable while still making the imported artifact addressable in SQLite.This example requires an external archive and writes to the persistent Local Registry, so it is not executed as a doctest.
>>> artifact = Artifact.import_archive("data/random_lp_instance.ommx") >>> print(artifact.image_name) ghcr.io/jij-inc/ommx/random_lp_instance:...
- inspect_archive(path: str | PathLike | Path) ArchiveManifest#
Read a
.ommxOCI archive’s manifest and layer descriptors without importing it into the SQLite Local Registry. Useful when you want to inspect an archive’s contents (e.g. iterate layer media types or check the artifact type) without triggering a registry write — the analogue ofommx inspect <archive>from the CLI.For full registry import (so the artifact is reachable by
Artifact.load(image_name)later), useArtifact.import_archive().This example requires an external archive and is not executed as a doctest.
>>> manifest = Artifact.inspect_archive("data/random_lp_instance.ommx") >>> for layer in manifest.layers: ... print(layer.media_type) application/org.ommx.v1.instance
- load(image_name: str) Artifact#
Load an artifact stored as a container image in local or remote registry.
A complete local copy is used without contacting the remote registry. If the reference or required manifest, config, or layer data is missing, this attempts a fresh remote pull. A tag with a complete local copy is not refreshed. Use a digest reference to identify a particular published Artifact; see
Artifactfor names and digests.This example requires remote registry access and is not executed as a doctest.
>>> artifact = Artifact.load("ghcr.io/jij-inc/ommx/random_lp_instance:4303c7f") >>> print(artifact.image_name) ghcr.io/jij-inc/ommx/random_lp_instance:4303c7f
Raises
RemoteArtifactNotFoundErrorwhen the exact remote reference does not exist. Other remote access failures raise subclasses ofRemoteArtifactError. An invalid image reference raisesValueErrorbefore any remote access is attempted.
- load_archive(path: str | PathLike | Path) Artifact#
Removed in v3 — use
import_archive()orinspect_archive()instead.In v2
Artifact.load_archive(file)opened a.ommxarchive in place without touching the SQLite Local Registry. v3 changed that contract: archive ingest now writes the artifact permanently into the user’s persistent SQLite Local Registry. To make the semantic shift visible (rather than silently polluting the registry on upgrade), the v2 name raises an explicit migration error:Artifact.import_archive(file)— the replacement with the v3 registry-write semantics.Artifact.inspect_archive(file)— a side-effect-free read of the manifest / layer descriptors without registry import.
- push() None#
Push the artifact to remote registry.
- save(path: str | PathLike | Path) None#
Save the artifact as a
.ommxOCI archive file atpath.The archive is an exchange-format export of the registry-resident artifact. Loading the archive back via
Artifact.import_archive()reimports it into the SQLite Local Registry under the same image name;Artifact.inspect_archive()reads the manifest / layer descriptors without writing into the registry. SeeArtifactfor a complete save/import example whose receiver starts in a new process with an empty registry.
- TRACE_OTLP_PROTOBUF_MEDIA_TYPE: str#
Media type of an Experiment Run trace encoded as OTLP protobuf.
- property annotations: dict[str, str]#
Read-only property.
Annotations in the artifact manifest.
- property instance: Instance#
Read-only property.
The first instance layer in the artifact.
Raises
ValueErrorif no instance layer is found. For multiple instance layers, useget_instance()with a descriptor.
- property layers: list[Descriptor]#
Read-only property.
- property parametric_instance: ParametricInstance#
Read-only property.
The first parametric instance layer in the artifact.
Raises
ValueErrorif no parametric instance layer is found. For multiple parametric instance layers, useget_parametric_instance()with a descriptor.
- property sample_set: SampleSet#
Read-only property.
The first sample set layer in the artifact.
Raises
ValueErrorif no sample set layer is found. For multiple sample set layers, useget_sample_set()with a descriptor.
- property solution: Solution#
Read-only property.
The first solution layer in the artifact.
Raises
ValueErrorif no solution layer is found. For multiple solution layers, useget_solution()with a descriptor.