Skip to content

Commit

Permalink
Remove LinearType; ClassicTypes are the only subtype of SimpleTypes (#…
Browse files Browse the repository at this point in the history
…252)

* Linear containers can have classic values too
* Remove one TypeRow::from, inlining into the other
* Flatten LinearType into SimpleType
* Inline single use of impl_from_into_simple_type macro
* Drop unused purely_linear methods, also LeafOp::linear_count and Signature::linear
* PrimType::LINEAR -> CLASSIC; Alias{ID,Defn,Decl}: linear -> classic
* Remove SimpleType::is_linear, keeping only is_classical
* Spec: remove LinearType, use subset; update type constraints TODO, dropping const-able
  • Loading branch information
acl-cqc authored Jul 17, 2023
1 parent 200998b commit 2b29558
Show file tree
Hide file tree
Showing 15 changed files with 142 additions and 241 deletions.
62 changes: 28 additions & 34 deletions specification/hugr.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,10 @@ of edge for different relationships.
children.

`Value` and `Static` edges are sometimes referred to as _dataflow_ edges.
A `Value` edge can carry data of any `SimpleType`: either a `ClassicType`
(ordinary classical data) or a `LinearType` (data which cannot be copied,
including quantum data). A `Static` edge can only carry a `ClassicType`. For
A `Value` edge can carry data of any `SimpleType`: these include the `ClassicType`s
(which can be freely copied or discarded - i.e. ordinary classical data)
as well as anything which cannot - e.g. quantum data.
A `Static` edge can only carry a `ClassicType`. For
more details see the [Type System](#type-system) section.

As well as the type, dataflow edges are also parametrized by a
Expand All @@ -144,7 +145,7 @@ As well as the type, dataflow edges are also parametrized by a
[Non-local Edges](#non-local-edges)

```
SimpleType ::= ClassicType | LinearType
SimpleType ClassicType -- In the absence of unicode: "SimpleType is a superset of ClassicType"
EdgeKind ::= Hierarchy | Value(Locality, SimpleType) | Static(Local | Ext, ClassicType) | Order | ControlFlow
Expand All @@ -163,7 +164,7 @@ edges, with `Static` edges following `Value` edges in the ordering.
Note that the locality is not fixed or even specified by the signature.

A source port with a `ClassicType` may have any number of edges associated with
it (including zero, which means "discard"). A port with a `LinearType`, and a target port of any type,
it (including zero, which means "discard"). Any other port
must have exactly one edge associated with it. This captures the property of
linear types that the value is used exactly once. See [Linearity](#linearity).

Expand Down Expand Up @@ -990,12 +991,11 @@ A grammar of available types is defined below.
```haskell
Type ::= [Resources]SimpleType
-- Rows are ordered lists, not sets
# ::= #(LinearType), #(ClassicType)
# ::= #(SimpleType)
#(T) ::= (T)*
Resources ::= (Resource)* -- set not list
SimpleType ::= ClassicType | LinearType
Container(T) ::= List(T)
| Tuple(#(T))
| Array<u64>(T)
Expand All @@ -1009,9 +1009,10 @@ ClassicType ::= int<N>
| Graph[R](#, #)
| Opaque(Name, #)
| Container(ClassicType)
LinearType ::= Qubit
SimpleType ::= Qubit
| QPaque(Name, #)
| Container(LinearType)
| Container(SimpleType) -- Sometimes called 'Qontainer'
| ClassicType
```

SimpleTypes are the types of *values* which can be sent down wires,
Expand All @@ -1034,8 +1035,8 @@ Container types are defined in terms of statically-known element types.
Besides `Array<N>`, `Sum` and `Tuple`, these also include variable-sized
types: `Graph`, `Map` and
`List` (TODO: can we leave those to the Tierkreis resource?). `NewType`
allows named newtypes to be used. Containers are linear if any of their
components are linear.
allows named newtypes to be used. Containers are classic (copyable) only
if all of their components are classic.

Note: any array can be turned into an equivalent tuple, but arrays also
support dynamically-indexed `get`. (TODO: Indexed by u64, with panic if
Expand All @@ -1055,18 +1056,19 @@ i.e. this does not affect behaviour of the HUGR. Row types are used
### Linearity

For expressing and rewriting quantum programs we draw a distinction between
`ClassicType` and `LinearType`, the latter being values which must be used
arbitrary `SimpleType`s and the subset of `ClassicType`s. Only `ClassicType`s
may be used more than once or discarded; the former must always be used
exactly once. This leads to a constraint on the HUGR that outgoing ports
of `LinearType` must have exactly one edge leaving them. `ClassicType` outgoing
ports can have any number of connected edges (0 is equivalent to a discard).
must have exactly one edge leaving them unless they are `ClassicType`, where
outgoing ports may have any number of connected edges (0 is equivalent
to a discard). All incoming ports must have exactly one edge connected to them.

Our linear types behave like other values passed down a wire. Quantum
gates behave just like other nodes on the graph with inputs and outputs,
but there is only one edge leaving or entering each port. In fully
qubit-counted contexts programs take in a number of qubits as input and
return the same number, with no discarding. See
[quantum resource](#quantum-resource)
for more.
# Our linear types behave like other values passed down a wire.
# Quantum gates behave just like other nodes on the graph with
# inputs and outputs, but there is only one edge leaving (or entering) each port.
In fully qubit-counted contexts programs take in a number of qubits
as input and return the same number, with no discarding. See
[quantum resource](#quantum-resource) for more.

### Resources

Expand Down Expand Up @@ -1096,20 +1098,12 @@ resource requirements.
We will likely also want to add a fixed set of attributes to certain
subsets of `TYPE`. In Tierkreis these are called “type constraints”. For
example, the `Map` type can only be constructed when the type that we
map from is `Hashable`. For the Hugr, we may need this `Hashable`
constraint, as well as a `Nonlinear` constraint. Finally there may be a
`const-able` or `serializable` constraint meaning that the value can be
put into a `const`-node: this implies the type is `Nonlinear` (but not
vice versa).
map from is `Hashable`. For the Hugr, we will need this `Hashable`
constraint, as well as a `Classic` constraint.

**TODO**: is this set of constraints (nonlinear, const-able, hashable)
fixed? Then Map is in the core HUGR spec.

Or, can extensions (resources) add new constraints? This is probably too
complex, but then both hashable and Map could be in the Tierkreis
resource.

(Or, can we do Map without hashable?)
**TODO**: fix this set of constraints (classic/copyable, hashable);
extensions/resources *cannot* add new constraints. Use hashable for
static type parameters, put Map in Tierkreis resource not core spec.

### Resources

Expand Down
9 changes: 4 additions & 5 deletions src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ use thiserror::Error;

use crate::hugr::{HugrError, Node, ValidationError, Wire};
use crate::ops::handle::{BasicBlockID, CfgID, ConditionalID, DfgID, FuncID, TailLoopID};

use crate::types::LinearType;
use crate::types::SimpleType;

pub mod handle;
pub use handle::BuildHandle;
Expand Down Expand Up @@ -63,7 +62,7 @@ pub enum BuildError {

/// Can't copy a linear type
#[error("Can't copy linear type: {0:?}.")]
NoCopyLinear(LinearType),
NoCopyLinear(SimpleType),

/// Error in CircuitBuilder
#[error("Error in CircuitBuilder: {0}.")]
Expand All @@ -72,7 +71,7 @@ pub enum BuildError {

#[cfg(test)]
mod test {
use crate::types::{ClassicType, LinearType, Signature, SimpleType};
use crate::types::{ClassicType, Signature, SimpleType};
use crate::Hugr;

use super::handle::BuildHandle;
Expand All @@ -82,7 +81,7 @@ mod test {
pub(super) const NAT: SimpleType = SimpleType::Classic(ClassicType::i64());
pub(super) const F64: SimpleType = SimpleType::Classic(ClassicType::F64);
pub(super) const BIT: SimpleType = SimpleType::Classic(ClassicType::bit());
pub(super) const QB: SimpleType = SimpleType::Linear(LinearType::Qubit);
pub(super) const QB: SimpleType = SimpleType::Qubit;

/// Wire up inputs of a Dataflow container to the outputs.
pub(super) fn n_identity<T: DataflowSubContainer>(
Expand Down
8 changes: 3 additions & 5 deletions src/builder/build_traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -614,7 +614,7 @@ fn wire_up<T: Dataflow + ?Sized>(
if let EdgeKind::Value(typ) = base.get_optype(src).port_kind(src_offset).unwrap() {
if !local_source {
// Non-local value sources require a state edge to an ancestor of dst
if typ.is_linear() {
if !typ.is_classical() {
let val_err: ValidationError = InterGraphEdgeError::NonClassicalData {
from: src,
from_offset: Port::new_outgoing(src_port),
Expand Down Expand Up @@ -646,11 +646,9 @@ fn wire_up<T: Dataflow + ?Sized>(
// TODO: Avoid adding duplicate edges
// This should be easy with https://github.com/CQCL-DEV/hugr/issues/130
base.add_other_edge(src, src_sibling)?;
} else if base.linked_ports(src, src_offset).next().is_some() {
} else if !typ.is_classical() && base.linked_ports(src, src_offset).next().is_some() {
// Don't copy linear edges.
if let SimpleType::Linear(lty) = typ {
return Err(BuildError::NoCopyLinear(lty));
}
return Err(BuildError::NoCopyLinear(typ));
}
}

Expand Down
5 changes: 3 additions & 2 deletions src/builder/dataflow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ mod test {
use crate::ops::handle::NodeHandle;
use crate::ops::OpTag;
use crate::ops::OpTrait;
use crate::types::SimpleType;
use crate::{
builder::{
test::{n_identity, BIT, NAT, QB},
Expand All @@ -205,7 +206,7 @@ mod test {
ops::LeafOp,
resource::ResourceSet,
type_row,
types::{LinearType, Signature},
types::Signature,
Wire,
};

Expand Down Expand Up @@ -306,7 +307,7 @@ mod test {
Ok(module_builder.finish_hugr()?)
};

assert_eq!(builder(), Err(BuildError::NoCopyLinear(LinearType::Qubit)));
assert_eq!(builder(), Err(BuildError::NoCopyLinear(SimpleType::Qubit)));
}

#[test]
Expand Down
12 changes: 6 additions & 6 deletions src/builder/module_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,13 +132,13 @@ impl<T: AsMut<Hugr> + AsRef<Hugr>> ModuleBuilder<T> {
// Could be fixed by removing single-entry requirement and sorting from
// every 0-input node.
let name: SmolStr = name.into();
let linear = typ.is_linear();
let classical = typ.is_classical();
let node = self.add_child_op(ops::AliasDefn {
name: name.clone(),
definition: typ,
})?;

Ok(AliasID::new(node, name, linear))
Ok(AliasID::new(node, name, classical))
}

/// Add a [`OpType::AliasDecl`] node and return a handle to the Alias.
Expand All @@ -148,15 +148,15 @@ impl<T: AsMut<Hugr> + AsRef<Hugr>> ModuleBuilder<T> {
pub fn add_alias_declare(
&mut self,
name: impl Into<SmolStr>,
linear: bool,
classical: bool,
) -> Result<AliasID<false>, BuildError> {
let name: SmolStr = name.into();
let node = self.add_child_op(ops::AliasDecl {
name: name.clone(),
linear,
classical,
})?;

Ok(AliasID::new(node, name, linear))
Ok(AliasID::new(node, name, classical))
}
}

Expand Down Expand Up @@ -196,7 +196,7 @@ mod test {
let build_result = {
let mut module_builder = ModuleBuilder::new();

let qubit_state_type = module_builder.add_alias_declare("qubit_state", true)?;
let qubit_state_type = module_builder.add_alias_declare("qubit_state", false)?;

let f_build = module_builder.define_function(
"main",
Expand Down
4 changes: 2 additions & 2 deletions src/hugr/region.rs
Original file line number Diff line number Diff line change
Expand Up @@ -415,13 +415,13 @@ mod test {
builder::{Container, Dataflow, DataflowSubContainer, HugrBuilder, ModuleBuilder},
ops::{handle::NodeHandle, LeafOp},
type_row,
types::{ClassicType, LinearType, Signature, SimpleType},
types::{ClassicType, Signature, SimpleType},
};

use super::*;

const NAT: SimpleType = SimpleType::Classic(ClassicType::i64());
const QB: SimpleType = SimpleType::Linear(LinearType::Qubit);
const QB: SimpleType = SimpleType::Qubit;

/// Make a module hugr with a fn definition containing an inner dfg node.
///
Expand Down
6 changes: 3 additions & 3 deletions src/hugr/rewrite/simple_replace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -266,12 +266,12 @@ mod test {
use crate::hugr::{Hugr, Node};
use crate::ops::OpTag;
use crate::ops::{LeafOp, OpTrait, OpType};
use crate::types::{ClassicType, LinearType, Signature, SimpleType};
use crate::types::{ClassicType, Signature, SimpleType};
use crate::{type_row, Port};

use super::SimpleReplacement;

const QB: SimpleType = SimpleType::Linear(LinearType::Qubit);
const QB: SimpleType = SimpleType::Qubit;

/// Creates a hugr like the following:
/// -- H --
Expand Down Expand Up @@ -511,7 +511,7 @@ mod test {

#[test]
fn test_replace_cx_cross() {
let q_row: Vec<SimpleType> = vec![LinearType::Qubit.into(), LinearType::Qubit.into()];
let q_row: Vec<SimpleType> = vec![SimpleType::Qubit, SimpleType::Qubit];
let mut builder = DFGBuilder::new(q_row.clone(), q_row).unwrap();
let mut circ = builder.as_circuit(builder.input_wires().collect());
circ.append(LeafOp::CX, [0, 1]).unwrap();
Expand Down
6 changes: 3 additions & 3 deletions src/hugr/serialize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ pub mod test {
ModuleBuilder,
},
ops::{dataflow::IOTrait, Input, LeafOp, Module, Output, DFG},
types::{ClassicType, LinearType, Signature, SimpleType},
types::{ClassicType, Signature, SimpleType},
Port,
};
use itertools::Itertools;
Expand All @@ -255,7 +255,7 @@ pub mod test {
};

const NAT: SimpleType = SimpleType::Classic(ClassicType::i64());
const QB: SimpleType = SimpleType::Linear(LinearType::Qubit);
const QB: SimpleType = SimpleType::Qubit;

#[test]
fn empty_hugr_serialize() {
Expand Down Expand Up @@ -425,7 +425,7 @@ pub mod test {

#[test]
fn hierarchy_order() {
let qb: SimpleType = LinearType::Qubit.into();
let qb = SimpleType::Qubit;
let dfg = DFGBuilder::new([qb.clone()].to_vec(), [qb.clone()].to_vec()).unwrap();
let [old_in, out] = dfg.io();
let w = dfg.input_wires();
Expand Down
4 changes: 2 additions & 2 deletions src/hugr/validate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -742,13 +742,13 @@ mod test {
use crate::hugr::{HugrError, HugrMut};
use crate::ops::dataflow::IOTrait;
use crate::ops::{self, ConstValue, LeafOp, OpType};
use crate::types::{ClassicType, LinearType, Signature};
use crate::types::{ClassicType, Signature};
use crate::Direction;
use crate::{type_row, Node};

const NAT: SimpleType = SimpleType::Classic(ClassicType::i64());
const B: SimpleType = SimpleType::Classic(ClassicType::bit());
const Q: SimpleType = SimpleType::Linear(LinearType::Qubit);
const Q: SimpleType = SimpleType::Qubit;

/// Creates a hugr with a single function definition that copies a bit `copies` times.
///
Expand Down
18 changes: 11 additions & 7 deletions src/ops/handle.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Handles to nodes in HUGR.
//!
use crate::types::{ClassicType, Container, LinearType, SimpleType};
use crate::types::{ClassicType, Container, SimpleType};
use crate::Node;

use derive_more::From as DerFrom;
Expand Down Expand Up @@ -74,21 +74,25 @@ pub struct FuncID<const DEF: bool>(Node);
pub struct AliasID<const DEF: bool> {
node: Node,
name: SmolStr,
linear: bool,
classical: bool,
}

impl<const DEF: bool> AliasID<DEF> {
/// Construct new AliasID
pub fn new(node: Node, name: SmolStr, linear: bool) -> Self {
Self { node, name, linear }
pub fn new(node: Node, name: SmolStr, classical: bool) -> Self {
Self {
node,
name,
classical,
}
}

/// Construct new AliasID
pub fn get_alias_type(&self) -> SimpleType {
if self.linear {
Container::<LinearType>::Alias(self.name.clone()).into()
} else {
if self.classical {
Container::<ClassicType>::Alias(self.name.clone()).into()
} else {
Container::<SimpleType>::Alias(self.name.clone()).into()
}
}
/// Retrieve the underlying core type
Expand Down
Loading

0 comments on commit 2b29558

Please sign in to comment.