Skip to content
This repository has been archived by the owner on Dec 9, 2021. It is now read-only.

Commit

Permalink
DeterministicGate etc.
Browse files Browse the repository at this point in the history
  • Loading branch information
dlubarov committed Nov 10, 2020
1 parent c37807d commit 995e4c1
Show file tree
Hide file tree
Showing 6 changed files with 230 additions and 69 deletions.
102 changes: 71 additions & 31 deletions src/gates2/arithmetic_gate.rs
Original file line number Diff line number Diff line change
@@ -1,56 +1,96 @@
use std::marker::PhantomData;
use std::rc::Rc;

use crate::{CircuitBuilder2, ConstraintPolynomial, DeterministicGate, Field, Gate2, GateInstance, Target2};

pub const ID: &'static str = "ARITHMETIC";
pub const CONST_PRODUCT_WEIGHT: usize = 0;
pub const CONST_ADDEND_WEIGHT: usize = 1;
pub const WIRE_MULTIPLICAND_0: usize = 0;
pub const WIRE_MULTIPLICAND_1: usize = 1;
pub const WIRE_ADDEND: usize = 2;
pub const WIRE_OUTPUT: usize = 3;

/// A gate which can be configured to perform various arithmetic. In particular, it computes
///
/// ```text
/// output := const_product_weight * multiplicand_0 * multiplicand_1
/// + const_addend_weight * addend
/// output := product_weight * multiplicand_0 * multiplicand_1
/// + addend_weight * addend
/// ```
struct ArithmeticGate<F: Field> {
_phantom: PhantomData<F>
///
/// where `product_weight` and `addend_weight` are constants, and the other variables are wires.
struct ArithmeticGate2 {
}

impl ArithmeticGate2 {
pub const ID: &'static str = "ArithmeticGate";
pub const CONST_PRODUCT_WEIGHT: usize = 0;
pub const CONST_ADDEND_WEIGHT: usize = 1;
pub const WIRE_MULTIPLICAND_0: usize = 0;
pub const WIRE_MULTIPLICAND_1: usize = 1;
pub const WIRE_ADDEND: usize = 2;
pub const WIRE_OUTPUT: usize = 3;

/// Computes `x y + z`.
pub fn mul_add<F: Field>(
builder: &mut CircuitBuilder2<F>,
x: Target2<F>,
y: Target2<F>,
z: Target2<F>,
) -> Target2<F> {
let gate_type = gate_type();
let constants = vec![F::ONE, F::ONE];
let gate = builder.add_gate(GateInstance { gate_type, constants });

builder.copy(x, Target2::wire(gate, Self::WIRE_MULTIPLICAND_0));
builder.copy(y, Target2::wire(gate, Self::WIRE_MULTIPLICAND_1));
builder.copy(z, Target2::wire(gate, Self::WIRE_ADDEND));

Target2::wire(gate, Self::WIRE_OUTPUT)
}

/// Computes `x y`.
pub fn mul<F: Field>(
builder: &mut CircuitBuilder2<F>,
x: Target2<F>,
y: Target2<F>,
) -> Target2<F> {
let zero = builder.zero();
Self::mul_add(builder, x, y, zero)
}

/// Computes `x + y`.
pub fn add<F: Field>(
builder: &mut CircuitBuilder2<F>,
x: Target2<F>,
y: Target2<F>,
) -> Target2<F> {
let one = builder.one();
Self::mul_add(builder, x, one, y)
}
}

impl<F: Field> DeterministicGate<F> for ArithmeticGate<F> {
impl<F: Field> DeterministicGate<F> for ArithmeticGate2 {
fn id(&self) -> String {
"ArithmeticGate".into()
}

fn outputs(&self) -> Vec<(usize, ConstraintPolynomial<F>)> {
let const_0 = ConstraintPolynomial::local_constant(CONST_PRODUCT_WEIGHT);
let const_1 = ConstraintPolynomial::local_constant(CONST_ADDEND_WEIGHT);
let multiplicand_0 = ConstraintPolynomial::local_wire_value(WIRE_MULTIPLICAND_0);
let multiplicand_1 = ConstraintPolynomial::local_wire_value(WIRE_MULTIPLICAND_1);
let addend = ConstraintPolynomial::local_wire_value(WIRE_ADDEND);
let const_0 = ConstraintPolynomial::local_constant(Self::CONST_PRODUCT_WEIGHT);
let const_1 = ConstraintPolynomial::local_constant(Self::CONST_ADDEND_WEIGHT);
let multiplicand_0 = ConstraintPolynomial::local_wire_value(Self::WIRE_MULTIPLICAND_0);
let multiplicand_1 = ConstraintPolynomial::local_wire_value(Self::WIRE_MULTIPLICAND_1);
let addend = ConstraintPolynomial::local_wire_value(Self::WIRE_ADDEND);

let out = const_0 * multiplicand_0 * &multiplicand_1 + const_1 * &addend;
vec![(WIRE_OUTPUT, out)]
vec![(Self::WIRE_OUTPUT, out)]
}
}

fn gate_type<F: Field>() -> Rc<Gate2<F>> {
fn gate_type<F: Field>() -> Rc<dyn Gate2<F>> {
todo!()
}

fn add<F: Field>(builder: &mut CircuitBuilder2<F>, x: Target2<F>, y: Target2<F>) -> Target2<F> {
let gate_type = gate_type();
let constants = vec![F::ONE, F::ONE];
let gate = builder.add_gate(GateInstance { gate_type, constants });
let one = builder.one();

builder.copy(x, Target2::wire(gate, WIRE_MULTIPLICAND_0));
builder.copy(one, Target2::wire(gate, WIRE_MULTIPLICAND_1));
builder.copy(y, Target2::wire(gate, WIRE_ADDEND));
#[cfg(test)]
mod tests {
use crate::{CircuitBuilder2, TweedledumBase};
use crate::gates2::arithmetic_gate::ArithmeticGate2;

Target2::wire(gate, WIRE_OUTPUT)
fn add() {
let mut builder = CircuitBuilder2::<TweedledumBase>::new();
let one = builder.one();
let two = builder.two();
let sum = ArithmeticGate2::add(&mut builder, one, one);
}
}
100 changes: 87 additions & 13 deletions src/gates2/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::rc::Rc;

use crate::{Field, ConstraintPolynomial, WitnessGenerator2, Target2, PartialWitness2};
use crate::{Field, ConstraintPolynomial, WitnessGenerator2, Target2, PartialWitness2, EvaluationVars, SimpleGenerator, Wire};
use std::iter;

pub mod arithmetic_gate;

Expand All @@ -9,7 +10,16 @@ pub trait Gate2<F: Field> {

fn constraints(&self) -> Vec<ConstraintPolynomial<F>>;

fn generators(&self, index: usize) -> Vec<Box<dyn WitnessGenerator2<F>>>;
fn generators(
&self,
gate_index: usize,
local_constants: Vec<F>,
next_constants: Vec<F>,
) -> Vec<Box<dyn WitnessGenerator2<F>>>;

fn max_constant_index(&self) -> Option<usize>;

fn max_wire_input_index(&self) -> Option<usize>;
}

/// A deterministic gate. Each entry in `outputs()` describes how that output is evaluated; this is
Expand All @@ -31,32 +41,96 @@ impl<F: Field> Gate2<F> for dyn DeterministicGate<F> {
.collect()
}

fn generators(&self, index: usize) -> Vec<Box<dyn WitnessGenerator2<F>>> {
fn generators(
&self,
gate_index: usize,
local_constants: Vec<F>,
next_constants: Vec<F>,
) -> Vec<Box<dyn WitnessGenerator2<F>>> {
struct OutputGenerator<F: Field> {
i: usize,
gate_index: usize,
input_index: usize,
out: ConstraintPolynomial<F>,
local_constants: Vec<F>,
next_constants: Vec<F>,
}

impl<F: Field> WitnessGenerator2<F> for OutputGenerator<F> {
fn watch_list(&self) -> Vec<Target2<F>> {
todo!()
impl<F: Field> SimpleGenerator<F> for OutputGenerator<F> {
fn dependencies(&self) -> Vec<Target2<F>> {
self.out.dependencies(self.gate_index)
.into_iter()
.map(Target2::Wire)
.collect()
}

fn run(&mut self, witness: &PartialWitness2<F>) -> (PartialWitness2<F>, bool) {
// ConstraintPolynomial::evaluate_all(&self.outputs)
todo!()
fn run_once(&mut self, witness: &PartialWitness2<F>) -> PartialWitness2<F> {
let mut local_wire_values = Vec::new();
let mut next_wire_values = Vec::new();

// Get an exclusive upper bound on the largest input index in this constraint.
let input_limit_exclusive = self.out.max_wire_input_index()
.map_or(0, |i| i + 1);

for input in 0..input_limit_exclusive {
let local_wire = Wire { gate: self.gate_index, input };
let next_wire = Wire { gate: self.gate_index + 1, input };

// Lookup the values if they exist. If not, we can just insert a zero, knowing
// that it will not be used. (If it was used, it would have been included in our
// dependencies, and this generator would not have run yet.)
let local_value = witness.try_get(Target2::Wire(local_wire)).unwrap_or(F::ZERO);
let next_value = witness.try_get(Target2::Wire(next_wire)).unwrap_or(F::ZERO);

local_wire_values.push(local_value);
next_wire_values.push(next_value);
}

let vars = EvaluationVars {
local_constants: &self.local_constants,
next_constants: &self.next_constants,
local_wire_values: &local_wire_values,
next_wire_values: &next_wire_values,
};

let result_wire = Wire { gate: self.gate_index, input: self.input_index };
let result_value = self.out.evaluate(vars);
let mut witness = PartialWitness2::new();
witness.set(Target2::Wire(result_wire), result_value);
witness
}
}

self.outputs()
.into_iter()
.map(|(i, out)| {
let b: Box::<dyn WitnessGenerator2<F> + 'static> =
Box::new(OutputGenerator { i, out });
.map(|(input_index, out)| {
let og = OutputGenerator {
gate_index,
input_index,
out,
local_constants: local_constants.clone(),
next_constants: next_constants.clone(),
};
let b: Box::<dyn WitnessGenerator2<F> + 'static> = Box::new(og);
b
})
.collect()
}

fn max_constant_index(&self) -> Option<usize> {
self.outputs().into_iter()
.map(|(i, out)| out.max_constant_index())
.filter_map(|out_max| out_max)
.max()
}

fn max_wire_input_index(&self) -> Option<usize> {
self.outputs().into_iter()
// For each output, we consider both the output wire and the wires it depends on.
.flat_map(|(i, out)| out.max_wire_input_index()
.into_iter()
.chain(iter::once(i)))
.max()
}
}

pub struct GateInstance<F: Field> {
Expand Down
71 changes: 49 additions & 22 deletions src/plonk2/constraint_polynomial.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
use std::hash::{Hash, Hasher};
use std::ptr;
use std::rc::Rc;
use crate::Field;
use crate::{Field, Wire};
use std::collections::HashMap;
use std::ops::{Add, Sub, Mul};
use std::ops::{Add, Sub, Mul, Neg};

struct EvaluationVars<'a, F: Field> {
local_constants: &'a [F],
next_constants: &'a [F],
local_wire_values: &'a [F],
next_wire_values: &'a [F],
pub(crate) struct EvaluationVars<'a, F: Field> {
pub(crate) local_constants: &'a [F],
pub(crate) next_constants: &'a [F],
pub(crate) local_wire_values: &'a [F],
pub(crate) next_wire_values: &'a [F],
}

/// A polynomial over all the variables that are subject to constraints (local constants, next
Expand Down Expand Up @@ -43,10 +43,6 @@ impl<F: Field> ConstraintPolynomial<F> {
Self::from_inner(ConstraintPolynomialInner::NextWireValue(index))
}

fn neg(self) -> Self {
todo!()
}

fn add(self, rhs: Self) -> Self {
Self::from_inner(ConstraintPolynomialInner::Sum {
lhs: self.0,
Expand Down Expand Up @@ -76,20 +72,35 @@ impl<F: Field> ConstraintPolynomial<F> {
(self.0).0.degree()
}

/// Returns the set of wires that this constraint would depend on if it were applied at `index`.
pub(crate) fn dependencies(&self, index: usize) -> Vec<Wire> {
todo!()
}

/// Find the largest input index among the wires this constraint depends on.
pub(crate) fn max_wire_input_index(&self) -> Option<usize> {
self.dependencies(0)
.into_iter()
.map(|wire| wire.input)
.max()
}

pub(crate) fn max_constant_index(&self) -> Option<usize> {
todo!()
}

pub(crate) fn evaluate(&self, vars: EvaluationVars<F>) -> F {
let results = Self::evaluate_all(&[self.clone()], vars);
assert_eq!(results.len(), 1);
results[0]
}

/// Evaluate multiple constraint polynomials simultaneously. This can be more efficient than
/// evaluating them sequentially, since shared intermediate results will only be computed once.
pub(crate) fn evaluate_all(
polynomials: &[ConstraintPolynomial<F>],
local_constants: &[F],
next_constants: &[F],
local_wire_values: &[F],
next_wire_values: &[F],
vars: EvaluationVars<F>,
) -> Vec<F> {
let vars = EvaluationVars {
local_constants,
next_constants,
local_wire_values,
next_wire_values,
};

let mut mem = HashMap::new();
polynomials.iter()
.map(|p| p.0.evaluate_memoized(&vars, &mut mem))
Expand All @@ -101,6 +112,22 @@ impl<F: Field> ConstraintPolynomial<F> {
}
}

impl<F: Field> Neg for ConstraintPolynomial<F> {
type Output = Self;

fn neg(self) -> Self {
self * ConstraintPolynomial::constant(F::NEG_ONE)
}
}

impl<F: Field> Neg for &ConstraintPolynomial<F> {
type Output = ConstraintPolynomial<F>;

fn neg(self) -> ConstraintPolynomial<F> {
self.clone().neg()
}
}

/// Generates the following variants of a binary operation:
/// - `Self . Self`
/// - `&Self . Self`
Expand Down
2 changes: 1 addition & 1 deletion src/plonk2/generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pub trait SimpleGenerator<F: Field> {
fn run_once(&mut self, witness: &PartialWitness2<F>) -> PartialWitness2<F>;
}

impl<F: Field> WitnessGenerator2<F> for dyn SimpleGenerator<F> {
impl<F: Field, SG: SimpleGenerator<F>> WitnessGenerator2<F> for SG {
fn watch_list(&self) -> Vec<Target2<F>> {
self.dependencies()
}
Expand Down
Loading

0 comments on commit 995e4c1

Please sign in to comment.