Skip to content

Commit 67441b6

Browse files
committed
signoff: Horner evaluation
1 parent 8efe4f3 commit 67441b6

File tree

3 files changed

+98
-127
lines changed

3 files changed

+98
-127
lines changed

tasm-lib/benchmarks/tasmlib_array_horner_evaluation_with_100_coefficients.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,21 @@
22
{
33
"name": "tasmlib_array_horner_evaluation_with_100_coefficients",
44
"benchmark_result": {
5-
"clock_cycle_count": 917,
6-
"hash_table_height": 984,
5+
"clock_cycle_count": 813,
6+
"hash_table_height": 858,
77
"u32_table_height": 0,
8-
"op_stack_table_height": 1409,
8+
"op_stack_table_height": 1207,
99
"ram_table_height": 300
1010
},
1111
"case": "CommonCase"
1212
},
1313
{
1414
"name": "tasmlib_array_horner_evaluation_with_100_coefficients",
1515
"benchmark_result": {
16-
"clock_cycle_count": 917,
17-
"hash_table_height": 984,
16+
"clock_cycle_count": 813,
17+
"hash_table_height": 858,
1818
"u32_table_height": 0,
19-
"op_stack_table_height": 1409,
19+
"op_stack_table_height": 1207,
2020
"ram_table_height": 300
2121
},
2222
"case": "WorstCase"

tasm-lib/benchmarks/tasmlib_array_horner_evaluation_with_587_coefficients.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,21 @@
22
{
33
"name": "tasmlib_array_horner_evaluation_with_587_coefficients",
44
"benchmark_result": {
5-
"clock_cycle_count": 5300,
6-
"hash_table_height": 5658,
5+
"clock_cycle_count": 4709,
6+
"hash_table_height": 4950,
77
"u32_table_height": 0,
8-
"op_stack_table_height": 8227,
8+
"op_stack_table_height": 7051,
99
"ram_table_height": 1761
1010
},
1111
"case": "CommonCase"
1212
},
1313
{
1414
"name": "tasmlib_array_horner_evaluation_with_587_coefficients",
1515
"benchmark_result": {
16-
"clock_cycle_count": 5300,
17-
"hash_table_height": 5658,
16+
"clock_cycle_count": 4709,
17+
"hash_table_height": 4950,
1818
"u32_table_height": 0,
19-
"op_stack_table_height": 8227,
19+
"op_stack_table_height": 7051,
2020
"ram_table_height": 1761
2121
},
2222
"case": "WorstCase"
Lines changed: 86 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,35 @@
1+
use std::collections::HashMap;
2+
13
use itertools::Itertools;
24
use triton_vm::prelude::*;
35

46
use crate::data_type::ArrayType;
57
use crate::prelude::*;
8+
use crate::traits::basic_snippet::Reviewer;
9+
use crate::traits::basic_snippet::SignOffFingerprint;
610

711
/// Evaluate a polynomial in a point using the Horner method.
812
///
9-
/// HornerEvaluation takes an array of coefficients (representing a polynomial)
10-
/// and a scalar (representing an indeterminate) and computes the value of the
11-
/// polynomial in that point. It can be used for univariate batching, whereby
12-
/// the object is to compute a random linear sum of a given set of points, and
13-
/// the weights are given by the powers of one challenge.
13+
/// `HornerEvaluation` takes an array of coefficients (representing a
14+
/// polynomial) and a scalar (representing an indeterminate) and computes the
15+
/// value of the polynomial in that point. It can be used for univariate
16+
/// batching, whereby the object is to compute a random linear sum of a given
17+
/// set of points, and the weights are given by the powers of one challenge.
18+
///
19+
/// ### Behavior
20+
///
21+
/// ```text
22+
/// BEFORE: _ *coefficients [indeterminate: XFieldElement]
23+
/// AFTER: _ [evaluation: XFieldElement]
24+
/// ```
25+
///
26+
/// ### Preconditions
27+
///
28+
/// None.
29+
///
30+
/// ### Postconditions
31+
///
32+
/// None.
1433
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
1534
pub struct HornerEvaluation {
1635
pub num_coefficients: usize,
@@ -24,164 +43,121 @@ impl HornerEvaluation {
2443

2544
impl BasicSnippet for HornerEvaluation {
2645
fn inputs(&self) -> Vec<(DataType, String)> {
46+
let coefficients_ty = DataType::Array(Box::new(ArrayType {
47+
element_type: DataType::Xfe,
48+
length: self.num_coefficients,
49+
}));
50+
2751
vec![
28-
(
29-
DataType::Array(Box::new(ArrayType {
30-
element_type: DataType::Xfe,
31-
length: self.num_coefficients,
32-
})),
33-
"*coefficients".to_string(),
34-
),
52+
(coefficients_ty, "*coefficients".to_string()),
3553
(DataType::Xfe, "indeterminate".to_string()),
3654
]
3755
}
3856

3957
fn outputs(&self) -> Vec<(DataType, String)> {
40-
vec![(DataType::Xfe, "value".to_string())]
58+
vec![(DataType::Xfe, "evaluation".to_string())]
4159
}
4260

4361
fn entrypoint(&self) -> String {
44-
format!(
45-
"tasmlib_array_horner_evaluation_with_{}_coefficients",
46-
self.num_coefficients
47-
)
62+
let n = self.num_coefficients;
63+
format!("tasmlib_array_horner_evaluation_with_{n}_coefficients")
4864
}
4965

50-
fn code(&self, _library: &mut Library) -> Vec<LabelledInstruction> {
51-
let entrypoint = self.entrypoint();
52-
66+
fn code(&self, _: &mut Library) -> Vec<LabelledInstruction> {
5367
let update_running_evaluation = triton_asm! {
54-
// BEFORE: _ *coefficients_end [x] [v]
55-
// AFTER : _ [vx+c]
68+
// BEFORE: _ *coefficients_end [x: XFE] [v: XFE]
69+
// AFTER : _ *coefficients_end-3 [x: XFE] [vx+c: XFE]
5670
dup 5 dup 5 dup 5 // _ *coefficients_end [x] [v] [x]
57-
xx_mul // _ *coefficients_end [x] [vx]
58-
dup 6 // _ *coefficients_end [x] [vx] *coefficients_end
59-
read_mem 3 // _ *coefficients_end [x] [vx] [c] *coefficients_end+3
60-
swap 10 // _ *coefficients_end-3 [x] [vx] [c] *coefficients_end
61-
pop 1 // _ *coefficients_end-3 [x] [vx] [c]
62-
xx_add // _ *coefficients_end-3 [x] [vc+c]
71+
xx_mul // _ *coefficients_end [x] [vx]
72+
pick 6 // _ [x] [vx] *coefficients_end
73+
read_mem 3 // _ [x] [vx] [c] *coefficients_end-3
74+
place 9 // _ *coefficients_end-3 [x] [vx] [c]
75+
xx_add // _ *coefficients_end-3 [x] [vx+c]
6376
};
64-
6577
let update_running_evaluation_for_each_coefficient = (0..self.num_coefficients)
6678
.flat_map(|_| update_running_evaluation.clone())
6779
.collect_vec();
6880

69-
let jump_to_end = self.num_coefficients as isize * 3 - 1;
70-
7181
triton_asm! {
72-
// BEFORE: _ *coefficients x2 x1 x0
73-
// AFTER: _ v2 v1 v0
74-
{entrypoint}:
75-
// point to the last element of the array
76-
swap 3
77-
push {jump_to_end} add
78-
swap 3
79-
80-
// push initial value of running evaluation
81-
push 0 push 0 push 0 // _ *coefficients_end [x] [v]
82-
83-
// update running evaluation {num_coefficients_end}-many times
82+
// BEFORE: _ *coefficients [x: XFE]
83+
// AFTER: _ [v: XFE]
84+
{self.entrypoint()}:
85+
pick 3 // _ [x: XFE] *coefficients
86+
addi {(self.num_coefficients * 3).saturating_sub(1)}
87+
place 3 // _ *coefficients_end [x: XFE]
88+
push 0 push 0 push 0 // _ *coefficients_end [x: XFE] [v: XFE]
8489
{&update_running_evaluation_for_each_coefficient}
85-
// _ *coefficients_end-3n [x] [v']
86-
87-
// clean up stack
88-
// _ *coefficients_end-3n x2 x1 x0 v2 v1 v0
89-
swap 4 pop 1 // _ *coefficients_end-3n x2 v0 x0 v2 v1
90-
swap 4 pop 1 // _ *coefficients_end-3n v1 v0 x0 v2
91-
swap 4 pop 1 // _ v2 v1 v0 x0
92-
pop 1 // _ v2 v1 v0
90+
// _ *coefficients_end-3n [x: XFE] [v': XFE]
91+
place 6 place 6 place 6 // _ [v': XFE] *coefficients_end-3n [x: XFE]
92+
pop 4 // _ [v': XFE]
9393
return
9494
}
9595
}
96+
97+
fn sign_offs(&self) -> HashMap<Reviewer, SignOffFingerprint> {
98+
let mut sign_offs = HashMap::new();
99+
if self.num_coefficients == 4 {
100+
sign_offs.insert(Reviewer("ferdinand"), 0xec460e65f9c22a87.into());
101+
}
102+
103+
sign_offs
104+
}
96105
}
97106

98107
#[cfg(test)]
99108
mod tests {
100-
use num::Zero;
101-
102109
use super::*;
103-
use crate::empty_stack;
104110
use crate::rust_shadowing_helper_functions::array::array_get;
105111
use crate::rust_shadowing_helper_functions::array::insert_as_array;
106112
use crate::test_prelude::*;
113+
use crate::twenty_first::prelude::Polynomial;
107114

108-
impl Function for HornerEvaluation {
115+
impl Accessor for HornerEvaluation {
109116
fn rust_shadow(
110117
&self,
111118
stack: &mut Vec<BFieldElement>,
112-
memory: &mut HashMap<BFieldElement, BFieldElement>,
119+
memory: &HashMap<BFieldElement, BFieldElement>,
113120
) {
114-
// read indeterminate
115-
let x = XFieldElement::new([
116-
stack.pop().unwrap(),
117-
stack.pop().unwrap(),
118-
stack.pop().unwrap(),
119-
]);
120-
121-
// read location of array
122-
let pointer = stack.pop().unwrap();
121+
let indeterminate = pop_encodable(stack);
122+
let coefficient_ptr = stack.pop().unwrap();
123123

124-
// read array of coefficients
125124
let coefficients = (0..self.num_coefficients)
126-
.map(|i| array_get(pointer, i, memory, 3))
125+
.map(|i| array_get(coefficient_ptr, i, memory, 3))
127126
.map(|bfes| XFieldElement::new(bfes.try_into().unwrap()))
128-
.collect_vec();
129-
130-
// evaluate polynomial using Horner's method
131-
let mut running_evaluation = XFieldElement::zero();
132-
for c in coefficients.into_iter().rev() {
133-
running_evaluation *= x;
134-
running_evaluation += c;
135-
}
136-
137-
// push value to stack
138-
let mut value = running_evaluation.coefficients.to_vec();
139-
stack.push(value.pop().unwrap());
140-
stack.push(value.pop().unwrap());
141-
stack.push(value.pop().unwrap());
127+
.collect();
128+
let polynomial = Polynomial::new(coefficients);
129+
let evaluation = polynomial.evaluate_in_same_field(indeterminate);
130+
131+
push_encodable(stack, &evaluation);
142132
}
143133

144134
fn pseudorandom_initial_state(
145135
&self,
146136
seed: [u8; 32],
147-
_bench_case: Option<BenchmarkCase>,
148-
) -> FunctionInitialState {
137+
_: Option<BenchmarkCase>,
138+
) -> AccessorInitialState {
149139
let mut rng = StdRng::from_seed(seed);
150140

151-
// sample coefficients
152141
let coefficients = (0..self.num_coefficients)
153142
.map(|_| rng.gen::<XFieldElement>())
154-
.collect_vec();
143+
.collect();
144+
let address = rng.gen();
155145

156-
// sample address
157-
let address = BFieldElement::new(rng.next_u64() % (1 << 30));
158-
println!("address: {}", address.value());
159-
160-
// store coefficients
161-
let mut memory: HashMap<BFieldElement, BFieldElement> = HashMap::new();
146+
let mut memory = HashMap::new();
162147
insert_as_array(address, &mut memory, coefficients);
163148

164-
// sample indeterminate
165-
let x: XFieldElement = rng.gen();
166-
167-
// prepare stack
168-
let mut stack = empty_stack();
149+
let mut stack = self.init_stack_for_isolated_run();
169150
stack.push(address);
170-
stack.push(x.coefficients[2]);
171-
stack.push(x.coefficients[1]);
172-
stack.push(x.coefficients[0]);
151+
push_encodable(&mut stack, &rng.gen::<XFieldElement>()); // indeterminate
173152

174-
FunctionInitialState { stack, memory }
153+
AccessorInitialState { stack, memory }
175154
}
176155
}
177156

178157
#[test]
179-
fn horner_evaluation() {
180-
for n in [0, 1, 20, 587, 1000] {
181-
let horner = HornerEvaluation {
182-
num_coefficients: n,
183-
};
184-
ShadowedFunction::new(horner).test();
158+
fn rust_shadow() {
159+
for n in [0, 1, 4, 20, 587, 1000] {
160+
ShadowedAccessor::new(HornerEvaluation::new(n)).test();
185161
}
186162
}
187163
}
@@ -192,14 +168,9 @@ mod benches {
192168
use crate::test_prelude::*;
193169

194170
#[test]
195-
fn bench_100() {
196-
let num_coefficients = 100;
197-
ShadowedFunction::new(HornerEvaluation { num_coefficients }).bench();
198-
}
199-
200-
#[test]
201-
fn bench_587() {
202-
let num_coefficients = 587;
203-
ShadowedFunction::new(HornerEvaluation { num_coefficients }).bench();
171+
fn bench() {
172+
for n in [100, 587] {
173+
ShadowedAccessor::new(HornerEvaluation::new(n)).bench();
174+
}
204175
}
205176
}

0 commit comments

Comments
 (0)