forked from 1b-agent/fieldcraft-upstream
solvers, meshers: stop with Cancelled at each loop's natural points
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
This commit is contained in:
1 parent
a2d646a75e
commit
5fcbee028e
12 files changed
+47
-1
No files matched your search
@@ -363,6 +363,7 @@ pub fn solve_explicit(model: &Model, step: Option<&str>, limits: &Limits) -> Res
|
||||
loop {
|
||||
if increments.is_multiple_of(PROGRESS_EVERY) {
|
||||
fieldcraft_model::progress::count(t);
|
||||
fieldcraft_model::progress::check()?;
|
||||
}
|
||||
if increments >= max_increments {
|
||||
completed = false;
|
||||
|
||||
@@ -137,6 +137,7 @@ pub fn run(setup: &Setup, flow: &mut Flow, limits: &Limits) -> Result<FluidResul
|
||||
let mut next_frame = 1u64;
|
||||
fieldcraft_model::progress::stage("Fluid steps", Some(total as f64), "");
|
||||
for k in 1..=total {
|
||||
fieldcraft_model::progress::check()?;
|
||||
flow.step()?;
|
||||
if k % 64 == 0 {
|
||||
fieldcraft_model::progress::count(k as f64);
|
||||
|
||||
@@ -276,6 +276,7 @@ pub fn hex_mesh(surface: &BoundarySurface, faces: &[Option<Surface>], field: &dy
|
||||
};
|
||||
let mut reasons: Vec<String> = Vec::new();
|
||||
for &method in methods {
|
||||
fieldcraft_model::progress::check()?;
|
||||
let mut last = None;
|
||||
match method {
|
||||
HexMethod::Extrude | HexMethod::Revolve => {
|
||||
@@ -287,6 +288,7 @@ pub fn hex_mesh(surface: &BoundarySurface, faces: &[Option<Surface>], field: &dy
|
||||
}
|
||||
};
|
||||
for setup in setups {
|
||||
fieldcraft_model::progress::check()?;
|
||||
// The cross-section is laid out once per frame; refinement retries reuse it.
|
||||
let prepared = match layered::prepare(&ctx, setup) {
|
||||
Ok(p) => p,
|
||||
@@ -364,6 +366,7 @@ fn candidate_setups(ctx: &Ctx, method: HexMethod) -> std::result::Result<Vec<lay
|
||||
fn with_refinement(ctx: &Ctx, method: HexMethod, run: impl Fn(u32) -> Result<Raw>) -> Result<HexMesh> {
|
||||
let mut last = Error::Failed("no attempt".into());
|
||||
for refine in 0..=MAX_REFINE {
|
||||
fieldcraft_model::progress::check()?;
|
||||
match run(refine).and_then(|raw| finish(&Cells::one(ctx), raw, method)) {
|
||||
Ok(mesh) => return Ok(mesh),
|
||||
Err(e @ Error::Failed(_)) => last = e,
|
||||
|
||||
@@ -251,6 +251,7 @@ pub fn octree_hex_mesh(surface: &BoundarySurface, field: &dyn SizeField, options
|
||||
let mut hints: Vec<P> = Vec::new();
|
||||
let mut retries: Vec<String> = Vec::new();
|
||||
for attempt in 0..options.attempts {
|
||||
fieldcraft_model::progress::check()?;
|
||||
let cap = if attempt == 0 {
|
||||
options.max_elements
|
||||
} else {
|
||||
|
||||
@@ -460,6 +460,7 @@ pub fn improve(cx: &mut TetComplex, ctx: &Context, o: &ImproveOptions) -> Result
|
||||
stats.passes += 1;
|
||||
fieldcraft_model::progress::stage("Improvement pass", Some(o.max_passes as f64), "");
|
||||
fieldcraft_model::progress::count(stats.passes as f64);
|
||||
fieldcraft_model::progress::check()?;
|
||||
let before = match carried.take() {
|
||||
Some(b) => b,
|
||||
None => indicators(cx)?,
|
||||
|
||||
@@ -491,6 +491,7 @@ pub fn quality_mesh(surface: &BoundarySurface, cdt: &TetMesh, analytic: Vec<Opti
|
||||
progress::stage("Refinement round", Some(o.rounds as f64), "");
|
||||
for round in 0..o.rounds {
|
||||
progress::count((round + 1) as f64);
|
||||
progress::check()?;
|
||||
let stats = refine::refine(&mut cx, &ctx, &ropt)?;
|
||||
let quiet = stats.interior + stats.boundary == 0;
|
||||
let stopped = stats.stopped.clone();
|
||||
@@ -514,6 +515,7 @@ pub fn quality_mesh(surface: &BoundarySurface, cdt: &TetMesh, analytic: Vec<Opti
|
||||
}
|
||||
// Put the boundary vertices on the exact surfaces (the tessellation is within its chordal
|
||||
// tolerance of them).
|
||||
progress::check()?;
|
||||
progress::stage("Snapping to the geometry", None, "");
|
||||
report.unsnapped = snap_to_geometry(&mut cx, &exact, true)?;
|
||||
let c = cx.compact()?;
|
||||
|
||||
@@ -263,6 +263,10 @@ pub fn refine(cx: &mut TetComplex, ctx: &Context, o: &RefineOptions) -> Result<R
|
||||
while head < queue.len() {
|
||||
let t = queue.get(head).copied().unwrap_or(NONE);
|
||||
head += 1;
|
||||
// A round can insert hundreds of thousands of vertices: stop between them when asked.
|
||||
if head.is_multiple_of(1024) {
|
||||
fieldcraft_model::progress::check()?;
|
||||
}
|
||||
// Keep the queue's memory bounded: drop the processed prefix now and then.
|
||||
if head > 1 << 20 {
|
||||
queue.drain(..head);
|
||||
|
||||
@@ -271,6 +271,7 @@ pub(crate) fn factor_error(e: FactorError, mesh: &Mesh, reduction: &Reduction) -
|
||||
}
|
||||
FactorError::OutOfMemory { entries } => Error::Limit(format!("not enough memory for the factorization ({entries} entries)")),
|
||||
FactorError::Internal(what) => Error::Internal(what),
|
||||
FactorError::Cancelled => Error::Model(fieldcraft_model::Error::Cancelled),
|
||||
FactorError::NotPositiveDefinite { column } => {
|
||||
let at = reduction.eq_dof.get(column).map_or_else(|| "an unknown degree of freedom".to_string(), |d| mesh.describe_dof(*d));
|
||||
Error::Singular(format!(
|
||||
|
||||
@@ -221,6 +221,8 @@ pub enum FactorError {
|
||||
OutOfMemory { entries: usize },
|
||||
/// A broken internal invariant (a bug), reported instead of a panic.
|
||||
Internal(String),
|
||||
/// The command was cancelled ([`fieldcraft_model::progress`]) between supernodes.
|
||||
Cancelled,
|
||||
}
|
||||
|
||||
impl FactorError {
|
||||
@@ -892,8 +894,17 @@ impl Symbolic {
|
||||
at = range.end;
|
||||
}
|
||||
let broken = std::sync::Mutex::new(None::<FactorError>);
|
||||
// The pool's threads do not report: they ask the caller's progress whether to stop.
|
||||
let progress = fieldcraft_model::progress::current();
|
||||
let cancelled = || progress.as_ref().is_some_and(|p| p.is_cancelled());
|
||||
parallel::for_each_mut(&mut pieces, |_, (range, piece, failed)| {
|
||||
for j in range.clone() {
|
||||
if cancelled() {
|
||||
if let Ok(mut b) = broken.lock() {
|
||||
b.get_or_insert(FactorError::Cancelled);
|
||||
}
|
||||
break;
|
||||
}
|
||||
let (done, own) = piece.split_at_mut(j - range.start);
|
||||
let Some(own) = own.first_mut() else { break };
|
||||
match supernode(&context, j, done, range.start, own) {
|
||||
@@ -923,6 +934,9 @@ impl Symbolic {
|
||||
None => e,
|
||||
};
|
||||
for &j in &self.top {
|
||||
if cancelled() {
|
||||
return Err(FactorError::Cancelled);
|
||||
}
|
||||
if earliest.as_ref().and_then(FactorError::column).is_some_and(|c| self.structure.cols(j).start > c) {
|
||||
break;
|
||||
}
|
||||
@@ -1078,6 +1092,14 @@ mod tests {
|
||||
}
|
||||
let e = Factor::new(&UpperColumns::from_symmetric(&dense_to_csr(&sing)), usize::MAX).unwrap_err();
|
||||
assert_eq!(e, FactorError::NotPositiveDefinite { column: n - 1 });
|
||||
// A cancelled command stops between supernodes, in Cholesky and LDLᵀ alike.
|
||||
let p = fieldcraft_model::progress::Progress::new();
|
||||
p.cancel();
|
||||
fieldcraft_model::progress::scoped(Some(p), || {
|
||||
let upper = UpperColumns::from_symmetric(&csr);
|
||||
assert_eq!(Factor::new(&upper, usize::MAX).unwrap_err(), FactorError::Cancelled);
|
||||
assert_eq!(crate::ldlt::LdltFactor::new(&upper, usize::MAX).unwrap_err(), FactorError::Cancelled);
|
||||
});
|
||||
// Empty and 1 × 1 matrices.
|
||||
let empty = Csr { n: 0, ptr: vec![0], idx: vec![], val: vec![] };
|
||||
let f = Factor::new(&UpperColumns::from_symmetric(&empty), 10).unwrap();
|
||||
|
||||
@@ -296,6 +296,7 @@ pub fn lanczos(op: &impl Operator, shift: f64, wanted: Wanted, block: usize, max
|
||||
let mut prev_b: Option<Vec<Vec<f64>>> = None;
|
||||
let mut next_check = (wanted.count + b).max(2 * b);
|
||||
loop {
|
||||
fieldcraft_model::progress::check()?;
|
||||
let j0 = q.len();
|
||||
// W = Op Q_j.
|
||||
let mut w: Vec<Vec<f64>> = Vec::with_capacity(b);
|
||||
|
||||
@@ -308,6 +308,7 @@ fn factor(a: &Csr, limits: &Limits, mesh: &HeatMesh, eqs: &Reduction) -> Result<
|
||||
}
|
||||
FactorError::OutOfMemory { entries } => Error::Limit(format!("not enough memory for the factorization ({entries} entries)")),
|
||||
FactorError::Internal(what) => Error::Internal(what),
|
||||
FactorError::Cancelled => Error::Model(fieldcraft_model::Error::Cancelled),
|
||||
FactorError::NotPositiveDefinite { column } | FactorError::Singular { column } => {
|
||||
let node = eqs.eq_dof.get(column).and_then(|d| mesh.layout.locate(*d)).map(|(slot, _)| slot);
|
||||
let label = node.and_then(|n| mesh.labels.get(n)).copied().unwrap_or(0);
|
||||
@@ -434,6 +435,7 @@ fn run(model: &Model, resolved: &Resolved, step: &Step, limits: &Limits) -> Resu
|
||||
fieldcraft_model::progress::stage("Time increment", Some(steps as f64), "");
|
||||
for i in 1..=steps {
|
||||
fieldcraft_model::progress::count((i - 1) as f64);
|
||||
fieldcraft_model::progress::check()?;
|
||||
b.mul(&t, &mut rhs);
|
||||
for (r, fj) in rhs.iter_mut().zip(&f) {
|
||||
*r += dt * fj;
|
||||
|
||||
@@ -702,7 +702,11 @@ impl Run<'_> {
|
||||
let mut dq = rf.clone();
|
||||
let [kff] = red.reduce([&k])?;
|
||||
drop(k);
|
||||
let Ok(chol) = factor_raw(&kff, self.limits) else { return Ok(None) };
|
||||
let chol = match factor_raw(&kff, self.limits) {
|
||||
Ok(chol) => chol,
|
||||
Err(FactorError::Cancelled) => return Err(Failure::Fatal(fieldcraft_model::Error::Cancelled.into())),
|
||||
Err(_) => return Ok(None),
|
||||
};
|
||||
chol.solve(&mut dq)?;
|
||||
let energy = dot(&dq, &rf).abs();
|
||||
Ok(dq.iter().all(|v| v.is_finite()).then(|| (q0.iter().zip(&dq).map(|(a, d)| a + d).collect(), energy)))
|
||||
@@ -744,6 +748,7 @@ impl Run<'_> {
|
||||
let mut flips = 0usize;
|
||||
let max_iterations = usize::try_from(self.settings.max_iterations).unwrap_or(usize::MAX);
|
||||
for _ in 0..max_iterations {
|
||||
fieldcraft_model::progress::check().map_err(Error::from)?;
|
||||
// The round-off rule: nothing left to correct.
|
||||
if norm_inf(&r) <= ROUND_OFF * tol.force * self.scale() && self.force_floor.is_some() {
|
||||
return Ok(Converged { q, evaluation: eval, energy: first_energy });
|
||||
@@ -1234,6 +1239,7 @@ fn solve_on(model: &Model, step: &str, limits: &Limits, notes: Vec<String>) -> R
|
||||
fieldcraft_model::progress::stage("Load factor", Some(1.0), "");
|
||||
while t < 1.0 {
|
||||
fieldcraft_model::progress::count(lambda);
|
||||
fieldcraft_model::progress::check()?;
|
||||
if history.increments.len() >= max_increments {
|
||||
return Err(Error::Limit(format!(
|
||||
"step `{}`: {} increments reached the step's cap at load factor {lambda:.6} (raise max_increments)",
|
||||
@@ -1663,6 +1669,7 @@ fn arc_length(
|
||||
let mut last: Option<Evaluation> = None;
|
||||
let max_increments = usize::try_from(settings.max_increments).unwrap_or(usize::MAX);
|
||||
loop {
|
||||
fieldcraft_model::progress::check()?;
|
||||
if history.increments.len() >= max_increments {
|
||||
warnings.push(format!(
|
||||
"the arc-length step `{}` ended at its cap of {} increments, at load factor {lambda:.6}",
|
||||
|
||||
Reference in new issue
Block a user