# Comment evaluation corpus -- rust-pybamm-doc, needs labels
#
# Real own-line comments with the code that follows. Mark every `verdict:` as:
#
#   slop  - should not exist: restates the code, narrates an edit, labels a
#           section, leaks process, records history, or is a long explanation
#           where a short one would do
#   keep  - deleting it would lose a fact not recoverable from the code
#   skip  - genuinely cannot tell without more context
#
# Leave `?` on anything you do not reach; partial labelling still scores.
#
# Nothing here reveals which rule (if any) fires on a case, or how the case
# was sampled.

### 1  pybamm-core/src/adjoint.rs:1
# Reverse-mode (VJP) assembly of a dense scalar Jacobian row.
#
# A wide scalar row split out of the column coloring is filled by one backward
# pass over its primal sub-expression instead of one forward JVP sweep per
# column. The sub-expression is compiled with a no-reuse (SSA) slot layout
# ([`CompiledExpr::new_pinned`]), so after one primal evaluation the scratch
# buffer holds every intermediate; each instruction's operand slots are then
# stable value-tape offsets the adjoint reads directly. The backward `match`
# over [`Instruction`] is exhaustive: every op has an adjoint, so there is no
# runtime fallback.
#
# The pinned layout forms branch blocks like every other layout, so the
# backward walk jumps over the blocks of inactive conditional branches instead
# of replaying adjoints that are all no-ops.
    use crate::arena::{Arena, NodeId};
    use crate::branch_regions::{active_branch, dispatch_span_end};
    use crate::eval::{
verdict: ?

### 2  pybamm-core/src/branch_regions.rs:212
# Clone subgraphs shared between a strict subset of one conditional's branches
# so each sharing branch owns its own copy.
#
# Returns `None` when nothing needs cloning, meaning there is no conditional or
# every shared cone node is `Common` or shared by *all* branches. Also returns
# `None` when the projected clone count would exceed the clone budget. `None` is
# only slower, never wrong: the caller lowers the unprivatised arena.
#
# Must run **after** `cse`, which created the sharing, and immediately before
# lowering, which is why `IRBuilder` calls it.
    pub fn privatise_conditionals(arena: &Arena, root: NodeId) -> Option<(Arena, NodeId)> {
    if !has_multi_branch_conditional(arena) {
    return None;
    }
verdict: ?

### 3  pybamm-core/src/branch_regions.rs:290
# Rebuild `node` against the privatised copies, reading each child from
# `branch`'s copy where one exists.
#
# `Conditional` is the one node whose child *positions* carry per-branch
# meaning: `cse` can alias two branch slots onto the same node, and each slot
# must still resolve to its own branch's clone. Every other node's children
# inherit the consumer's own branch, so one uniform closure suffices.
#
# A slot index is unambiguous even though [`resolve_child`] keys clones by index
# alone: a node needed by two conditionals is `Ownership::Common`, which is not
# privatisable.
    fn remap_children(
    node: &Node,
    branch: Option<u32>,
    common: &[Option<NodeId>],
verdict: ?

### 4  pybamm-core/src/branch_regions.rs:333
# Index in [`RegionSchedule::order`] where this group's block nodes start.
    pub anchor: usize,
    pub branch_lens: Vec<usize>,
    }
verdict: ?

### 5  pybamm-core/src/eval_batch.rs:661
# Evaluate the tape for `k` lanes (time points) at once.
#
# `scratch` must hold at least `scratch_len() * k` elements. Slot `s` occupies
# `[s*k, (s + slot_len)*k)`, and element `e` of it holds its `k` lane values
# contiguously at `[(s+e)*k, (s+e+1)*k)`. `ts` supplies the `k` time values,
# `y_cols` is the `(n_states, k)` F-contiguous state matrix, and `inputs` is
# shared across lanes. Returns the root slot as `(out_len, k)` lane-minor:
# element `e`, lane `l` at relative index `e*k + l`.
#
# Results are bitwise identical to `k` independent [`eval`](Self::eval) calls.
# Primal-only: a tangent or state-derivative load returns [`BatchEvalError`].
    pub fn eval_batch<'s>(
    &self,
    scratch: &'s mut [f64],
    k: usize,
verdict: ?

### 6  pybamm-core/src/ffi.rs:57
# ABI contract version for the Rust FFI surface.
#
# Bump by 1 whenever ANY exported signature changes,
# arg/return types, or a function added, removed, or reordered. The C++
# consumer pins the expected value in `PYBAMM_RUST_ABI_VERSION`, and the
# drift test asserts the two are equal.
#
# `pybamm_rust_abi_version` itself must forever keep the signature `-> u32` with no
# arguments: it is the probe the C++ consumer calls to read this version, so it
# cannot follow the bump rule it enforces.
    pub const RUST_ABI_VERSION: u32 = 1;
verdict: ?

### 7  pybamm-core/src/ffi.rs:704
# Get the number of forward-sensitivity parameters configured on the model.
#
# # Safety
#
# - `user_data` must point to a valid `ModelEvaluator` instance
#
# # Returns
#
# - Number of sensitivity parameters on success (>= 0)
# - `ERROR_NULL_POINTER` (-1) if `user_data` is null
# - `ERROR_PANIC` (-2) if a Rust panic occurred
    pub unsafe extern "C" fn pybamm_rust_n_sens_params(user_data: *const c_void) -> c_int {
    unsafe { with_model(user_data, [], |model| model.n_sens_params() as c_int) }
    }
verdict: ?

### 8  pybamm-core/src/ir.rs:371
# Flat per-cell power-basis tensors: cell-major, `order^ndim` coeffs per cell.
    pub coeffs: Vec<f64>,
    pub order: u32,
    }
verdict: ?

### 9  pybamm-core/src/ir.rs:509
# Constant pool for large data
    consts: ConstPool,
    n_states: usize,
    n_params: usize,
    uses_state_dot: bool,
verdict: ?

### 10  pybamm-core/src/ir.rs:991
# Assert that no instruction outside a branch block reads a value a block
# defined. This is the one miscompilation this plan produces silently, where
# the reader sees whatever a previous solve left in the recycled slot. Runs
# once per compile, in release too.
#
# Tracks the *last writer* of each buffer element, not set membership:
# recycling makes "something outside also writes here" routinely true.
# Extents are element-wise, so a partial overlap still trips. A `Dispatch`
# selector that is never written has no writer to blame, so its definition
# is asserted directly.
#
# # Panics
# Panics if an outside instruction's read resolves to a block-owned
# definition, naming the reader, the element and the defining instruction,
# or if a `Dispatch`'s selector has no earlier definition.
    fn assert_block_slots_private(&self, buffer_size: usize) {
    if self.consts.branch_blocks.is_empty() {
    return;
    }
verdict: ?

### 11  pybamm-core/src/ir.rs:1362
# Hand-build a tape that violates `assert_block_slots_private`: an
# instruction outside the one `Dispatch` block reads the slot that
# block's only instruction wrote. No real scheduler produces this; it
# exists to exercise the guard, which otherwise ships untested.
    fn test_tape_reading_across_block_boundary() -> Self {
    let mut consts = ConstPool::new();
    consts.branch_blocks.push((1, 1)); // one block: 1 instruction, starting right after the Dispatch
    let instructions = vec![
verdict: ?

### 12  pybamm-core/src/jacobian.rs:32
# Which inputs a Jacobian differentiates against; the same two cases the
# tangent transform seeds, so the enum is shared rather than duplicated.
    pub use crate::tangent::DiffTarget;
verdict: ?

### 13  pybamm-core/src/jacobian.rs:217
# As [`Self::new_wrt_states`], sweeping every entry rather than lifting the
# constant ones out. The reference path the exactness tests compare against.
    pub fn new_wrt_states_unsplit(
    arena: &Arena,
    root: NodeId,
    n_rows: usize,
verdict: ?

### 14  pybamm-core/src/jacobian.rs:1034
# `n` x `n` identity-like pattern: one nonzero per row, on the diagonal.
    fn make_diagonal_pattern_local(n: usize) -> SparsityPattern {
    let mut pattern = SparsityPattern::new(n, n);
    for row in 0..n {
    pattern.indptr[row] = row;
verdict: ?

### 15  pybamm-core/src/model.rs:1
# `CompiledModel` for DAE system evaluation.
#
# `PyBaMM` models are DAE systems of the form `M * y' = f(t, y)` where `M` is
# a constant mass matrix (often singular), `f(t, y)` is the right-hand side,
# and `y` is the state vector. Newton iteration computes `J = df/dy - cj * M`.
#
# `CompiledModel` holds the primal evaluator, symbolic `df/dy`, mass matrix,
# and sparsity/coloring information for efficient Jacobian assembly. It is
# immutable, so evaluation needs a `Workspace` alongside it; `ModelEvaluator`
# pairs the two into one `&mut self` handle for callers that want that.
    use std::sync::Arc;
    const JAC_LANE_SCRATCH_BUDGET: usize = 32 << 20;
verdict: ?

### 16  pybamm-core/src/model.rs:891
# Get the algebraic-state mask: `true` = algebraic, `false` = differential.
#
# A state `i` is classified algebraic when row `i` of the mass matrix has
# no diagonal entry, which matches `PyBaMM`'s convention.
    pub fn algebraic_ids(&self) -> &[bool] {
    &self.algebraic_ids
    }
verdict: keep

### 17  pybamm-core/src/model.rs:908
# Number of algebraic states (0 if no algebraic sub-block).
    pub const fn n_algebraic(&self) -> usize {
    self.n_algebraic
    }
verdict: ?

### 18  pybamm-core/src/model.rs:1154
# Compile and append an output-variable expression to this model.
#
# `node` must already exist in `arena`. The expression's output length
# is captured at compile time and is later available via `output_len_at`.
# Tangent graphs (dH/dp, dH/dy) are compiled alongside the primal.
    pub fn add_output(&mut self, arena: &Arena, node: NodeId) {
    let ir = TypedIr::from_arena(arena, node);
    let len = ir.output_len();
    self.output_lens.push(len);
verdict: ?

### 19  pybamm-core/src/model.rs:2309
# Length of output variable `var_idx`; see
# [`CompiledModel::output_len_at`] for the bound it panics on.
    pub fn output_len_at(&self, var_idx: usize) -> usize {
    self.compiled.output_len_at(var_idx)
    }
verdict: ?

### 20  pybamm-core/src/model.rs:2358
# CSR pattern of the assembled Jacobian `df/dy - cj*M`, which is the union
# of the `df/dy` and mass patterns.
    pub fn sparsity(&self) -> &SparsityPattern {
    self.compiled.sparsity()
    }
verdict: ?

### 21  pybamm-core/src/model.rs:3043
# A tail narrower than the lane width takes its own narrower walk, so the
# block loop must still reproduce the unbatched tape column for column.
    fn a_narrow_tail_block_matches_the_unbatched_tape() {
verdict: ?

### 22  pybamm-core/src/node.rs:1
# The expression vocabulary Python hands over.
#
# [`Node`] is one DAG node: an operator referencing its children by
# [`NodeId`], a leaf reading state or parameters, or a literal
# carrying its own data (dense arrays, CSR matrices, 1-D and N-D interpolant
# tables). Everything downstream matches on this enum, from simplification
# through differentiation to lowering, so support for a new `PyBaMM` operator
# starts with a variant here.
#
# Nodes are shape-carrying but not shape-checked on construction;
# [`first_invalid`](crate::first_invalid) and
# [`first_unsupported`](crate::first_unsupported) report what lowering will
# reject.
    use crate::arena::NodeId;
    use crate::error::CoreError;
verdict: ?

### 23  pybamm-core/src/node.rs:335
# One node of the expression DAG.
#
# Children are [`NodeId`]s into the arena that owns this node, so a node is
# meaningless on its own and cheap to share. Values are `f64` vectors: the
# arithmetic and unary variants are element-wise with a scalar operand
# broadcasting against a vector, matching [`BinaryOp`](crate::BinaryOp) and
# [`UnaryOp`](crate::UnaryOp), which they lower to one-for-one.
#
# Variants marked internal are produced by differentiation rather than by
# Python, and a few carry `Box`ed payloads to keep the enum small enough that a
# DAG of them stays compact.
    pub enum Node {
    Scalar(f64),
    Array(Box<ArrayData>),
verdict: ?

### 24  pybamm-core/src/simplify.rs:209
# Recursively simplify a node, using memoization to avoid redundant work.
    fn simplify_node(
    arena: &mut Arena,
    id: NodeId,
    mode: SimplifyMode,
verdict: keep

### 25  pybamm-core/src/solver/batch.rs:1
# Solving many input sets concurrently.
#
# The one method here is a fan-out over [`PreparedSolver::solve`], never a
# second implementation of a solve. That is what makes a batch bit-identical to
# the serial loop by construction: one immutable tape shared through the `Arc`,
# one fresh `Workspace` minted inside each `solve()`, and rayon only choosing
# the order the independent calls run in.
#
# The [`SolveRequest`] is shared across the batch rather than per set, which
# matches the callers: `DiffsolSolver` computes one output grid for every set,
# `BaseSolver` refuses to run sets whose discontinuities differ, and the
# payload flags come from the model. Only [`InputSet`] varies per set.
#
# Scheduling is the caller's: these run on the ambient rayon pool, so a caller
# that wants a specific width wraps the call in `ThreadPool::install`.
    use rayon::prelude::*;
    use super::solve::{InputSet, PreparedSolver, SolveOutcome, SolveRequest};
    use crate::error::CoreError;
verdict: slop

### 26  pybamm-core/src/solver/equations.rs:27
# Local ODE-equations container implementing diffsol's public `OdeEquations`
# trait.
#
# diffsol mints an operator view per callback invocation — `eqn.rhs()` on every
# residual call, and again for every `nstates()` — so the views borrow from
# here instead of owning: a mint is a few pointer copies, and the compiler,
# not a refcount, keeps this alive for as long as a view exists.
#
# `Arc` marks the state shared with the `PreparedSolver` across solves, which
# is `Send + Sync`; everything built per solve is owned outright.
    pub struct Equations {
    pub(crate) compiled: Arc<CompiledModel>,
    pub(crate) ws: Rc<RefCell<Workspace>>,
    pub(crate) params: Vec<f64>,
verdict: ?

### 27  pybamm-core/src/solver/mod.rs:1
# In-process DAE solving through diffsol.
#
# diffsol drives a model through one operator trait per callback it needs, and
# this module supplies them from a [`ModelEvaluator`](crate::ModelEvaluator):
# `rhs` for `f(t, y; p)` and its Jacobian, `mass` for `M`, `init` for `y0`,
# `root` for events, `output` for observed variables, and `reset` as a required
# no-op. `equations` binds those into the `OdeEquations` diffsol consumes,
# `solve` owns problem setup and the solve loop, and `batch` fans that loop out
# over many input sets on rayon.
#
# The operators share a field vocabulary: `compiled` is the shared immutable
# [`CompiledModel`](crate::model::CompiledModel), `ws` the solve-local
# scratch, `inputs` this solve's parameter values, and `context` faer's
# allocator handle. Because `ws` is per solve, one solve's scratch is never
# visible to another.
#
# The functions here translate our sparsity patterns into faer's, which is the
# matrix backend diffsol is instantiated with throughout.
    pub mod batch;
    pub mod equations;
    pub mod init;
    pub mod linear;
verdict: ?

### 28  pybamm-core/src/solver/reset.rs:62
# Identity Jacobian-vector product.
    fn jac_mul_inplace(&self, _x: &FaerVec<f64>, _t: f64, v: &FaerVec<f64>, y: &mut FaerVec<f64>) {
    y.as_mut_slice().copy_from_slice(v.as_slice());
    }
    }
verdict: keep

### 29  pybamm-core/src/solver/solve.rs:1
# Problem setup and the solve loop.
#
# [`PreparedSolver`] holds everything reusable about a model, meaning the
# immutable compiled model, the converted sparsity patterns and the sizes, so
# repeated solves pay setup once. Each solve then builds its own workspace, its
# own diffsol `OdeSolverProblem` and its own diffsol instance, which is what
# keeps sequential solves independent and the prepared handle `Send + Sync`.
#
# What a solve carries back — states or output variables, with or without
# sensitivities — is asked for on [`SolveRequest`] and answered in one
# [`SolveOutcome`], so the payload combinations share one trajectory layout and
# one set of termination fields rather than a result type each.
    use std::cell::RefCell;
    use std::ops::Range;
    use std::rc::Rc;
    use std::sync::Arc;
verdict: slop

### 30  pybamm-core/src/solver/solve.rs:258
# Prepare-once/execute-many handle for repeated solves of the same model.
#
# Holds the shared immutable `CompiledModel`, the tolerances and integrator
# options, pre-converted sparsity patterns, and the system sizes. It is *not*
# a fully specified problem: `y0`, `t_eval` and the parameter vector all arrive
# per call, and each call to [`solve`](Self::solve) builds its own
# `Workspace`, its own diffsol `OdeSolverProblem` and its own diffsol solver
# from them. Carrying no
# per-solve mutable state is what makes this `Send + Sync` and safe to share
# across threads.
    pub struct PreparedSolver {
    compiled: Arc<CompiledModel>,
    rtol: f64,
    atol: Vec<f64>,
verdict: keep

### 31  pybamm-core/src/solver/solve.rs:312
# Build a prepared problem from a compiled model and tolerances.
#
# Performs all expensive one-time setup: sparsity conversion, size
# extraction, and wrapping the immutable compiled model in an `Arc`.
    pub fn new(model: ModelEvaluator, rtol: f64, atol: &[f64]) -> Result<Self, CoreError> {
    let n_states = model.n_states();
    if atol.len() != n_states {
    return Err(CoreError::AtolLength {
verdict: keep

### 32  pybamm-core/src/solver/solve.rs:1314
# The times a solve reports at, together with the stop times inside them.
#
# `stop` is a subset of `eval`: a stop time absent from the output grid cannot
# end a segment, because the segment's last output time is what the integrator
# is told to stop on.
    struct TimePlan<'a> {
    eval: &'a [f64],
    stop: &'a [f64],
    }
verdict: keep

### 33  pybamm-core/src/sparsity.rs:1
# Per-output sparsity of `d(root)/dy`, read off the DAG without evaluating it.
#
# Each node is annotated with the set of state indices its value can depend on,
# propagated bottom-up as bitsets; the sets reaching each element of the root
# become one row of a CSR [`SparsityPattern`]. Tracking outputs individually
# rather than unioning them is what makes coloring pay: a tridiagonal Jacobian
# keeps its three colors instead of collapsing to a dense union.
#
# The result is structural and conservative: an entry may be present and
# evaluate to zero, but a missing entry is zero for every `y`, which is the
# direction coloring and assembly depend on.
    use crate::arena::{Arena, NodeId};
    use crate::node::Node;
verdict: ?

### 34  pybamm-core/src/zero_propagate.rs:27
# Zero status from the lattice
    pub zero_status: ZeroStatus,
    }
    impl ShapeInfo {
    pub const fn new(len: usize, zero_status: ZeroStatus) -> Self {
verdict: ?

### 35  pybamm-core/tests/proptest_const_entries.rs:1
# Soundness of the constant-entry classifier, and of assembling on it.
#
# Two properties, both over random graphs at random states. First: an entry
# the classifier calls constant must equal what the tangent tape produces for
# that column, at every state — a wrong constant does not crash, it quietly
# degrades Newton convergence, so this is the property the whole scheme rests
# on. Second: assembling with the split on must reproduce the unsplit
# assembly entry for entry.
#
# Non-finite tape values are exempt from both: where a tape overflows, a term
# the fold drops as exactly zero evaluates to `inf * 0.0` and poisons the
# sweep, so the two legitimately disagree (see `const_entries`).
    mod common;
    use common::cases::{TangentCase, arb_split_eval_case, targeted_split_eval_cases};
    use proptest::prelude::*;
    use pybamm_core::const_entries::classify_constant_entries;
verdict: slop

### 36  pybamm-core/tests/test_branch_shortcircuit.rs:227
# Differential test between the two block-forming layouts, reuse slots
# (`from_arena`) and pinned SSA slots (`from_arena_pinned`), plus a closed-form
# oracle, on a graph mixing every hazard: a cone shared by a strict subset of
# branches, a `Common` node also read outside the conditional, a nested
# conditional, and a `SparseMatrix` inside a block.
#
# The worst failure mode is a node wrongly placed in a block whose value is read
# from outside. That gives a *different* wrong value per layout, so the two tapes
# disagree, and the oracle catches a mistake they share.
    fn scheduled_tape_matches_the_pinned_tape_and_a_closed_form_oracle() {
    let mut arena = Arena::new();
    let y = arena.alloc(Node::StateVector { start: 0, end: 3 });
    let sel = arena.alloc(Node::StateVector { start: 3, end: 4 });
verdict: slop

### 37  pybamm-core/tests/test_ffi_abi_contract.rs:279
# Every Rust FFI entry point returns a status (`SUCCESS` or a negative
# `ERROR_*`), and a panic caught at the boundary returns `ERROR_PANIC` without
# having written the output buffer. A call that drops the status therefore lets
# the previous step's stale data flow on into SUNDIALS as a valid evaluation.
#
# Enforced structurally: the consumer must route every call through
# `PYBAMM_RUST_CALL` (status-returning) or `PYBAMM_RUST_VALUE` (count-returning),
# both of which raise on a failure code. No raw `rust_ffi().x(...)` may remain.
    fn consumer_checks_every_ffi_return_status() {
    let src = fs::read_to_string(CONSUMER_IMPL)
    .unwrap_or_else(|e| panic!("cannot read {CONSUMER_IMPL}: {e}"));
    let unchecked: Vec<&str> = src
verdict: ?

### 38  pybamm-python/src/expr.rs:865
# Empty args tuple for pickle (zero-arg `#[new]`); explicit `PyTuple`
# because pyo3 maps a `()` return to Python `None`, which pickle rejects.
    fn __getnewargs__<'py>(&self, py: Python<'py>) -> Bound<'py, pyo3::types::PyTuple> {
    pyo3::types::PyTuple::empty(py)
    }
    }
verdict: ?

### 39  pybamm-python/src/solver.rs:451
# Solve the model over the given time span.
#
# `t_stop` holds the discontinuity times the integrator must land on
# exactly, restarting there; every one of them must also appear in
# `t_eval`, which is where the solution is reported.
#
# `outputs` reports the model's registered output variables instead of the
# full state, which cuts the FFI transfer when only a few variables are
# wanted, and `sensitivities` adds the forward-sensitivity blocks, seeded by
# `y0_sens` (flattened `dy0/dp`, column-major over the requested
# parameters). The two compose, so the four payload combinations are these
# two flags rather than four entry points.
    fn solve(
    &self,
verdict: keep

### 40  pybamm-python/src/solver.rs:490
# Solve every input set in `y0`/`inputs`, `num_threads` at a time.
#
# `y0` and `inputs` are C-contiguous 2-D arrays with one row per input set,
# and `y0_sens` one seed row per set; `t_eval`, `t_stop` and the payload
# flags are shared, as they already are on the callers. The returned list
# has one entry per row, in row order: a result object, or — for a set that
# failed — the exception *instance*, unraised, which is what keeps the
# failing set's identity that one collapsed error would lose.
#
# One `py.detach()` covers the batch, so Ctrl-C lands when it returns.
    fn solve_batch(
    &self,
    py: Python<'_>,
verdict: slop
