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 {
|
loop {
|
||||||
if increments.is_multiple_of(PROGRESS_EVERY) {
|
if increments.is_multiple_of(PROGRESS_EVERY) {
|
||||||
fieldcraft_model::progress::count(t);
|
fieldcraft_model::progress::count(t);
|
||||||
|
fieldcraft_model::progress::check()?;
|
||||||
}
|
}
|
||||||
if increments >= max_increments {
|
if increments >= max_increments {
|
||||||
completed = false;
|
completed = false;
|
||||||
|
|||||||
@@ -137,6 +137,7 @@ pub fn run(setup: &Setup, flow: &mut Flow, limits: &Limits) -> Result<FluidResul
|
|||||||
let mut next_frame = 1u64;
|
let mut next_frame = 1u64;
|
||||||
fieldcraft_model::progress::stage("Fluid steps", Some(total as f64), "");
|
fieldcraft_model::progress::stage("Fluid steps", Some(total as f64), "");
|
||||||
for k in 1..=total {
|
for k in 1..=total {
|
||||||
|
fieldcraft_model::progress::check()?;
|
||||||
flow.step()?;
|
flow.step()?;
|
||||||
if k % 64 == 0 {
|
if k % 64 == 0 {
|
||||||
fieldcraft_model::progress::count(k as f64);
|
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();
|
let mut reasons: Vec<String> = Vec::new();
|
||||||
for &method in methods {
|
for &method in methods {
|
||||||
|
fieldcraft_model::progress::check()?;
|
||||||
let mut last = None;
|
let mut last = None;
|
||||||
match method {
|
match method {
|
||||||
HexMethod::Extrude | HexMethod::Revolve => {
|
HexMethod::Extrude | HexMethod::Revolve => {
|
||||||
@@ -287,6 +288,7 @@ pub fn hex_mesh(surface: &BoundarySurface, faces: &[Option<Surface>], field: &dy
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
for setup in setups {
|
for setup in setups {
|
||||||
|
fieldcraft_model::progress::check()?;
|
||||||
// The cross-section is laid out once per frame; refinement retries reuse it.
|
// The cross-section is laid out once per frame; refinement retries reuse it.
|
||||||
let prepared = match layered::prepare(&ctx, setup) {
|
let prepared = match layered::prepare(&ctx, setup) {
|
||||||
Ok(p) => p,
|
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> {
|
fn with_refinement(ctx: &Ctx, method: HexMethod, run: impl Fn(u32) -> Result<Raw>) -> Result<HexMesh> {
|
||||||
let mut last = Error::Failed("no attempt".into());
|
let mut last = Error::Failed("no attempt".into());
|
||||||
for refine in 0..=MAX_REFINE {
|
for refine in 0..=MAX_REFINE {
|
||||||
|
fieldcraft_model::progress::check()?;
|
||||||
match run(refine).and_then(|raw| finish(&Cells::one(ctx), raw, method)) {
|
match run(refine).and_then(|raw| finish(&Cells::one(ctx), raw, method)) {
|
||||||
Ok(mesh) => return Ok(mesh),
|
Ok(mesh) => return Ok(mesh),
|
||||||
Err(e @ Error::Failed(_)) => last = e,
|
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 hints: Vec<P> = Vec::new();
|
||||||
let mut retries: Vec<String> = Vec::new();
|
let mut retries: Vec<String> = Vec::new();
|
||||||
for attempt in 0..options.attempts {
|
for attempt in 0..options.attempts {
|
||||||
|
fieldcraft_model::progress::check()?;
|
||||||
let cap = if attempt == 0 {
|
let cap = if attempt == 0 {
|
||||||
options.max_elements
|
options.max_elements
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -460,6 +460,7 @@ pub fn improve(cx: &mut TetComplex, ctx: &Context, o: &ImproveOptions) -> Result
|
|||||||
stats.passes += 1;
|
stats.passes += 1;
|
||||||
fieldcraft_model::progress::stage("Improvement pass", Some(o.max_passes as f64), "");
|
fieldcraft_model::progress::stage("Improvement pass", Some(o.max_passes as f64), "");
|
||||||
fieldcraft_model::progress::count(stats.passes as f64);
|
fieldcraft_model::progress::count(stats.passes as f64);
|
||||||
|
fieldcraft_model::progress::check()?;
|
||||||
let before = match carried.take() {
|
let before = match carried.take() {
|
||||||
Some(b) => b,
|
Some(b) => b,
|
||||||
None => indicators(cx)?,
|
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), "");
|
progress::stage("Refinement round", Some(o.rounds as f64), "");
|
||||||
for round in 0..o.rounds {
|
for round in 0..o.rounds {
|
||||||
progress::count((round + 1) as f64);
|
progress::count((round + 1) as f64);
|
||||||
|
progress::check()?;
|
||||||
let stats = refine::refine(&mut cx, &ctx, &ropt)?;
|
let stats = refine::refine(&mut cx, &ctx, &ropt)?;
|
||||||
let quiet = stats.interior + stats.boundary == 0;
|
let quiet = stats.interior + stats.boundary == 0;
|
||||||
let stopped = stats.stopped.clone();
|
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
|
// Put the boundary vertices on the exact surfaces (the tessellation is within its chordal
|
||||||
// tolerance of them).
|
// tolerance of them).
|
||||||
|
progress::check()?;
|
||||||
progress::stage("Snapping to the geometry", None, "");
|
progress::stage("Snapping to the geometry", None, "");
|
||||||
report.unsnapped = snap_to_geometry(&mut cx, &exact, true)?;
|
report.unsnapped = snap_to_geometry(&mut cx, &exact, true)?;
|
||||||
let c = cx.compact()?;
|
let c = cx.compact()?;
|
||||||
|
|||||||
@@ -263,6 +263,10 @@ pub fn refine(cx: &mut TetComplex, ctx: &Context, o: &RefineOptions) -> Result<R
|
|||||||
while head < queue.len() {
|
while head < queue.len() {
|
||||||
let t = queue.get(head).copied().unwrap_or(NONE);
|
let t = queue.get(head).copied().unwrap_or(NONE);
|
||||||
head += 1;
|
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.
|
// Keep the queue's memory bounded: drop the processed prefix now and then.
|
||||||
if head > 1 << 20 {
|
if head > 1 << 20 {
|
||||||
queue.drain(..head);
|
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::OutOfMemory { entries } => Error::Limit(format!("not enough memory for the factorization ({entries} entries)")),
|
||||||
FactorError::Internal(what) => Error::Internal(what),
|
FactorError::Internal(what) => Error::Internal(what),
|
||||||
|
FactorError::Cancelled => Error::Model(fieldcraft_model::Error::Cancelled),
|
||||||
FactorError::NotPositiveDefinite { column } => {
|
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));
|
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!(
|
Error::Singular(format!(
|
||||||
|
|||||||
@@ -221,6 +221,8 @@ pub enum FactorError {
|
|||||||
OutOfMemory { entries: usize },
|
OutOfMemory { entries: usize },
|
||||||
/// A broken internal invariant (a bug), reported instead of a panic.
|
/// A broken internal invariant (a bug), reported instead of a panic.
|
||||||
Internal(String),
|
Internal(String),
|
||||||
|
/// The command was cancelled ([`fieldcraft_model::progress`]) between supernodes.
|
||||||
|
Cancelled,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FactorError {
|
impl FactorError {
|
||||||
@@ -892,8 +894,17 @@ impl Symbolic {
|
|||||||
at = range.end;
|
at = range.end;
|
||||||
}
|
}
|
||||||
let broken = std::sync::Mutex::new(None::<FactorError>);
|
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)| {
|
parallel::for_each_mut(&mut pieces, |_, (range, piece, failed)| {
|
||||||
for j in range.clone() {
|
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 (done, own) = piece.split_at_mut(j - range.start);
|
||||||
let Some(own) = own.first_mut() else { break };
|
let Some(own) = own.first_mut() else { break };
|
||||||
match supernode(&context, j, done, range.start, own) {
|
match supernode(&context, j, done, range.start, own) {
|
||||||
@@ -923,6 +934,9 @@ impl Symbolic {
|
|||||||
None => e,
|
None => e,
|
||||||
};
|
};
|
||||||
for &j in &self.top {
|
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) {
|
if earliest.as_ref().and_then(FactorError::column).is_some_and(|c| self.structure.cols(j).start > c) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1078,6 +1092,14 @@ mod tests {
|
|||||||
}
|
}
|
||||||
let e = Factor::new(&UpperColumns::from_symmetric(&dense_to_csr(&sing)), usize::MAX).unwrap_err();
|
let e = Factor::new(&UpperColumns::from_symmetric(&dense_to_csr(&sing)), usize::MAX).unwrap_err();
|
||||||
assert_eq!(e, FactorError::NotPositiveDefinite { column: n - 1 });
|
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.
|
// Empty and 1 × 1 matrices.
|
||||||
let empty = Csr { n: 0, ptr: vec![0], idx: vec![], val: vec![] };
|
let empty = Csr { n: 0, ptr: vec![0], idx: vec![], val: vec![] };
|
||||||
let f = Factor::new(&UpperColumns::from_symmetric(&empty), 10).unwrap();
|
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 prev_b: Option<Vec<Vec<f64>>> = None;
|
||||||
let mut next_check = (wanted.count + b).max(2 * b);
|
let mut next_check = (wanted.count + b).max(2 * b);
|
||||||
loop {
|
loop {
|
||||||
|
fieldcraft_model::progress::check()?;
|
||||||
let j0 = q.len();
|
let j0 = q.len();
|
||||||
// W = Op Q_j.
|
// W = Op Q_j.
|
||||||
let mut w: Vec<Vec<f64>> = Vec::with_capacity(b);
|
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::OutOfMemory { entries } => Error::Limit(format!("not enough memory for the factorization ({entries} entries)")),
|
||||||
FactorError::Internal(what) => Error::Internal(what),
|
FactorError::Internal(what) => Error::Internal(what),
|
||||||
|
FactorError::Cancelled => Error::Model(fieldcraft_model::Error::Cancelled),
|
||||||
FactorError::NotPositiveDefinite { column } | FactorError::Singular { column } => {
|
FactorError::NotPositiveDefinite { column } | FactorError::Singular { column } => {
|
||||||
let node = eqs.eq_dof.get(column).and_then(|d| mesh.layout.locate(*d)).map(|(slot, _)| slot);
|
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);
|
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), "");
|
fieldcraft_model::progress::stage("Time increment", Some(steps as f64), "");
|
||||||
for i in 1..=steps {
|
for i in 1..=steps {
|
||||||
fieldcraft_model::progress::count((i - 1) as f64);
|
fieldcraft_model::progress::count((i - 1) as f64);
|
||||||
|
fieldcraft_model::progress::check()?;
|
||||||
b.mul(&t, &mut rhs);
|
b.mul(&t, &mut rhs);
|
||||||
for (r, fj) in rhs.iter_mut().zip(&f) {
|
for (r, fj) in rhs.iter_mut().zip(&f) {
|
||||||
*r += dt * fj;
|
*r += dt * fj;
|
||||||
|
|||||||
@@ -702,7 +702,11 @@ impl Run<'_> {
|
|||||||
let mut dq = rf.clone();
|
let mut dq = rf.clone();
|
||||||
let [kff] = red.reduce([&k])?;
|
let [kff] = red.reduce([&k])?;
|
||||||
drop(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)?;
|
chol.solve(&mut dq)?;
|
||||||
let energy = dot(&dq, &rf).abs();
|
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)))
|
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 mut flips = 0usize;
|
||||||
let max_iterations = usize::try_from(self.settings.max_iterations).unwrap_or(usize::MAX);
|
let max_iterations = usize::try_from(self.settings.max_iterations).unwrap_or(usize::MAX);
|
||||||
for _ in 0..max_iterations {
|
for _ in 0..max_iterations {
|
||||||
|
fieldcraft_model::progress::check().map_err(Error::from)?;
|
||||||
// The round-off rule: nothing left to correct.
|
// The round-off rule: nothing left to correct.
|
||||||
if norm_inf(&r) <= ROUND_OFF * tol.force * self.scale() && self.force_floor.is_some() {
|
if norm_inf(&r) <= ROUND_OFF * tol.force * self.scale() && self.force_floor.is_some() {
|
||||||
return Ok(Converged { q, evaluation: eval, energy: first_energy });
|
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), "");
|
fieldcraft_model::progress::stage("Load factor", Some(1.0), "");
|
||||||
while t < 1.0 {
|
while t < 1.0 {
|
||||||
fieldcraft_model::progress::count(lambda);
|
fieldcraft_model::progress::count(lambda);
|
||||||
|
fieldcraft_model::progress::check()?;
|
||||||
if history.increments.len() >= max_increments {
|
if history.increments.len() >= max_increments {
|
||||||
return Err(Error::Limit(format!(
|
return Err(Error::Limit(format!(
|
||||||
"step `{}`: {} increments reached the step's cap at load factor {lambda:.6} (raise max_increments)",
|
"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 mut last: Option<Evaluation> = None;
|
||||||
let max_increments = usize::try_from(settings.max_increments).unwrap_or(usize::MAX);
|
let max_increments = usize::try_from(settings.max_increments).unwrap_or(usize::MAX);
|
||||||
loop {
|
loop {
|
||||||
|
fieldcraft_model::progress::check()?;
|
||||||
if history.increments.len() >= max_increments {
|
if history.increments.len() >= max_increments {
|
||||||
warnings.push(format!(
|
warnings.push(format!(
|
||||||
"the arc-length step `{}` ended at its cap of {} increments, at load factor {lambda:.6}",
|
"the arc-length step `{}` ended at its cap of {} increments, at load factor {lambda:.6}",
|
||||||
|
|||||||
Reference in new issue
Block a user