halo2_proofs/dev/failure/
emitter.rs

1use std::collections::BTreeMap;
2use std::iter;
3
4use group::ff::Field;
5
6use super::FailureLocation;
7use crate::{
8    dev::{metadata, util},
9    plonk::{Any, Expression},
10};
11
12fn padded(p: char, width: usize, text: &str) -> String {
13    let pad = width - text.len();
14    format!(
15        "{}{}{}",
16        iter::repeat(p).take(pad - pad / 2).collect::<String>(),
17        text,
18        iter::repeat(p).take(pad / 2).collect::<String>(),
19    )
20}
21
22/// Renders a cell layout around a given failure location.
23///
24/// `highlight_row` is called at the end of each row, with the offset of the active row
25/// (if `location` is in a region), and the rotation of the current row relative to the
26/// active row.
27pub(super) fn render_cell_layout(
28    prefix: &str,
29    location: &FailureLocation,
30    columns: &BTreeMap<metadata::Column, usize>,
31    layout: &BTreeMap<i32, BTreeMap<metadata::Column, String>>,
32    highlight_row: impl Fn(Option<i32>, i32),
33) {
34    let col_width = |cells: usize| cells.to_string().len() + 3;
35
36    // If we are in a region, show rows at offsets relative to it. Otherwise, just show
37    // the rotations directly.
38    let offset = match location {
39        FailureLocation::InRegion { region, offset } => {
40            eprintln!("{}Cell layout in region '{}':", prefix, region.name);
41            eprint!("{}  | Offset |", prefix);
42            Some(*offset as i32)
43        }
44        FailureLocation::OutsideRegion { row } => {
45            eprintln!("{}Cell layout at row {}:", prefix, row);
46            eprint!("{}  |Rotation|", prefix);
47            None
48        }
49    };
50
51    // Print the assigned cells, and their region offset or rotation.
52    for (column, cells) in columns {
53        let width = col_width(*cells);
54        eprint!(
55            "{}|",
56            padded(
57                ' ',
58                width,
59                &format!(
60                    "{}{}",
61                    match column.column_type {
62                        Any::Advice => "A",
63                        Any::Fixed => "F",
64                        Any::Instance => "I",
65                    },
66                    column.index,
67                )
68            )
69        );
70    }
71    eprintln!();
72    eprint!("{}  +--------+", prefix);
73    for cells in columns.values() {
74        eprint!("{}+", padded('-', col_width(*cells), ""));
75    }
76    eprintln!();
77    for (rotation, row) in layout {
78        eprint!(
79            "{}  |{}|",
80            prefix,
81            padded(' ', 8, &(offset.unwrap_or(0) + rotation).to_string())
82        );
83        for (col, cells) in columns {
84            let width = col_width(*cells);
85            eprint!(
86                "{}|",
87                padded(
88                    ' ',
89                    width,
90                    row.get(col).map(|s| s.as_str()).unwrap_or_default()
91                )
92            );
93        }
94        highlight_row(offset, *rotation);
95        eprintln!();
96    }
97}
98
99pub(super) fn expression_to_string<F: Field>(
100    expr: &Expression<F>,
101    layout: &BTreeMap<i32, BTreeMap<metadata::Column, String>>,
102) -> String {
103    expr.evaluate(
104        &util::format_value,
105        &|_| panic!("virtual selectors are removed during optimization"),
106        &|query, column, rotation| {
107            if let Some(label) = layout
108                .get(&rotation.0)
109                .and_then(|row| row.get(&(Any::Fixed, column).into()))
110            {
111                label.clone()
112            } else if rotation.0 == 0 {
113                // This is most likely a merged selector
114                format!("S{}", query)
115            } else {
116                // No idea how we'd get here...
117                format!("F{}@{}", column, rotation.0)
118            }
119        },
120        &|_, column, rotation| {
121            layout
122                .get(&rotation.0)
123                .unwrap()
124                .get(&(Any::Advice, column).into())
125                .unwrap()
126                .clone()
127        },
128        &|_, column, rotation| {
129            layout
130                .get(&rotation.0)
131                .unwrap()
132                .get(&(Any::Instance, column).into())
133                .unwrap()
134                .clone()
135        },
136        &|a| {
137            if a.contains(' ') {
138                format!("-({})", a)
139            } else {
140                format!("-{}", a)
141            }
142        },
143        &|a, b| {
144            if let Some(b) = b.strip_prefix('-') {
145                format!("{} - {}", a, b)
146            } else {
147                format!("{} + {}", a, b)
148            }
149        },
150        &|a, b| match (a.contains(' '), b.contains(' ')) {
151            (false, false) => format!("{} * {}", a, b),
152            (false, true) => format!("{} * ({})", a, b),
153            (true, false) => format!("({}) * {}", a, b),
154            (true, true) => format!("({}) * ({})", a, b),
155        },
156        &|a, s| {
157            if a.contains(' ') {
158                format!("({}) * {}", a, util::format_value(s))
159            } else {
160                format!("{} * {}", a, util::format_value(s))
161            }
162        },
163    )
164}