remainder/mle/betavalues.rs
1//! Module for dealing with the Beta equality function.
2
3use itertools::Itertools;
4use serde::{Deserialize, Serialize};
5use shared_types::{utils::bookkeeping_table::initialize_tensor, Field};
6use std::fmt::Debug;
7
8use super::{evals::MultilinearExtension, MleIndex};
9
10/// A struct that holds the claim and the relevant bound values for the beta
11/// equality MLE. Rather than storing the entire beta table, we simply store the
12/// points in the claim that are still "unbound", and the points that have been
13/// bound using the \[Thaler13\] definition of a beta table.
14///
15/// Beta tables are used to "linearize" an expression that we wish to evaluate
16/// over a claimed point `(g_0, ..., g_n)`. Therefore we create an MLE that
17/// evaluates to `1` at this point and `0` at every other point, which is a
18/// beta table. This would be a table of size `2^n`.
19///
20/// Instead, we represent the beta MLE `\beta(g_0, ..., g_{n-1}; x_0, ..., x_{n-1})` by mapping each
21/// index `i` that participates in this MLE to either `g_i`, if `x_i` has not yet been bound, or to
22/// `(1 - r_i)*(1 - g_i) + r_i*g_i`, if `x_i` has already been bound to `r_i`. Indices in the range
23/// `{0, 1, ..., n-1}` which are not part of this MLE are called "unassigned" and they don't map to
24/// any value.
25#[derive(Debug, Clone, Serialize, Deserialize)]
26#[serde(bound = "F: Field")]
27pub struct BetaValues<F: Field> {
28 /// Map from each index to a beta value type
29 pub values: Vec<BetaValueType<F>>,
30}
31
32/// Type of beta values
33#[derive(Debug, Clone, Serialize, Deserialize)]
34#[serde(bound = "F: Field")]
35pub enum BetaValueType<F: Field> {
36 /// In a linear round, there is no corresponding beta value
37 Unassigned,
38 /// Challenges that have not yet been "bound" in the
39 /// sumcheck protocol, as `g_i`
40 Unbound(F),
41 /// Challenges that have already been bound in the sumcheck protocol,
42 /// as `(1 - r_i)(1 - g_i) + r_i*g_i` where `i` is the key, `g_i` is the
43 /// claim challenge point, and `r_i` is the current challenge point.
44 Updated(F),
45}
46
47impl<F: Field> BetaValues<F> {
48 /// Constructs a new beta table using a vector of the challenge points in a
49 /// claim along with it's corresponding round index as a tuple.
50 pub fn new(layer_claim_vars_and_index: Vec<(usize, F)>) -> Self {
51 let mut values = Vec::new();
52 layer_claim_vars_and_index.iter().for_each(|(idx, elem)| {
53 if values.len() <= *idx {
54 values.extend(vec![BetaValueType::Unassigned; *idx + 1 - values.len()]);
55 }
56 values[*idx] = BetaValueType::Unbound(*elem);
57 });
58 BetaValues { values }
59 }
60
61 /// Updates the given value of beta using a new challenge point. Simply
62 /// `(1 - r_i)*(1 - g_i) + (r_i * g_i)` for an index `i`, previous claim
63 /// challenge point `g_i` and current challenge `r_i`.
64 ///
65 /// We remove it from the unbound hashmap and add it to the bound hashmap.
66 pub fn beta_update(&mut self, round_index: usize, challenge: F) {
67 assert!(self.values.len() > round_index);
68 if let BetaValueType::Unbound(val_to_update) = self.values[round_index] {
69 let updated_val =
70 ((F::ONE - val_to_update) * (F::ONE - challenge)) + (val_to_update * challenge);
71 self.values[round_index] = BetaValueType::Updated(updated_val);
72 } else {
73 unreachable!()
74 }
75 }
76
77 /// obtain the value at an index if it is updated
78 /// do not check for out of bound
79 pub fn get_updated_value(&self, index: usize) -> Option<F> {
80 if self.values.len() <= index {
81 None
82 } else if let BetaValueType::Updated(v) = self.values[index] {
83 Some(v)
84 } else {
85 None
86 }
87 }
88
89 /// obtain the value at an index if it is unbounded
90 pub fn get_unbound_value(&self, index: usize) -> Option<F> {
91 match self.values.get(index) {
92 Some(BetaValueType::Unbound(v)) => Some(*v),
93 _ => None,
94 }
95 }
96
97 /// determine if no entry is unbound
98 pub fn is_fully_bounded(&self) -> bool {
99 (0..self.values.len())
100 .filter(|i| self.get_unbound_value(*i).is_some())
101 .count()
102 == 0
103 }
104
105 /// obtain product of all updated values
106 pub fn fold_updated_values(&self) -> F {
107 (0..self.values.len())
108 .filter_map(|i| self.get_updated_value(i))
109 .product()
110 }
111
112 /// Given a vector of mle indices, returns the relevant beta bound and
113 /// unbound values we need. if the index is `Indexed(usize)`, then we grab
114 /// the *unbound* value and if it is `Bound(usize, chal)` we grab the
115 /// *bound* value.
116 pub fn get_relevant_beta_unbound_and_bound(
117 &self,
118 mle_indices: &[MleIndex<F>],
119 round_index: usize,
120 computes_evals: bool,
121 ) -> (Vec<F>, Vec<F>) {
122 // We always want every bound value so far.
123 // If we are computing evaluations for this node, then we want
124 // all of the updated beta values so far.
125 let bound_betas = if computes_evals {
126 (0..self.values.len())
127 .filter_map(|i| self.get_updated_value(i))
128 .collect()
129 }
130 // Otherwise, we are just computing the sum of the variables
131 // multiplied by the beta this round. This means that there is
132 // a beta MLE outside of this expression that factors in the
133 // updated values already, either in the form of the verifier
134 // computing the full sumcheck evaluation multiplied by the fully
135 // bound beta, or in the form of a selector where the updated
136 // values are factored directly into the evaluation of the selector
137 // variable itself.
138 else {
139 Vec::new()
140 };
141
142 let mut unbound_betas = mle_indices
143 .iter()
144 .filter_map(|index| match index {
145 MleIndex::Indexed(i) => self.get_unbound_value(*i),
146 _ => None,
147 })
148 .collect_vec();
149
150 // If the MLE indices does not contain the current "independent variable",
151 // then we want to manually include it in our unbound betas.
152 if !mle_indices.contains(&MleIndex::Indexed(round_index)) && computes_evals {
153 unbound_betas.push(self.get_unbound_value(round_index).unwrap())
154 }
155 (unbound_betas, bound_betas)
156 }
157
158 /// Takes two challenge points and computes the fully bound beta equality
159 /// value.
160 pub fn compute_beta_over_two_challenges(challenge_one: &[F], challenge_two: &[F]) -> F {
161 assert_eq!(challenge_one.len(), challenge_two.len());
162
163 // Formula is just: \prod_i (x_i * y_i) + (1 - x_i) * (1 - y_i)
164 let one = F::ONE;
165 challenge_one
166 .iter()
167 .zip(challenge_two.iter())
168 .fold(F::ONE, |acc, (x_i, y_i)| {
169 acc * ((*x_i * y_i) + (one - x_i) * (one - y_i))
170 })
171 }
172
173 /// Computes the value of `\beta(challenge; b)`, where `b` is the binary
174 /// representation of `idx`.
175 pub fn compute_beta_over_challenge_and_index(challenge: &[F], idx: usize) -> F {
176 let n = challenge.len();
177 challenge.iter().enumerate().fold(F::ONE, |acc, (i, x_i)| {
178 let mask = 1_usize << (n - 1 - i);
179 if idx & mask != 0 {
180 // i-th bit is on, multiply by `x_i`
181 acc * x_i
182 } else {
183 // i-th bit is off, multiply by `(1 - x_i)`
184 acc * (F::ONE - x_i)
185 }
186 })
187 }
188
189 /// Returns the full beta equality table as defined in \[Thaler13\], so over
190 /// `n` challenge points it returns a table of size `2^n`. This is when we
191 /// do still need the entire beta table. Essentially this is an MLE whose coefficients represent
192 /// whether the index is equal to the random challenge point.
193 pub fn new_beta_equality_mle(layer_claim_vars: Vec<F>) -> MultilinearExtension<F> {
194 MultilinearExtension::new(initialize_tensor(&layer_claim_vars))
195 }
196}