Compare commits

...

4 Commits

7 changed files with 1896 additions and 6 deletions
@@ -905,6 +905,7 @@ pub fn gn_enclose_over(
) -> Result<Option<Enclosure>> {
crate::profile::profile_scope!(k, "blend::certificate::gn_enclose_over");
let s = at.s;
crate::profile::profile_note!(k, "enclose s=[{},{}] order={} n={}", s.lo, s.hi, order_s, n);
let tr = Trunc::new(order_s, n)?;
// a ball whose radius varies: its law over the span (0d43b4c)
let law_r = match law {
@@ -1018,6 +1019,7 @@ pub fn gn_enclose_over(
} else {
let contracts = q < 1.0;
if !contracts {
crate::profile::profile_note!(k, "enclose reject contraction q={q} limit=1");
return Ok(None); // the span is too long for its Jacobian to be one preconditioner's
}
// the next box from the contraction: K's radii are at most d + |C| w for a box
@@ -1053,8 +1055,10 @@ pub fn gn_enclose_over(
}
}
if !proved {
crate::profile::profile_note!(k, "enclose reject inclusion iterations={it} q={q}");
return Ok(None);
}
crate::profile::profile_note!(k, "enclose root q={q} iterations={it}");
// the feet's Taylor coefficients over the span, order by order: x = -Y R + C x,
// |x| <= rho
@@ -1674,6 +1678,7 @@ pub fn arc_rows<S: TayScalar + GnScalar>(
#[must_use]
#[allow(clippy::too_many_arguments)]
pub fn arc_row_terms_over(
k: &Kernel,
f: &[VSer<Ivl>; 2],
centre: &VSer<Ivl>,
fm: &[VSer<Ivl>; 2],
@@ -1683,6 +1688,8 @@ pub fn arc_row_terms_over(
rt: &mut GnRowTerms,
touch: bool,
) -> bool {
#[cfg(not(feature = "profile"))]
let _ = k;
const K: usize = 4 + TAYLOR;
let mut rho = [[Tay::<Ivl>::zero(); 3]; 2];
let mut mc = [Tay::<Ivl>::zero(); 3];
@@ -1725,6 +1732,7 @@ pub fn arc_row_terms_over(
bd = bd + (r0q - mq) * dq;
}
if !dd.at(0).is_positive() {
crate::profile::profile_note!(k, "arc reject speed2={:?} required_lo>0", dd.at(0));
return false;
}
let kk = bd / dd;
@@ -1734,6 +1742,26 @@ pub fn arc_row_terms_over(
}
let [rho0, rho1] = &rho;
let Some((h, w)) = arc_rows(rho0, rho1, &mc, o) else {
#[cfg(feature = "profile")]
{
// Repeat only the failed positivity test for the diagnostic; never use
// the observation to decide certification or alter its arithmetic.
let mut b11 = Tay::<Ivl>::zero();
let mut b22 = Tay::<Ivl>::zero();
let mut b12 = Tay::<Ivl>::zero();
for ((&r1, &r2), &mq) in rho0.iter().zip(rho1).zip(&mc) {
let b1 = r1 - mq;
let b2 = r2 - mq;
b11 = b11 + b1 * b1;
b22 = b22 + b2 * b2;
b12 = b12 + b1 * b2;
}
crate::profile::profile_note!(k, "arc reject radii2={:?},{:?} required_lo>0", b11.at(0), b22.at(0));
if b11.at(0).is_positive() && b22.at(0).is_positive() {
let half = (Tay::<Ivl>::lift(1.0) + b12 / tay_sqrt(&(b11 * b22))) * Tay::<Ivl>::lift(0.5);
crate::profile::profile_note!(k, "arc reject half={:?} required_lo>0", half.at(0));
}
}
return false;
};
// |S - O| <= |m - O| + the ball's radius over the piece
@@ -1754,6 +1782,7 @@ pub fn arc_row_terms_over(
let dx = 24.0 * crate::geom::vector3d::sqrt(sq);
let dy = 24.0 * wj.at(TAY).mag();
let dwlo = wj.at(0).lo;
crate::profile::profile_note!(k, "arc row={j} x4={dx} w4={dy} w_lo={dwlo} radius={d_r}");
if !(dx.is_finite() && dy.is_finite() && dwlo > 0.0) {
return false;
}
@@ -1814,6 +1843,15 @@ pub fn gn_certify_span(
mut defs: Option<&mut GnDefs<'_>>,
) -> Result<f64> {
crate::profile::profile_scope!(k, "blend::certificate::gn_certify_span");
crate::profile::profile_note!(
k,
"span begin s=[{},{}] target={} n={} law={}",
a.s,
b.s,
target,
n,
law.is_some()
);
// the series' orders are checked once (the fork leaves them to its callers)
Trunc::new(4 + TAYLOR, n)?;
let mut bound_out = f64::INFINITY;
@@ -1848,6 +1886,7 @@ pub fn gn_certify_span(
for level in 0..=6_u32 {
k.check_cancelled(OP_CERTIFY_SPAN)?;
let pieces = 1_usize << level;
crate::profile::profile_note!(k, "level begin level={level} pieces={pieces}");
// the pieces' end stations
let mut ends = Vec::new();
ends.push(*a);
@@ -1889,6 +1928,7 @@ pub fn gn_certify_span(
i += 1;
}
if !solved {
crate::profile::profile_note!(k, "level reject station");
continue;
}
ends.push(*b);
@@ -1981,6 +2021,7 @@ pub fn gn_certify_span(
Some(ps) => {
let d = Ivl::new(ea.s, eb.s) - Ivl::from(ps.mid);
arc_row_terms_over(
k,
&ps.f,
&ps.v,
&ps.fm,
@@ -1996,6 +2037,10 @@ pub fn gn_certify_span(
}
}
if !proved {
crate::profile::profile_note!(
k,
"level reject enclosure_or_rows m4={m4:?} uv4={uv4:?}"
);
continue;
}
// the level's bound with the uv curves' term `uv` (#2690: taken with each
@@ -2034,6 +2079,11 @@ pub fn gn_certify_span(
};
let den = lj - kh * yj;
let num = wj.hi * da + kh * xj + kh * yj * rows.r;
crate::profile::profile_note!(
k,
"row bound row={j} den={den} w_lo={lj} kh_w4={} num={num}",
kh * yj
);
let positive = den > 0.0;
bound = if positive {
cmax(bound, num / den)
@@ -2049,6 +2099,7 @@ pub fn gn_certify_span(
};
let bound = level_bound(&uv4_fork);
if !bound.is_finite() {
crate::profile::profile_note!(k, "level reject nonfinite_bound");
continue;
}
// certified by each parameter's own term, never larger than the fork's
@@ -2060,6 +2111,10 @@ pub fn gn_certify_span(
v.w = [cmin(lo, rlo), cmax(hi, rhi)];
}
}
crate::profile::profile_note!(
k,
"level bound={bound} target={target} uv4={uv4:?} m4={m4:?}"
);
bound_out = cmin(bound_out, bound);
if bound_out <= target {
break;
@@ -2076,6 +2131,7 @@ pub fn gn_certify_span(
}
lower = cmax(lower, cmax(if rows_vary { 0.0 } else { sum }, k4 * uvl));
}
crate::profile::profile_note!(k, "level lower={lower} target={target} last={last}");
if round_down(lower * (1.0 - 1.0e-12)) > target {
break;
}
@@ -2085,6 +2141,7 @@ pub fn gn_certify_span(
}
last = bound;
}
crate::profile::profile_note!(k, "span end bound={bound_out} target={target}");
Ok(bound_out)
}
+67
View File
@@ -74,7 +74,21 @@ macro_rules! profile_count {
($k:expr, $counter:ident, $n:expr) => {};
}
/// Records a diagnostic note only when explicitly captured by the profiler.
#[cfg(feature = "profile")]
macro_rules! profile_note {
($k:expr, $($arg:tt)*) => {
$k.profiler.note(|| format!($($arg)*));
};
}
#[cfg(not(feature = "profile"))]
macro_rules! profile_note {
($k:expr, $($arg:tt)*) => {};
}
pub(crate) use profile_count;
pub(crate) use profile_note;
pub(crate) use profile_scope;
#[cfg(feature = "profile")]
@@ -176,6 +190,7 @@ mod enabled {
nodes: Vec<Node>,
roots: Vec<u32>,
stack: Vec<Frame>,
notes: Option<Vec<String>>,
}
impl State {
@@ -321,6 +336,30 @@ mod enabled {
}
impl Profiler {
/// Starts a fresh diagnostic capture. Normal profiles keep no per-call notes.
/// At most 1,000,000 notes are retained, independently of the call-path profile.
/// A final `diagnostic capture overflow` note makes truncation explicit.
pub fn capture_notes(&self) {
self.shared.lock().notes = Some(Vec::new());
}
/// Ends diagnostic capture and returns its notes in execution order.
#[must_use]
pub fn take_notes(&self) -> Vec<String> {
self.shared.lock().notes.take().unwrap_or_default()
}
/// Adds a note, formatting it only during an explicit diagnostic capture.
pub(crate) fn note(&self, make: impl FnOnce() -> String) {
if let Some(notes) = self.shared.lock().notes.as_mut() {
if notes.len() < 1_000_000 {
notes.push(make());
} else if notes.len() == 1_000_000 {
notes.push("diagnostic capture overflow".to_owned());
}
}
}
/// Opens a scope named `name` under the innermost open one; it closes when the
/// returned guard drops. Prefer [`profile_scope!`](crate::profile), which keeps the
/// guard to the end of the block.
@@ -590,6 +629,34 @@ mod enabled {
profile_count!(k, Evaluations, 1);
}
#[test]
fn diagnostic_notes_are_opt_in_ordered_and_reset() {
let k = Kernel::default();
k.profiler.note(|| panic!("uncaptured notes must not be formatted"));
assert!(k.profiler.take_notes().is_empty());
k.profiler.capture_notes();
k.profiler.note(|| "first".to_owned());
k.profiler.clone().note(|| "second".to_owned());
assert_eq!(k.profiler.take_notes(), ["first", "second"]);
assert!(k.profiler.take_notes().is_empty());
k.profiler.capture_notes();
k.profiler.note(|| "discarded".to_owned());
k.profiler.capture_notes();
assert!(k.profiler.take_notes().is_empty());
}
#[test]
fn diagnostic_overflow_is_explicit_and_bounded() {
let p = super::Profiler::default();
p.capture_notes();
p.shared.lock().notes = Some(vec![String::new(); 1_000_000]);
p.note(|| panic!("overflow must not format a note"));
p.note(|| panic!("overflow must stay bounded"));
let notes = p.take_notes();
assert_eq!(notes.len(), 1_000_001);
assert_eq!(notes.last().unwrap(), "diagnostic capture overflow");
}
#[test]
fn scopes_aggregate_by_path_and_close_on_early_returns() {
let mut k = Kernel::default();
+88 -6
View File
@@ -518,11 +518,23 @@ impl LawRound {
degree: usize,
knots: &[f64],
radii: &[f64],
) -> Self {
Self::build_on(&mut law_kernel(), size, from, to, degree, knots, radii)
}
#[allow(clippy::too_many_arguments)]
fn build_on(
k: &mut Kernel,
size: [f64; 3],
from: Point3,
to: Point3,
degree: usize,
knots: &[f64],
radii: &[f64],
) -> Self {
use solidcore::blend::fillet_geom::p1::FilletSolverId;
let mut k = law_kernel();
let mut b = create_box(&mut k, Vec3::ZERO, size[0], size[1], size[2]).unwrap();
let (e, reversed) = edge_between(&k, &b, from, to);
let mut b = create_box(k, Vec3::ZERO, size[0], size[1], size[2]).unwrap();
let (e, reversed) = edge_between(k, &b, from, to);
let law = spline_law(degree, knots, radii, reversed);
let base = radii[0].max(radii[radii.len() - 1]);
let mut tols = SolverTols {
@@ -531,11 +543,11 @@ impl LawRound {
};
let mut pieces = Vec::new();
{
let mut session = FilletSession::new(&mut k, &mut b).unwrap();
let mut session = FilletSession::new(k, &mut b).unwrap();
session
.prepare_variable_radius_edges(&mut k, &[e], base, law, 1, 1)
.prepare_variable_radius_edges(k, &[e], base, law, 1, 1)
.unwrap();
session.do_fillet(&mut k).unwrap();
session.do_fillet(k).unwrap();
for id in 0.. {
let Ok(s) = session.solver(FilletSolverId(id)) else {
break;
@@ -1204,3 +1216,73 @@ fn law_peak() {
assert_eq!(faces, 8);
}
}
/// Count the unmodified certificate on the ops law-corner test's four inputs, plus
/// the existing two-piece (2, 4, 2) helper. No wall-clock value is an oracle.
#[cfg(feature = "profile")]
#[test]
#[ignore = "opt-in per-span certificate diagnostic (#3078)"]
fn variable_round_certificate_work() {
let cases = [
(
"ops peak",
[10.0, 20.0, 30.0],
Point3::new(10.0, 0.0, 30.0),
Point3::new(10.0, 20.0, 30.0),
LINEAR,
&[2.0, 4.0, 2.0][..],
),
(
"ops dip",
[20.0; 3],
CUBE_FROM,
CUBE_TO,
LINEAR,
&[4.0, 2.0, 4.0][..],
),
(
"ops dip peak",
[20.0; 3],
CUBE_FROM,
CUBE_TO,
&[0.0, 0.0, 0.25, 0.75, 1.0, 1.0][..],
&[4.0, 2.0, 4.0, 2.0][..],
),
(
"ops dip peak reversed",
[20.0; 3],
CUBE_TO,
CUBE_FROM,
&[0.0, 0.0, 0.25, 0.75, 1.0, 1.0][..],
&[4.0, 2.0, 4.0, 2.0][..],
),
];
for (name, size, from, to, knots, radii) in cases {
let mut k = law_kernel();
k.profiler.capture_notes();
let round = LawRound::build_on(&mut k, size, from, to, 1, knots, radii);
assert!(!round.pieces.is_empty());
eprintln!("CASE {name}");
let notes = k.profiler.take_notes();
assert!(notes.iter().any(|s| s.starts_with("span begin")));
assert!(!notes.iter().any(|s| s == "diagnostic capture overflow"));
let enclosure_calls = notes.iter().filter(|s| s.starts_with("enclose s=")).count() as u64;
for note in notes {
eprintln!("{note}");
}
let profile = k.take_profile();
assert_eq!(enclosure_calls, profile.summary.iter().find(|s| s.name == "blend::certificate::gn_enclose_over").unwrap().count);
profile.write_jsonl(&mut std::io::stderr()).unwrap();
}
let k = gn_kernel();
k.profiler.capture_notes();
let (pieces, _) = law_corner_pieces(&k, [2.0, 4.0, 2.0]);
assert_eq!(pieces.pieces.len(), 2);
eprintln!("CASE helper peak");
// This existing curve-only helper has no support faces/definition certificate.
// Keep that zero-work control distinct from the certified executive inputs.
assert!(k.profiler.take_notes().is_empty());
let profile = k.take_profile();
assert!(!profile.summary.iter().any(|s| s.name == "blend::certificate::gn_certify_span"));
profile.write_jsonl(&mut std::io::stderr()).unwrap();
}
@@ -0,0 +1,157 @@
# #3078: variable-round certificate work
Unit: blend.fillet_section. Diagnostic only; no certificate arithmetic, target,
acceptance test, station solve, or fitted net is changed.
## Reproduction and evidence
`variable_round_certificate_work` in `wr685_law_corners.rs` is an opt-in,
profile-feature test. It constructs the four box/edge/law inputs from
`solidcore-ops/tests/blend.rs::a_variable_round_with_a_corner_in_its_law_is_its_balls_union`
through the kernel executive, with all fixes enabled. Each construction uses a
fresh kernel. It then runs the existing two-piece `(2, 4, 2)` curve-only helper.
That helper invokes `CircularCrossSectionFsg::create_surface`, not a support-face
procedural definition: it has **zero certificate spans and zero enclosures**.
It is a zero-work control, not an example of level-zero certification.
Run the following on a build host (the operator's `myy-cargo` wrapper is required
on the laptop):
```sh
cargo test --profile goldens -p solidcore --features profile \
--test wr685_law_corners variable_round_certificate_work -- --ignored --nocapture
```
The profiler records notes only during an explicit capture. Without `profile`
the instrumentation expands to nothing. A capture is bounded, with an explicit
overflow marker; the measuring test rejects overflow and compares its enclosure
notes to the independent call-path profiler total. The original 100,000-note
capture truncated the two longer cases; those partial counts are not evidence
for the completed table. The final capture uses a 1,000,000-note limit.
## Counts
The accompanying `blend.fillet_section--issue-3078-shorter-cases.csv` records
every attempted span and every reached level for the **two complete shorter
captures only**. The longer two captures were truncated and are deliberately
excluded. The table includes rejected parent spans later bisected by the caller. `pieces` is the
level's planned partition size, not the number necessarily visited: a failed
first piece can end a level after just two enclosure calls. `enclosures` counts
actual calls. Repeated `[s0,s1]` attempts are retained in execution order.
`span_bound` is the best returned bound; an accepted attempt is one at or below
its recorded target. The CSV retains the failed arc construction's interval inputs, minimum rational
denominator and its numerator/weight terms, finite upper/lower bounds and excess
over target, and maximum station/UV fourth-derivative bounds. The new diagnostic
prints the circle's failed positivity interval directly, but that version has
not returned a remote verdict. No sampling manufactures bounds.
| Input | Span attempts | Accepted / rejected parents | Enclosures | Last-level counts L3 / L4 / L5 / L6 |
| --- | ---: | ---: | ---: | ---: |
| peak | 124 | 64 / 60 | 20,808 | 16 / 24 / 17 / 67 |
| dip | 104 | 54 / 50 | 18,774 | 16 / 12 / 12 / 64 |
| dip-peak | incomplete capture | unknown | profiler total 29,368 | unknown |
| reversed dip-peak | incomplete capture | unknown | profiler total 29,198 | unknown |
| curve-only helper | 0 | 0 / 0 | 0 | none |
The independent profiler totals for the longer inputs are complete, but the
per-span notes are not. Do not use their truncated per-span counts. Peak level
starts are `124,124,124,124,108,84,67`; dip's are
`104,104,104,104,88,76,64` (L0 through L6). Peak/dip have 136/104 failed
arc-row levels and 28/22 non-finite-bound levels. The extractor verifies every
span has an end and enclosure counts equal the independent profiler total;
1,399 level records cover all 228 shorter-case span attempts.
## What forces the work
The expensive refinement is observable without timings. In the first peak-case
span `[-4.999899999999967, 0]`, target `4.000000000000001e-6`, levels 0 through 4
fail arc-circle positivity after the first piece. Both station fourth-derivative
bounds remain around `2.2e-29` to `2.5e-29`, not large geometric curvature.
At level 5 all 32 pieces are enclosed (64 calls), but the middle homogeneous row
has
```text
w_lo = 0.5795065387500216
(h^4/384) * w4 = 17.410143434327928
w_lo - (h^4/384) * w4 = -16.830636895577907
```
Level 6 encloses all 64 pieces (128 calls), yet its denominator is still
`0.6519574090667212 - 0.7782375059267068 = -0.12628009685998565`.
The returned bound is infinity; this one rejected parent already cost 202 calls.
Halving that span gives `[-4.999899999999967, -2.4999499999999837]`:
level 5's finite bound is `1.1381794509220917`, level 6's is
`0.1959090577823233`, still about 48,977 times the original `4e-6` target.
Its station and UV fourth bounds remain about `2.4e-29`.
This localizes the excessive refinement to the interval **touch-circle / rational
arc-row composition**, not failure to prove the station root or a large rail
fourth derivative. On the complete peak/dip traces every one of the 20,808 / 18,774
root proofs succeeds in one Krawczyk iteration. Their contraction factors range
from `1.24e-13` to `2.47e-13` / `9.00e-14` to `3.04e-13`, far below the rejection
threshold of one. In `arc_row_terms_over`, each rail and the centre is first
centred independently over the whole piece. The touch-circle projection, relative
radius vectors, squared lengths, normalized dot product, square root, and
homogeneous middle row then compose these dependent intervals. Their common
motion and scale correlations are no longer available. Subdividing the enclosure
domain reduces this overestimation, but the remainder multiplier is the original
span's `h^4/384`, correctly not the piece's. The resulting excessive row fourth
bounds force deeper enclosure levels and eventually smaller interpolation spans.
For the ideal plane-plane geometry with an affine centre and linear radius on
one smooth law segment, the touch-circle radius vectors are fixed directions
times the radius: their normalized dot product and weight are constant, and the
homogeneous rows are affine. This explains the cancellation that the interval
composition misses. The tiny *numerically enclosed* station fourth derivatives
are not an algebraic proof that the stored definition's derivatives are zero;
none is discarded. Law kinks already delimit smooth pieces, and the quoted
failure occurs within a single piece, so removing or smoothing a kink is not a
sound remedy. The target is unchanged and not intrinsically incompatible with
the geometry; it exposes the arc-row overestimation.
## Scope and next proof obligation
No performance fix is included. The measured cause is repeated high-level
certification work; this does not measure machine copies or establish their
share of elapsed time. Series temporaries may make each enclosure expensive,
but the add-of-scale/in-place work of #2728 is deliberately untouched.
A general fix needs a dependency-preserving enclosure of the relative circle
vectors and rational rows, with a proof that all required derivative intervals
and denominator lower bounds remain enclosing, including varying-speed centres
and non-planar supports. An affine-only numerical-zero shortcut, sampled bound,
smaller multiplier, relaxed target, or skipped positivity test would not supply
that proof. The diagnostic is retained to compare every span before/after such
a change. There are no claimed after-count reductions or golden movements.
## Validation
- Earlier remote job 31148 returned wrapper exit 0 with no log/copy-back; the
operator confirmed it had been deliberately cancelled and nothing ran.
- Remote job 31215, through `myy-cargo`: `cargo fmt --all` and the opt-in trace
test passed (1 passed, 15 filtered out). That run used the original capture
instrumentation at `44e13e89a17` plus formatting, not the subsequent larger
capture / numeric-positivity diagnostics / profiler unit tests.
- The longer captures in 31215 hit the old note limit. Shorter-case extraction
and its completeness/count assertions pass (228 spans, 1,399 levels).
- Remote job **31233**, name `sc-gpt-1107-141314-2-edit`, was submitted through
`myy-cargo -t 120` for the complete trace and formatting. The local background
waiter reached its maximum 7,200-second tool limit and was killed, with **no
remote exit verdict and no copied-back files**. The Slurm job's state is
unknown; it has not been cancelled or restarted. Do not resubmit into that
build tree until the operator resolves its state. The wrapper reported its
remote log as
`/home/aapo/scratch/solidcore/logs/slurm-31233-gpt-1107-141314-2-edit.out`.
- The latest instrumentation changes and added profile tests are therefore
uncompiled/unverified. Its formatting has not returned either.
- `git diff --check` passed locally. No cargo/rustc/build/test ran on the laptop.
- `reports/issue-3078-validate.sh` records the remaining validation commands:
section/profile lib tests, full ops blend binary, all selected variable/fillet
integration binaries, fmt, workspace and profile clippy, wasm, panic-site
check, and blend-unit goldens. **None of those gates has run on the latest
source.** No golden movement/no-change comparison is claimed.
This is a partial investigation, not a ready performance-fix branch. Remaining
work is the complete four-case per-span capture, direct numerical circle
positivity diagnostics, compilation of the final instrumentation/tests, and all
requested gates/goldens. No certificate-preserving optimization is claimed.
File diff suppressed because it is too large Load Diff
+83
View File
@@ -0,0 +1,83 @@
"""Extract every attempted span/level; verify counts against independent profiles."""
import collections
import csv
import json
import re
import sys
from pathlib import Path
rows = []
case = None
span = None
level = None
counts = collections.defaultdict(collections.Counter)
profile_counts = {}
number = r'[-+]?(?:\d*\.?\d+(?:[eE][-+]?\d+)?|inf|NaN)'
for text in Path(sys.argv[1]).read_text().splitlines():
if text.startswith('CASE '):
case = text[5:]
elif text.startswith('span begin'):
counts[case]['spans'] += 1
s = re.match(r'span begin s=\[(.*?),(.*?)\] target=(\S+) n=(\d+) law=(\S+)', text)
span = dict(case=case, span=counts[case]['spans'], s0=s[1], s1=s[2], target=s[3])
elif text.startswith('level begin'):
m = re.match(r'level begin level=(\d+) pieces=(\d+)', text)
level = dict(span, level=m[1], pieces=m[2], enclosures=0, failure='', bound='', lower='', min_den='', span_bound='')
rows.append(level)
counts[case]['L'+m[1]] += 1
elif text.startswith('enclose s='):
level['enclosures'] += 1
counts[case]['enclosures'] += 1
elif text.startswith(('enclose reject', 'arc reject')):
level['failure'] += text + '; '
counts[case][text.split('=')[0].split(' radii2')[0].split(' half')[0]] += 1
elif text.startswith('level reject'):
level['failure'] += text.split(' m4=')[0] + '; '
if ' m4=' in text:
m4, uv4 = text.split(' m4=')[1].split(' uv4=')
level['m4_max'] = max(map(float, re.findall(number, m4)))
level['uv4_max'] = max(map(float, re.findall(number, uv4)))
counts[case][text.split(' m4=')[0]] += 1
elif text.startswith('row bound'):
den = float(re.search(r'den=(\S+)', text)[1])
if level['min_den'] == '' or den < level['min_den']:
level['min_den'] = den
level['worst_row'] = text
elif text.startswith('level bound='):
m = re.match(r'level bound=(\S+) target=\S+ uv4=(.*?) m4=(.*)', text)
level['bound'] = m[1]
level['bound_excess'] = float(m[1]) - float(span['target'])
if level['bound_excess'] > 0:
level['failure'] += 'upper bound > target; '
level['uv4_max'] = max(map(float, re.findall(number, m[2])))
level['m4_max'] = max(map(float, re.findall(number, m[3])))
elif text.startswith('level lower='):
level['lower'] = text.split()[1].split('=')[1]
level['last'] = text.split()[-1].split('=')[1]
elif text.startswith('span end'):
bound = re.search(r'bound=(\S+)', text)[1]
for row in reversed(rows):
if row['case'] != case or row['span'] != span['span']:
break
row['span_bound'] = bound
counts[case]['span_ends'] += 1
counts[case]['accepted' if float(bound) <= float(span['target']) else 'rejected'] += 1
counts[case]['last_L'+level['level']] += 1
elif text.startswith('{"type":"summary"'):
record = json.loads(text)
if record['name'] == 'blend::certificate::gn_enclose_over':
profile_counts[case] = record['count']
elif text == 'diagnostic capture overflow':
raise AssertionError('incomplete capture')
for case, c in counts.items():
assert c['spans'] == c['span_ends'], (case, c)
assert c['enclosures'] == profile_counts[case], (case, c, profile_counts)
print(case, json.dumps(dict(c), sort_keys=True))
fields = ['case', 'span', 's0', 's1', 'target', 'level', 'pieces', 'enclosures', 'failure', 'bound', 'bound_excess', 'lower', 'last', 'min_den', 'worst_row', 'm4_max', 'uv4_max', 'span_bound']
with Path(sys.argv[2]).open('w') as out:
writer = csv.DictWriter(out, fieldnames=fields)
writer.writeheader()
writer.writerows(rows)
print('rows', len(rows))
+44
View File
@@ -0,0 +1,44 @@
#!/usr/bin/env bash
# Run one focused phase per myy-cargo job; never run builds on the laptop.
set -uo pipefail
export CARGO_BUILD_JOBS=2
phase=${1:?use tests, integrations, gates, or goldens}
mkdir -p reports/issue-3078-validation
failed=0
run() {
local name=$1; shift
printf 'BEGIN %s\n' "$name"
nice -n 10 "$@" >"reports/issue-3078-validation/$name.log" 2>&1
local rc=$?
printf '%s exit=%s\n' "$name" "$rc" | tee -a "reports/issue-3078-validation/$phase-status.txt"
tail -12 "reports/issue-3078-validation/$name.log"
if [ "$rc" != 0 ]; then failed=1; fi
}
: >"reports/issue-3078-validation/$phase-status.txt"
case "$phase" in
tests)
run section-lib cargo test --profile goldens -p solidcore --features profile --lib -- blend::fillet_section
run profile-lib cargo test --profile goldens -p solidcore --features profile --lib -- profile::
run ops-blend cargo test --profile goldens -p solidcore-ops --test blend
;;
integrations)
args=()
while IFS= read -r file; do
args+=(--test "$(basename "$file" .rs)")
done < <(grep -ilE 'variable|fillet' crates/solidcore/tests/*.rs | sort)
printf '%s\n' "${args[@]}" > reports/issue-3078-validation/integration-targets.txt
run integrations cargo test --profile goldens -p solidcore --no-fail-fast "${args[@]}" -- --test-threads=2
;;
gates)
run fmt cargo fmt --all --check
run clippy cargo clippy --workspace --all-targets -- -D warnings
run profile-clippy cargo clippy -p solidcore --features profile --lib --test wr685_law_corners -- -D warnings
run wasm cargo check -p solidcore --target wasm32-unknown-unknown
;;
goldens)
run panic-sites python3 -I scripts/ci/check_panic_sites.py --base f4f70127afe
run blend-goldens cargo run --profile goldens -p goldens -- --unit 'blend.*' --goldens
;;
*) printf 'Unknown phase: %s\n' "$phase" >&2; exit 2 ;;
esac
exit "$failed"