Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,18 @@ When using the `gh` CLI to view issues, PRs, or other GitHub objects:
- **ALWAYS** use the patch editing mechanism provided by the agent
- Shell text tools may be used for **read‑only analysis only**

### Rust Error Handling

- Do not introduce `Box<dyn std::error::Error>`, `Box<dyn Error>`, or `anyhow::Error` as fallible return types in production `src/` code, public doctests, examples, or benchmarks that demonstrate user-facing workflows
- Prefer `CdtResult<T>` and narrow `CdtError` variants with structured context for distinct I/O, serialization, validation, backend, checkpoint, or output failure modes
- `&dyn Error` is acceptable for `std::error::Error::source`, tests that verify standard error trait behavior, and lint fixtures that intentionally exercise forbidden generic-error patterns
- Detailed error-type guidance lives in `docs/dev/rust.md`

### Rust Import Hygiene

- Keep production module preambles free of test-only imports; place `#[cfg(test)]` imports inside the relevant `tests` module instead
- Detailed import guidance lives in `docs/dev/rust.md`

### Public API Preludes

- Keep `prelude::*` small and focused on common quick-start workflows.
Expand Down Expand Up @@ -145,7 +157,7 @@ just ci

Refer to `docs/dev/commands.md` for full details.

When adding or renaming Cargo examples, update `just validate-examples` markers as needed so CI keeps validating the user-facing example contracts.
When adding or renaming Cargo examples, update `just examples-validate` markers as needed so CI keeps validating the user-facing example contracts.

For tooling-alignment work, update `docs/dev/tooling-alignment.md` with the comparison and rationale before adding or changing config, workflow, or repository-rule files.

Expand Down
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,15 @@ harness = false
[dependencies]
clap = { version = "4.6.1", features = [ "derive" ] }
delaunay = "0.7.6"
markov-chain-monte-carlo = "0.3.0"
markov-chain-monte-carlo = { version = "0.3.0", features = [ "serde" ] }
dirs = "6.0.0"
env_logger = "0.11.10"
float-ord = "0.3.2"
log = "0.4.29"
num-traits = "0.2.19"
rand = "0.10.1"
rand = { version = "0.10.1", features = [ "serde" ] }
serde = { version = "1.0.228", features = [ "derive" ] }
serde_json = "1.0.145"
thiserror = "2.0.18"

[dev-dependencies]
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ The library leverages high-performance [Delaunay triangulation] backends and pro
- [x] Regge action calculation with configurable coupling constants
- [x] Alexander/Pachner-style local move proposals with causal constraints
- [x] Volume-profile, Hausdorff-dimension, and spectral-dimension observables for CDT analysis
- [x] CSV/JSON simulation output for external analysis workflows
- [x] Resumable serde-backed CDT/MCMC checkpoints for durable chain continuation
- [x] Focused public preludes for simulation, triangulation, geometry, action, and observables
- [x] Command-line interface, examples, Criterion benchmarks, and CI-aligned validation tooling
- [x] Cross-platform compatibility: Linux, macOS, Windows
Expand Down
3 changes: 2 additions & 1 deletion docs/code_organization.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ causal-triangulations/
│ │ └── performance_test.sh
│ ├── basic_cdt.rs
│ ├── find_good_seeds.rs
│ └── observables.rs
│ ├── observables.rs
│ └── output_and_checkpoint.rs
├── proptest-regressions/
│ └── cdt/
│ └── triangulation.txt
Expand Down
8 changes: 4 additions & 4 deletions docs/dev/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ Validate with:

```bash
just examples
just validate-examples
just examples-validate
```

Examples must:
Expand All @@ -155,7 +155,7 @@ Examples must:
- run successfully
- demonstrate correct API usage

`just validate-examples` additionally checks stable output markers for user-facing Cargo examples. Keep those markers semantic rather than exact numeric values so simulation output can evolve without making the example contract brittle.
`just examples-validate` additionally checks stable output markers for user-facing Cargo examples. Keep those markers semantic rather than exact numeric values so simulation output can evolve without making the example contract brittle.

When adding or renaming a Cargo example, update `scripts/run_all_examples.sh` `validate_example_output()` with stable semantic output markers, or intentionally document why success-only validation is sufficient for that example.

Expand Down Expand Up @@ -291,7 +291,7 @@ just test-python # pytest
| Run all tests | `just test-all` |
| Run Python tests | `just test-python` |
| Run examples | `just examples` |
| Validate examples | `just validate-examples` |
| Validate examples | `just examples-validate` |
| Run full CI | `just ci` |
| Pre-commit check | `just commit-check` |

Expand All @@ -302,7 +302,7 @@ just test-python # pytest
| Changed files | Command |
| ------------- | -------------------------------------- |
| `tests/` | `just test-integration` (or `just ci`) |
| `examples/` | `just validate-examples` |
| `examples/` | `just examples-validate` |
| `benches/` | `just bench-compile` |
| `src/` | `just test` |
| `scripts/` | `just test-python` |
Expand Down
4 changes: 4 additions & 0 deletions docs/dev/rust.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ Keep error layers orthogonal. Invalid input or handles, unsupported operations,

Public error enums must be `#[non_exhaustive]` so new variants remain additive.

Do not use `Box<dyn std::error::Error>`, `Box<dyn Error>`, or `anyhow::Error` as fallible return types in production `src/` code, public doctests, examples, or benchmarks that demonstrate user-facing workflows. Prefer the crate's typed `CdtResult<T>` and add a narrow `CdtError` variant when a distinct I/O, serialization, validation, backend, or checkpoint failure mode is otherwise only representable as a generic error. `&dyn Error` is acceptable for implementing `std::error::Error::source`, for tests that explicitly verify the standard error trait implementation, and for lint fixtures that exercise the forbidden pattern.

Example:

```rust
Expand All @@ -164,6 +166,8 @@ Always import types at the top of the module rather than using fully‑qualified

Group imports from the same module into a single `use` statement with braces.

Do not put test-only imports in the production module preamble. Move `#[cfg(test)]` imports into the relevant `tests` module so normal builds do not carry test-only style noise at the top of implementation files.

If a test module already has `use super::*;`, do not re‑import items that are already brought into scope by the parent module's imports.

---
Expand Down
4 changes: 2 additions & 2 deletions docs/dev/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,12 +199,12 @@ Before proposing patches agents should run:
just ci
```

The `ci` recipe runs `check bench-compile test-all validate-examples`, which enforces:
The `ci` recipe runs `check bench-compile test-all examples-validate`, which enforces:

- **check** (via `lint`): formatting, clippy, documentation builds, markdown, spelling, config validation (JSON, TOML, YAML, GitHub Actions)
- **bench-compile**: benchmarks compile without warnings under `-D warnings`
- **test-all**: unit tests, doc tests, integration tests, and Python tests (pytest)
- **validate-examples**: all Cargo examples run successfully and user-facing examples emit stable output markers
- **examples-validate**: all Cargo examples run successfully and user-facing examples emit stable output markers

---

Expand Down
2 changes: 1 addition & 1 deletion docs/dev/tooling-alignment.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ The useful updates ported from MCMC are:
- the MCMC-style `cliff.toml` template and `just changelog-unreleased <version>` flow, adapted to keep CDT's changelog archive step and avoid temporary local release tags;
- a Semgrep rule that rejects `NaN` and infinity defaults after failed floating-point conversions, with a regression fixture under `tests/semgrep/`.
- production-only Rust Semgrep rules that reject bare `unwrap()` and explicit `panic!` in non-test `src/` code while preserving idiomatic fail-fast usage in tests, doctests, examples, and benchmark setup.
- a `validate-examples` recipe that runs Cargo examples and verifies stable output markers for the user-facing example contracts.
- an `examples-validate` recipe that runs Cargo examples and verifies stable output markers for the user-facing example contracts.

## Deferred Updates

Expand Down
109 changes: 109 additions & 0 deletions examples/output_and_checkpoint.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
#![forbid(unsafe_code)]

//! Example: writing simulation output files and round-tripping a CDT checkpoint.
//!
//! This example runs a short CDT simulation, writes the configured CSV and JSON
//! outputs, and serializes the final triangulation as a serde checkpoint.

use causal_triangulations::prelude::simulation::*;
use serde_json::{Value, from_str, to_string};
use std::env;
use std::fs;
use std::path::Path;
use std::process;

fn main() -> CdtResult<()> {
let output_dir =
env::temp_dir().join(format!("causal-triangulations-output-{}", process::id()));
let csv_path = output_dir.join("measurements.csv");
let json_path = output_dir.join("summary.json");

let config = CdtConfig {
simulate: true,
steps: 4,
thermalization_steps: 0,
measurement_frequency: 1,
seed: Some(13),
output_csv: Some(csv_path.clone()),
output_json: Some(json_path.clone()),
..CdtConfig::new(12, 3)
};

let results = run_simulation(&config)?;

let csv = read_output(&csv_path, "CSV")?;
let summary_json = read_output(&json_path, "JSON")?;
let summary: Value = from_str(&summary_json).map_err(|err| CdtError::OutputReadFailed {
path: json_path.display().to_string(),
format: "JSON".to_string(),
detail: err.to_string(),
})?;
assert!(csv.starts_with("step,action,vertices,edges,triangles,accepted,delta_action\n"));
assert_eq!(summary["config"]["vertices"], config.vertices);
assert_eq!(
summary["final_triangulation"]["time_slices"],
config.timeslices
);

let checkpoint = to_string(&results.triangulation).map_err(|err| {
CdtError::CheckpointSerializationFailed {
operation: "serialize".to_string(),
target: "final triangulation".to_string(),
detail: err.to_string(),
}
})?;
let restored: CdtTriangulation2D =
from_str(&checkpoint).map_err(|err| CdtError::CheckpointSerializationFailed {
operation: "deserialize".to_string(),
target: "final triangulation".to_string(),
detail: err.to_string(),
})?;
restored.validate_topology()?;
restored.validate_foliation()?;
restored.validate_causality()?;
restored.validate_cell_classification()?;

let mcmc_checkpoint = MetropolisAlgorithm::new(
MetropolisConfig::new(1.0, 2, 0, 1).with_seed(13),
ActionConfig::default(),
)
.run_to_checkpoint(CdtTriangulation2D::from_cdt_strip(4, 3)?)?;
let checkpoint_json =
to_string(&mcmc_checkpoint).map_err(|err| CdtError::CheckpointSerializationFailed {
operation: "serialize".to_string(),
target: "MCMC state".to_string(),
detail: err.to_string(),
})?;
let restored_checkpoint: CdtMcmcCheckpoint =
from_str(&checkpoint_json).map_err(|err| CdtError::CheckpointSerializationFailed {
operation: "deserialize".to_string(),
target: "MCMC state".to_string(),
detail: err.to_string(),
})?;
let resumed = MetropolisAlgorithm::new(
MetropolisConfig::new(1.0, 2, 0, 1).with_seed(999),
ActionConfig::default(),
)
.resume_from_checkpoint(restored_checkpoint)?;

println!("CSV output rows: {}", csv.lines().count().saturating_sub(1));
println!(
"JSON summary measurements: {}",
summary["measurements"].as_array().map_or(0, Vec::len)
);
println!("Checkpoint roundtrip vertices: {}", restored.vertex_count());
println!("Resumed MCMC checkpoint steps: {}", resumed.steps.len());
println!("Output and checkpoint example completed successfully!");

let _ = fs::remove_dir_all(output_dir);
Ok(())
}

/// Read an example output file and preserve the path and format in typed errors.
fn read_output(path: &Path, format: &'static str) -> CdtResult<String> {
fs::read_to_string(path).map_err(|err| CdtError::OutputReadFailed {
path: path.display().to_string(),
format: format.to_string(),
detail: err.to_string(),
})
}
6 changes: 3 additions & 3 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ check-fast:

# CI simulation: comprehensive validation (matches .github/workflows/ci.yml)
# Runs: checks + all tests (Rust + Python) + validated examples + bench compile
ci: check bench-compile test-all validate-examples
ci: check bench-compile test-all examples-validate
@echo "🎯 CI checks complete!"

# CI with performance baseline
Expand Down Expand Up @@ -200,7 +200,7 @@ doc-check:
examples:
./scripts/run_all_examples.sh

validate-examples:
examples-validate:
./scripts/run_all_examples.sh --validate

# Fix (mutating): apply formatters/auto-fixes
Expand Down Expand Up @@ -232,7 +232,7 @@ help-workflows:
@echo " just test-cli # CLI integration tests only"
@echo " just test-examples # Compile all examples as tests"
@echo " just examples # Run all example scripts"
@echo " just validate-examples # Run examples and validate stable output markers"
@echo " just examples-validate # Run examples and validate stable output markers"
@echo " just coverage # Generate coverage report (HTML)"
@echo " just coverage-ci # Generate coverage for CI (XML)"
@echo ""
Expand Down
6 changes: 6 additions & 0 deletions scripts/run_all_examples.sh
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,12 @@ validate_example_output() {
require_marker "$example" "$output" "Final Hausdorff-dimension estimate"
require_marker "$example" "$output" "Final spectral-dimension estimate"
;;
output_and_checkpoint)
require_marker "$example" "$output" "CSV output rows"
require_marker "$example" "$output" "JSON summary measurements"
require_marker "$example" "$output" "Resumed MCMC checkpoint steps"
require_marker "$example" "$output" "Output and checkpoint example completed successfully"
;;
find_good_seeds)
require_marker "$example" "$output" "SEED VALIDATION"
require_marker "$example" "$output" "ADDITIONAL SEED TESTING"
Expand Down
3 changes: 2 additions & 1 deletion src/cdt/action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//! which is based on the Regge calculus formulation of general relativity.

use crate::errors::{CdtError, CdtResult};
use serde::{Deserialize, Serialize};

/// Calculates the 2D Regge Action for a given triangulation.
///
Expand Down Expand Up @@ -57,7 +58,7 @@ pub fn compute_regge_action(
}

/// Configuration for CDT action parameters.
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ActionConfig {
/// Coupling constant for vertices (κ₀)
pub coupling_0: f64,
Expand Down
Loading
Loading