Initialize Track 2 Rust workspace
This commit is contained in:
parent
4f36070a7f
commit
b761cac274
|
|
@ -10,6 +10,7 @@ venv/
|
||||||
build/
|
build/
|
||||||
dist/
|
dist/
|
||||||
*.egg-info/
|
*.egg-info/
|
||||||
|
target/
|
||||||
|
|
||||||
runs/state/*
|
runs/state/*
|
||||||
!runs/state/.gitkeep
|
!runs/state/.gitkeep
|
||||||
|
|
@ -23,3 +24,4 @@ runs/scratch/*
|
||||||
*.log
|
*.log
|
||||||
*.tmp
|
*.tmp
|
||||||
*.swp
|
*.swp
|
||||||
|
Cargo.lock
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
[workspace]
|
||||||
|
members = ["rust/track2-core"]
|
||||||
|
resolver = "2"
|
||||||
|
|
||||||
|
[workspace.package]
|
||||||
|
edition = "2024"
|
||||||
|
license = "MIT"
|
||||||
|
authors = ["welsberr <welsberr@gmail.com>"]
|
||||||
|
repository = "https://git.cns.fyi/welsberr/renunney"
|
||||||
|
|
||||||
9
Makefile
9
Makefile
|
|
@ -15,6 +15,7 @@ FIG1_M10 := $(REPO_ROOT)/config/track1_figure1_paper_M_1_0.json
|
||||||
FIG1_M100 := $(REPO_ROOT)/config/track1_figure1_paper_M_10_0.json
|
FIG1_M100 := $(REPO_ROOT)/config/track1_figure1_paper_M_10_0.json
|
||||||
|
|
||||||
.PHONY: help init doctor list-jobs run-one run-loop run-loop-one collate-figure1 track1-sim-smoke \
|
.PHONY: help init doctor list-jobs run-one run-loop run-loop-one collate-figure1 track1-sim-smoke \
|
||||||
|
rust-check rust-test \
|
||||||
submit-figure1-m005 submit-figure1-m025 submit-figure1-m05 submit-figure1-m10 submit-figure1-m100 \
|
submit-figure1-m005 submit-figure1-m025 submit-figure1-m05 submit-figure1-m10 submit-figure1-m100 \
|
||||||
submit-all-figure1 status results-tree
|
submit-all-figure1 status results-tree
|
||||||
|
|
||||||
|
|
@ -24,6 +25,8 @@ help:
|
||||||
@echo " doctor Show key paths and verify local orchestration and Track 1 paths"
|
@echo " doctor Show key paths and verify local orchestration and Track 1 paths"
|
||||||
@echo " list-jobs List jobs in the local registry"
|
@echo " list-jobs List jobs in the local registry"
|
||||||
@echo " track1-sim-smoke Run one local Track 1 simulation through renunney runner"
|
@echo " track1-sim-smoke Run one local Track 1 simulation through renunney runner"
|
||||||
|
@echo " rust-check Run cargo check for the Track 2 Rust workspace"
|
||||||
|
@echo " rust-test Run cargo test for the Track 2 Rust workspace"
|
||||||
@echo " run-one Claim and run one queued job"
|
@echo " run-one Claim and run one queued job"
|
||||||
@echo " run-loop Run worker loop until queue empty"
|
@echo " run-loop Run worker loop until queue empty"
|
||||||
@echo " run-loop-one Run exactly one queued job through the worker loop"
|
@echo " run-loop-one Run exactly one queued job through the worker loop"
|
||||||
|
|
@ -59,6 +62,12 @@ track1-sim-smoke:
|
||||||
mkdir -p $(MPLCONFIGDIR)
|
mkdir -p $(MPLCONFIGDIR)
|
||||||
MPLCONFIGDIR=$(MPLCONFIGDIR) $(PYTHON) $(TRACK1) --mode simulate --K 5000 --N0 50 --n 1 --u 5e-6 --R 10 --T 40 --epochs 8 --seed 1
|
MPLCONFIGDIR=$(MPLCONFIGDIR) $(PYTHON) $(TRACK1) --mode simulate --K 5000 --N0 50 --n 1 --u 5e-6 --R 10 --T 40 --epochs 8 --seed 1
|
||||||
|
|
||||||
|
rust-check:
|
||||||
|
cargo check --manifest-path $(REPO_ROOT)/Cargo.toml
|
||||||
|
|
||||||
|
rust-test:
|
||||||
|
cargo test --manifest-path $(REPO_ROOT)/Cargo.toml
|
||||||
|
|
||||||
run-one:
|
run-one:
|
||||||
mkdir -p $(MPLCONFIGDIR)
|
mkdir -p $(MPLCONFIGDIR)
|
||||||
MPLCONFIGDIR=$(MPLCONFIGDIR) $(PYTHON) $(ORCH) run-one --db $(DB) --result-root $(RESULT_ROOT) --scratch-root $(SCRATCH_ROOT)
|
MPLCONFIGDIR=$(MPLCONFIGDIR) $(PYTHON) $(ORCH) run-one --db $(DB) --result-root $(RESULT_ROOT) --scratch-root $(SCRATCH_ROOT)
|
||||||
|
|
|
||||||
19
README.md
19
README.md
|
|
@ -73,6 +73,7 @@ runtime now lives in `renunney`.
|
||||||
- [docs/MIGRATION.md](/mnt/CIFS/pengolodh/Docs/Projects/renunney/docs/MIGRATION.md)
|
- [docs/MIGRATION.md](/mnt/CIFS/pengolodh/Docs/Projects/renunney/docs/MIGRATION.md)
|
||||||
- [docs/WORKFLOW.md](/mnt/CIFS/pengolodh/Docs/Projects/renunney/docs/WORKFLOW.md)
|
- [docs/WORKFLOW.md](/mnt/CIFS/pengolodh/Docs/Projects/renunney/docs/WORKFLOW.md)
|
||||||
- [docs/NUNNEY_ANALYSIS.md](/mnt/CIFS/pengolodh/Docs/Projects/renunney/docs/NUNNEY_ANALYSIS.md)
|
- [docs/NUNNEY_ANALYSIS.md](/mnt/CIFS/pengolodh/Docs/Projects/renunney/docs/NUNNEY_ANALYSIS.md)
|
||||||
|
- [docs/TRACK2_RUST.md](/mnt/CIFS/pengolodh/Docs/Projects/renunney/docs/TRACK2_RUST.md)
|
||||||
|
|
||||||
## Layout
|
## Layout
|
||||||
|
|
||||||
|
|
@ -107,6 +108,13 @@ Run one local Track 1 simulation:
|
||||||
make track1-sim-smoke
|
make track1-sim-smoke
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Verify the Track 2 Rust workspace:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make rust-check
|
||||||
|
make rust-test
|
||||||
|
```
|
||||||
|
|
||||||
Submit a paper-scale Figure 1 treatment:
|
Submit a paper-scale Figure 1 treatment:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
@ -127,9 +135,12 @@ make collate-figure1
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
The Track 1 runtime and orchestration stack are now local to `renunney`. The
|
The Track 1 runtime and orchestration stack are now local to `renunney`.
|
||||||
next major step is no longer migration of Track 1 code; it is either:
|
Track 2 has also started: the repo now includes a Rust workspace and an
|
||||||
|
initial `track2-core` crate for threshold-centered abstractions. The current
|
||||||
|
next major steps are:
|
||||||
|
|
||||||
- hardening multi-host orchestration,
|
- hardening multi-host orchestration for Track 1 replication,
|
||||||
- organizing publication-quality replication outputs,
|
- organizing publication-quality replication outputs,
|
||||||
- or starting the Rust-backed Track 2 path.
|
- and expanding the Rust Track 2 core from threshold abstractions into a
|
||||||
|
simulation-state and estimation kernel.
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,9 @@ Operational code still lives in:
|
||||||
`renunney` currently acts as:
|
`renunney` currently acts as:
|
||||||
|
|
||||||
- a clean git repository,
|
- a clean git repository,
|
||||||
- a run-control wrapper,
|
- the local home of the Track 1 runtime and orchestration stack,
|
||||||
- a stable place for deployment and orchestration commands,
|
- a stable place for deployment and orchestration commands,
|
||||||
- the eventual destination for migrated code.
|
- and the starting point for the Track 2 Rust implementation.
|
||||||
|
|
||||||
## Recommended Migration Order
|
## Recommended Migration Order
|
||||||
|
|
||||||
|
|
@ -44,6 +44,10 @@ Operational code still lives in:
|
||||||
11. Track 1 runtime path is now fully local to `renunney`.
|
11. Track 1 runtime path is now fully local to `renunney`.
|
||||||
12. Reduce or remove any remaining compatibility-layer imports outside the Track 1 runtime path.
|
12. Reduce or remove any remaining compatibility-layer imports outside the Track 1 runtime path.
|
||||||
13. Migrate docs and example configs last, after path references are updated.
|
13. Migrate docs and example configs last, after path references are updated.
|
||||||
|
14. Start Track 2 in-repo as a Rust workspace:
|
||||||
|
- `Cargo.toml`
|
||||||
|
- `rust/track2-core`
|
||||||
|
- `docs/TRACK2_RUST.md`
|
||||||
|
|
||||||
## Constraint
|
## Constraint
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
# Track 2 Rust Plan
|
||||||
|
|
||||||
|
Updated: 2026-04-11
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
This note defines the initial Rust entry point for Track 2 in `renunney`.
|
||||||
|
|
||||||
|
Track 2 is not a line-by-line translation of Nunney's published threshold
|
||||||
|
heuristic. It is the modern path: explicit threshold definitions, clearer
|
||||||
|
simulation contracts, and a performant kernel.
|
||||||
|
|
||||||
|
## Why Rust
|
||||||
|
|
||||||
|
Rust is the preferred Track 2 kernel language because it directly addresses the
|
||||||
|
main engineering problems revealed by Track 1:
|
||||||
|
|
||||||
|
- heavy repeated stochastic simulation,
|
||||||
|
- threshold sweeps over many independent jobs,
|
||||||
|
- need for clear data structures and reproducible binaries,
|
||||||
|
- and a likely future need for Python bindings or service backends.
|
||||||
|
|
||||||
|
## Initial Scope
|
||||||
|
|
||||||
|
The first Rust step is intentionally narrow:
|
||||||
|
|
||||||
|
- create a Rust workspace,
|
||||||
|
- define a Track 2 core crate,
|
||||||
|
- and encode threshold-centered abstractions rather than immediately porting
|
||||||
|
the entire biological simulator.
|
||||||
|
|
||||||
|
This is the correct order because Track 2 should start from a clean statement
|
||||||
|
of what is being estimated.
|
||||||
|
|
||||||
|
## Current Crate
|
||||||
|
|
||||||
|
The initial crate is:
|
||||||
|
|
||||||
|
- `rust/track2-core`
|
||||||
|
|
||||||
|
Current contents:
|
||||||
|
|
||||||
|
- `ExtinctionCount`
|
||||||
|
- `ThresholdPoint`
|
||||||
|
- `ThresholdBracket`
|
||||||
|
- `ThresholdEstimate`
|
||||||
|
- `bracket_threshold(...)`
|
||||||
|
- `midpoint_threshold(...)`
|
||||||
|
|
||||||
|
These are placeholders for a modern threshold-estimation path where:
|
||||||
|
|
||||||
|
- extinction probability is explicit,
|
||||||
|
- bracketing is explicit,
|
||||||
|
- and the estimator is separate from the simulation kernel.
|
||||||
|
|
||||||
|
## Next Rust Steps
|
||||||
|
|
||||||
|
1. Add a simulation-state model for Track 2.
|
||||||
|
2. Add a trait or function contract for producing extinction probabilities from
|
||||||
|
repeated stochastic runs.
|
||||||
|
3. Add a threshold search strategy that consumes those estimates.
|
||||||
|
4. Add serialization-friendly input/output structs for orchestration.
|
||||||
|
5. Only then start porting heavy simulation loops from Python.
|
||||||
|
|
||||||
|
## Operational Targets
|
||||||
|
|
||||||
|
The repo Makefile should treat Rust as a first-class build/test surface:
|
||||||
|
|
||||||
|
- `make rust-test`
|
||||||
|
- `make rust-check`
|
||||||
|
|
||||||
|
That keeps Track 2 visible in daily workflow from the start.
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
[package]
|
||||||
|
name = "track2-core"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
license = "MIT"
|
||||||
|
authors = ["welsberr <welsberr@gmail.com>"]
|
||||||
|
description = "Track 2 threshold-centered cost-of-substitution simulation core"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "track2_core"
|
||||||
|
path = "src/lib.rs"
|
||||||
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
//! Track 2 core for the cost-of-substitution project.
|
||||||
|
//!
|
||||||
|
//! Track 2 is intentionally not a line-by-line reproduction of Nunney's
|
||||||
|
//! published threshold heuristic. Its goal is to provide a performant,
|
||||||
|
//! explicitly specified simulation and threshold-estimation substrate that can
|
||||||
|
//! later support richer kernels and cleaner inference.
|
||||||
|
|
||||||
|
pub mod threshold;
|
||||||
|
|
||||||
|
pub use threshold::{
|
||||||
|
ExtinctionCount,
|
||||||
|
ThresholdBracket,
|
||||||
|
ThresholdEstimate,
|
||||||
|
ThresholdPoint,
|
||||||
|
bracket_threshold,
|
||||||
|
midpoint_threshold,
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,140 @@
|
||||||
|
//! Threshold-centered helpers for Track 2.
|
||||||
|
//!
|
||||||
|
//! These functions do not assume Nunney's published 20-run acceptance rule.
|
||||||
|
//! They work with explicit extinction probabilities or extinction counts over a
|
||||||
|
//! set of trials, which is the right abstraction for the modern Track 2 path.
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct ExtinctionCount {
|
||||||
|
pub extinct: u32,
|
||||||
|
pub total: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExtinctionCount {
|
||||||
|
pub fn new(extinct: u32, total: u32) -> Self {
|
||||||
|
assert!(total > 0, "total trials must be positive");
|
||||||
|
assert!(extinct <= total, "extinct count cannot exceed total trials");
|
||||||
|
Self { extinct, total }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn survival_probability(self) -> f64 {
|
||||||
|
1.0 - (self.extinct as f64 / self.total as f64)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn extinction_probability(self) -> f64 {
|
||||||
|
self.extinct as f64 / self.total as f64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub struct ThresholdPoint {
|
||||||
|
pub t_value: f64,
|
||||||
|
pub extinction_probability: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ThresholdPoint {
|
||||||
|
pub fn new(t_value: f64, extinction_probability: f64) -> Self {
|
||||||
|
assert!(t_value > 0.0, "T must be positive");
|
||||||
|
assert!(
|
||||||
|
(0.0..=1.0).contains(&extinction_probability),
|
||||||
|
"extinction probability must lie in [0, 1]"
|
||||||
|
);
|
||||||
|
Self {
|
||||||
|
t_value,
|
||||||
|
extinction_probability,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub struct ThresholdBracket {
|
||||||
|
pub lower: ThresholdPoint,
|
||||||
|
pub upper: ThresholdPoint,
|
||||||
|
pub target_extinction_probability: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub struct ThresholdEstimate {
|
||||||
|
pub target_extinction_probability: f64,
|
||||||
|
pub estimated_t: f64,
|
||||||
|
pub lower_t: f64,
|
||||||
|
pub upper_t: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bracket_threshold(
|
||||||
|
lower: ThresholdPoint,
|
||||||
|
upper: ThresholdPoint,
|
||||||
|
target_extinction_probability: f64,
|
||||||
|
) -> Option<ThresholdBracket> {
|
||||||
|
assert!(
|
||||||
|
(0.0..=1.0).contains(&target_extinction_probability),
|
||||||
|
"target extinction probability must lie in [0, 1]"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
lower.t_value < upper.t_value,
|
||||||
|
"lower T must be strictly less than upper T"
|
||||||
|
);
|
||||||
|
|
||||||
|
let lower_delta = lower.extinction_probability - target_extinction_probability;
|
||||||
|
let upper_delta = upper.extinction_probability - target_extinction_probability;
|
||||||
|
|
||||||
|
if lower_delta == 0.0 || upper_delta == 0.0 || lower_delta.signum() != upper_delta.signum() {
|
||||||
|
Some(ThresholdBracket {
|
||||||
|
lower,
|
||||||
|
upper,
|
||||||
|
target_extinction_probability,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn midpoint_threshold(bracket: ThresholdBracket) -> ThresholdEstimate {
|
||||||
|
ThresholdEstimate {
|
||||||
|
target_extinction_probability: bracket.target_extinction_probability,
|
||||||
|
estimated_t: 0.5 * (bracket.lower.t_value + bracket.upper.t_value),
|
||||||
|
lower_t: bracket.lower.t_value,
|
||||||
|
upper_t: bracket.upper.t_value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn extinction_count_probabilities_are_consistent() {
|
||||||
|
let count = ExtinctionCount::new(3, 10);
|
||||||
|
assert!((count.extinction_probability() - 0.3).abs() < 1e-12);
|
||||||
|
assert!((count.survival_probability() - 0.7).abs() < 1e-12);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bracket_threshold_detects_crossing() {
|
||||||
|
let lower = ThresholdPoint::new(8.0, 0.8);
|
||||||
|
let upper = ThresholdPoint::new(12.0, 0.2);
|
||||||
|
let bracket = bracket_threshold(lower, upper, 0.5).expect("expected bracket");
|
||||||
|
assert_eq!(bracket.lower.t_value, 8.0);
|
||||||
|
assert_eq!(bracket.upper.t_value, 12.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bracket_threshold_rejects_non_crossing_points() {
|
||||||
|
let lower = ThresholdPoint::new(8.0, 0.8);
|
||||||
|
let upper = ThresholdPoint::new(12.0, 0.7);
|
||||||
|
assert!(bracket_threshold(lower, upper, 0.5).is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn midpoint_threshold_returns_bracket_midpoint() {
|
||||||
|
let bracket = ThresholdBracket {
|
||||||
|
lower: ThresholdPoint::new(8.0, 0.8),
|
||||||
|
upper: ThresholdPoint::new(12.0, 0.2),
|
||||||
|
target_extinction_probability: 0.5,
|
||||||
|
};
|
||||||
|
let estimate = midpoint_threshold(bracket);
|
||||||
|
assert_eq!(estimate.estimated_t, 10.0);
|
||||||
|
assert_eq!(estimate.lower_t, 8.0);
|
||||||
|
assert_eq!(estimate.upper_t, 12.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue