Source code for primitives.expression_bytecode.expressions_bin

"""Expression binary parser.

Faithful translation from:
- pil2-stark/src/starkpil/expressions_bin.hpp
- pil2-stark/src/starkpil/expressions_bin.cpp

Parses compiled expression bytecode from expressions.bin files.
The binary file contains:
1. Expression bytecode (operations and arguments)
2. Constraint bytecode (for debugging/verification)
3. Hints (for witness generation)

Key types referenced by OpType:
    Zi          -- inverse vanishing polynomial 1/Z_H(x) where Z_H(x) = x^N - 1
    xDivXSubXi  -- x/(x - xi), precomputed quotient for FRI opening
    xi          -- challenge evaluation point (random point from Fiat-Shamir)
"""

import struct
from dataclasses import dataclass, field
from enum import Enum
from pathlib import Path

import numpy as np

# Section IDs (expressions_bin.hpp lines 20-27)
[docs] EXPRESSIONS_SECTION = 1
[docs] CONSTRAINTS_SECTION = 2
[docs] HINTS_SECTION = 3
[docs] N_SECTIONS = 3
[docs] GLOBAL_CONSTRAINTS_SECTION = 1
[docs] GLOBAL_HINTS_SECTION = 2
[docs] N_GLOBAL_SECTIONS = 2
# C++: pil2-stark/src/starkpil/stark_info.hpp::opType (lines 30-49)
[docs] class OpType(Enum): """Operation argument types. Corresponds to C++ enum opType in stark_info.hpp (lines 30-49). These define the source/destination types for expression operands. """
[docs] const_ = 0 # Constant polynomial
[docs] cm = 1 # Committed polynomial
[docs] tmp = 2 # Temporary value
[docs] public_ = 3 # Public input
[docs] airgroupvalue = 4 # AIR group value
[docs] challenge = 5 # Fiat-Shamir challenge
[docs] number = 6 # Literal constant
[docs] string_ = 7 # String value
[docs] airvalue = 8 # AIR value
[docs] proofvalue = 9 # Proof value
[docs] custom = 10 # Custom commit
[docs] x = 11 # Evaluation point x
[docs] Zi = 12 # Zerofier value
[docs] eval = 13 # Evaluation
[docs] xDivXSubXi = 14 # x/(x-xi) precomputed
[docs] q = 15 # Quotient polynomial
[docs] f = 16 # FRI polynomial
# C++: No direct equivalent (C++ uses enum directly)
[docs] def optype_from_string(s: str) -> OpType: """Convert string to OpType enum.""" mapping = { "const": OpType.const_, "cm": OpType.cm, "tmp": OpType.tmp, "public": OpType.public_, "airgroupvalue": OpType.airgroupvalue, "challenge": OpType.challenge, "number": OpType.number, "string": OpType.string_, "airvalue": OpType.airvalue, "proofvalue": OpType.proofvalue, "custom": OpType.custom, "x": OpType.x, "Zi": OpType.Zi, "eval": OpType.eval, "xDivXSubXi": OpType.xDivXSubXi, "q": OpType.q, "f": OpType.f, } return mapping.get(s, OpType.number)
# C++: pil2-stark/src/starkpil/expressions_bin.hpp::ParserParams (lines 53-69) @dataclass
[docs] class ParserParams: """Parameters for a single expression. Corresponds to C++ struct ParserParams in expressions_bin.hpp (lines 53-69). Attributes: stage: Proof stage (0=custom, 1=trace, 2+=intermediate) exp_id: Expression ID in the expression database n_temp1: Number of scalar temporaries needed n_temp3: Number of field extension (dim=3) temporaries needed n_ops: Number of operations in this expression ops_offset: Offset into global ops array n_args: Number of arguments (8 per operation) args_offset: Offset into global args array first_row: First valid row for cyclic constraints last_row: Last valid row for cyclic constraints dest_dim: Destination dimension (1 or 3) dest_id: Destination identifier im_pol: Is intermediate polynomial line: Source code line (for debugging) """
[docs] stage: int = 0
[docs] exp_id: int = 0
[docs] n_temp1: int = 0
[docs] n_temp3: int = 0
[docs] n_ops: int = 0
[docs] ops_offset: int = 0
[docs] n_args: int = 0
[docs] args_offset: int = 0
[docs] first_row: int = 0
[docs] last_row: int = 0
[docs] dest_dim: int = 0
[docs] dest_id: int = 0
[docs] im_pol: bool = False
[docs] line: str = ""
# C++: pil2-stark/src/starkpil/expressions_bin.hpp::ParserArgs (lines 71-77) @dataclass
[docs] class ParserArgs: """Global bytecode and constants. Corresponds to C++ struct ParserArgs in expressions_bin.hpp (lines 71-77). Attributes: ops: Operation codes (uint8). Each value is 0-2: 0 = dim1 x dim1 -> dim1 (scalar operation) 1 = dim3 x dim1 -> dim3 (field extension x scalar) 2 = dim3 x dim3 -> dim3 (field extension x field extension) args: Operation arguments (uint16). 8 values per operation: [0] = operation type (0=add, 1=sub, 2=mul, 3=sub_swap) [1] = destination temp index [2] = source A type (OpType value or temp buffer index) [3] = source A column/index [4] = source A stride index (for opening points) [5] = source B type [6] = source B column/index [7] = source B stride index numbers: Literal constants (Goldilocks field elements as uint64) n_numbers: Number of literal constants """
[docs] ops: np.ndarray = field(default_factory=lambda: np.array([], dtype=np.uint8))
[docs] args: np.ndarray = field(default_factory=lambda: np.array([], dtype=np.uint16))
[docs] numbers: np.ndarray = field(default_factory=lambda: np.array([], dtype=np.uint64))
[docs] n_numbers: int = 0
# C++: pil2-stark/src/starkpil/expressions_bin.hpp::HintFieldValue (lines 30-39) @dataclass
[docs] class HintFieldValue: """Hint field value. Corresponds to C++ struct HintFieldValue in expressions_bin.hpp (lines 30-39). """
[docs] operand: OpType = OpType.number
[docs] id: int = 0
[docs] commit_id: int = 0
[docs] row_offset_index: int = 0
[docs] dim: int = 0
[docs] value: int = 0
[docs] string_value: str = ""
[docs] pos: list[int] = field(default_factory=list)
# C++: pil2-stark/src/starkpil/expressions_bin.hpp::HintField (lines 41-44) @dataclass
[docs] class HintField: """Hint field. Corresponds to C++ struct HintField in expressions_bin.hpp (lines 41-44). """
[docs] name: str = ""
[docs] values: list[HintFieldValue] = field(default_factory=list)
# C++: pil2-stark/src/starkpil/expressions_bin.hpp::Hint (lines 47-51) @dataclass
[docs] class Hint: """Hint for witness generation. Corresponds to C++ struct Hint in expressions_bin.hpp (lines 47-51). """
[docs] name: str = ""
[docs] fields: list[HintField] = field(default_factory=list)
# C++: No direct equivalent (C++ uses direct file I/O in expressions_bin.cpp)
[docs] class BinFileReader: """Binary file reader with little-endian decoding. Mimics C++ BinFileUtils::BinFile interface for reading. Corresponds to C++ BinFile constructor in binfile_utils.cpp (lines 63-131). """ # C++: Inline file reading in ExpressionsBin::load methods def __init__(self, file_path: str) -> None: """Open binary file for reading. Args: file_path: Path to .bin file Raises: ValueError: If file format is invalid """
[docs] self.path = Path(file_path)
with open(self.path, 'rb') as f: self.data = f.read()
[docs] self.pos = 0
[docs] self.reading_section = None
[docs] self.sections = {} # Map from section_id to list of (start, size) tuples
# Parse header - magic "chps" (4 bytes) magic = self.data[0:4] if magic != b'chps': raise ValueError(f"Invalid magic: expected b'chps', got {magic}") self.pos = 4 # Read version (uint32) version = self.read_u32_le() if version > 1: raise ValueError(f"Unsupported version: expected <=1, got {version}") # Read number of sections (uint32)
[docs] self.n_sections = self.read_u32_le()
# Parse section table of contents for _ in range(self.n_sections): section_type = self.read_u32_le() section_size = self.read_u64_le() # Store section metadata (offset and size) if section_type not in self.sections: self.sections[section_type] = [] self.sections[section_type].append((self.pos, section_size)) # Advance past section data self.pos += section_size # Reset position for section reading self.pos = 0 # C++: Inline file reading in ExpressionsBin::load methods
[docs] def read_bytes(self, n: int) -> bytes: """Read n raw bytes.""" result = self.data[self.pos:self.pos + n] self.pos += n return result
# C++: Inline file reading in ExpressionsBin::load methods
[docs] def read_u8_le(self) -> int: """Read uint8 little-endian.""" val = struct.unpack('<B', self.data[self.pos:self.pos + 1])[0] self.pos += 1 return val
# C++: Inline file reading in ExpressionsBin::load methods
[docs] def read_u16_le(self) -> int: """Read uint16 little-endian.""" val = struct.unpack('<H', self.data[self.pos:self.pos + 2])[0] self.pos += 2 return val
# C++: Inline file reading in ExpressionsBin::load methods
[docs] def read_u32_le(self) -> int: """Read uint32 little-endian.""" val = struct.unpack('<I', self.data[self.pos:self.pos + 4])[0] self.pos += 4 return val
# C++: Inline file reading in ExpressionsBin::load methods
[docs] def read_u64_le(self) -> int: """Read uint64 little-endian.""" val = struct.unpack('<Q', self.data[self.pos:self.pos + 8])[0] self.pos += 8 return val
# C++: Inline file reading in ExpressionsBin::load methods
[docs] def read_string(self) -> str: """Read null-terminated string. Corresponds to C++ BinFile::readString() which reads until null byte. """ # Find null terminator start = self.pos while self.pos < len(self.data) and self.data[self.pos] != 0: self.pos += 1 # Extract string bytes (excluding null terminator) s_bytes = self.data[start:self.pos] # Skip null terminator if self.pos < len(self.data): self.pos += 1 # Decode UTF-8 return s_bytes.decode('utf-8')
# C++: Section reading in expressions_bin.cpp
[docs] def start_read_section(self, section_id: int, section_pos: int = 0) -> None: """Start reading a section. Corresponds to C++ BinFile::startReadSection() (lines 138-157). Args: section_id: Section identifier section_pos: Section instance index (default 0) Raises: ValueError: If section doesn't exist """ if section_id not in self.sections: raise ValueError(f"Section {section_id} does not exist") if section_pos >= len(self.sections[section_id]): raise ValueError( f"Section pos {section_pos} out of range. " f"Section {section_id} has {len(self.sections[section_id])} instances." ) # Set current position to start of section data section_start, section_size = self.sections[section_id][section_pos] self.pos = section_start self.section_start = section_start self.section_end = section_start + section_size self.reading_section = section_id
# C++: Section reading in expressions_bin.cpp
[docs] def end_read_section(self, check: bool = True) -> None: """End reading a section. Corresponds to C++ BinFile::endReadSection() (lines 159-166). Args: check: If True, verify we read exactly section_size bytes """ if check and self.reading_section is not None: if self.pos != self.section_end: raise ValueError( f"Section size mismatch: read {self.pos - self.section_start} bytes, " f"expected {self.section_end - self.section_start}" ) self.reading_section = None
# --- Hint Parsing Helpers --- def _parse_hint_field_value(reader: BinFileReader) -> HintFieldValue: """Parse a single HintFieldValue from the binary reader. Reads the operand type and its associated data (id, dim, value, etc.) depending on the operand kind. """ hfv = HintFieldValue() operand_str = reader.read_string() hfv.operand = optype_from_string(operand_str) if hfv.operand == OpType.number: hfv.value = reader.read_u64_le() elif hfv.operand == OpType.string_: hfv.string_value = reader.read_string() else: hfv.id = reader.read_u32_le() if hfv.operand in [OpType.custom, OpType.const_, OpType.cm]: hfv.row_offset_index = reader.read_u32_le() if hfv.operand == OpType.tmp: hfv.dim = reader.read_u32_le() if hfv.operand == OpType.custom: hfv.commit_id = reader.read_u32_le() n_pos = reader.read_u32_le() for _ in range(n_pos): hfv.pos.append(reader.read_u32_le()) return hfv def _parse_hint_field(reader: BinFileReader) -> HintField: """Parse a single HintField (name + list of values) from the binary reader.""" hf = HintField() hf.name = reader.read_string() n_values = reader.read_u32_le() for _ in range(n_values): hf.values.append(_parse_hint_field_value(reader)) return hf def _parse_hint(reader: BinFileReader) -> Hint: """Parse a single Hint (name + list of fields) from the binary reader.""" hint = Hint() hint.name = reader.read_string() n_fields = reader.read_u32_le() for _ in range(n_fields): hint.fields.append(_parse_hint_field(reader)) return hint def _parse_global_hint_field_value(reader: BinFileReader) -> HintFieldValue: """Parse a HintFieldValue from a global hints section. Global hints have a different format than per-AIR hints: they use explicit dim fields for airgroupvalue/airvalue and restrict the set of allowed operand types. """ hfv = HintFieldValue() operand_str = reader.read_string() hfv.operand = optype_from_string(operand_str) if hfv.operand == OpType.number: hfv.value = reader.read_u64_le() elif hfv.operand == OpType.string_: hfv.string_value = reader.read_string() elif hfv.operand in [OpType.airgroupvalue, OpType.airvalue]: hfv.dim = reader.read_u32_le() hfv.id = reader.read_u32_le() elif hfv.operand in [OpType.tmp, OpType.public_, OpType.proofvalue]: hfv.id = reader.read_u32_le() else: raise ValueError(f"Invalid operand type in global hints: {operand_str}") n_pos = reader.read_u32_le() for _ in range(n_pos): hfv.pos.append(reader.read_u32_le()) return hfv def _parse_global_hint_field(reader: BinFileReader) -> HintField: """Parse a HintField from a global hints section.""" hf = HintField() hf.name = reader.read_string() n_values = reader.read_u32_le() for _ in range(n_values): hf.values.append(_parse_global_hint_field_value(reader)) return hf def _parse_global_hint(reader: BinFileReader) -> Hint: """Parse a Hint from a global hints section.""" hint = Hint() hint.name = reader.read_string() n_fields = reader.read_u32_le() for _ in range(n_fields): hint.fields.append(_parse_global_hint_field(reader)) return hint # C++: pil2-stark/src/starkpil/expressions_bin.hpp::ExpressionsBin (lines 79-145)
[docs] class ExpressionsBin: """Compiled expression database. Corresponds to C++ class ExpressionsBin in expressions_bin.hpp (lines 79-145). This class loads and manages compiled expression bytecode from .bin files. The bytecode represents arithmetic constraint expressions compiled into a stack-based operation sequence. """ # C++: ExpressionsBin constructor def __init__(self) -> None: """Initialize empty expressions database."""
[docs] self.n_ops_total: int = 0
[docs] self.n_args_total: int = 0
# Expression metadata indexed by expression ID
[docs] self.expressions_info: dict[int, ParserParams] = {}
# Constraint metadata (for debugging)
[docs] self.constraints_info_debug: list[ParserParams] = []
# Hints for witness generation
[docs] self.hints: list[Hint] = []
# Global bytecode for expressions
[docs] self.expressions_bin_args_expressions = ParserArgs()
# Global bytecode for constraints
[docs] self.expressions_bin_args_constraints = ParserArgs()
# Maximum temporary storage needed
[docs] self.max_tmp1: int = 0
[docs] self.max_tmp3: int = 0
[docs] self.max_args: int = 0
[docs] self.max_ops: int = 0
# C++: ExpressionsBin::load (expressions_bin.cpp) @classmethod
[docs] def from_file(cls, file_path: str, global_bin: bool = False, verifier_bin: bool = False) -> 'ExpressionsBin': """Load ExpressionsBin from binary file. Corresponds to C++ constructor (lines 3-13 of expressions_bin.cpp). Args: file_path: Path to .bin file global_bin: Load as global constraints binary verifier_bin: Load as verifier binary Returns: Loaded ExpressionsBin instance """ expr_bin = cls() reader = BinFileReader(file_path) if global_bin: expr_bin._load_global_bin(reader) elif verifier_bin: expr_bin._load_verifier_bin(reader) else: expr_bin._load_expressions_bin(reader) return expr_bin
# C++: ExpressionsBin::loadExpressionsBin def _load_expressions_bin(self, reader: BinFileReader) -> None: """Load expressions binary file. Corresponds to C++ ExpressionsBin::loadExpressionsBin() (lines 364-526 of expressions_bin.cpp). File format: - Section 1 (EXPRESSIONS_SECTION): Expression bytecode - Section 2 (CONSTRAINTS_SECTION): Constraint bytecode - Section 3 (HINTS_SECTION): Witness generation hints """ # Load expressions section (lines 365-419) reader.start_read_section(EXPRESSIONS_SECTION) self.max_tmp1 = reader.read_u32_le() self.max_tmp3 = reader.read_u32_le() self.max_args = reader.read_u32_le() self.max_ops = reader.read_u32_le() n_ops_expressions = reader.read_u32_le() self.n_ops_total = n_ops_expressions n_args_expressions = reader.read_u32_le() self.n_args_total = n_args_expressions n_numbers_expressions = reader.read_u32_le() n_expressions = reader.read_u32_le() # Read expression metadata (lines 385-407) for i in range(n_expressions): params = ParserParams() exp_id = reader.read_u32_le() params.exp_id = exp_id params.dest_dim = reader.read_u32_le() params.dest_id = reader.read_u32_le() params.stage = reader.read_u32_le() params.n_temp1 = reader.read_u32_le() params.n_temp3 = reader.read_u32_le() params.n_ops = reader.read_u32_le() params.ops_offset = reader.read_u32_le() params.n_args = reader.read_u32_le() params.args_offset = reader.read_u32_le() params.line = reader.read_string() self.expressions_info[exp_id] = params # Read bytecode arrays (lines 409-417) ops = np.zeros(n_ops_expressions, dtype=np.uint8) for j in range(n_ops_expressions): ops[j] = reader.read_u8_le() self.expressions_bin_args_expressions.ops = ops args = np.zeros(n_args_expressions, dtype=np.uint16) for j in range(n_args_expressions): args[j] = reader.read_u16_le() self.expressions_bin_args_expressions.args = args numbers = np.zeros(n_numbers_expressions, dtype=np.uint64) for j in range(n_numbers_expressions): numbers[j] = reader.read_u64_le() self.expressions_bin_args_expressions.numbers = numbers self.expressions_bin_args_expressions.n_numbers = n_numbers_expressions reader.end_read_section() # Load constraints section (lines 420-472) reader.start_read_section(CONSTRAINTS_SECTION) n_ops_debug = reader.read_u32_le() n_args_debug = reader.read_u32_le() n_numbers_debug = reader.read_u32_le() n_constraints = reader.read_u32_le() # Read constraint metadata (lines 433-459) for i in range(n_constraints): params = ParserParams() params.stage = reader.read_u32_le() params.exp_id = 0 params.dest_dim = reader.read_u32_le() params.dest_id = reader.read_u32_le() params.first_row = reader.read_u32_le() params.last_row = reader.read_u32_le() params.n_temp1 = reader.read_u32_le() params.n_temp3 = reader.read_u32_le() params.n_ops = reader.read_u32_le() params.ops_offset = reader.read_u32_le() params.n_args = reader.read_u32_le() params.args_offset = reader.read_u32_le() params.im_pol = bool(reader.read_u32_le()) params.line = reader.read_string() self.constraints_info_debug.append(params) # Read constraint bytecode (lines 462-470) ops_debug = np.zeros(n_ops_debug, dtype=np.uint8) for j in range(n_ops_debug): ops_debug[j] = reader.read_u8_le() self.expressions_bin_args_constraints.ops = ops_debug args_debug = np.zeros(n_args_debug, dtype=np.uint16) for j in range(n_args_debug): args_debug[j] = reader.read_u16_le() self.expressions_bin_args_constraints.args = args_debug numbers_debug = np.zeros(n_numbers_debug, dtype=np.uint64) for j in range(n_numbers_debug): numbers_debug[j] = reader.read_u64_le() self.expressions_bin_args_constraints.numbers = numbers_debug self.expressions_bin_args_constraints.n_numbers = n_numbers_debug reader.end_read_section() # Load hints section (lines 473-525) reader.start_read_section(HINTS_SECTION) n_hints = reader.read_u32_le() for _ in range(n_hints): self.hints.append(_parse_hint(reader)) reader.end_read_section() # C++: ExpressionsBin::loadVerifierBin def _load_verifier_bin(self, reader: BinFileReader) -> None: """Load verifier binary file. Corresponds to C++ ExpressionsBin::loadVerifierBin() (lines 528-582 of expressions_bin.cpp). Verifier binary only contains expressions section (no constraints or hints). """ reader.start_read_section(EXPRESSIONS_SECTION) self.max_tmp1 = reader.read_u32_le() self.max_tmp3 = reader.read_u32_le() self.max_args = reader.read_u32_le() self.max_ops = reader.read_u32_le() n_ops_expressions = reader.read_u32_le() self.n_ops_total = n_ops_expressions n_args_expressions = reader.read_u32_le() self.n_args_total = n_args_expressions n_numbers_expressions = reader.read_u32_le() n_expressions = reader.read_u32_le() # Read expression metadata for i in range(n_expressions): params = ParserParams() exp_id = reader.read_u32_le() params.exp_id = exp_id params.dest_dim = reader.read_u32_le() params.dest_id = reader.read_u32_le() params.stage = reader.read_u32_le() params.n_temp1 = reader.read_u32_le() params.n_temp3 = reader.read_u32_le() params.n_ops = reader.read_u32_le() params.ops_offset = reader.read_u32_le() params.n_args = reader.read_u32_le() params.args_offset = reader.read_u32_le() params.line = reader.read_string() self.expressions_info[exp_id] = params # Read bytecode arrays ops = np.zeros(n_ops_expressions, dtype=np.uint8) for j in range(n_ops_expressions): ops[j] = reader.read_u8_le() self.expressions_bin_args_expressions.ops = ops args = np.zeros(n_args_expressions, dtype=np.uint16) for j in range(n_args_expressions): args[j] = reader.read_u16_le() self.expressions_bin_args_expressions.args = args numbers = np.zeros(n_numbers_expressions, dtype=np.uint64) for j in range(n_numbers_expressions): numbers[j] = reader.read_u64_le() self.expressions_bin_args_expressions.numbers = numbers self.expressions_bin_args_expressions.n_numbers = n_numbers_expressions reader.end_read_section() # C++: ExpressionsBin::loadGlobalBin def _load_global_bin(self, reader: BinFileReader) -> None: """Load global binary file. Corresponds to C++ ExpressionsBin::loadGlobalBin() (lines 584-682 of expressions_bin.cpp). Global binary contains global constraints and hints (no expressions). """ reader.start_read_section(GLOBAL_CONSTRAINTS_SECTION) n_ops_debug = reader.read_u32_le() n_args_debug = reader.read_u32_le() n_numbers_debug = reader.read_u32_le() n_global_constraints = reader.read_u32_le() # Read constraint metadata for i in range(n_global_constraints): params = ParserParams() params.dest_dim = reader.read_u32_le() params.dest_id = reader.read_u32_le() params.n_temp1 = reader.read_u32_le() params.n_temp3 = reader.read_u32_le() params.n_ops = reader.read_u32_le() params.ops_offset = reader.read_u32_le() params.n_args = reader.read_u32_le() params.args_offset = reader.read_u32_le() params.line = reader.read_string() self.constraints_info_debug.append(params) # Read bytecode ops_debug = np.zeros(n_ops_debug, dtype=np.uint8) for j in range(n_ops_debug): ops_debug[j] = reader.read_u8_le() self.expressions_bin_args_constraints.ops = ops_debug args_debug = np.zeros(n_args_debug, dtype=np.uint16) for j in range(n_args_debug): args_debug[j] = reader.read_u16_le() self.expressions_bin_args_constraints.args = args_debug numbers_debug = np.zeros(n_numbers_debug, dtype=np.uint64) for j in range(n_numbers_debug): numbers_debug[j] = reader.read_u64_le() self.expressions_bin_args_constraints.numbers = numbers_debug self.expressions_bin_args_constraints.n_numbers = n_numbers_debug reader.end_read_section() # Load global hints section reader.start_read_section(GLOBAL_HINTS_SECTION) n_hints = reader.read_u32_le() for _ in range(n_hints): self.hints.append(_parse_global_hint(reader)) reader.end_read_section() # C++: ExpressionsBin::getExpression
[docs] def get_expression(self, exp_id: int) -> ParserParams: """Get expression parameters by ID. Args: exp_id: Expression ID Returns: ParserParams for the expression Raises: KeyError: If expression ID not found """ return self.expressions_info[exp_id]
# C++: ExpressionsBin::getHintIdsByName
[docs] def get_hint_ids_by_name(self, name: str) -> list[int]: """Get hint indices by name. Corresponds to C++ ExpressionsBin::getHintIdsByName() (lines 684-691 of expressions_bin.cpp). Args: name: Hint name to search for Returns: List of hint indices with matching name """ hint_ids = [] for i, hint in enumerate(self.hints): if hint.name == name: hint_ids.append(i) return hint_ids
# C++: ExpressionsBin::getNumberHintIdsByName
[docs] def get_number_hint_ids_by_name(self, name: str) -> int: """Get count of hints by name. Corresponds to C++ ExpressionsBin::getNumberHintIdsByName() (lines 694-704 of expressions_bin.cpp). Args: name: Hint name to search for Returns: Number of hints with matching name """ return len(self.get_hint_ids_by_name(name))
# C++: ExpressionsBin::getHintField
[docs] def get_hint_field(self, hint_id: int, field_name: str) -> HintField: """Get a specific field from a hint by name. Args: hint_id: Index into self.hints field_name: Name of field (e.g., "numerator", "denominator", "reference") Returns: HintField containing the field values Raises: ValueError: If field not found in hint """ hint = self.hints[hint_id] for hf in hint.fields: if hf.name == field_name: return hf raise ValueError(f"Field '{field_name}' not found in hint '{hint.name}'")