Compare commits

...

3 Commits

Author SHA1 Message Date
88eb05ae94 Add 1D random walk dynamics and example
All checks were successful
CI / Format (push) Successful in 1m9s
CI / Clippy (push) Successful in 2m27s
CI / Test (push) Successful in 2m42s
Implement RandomWalk1D dynamics for lattice-based
simulations and provide an example that demonstrates
diffusive scaling. Move prelude into lib.rs for simpler
module organization. Use () for lattice momentum since
discrete walks don't have meaningful momenta.
2026-05-05 19:28:06 +09:00
8d2d261564 Add simul-lattice crate for discrete space simulations 2026-05-05 00:50:37 +09:00
dffa67d505 docs: reorder M0+ — lattice canary first, trait fix after decision gate
trait 정렬 fix 가 후속의 base 라는 직관적 의존성은 실제로는 약함:
1D random walker 는 simul-core trait 만 사용하므로 simul-euclidean
ForceInteraction 부정합과 독립이다. 반면 trait fix 의 *최종 모양*
(Handle 을 simul-core 로 lift 할지, EuclideanInteraction sub-trait
으로 둘지) 은 두 데이터 포인트 (Euclidean MD + Lattice) 가 있어야
근거 있게 결정됨.

따라서 M0+ 단위 순서를 1↔2 swap + 결정 게이트 신규 §2 추가:

  (구) 1. Trait fix → 2. canary → 3-7. forces/wrappers
  (신) 1. canary → 2. 추상 재설계 결정 → 3. Trait fix → 4-8.

PROGRESS.md §1 M0+ 표 행 재배치, §6 다음 한 단위 갱신.
learning/M0+.md 섹션 순서 물리 재배치 + 새 §2 결정 게이트 추가.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 00:05:58 +09:00
11 changed files with 319 additions and 84 deletions

12
Cargo.lock generated
View File

@@ -2923,6 +2923,7 @@ dependencies = [
"rand 0.9.2",
"simul-core",
"simul-euclidean",
"simul-lattice",
]
[[package]]
@@ -2957,6 +2958,17 @@ dependencies = [
"thiserror",
]
[[package]]
name = "simul-lattice"
version = "0.1.0"
dependencies = [
"approx",
"nalgebra",
"rand 0.9.2",
"rand_distr",
"simul-core",
]
[[package]]
name = "slab"
version = "0.4.12"

View File

@@ -4,7 +4,7 @@ members = [
"simul-core",
"simul-euclidean",
"simul-io",
"simul",
"simul", "simul-lattice",
]
[workspace.package]
@@ -31,3 +31,4 @@ approx = "0.5"
simul-core = { path = "simul-core" }
simul-euclidean = { path = "simul-euclidean" }
simul-io = { path = "simul-io" }
simul-lattice = { path = "simul-lattice" }

View File

@@ -38,13 +38,14 @@
| # | 단위 | 상태 | 시간 | LOC | Tier-1 | 패턴 | 비고 |
|---|---|---|---:|---:|---:|---|---|
| 1 | Trait 정렬 fix (`Interaction``ForceInteraction`) | ⬜ | 한나절 | ~50 | | P1 | **첫 PR**. 후속 모두의 base. |
| 2 | `simul-lattice/` canary (1D random walker) | ⬜ | 1주 | ~200 | (+1 canary) | P1+P2 | 4중 방어선 §11.6-(a) |
| 3 | `RMSDForce` `RmsdInteraction<D>` | ⬜ | 12일 | ~150 | +1 | P1 | TheobaldLiu quat align |
| 4 | `RGForce``RgInteraction<D>` | ⬜ | 1일 | ~80 | +1 | P1 | 단순 — Σ\|rr_cm\|²/N |
| 5 | `OrientationRestraintForce` | ⬜ | 2일 | ~150 | +1 | P1 | rotation matrix |
| 6 | `AndersenThermostat``Thermostat` trait | ⬜ | 1일 | ~120 | +1 | P12 | VelocityModifier 패턴 정착 |
| 7 | Tier-1 wrapper 14건 (Verlet/Langevin/Brownian 단발 + System/Spline/SplineFitter/MultipleForces 등) | ⬜ | 며칠 | ~600 | +14 (현재 2 → 16) | — | T5 addendum §3 게이팅 표 |
| 1 | `simul-lattice/` canary (1D random walker) | ⬜ | 1주 | ~200 | (+1 canary) | P2 | 4중 방어선 §11.6-(a). **추상 결정의 데이터 포인트 #2**. |
| 2 | 추상 재설계 결정 (Handle을 simul-core로 lift할지) | ⬜ | 한나절 | — | — | P1 | canary 결과 기반. ROADMAP §6 P1 갱신. |
| 3 | Trait 정렬 (`Interaction` `EuclideanInteraction`) | ⬜ | 한나절~1일 | ~50 | | P1 | (2)에서 결정한 모양으로. |
| 4 | `RMSDForce``RmsdInteraction<D>` | ⬜ | 12일 | ~150 | +1 | P1 | TheobaldLiu quat align |
| 5 | `RGForce``RgInteraction<D>` | ⬜ | 1일 | ~80 | +1 | P1 | 단순 — Σ\|rr_cm\|²/N |
| 6 | `OrientationRestraintForce` | ⬜ | 2일 | ~150 | +1 | P1 | rotation matrix |
| 7 | `AndersenThermostat``Thermostat` trait | ⬜ | 1일 | ~120 | +1 | P12 | VelocityModifier 패턴 정착 |
| 8 | Tier-1 wrapper 14건 (Verlet/Langevin/Brownian 단발 + System/Spline/SplineFitter/MultipleForces 등) | ⬜ | 며칠 | ~600 | +14 (현재 2 → 16) | — | T5 addendum §3 게이팅 표 |
**M0+ DoD**:
- `cargo test --workspace` 통과
@@ -152,9 +153,9 @@
## 6. 다음 한 단위 (지금 무엇)
> ⏭ **trait 정렬 fix** (M0+ #1, 한나절)
> ⏭ **simul-lattice canary (1D random walker)** (M0+ #1, 1주)
>
> 이유: 후속 모든 force/integrator 의 base. 현재 `simul-core::Interaction` 과 `simul-euclidean::ForceInteraction` 부정합 (T6 §3.1). 가장 즉시 효과 큰 한 줄 작업. `_template_openmm.rs` 의 Phase 1 정책 주의 블록도 이 fix 후 갱신 가능.
> 이유: trait 추상 재설계의 데이터 포인트 #2 확보 — Handle을 simul-core로 lift할지를 *예측*이 아니라 *발견*으로 결정하기 위함. 4중 방어선 §11.6-(a) 살아있는 가드레일도 동시 설치. 1D random walker는 simul-core trait만 사용 — `ForceInteraction` 부정합과 독립이므로 trait fix 선행 불필요.
>
> 디테일: `learning/M0+.md` §1.

View File

@@ -10,76 +10,19 @@
| 순 | 단위 | 시간 | 의존 |
|---:|---|---|---|
| 1 | **Trait 정렬 fix** | 한나절 | — (이게 후속 모두의 base) |
| 2 | **simul-lattice canary** | 1주 | (1) — fix 된 trait 위에서 lattice 가 컴파일되는지 검증 |
| 3 | RMSD / RG / OrientationRestraint | 45일 | (1) |
| 4 | AndersenThermostat → `Thermostat` trait | 1일 | (1) |
| 5 | Tier-1 wrapper 14건 | 며칠 | (1)~(4) |
| 1 | **simul-lattice canary** (1D random walker) | 1주 | — (simul-core 만 사용, trait fix 와 독립) |
| 2 | **추상 재설계 결정** | 한나절 | (1) — canary 결과로 Handle을 simul-core lift할지 결정 |
| 3 | **Trait 정렬 fix** | 한나절~1일 | (2) — 결정된 모양으로 실행 |
| 4 | RMSD / RG / OrientationRestraint | 45일 | (3) |
| 5 | AndersenThermostat → `Thermostat` trait | 1일 | (3) |
| 6 | Tier-1 wrapper 14건 | 며칠 | (3)~(5) |
> (1)~(2) *아키텍처* 작업이 (3)~(5) *구현* 작업이다. 1주차에 (1)+(2) 를 못 끝내면 후순위 단위들이 모두 흔들린다.
> (1)~(3) *아키텍처* 작업이다 — 추상이 lattice + euclidean 두 데이터 포인트로 결정된 후 trait fix 실행. (4)~(6) *구현* 작업.
> canary 가 trait fix 에 의존하지 않는 이유: 1D random walker 는 `Dynamics<Lattice<1>>` + `StateSpace` 만 사용하며 simul-euclidean 의 `ForceInteraction` 과 무관 — 부정합과 독립.
---
## 1. Trait 정렬 fix
### 배경 (T6 §3.1)
현재:
```rust
// simul-core/src/interaction.rs
pub trait Interaction<S: StateSpace>: Send + Sync + Debug {
type Output;
fn compute(&self, state: &SystemState<S>) -> Self::Output;
fn name(&self) -> &'static str;
}
// simul-euclidean/src/interaction/mod.rs
pub trait ForceInteraction<const D: usize> {
fn compute_forces(
&self,
client: &Client,
positions: &Handle,
forces: &mut Handle,
n: usize,
) -> f64;
}
```
`ForceInteraction``Interaction` 을 구현하지 않는다. 두 trait 가 평행 — `Interaction::Output` associated type 이 lattice 까지 수용 가능한 추상인데 ForceInteraction 이 그 추상을 *우회* 한다.
### 권고 (T7 P1)
`ForceInteraction``Interaction`*sub-trait* 로:
```rust
// simul-euclidean
pub trait ForceInteraction<const D: usize>:
Interaction<Euclidean<D>, Output = (DeviceForces, f64)>
{
fn compute_forces(...); // 기존 시그니처 유지
}
```
또는 간단하게: `Interaction::compute()``ForceInteraction::compute_forces` 의 wrapper 로 자동 구현하는 default impl 추가.
### Definition of Done
- [ ] `simul-core::Interaction``ForceInteraction` 의 super-trait
- [ ] `cargo test --workspace` 통과 (`nonbonded_openmm.rs` / `harmonic_bond_openmm.rs` 무수정)
- [ ] `_template_openmm.rs` 상단의 "trait 부정합 (T6 §3.1) 때문에 일시적" 주석 갱신 또는 제거
- [ ] DESIGN.md §"Core Abstractions" 의 trait 다이어그램 갱신
### 함정
- `Interaction::Output``ForceInteraction::compute_forces` 의 반환 타입이 다르면 super-trait 관계가 안 됨 → associated type 으로 통합
- `Send + Sync + Debug + 'static` bound 일관성
### 학습 가치
**중**. Rust trait 의 sub-trait + associated type 조합. `where Self: SuperTrait<...>` ergonomic.
### 참고 자료
- ROADMAP §6 P1
- `.team-output/T6-rust-architect.md` §3.1
- `.team-output/T7-rust-architect.md` §P1
---
## 2. simul-lattice canary (1D random walker)
## 1. simul-lattice canary (1D random walker)
### 배경
ROADMAP §11.6-(a). simul-core trait 가 MD-specific 으로 부풀어 오르는 것을 잡기 위한 살아있는 가드레일.
@@ -144,7 +87,112 @@ ROADMAP §11.6-(a). simul-core trait 가 MD-specific 으로 부풀어 오르는
---
## 3. RMSDForce → `RmsdInteraction<D>`
## 2. 추상 재설계 결정
### 배경
canary 구현이 끝난 시점의 *결정 게이트*. `simul-core::Interaction` 에 device handle 개념을 lift 할지, simul-euclidean 에만 sub-trait 으로 둘지를 **두 데이터 포인트** (Euclidean MD + Lattice random walker) 기반으로 결정. 추상을 *예측* 이 아니라 *발견* 으로 정의.
### 결정 트리
- **canary 가 host `Vec<i64>` 만으로 충분** (기대 시나리오) → Handle 을 simul-core 에 올리지 않음. `EuclideanInteraction: Interaction<Euclidean<D>, Output = ForceOutput<D>>` sub-trait 이 device path 를 보유. §3 Trait 정렬 fix 는 sub-trait 추가만 (한나절).
- **canary 가 어떤 형태든 buffer/storage 추상을 자연스럽게 요구** → `StateSpace::Buffer` associated type 을 simul-core 에 도입. `SystemState` 리팩터 동반 → 한나절 스코프 초과 → 별도 마일스톤으로 격상.
- **결정 보류** (1D random walker 가 너무 trivial 해서 데이터가 부족) → "현재로서 lift 할 근거 없음, M3 (lattice MC 본격) 시 재평가" 로 결정. 결정 자체도 결정.
### Definition of Done
- [ ] 결정 문서 `learning/decisions/abstraction-after-canary.md` 작성 (1페이지)
- [ ] DESIGN.md "Core Abstractions" 의 trait 다이어그램에 결정 반영
- [ ] §3 Trait 정렬 fix 의 구체 시그니처 (이름: `EuclideanInteraction` 권장) 가 결정됨
### 함정
- 아키텍처를 *예쁘게* 만들려고 lattice 가 필요로 하지 않는 추상을 simul-core 에 끌어오지 말 것. YAGNI.
- 반대로, canary 에서 *명백히* device-like 추상이 필요한데 simul-euclidean 에만 두면 다음 lattice 작업 (M3) 에서 또 trait drift 가 생긴다. 두 방향 모두 위험.
- 결정 문서를 만들지 않으면 "왜 이 모양이지?" 가 6개월 후 잊힘 → 반드시 짧게라도 기록.
### 학습 가치
**상**. 추상을 *예측* 이 아니라 *발견* 으로 정의하는 절차 자체. 결정 문서화 패턴 ADR (Architecture Decision Record).
### 참고
- 본 M0+.md §1 (canary 결과)
- ROADMAP §6 P1, §11.6
- `.team-output/T6-rust-architect.md` §3.1 (현 trait 부정합 진단)
- `.team-output/T7-rust-architect.md` §P1
---
## 3. Trait 정렬 fix
### 배경 (T6 §3.1)
현재:
```rust
// simul-core/src/interaction.rs
pub trait Interaction<S: StateSpace>: Send + Sync + Debug {
type Output;
fn compute(&self, state: &SystemState<S>) -> Self::Output;
fn name(&self) -> &'static str;
}
// simul-euclidean/src/interaction/mod.rs
pub trait ForceInteraction<const D: usize> {
fn compute_forces(
&self,
client: &Client,
positions: &Handle,
forces: &mut Handle,
n: usize,
) -> f64;
}
```
`ForceInteraction` 이 `Interaction` 을 구현하지 않는다. 두 trait 가 평행 — `Interaction::Output` associated type 이 lattice 까지 수용 가능한 추상인데 ForceInteraction 이 그 추상을 *우회* 한다.
### 권고 (T7 P1, §2 결정 결과 반영)
§2 의 결정에 따라 두 가지 모양 중 하나:
**(a) sub-trait** (Handle simul-core 에 lift 안 함):
```rust
// simul-euclidean
pub trait EuclideanInteraction<const D: usize>:
Interaction<Euclidean<D>, Output = (DeviceForces, f64)>
{
fn compute_forces(...); // 기존 시그니처 유지
}
```
**(b) Buffer associated type** (Handle 을 simul-core 로 lift):
```rust
// simul-core
pub trait StateSpace {
type Point;
type Momentum;
type Buffer; // Handle for Euclidean, Vec for Lattice
...
}
```
(b) 채택 시 SystemState 리팩터 동반 → 별도 마일스톤. (a) 가 한나절 스코프 기본값.
또는 간단하게: `Interaction::compute()` 를 `ForceInteraction::compute_forces` 의 wrapper 로 자동 구현하는 default impl 추가 — (a) 의 변형.
### Definition of Done
- [ ] `simul-core::Interaction` 이 `EuclideanInteraction` (구 `ForceInteraction`) 의 super-trait
- [ ] `cargo test --workspace` 통과 (`nonbonded_openmm.rs` / `harmonic_bond_openmm.rs` 무수정)
- [ ] `_template_openmm.rs` 상단의 "trait 부정합 (T6 §3.1) 때문에 일시적" 주석 갱신 또는 제거
- [ ] DESIGN.md §"Core Abstractions" 의 trait 다이어그램 갱신
### 함정
- `Interaction::Output` 와 `ForceInteraction::compute_forces` 의 반환 타입이 다르면 super-trait 관계가 안 됨 → associated type 으로 통합
- `Send + Sync + Debug + 'static` bound 일관성
### 학습 가치
**중**. Rust trait 의 sub-trait + associated type 조합. `where Self: SuperTrait<...>` ergonomic.
### 참고 자료
- ROADMAP §6 P1
- `.team-output/T6-rust-architect.md` §3.1
- `.team-output/T7-rust-architect.md` §P1
---
## 4. RMSDForce → `RmsdInteraction<D>`
### 배경
OpenMM `RMSDForce`: 두 입자 set 사이의 root-mean-square deviation. 보통 reference 분자와 정렬 후 RMSD 계산. TheobaldLiu (quaternion-based) 알고리즘이 표준.
@@ -177,7 +225,7 @@ OpenMM `RMSDForce`: 두 입자 set 사이의 root-mean-square deviation. 보통
---
## 4. RGForce → `RgInteraction<D>`
## 5. RGForce → `RgInteraction<D>`
### 배경
Radius of gyration: `R_g² = Σᵢ |rᵢ r_cm|² / N`. 또는 mass-weighted: `Σᵢ mᵢ |rᵢ r_cm|² / Σᵢ mᵢ`.
@@ -205,7 +253,7 @@ Radius of gyration: `R_g² = Σᵢ |rᵢ r_cm|² / N`. 또는 mass-weighted:
---
## 5. OrientationRestraintForce
## 6. OrientationRestraintForce
### 배경
참조 분자에 대한 *회전* 제약. `E = k · (1 cos²(θ))` 또는 quaternion 기반.
@@ -235,7 +283,7 @@ Radius of gyration: `R_g² = Σᵢ |rᵢ r_cm|² / N`. 또는 mass-weighted:
---
## 6. AndersenThermostat → `Thermostat` trait
## 7. AndersenThermostat → `Thermostat` trait
### 배경
일정 확률 `ν · dt` 로 입자의 속도를 MaxwellBoltzmann 분포에서 새로 추첨. 캐노니컬 앙상블 (NVT) 의 단순 구현.
@@ -267,7 +315,7 @@ Radius of gyration: `R_g² = Σᵢ |rᵢ r_cm|² / N`. 또는 mass-weighted:
---
## 7. Tier-1 wrapper 14건
## 8. Tier-1 wrapper 14건
### 배경
`tests/UPSTREAM_TESTS.md` §2 의 "현재 시점 ~14개" 게이팅. 새 *force/integrator 구현* 이 아니라 *기존 simul 의 등가성 검증* 을 추가.

16
simul-lattice/Cargo.toml Normal file
View File

@@ -0,0 +1,16 @@
[package]
name = "simul-lattice"
description = "Lattice space simulations (MD, Brownian, Langevin) for the simul framework"
version.workspace = true
edition.workspace = true
license.workspace = true
[dependencies]
simul-core = { workspace = true }
nalgebra = { workspace = true }
rand = { workspace = true }
rand_distr = { workspace = true }
[dev-dependencies]
approx = { workspace = true }
nalgebra = { workspace = true }

View File

@@ -0,0 +1,47 @@
//! Example: 1D simple random walk
//!
//! Runs N_TRIAL independent walks of TIME steps each, then verifies the
//! diffusive scaling: for a 1D simple random walk, <x(t)> = 0 and <x^2(t)> = t.
use nalgebra::SVector;
use rand::{SeedableRng, rngs::StdRng};
use simul_core::dynamics::Dynamics;
use simul_core::state::SystemState;
use simul_lattice::prelude::*;
fn main() -> Result<(), Box<dyn std::error::Error>> {
const N_TRIAL: usize = 10000;
const TIME: usize = 1000;
let mut rng = StdRng::seed_from_u64(42);
let dynamics = RandomWalk1D;
let mut sum_x = 0.0_f64;
let mut sum_x2 = 0.0_f64;
for _ in 0..N_TRIAL {
let mut state: SystemState<Lattice<1>> = SystemState::new();
state.positions.push(SVector::<i32, 1>::zeros());
for _ in 0..TIME {
dynamics.step(&mut state, &mut rng);
}
let x = state.positions[0][0] as f64;
sum_x += x;
sum_x2 += x * x;
}
let mean = sum_x / N_TRIAL as f64;
let var = sum_x2 / N_TRIAL as f64;
println!("RandomWalk1D - 1D simple random walk");
println!("=====================================");
println!(" Trials : {}", N_TRIAL);
println!(" Steps / trial : {}", TIME);
println!();
println!(" <x> = {:>8.3} (expected ~ 0)", mean);
println!(" <x^2> = {:>8.3} (expected ~ {})", var, TIME);
Ok(())
}

View File

@@ -0,0 +1,28 @@
//! Dynamics implementations for Lattice space
use crate::space::Lattice;
use rand::Rng;
use simul_core::dynamics::{Dynamics, TimeType};
use simul_core::state::SystemState;
/// Single particle 1D Random Walk dynamics
#[derive(Clone, Copy, Debug)]
pub struct RandomWalk1D;
impl Dynamics<Lattice<1>> for RandomWalk1D {
fn step<R: Rng>(&self, state: &mut SystemState<Lattice<1>>, rng: &mut R) -> f64 {
for p in state.positions.iter_mut() {
p[0] += if rng.random_bool(0.5) { 1 } else { -1 };
}
1.0 // discrete step
}
fn time_type(&self) -> TimeType {
TimeType::Discrete
}
fn is_deterministic(&self) -> bool {
false
}
fn name(&self) -> &'static str {
"RandomWalk1D"
}
}

15
simul-lattice/src/lib.rs Normal file
View File

@@ -0,0 +1,15 @@
//! simul-lattice: Discrete space simulations
//!
//! This crate provides implementations for simulations in discrete space:
//! - Molecular dynamics (Verlet integrator)
//! - Langevin dynamics (stochastic)
//! - Brownian dynamics (overdamped)
pub mod dynamics;
pub mod space;
pub mod prelude {
//! Convenient re-exports for common use
pub use crate::dynamics::RandomWalk1D;
pub use crate::space::Lattice;
}

View File

@@ -0,0 +1,58 @@
//! Lattice space implementation
use nalgebra::SVector;
use simul_core::space::StateSpace;
/// D-dimensional Lattice space
///
/// This is the standard discrete space for molecular dynamics and Brownian motion.
/// Points are represented as D-dimensional vectors of i32.
#[derive(Clone, Debug, Default)]
pub struct Lattice<const D: usize>;
impl<const D: usize> StateSpace for Lattice<D> {
const DIM: usize = D;
type Point = SVector<i32, D>;
type Momentum = ();
fn is_continuous() -> bool {
false
}
fn name() -> &'static str {
match D {
1 => "Lattice 1D",
2 => "Lattice 2D",
3 => "Lattice 3D",
_ => "Lattice ND",
}
}
fn zero_point() -> Self::Point {
SVector::zeros()
}
fn zero_momentum() -> Self::Momentum {}
}
/// Type alias for 1D Lattice space
pub type Space1D = Lattice<1>;
/// Type alias for 2D Lattice space
pub type Space2D = Lattice<2>;
/// Type alias for 3D Lattice space
pub type Space3D = Lattice<3>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_lattice_3d() {
assert_eq!(Lattice::<3>::DIM, 3);
assert!(!Lattice::<3>::is_continuous());
assert_eq!(Lattice::<3>::name(), "Lattice 3D");
}
}

View File

@@ -8,14 +8,15 @@ license.workspace = true
[features]
default = ["euclidean"]
euclidean = ["simul-euclidean"]
# lattice = ["simul-lattice"] # Future
lattice = ["simul-lattice"]
[dependencies]
simul-core = { workspace = true }
simul-euclidean = { workspace = true, optional = true }
simul-lattice = { workspace = true, optional = true }
nalgebra = { workspace = true }
rand = { workspace = true }
# simul-lattice = { workspace = true, optional = true } # Future
[[example]]
name = "hello_argon"

View File

@@ -27,6 +27,10 @@ pub use simul_core as core;
#[cfg(feature = "euclidean")]
pub use simul_euclidean as euclidean;
// Re-export lattice (when feature enabled)
#[cfg(feature = "lattice")]
pub use simul_lattice as lattice;
pub mod prelude {
//! Convenient re-exports for common use
//!
@@ -43,4 +47,8 @@ pub mod prelude {
// Euclidean types (when enabled)
#[cfg(feature = "euclidean")]
pub use simul_euclidean::prelude::*;
// Lattice types (when enabled)
#[cfg(feature = "lattice")]
pub use simul_lattice::prelude::*;
}