Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
99 commits
Select commit Hold shift + click to select a range
1d768bb
Import model in python.
zrho Aug 27, 2025
2774544
Allow postponed annotations.
cqc-alec Sep 19, 2025
dfab7ff
Use default_factory.
cqc-alec Sep 19, 2025
f9c742a
Add dummy function body.
cqc-alec Sep 19, 2025
d9390c0
Add stub Hugr.from_model() method.
cqc-alec Sep 19, 2025
7f85ea8
Simplify.
cqc-alec Sep 19, 2025
e97027a
Refactor.
cqc-alec Sep 19, 2025
fb1fd42
Remove unneeded `parent` argument from `import_node_in_module()`.
cqc-alec Sep 22, 2025
bf41995
Implement Hugr.from_model().
cqc-alec Sep 22, 2025
c301f45
[?] Don't record links if there's nowhere to record them.
cqc-alec Sep 22, 2025
d430fef
Set entrypoint.
cqc-alec Sep 22, 2025
c3c00e1
Specify parents of Input and Output nodes.
cqc-alec Sep 22, 2025
27e0376
Rename.
cqc-alec Sep 22, 2025
ff30f8f
Add links to hugr.
cqc-alec Sep 22, 2025
2a5c350
Remove outdated comment.
cqc-alec Oct 10, 2025
ccf18dc
Add Package.from_model() and use it.
cqc-alec Oct 13, 2025
3af0dfb
Format.
cqc-alec Oct 13, 2025
2835d2f
Lint.
cqc-alec Oct 13, 2025
4dd9905
Lint.
cqc-alec Oct 13, 2025
a527789
Remove unused import.
cqc-alec Oct 13, 2025
948263c
Uninherit Term and Op from ABC.
cqc-alec Oct 13, 2025
4bbdd74
Add docstring.
cqc-alec Oct 13, 2025
97efde4
Add docstring.
cqc-alec Oct 13, 2025
b669703
Break line.
cqc-alec Oct 13, 2025
21d1292
Raise error from caught error.
cqc-alec Oct 13, 2025
a2e5e76
Add docstrings.
cqc-alec Oct 13, 2025
1af9e4f
Make methods private.
cqc-alec Oct 13, 2025
8233fbd
Underscore not-yet-used variables.
cqc-alec Oct 13, 2025
74a89f5
[?] Just keep a single collection of links.
cqc-alec Oct 14, 2025
3af90fa
Fix typo.
cqc-alec Oct 15, 2025
5027575
Import Call nodes.
cqc-alec Oct 17, 2025
ddb2972
Special-case Qubit type when importing.
cqc-alec Oct 18, 2025
a681d30
Refactor.
cqc-alec Oct 20, 2025
829f099
Add docstring.
cqc-alec Oct 20, 2025
51b6f17
Change data structure used for `ModelImport.linked_ports`.
cqc-alec Oct 20, 2025
b85b8fc
Add docstrings.
cqc-alec Oct 20, 2025
9ee5c1e
Fix two bugs.
cqc-alec Oct 20, 2025
2f18e06
Refactor.
cqc-alec Oct 20, 2025
073985f
Add docstrings.
cqc-alec Oct 21, 2025
ee128f5
Raise NotImplementedError instead of returning nonsense.
cqc-alec Oct 21, 2025
b6efee4
Refactor and add TODO comment.
cqc-alec Oct 23, 2025
4ed1b06
Explicitly set num_outs to 1 when adding Call nodes.
cqc-alec Nov 5, 2025
c546f53
Import CFGs.
cqc-alec Oct 23, 2025
8ac0605
Expand TODO.
cqc-alec Oct 23, 2025
6f6eb42
Refactor.
cqc-alec Oct 23, 2025
14eb445
Import "core.load_const" nodes.
cqc-alec Oct 27, 2025
33394a2
Import "core.const.adt" values.
cqc-alec Oct 27, 2025
9ea79b4
Treat a general sum of empty rows as a unit sum when hashing.
cqc-alec Oct 31, 2025
49e0c2c
Set the correct number of outputs when adding Custom nodes.
cqc-alec Oct 31, 2025
fc4109b
[?] Use TypeBound.Copyable for Opaque ops.
cqc-alec Nov 3, 2025
d481193
Import "core.make_adt" nodes.
cqc-alec Nov 4, 2025
90cbe8e
Set number of outputs on Input nodes.
cqc-alec Nov 4, 2025
4a8b9c8
fixup: index fn_nodes by symbol name
cqc-alec Nov 4, 2025
051e4c3
Raise NotImplementedError for LoadFunc.
cqc-alec Nov 4, 2025
403a90e
Import LoadFunc nodes.
cqc-alec Nov 4, 2025
309dc07
Import qubit as TypeArg.
cqc-alec Nov 4, 2025
e9e7f71
Import CallIndirect nodes.
cqc-alec Nov 4, 2025
a8adbb3
Import MakeTuple and UnpackTuple nodes.
cqc-alec Nov 5, 2025
81cbeb5
Fix logic.
cqc-alec Nov 5, 2025
4893106
Delay adding edges for function calls.
cqc-alec Nov 5, 2025
adf846c
Import params of PolyFuncType.
cqc-alec Nov 5, 2025
7f02688
Refactor.
cqc-alec Nov 5, 2025
1d93539
Set correct number of outputs for Call node.
cqc-alec Nov 11, 2025
90f9420
Import DeclareAlias and DefineAlias.
cqc-alec Nov 5, 2025
ce6cd48
Add module-level metadata.
cqc-alec Nov 5, 2025
74a3bf9
Ensure integer value is in signed form for conversion to IntVal.
cqc-alec Nov 6, 2025
9eb4218
Import string constants.
cqc-alec Nov 6, 2025
f1e00c4
Import TypeTypeArg.
cqc-alec Nov 6, 2025
fab9552
Import static array values.
cqc-alec Nov 6, 2025
5eb7c99
Use different link prefixes in different module children.
cqc-alec Nov 7, 2025
9bf1e5b
Refactor.
cqc-alec Nov 10, 2025
1fc34b6
Make "core.make_adt" imports work when only tag is provided.
cqc-alec Nov 10, 2025
73962b6
Refactor.
cqc-alec Nov 10, 2025
a2a741f
Add FIXME comments. How to construct PolyFuncType?
cqc-alec Nov 11, 2025
c013cae
Import collections.list.List values.
cqc-alec Nov 11, 2025
787f5a7
Import arithmetic.int.types.int values.
cqc-alec Nov 11, 2025
d8b574a
Generalize compat.const_json imports.
cqc-alec Nov 11, 2025
1046dfa
Handle compat.const_json uniformly. (No special case for prelude.stri…
cqc-alec Nov 11, 2025
d64be1b
Fix: don't treat empty string as None.
cqc-alec Nov 12, 2025
4834f5c
Add FIXME comment.
cqc-alec Nov 12, 2025
b88d5a4
Determine TypeBound for collections.list.List.
cqc-alec Nov 12, 2025
7f30f9c
Always give LoadConst node an output port.
cqc-alec Nov 12, 2025
827fea8
Set number of output ports for TailLoop nodes.
cqc-alec Nov 12, 2025
a8e8bee
Always give LoadFunc node an output port.
cqc-alec Nov 12, 2025
bb63f47
Set number of output ports on CallIndirect nodes.
cqc-alec Nov 12, 2025
1127655
Special-case import of core.adt in compat.const_json terms.
cqc-alec Nov 13, 2025
992960d
Give more context in error message.
cqc-alec Nov 14, 2025
eb124b8
Move FIXME.
cqc-alec Nov 13, 2025
846357d
Import packaged extensions.
cqc-alec Nov 14, 2025
fac1b60
Lints.
cqc-alec Nov 14, 2025
fa42d0f
Revert unnecessary change.
cqc-alec Nov 14, 2025
0095210
Document return value of ModelImport.import_node_in_dfg().
cqc-alec Nov 14, 2025
e68aea9
Refactor.
cqc-alec Nov 14, 2025
a452556
Import collections.array.const values.
cqc-alec Nov 14, 2025
0383a41
Rename.
cqc-alec Nov 14, 2025
f3b2d12
Merge branch 'main' into ae/model.4
cqc-alec Nov 19, 2025
957ce85
Merge branch 'main' into ae/model.4
cqc-alec Nov 25, 2025
3c98848
Make `link_prefix` nullable and unset it at the end of `import_node_i…
cqc-alec Nov 28, 2025
aae3ff8
Merge branch 'main' into ae/model.4
cqc-alec Nov 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions hugr-core/src/import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1140,12 +1140,12 @@ impl<'a> Context<'a> {

/// Import a node with a custom operation.
///
/// A custom operation in `hugr-model` referred to by a symbol application
/// term. The name of the symbol specifies the name of the custom operation,
/// and the arguments supplied to the symbol are the arguments to be passed
/// to the custom operation. This method imports the custom operations as
/// [`OpaqueOp`]s. The [`OpaqueOp`]s are then resolved later against the
/// [`ExtensionRegistry`].
/// A custom operation in `hugr-model` is referred to by a symbol
/// application term. The name of the symbol specifies the name of the
/// custom operation, and the arguments supplied to the symbol are the
/// arguments to be passed to the custom operation. This method imports the
/// custom operations as [`OpaqueOp`]s. The [`OpaqueOp`]s are then resolved
/// later against the [`ExtensionRegistry`].
///
/// Some operations that needed to be builtins in `hugr-core` are custom
/// operations in `hugr-model`. This method detects these and converts them
Expand Down
2 changes: 1 addition & 1 deletion hugr-model/src/v0/binary/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@
mod read;
mod write;

pub use read::{ReadError, read_from_reader, read_from_slice};
pub use read::{ReadError, read_from_reader, read_from_slice, read_from_slice_with_suffix};
pub use write::{WriteError, write_to_vec, write_to_writer};
19 changes: 18 additions & 1 deletion hugr-model/src/v0/binary/read.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::v0::table;
use crate::{CURRENT_VERSION, v0 as model};
use bumpalo::Bump;
use bumpalo::collections::Vec as BumpVec;
use std::io::BufRead;
use std::io::{BufRead, BufReader, Read};

/// An error encountered while deserialising a model.
#[derive(Debug, derive_more::From, derive_more::Display, derive_more::Error)]
Expand All @@ -27,10 +27,27 @@ pub enum ReadError {
type ReadResult<T> = Result<T, ReadError>;

/// Read a hugr package from a byte slice.
///
/// If the slice contains bytes beyond the encoded package, they are ignored.
pub fn read_from_slice<'a>(slice: &[u8], bump: &'a Bump) -> ReadResult<table::Package<'a>> {
read_from_reader(slice, bump)
}

/// Read a hugr package from a byte slice.
///
/// If the slice contains bytes beyond the encoded package, they are returned
/// along with the decoded package.
pub fn read_from_slice_with_suffix<'a>(
slice: &[u8],
bump: &'a Bump,
) -> ReadResult<(table::Package<'a>, Vec<u8>)> {
let mut buffer = BufReader::new(slice);
let package = read_from_reader(&mut buffer, bump)?;
let mut suffix: Vec<u8> = vec![];
buffer.read_to_end(&mut suffix)?;
Ok((package, suffix))
}

/// Read a hugr package from an impl of [`BufRead`].
pub fn read_from_reader(reader: impl BufRead, bump: &Bump) -> ReadResult<table::Package<'_>> {
let reader =
Expand Down
6 changes: 3 additions & 3 deletions hugr-py/rust/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,14 +74,14 @@ fn package_to_bytes(package: ast::Package) -> PyResult<Vec<u8>> {
}

#[pyfunction]
fn bytes_to_package(bytes: &[u8]) -> PyResult<ast::Package> {
fn bytes_to_package(bytes: &[u8]) -> PyResult<(ast::Package, Vec<u8>)> {
let bump = bumpalo::Bump::new();
let table = hugr_model::v0::binary::read_from_slice(bytes, &bump)
let (table, suffix) = hugr_model::v0::binary::read_from_slice_with_suffix(bytes, &bump)
.map_err(|err| PyValueError::new_err(err.to_string()))?;
let package = table
.as_ast()
.ok_or_else(|| PyValueError::new_err("Malformed package"))?;
Ok(package)
Ok((package, suffix))
}

/// Returns the current version of the HUGR model format as a tuple of (major, minor, patch).
Expand Down
2 changes: 1 addition & 1 deletion hugr-py/src/hugr/_hugr/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def bytes_to_module(binary: bytes) -> hugr.model.Module: ...
def package_to_string(package: hugr.model.Package) -> str: ...
def string_to_package(string: str) -> hugr.model.Package: ...
def package_to_bytes(package: hugr.model.Package) -> bytes: ...
def bytes_to_package(binary: bytes) -> hugr.model.Package: ...
def bytes_to_package(binary: bytes) -> tuple[hugr.model.Package, bytes]: ...
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't this be a breaking change?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this doesn't count as a breaking change since the hugr._hugr module is underscored.

def current_model_version() -> tuple[int, int, int]: ...
def to_json_envelope(binary: bytes) -> bytes: ...
def run_cli() -> None: ...
Expand Down
27 changes: 19 additions & 8 deletions hugr-py/src/hugr/envelope.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@

import pyzstd

from hugr import cli
import hugr._hugr as rust

if TYPE_CHECKING:
from hugr.hugr.base import Hugr
Expand All @@ -64,7 +64,6 @@ def make_envelope(package: Package | Hugr, config: EnvelopeConfig) -> bytes:
if not isinstance(package, Package):
package = Package(modules=[package], extensions=[])

# Currently only uncompressed JSON is supported.
payload: bytes
match config.format:
case EnvelopeFormat.JSON:
Expand Down Expand Up @@ -103,6 +102,7 @@ def make_envelope_str(package: Package | Hugr, config: EnvelopeConfig) -> str:
def read_envelope(envelope: bytes) -> Package:
"""Decode a HUGR package from an envelope."""
import hugr._serialization.extension as ext_s
from hugr.package import Package

header = EnvelopeHeader.from_bytes(envelope)
payload = envelope[10:]
Expand All @@ -113,12 +113,23 @@ def read_envelope(envelope: bytes) -> Package:
match header.format:
case EnvelopeFormat.JSON:
return ext_s.Package.model_validate_json(payload).deserialize()
case EnvelopeFormat.MODEL | EnvelopeFormat.MODEL_WITH_EXTS:
# TODO Going via JSON is a temporary solution, until we get model import to
# python properly implemented.
# https://github.com/CQCL/hugr/issues/2287
json_data = cli.convert(envelope, format="json")
return read_envelope(json_data)
case EnvelopeFormat.MODEL:
model_package, suffix = rust.bytes_to_package(payload)
if suffix:
msg = f"Excess bytes in envelope with format {EnvelopeFormat.MODEL}."
raise ValueError(msg)
return Package.from_model(model_package)
case EnvelopeFormat.MODEL_WITH_EXTS:
from hugr.ext import Extension

model_package, suffix = rust.bytes_to_package(payload)
return Package(
modules=Package.from_model(model_package).modules,
extensions=[
Extension.from_json(json.dumps(extension))
for extension in json.loads(suffix)
],
)


def read_envelope_hugr(envelope: bytes) -> Hugr:
Expand Down
13 changes: 13 additions & 0 deletions hugr-py/src/hugr/hugr/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1114,6 +1114,19 @@ def from_str(envelope: str) -> Hugr:
"""
return read_envelope_hugr_str(envelope)

@staticmethod
def from_model(module: model.Module) -> Hugr:
"""Import from the hugr model format."""
from hugr.model.load import ModelImport

loader = ModelImport(module=module)
for i, node in enumerate(module.root.children):
loader.import_node_in_module(node, i)
loader.link_ports()
loader.link_static_ports()
loader.add_module_metadata()
return loader.hugr

def to_bytes(self, config: EnvelopeConfig | None = None) -> bytes:
"""Serialize the HUGR into an envelope byte string.

Expand Down
61 changes: 56 additions & 5 deletions hugr-py/src/hugr/model/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
"""HUGR model data structures."""

from collections.abc import Sequence
import warnings
from collections.abc import Generator, Sequence
from dataclasses import dataclass, field
from enum import Enum
from typing import Protocol

from semver import Version

Expand All @@ -21,7 +21,7 @@ def _current_version() -> Version:
CURRENT_VERSION: Version = _current_version()


class Term(Protocol):
class Term:
"""A model term for static data such as types, constants and metadata."""

def __str__(self) -> str:
Expand All @@ -33,6 +33,26 @@ def from_str(s: str) -> "Term":
"""Read the term from its string representation."""
return rust.string_to_term(s)

def to_list_parts(self) -> Generator["SeqPart"]:
if isinstance(self, List):
for part in self.parts:
if isinstance(part, Splice):
yield from part.seq.to_list_parts()
else:
yield part
else:
yield Splice(self)

def to_tuple_parts(self) -> Generator["SeqPart"]:
if isinstance(self, Tuple):
for part in self.parts:
if isinstance(part, Splice):
yield from part.seq.to_tuple_parts()
else:
yield part
else:
yield Splice(self)


@dataclass(frozen=True)
class Wildcard(Term):
Expand Down Expand Up @@ -129,9 +149,13 @@ def from_str(s: str) -> "Symbol":
return rust.string_to_symbol(s)


class Op(Protocol):
class Op:
"""The operation of a node."""

def symbol_name(self) -> str | None:
"""Returns name of the symbol introduced by this node, if any."""
return None


@dataclass(frozen=True)
class InvalidOp(Op):
Expand Down Expand Up @@ -159,13 +183,19 @@ class DefineFunc(Op):

symbol: Symbol

def symbol_name(self) -> str | None:
return self.symbol.name


@dataclass(frozen=True)
class DeclareFunc(Op):
"""Function declaration."""

symbol: Symbol

def symbol_name(self) -> str | None:
return self.symbol.name


@dataclass(frozen=True)
class CustomOp(Op):
Expand All @@ -181,13 +211,19 @@ class DefineAlias(Op):
symbol: Symbol
value: Term

def symbol_name(self) -> str | None:
return self.symbol.name


@dataclass(frozen=True)
class DeclareAlias(Op):
"""Alias declaration."""

symbol: Symbol

def symbol_name(self) -> str | None:
return self.symbol.name


@dataclass(frozen=True)
class TailLoop(Op):
Expand All @@ -205,20 +241,29 @@ class DeclareConstructor(Op):

symbol: Symbol

def symbol_name(self) -> str | None:
return self.symbol.name


@dataclass(frozen=True)
class DeclareOperation(Op):
"""Operation declaration."""

symbol: Symbol

def symbol_name(self) -> str | None:
return self.symbol.name


@dataclass(frozen=True)
class Import(Op):
"""Import operation."""

name: str

def symbol_name(self) -> str | None:
return self.name


@dataclass
class Node:
Expand Down Expand Up @@ -302,7 +347,13 @@ def from_str(s: str) -> "Package":
@staticmethod
def from_bytes(b: bytes) -> "Package":
"""Read a package from its binary representation."""
return rust.bytes_to_package(b)
package, suffix = rust.bytes_to_package(b)
if suffix:
warnings.warn(
"Binary encoding of model Package contains extra bytes: ignoring them.",
stacklevel=2,
)
return package

@property
def version(self) -> Version:
Expand Down
Loading
Loading