-
Notifications
You must be signed in to change notification settings - Fork 48
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: generic circuit struct #123
feat: generic circuit struct #123
Conversation
740a715
to
98cd0b9
Compare
|
The model has been updated with few key changes:
A key change is that the execution is defined externally on both, by part of an This means that each structure can perform multiple operations with the same configuration. |
3ba9966
to
599f66c
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I'm convinced of the execution abstractions here. For now I think it would be easiest to just stick to developing the Circuit
type and have it enforce the invariants we discussed previously, eg topological sort etc. We can add execution in a follow up PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice, this will be a much more flexible base for our circuit models!
Some requests 🙏
|
||
/// Circuit errors. | ||
#[derive(Debug, Error, PartialEq, Eq)] | ||
pub enum CircuitError { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pub enum CircuitError { | |
pub enum CircuitBuilderError { |
pub mod circuit; | ||
pub mod model; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's make these private and export needed types
} | ||
|
||
#[cfg(test)] | ||
mod tests { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we add a couple test cases?
- Unordered gates
- Disconnected gate
- Node ids are contiguous
- Node ids are ordered: { input nodes } .. { output nodes }
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added the disconnected gate test, but I'm confused about testing the node ids, in which way they should be contiguous or ordered? The order is based on that the gates should be ready to be processed, so node ids are not always contiguous or ordered
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The order is based on that the gates should be ready to be processed
Exactly, so the node id should correspond to the gate position. For example:
assert!(gates.windows(2).map(|window| window[1].out.0 == window[0].out.0 + 1).all());
This asserts the nodes are ordered and contiguous.
/// A `Component` trait that represents a block with inputs and outputs. | ||
pub trait Component { | ||
/// Returns the input node indices. | ||
fn get_inputs(&self) -> Vec<usize>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should return impl Iterator<Item = &Node>
so that it's possible for the ids to be stack allocated, ie [Node; 2]
, or some other data structure. Otherwise every call to this function is going to incur a heap allocation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we add a Node
newtype, as we currently have in mpz-circuits
. Gives us better typing. Perhaps we should also switch it from usize
to u32
so the circuit isn't needlessly twice the size on 64 bit archs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- I think that adding a
Node
type is not strictly necessary so I wouldn't implement it in the model. Don't see how much of an improvement this extra typing could provide on our level, perhaps implementers could add this layer themselves. - The input or output is intended to be a position on a linear memory array so it will be
usize
in the end, on 64-bit architectures you'll have to complete the address to access an index.
But both are good suggestions, I could be missing something!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't see how much of an improvement this extra typing could provide on our level
It's all about the public API: What do you want to commit to? Using usize
commits to that underlying representation, whereas a Node(u32)
type allows us to constrain the data type and the traits which a node implements. For example why would a node implement Add
? usize
does. Do we want to allow it to be mutated? How might that affect certain invariants we have elsewhere? This is less an issue for private types, but this is explicitly part of the crates pub api.
The input or output is intended to be a position on a linear memory array so it will be usize in the end
Yes it will be used to index into linear memory, but a Vec<u32>
is half the size of a Vec<u64>
, and hence a Vec<Gate>
will be half the size if Node
is backed by a u32
instead of u64
on 64-bit archs.
assert!( | ||
circuit.is_ok(), | ||
"Failed to build circuit: {:?}", | ||
circuit.err() | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be an error, no? A circuit should not have disconnected gates
} | ||
|
||
#[cfg(test)] | ||
mod tests { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The order is based on that the gates should be ready to be processed
Exactly, so the node id should correspond to the gate position. For example:
assert!(gates.windows(2).map(|window| window[1].out.0 == window[0].out.0 + 1).all());
This asserts the nodes are ordered and contiguous.
|
||
/// A circuit node, holds a generic type identifier. | ||
#[derive(Debug, Eq, PartialEq)] | ||
pub struct Node<T> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this generic?
Closing for #156 |
Description
This PR aims to introduce a new generic circuit struct:
The
Circuit
is a collection of gates. Since for the execution of these gates is important that these form a directed acyclic graph (DAG), the correct way of building this circuit is using theCircuitBuilder
The
CircuitBuilder
sort the gates topologically, this means in the order they should be executed taking dependencies into account, and ensures that they form a DAG.Here is our only constrain for the gates, they have to implement the
Component
trait, this means that we should be able to fetch their inputs and outputs. This is strictly necessary to sort the gates.The
Component
is our only trait in the model module at the moment, aimed at storing defining traits.Example
A sample of how to use this can be found in the
tests
directory.