The mathematics of the geology, GPR and block-cutting stacks¶
Code-bound equation layer for the shipping geology/discontinuity, GPR-processing and block-cutting implementations in this repository. Each equation: statement, short derivation, code provenance, method-class citation. Derived from the implementation as read, not copied from paper figures. Renders natively on GitHub and on the docs site (MathJax).
Shipping implementations covered: TerzaghiCorrection, FractureIntensity,
BlockSizeMath, InSituBlockSize, SetClusterer, KinematicAnalysis,
JointSetDfnGenerator, BaecherDfnGenerator (Part A);
RadargramProcessor, FractureExtractor, Kriging, FractureUncertainty,
GprDetectionCalibration (Part B); BlockCutOptSolver + CuttingGrid,
SlabYieldOptimizer, BlockYieldOptimizer, WireSawFeasibility,
CutOrientationOptimizer, RecoveryCascade, FisherRobustSampler,
AmrrPlanner, BlockYieldEstimator (Part C). All under
src/Frahan.StonePack.Core/ unless noted; one GH-layer item
(FractureBlockPackComponent) is included because the uncertainty-safe yield
toggle lives there.
Where the code deviates from the textbook formulation, the CODE's version is stated and the deviation is flagged. Topics requested but absent from code are declared in one line.
Part A. Geology / discontinuity stack¶
A1. Terzaghi orientation-bias correction¶
A1.1 Weight¶
A sampling element (scanline or rock-face window) under-samples discontinuities sub-parallel to it. Each discontinuity gets the Terzaghi weight
where \(\mathbf{p}_i\) is the discontinuity pole and \(\angle(\cdot,\cdot)\) is the acute (axial) angle. \(\delta\) is the angle between the discontinuity PLANE and the sampler; as \(\delta \to 0\) (grazing) the weight diverges, so it is capped by a blind-zone half-angle \(\delta_{\min}\) (default \(15^\circ\)):
Derivation. The expected number of intersections of a plane set with a
scanline is proportional to \(|\cos\angle(\mathbf{t},\mathbf{p})| = \sin\delta\);
dividing each observation by its intersection probability de-biases the count.
Features inside the blind zone are counted as clamped
(TerzaghiResult.Clamped) and flagged as lower-confidence rather than
extrapolated. Code: TerzaghiCorrection.Weight,
TerzaghiCorrection.ScanlineBiasAngleDeg, TerzaghiCorrection.WindowBiasAngleDeg,
TerzaghiCorrection.Correct (Discontinuity/TerzaghiCorrection.cs).
Citation ([Algorithm] attribute, TerzaghiCorrectionComponent): Terzaghi 1965,
Geotechnique 15(3):287-304; Priest 1993 ch. 5; capped-weight practice (Park & West).
A1.2 Corrected set proportions and weighted mean pole¶
a weighted AXIAL mean: each pole is folded to the lower hemisphere and
sign-aligned to the largest-weight pole before summing, so \(\mathbf{p}\) and
\(-\mathbf{p}\) (the same plane) never cancel. Code:
TerzaghiCorrection.Correct (per-set sums), TerzaghiCorrection.CorrectedMeanPole.
A2. Fracture intensity (Dershowitz-Herda P_ij family)¶
A2.1 P32 from spacings (persistent-set route)¶
For persistent parallel sets with normal spacing \(s_j\) (scaled to metres by
unitScale):
each set contributing unit fracture area per \(s_j\) of thickness. This equals
the Palmstrom volumetric joint count \(J_v\) for persistent joints (cross-checked
against BlockSizeMath.Jv). Code: FractureIntensity.P32FromSpacings.
Citation ([Algorithm] attribute, FractureIntensityComponent):
Dershowitz & Herda 1992, 33rd US Rock Mech. Symp.
A2.2 Scanline forward and inverse (the Terzaghi geometry factor)¶
Derivation. A scanline crosses a set of volumetric intensity \(P_{32}\) at
linear frequency \(P_{32}|\cos\theta|\) where \(\theta\) is the scanline-to-pole
angle; inverting recovers \(P_{32}\) from a measured count. Code deviation:
the inverse clamps the cosine at
\(\cos(90^\circ - \delta_{\min})\) (default \(\delta_{\min}=15^\circ\)) so a
grazing set does not blow up, mirroring the Terzaghi blind-zone cap; the
textbook inversion has no floor. Code: FractureIntensity.P10AlongScanline,
FractureIntensity.P32FromP10. Citation: Wang 2005 (stereological P10/P21 to P32).
A2.3 Direct DFN route and companions¶
Code: FractureIntensity.P32FromAreas, FractureIntensity.P30FromCount,
FractureIntensity.P21FromTraces; assembled by FractureIntensity.Compute.
A3. Deterministic block size (Palmstrom)¶
For the 3 dominant sets with unit poles \(\mathbf{n}_1,\mathbf{n}_2,\mathbf{n}_3\), spacings \(s_1,s_2,s_3\) and pairwise acute inter-set angles \(\gamma_{ij}\):
with the block-size index \(I_b = (s_1+s_2+s_3)/3\) and equivalent diameter
\(D_{eq} = V_b^{1/3}\). Guards: \(V_b\) is declared undefined when any
\(\sin\gamma_{ij} < 0.087\) (sets within about \(5^\circ\) of parallel) or when
fewer than 3 valid sets exist (slabs / columns, not blocks). Code note: the
code uses the Palmstrom 1974 correlation constant pair \((110, 2.5)\), not the
older Palmstrom 1982 \((115, 3.3)\) form. Code: BlockSizeMath.Compute,
BlockSizeMath.ComputeFromDip (Discontinuity/BlockSizeMath.cs). Citation
(file header): Palmstrom 1995/2005; ISRM Suggested Methods (spacing along the
set normal).
A4. In-situ block-size distribution (Monte-Carlo IBSD)¶
A4.1 Per-realization block volume¶
the scalar-triple-product (determinant) form of Palmstrom's formula: \(q = 1\)
for mutually orthogonal sets (right prism), \(q \to 0\) as sets go coplanar.
Realizations with \(q < 0.02\) (roughly \(1^\circ\) from coplanar) are skipped as
ill-conditioned. With more than 3 sets, the 3 smallest-spacing samples bound
the block. Code: InSituBlockSize.Simulate, InSituBlockSize.Det.
Citation ([Algorithm] attribute, InSituBlockSizeComponent): Kalenchuk,
Diederichs & McKinnon 2006; Palmstrom 2005.
A4.2 Fisher orientation sampling¶
Each set's pole is drawn from a Fisher (von Mises-Fisher) distribution about the mean pole \(\boldsymbol\mu\) with concentration \(\kappa\) by inverse-CDF of the colatitude:
with \(u,\ \phi/2\pi \sim U(0,1)\) and \((\mathbf a, \mathbf b)\) an orthonormal basis normal to \(\boldsymbol\mu\). The concentration comes from an input angular scatter via Fisher's circular-s.d. approximation:
Code: InSituBlockSize.FisherSample (identical sampler in
BaecherDfnGenerator.FisherSample). Citation (file header): Fisher, Lewis &
Embleton 1987 (inverse-CDF colatitude sampling).
A4.3 Spacing sampling¶
Box-Muller supplies \(z\). Code: InSituBlockSize.SampleSpacing.
A4.4 Outputs¶
Empirical \(P_{10}/P_{50}/P_{90}\) volume percentiles (linear interpolation on
the sorted sample), \(D_{eq} = V_{b,50}^{1/3}\), mean non-orthogonality
\(\bar q\), right-prism fraction \(\Pr[q \ge 0.95]\) (the sawable-to-rectangular
signal), and a shape class from the sorted spacings with elongation
\(= s_{\max}/s_{\text{mid}} \ge 2\) and flatness \(= s_{\text{mid}}/s_{\min} \ge 2\)
splitting blocky / columnar / tabular / columnar+tabular. Code:
InSituBlockSize.Simulate, InSituBlockSize.ShapeClass,
InSituBlockSize.Percentile.
A5. Joint-set statistics: Watson-kernel mean-shift clustering¶
Facet poles (lower hemisphere, weighted by facet point count \(w_j\)) are clustered by mean-shift on the unit sphere with a Watson AXIAL kernel
which is antipodally symmetric (\(\mathbf{x}\) and \(-\mathbf{x}\) equal), so a set never splits across the stereonet equator. The mean-shift update sign-folds each pole toward the current mode:
iterated to convergence (\(< 0.02^\circ\) axial move, max 60 iterations);
converged modes within MergeDeg (default \(8^\circ\) axial) merge; facets are
assigned to the nearest mode by axial angle; the number of sets is discovered,
not preset. Per-set normal spacing = the mean of the positive gaps between
sorted projections of member-facet centroids onto the set pole:
Code note: the code's own header calls the kernel Watson; the exponent
matches the Watson (axial bipolar) density \(\propto e^{\kappa(\mathbf m\cdot\mathbf x)^2}\)
up to the constant factor \(e^{-\kappa}\). Code: SetClusterer.Cluster,
SetClusterer.NormalSpacing (Discontinuity/DiscontinuitySetClusterer.cs).
Citation (file header): set identification after Riquelme et al. 2014 (DSE).
Fisher statistics: present as the sampling distribution (A4.2, A7.2); no
Fisher \(\kappa\) ESTIMATION from data is implemented in Core (the
\(\kappa = (81/\text{scatter})^2\) map converts a user-supplied scatter).
Bingham statistics: NOT present anywhere in the codebase (grep over src/),
one line and moving on.
A6. Kinematic feasibility (Markland-style screens, as coded)¶
Convention: dip \(\in [0^\circ, 90^\circ]\), dip-direction azimuth clockwise from North; friction angle \(\phi\); lateral limit \(\Delta\) (default \(20^\circ\)). Signed azimuth difference folded to \((-180^\circ, 180^\circ]\). The apparent dip of the cut face (dip \(\psi_f\), dip-dir \(\alpha_f\)) along azimuth \(\alpha\):
Code: KinematicAnalysis.ApparentDip, KinematicAnalysis.AzDiff.
A6.1 Planar sliding (per set)¶
Feasible iff all three hold:
Code deviation from classic Markland: daylighting is tested against the
face's APPARENT dip evaluated along the SET's dip direction
\(\psi_{\text{app}}(\alpha_s)\), not the face's true dip \(\psi_f\); this is a
stricter, geometrically exact daylight test for sets oblique to the face.
Code: KinematicAnalysis.Analyze (planar block). Citation ([Algorithm]
attribute, KinematicFeasibilityComponent): Markland 1972; Hoek & Bray 1981;
Wyllie & Mah 2004 ch. 7.
A6.2 Wedge sliding (per pair)¶
The intersection line of sets \(i,j\) is \(\mathbf{L} = \mathbf{n}_i \times \mathbf{n}_j\) (pointed downward), with
feasible iff
Code: KinematicAnalysis.Intersection, KinematicAnalysis.Analyze (wedge
block). Citation: Hoek & Bray 1981. Code note: no lateral-limit gate on
wedges (daylight along the trend is the direction test), and friction is
screened on the plunge only (the standard Markland wedge screen; no wedge-factor
refinement).
A6.3 Flexural toppling (per set)¶
Feasible iff the set dips INTO the face and inter-layer slip is possible:
Derivation. \((90^\circ - \psi_s)\) is the inclination of the layer normals;
slip on layer boundaries requires the face to steepen past friction by that
amount (the Goodman-Bray condition). Code: KinematicAnalysis.Analyze
(toppling block). Citation: Goodman & Bray 1976. Code note: the slip
condition uses the face's TRUE dip \(\psi_f\), not an apparent dip.
A7. DFN generation¶
A7.1 Infinite-plane joint-set DFN (spacing walk)¶
For each set with unit normal \(\mathbf{n}\) and mean spacing \(\bar s\): project the 8 box corners onto \(\mathbf{n}\) to get \([t_{\min}, t_{\max}]\) (measured from the box centre \(\mathbf{c}\)), then emit planes at
each plane through \(\mathbf{c} + t_k \mathbf{n}\) with normal optionally perturbed by a tangent-plane Gaussian (small-angle Fisher approximation):
with \((\mathbf u, \mathbf v)\) an orthonormal in-plane basis. Code:
JointSetDfnGenerator.Generate, JointSetDfnGenerator.PerturbNormal
(Masonry/Quarry/JointSetDfnGenerator.cs). Citation (file header +
[Algorithm] attribute): Priest 1993 ch. 4 (spacing along a scanline); ISRM
Suggested Methods.
A7.2 Baecher disc DFN (finite persistence)¶
Each fracture is a finite circular disc: centre uniform in the domain (Poisson point process), pole Fisher-sampled about the set mean (sampler identical to A4.2), radius lognormal. The disc count per set follows from the linear intensity \(P_{10} = 1/\bar s\):
Derivation. A set of discs of mean squared radius \(\mathbb E[r^2]\) crossed normal to their mean pole produces \(N \pi \mathbb E[r^2] / V\) intersections per unit length; setting that equal to \(P_{10}\) gives \(N\). The lognormal radius is sampled with the arithmetic-mean/CV parameterization
and the realized intensity is reported as \(P_{32} = \sum_i \pi r_i^2 / V\).
Code: BaecherDfnGenerator.Generate, BaecherDfnGenerator.LogNormal,
BaecherDfnGenerator.FisherSample
(Masonry/Quarry/BlockCutOpt/BaecherDfnGenerator.cs). Citation (file header):
Baecher, Lanney & Einstein 1977; Fisher 1953; Dershowitz & Herda 1992.
Part B. GPR processing stack¶
B1. Velocity model and time-to-depth¶
with \(c = 0.299792458\) m/ns. Per-stone \((\varepsilon_r, v, f)\) presets live in
GprPresets (marble \(\varepsilon_r \approx 9\), \(v \approx 0.10\) m/ns; granite
\(\varepsilon_r \approx 6\), \(v \approx 0.12\) m/ns), kept self-consistent via
\(\varepsilon_r = (c/v)^2\). Grid extraction returns the true sample interval
\(\Delta t\) (ns) when the reader knows it, else recovers it from the
metres-per-sample step at vacuum velocity, \(\Delta t = 2\,\Delta z / c\).
Code: RadargramProcessor.ToGrid, FractureExtractor.Extract (depth per pick),
GprPresets. Citation ([Algorithm] attribute, GprFractureExtractComponent):
"v=c/sqrt(eps_r); depth=v*t/2".
B2. B-scan processing chain¶
Validated order (RadargramProcessor.Run): dewow, background removal,
time-zero mute, smooth2d(1,1), t-power gain, [Stolt migration, smooth2d(1,2)],
Hilbert energy, smooth2d(2,2), depth-equalize. Code note: AGC is
implemented but NOT in the validated chain (the legacy prototype used it; the
shipping Run does not).
B2.1 Dewow (running-mean high-pass)¶
per trace, window \(W = \max(5, \lfloor n_s/30 \rfloor)\); out-of-range samples
contribute zero but the divisor stays the FULL width \(W\) (matches
numpy.convolve(..., 'same') exactly; a textbook running mean would divide by
the clipped count). Code: RadargramProcessor.Dewow,
RadargramProcessor.BoxMeanSame.
B2.2 Background removal and time-zero mute¶
removing horizontal banding / the direct wave, then zeroing the air-wave
coupling band. Code: RadargramProcessor.BackgroundRemoval,
RadargramProcessor.TimeZeroMute.
B2.3 t-power gain¶
compensating spherical divergence + absorption. Code deviation: the gain
argument is offset by \(+1\) (so the first sample is not zeroed) and the default
exponent is the empirically tuned \(1.6\), not the textbook \(t^1\) (spherical) or
\(t^2\) (energy) laws. Code: RadargramProcessor.TPowerGain.
B2.4 AGC (implemented, not in the validated chain)¶
sliding-window RMS normalisation with the same 'same'-convention box mean.
Code: RadargramProcessor.Agc.
B2.5 Stolt f-k migration (constant velocity)¶
Exploding-reflector model with migration velocity \(v_m = v/2\). After a 2-D DFT \(P(\omega, k_x)\) on the exact grid (arbitrary-length DFT, no zero-padding), each output wavenumber pair \((k_z, k_x)\) with \(k_z = \omega/v_m\) samples the source spectrum at the Stolt-mapped frequency with the Stolt Jacobian:
by linear interpolation in \(\omega\) (out-of-range values are set to 0), then inverse 2-D DFT. A cosine dip taper suppresses steep-dip aliasing:
Code: RadargramProcessor.StoltMigration, Fft.Dft. Citation
([Algorithm] attribute): Stolt 1978. Code deviations: cosine taper band
\(0.85\)-\(1.0\) and left/right-zero interpolation are implementation choices of
the validated Python prototype, ported bit-compatibly.
B2.6 Hilbert instantaneous energy¶
the squared analytic-signal envelope per trace: fractures/cavities reflect
strongly (high \(E\)), intact stone is the low-\(E\) background. Code:
RadargramProcessor.HilbertEnergy, Fft.AnalyticEnvelope. Citation
([Algorithm] attribute): Taner 1979 (instantaneous attributes); Porsani 2006,
Isakova 2021 (high energy = fracture).
B2.7 Depth equalization¶
per-row median normalisation (box-smoothed, \(W = 31\)): a locally strong DEEP
reflector reads as a fracture despite absolute decay with depth. Code:
RadargramProcessor.DepthEqualizeEnergy.
B3. Fracture picking with dip-aware lateral continuity¶
Candidates are per-trace local maxima of the energy section above the \(q = 0.985\) global quantile. A candidate is kept only if it has lateral support: for each of \(n_{sl} = 9\) candidate slopes \(\sigma\) (samples/trace) up to the dip gate
the pick mask is sheared so a reflector of that dip is horizontal, like-picks
are counted within a \(\pm 2\)-sample depth band over a 41-trace window, and the
pick survives iff the maximum support over slopes is \(\ge 12\). Each surviving
pick carries \(z = v\,t/2\) and confidence \(=\) energy normalised to \([0,1]\).
Code deviation: the USGS criterion is a horizontal \(\ge 40\)-trace
continuity rule; the dip-aware shear extension (following reflectors up to
\(45^\circ\)) is a Frahan evolution that keeps dipping shear zones while
enforcing the USGS \(< 45^\circ\) gate. Code: FractureExtractor.Extract,
FractureExtractor.ToReflectorPicks. Citation ([Algorithm] attribute):
USGS Mirror Lake WRIR 99-4018C (continuity); Porsani 2006; Isakova 2021.
B4. Fracture-surface kriging (Gaussian-process regression)¶
Simple kriging on mean-centred depths \(z_i\) at pick locations \((x_i, y_i)\) with a GAUSSIAN covariance and nugget:
\(\sigma(\mathbf{x}_*)\) is the posterior standard deviation used as \(\sigma_{\text{interp}}\) in the tolerance ladder (about 0 at picks, growing in the gaps between scan lines). Hyperparameters \((\text{range}, \text{nugget})\) are fitted by minimising the negative log marginal likelihood over a small grid (ranges \(\{0.03, \ldots, 0.6\}\times\)extent, nuggets \(\{0.003, \ldots, 0.08\}\times\)sill):
Code deviations: (1) the variogram is not fitted experimentally; the model
is fixed Gaussian and the range/nugget come from the marginal likelihood, which
the header motivates as capturing cross-line correlation that a
nearest-neighbour range heuristic under-estimates. (2) Predict returns the
LATENT variance \(\operatorname{var}(x_*) = \text{sill} - \mathbf w^\top \mathbf w\)
(no nugget added back), the variance of the noise-free field, matching
sklearn's GaussianProcessRegressor latent-function std; add the nugget at the
caller for observation variance. The file-header comment previously wrote
(sill + nugget); corrected 2026-07-06 to match Predict (the code is
authoritative).
(3) Cholesky failures (near-duplicate points) bump the nugget by \(\times 10\) up
to 6 attempts. Code: Kriging..ctor, Kriging.Predict, Kriging.Sigma,
Kriging.FitMarginalLikelihood
(Masonry/Quarry/Processing/Kriging.cs). Citation ([Algorithm] attribute,
FractureBoundedSlabsComponent): ordinary-kriging bed surfaces, Cressie 1993;
validated against the sklearn prototype (fracture_uncertainty.py).
In the shipping 3-D surface component the kriging is applied to the RESIDUAL
about a least-squares dip plane,
\(z_i - (a x_i + b y_i + c)\), so the plane carries dip and position and the
kriged residual is a bounded undulation (clamped to the residual range).
Code: GprFractureSurface3DComponent.SolveSafe (GH layer).
B5. The tolerance ladder (position uncertainty)¶
Per-location 1-sigma deviation of the reconstructed fracture from the real fracture, propagated in quadrature:
with the three rungs, exactly as coded:
Derivation. \(z = v t/2\) gives \(\partial z/\partial v = z/v\) (velocity term, grows with depth) and \(\partial z / \partial t_0 = v/2\) (time-zero term, dominates at shallow depth, Xie, Lai & Derobert 2021 Eq. 3-4); \(\lambda/4\) is the vertical-resolution floor; \(\sigma_v / v = \tfrac12 \sigma_\varepsilon/\varepsilon_r\) follows from \(v \propto \varepsilon_r^{-1/2}\); the mesh term is the sagitta of a chord of length \(h\) on curvature \(\kappa\). The optimisation target is the confidence that the fracture lies within tolerance \(T\):
averaged over the surface (\(\operatorname{erf}\) via Abramowitz & Stegun 7.1.26,
error \(< 1.5\times10^{-7}\), no MathNet dependency). Code:
FractureUncertainty.DepthSigma, FractureUncertainty.VelocityRelUncertainty,
FractureUncertainty.LambdaQuarter, FractureUncertainty.TimeZeroSigma,
FractureUncertainty.RectTimeZeroSigma, FractureUncertainty.MeshSigma,
FractureUncertainty.Combine, FractureUncertainty.ConfidenceWithin,
FractureUncertainty.Erf. Citations (file header): Xie, Lai & Derobert 2021
(Measurement 168:108330); Porsani 2006 (velocity error at depth).
B6. The detection rung and uncertainty-safe yield¶
B6.1 Minimum detectable area (depth-aware Fresnel floor)¶
Derivation. The first Fresnel zone has radius \(r_F = \sqrt{\lambda z / 2}\),
area \(\pi \lambda z / 2\); the code's calibrated floor
\((\lambda/4)(z/2) = \lambda z / 8\) is \(1/(4\pi)\) of that full zone area
(a sub-Fresnel fracture still reflects, weakly), calibrated so the 1-10 m^2
open sub-horizontal population reproduces Molron 2020's ~80% detection.
The depth-free term \((3\lambda/4)^2\) is the shallow resolution floor.
Code deviation: the older depth-free-only form under-estimated \(A_{\min}\);
the depth term was added (EVOLVED 2026-06-05). Code:
FractureUncertainty.MinDetectableArea.
B6.2 Detection probability¶
\(\eta_0\) is the per-stone base imaging efficiency from
GprDetectionCalibration (granite 0.80 MEASURED, Molron 2020; others
extrapolated with an IsMeasured flag). Minor doc mismatch: the XML
comment says "default 0.85" for baseEfficiency; the signature default is
0.80. The detection-adjusted effective confidence caps the position
confidence:
Code: FractureUncertainty.DetectionProbability,
FractureUncertainty.EffectiveConfidence, FractureUncertainty.Summarise,
GprDetectionCalibration. Citations (file headers): Molron et al. 2020
(Aspo, 10.1016/j.enggeo.2020.105674); Dorn et al. 2012.
B6.3 Uncertainty-safe yield (how sigma enters the yield)¶
The fracture position sigma enters the block packer as a HARD inward clearance: a candidate block passes only if the block GROWN by the clearance \(c = \sigma_{\text{fracture}}\) on all sides lies fully inside the fracture-bounded slab mesh (centre + 8 expanded corners, parity ray-cast point-in-mesh):
so no block sits within the measured GPR uncertainty of a fracture; the
geometric (clearance-ignored) number is the optimistic bound. There is no
class named UncertaintySafeYield; the mechanism is this clearance toggle.
Code: FractureBlockPackComponent.SolveSafe,
FractureBlockPackComponent.BlockInside
(src/Frahan.StonePack.GH/Quarry/FractureBlockPackComponent.cs, GH layer);
sigma supplied by Kriging.Sigma via GprFractureSurface3DComponent.
Part C. Block-cutting optimizers¶
C1. BlockCutOpt (Elkarmoty 2020) pose search¶
C1.1 Candidate grid¶
The tested region (AABB) is tiled with an oriented grid of candidate blocks of size \((L_x, L_y, L_z)\) at kerf-inflated pitch:
centred on the region centroid, rotated by \(R\), translated by \((dx, dy)\), and
clipped to blocks whose 8 corners lie inside the tested area. The OBB body
uses the un-inflated half-sizes; kerf only widens the pitch. Code:
CuttingGrid.GenerateTilted (Masonry/Quarry/BlockCutOpt/CuttingGrid.cs).
Citation ([Algorithm] attributes, BlockCutOptComponents): Elkarmoty, Bondua
& Bruno 2020, Resources Policy 68:101761 (DOI 10.1016/j.resourpol.2020.101761),
psi-only; the \((\theta, \phi)\) tilt axes are the Frahan I1 improvement.
C1.2 Objective and recovery (Eq. 7-1 as coded)¶
exhaustive enumeration over the 5-D pose grid, triangle-OBB tests accelerated by an AABB BVH (Akenine-Moller 2001 overlap test, Frahan I2); the parallel solver reproduces the serial argmax bit-identically (ties go to the earliest pose). Recovery:
with the kerf volume approximated as a uniform thin film over the footprint
(explicitly marked Phase-1 grade in code). Code: BlockCutOptSolver.Solve,
BlockCutOptSolver.SolveInternal, BlockCutOptSolver.SolveInternalSerial,
BlockCutOptSolver.ApproximateKerfVolume, TriangleAabbBvh.AnyTriangleIntersects.
Deviation / absence note: the requested "guillotine DP recurrence"
(Gilmore & Gomory 1965 staged-guillotine dynamic program) is NOT implemented
anywhere in the BlockCutOpt stack; Gilmore-Gomory is cited as grounding in
RecoveryCascade and in [Algorithm] attributes
(FractureBoundedSlabsComponent, BedBlockLayoutComponent), but the shipping
solvers are (a) this exhaustive pose search, (b) the Kim 2025 randomized
guillotine TREE packer (TreePackForest.Pack: each placement splits the slab
into three axis-aligned sub-slabs along the element's free faces, split order
randomized, forest-of-trees restarts; a stochastic constructive method, not a
DP), and (c) a recursive full-span 3-D guillotine packer in
FractureBlockPackComponent (packer mode 5) whose recursion is
region \(\to\) place flush block \(\to\) split remainder by three full-span cuts
\(\to\) recurse. No value-function recurrence \(V(w, h) = \max(\ldots)\) exists in code.
C2. SlabYieldOptimizer (per-block slab plan)¶
For each candidate plan (axis \(a\), thickness \(t\), kerf \(k\)) on a block with AABB extent \(E_a\) and cross-section area \(A_\perp\):
picking the highest-scoring plan. A conflict is a fracture plane aligned with the slab axis within tolerance whose anchor point lies in the block AABB:
default \(\lambda_c = 0.05\), tol \(= 0.10\) rad. Derivation of \(n\): \(n\) slabs
consume \(n t + (n-1) k \le E_a\), i.e. \(n \le (E_a + k)/(t + k)\). Code:
SlabYieldOptimizer.PickBest, SlabYieldOptimizer.CountConflicts,
SlabYieldOptimizer.ToFracturePlanes
(Masonry/Quarry/GeoCut/SlabYieldOptimizer.cs). Code note: the crack
penalty is a linear score deduction (a heuristic), not a constraint; only
near-axis-parallel planes anchored inside the box count, so oblique fractures
are invisible to this scorer.
C3. BlockYieldOptimizer (raw block to product blocks, fracture dodging)¶
C3.1 Per-axis tiling¶
For raw length \(L\), target size \(s\) with tolerance band \([s - \tau,\ s + \tau]\) and kerf \(k\), the largest size at count \(n\) and the axis choice are
maximising the used length (the remainder is trim waste); the best of the 6
axis-to-target permutations wins. Geometric yield
\(= n_x n_y n_z \cdot V_{\text{block}} / V_{\text{raw}}\). Code:
BlockYieldOptimizer.Optimize, BlockYieldOptimizer.Axis
(Fabrication/BlockYieldOptimizer.cs). Citation ([Algorithm] attribute,
BlockYieldComponent): waste-minimising rectangular cutting, size flexes
within tolerance.
C3.2 Fracture dodging (grid phase search)¶
With fracture planes given in the block frame, the grid ORIGIN is slid within the per-axis trim slack (a coarse 3-D phase grid, work-capped at \(\text{steps}^3 \cdot N_{\text{blocks}} \le 5\times10^5\)):
a plane crossing a box iff the 8 corner signed distances have mixed signs
(tolerance \(10^{-7}\)). Blocks fall BETWEEN fractures instead of across them.
Code: BlockYieldOptimizer.OptimizePhase, BlockYieldOptimizer.Crosses.
C4. Wire-saw feasibility¶
A tensioned wire is straight at every instant, so the swept cut surface must be RULED:
and a ruled surface that is also DEVELOPABLE (\(K = 0\)) is the cleanest single-pass cut. As coded, on an \(n \times n\) sample grid:
where \(\operatorname{dev}\) is the max distance of an isocurve's interior sample points from its endpoint chord (a degenerate chord, i.e. a closed/periodic isocurve, returns the loop spread so a cylinder seam cannot read as a ruling);
with the ruling twist reported as the max angle between consecutive rulings. The kerf-compensated toolpath is the cut surface offset along its normal by
Code: WireSawFeasibility.Analyze, WireSawFeasibility.ChordDeviation,
WireSawFeasibility.BuildRulings (Fabrication/WireSawFeasibility.cs).
Citation ([Algorithm] attributes, WireSawFeasibilityComponent +
WireSawToolpathAdapterComponent): robotic diamond-wire cutting of natural
stone, J. Comp. Design & Engineering 2024 (Zhang et al., 11(6):75-85,
DOI 10.1093/jcde/qwae094: developable ruled cut, kerf \(\Delta = (D+\delta)/2\));
do Carmo (ruled/developable: \(K = 0 \iff\) developable). Code note: the
feasibility inequality is a sampled chord-deviation test at tolerance
\(\tau = 0.005 \times\) diagonal, not an exact ruled-surface certificate; a
swept-plane CLEARANCE check (wire access/collision) is not part of this class.
C5. Cut-orientation optimizer¶
Choose an orthonormal cut frame \(\{\mathbf c_1, \mathbf c_2, \mathbf c_3\}\) (right prisms by construction, grid \(q = |\det| = 1\)) maximising alignment with the joint fabric:
with \(m\) a greedy UNIQUE matching of cut normals to distinct joint poles
(all (axis, pole) pairs sorted by \(|\cos|\) descending, strongest first).
Search spaces: bench-constrained mode pins \(\mathbf c_1 = \mathbf z\) and sweeps
the vertical grid's strike over \([0, 180^\circ)\) in azSteps (1 DOF); free
mode samples \(\mathbf c_1\) on a Fibonacci hemisphere (golden angle
\(\pi(3 - \sqrt 5)\)) times a roll sweep (3 DOF over SO(3)). Reported fit score
\(= \tfrac13\sum_i |\cos(\text{obliquity}_i)|\), plus the natural fabric's
\(q = |\det(\mathbf n_1, \mathbf n_2, \mathbf n_3)|\) for contrast. Code:
CutOrientationOptimizer.Optimize, CutOrientationOptimizer.Score
(Fabrication/CutOrientationOptimizer.cs). Citation ([Algorithm] attribute,
CutOrientationOptimizerComponent): orthogonal saw grid vs joint fabric,
maximise \(\sum |\mathbf c \cdot \mathbf p|\); Palmstrom 2005 right-prism criterion.
C6. RecoveryCascade (multi-scale re-cut recursion)¶
At each scale \(s\) (coarse to fine) BlockCutOpt chooses the winning pose; the
winning grid is partitioned into kept (no fracture intersection) and cracked;
each cracked block's AABB is fed to the next finer scale, until the remnant
falls below the finest marketable volume. The recursion, verbatim from the
code header and matched by Recurse:
with kept/cracked decided by the shared BVH predicate
!bvh.AnyTriangleIntersects(obb). Recovery metrics:
plus per-tier kerf volume \(= (\text{inflated} - \text{block})\) volume per
recovered block. With a single scale the cascade reduces exactly to
BlockCutOptSolver.Solve (same winning pose, same predicate). Depth is capped
at the number of scales, so recursion is bounded. Code: RecoveryCascade.Run,
RecoveryCascade.Recurse, CascadeResult.RecoveryFraction,
CascadeResult.Bcsdbbv (Masonry/Quarry/BlockCutOpt/RecoveryCascade.cs,
CascadeResult.cs). Citations (file header): Yarahmadi 2018 (conditional
two-scale); Cherri 2009 (usable-leftover threshold); Gilmore & Gomory 1965
(staged guillotine, grounding only, see C1.2 note); Hahn 1968 / Afsharian 2014
(defect-aware rough-mill cut-up); Jalalian 2023 (BCSdbBV); unified 3-D cascade
introduced in Murugean 2026 (submitted; deposit DOI 10.5281/zenodo.20608279).
C7. Robustness and downstream estimates¶
C7.1 Fisher-robust recovery (sensitivity to fracture-mapping error)¶
\(M\) Monte-Carlo DFN realizations (JointSetDfnGenerator with seeds \(\text{base} + m\)) are each solved by BlockCutOpt; the report is the empirical recovery distribution and the robust direction:
Code: FisherRobustSampler.Solve
(Masonry/Quarry/BlockCutOpt/FisherRobustSampler.cs). Citation ([Algorithm]
attribute, BlockCutOptInspectorComponents): Fisher-distribution joint-scatter
robustness sampling, Azarafza et al. 2016.
C7.2 Per-bench yield estimate (proxies)¶
with \(G\) the geology grade. All three are declared deterministic proxies in
the code header. Code: BlockYieldEstimator.EstimateOne,
BlockYieldEstimator.ComputeFractureRisk,
BlockYieldEstimator.EstimateCuttingTimeMin
(Masonry/Quarry/CutOpt/BlockYieldEstimator.cs).
C7.3 AMRR in-block plane-sequence cutting (staged cuts that DO exist)¶
Iteratively cut a convex blank \(Q\) toward a convex target \(T\): at each step find the vertex of \(Q\) farthest from \(T\), cut with the plane through it tangent to \(T\) (keeping \(T\)), until the outside volume is below a convergence fraction. The objective is the average material-removal rate:
per-step instantaneous \(\mathrm{MRR}_i = V_{r,i}/\tau_i\). Code:
AmrrPlanner (Masonry/Quarry/BlockCutOpt/AmrrPlanner.cs). Citation
([Algorithm] attribute, BlockCutOptComponents): Shao, Liu & Gao 2022,
Processes (MDPI), sections 2.4-2.6.
Absent topics (declared, not fabricated)¶
- Bingham orientation statistics: not present anywhere in
src/. - Fisher \(\kappa\) maximum-likelihood ESTIMATION from data: not present; only the \(\kappa = (81/\text{scatter})^2\) conversion and Fisher SAMPLING exist.
- Gilmore-Gomory guillotine DP value recurrence: cited, never implemented (see C1.2); the shipping guillotine machinery is stochastic tree splitting (Kim 2025 port) and a recursive full-span splitter.
- Experimental variogram fitting (spherical/exponential model selection): not present; the kriging covariance is fixed Gaussian with NLML-fitted range/nugget (B4).
- A class named
UncertaintySafeYield: does not exist; the uncertainty-safe yield is the sigma-clearance toggle inFractureBlockPackComponent(B6.3).