tasm_lib/array/
horner_evaluation.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
use itertools::Itertools;
use triton_vm::prelude::triton_asm;

use crate::data_type::ArrayType;
use crate::data_type::DataType;
use crate::traits::basic_snippet::BasicSnippet;

/// Evaluate a polynomial in a point using the Horner method.
///
/// HornerEvaluation takes an array of coefficients (representing a polynomial)
/// and a scalar (representing an indeterminate) and computes the value of the
/// polynomial in that point. It can be used for univariate batching, whereby
/// the object is to compute a random linear sum of a given set of points, and
/// the weights are given by the powers of one challenge.
pub struct HornerEvaluation {
    pub num_coefficients: usize,
}

impl HornerEvaluation {
    pub fn new(num_coefficients: usize) -> Self {
        Self { num_coefficients }
    }
}

impl BasicSnippet for HornerEvaluation {
    fn inputs(&self) -> Vec<(crate::data_type::DataType, String)> {
        vec![
            (
                DataType::Array(Box::new(ArrayType {
                    element_type: DataType::Xfe,
                    length: self.num_coefficients,
                })),
                "*coefficients".to_string(),
            ),
            (DataType::Xfe, "indeterminate".to_string()),
        ]
    }

    fn outputs(&self) -> Vec<(crate::data_type::DataType, String)> {
        vec![(DataType::Xfe, "value".to_string())]
    }

    fn entrypoint(&self) -> String {
        format!(
            "tasmlib_array_horner_evaluation_with_{}_coefficients",
            self.num_coefficients
        )
    }

    fn code(
        &self,
        _library: &mut crate::library::Library,
    ) -> Vec<triton_vm::prelude::LabelledInstruction> {
        let entrypoint = self.entrypoint();

        let update_running_evaluation = triton_asm! {
            // BEFORE: _ *coefficients_end [x] [v]
            // AFTER : _ [vx+c]
            dup 5 dup 5 dup 5           // _ *coefficients_end [x] [v] [x]
            xx_mul                       // _ *coefficients_end [x] [vx]
            dup 6                       // _ *coefficients_end [x] [vx] *coefficients_end
            read_mem 3                  // _ *coefficients_end [x] [vx] [c] *coefficients_end+3
            swap 10                     // _ *coefficients_end-3 [x] [vx] [c] *coefficients_end
            pop 1                       // _ *coefficients_end-3 [x] [vx] [c]
            xx_add                       // _ *coefficients_end-3 [x] [vc+c]
        };

        let update_running_evaluation_for_each_coefficient = (0..self.num_coefficients)
            .flat_map(|_| update_running_evaluation.clone())
            .collect_vec();

        let jump_to_end = self.num_coefficients as isize * 3 - 1;

        triton_asm! {
            // BEFORE: _ *coefficients x2 x1 x0
            // AFTER:  _ v2 v1 v0
            {entrypoint}:
                // point to the last element of the array
                swap 3
                push {jump_to_end} add
                swap 3

                // push initial value of running evaluation
                push 0 push 0 push 0    // _ *coefficients_end [x] [v]

                // update running evaluation {num_coefficients_end}-many times
                {&update_running_evaluation_for_each_coefficient}
                                        // _ *coefficients_end-3n [x] [v']

                // clean up stack
                                        // _ *coefficients_end-3n x2 x1 x0 v2 v1 v0
                swap 4 pop 1            // _ *coefficients_end-3n x2 v0 x0 v2 v1
                swap 4 pop 1            // _ *coefficients_end-3n v1 v0 x0 v2
                swap 4 pop 1            // _ v2 v1 v0 x0
                pop 1                   // _ v2 v1 v0
                return
        }
    }
}

#[cfg(test)]
mod test {
    use std::collections::HashMap;

    use num::Zero;
    use rand::prelude::*;
    use triton_vm::twenty_first::prelude::*;

    use super::*;
    use crate::empty_stack;
    use crate::rust_shadowing_helper_functions::array::array_get;
    use crate::rust_shadowing_helper_functions::array::insert_as_array;
    use crate::traits::function::Function;
    use crate::traits::function::FunctionInitialState;
    use crate::traits::function::ShadowedFunction;
    use crate::traits::rust_shadow::RustShadow;

    impl Function for HornerEvaluation {
        fn rust_shadow(
            &self,
            stack: &mut Vec<triton_vm::prelude::BFieldElement>,
            memory: &mut std::collections::HashMap<
                triton_vm::prelude::BFieldElement,
                triton_vm::prelude::BFieldElement,
            >,
        ) {
            // read indeterminate
            let x = XFieldElement::new([
                stack.pop().unwrap(),
                stack.pop().unwrap(),
                stack.pop().unwrap(),
            ]);

            // read location of array
            let pointer = stack.pop().unwrap();

            // read array of coefficients
            let coefficients = (0..self.num_coefficients)
                .map(|i| array_get(pointer, i, memory, 3))
                .map(|bfes| XFieldElement::new(bfes.try_into().unwrap()))
                .collect_vec();

            // evaluate polynomial using Horner's method
            let mut running_evaluation = XFieldElement::zero();
            for c in coefficients.into_iter().rev() {
                running_evaluation *= x;
                running_evaluation += c;
            }

            // push value to stack
            let mut value = running_evaluation.coefficients.to_vec();
            stack.push(value.pop().unwrap());
            stack.push(value.pop().unwrap());
            stack.push(value.pop().unwrap());
        }

        fn pseudorandom_initial_state(
            &self,
            seed: [u8; 32],
            _bench_case: Option<crate::snippet_bencher::BenchmarkCase>,
        ) -> crate::traits::function::FunctionInitialState {
            let mut rng: StdRng = SeedableRng::from_seed(seed);

            // sample coefficients
            let coefficients = (0..self.num_coefficients)
                .map(|_| rng.gen::<XFieldElement>())
                .collect_vec();

            // sample address
            let address = BFieldElement::new(rng.next_u64() % (1 << 30));
            println!("address: {}", address.value());

            // store coefficients
            let mut memory: HashMap<BFieldElement, BFieldElement> = HashMap::new();
            insert_as_array(address, &mut memory, coefficients);

            // sample indeterminate
            let x: XFieldElement = rng.gen();

            // prepare stack
            let mut stack = empty_stack();
            stack.push(address);
            stack.push(x.coefficients[2]);
            stack.push(x.coefficients[1]);
            stack.push(x.coefficients[0]);

            FunctionInitialState { stack, memory }
        }
    }

    #[test]
    fn horner_evaluation() {
        for n in [0, 1, 20, 587, 1000] {
            let horner = HornerEvaluation {
                num_coefficients: n,
            };
            ShadowedFunction::new(horner).test();
        }
    }
}

#[cfg(test)]
mod benches {
    use super::*;
    use crate::traits::function::ShadowedFunction;
    use crate::traits::rust_shadow::RustShadow;

    #[test]
    fn horner_evaluation_100() {
        ShadowedFunction::new(HornerEvaluation {
            num_coefficients: 100,
        })
        .bench();
    }

    #[test]
    fn horner_evaluation_587() {
        ShadowedFunction::new(HornerEvaluation {
            num_coefficients: 587,
        })
        .bench();
    }
}