Python API

noodlelab.verify

Verifiable calculations in plain Python: units, uncertainty, requirements, checks and an audit trail, for people and for AI agents writing code.

This is noodlelab’s science without the editor. Everything here works in any script, notebook or test after pip install noodlelab:

import noodlelab.verify as nv

with nv.record("pendulum") as rec:                      # writes runs/<...>/provenance.json
    L = rec.input("L", "1.000 ± 0.002 m", source="tape measure, lab book p. 12")
    g = rec.input("g", "9.81 ± 0.02 m/s^2", source="local gravity survey")
    T = rec.result("period", nv.const.tau * (L / g) ** 0.5)  # (2.006 ± 0.003) s
    rec.require("PER-001 period <= 2.1 s [Analysis]  # The swing shall take at most 2.1 s")
    rec.verify("PER-001", T)                                # ✓ margin +0.094 s
assert rec.passed

The rules it helps you keep (and audit() checks afterwards):

  1. Every number has a unit. Values are Pint quantities; mixing dimensions raises instead of silently giving nonsense. Dimensionless results say so (unit="1"). Constants are named, never retyped: nv.const.c, rec.constant("g0") (see noodlelab.constants), with their unit, their source and, for measured ones such as G, their CODATA uncertainty.

  2. Every measured input has an uncertainty and a source. Uncertainty is propagated by the GUM’s law (first order, with correlations kept), and budget() says which input contributes most. monte_carlo() checks the linear result where the model is not linear (JCGM 101).

  3. Requirements are written down before they are checked, as text a person can read (COM-001 link_margin >= 3 dB [Analysis]), and each check reports its margin, not just pass or fail. An uncertain result whose margin is no larger than its expanded uncertainty U = k·u (k = 2, or from the degrees of freedom with coverage=0.95) is inconclusive, and does not pass: the design may meet the requirement, but the calculation cannot show it. A factor of safety (fs=1.5 in the line) adds the margin of safety, which must not be negative.

  4. Every run leaves a record: inputs, results, requirements, checks, the code (its hash and git commit), the files read and written (SHA-256) and the environment (Python, platform, package versions), so someone else can see exactly what was computed and repeat it.

noodlelab verify script.py runs a script, audits every record it wrote and exits non-zero when a check fails or is inconclusive, for CI and for agents checking their own work. Graphs from the editor are verified the same way (noodlelab verify analysis.graph.json).

class noodlelab.verify.Check(passed: 'bool', message: 'str' = '', requirement: 'str' = '', statement: 'str' = '', method: 'str' = '', required: 'str' = '', achieved: 'str' = '', margin: 'float | None' = None, unit: 'str' = '', margin_pct: 'float | None' = None, uncertainty: 'float | None' = None, inconclusive: 'bool' = False, margin_of_safety: 'float | None' = None, p_conform: 'float | None' = None, rule: 'str' = '')[source]
Parameters:
  • passed (bool)

  • message (str)

  • requirement (str)

  • statement (str)

  • method (str)

  • required (str)

  • achieved (str)

  • margin (float | None)

  • unit (str)

  • margin_pct (float | None)

  • uncertainty (float | None)

  • inconclusive (bool)

  • margin_of_safety (float | None)

  • p_conform (float | None)

  • rule (str)

passed: bool
message: str = ''
requirement: str = ''
statement: str = ''
method: str = ''
required: str = ''
achieved: str = ''
margin: float | None = None
unit: str = ''
margin_pct: float | None = None
uncertainty: float | None = None
inconclusive: bool = False
margin_of_safety: float | None = None
p_conform: float | None = None
rule: str = ''
property status: str

"passed", "failed" or "inconclusive".

classmethod from_dict(data, strict=False)[source]

A check from its saved fields (see details()). strict: a missing passed is an error, not a failed check.

Parameters:
  • data (dict[str, Any])

  • strict (bool)

Return type:

Check

details()[source]

The requirement fields that are set, for events and records. False is left out like an empty field.

Return type:

dict[str, Any]

class noodlelab.verify.DecisionRule(kind, factor=1.0, max_risk=0.025)[source]

How to decide a requirement when the uncertainty of the result matters (JCGM 106, 8):

  • simple: simple acceptance, the best estimate decides (8.2). The chance of a wrong verdict can be up to 50 %, which the check’s probability of conformity shows;

  • guard band: guarded acceptance, the value must be inside the limit by factor·U (8.3.2), so a value near the limit fails instead of being inconclusive;

  • risk: the probability of conformity decides: pass when it is at least 1 − max_risk (the consumer’s risk of accepting a design that does not conform), fail when it is at most max_risk, inconclusive between (8.4, a specific risk).

Parameters:
  • kind (Literal['simple', 'guard band', 'risk'])

  • factor (float)

  • max_risk (float)

kind: Literal['simple', 'guard band', 'risk']
factor: float = 1.0
max_risk: float = 0.025
property token: str

risk=2.5%.

Type:

The rule as a requirement line writes it

classmethod parse(text)[source]

A rule from simple, guard=1.5 or risk=2.5% (guard and risk alone take the defaults: 1·U and 2.5 %); None for "" or none.

Parameters:

text (str)

Return type:

DecisionRule | None

class noodlelab.verify.Budget(rows, unit='')[source]

An uncertainty budget: [(input, contribution, share of variance), ...], largest first, with the contributions in unit. A list, which a notebook shows as a table.

Parameters:
  • rows (Iterable[tuple[str, float, float]])

  • unit (str)

class noodlelab.verify.Finding(code, level, message, where='')[source]

Something audit() found in a record.

Parameters:
  • code (str)

  • level (Literal['error', 'warning', 'info'])

  • message (str)

  • where (str)

code: str
level: Literal['error', 'warning', 'info']
message: str
where: str = ''
class noodlelab.verify.MonteCarlo(mean, std, low, high, p, trials, unit, gum, agrees, message, samples=None, batches=1, stable=None, method='random')[source]

A Monte Carlo evaluation (JCGM 101) of a model, next to its GUM result.

Parameters:
  • mean (float)

  • std (float)

  • low (float)

  • high (float)

  • p (float)

  • trials (int)

  • unit (str)

  • gum (Any)

  • agrees (bool)

  • message (str)

  • samples (Any)

  • batches (int)

  • stable (bool | None)

  • method (str)

mean: float
std: float
low: float
high: float
p: float
trials: int
unit: str
gum: Any
agrees: bool
message: str
samples: Any = None
batches: int = 1
stable: bool | None = None
method: str = 'random'
property value: Any

mean ± std.

Type:

The Monte Carlo result as an uncertain value

property inconclusive: bool

Too few trials to tell (the message says how many would).

check(name='')[source]
Parameters:

name (str)

Return type:

Check

exception noodlelab.verify.NoodlelabWarning[source]

What noodlelab says about a calculation that ran but may not be what was meant: requirements left unverified, correlations that were lost.

class noodlelab.verify.Quantity(value, unit=None)[source]

A Pint quantity as a type hint: Quantity["m/s"] for a speed, Quantity for any quantity. Quantity(9.81, "m/s^2") makes one, and isinstance(x, Quantity) is true for every Pint quantity.

Parameters:
  • value (Any)

  • unit (str | None)

Return type:

Any

class noodlelab.verify.Record(name, *, out=None, write=True, auto_verify=True, _caller=None)[source]

The audit trail of one calculation. See record().

Parameters:
  • name (str)

  • out (str | Path | None)

  • write (bool)

  • auto_verify (bool)

  • _caller (str | None)

status: str
error: str | None
checks: list[Check]
inputs: dict[str, dict[str, Any]]
results: dict[str, dict[str, Any]]
calls: list[dict[str, Any]]
files: dict[str, list[dict[str, Any]]]
notes: list[str]
path: Path | None
close()[source]

Finish the record and write it (done on leaving the with block).

Return type:

Path | None

input(name, value, *, unit='', source='', note='')[source]

A value the calculation starts from: "9.81 ± 0.02 m/s^2", a measure`d or :func:`q() value, or a plain number with unit. Name its source (instrument, dataset, paper, datasheet): the audit asks for one. Measured inputs are labelled name in uncertainty budgets. Returns the value to compute with.

Parameters:
  • name (str)

  • value (Any)

  • unit (str)

  • source (str)

  • note (str)

Return type:

Any

constant(name, *, label='')[source]

A named constant as an input (rec.constant("g0")), recorded under label (default: its name) with its source, so the audit needs no more. Returns the value to compute with.

Parameters:
  • name (str)

  • label (str)

Return type:

Any

add_inputs(**values)[source]

Several inputs at once, named by their keywords, in order:

h, g = rec.add_inputs(
    h=("2.00 ± 0.01 m", "tape measure"),   # (value, source)
    g={"value": "9.81 ± 0.02 m/s^2", "source": "survey", "note": "local"},
)

Each is a value as input() takes it, (value, source) or a dict of input()’s arguments. Returns them as a namespace (inputs.h) that also unpacks in order.

Parameters:

values (Any)

Return type:

Values

add_results(**values)[source]

Several results at once, named by their keywords: rec.add_results(t=(2 * h / g) ** 0.5, v=g * t). Each is a value, or a dict of result()’s arguments ({"value": n, "exact": True}). Returns them as a namespace that also unpacks in order.

Parameters:

values (Any)

Return type:

Values

result(name, value, *, unit='', exact=False, note='')[source]

A result of the calculation, recorded with its unit, uncertainty and budget. Converted to unit when given. exact=True says it has no uncertainty on purpose (a count, a definition), which the audit then accepts. Returns the value.

Parameters:
  • name (str)

  • value (Any)

  • unit (str)

  • exact (bool)

  • note (str)

Return type:

Any

require(spec, value=<not given>, *, k=2.0, coverage=None, fs=None, rule=None)[source]

Write down requirements (text, one per line, or parsed ones) before checking them; each is verified with verify(), or when the record closes against the result of the same name.

Given a value too, the one requirement is verified at once and the Check returned: rec.require("DRP-001 fall_time <= 1 s", t). k, coverage, fs and rule as for verify().

Parameters:
Return type:

Any

verify(req, value, *, k=2.0, coverage=None, fs=None, rule=None)[source]

Check a result against a requirement: its id (given with require()), a Requirement, or a line of text. k, coverage, fs and rule as for verify(): the coverage factor, or the coverage probability to take it from the degrees of freedom, a factor of safety and a decision rule. value can be a MonteCarlo result.

Parameters:
  • req (str | Requirement)

  • value (Any)

  • k (float)

  • coverage (float | None)

  • fs (float | None)

  • rule (DecisionRule | str | None)

Return type:

Check

expect(condition, message)[source]

A check that is not a requirement: a sanity bound, a conservation law, agreement with a reference. condition is truthy when it holds.

Parameters:
  • condition (Any)

  • message (str)

Return type:

Check

close_to(name, value, reference, *, rtol=0.0, atol=0.0, k=2.0)[source]

Check value agrees with reference (a textbook value, another method, an earlier result): within rtol/atol when given, else within k times their combined standard uncertainty (En ≤ 1 at k = 2).

Quantities are compared in the reference’s unit, so 20 degC and 293.15 K agree. With a temperature in degC or degF, atol is a difference (0.5 K or 0.5 delta_degC; 0.5 degC is read as one too) and rtol is relative to the absolute temperature.

Parameters:
  • name (str)

  • value (Any)

  • reference (Any)

  • rtol (float)

  • atol (Any)

Return type:

Check

read(path)[source]

Note a file the calculation reads (its SHA-256 goes in the record).

Parameters:

path (str | Path)

Return type:

Path

output(name)[source]

A path in this record’s folder to write an output to (a plot, a table); its SHA-256 is recorded when the record closes.

Parameters:

name (str)

Return type:

Path

wrote(path)[source]

Note a file the calculation wrote outside the record’s folder.

Parameters:

path (str | Path)

Return type:

Path

note(text)[source]

A remark for the reader: an assumption, a simplification, a caveat.

Parameters:

text (str)

Return type:

None

property passed: bool

Every check passed, every requirement written down was verified, and the block did not raise.

property unverified: list[str]

Requirements written down but never checked.

to_dict(*, light=False)[source]

The record as JSON-ready data (what write() saves). light=True leaves out the environment and the installed packages, which take a while to list, for showing the record rather than keeping it.

Parameters:

light (bool)

Return type:

dict[str, Any]

write()[source]

Save the record as provenance.json in its folder.

Return type:

Path

summary()[source]

A few lines for a person (or an agent’s context): the verdict, each check, requirements left unverified and the audit’s findings.

Return type:

str

to_markdown()[source]

The record as Markdown: the verdict, then tables of inputs, results and checks (for a report, a pull request or an agent’s answer).

Return type:

str

to_latex(*, standalone=False)[source]

The record as a LaTeX calculation sheet; standalone=True makes a whole document.

Parameters:

standalone (bool)

Return type:

str

class noodlelab.verify.Requirement(id: 'str', quantity: 'str', op: 'Op', limit: 'float', unit: 'str' = '', upper: 'float | None' = None, tolerance: 'float' = 0.0, text: 'str' = '', method: 'str' = 'Analysis', priority: 'str' = '', parent: 'str' = '', fs: 'float | None' = None, rule: 'str' = '')[source]
Parameters:
  • id (str)

  • quantity (str)

  • op (Literal['>=', '<=', '>', '<', '==', 'between'])

  • limit (float)

  • unit (str)

  • upper (float | None)

  • tolerance (float)

  • text (str)

  • method (str)

  • priority (str)

  • parent (str)

  • fs (float | None)

  • rule (str)

id: str
quantity: str
op: Literal['>=', '<=', '>', '<', '==', 'between']
limit: float
unit: str = ''
upper: float | None = None
tolerance: float = 0.0
text: str = ''
method: str = 'Analysis'
priority: str = ''
parent: str = ''
fs: float | None = None
rule: str = ''
property decision_rule: DecisionRule | None
property required: str

“≥ 3 dB”, “-20 to 60 °C”.

Type:

The requirement as a short phrase

property value: Any

a quantity when it has a unit (the lower limit of a range).

Type:

The limit as a value for the graph

magnitude(value)[source]

value as plain numbers in this requirement’s unit.

Parameters:

value (Any)

Return type:

Any

meets(margin)[source]

Whether a margin from margins() meets the requirement: a value exactly at the limit meets >= (margin 0) but not >. Element-wise on arrays.

Parameters:

margin (Any)

Return type:

Any

margins(value)[source]

How far inside the limits each value is, in the requirement’s unit (negative: outside; zero: at the limit, see meets()). Works element-wise on arrays and columns.

Parameters:

value (Any)

Return type:

Any

verify(value, *, k=2.0, fs=None, coverage=None, rule=None, samples=None)[source]

Compare a result with the requirement. An array passes when every element does; its margin is the smallest (the worst case).

An uncertain value is judged with its expanded uncertainty U = k·u (k = 2 by default, about 95 % for a normal distribution): it fails when its nominal value misses, passes when the nominal value is inside the limit by more than U, and is otherwise inconclusive (not passed): the design may meet the requirement, but the calculation cannot show it. An exact value is judged on its value alone.

coverage: take k from Student’s t for this coverage probability and the value’s effective degrees of freedom instead (see uncertainty.coverage_factor()), as a mean of few readings needs.

fs: a factor of safety (the requirement’s own fs when not given). The check then also reports the margin of safety MS (see the module’s docstring), and fails when MS < 0.

The check of an uncertain value also reports its probability of conformity; samples (Monte Carlo trials of the value, one row per trial) give it as the share of trials that conform. rule: decide with a DecisionRule (the requirement’s own when not given).

Parameters:
  • value (Any)

  • k (float)

  • fs (float | None)

  • coverage (float | None)

  • rule (DecisionRule | str | None)

  • samples (Any)

Return type:

Check

to_record()[source]
Return type:

dict[str, Any]

class noodlelab.verify.RecordData(data, *, path=None)[source]

A record read back from its file (see load()). It is the JSON as a dict, so data["inputs"] and json.dumps(data) work as they always did, but it cannot be changed: a record is evidence of a run.

Parameters:
  • data (dict[str, Any])

  • path (Path | None)

clear() → None.  Remove all items from D.
Parameters:
  • self (Any)

  • args (Any)

  • kwargs (Any)

Return type:

Any

pop(k[, d]) → v, remove specified key and return the corresponding value.

If the key is not found, return the default if given; otherwise, raise a KeyError.

Parameters:
  • self (Any)

  • args (Any)

  • kwargs (Any)

Return type:

Any

popitem(*args, **kwargs)

Remove and return a (key, value) pair as a 2-tuple.

Pairs are returned in LIFO (last-in, first-out) order. Raises KeyError if the dict is empty.

Parameters:
  • self (Any)

  • args (Any)

  • kwargs (Any)

Return type:

Any

setdefault(*args, **kwargs)

Insert key with a value of default if key is not in the dictionary.

Return the value for key if key is in the dictionary, else default.

Parameters:
  • self (Any)

  • args (Any)

  • kwargs (Any)

Return type:

Any

update([E, ]**F) → None.  Update D from dict/iterable E and F.

If E is present and has a .keys() method, then does: for k in E: D[k] = E[k] If E is present and lacks a .keys() method, then does: for k, v in E: D[k] = v In either case, this is followed by: for k in F: D[k] = F[k]

Parameters:
  • self (Any)

  • args (Any)

  • kwargs (Any)

Return type:

Any

property format: Literal['record', 'graph', 'unknown']

"record" (a script’s), "graph" (a graph run’s) or "unknown".

property name: str
property status: str
property passed: bool

every check passed, every requirement was verified and the calculation did not raise.

Type:

No error-level finding

property checks: list[Check]

The checks, as Check objects again.

result(name)[source]

A result (or input) as a value to compute with again: a quantity, an uncertain one when it had an uncertainty. Its correlations with other values are not in the record, so they are lost: the uncertainty is its own, independent of everything else’s.

Parameters:

name (str)

Return type:

Any

audit()[source]

The audit’s findings (see audit(), and audit_graph_record() for a graph run).

Return type:

list[Finding]

summary()[source]

A few lines for a person, as Record.summary() gives.

Return type:

str

diff(other, *, rtol=1e-09, atol=0.0)[source]

What changed from other (an earlier run) to this one: see noodlelab.core.rundiff.diff_runs().

Parameters:
  • other (RecordData | str | Path)

  • rtol (float)

  • atol (float)

to_markdown()[source]
Return type:

str

to_latex(*, standalone=False)[source]
Parameters:

standalone (bool)

Return type:

str

class noodlelab.verify.RequirementSet(items=())[source]

Requirements by id, in the order they were given.

Parameters:

items (tuple[Requirement, ...])

items: tuple[Requirement, ...] = ()
property ids: list[str]
get(rid)[source]
Parameters:

rid (str)

Return type:

Requirement

merged(other)[source]
Parameters:

other (RequirementSet)

Return type:

RequirementSet

rows()[source]
Return type:

list[list[str]]

class noodlelab.verify.Sweep(rows, params)[source]

The rows of a sweep(), one dict per point: its parameters and what the function returned there (result, or the keys of a dict it returned). column(name) gives one column; to_pandas() a table.

Parameters:
  • rows (Iterable[dict[str, Any]])

  • params (list[str])

column(name)[source]
Parameters:

name (str)

Return type:

list[Any]

property columns: list[str]
to_pandas()[source]

The rows as a pandas DataFrame (needs pandas): each column in its unit, uncertain values as they are.

Return type:

Any

class noodlelab.verify.Uncertain(value, uncertainty, unit='', name='')[source]

A value with a standard uncertainty, as a type hint: Uncertain for any, Uncertain["m"] for a length. Uncertain(9.81, 0.02, "m/s^2") makes one.

Parameters:
  • value (Any)

  • uncertainty (float)

  • unit (str)

  • name (str)

Return type:

Any

class noodlelab.verify.Values[source]

Values by name, from Record.add_inputs() or Record.add_results(): vals.h or vals["h"], and h, g = vals unpacks them in the order they were given.

class noodlelab.verify.Verifications(checks=())[source]

Checks of requirements, collected into a compliance matrix (one entry per requirement: verifying it again replaces the earlier entry).

Parameters:

checks (tuple[Check, ...])

checks: tuple[Check, ...] = ()
add(check)[source]
Parameters:

check (Check)

Return type:

Verifications

property passed: bool
property with_conformity: bool

Whether any check has a probability of conformity (an uncertain value).

property columns: list[str]

MATRIX_COLUMNS, with “P(conform)” after MS when any check has one.

Type:

The compliance matrix’s columns

rows()[source]
Return type:

list[list[str]]

noodlelab.verify.audit(rec)[source]

Check a record against the rules of a verifiable calculation (see RULES): failed or missing checks, results without units or uncertainty, inputs without a source, code that was not committed or has changed since. Takes a Record, its data, or its file.

Parameters:

rec (Record | dict[str, Any] | str | Path)

Return type:

list[Finding]

noodlelab.verify.budget(value)[source]

[(input, contribution, share of variance), ...], largest first: which named inputs the uncertainty of value comes from, contributions in its unit.

Parameters:

value (Any)

Return type:

Budget

noodlelab.verify.checked(req, *, k=2.0, coverage=None, fs=None, rule=None, strict=False)[source]

Check the result of every call of a function against a requirement:

@nv.checked("R1 distance <= 50 m")
def distance(g, T): ...

The check is made like verify() (same k, coverage, fs and rule) and lands in the active record, if there is one. A check that does not pass is reported by a NoodlelabWarning; strict=True raises AssertionError instead. The requirement is read when the function is decorated, so a misspelled line fails at import rather than at the first call. The function’s result is returned unchanged.

Parameters:
  • req (Requirement | str)

  • k (float)

  • coverage (float | None)

  • fs (float | None)

  • rule (DecisionRule | str | None)

  • strict (bool)

Return type:

Callable[[F], F]

noodlelab.verify.constant(name)[source]

A named constant with its unit: constant("c"), constant("g0"). Measured ones (G, m_e…) carry their CODATA uncertainty. The same as nv.const.c; add your own with define_constant().

Parameters:

name (str)

Return type:

Any

noodlelab.verify.correlated(values, corr=None, *, cov=None, names=None, unit='', dof=None)[source]

Values whose uncertainties are correlated (GUM 5.2), as a list:

  • correlated([a, b], [[1, 0.8], [0.8, 1]]): measured values a and b keep their value, standard uncertainty and unit, with the correlation matrix corr between them (two readings of one instrument);

  • correlated([1.2, 0.3], cov=pcov, names=["slope", "offset"], unit="V"): plain numbers with the covariance matrix cov, such as a fit’s parameters (dof: the degrees of freedom it was estimated with).

Each value is named in budgets after names, or the input it came from. A value that already depends on several sources keeps only its total uncertainty (with a warning). monte_carlo() draws them jointly.

Parameters:
  • values (Iterable[Any])

  • corr (Any)

  • cov (Any)

  • names (Iterable[str] | None)

  • unit (str)

  • dof (float | None)

Return type:

list[Any]

noodlelab.verify.current()[source]

The record being written (inside with record(...)), or None.

Return type:

Record | None

noodlelab.verify.define_constant(name, value, unit='', *, uncertainty=0.0, title='', source='', symbol='', override=False)

Add a constant for every graph and script in this process:

define("g_local", 9.8123, "m/s^2", uncertainty=0.0005, source="survey 2024")
define("rho_water", "998.2 kg/m^3", title="density of water at 20 °C")

Replacing a built-in constant warns, unless override=True says it is meant. pi, tau and e cannot be replaced.

Parameters:
  • name (str)

  • value (Any)

  • unit (str)

  • uncertainty (float)

  • title (str)

  • source (str)

  • symbol (str)

  • override (bool)

Return type:

Constant

noodlelab.verify.finished()[source]

Every record finished in this process, oldest first.

Return type:

list[Record]

noodlelab.verify.fmt(value, k=1.0)[source]

A value for people: (2.006 ± 0.003) s (GUM rounding), 3 dB, 0.5. k: coverage factor for an expanded uncertainty.

Parameters:
  • value (Any)

  • k (float)

Return type:

str

noodlelab.verify.from_repeats(samples, unit='', *, name='')[source]

The mean of repeated readings with its standard uncertainty s/√n (GUM Type A, 4.2): from_repeats([9.79, 9.82, 9.80, 9.81], "m/s^2", name="g"). The mean carries n - 1 degrees of freedom, so coverage=0.95 in verify() and monte_carlo() (a Student’s t) account for how few readings there were. samples may be a quantity array, or readings that are uncertain themselves (a calibrated instrument’s), whose uncertainty the mean keeps. Inside a monte_carlo() model this returns one draw.

Parameters:
  • samples (Any)

  • unit (str)

  • name (str)

Return type:

Any

noodlelab.verify.load(path)[source]

A record from its provenance.json (or the folder holding it): a script’s (record()) or a graph run’s. It is the record’s data, a read-only dict, with what a reader asks of it: passed, checks, result(name), summary(), audit() and diff(other).

Parameters:

path (str | Path)

Return type:

RecordData

noodlelab.verify.load_constants(path)

define() every constant in a constants.toml file. Raises ConstantsError listing every problem, since a script should stop on them.

Parameters:

path (str | Path)

Return type:

dict[str, Constant]

noodlelab.verify.measure(value, u=None, unit='', *, name='', distribution='normal', dof=None)[source]

A measured value with its standard uncertainty:

  • measure("9.81 ± 0.02 m/s^2"), measure("9.81(2) m/s^2")

  • measure(9.81, 0.02, "m/s^2")

  • measure(q("9.81 m/s^2"), 0.02)

name labels it in budget(). distribution (and dof for Student’s t) is what monte_carlo() samples; u is always the GUM standard uncertainty (for a rectangular distribution of half-width a, give a/√3; for the mean of n readings with spread s, s/√n with dof = n - 1, which is the scale of the t distribution sampled, JCGM 101 6.4.9). An unknown distribution, or t without dof, is refused. Inside a monte_carlo() model this returns one draw instead.

Parameters:
  • value (Any)

  • u (float | None)

  • unit (str)

  • name (str)

  • distribution (Literal['normal', 'rectangular', 'triangular', 't'])

  • dof (float | None)

Return type:

Any

noodlelab.verify.monte_carlo(model, trials=None, *, seed=1, p=0.95, max_trials=1000000, workers=None, qmc=False)[source]

Evaluate model (a function of no arguments that makes its uncertain inputs with measure() and returns a number or quantity) by Monte Carlo, and compare with linear GUM propagation (JCGM 101 8). Use it when the model is not linear in its inputs: agrees says whether the GUM uncertainty can be trusted. The inputs must be made inside the model.

Without trials, it runs as many as the results need (JCGM 101 7.9): batches of 10 000 until the mean, the standard uncertainty and the coverage interval stop moving at the precision they are reported to (two significant digits of u), or max_trials (stable says which). trials=20_000 runs exactly that many instead, as it did before.

A model that takes arrays (NumPy functions and arithmetic do) runs many trials at once; one that does not (an if, math.sin) runs trial by trial, and workers=4 spreads its batches over 4 processes, with the same result whatever the number. qmc=True draws each batch from a scrambled Sobol sequence (needs SciPy and a model that takes arrays): a smooth model needs fewer trials. Both need the adaptive run.

Parameters:
  • model (Callable[[], Any])

  • trials (int | None)

  • seed (int)

  • p (float)

  • max_trials (int)

  • workers (int | None)

  • qmc (bool)

Return type:

MonteCarlo

noodlelab.verify.nominal(value)[source]

The value without its uncertainty.

Parameters:

value (Any)

Return type:

Any

noodlelab.verify.notebook()[source]

Show uncertain values in this Jupyter (IPython) session as people write them, (2.006 ± 0.003) s with GUM rounding, and typeset (_repr_latex_). Records, checks, budgets and Monte Carlo results show as tables without it; this is for the values themselves, which belong to the uncertainties and Pint packages. Quantities without an uncertainty keep Pint’s display.

Return type:

None

noodlelab.verify.q(value, unit='')[source]

A quantity: q("9.81 m/s^2"), q(9.81, "m/s^2"), q("3 dB"). Text with an uncertainty ("9.81 ± 0.02 m/s^2") gives an uncertain one.

Parameters:
  • value (Any)

  • unit (str)

Return type:

Any

noodlelab.verify.record(name, *, out=None, write=True, auto_verify=True)[source]

Start a record of a calculation: with nv.record("link budget") as rec: ....

On leaving the block the record is written to <out>/<time>-<name>-<id>/provenance.json (out defaults to runs next to the script, or $NOODLELAB_RECORDS). write=False keeps it in memory only (tests).

A requirement still unverified when the block ends is checked against the result of the same name (fall_time against rec.result("fall time", ...)), unless auto_verify=False; one with no such result is left unverified, and a NoodlelabWarning says so.

Parameters:
  • name (str)

  • out (str | Path | None)

  • write (bool)

  • auto_verify (bool)

Return type:

Record

noodlelab.verify.recorded(name=None, *, out=None, write=True, auto_verify=True)[source]

Run every call of a function inside a record(), so an entry point leaves its provenance without a with block:

@nv.recorded("fall time")
def main(): ...

name defaults to the function’s name. The other arguments are those of record(). A call made while another record is active starts its own, which ends with the call.

Parameters:
  • name (str | Callable[[...], Any] | None)

  • out (str | Path | None)

  • write (bool)

  • auto_verify (bool)

Return type:

Any

noodlelab.verify.requirement(line)[source]

One requirement from a line of text (see requirements()).

Parameters:

line (str)

Return type:

Requirement

noodlelab.verify.requirements(text)[source]

Requirements from text, one per line:

COM-001 link_margin >= 3 dB [Analysis]   # The link shall close with 3 dB to spare
PWR-001 dc_power <= 40 W [Inspection]
TMP-001 temperature between -20 and 60 degC

or from records (rows of a CSV/YAML spec: id, quantity, op, limit, unit…).

Parameters:

text (str | Iterable[dict[str, Any]])

Return type:

RequirementSet

noodlelab.verify.std_dev(value)[source]

The standard uncertainty (0 for an exact value), in the value’s unit.

Parameters:

value (Any)

Return type:

Any

noodlelab.verify.sweep(fn, /, *, grid=True, points=11, **params)[source]

Run fn over a set of parameter values and collect the results: sweep(lambda L: period(L, g), L=q(np.linspace(0.5, 2, 7), "m")).

Each parameter is a list, an array (a quantity array gives quantities) or a range as text: L="0.5 .. 2 m" (points values from one end to the other) or method="[a, b]". grid=True runs every combination; grid=False takes the parameters side by side, which must then be as long. Inside a record the sweep’s parameters and size are recorded.

Parameters:
  • fn (Callable[[...], Any])

  • grid (bool)

  • points (int)

  • params (Any)

Return type:

Sweep

noodlelab.verify.traced(fn=None, *, name=None)[source]

Record every call of a function in the active record: its arguments, result (with units and uncertainty), duration and a hash of its source. Outside a record the function runs as usual, at no cost:

@nv.traced
def drag(rho, v, cd, area):
    return 0.5 * rho * v**2 * cd * area
Parameters:
  • fn (F | None)

  • name (str | None)

Return type:

Any

noodlelab.verify.verify(req, value, *, k=2.0, coverage=None, fs=None, rule=None)[source]

Check value against a requirement (a Requirement or its line of text). The Check says whether it passed, what was achieved and the margin in the requirement’s unit (negative: outside the limit). An array passes when every element does; its margin is the worst case.

An uncertain value must meet the limit by more than U = k·u (k = 2), or it is inconclusive; coverage=0.95 takes k from Student’s t and the value’s effective degrees of freedom instead (a mean of few readings). fs: a factor of safety (overrides fs= in the requirement’s line); the check reports the margin of safety and fails when it is negative.

An uncertain value’s check also has p_conform, the probability that the true value meets the requirement (JCGM 106). Pass a MonteCarlo result as value to take it from the trials, which follows a skewed result. rule: decide with a decision rule ("risk=2.5%", "guard=1", "simple"; overrides the one in the requirement’s line) instead of calling a margin within U inconclusive.

Parameters:
  • req (Requirement | str)

  • value (Any)

  • k (float)

  • coverage (float | None)

  • fs (float | None)

  • rule (DecisionRule | str | None)

Return type:

Check

noodlelab.verify.wrap(fn=None, *, units_in=None, units_out=None, elementwise=False, validate=False, name=None)[source]

Make a function from another library take and return quantities with uncertainty, and record its calls like traced().

The wrapped function receives plain numbers (or arrays) in units_in ({"parameter": "unit"}; a quantity is converted to it, a plain number is taken to be in it, a parameter left out is passed as it is). Its result gets the unit units_out: a unit string, or a function of the units the arguments came in ({"parameter": unit}) that returns one. For a function that returns a tuple (scipy.integrate.quad: value and error estimate) or a dict, give a tuple or dict of units, with None for an element to leave bare. The uncertainty of the inputs is propagated through the call to first order, by numerical derivatives (GUM 5.1), so the library function itself needs no changes. elementwise=True says it works element by element on arrays, which makes the propagation cheaper:

brentq = nv.wrap(scipy.optimize.brentq, units_in={"a": "m", "b": "m"}, units_out="m")

First order is wrong for a strongly nonlinear function. validate=True checks each call that has uncertain inputs against monte_carlo() (many more calls of the function), and says in the record, and in a NoodlelabWarning when it fails, whether the result can be trusted. It needs a single number as the result, and inputs that are independent measurements (not computed from others, not arrays); otherwise it says it could not validate.

Parameters of the function must be nameable: *args and **kwargs are refused, so wrap it in a function with named parameters instead. A call whose result cannot carry an uncertainty runs on the nominal values, with a NoodlelabWarning saying so.

Parameters:
  • fn (F | None)

  • units_in (dict[str, str] | None)

  • units_out (Any)

  • elementwise (bool)

  • validate (bool)

  • name (str | None)

Return type:

Any

noodlelab.display

Records, checks and values for people: HTML for notebooks, Markdown for reports and chat, LaTeX for documents.

A calculation is only verifiable if someone reads it, and a notebook is where many people do. Every renderer here works from the record’s data (what noodlelab.verify.Record.to_dict() gives and noodlelab.verify.load() reads back), so a record shows the same while it is written and years later. The HTML is plain tables with inline styles that read in light and dark notebooks, and the LaTeX needs only amssymb.

Jupyter draws text/latex with MathJax, which typesets mathematics but not tables, so records and checks show as HTML there; record_latex() is for a document (rec.to_latex()), and values get _repr_latex_ through noodlelab.verify.notebook().

noodlelab.display.value_text(value)[source]

(2.006 ± 0.003) s: GUM rounding, the unit as people write it.

Parameters:

value (Any)

Return type:

str

noodlelab.display.value_html(value)[source]
Parameters:

value (Any)

Return type:

str

noodlelab.display.value_latex(value, *, math_mode=True)[source]

(2.006 \pm 0.003)\,\mathrm{s}, in $...$ unless math_mode is False.

Parameters:
  • value (Any)

  • math_mode (bool)

Return type:

str

noodlelab.display.check_html(check)[source]

One check: its verdict, and its message (requirement, margin, P(conform)).

Parameters:

check (Any)

Return type:

str

noodlelab.display.check_markdown(check)[source]
Parameters:

check (Any)

Return type:

str

noodlelab.display.budget_html(rows, unit='')[source]

An uncertainty budget as a table: each input, its contribution and its share of the variance, with a bar so the largest stands out.

Parameters:
  • rows (list[tuple[str, float, float]])

  • unit (str)

Return type:

str

noodlelab.display.budget_markdown(rows, unit='')[source]
Parameters:
  • rows (list[tuple[str, float, float]])

  • unit (str)

Return type:

str

noodlelab.display.record_html(data, findings=(), passed=None)[source]

A record as HTML: the verdict, then inputs, results, checks and what the audit found, each as a small table.

Parameters:
  • data (dict[str, Any])

  • findings (list[Any])

  • passed (bool | None)

Return type:

str

noodlelab.display.record_markdown(data, findings=(), passed=None)[source]

A record as Markdown: the verdict, then tables of inputs, results and checks.

Parameters:
  • data (dict[str, Any])

  • findings (list[Any])

  • passed (bool | None)

Return type:

str

noodlelab.display.record_latex(data, findings=(), *, standalone=False)[source]

A record as a LaTeX calculation sheet: inputs with their sources, results, and each check with its margin. standalone=True makes a whole document (it needs amssymb, and booktabs for the rules).

Parameters:
  • data (dict[str, Any])

  • findings (list[Any])

  • standalone (bool)

Return type:

str

The pytest plugin

noodlelab in a test suite: a test that verifies a calculation fails when the calculation’s record does.

pytest loads this by itself once noodlelab is installed (it is a pytest11 entry point). It gives tests the nv_record fixture, a noodlelab.verify.Record named after the test:

def test_the_bracket_holds(nv_record):
    load = nv_record.input("load", "1200 ± 50 N", source="load case 3")
    stress = nv_record.result("stress", load / area)
    nv_record.require("STR-001 stress <= 180 MPa", stress)

@pytest.mark.noodlelab on a test gives it the record without asking for the fixture, so a test that only calls @nv.checked functions is failed by them.

When the test function returns, the record is closed (requirements named after a result are verified then, as in a with block) and audited: a failed or inconclusive check, a requirement never verified, or an error fails the test with the record’s summary. --nv-strict (or @pytest.mark.noodlelab(strict=True)) fails on the audit’s warnings too: results without units or uncertainty, nothing verified. Whether the code was committed is left out, since a developer’s tests run on changes not yet committed. Records are kept in memory unless --nv-records DIR is given.

Only pytest is imported here at start-up; noodlelab.verify is imported by the fixture, so test suites that never ask for it pay nothing more.

noodlelab.constants

Named constants for calculations in Python, with their units and, where they were measured, their CODATA uncertainty:

from noodlelab.constants import c, g0, k_B, pi

E = m * c**2                    # a Pint quantity, in kg·m²/s²
W = m * g0                      # standard gravity, 9.80665 m/s², exact
F = G * M * m / r**2            # G is uncertain: 6.67430(15)e-11 m³/(kg·s²)

pi, tau and e are the math module’s plain floats. Exact constants (c, h, k_B, g0…) are quantities, and measured ones (G, m_e, alpha…) are uncertain values named after the constant, so noodlelab.verify.budget() can list them. The full table, with sources, is in noodlelab.core.constants, and noodlelab constants prints it.

Constants you add with define() (or load() from a constants.toml) can be imported the same way once they are defined.

class noodlelab.constants.Constant(name: 'str', value: 'float', unit: 'str' = '', uncertainty: 'float' = 0.0, symbol: 'str' = '', title: 'str' = '', source: 'str' = '', group: 'str' = '', origin: 'Origin' = 'built-in', override: 'bool' = False)[source]
Parameters:
  • name (str)

  • value (float)

  • unit (str)

  • uncertainty (float)

  • symbol (str)

  • title (str)

  • source (str)

  • group (str)

  • origin (Literal['built-in', 'registered', 'workspace', 'graph'])

  • override (bool)

name: str
value: float
unit: str = ''
uncertainty: float = 0.0
symbol: str = ''
title: str = ''
source: str = ''
group: str = ''
origin: Literal['built-in', 'registered', 'workspace', 'graph'] = 'built-in'
override: bool = False
property exact: bool
value_of()[source]

The constant to compute with. A plain float for exact numbers, and a quantity for exact values with a unit. A measured value is uncertain, named after the constant. It is the same value every time, so results computed from G twice stay correlated. Inside a Monte Carlo trial it is a fresh draw instead.

Return type:

Any

key()[source]

What a cache key needs: the definition, not the description.

Return type:

dict[str, Any]

display()[source]

"9.80665 m/s^2", "6.6743e-11 ± 1.5e-15 m^3/(kg*s^2)".

Return type:

str

citation()[source]

What a record names as the source of an input taken from this constant.

Return type:

str

to_json()[source]
Return type:

dict[str, Any]

exception noodlelab.constants.ConstantsError[source]

A constant that cannot be defined (a bad name, value or unit).

noodlelab.constants.define(name, value, unit='', *, uncertainty=0.0, title='', source='', symbol='', override=False)[source]

Add a constant for every graph and script in this process:

define("g_local", 9.8123, "m/s^2", uncertainty=0.0005, source="survey 2024")
define("rho_water", "998.2 kg/m^3", title="density of water at 20 °C")

Replacing a built-in constant warns, unless override=True says it is meant. pi, tau and e cannot be replaced.

Parameters:
  • name (str)

  • value (Any)

  • unit (str)

  • uncertainty (float)

  • title (str)

  • source (str)

  • symbol (str)

  • override (bool)

Return type:

Constant

noodlelab.constants.load(path)[source]

define() every constant in a constants.toml file. Raises ConstantsError listing every problem, since a script should stop on them.

Parameters:

path (str | Path)

Return type:

dict[str, Constant]

noodlelab.constants.lookup(name)[source]

The constant called name; a KeyError suggests close names otherwise.

Parameters:

name (str)

Return type:

Constant

noodlelab.constants.names()[source]
Return type:

list[str]

Writing plugins

The public API for writing noodlelab plugins.

A plugin is a Python package that noodlelab finds through the noodlelab.nodes entry point, with a noodlelab-plugin.toml manifest beside its code. Its nodes are type-hinted functions with node(), and everything a node needs from noodlelab comes from here:

from noodlelab.plugin import Param, Quantity, node
from noodlelab.plugin.units import ureg

This package is the contract between noodlelab and its plugins. What it exports keeps working within an API_VERSION; a plugin states the version it was written for in its manifest (api = 1), and noodlelab refuses to load one written for a version it does not speak, rather than failing somewhere inside a run. Everything else in noodlelab (noodlelab.core, noodlelab.reports…) is internal and may change in any release, so a plugin that reaches into it can break without warning; noodlelab check-plugin refuses such imports.

The helpers are grouped in submodules, imported on demand so that a plugin pays only for what it uses:

  • units: the unit registry and quantities

  • uncertainty: values with standard uncertainties (GUM)

  • about: units and descriptions carried by table columns

  • tables: table columns as arrays, and table attributes in files

  • lists: names, numbers and class breaks typed into text widgets

  • expr: checking expressions typed into widgets before evaluating them

  • constants: named physical constants

  • figures: saving matplotlib figures

  • studies: parameter studies (design tables)

  • math: Typst maths for previews and reports

noodlelab.plugin.require(install, *modules)[source]

Raise a helpful ImportError when modules a plugin needs are missing.

install says how to get them: the name of a noodlelab tier ("maths"), or what to install ("noodlelab-thermo[steam]"). Call it at the top of a plugin’s __init__ for optional dependencies, so the plugin fails to load with that advice instead of an ImportError deep inside.

Parameters:
  • install (str)

  • modules (str)

Return type:

None

noodlelab.plugins

Plugins: finding, checking, installing and versioning node packs.

A plugin is a Python package registered under the noodlelab.nodes entry point, with a manifest (noodlelab-plugin.toml, see manifest). Plugins are written against noodlelab.plugin (singular), the public API; this package (plural) is the machinery around them.

From Python:

import noodlelab.plugins

noodlelab.plugins.require("thermo>=1.2")   # fail early, with the install command
noodlelab.plugins.install("thermo")         # in a notebook: shows the plan, asks
noodlelab.plugins.health()                  # what the CLI's `plugins check` shows
noodlelab.plugins.health(*, home=None, offline=False)[source]

The health of every installed plugin (a status.HealthReport).

Parameters:
  • home (Path | None)

  • offline (bool)

Return type:

Any

noodlelab.plugins.install(*specs, home=None, yes=False)[source]

Install plugins (and the plugins they require) into this Python environment, as noodlelab plugins add does: for notebooks. Unless yes, show the plan and ask first. The names of the plugins installed; restart the kernel to use a plugin that was upgraded.

Parameters:
  • specs (str)

  • home (Path | None)

  • yes (bool)

Return type:

list[str]

noodlelab.plugins.require(*specs)[source]

Check that plugins are installed, in the versions a script needs:

import noodlelab.plugins
noodlelab.plugins.require("thermo>=1.2", "geo")

Raise an ImportError naming what is missing and the command that installs it. It only reads package metadata, so it is cheap to call at the top of a script.

Parameters:

specs (str)

Return type:

None

The node API

noodlelab: verifiable science for people and AI agents.

For calculations in plain Python (units, uncertainty, requirements, checks and a provenance record of every run), use noodlelab.verify:

import noodlelab.verify as nv

with nv.record("pendulum") as rec:
    L = rec.input("L", "1.000 ± 0.002 m", source="tape measure")
    ...

Plugin authors write against noodlelab.plugin, which re-exports these names with the helpers for units, uncertainty and tables:

from noodlelab.plugin import Param, Probe, ProbeContext, RunContext, node, register_type
class noodlelab.FileRef(value='')[source]

A reference to a file in the workspace, on the server, or on a mount.

It is a str (so it serialises, hashes and prints as the reference), with methods that work the same for every backend.

Parameters:

value (str | PurePath)

Return type:

FileRef

bind(fs)[source]
Parameters:

fs (FileSystems | None)

Return type:

FileRef

property target: Target
property mount: str | None
property is_local: bool
property name: str
property suffix: str
with_suffix(suffix)[source]

The file next to this one with another extension, on the same storage: dem.asc → dem.prj, for sidecar files.

Parameters:

suffix (str)

Return type:

FileRef

open(mode='rb', **kwargs)[source]

Open for reading ("rb", "r") or, on writable storage, writing.

Parameters:
  • mode (str)

  • kwargs (Any)

Return type:

IO[Any]

read_bytes()[source]
Return type:

bytes

read_text(encoding='utf-8')[source]
Parameters:

encoding (str)

Return type:

str

exists()[source]
Return type:

bool

is_file()[source]
Return type:

bool

info()[source]
Return type:

dict[str, Any]

token()[source]

Changes when the file changes (modification time, size, ETag). Used by probes to revalidate cached previews.

Return type:

tuple[Any, …] | None

local_path()[source]

A local file with this content. Remote files are downloaded once into a cache (keyed by reference, size and modification time).

Return type:

Path

class noodlelab.Param(label=None, description='', widget=None, min=None, max=None, step=None, precision=None, choices=None, accept=None, directory=False, exists=True, options_from=None, empty=None, type=None, multiline=False, required='')[source]

Extra metadata for a parameter, used via Annotated[type, Param(...)].

Parameters:
  • label (str | None)

  • description (str)

  • widget (Literal['number', 'slider', 'combo', 'text', 'toggle', 'none'] | None)

  • min (float | None)

  • max (float | None)

  • step (float | None)

  • precision (int | None)

  • choices (Sequence[Any] | None)

  • accept (Sequence[str] | None)

  • directory (bool)

  • exists (bool)

  • options_from (str | None)

  • empty (str | None)

  • type (Any)

  • multiline (bool)

  • required (str)

label: str | None = None
description: str = ''
widget: Literal['number', 'slider', 'combo', 'text', 'toggle', 'none'] | None = None
min: float | None = None
max: float | None = None
step: float | None = None
precision: int | None = None
choices: Sequence[Any] | None = None
accept: Sequence[str] | None = None
directory: bool = False
exists: bool = True
options_from: str | None = None
empty: str | None = None
type: Any = None
multiline: bool = False
required: str = ''
class noodlelab.Preview(*, kind, summary, text=None, image=None, table=None, url=None)[source]
Parameters:
  • kind (Literal['value', 'text', 'image', 'table', 'file', 'math'])

  • summary (str)

  • text (str | None)

  • image (str | None)

  • table (TablePreview | None)

  • url (str | None)

kind: Literal['value', 'text', 'image', 'table', 'file', 'math']
summary: str
text: str | None
image: str | None
table: TablePreview | None
url: str | None
model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class noodlelab.Probe(preview=None, meta=None, output_meta=None, summary=None, sample=None, output_samples=None)[source]

What a probe returns (or yields).

preview: any value, rendered with the registered previewers (a DataFrame becomes a table, a Figure an image), or a ready Preview such as one from image_preview(). meta: small JSON describing the node’s outputs, readable downstream with ProbeContext.meta(); it applies to every output unless output_meta gives one per output name. summary overrides the preview’s one-line summary.

sample (or output_samples per output): a small value of the output’s type that downstream nodes are evaluated on while editing (see sample). When left out, a preview value of the output’s type is used, so returning df.head(20) is enough.

Parameters:
  • preview (Any)

  • meta (dict[str, Any] | None)

  • output_meta (dict[str, dict[str, Any]] | None)

  • summary (str | None)

  • sample (Any)

  • output_samples (dict[str, Any] | None)

preview: Any = None
meta: dict[str, Any] | None = None
output_meta: dict[str, dict[str, Any]] | None = None
summary: str | None = None
sample: Any = None
output_samples: dict[str, Any] | None = None
class noodlelab.ProbeContext(node_id, root, timeout, _upstream=<factory>, _linked=<factory>, _cancel=<factory>, _started=<factory>, watched=<factory>, logs=<factory>)[source]

Passed to probes and options functions that ask for it (by annotation or by naming a parameter ctx).

Parameters:
  • node_id (str)

  • root (Path)

  • timeout (float)

  • _upstream (dict[str, dict[str, Any] | None])

  • _linked (set[str])

  • _cancel (Event)

  • _started (float)

  • watched (dict[str, tuple[Callable[[], Any], Any]])

  • logs (list[str])

node_id: str
root: Path
timeout: float
watched: dict[str, tuple[Callable[[], Any], Any]]
logs: list[str]
meta(input)[source]

Meta of the output linked into input, or None (not linked, or the upstream node has no probe).

Parameters:

input (str)

Return type:

dict[str, Any] | None

linked(input)[source]
Parameters:

input (str)

Return type:

bool

watch(path)[source]

Mark a file the probe depends on: the cached result is dropped when it changes (modification time, size, or ETag for remote files). Accepts a Path (resolved against the workspace) or a FileRef; returns it.

Parameters:

path (str | PurePath)

Return type:

Any

property cancelled: bool

True once the node’s values changed and a newer probe started. Long probes should check this between steps (or call check_cancelled()).

check_cancelled()[source]
Return type:

None

property remaining: float

Seconds left in the time budget (may be negative).

log(message)[source]
Parameters:

message (str)

Return type:

None

class noodlelab.Problem(severity, message)[source]
Parameters:
  • severity (Literal['error', 'warning'])

  • message (str)

severity: Literal['error', 'warning']

Alias for field number 0

message: str

Alias for field number 1

class noodlelab.RunContext(run_id: 'str', node_id: 'str', workdir: 'Path', scratch: 'Path | None' = None, root: 'Path | None' = None, _emit: 'Callable[[str, dict[str, Any]], None]' = <function RunContext.<lambda> at 0x7ffa087ccae0>, _provenance: 'Any' = None, _about_in: 'dict[str, Any]' = <factory>, _declared: 'dict[str, Any]' = <factory>, _samples: 'Any' = None, _warnings: 'list[str]' = <factory>)[source]
Parameters:
  • run_id (str)

  • node_id (str)

  • workdir (Path)

  • scratch (Path | None)

  • root (Path | None)

  • _emit (Callable[[str, dict[str, Any]], None])

  • _provenance (Any)

  • _about_in (dict[str, Any])

  • _declared (dict[str, Any])

  • _samples (Any)

  • _warnings (list[str])

run_id: str
node_id: str
workdir: Path
scratch: Path | None = None
root: Path | None = None
monte_carlo_samples()[source]

The trials the executor ran for a Monte Carlo node (a Samples), or None.

Return type:

Any

describe(output='result', **fields)[source]

Say what an output is: title, description, sources, licences, citations or flags (lists of text). It travels downstream with the value (see noodlelab.core.about).

Parameters:
  • output (str)

  • fields (Any)

Return type:

None

input_about(name)[source]

What is known about input name: an About, or None.

Parameters:

name (str)

Return type:

Any

warn(message)[source]

Warn about this node’s result (see warn()).

Parameters:

message (str)

Return type:

None

log(message)[source]

Send a log line to the editor (shown in the node inspector).

Parameters:

message (str)

Return type:

None

progress(fraction, message='')[source]

Report progress between 0 and 1; drawn as a bar on the node.

Parameters:
  • fraction (float)

  • message (str)

Return type:

None

path(name)[source]

A path inside this run’s output directory (parent dirs are created).

Parameters:

name (str)

Return type:

Path

temp(name)[source]

A path in the run’s scratch folder, for large temporary files.

Parameters:

name (str)

Return type:

Path

run_info()[source]

What is known about this run so far, as plain JSON: its id, start time and user, the environment (noodlelab, Python, platform, key library versions), and every node that finished before this one, with its settings and fingerprinted input files (see noodlelab.core.provenance). Outside the editor and noodlelab run (e.g. in an exported script) only the environment is known.

Return type:

dict[str, Any]

classmethod standalone(workdir='noodlelab-output')[source]

A context for calling nodes from plain Python (e.g. exported scripts).

Parameters:

workdir (str | Path)

Return type:

RunContext

noodlelab.as_file(value)[source]

A FileRef from a string or path (for node code also called from scripts).

Parameters:

value (str | PurePath | FileRef)

Return type:

FileRef

noodlelab.define_constant(name, value, unit='', *, uncertainty=0.0, title='', source='', symbol='', override=False)

Add a constant for every graph and script in this process:

define("g_local", 9.8123, "m/s^2", uncertainty=0.0005, source="survey 2024")
define("rho_water", "998.2 kg/m^3", title="density of water at 20 °C")

Replacing a built-in constant warns, unless override=True says it is meant. pi, tau and e cannot be replaced.

Parameters:
  • name (str)

  • value (Any)

  • unit (str)

  • uncertainty (float)

  • title (str)

  • source (str)

  • symbol (str)

  • override (bool)

Return type:

Constant

noodlelab.downsample(array, max_side=256)[source]

Every n-th pixel so that neither of the first two axes exceeds max_side. Cheap and shape-preserving; for a real overview read less data instead (e.g. rasterio’s out_shape).

Parameters:
  • array (Any)

  • max_side (int)

Return type:

Any

noodlelab.error(message)[source]

Return from a .check function to report an error (blocks running).

Parameters:

message (str)

Return type:

Problem

noodlelab.image_preview(array, *, max_side=256, summary=None)[source]

A PNG thumbnail from a NumPy array, with no imaging dependencies.

Accepts (H, W) grayscale, (H, W, 3|4) RGB(A), and band-first (3|4, H, W) as rasterio returns it. Non-uint8 data is stretched between the 2nd and 98th percentiles; NaN and inf become black.

Parameters:
  • array (Any)

  • max_side (int)

  • summary (str | None)

Return type:

Preview

noodlelab.node(func: F) → F[source]
noodlelab.node(*, id: str | None = None, title: str | None = None, category: str = 'Utility', description: str | None = None, color: str | None = None, outputs: Sequence[str] | None = None, version: int = 1, cacheable: bool = True, checkpoint: bool = True, fold: bool = False, sample: bool | None = None, converter: bool | Literal['implicit'] = False, cost: float = 1.0, vectorized: bool = False, views: Sequence[Literal['processing', 'report']] | None = None) → Callable[[F], F]

Mark a function as a node. Usable bare (@node) or with options.

version: bump it to invalidate cached results by hand (editing the function’s source already does this automatically). cacheable: set False when the output can change for the same inputs (unseeded randomness, clocks, hardware, files the node finds by itself). Files named by Path/FileRef inputs need no flag: their size and modification time are part of the cache key. checkpoint: set False to keep results in memory only, e.g. for huge intermediates that are quick to recompute. fold: the node is cheap and pure (a constant such as “File Path” or “Number”), so it is also evaluated while the graph is edited and downstream checks and probes receive its value as if it were typed in. sample: set False to never run the node on samples while editing (expensive work, side effects, statistics a sample would misstate). By default cacheable nodes without a RunContext preview on samples, except nodes that read files (give those a probe); True includes them anyway. converter: True makes the node a way to turn its first input’s type into its output’s type; the editor inserts it when such a link is drawn. "implicit" (lossless, no settings) applies it on links, without a node. cost ranks converter chains (lower is preferred). vectorized: the function also works on many Monte Carlo trials at once: given arrays with one element per trial where it takes a number, it returns one element per trial, element by element (no if on a value), without side effects, and raises where it would raise for some trial alone. A Monte Carlo evaluation whose nodes are all vectorized runs its trials in batches; otherwise, or if a batch disagrees with trials run one at a time, trial by trial. views: the canvases whose add-node menus offer the node, “processing” and/or “report”; by default Report nodes are offered on the Reporting tab, Text nodes on both and all others on the Processing tab. Any node can be placed on either tab (the editor can be set to offer them all).

The returned function gets decorators for edit-time helpers: .check (live health checks), .probe (a quick preview and metadata, e.g. the first rows of a file), .options(input) (dynamic dropdown choices), .uses_constants (the named constants it reads, for its cache key) and, for converters, .accepts (which values the conversion fits).

Parameters:
  • func (Any)

  • options (Any)

Return type:

Any

noodlelab.register_codec(py_type, name, *, save, load, suffix='.bin')[source]

Store values of a type (or a qualified class name, for optional dependencies) in checkpoints with save/load instead of pickle. The name is written into every checkpoint, so keep it stable.

Parameters:
  • py_type (type | str)

  • name (str)

  • save (Callable[[Any, IO[bytes]], dict[str, Any] | None])

  • load (Callable[[IO[bytes], dict[str, Any]], Any])

  • suffix (str)

Return type:

Codec

noodlelab.register_fields(py_type, *, names, type, get)[source]

Describe the fields of a type (or a qualified class name) for split outputs.

names(ref, meta): the field names, usually from the output’s meta (meta may be None). type(ref, name, meta): a field’s type hint, also asked without meta when a saved link is checked; return Any when it cannot be known then. get(value, name): read a field of a real value.

Parameters:
  • py_type (type | str)

  • names (Callable[[TypeRef, dict[str, Any] | None], list[str]])

  • type (Callable[[TypeRef, str, dict[str, Any] | None], Any])

  • get (Callable[[Any, str], Any])

Return type:

None

noodlelab.register_meta(py_type)[source]

Register a function describing values of a type (or a qualified class name, for optional dependencies) as a small JSON dict.

Parameters:

py_type (type | str)

Return type:

Callable[[Callable[[Any], dict[str, Any] | None]], Callable[[Any], dict[str, Any] | None]]

noodlelab.register_preview(py_type)[source]
Parameters:

py_type (type | str)

Return type:

Callable[[Callable[[Any, PreviewContext], Preview]], Callable[[Any, PreviewContext], Preview]]

noodlelab.register_sampler(py_type)[source]

Register fn(value, size) -> smaller value of the same type for a type (or a qualified class name). size is roughly the number of rows or elements along each axis to keep.

Parameters:

py_type (type | str)

Return type:

Callable[[Callable[[Any, int], Any]], Callable[[Any, int], Any]]

noodlelab.register_type(py_type, name, color=None, description='')[source]

Map a Python type (or its qualified name) to a socket type.

Parameters:
  • py_type (type | str)

  • name (str)

  • color (str | None)

  • description (str)

Return type:

str

noodlelab.register_unit(newtype, unit)[source]

Give a NewType a unit: its sockets then connect to every type of the same dimension, converted on the link. Returns the NewType.

Parameters:
  • newtype (Any)

  • unit (str)

Return type:

Any

noodlelab.table_preview(columns, rows, *, total_rows=None, summary=None)[source]

A table preview without pandas, e.g. from csv.reader or a database cursor.

Parameters:
  • columns (list[Any])

  • rows (list[list[Any]])

  • total_rows (int | None)

  • summary (str | None)

Return type:

Preview

noodlelab.unit_type(name, unit, base=<class 'float'>)[source]

NewType(name, base) with a unit: Metres = unit_type("Metres", "m").

Parameters:
  • name (str)

  • unit (str)

  • base (Any)

Return type:

Any

noodlelab.warn(message)[source]

Warn about the result the running node is computing: a fit whose parameters the data cannot tell apart, a matrix close to singular… The run goes on; the warning is shown on the node and kept in the run’s record. Outside a run (plain Python) it is a Python UserWarning.

Parameters:

message (str)

Return type:

None

noodlelab.warning(message)[source]

Return from a .check function to report a warning (does not block).

Parameters:

message (str)

Return type:

Problem