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}