Core Concepts¶
This page explains the core concepts behind veriq and how they work together.
The Spreadsheet Analogy¶
veriq is like a smart spreadsheet for engineering calculations:
| Spreadsheet | veriq | Description |
|---|---|---|
| Workbook | Project | Top-level container |
| Sheet | Scope | Logical grouping (e.g., "Power", "Thermal") |
| Input cells | Model | Design parameters you provide |
| Formula cells | Calculation | Computed values |
| Conditional formatting | Verification | Requirement checks |
| Cell comments | Requirement | Engineering constraints with traceability |
Project¶
A Project is the top-level container for your engineering analysis:
A project contains one or more scopes and manages the overall dependency graph.
Scope¶
A Scope groups related models, calculations, and verifications:
power = vq.Scope("Power")
thermal = vq.Scope("Thermal")
project.add_scope(power)
project.add_scope(thermal)
Scopes typically represent subsystems like Power, Thermal, Structure, or AOCS.
Model¶
A Model defines the input data structure using Pydantic:
Key points:
- Use
@scope.root_model()to register a model - Each scope has exactly one root model
- Models are loaded from TOML input files
Calculation¶
A Calculation computes derived values from inputs:
class SolarOutput(BaseModel):
power: float
heat: float
@power.calculation()
def calculate_solar(
area: Annotated[float, vq.Ref("$.solar_panel_area")],
) -> SolarOutput:
power = area * 1361.0 * 0.3
heat = area * 1361.0 * 0.7
return SolarOutput(power=power, heat=heat)
Key points:
- Use
@scope.calculation()to register - Parameters use
Annotated[Type, vq.Ref(...)]to declare dependencies - Return a Pydantic model with the results
- veriq automatically determines execution order
Verification¶
A Verification checks that requirements are met:
@power.verification()
def verify_power_margin(
power: Annotated[float, vq.Ref("@calculate_solar.power")],
) -> bool:
return power >= 500.0 # Minimum 500W
Key points:
- Use
@scope.verification()to register - Return
Truefor pass,Falsefor fail - Run with
--verifyflag to execute verifications
Requirements¶
Requirements define engineering constraints that your design must satisfy. They form the traceability link between what must be achieved and how it's verified:
# Define a requirement
power.requirement(
"REQ-PWR-001",
"Battery capacity must be at least 100 Wh.",
verified_by=[vq.Ref("?verify_battery")],
)
Key points:
- Use
scope.requirement()to define requirements - Each requirement has an ID and description
- Link verifications with
verified_byusingvq.Ref("?verification_name") - Use context managers for hierarchical requirements
Hierarchical Requirements¶
Real engineering projects have requirements at multiple levels:
# Parent requirement with children
with system.requirement("REQ-SYS-001", "System requirements."):
power.requirement("REQ-PWR-001", "Power requirement.", verified_by=[vq.Ref("?verify_power")])
thermal.requirement("REQ-TH-001", "Thermal requirement.", verified_by=[vq.Ref("?verify_temp")])
Requirement Statuses¶
| Status | Symbol | Meaning |
|---|---|---|
| VERIFIED | ✓ | Has direct verifications, all passed |
| SATISFIED | ○ | No direct verifications, all children pass |
| FAILED | ✗ | Some verification or child failed |
| NOT_VERIFIED | ? | Leaf requirement with no verifications |
Cross-Scope Requirements¶
Add children to existing requirements from different scopes:
# Fetch existing requirement and add children
with system.fetch_requirement("REQ-SYS-001"):
power.requirement("REQ-PWR-002", "Another power requirement.", verified_by=[vq.Ref("?verify")])
You can also reference verifications from other scopes:
# Reference a verification in another scope
system.requirement(
"REQ-SYS-002",
"Cross-scope requirement.",
verified_by=[vq.Ref("?verify_power", scope="Power")],
)
Requirement Dependencies¶
Declare that one requirement depends on another:
req_parent = power.requirement("REQ-PWR-001", "Parent requirement.")
with power.requirement("REQ-PWR-002", "Child requirement."):
vq.depends(req_parent) # If REQ-PWR-001 fails, REQ-PWR-002 also fails
References¶
References (vq.Ref) declare where to find input values:
Model References¶
# Current scope's model field
vq.Ref("$.battery_capacity")
# Nested field
vq.Ref("$.design.solar_panel.area")
# Other scope's model field
vq.Ref("$.battery_capacity", scope="Power")
Calculation References¶
# Current scope's calculation output
vq.Ref("@calculate_solar.power")
# Other scope's calculation output
vq.Ref("@calculate_temperature.max_temp", scope="Thermal")
Table References¶
# Single-key table entry
vq.Ref("$.power_consumption[nominal]")
# Multi-key table entry
vq.Ref("$.peak_power[launch,nominal]")
Reference Syntax Summary¶
| Pattern | Meaning |
|---|---|
$ |
Root of current scope's model |
$.field |
Field in model |
$.field.subfield |
Nested field |
@calc |
Calculation output |
@calc.field |
Field in calculation output |
[key] |
Table entry |
scope="Name" |
Look in different scope |
Tables¶
Tables handle multi-dimensional data indexed by enums:
from enum import StrEnum
class Mode(StrEnum):
NOMINAL = "nominal"
SAFE = "safe"
class PowerModel(BaseModel):
consumption: vq.Table[Mode, float]
Single-Key Tables¶
# Definition
power: vq.Table[Mode, float]
# TOML
[Scope.model.power]
nominal = 100.0
safe = 50.0
# Access
power[Mode.NOMINAL] # Returns 100.0
Multi-Key Tables¶
# Definition
power: vq.Table[tuple[Phase, Mode], float]
# TOML
[Scope.model.power]
"launch,nominal" = 100.0
"cruise,safe" = 50.0
# Access
power[(Phase.LAUNCH, Mode.NOMINAL)] # Returns 100.0
Table Verifications¶
When a verification returns vq.Table[K, bool], each entry becomes a separate result:
@power.verification()
def verify_margin(
margin: Annotated[vq.Table[Mode, float], vq.Ref("@calc.margin")],
) -> vq.Table[Mode, bool]:
return vq.Table({mode: margin[mode] > 0 for mode in Mode})
External Data (FileRef)¶
FileRef allows you to reference external files in your models with automatic checksum tracking for reproducibility:
from veriq import FileRef
class DataModel(BaseModel):
config_file: FileRef
calibration_data: FileRef
Why Use FileRef?¶
- Reproducibility - Checksums ensure the same file content is used across runs
- Change Detection - veriq warns when referenced files change
- Clean Separation - Keep large data files (CSV, binary) separate from TOML input
Using FileRef in TOML¶
[Scope.model.config_file]
path = "data/config.json"
checksum = "sha256:abc123..." # Added by veriq after first run
[Scope.model.calibration_data]
path = "calibration/sensor_data.csv"
On the first run, veriq computes and stores the checksum. On subsequent runs, it validates that the file hasn't changed.
Accessing File Content in Calculations¶
In calculations, you receive the FileRef object and access data via its path attribute:
@scope.calculation()
def process_config(
config_file: Annotated[FileRef, vq.Ref("$.config_file")],
) -> ConfigResult:
# Read file content via path
content = config_file.path.read_text()
data = json.loads(content)
return ConfigResult(...)
Relative Paths¶
Relative paths in TOML are resolved relative to the TOML file's directory:
project/
├── input.toml # Contains path = "data/config.json"
└── data/
└── config.json # This file is referenced
Checksum Validation¶
| Scenario | Behavior |
|---|---|
| First run (no checksum) | Computes and stores checksum |
| Checksum matches | Proceeds normally |
| Checksum mismatch | Warns user that file changed |
Cross-Scope Dependencies¶
When a calculation or verification references another scope, declare it with imports:
@thermal.calculation(imports=["Power"])
def calculate_temperature(
heat: Annotated[float, vq.Ref("@calculate_solar.heat", scope="Power")],
) -> ThermalOutput:
...
The imports parameter:
- Declares dependencies on other scopes
- Enables cross-scope references with
scope="Name" - Helps veriq build the correct dependency graph
Dependency Graph¶
veriq builds a dependency graph from your references:
- Parse all
vq.Refannotations - Build edges between calculations
- Topologically sort for execution order
- Execute in order, passing results forward
This means:
- Calculations run in the correct order automatically
- Circular dependencies are detected and reported
- Cross-scope dependencies work seamlessly
Execution Flow¶
When you run veriq calc:
- Load - Parse TOML input into Pydantic models
- Build - Construct dependency graph from references
- Sort - Topologically sort calculations
- Execute - Run calculations in order
- Verify - Run verifications (if
--verify) - Export - Write results to output TOML