Skip to main content

StaticModelBuilder

Struct StaticModelBuilder 

Source
pub struct StaticModelBuilder { /* private fields */ }
Expand description

Deterministic single-shot builder. Mutable state is the growing control set (add_top_control) — the live-refine loop; build() converges the current state into a fresh StaticModel.

Implementations§

Source§

impl StaticModelBuilder

Source

pub fn flat( ni: usize, nj: usize, top_depth_m: f64, contact_depth_m: f64, opts: BuildOpts, ) -> Result<Self, StaticError>

A flat-box start: the four corner controls at top_depth_m, so the initial converged model is the flat box (the model-first walking skeleton). The framework is synthesized (flat Top horizon + one contact).

§Errors

StaticError::Grid if a dimension is zero; StaticError::InvalidInput if area/height are non-positive or the contact is not finite.

Source

pub fn from_wireframe( wf: &Wireframe, opts: BuildOpts, ) -> Result<Self, StaticError>

Seed from a constraining Wireframe — the data-layer hand-off. The wireframe drives the structure (its Top horizon becomes the control set; the first fluid contact sets the column base); the scalars it does not carry come from opts. Grid (ni, nj) is the top-surface lattice (ncol-1, nrow-1).

§Errors

StaticError if the wireframe has no Top horizon, the top surface is degenerate or fully undefined, no fluid contact is present, or opts dimensions/scalars are degenerate.

Source

pub fn from_horizon_stack( stack: HorizonStack, opts: BuildOpts, ) -> Result<Self, StaticError>

Seed from an ordered horizon stack — the canonical regional framework: N horizons top→down define N − 1 named zones, each with its own Conformity, layer count, and fluid contacts. This is the multi-zone generalization of Self::from_wireframe; the classic Top+Base wireframe is its 2-horizon / single-zone degenerate case.

The stack may mix all three horizon source kinds (HorizonSource): mapped surfaces, mapped-but-untied surfaces, and tops-only internal horizons draped conformally from the mapped horizon above at pick-controlled thickness.

opts supplies the areal footprint (area_m2), the gridder settings, and the fallback priors; its nk / conformity / gross_height_m are unused on this path (per-zone layering + the last horizon’s surface govern instead). The builder methods (with_logs, with_property, with_min_thickness_m, with_georef, …) apply as usual; with_min_thickness_m becomes the per-interface order-repair floor.

§Errors

StaticError::InvalidInput if the stack has fewer than 2 horizons, the zone-count arrays are not N − 1, the first (top) horizon is not Mapped, a tops-only horizon has no mapped horizon above it, or a mapped surface is degenerate / lattice-mismatched / fully undefined.

Source

pub fn from_scatter_stack( stack: HorizonStack, opts: BuildOpts, frame: StackFrame, ) -> Result<Self, StaticError>

Build from a stack that may carry raw scatter horizons (HorizonSource::Scatter) — the engine owns the gridding. Each scatter horizon is conditioned onto the frame lattice (snap-average onto the nearest node via the frame georef, genuine voids left NaN), then the stack resolves through the same merge-safe path as Self::from_horizon_stack (converged solve + ExtrapolationPolicy + isochore build-down over the actual observations). The frame georef is registered as the model’s world frame. Mapped / TopsOnly horizons pass through untouched.

This is the single scatter-gridding authority: no caller pre-grids scatter onto the lattice, so a data-void margin between two merged horizons collapses to zero instead of carrying independently-extrapolated fill.

Conditioning seam. Scatter is conditioned onto the frame lattice by bilinear-weighted minimum curvature (petekTools Conditioning::Bilinear): each off-node datum maps to its fractional node position and is honoured through the bilinear interpolation of its four surrounding nodes rather than snapped to the nearest — holding on-data fidelity to the lattice representation floor (grid_scatter owns the world→lattice conversion).

§Errors

StaticError as Self::from_horizon_stack, plus StaticError::InvalidInput if a scatter horizon has no points, or StaticError::Grid if it lands no point on the frame (fully undefined).

Source

pub fn condition_scatter_stack( stack: HorizonStack, frame: &StackFrame, ) -> Result<HorizonStack, StaticError>

Condition a raw-scatter stack onto frame ONCE — the expensive per-horizon cold bilinear solve (task_suite_scatter_perf) — returning the conditioned, all-HorizonSource::Mapped stack as a shared handle.

This is the dedup seam for callers that build both a StaticModel and its MC StaticModelTemplate from the same scatter: conditioning is draw-invariant and identical across the builder and the template, so re-running Self::from_scatter_stack / StaticModelTemplate::from_scatter_stack on each re-solves the whole 11-horizon cold gridding redundantly (the canonical build’s dominant cost). Condition once, then feed the returned stack to both paths without re-conditioning:

let g = frame.georef;
let conditioned = StaticModelBuilder::condition_scatter_stack(stack, &frame)?;
// model:
let model = StaticModelBuilder::from_horizon_stack(conditioned.clone(), opts)?
    .with_georef(g.origin_x, g.origin_y, g.spacing_x, g.spacing_y)
    .build()?;
// MC template (byte-for-byte the same geometry `realize` reproduces):
let template = StaticModelTemplate::from_horizon_stack(conditioned, opts)?
    .with_georef(g.origin_x, g.origin_y, g.spacing_x, g.spacing_y);

The result is bit-identical to conditioning inside each entry point — this only removes the redundant re-solve. Genuine data voids stay NaN.

§Errors

As Self::from_scatter_stack’s conditioning step: StaticError::InvalidInput if a scatter horizon has no points, StaticError::Grid if one lands no point on the frame.

Source

pub fn with_clamp_base_to_top(self, clamp: bool) -> Self

Opt into clamping a crossed base (base above top) to zero gross at the offending columns instead of erroring. Default is to error (StaticError::CrossedSurfaces); this zeroes only those columns and leaves the rest untouched (R1).

Source

pub fn with_min_thickness_m(self, min_thickness_m: f64) -> Self

Opt into post-gridding order-repair: where the gridded base sits less than min_thickness_m below the top (a thin or crossed column), pull the base down to exactly top + min_thickness_m, preserving the top (the better-constrained seismic pick). Independent gridding of Top and Base can overshoot at thin margins and undo a pointwise pre-repair; this repairs the gridded result rather than erroring. Off by default (the crossing guard stays the default); when enabled, Provenance::warnings records a BuildWarning::ThinColumnsRepaired with the repaired-node count and the worst violation. Takes precedence over Self::with_clamp_base_to_top (R-c).

Source

pub fn with_collapse_below_m(self, collapse_below_m: f64) -> Self

Opt into the cell-collapse threshold (Petrel-style): after layering, any cell thinner than collapse_below_m collapses to zero thickness, its sliver merged into a thicker zone-interior neighbour so rock is conserved (never deleted, never merged across a zone boundary). Off by default. When enabled, Provenance::warnings records a BuildWarning::CellsCollapsed with the count. It bites hardest on sub-dz slivers under fixed-count proportional layering in thin columns and on Follow-style truncation partials.

Source

pub fn with_extrapolation(self, policy: ExtrapolationPolicy) -> Self

Set the extrapolation policy for the horizon-stack build — how every solved stack surface/isochore behaves beyond its data hull. Default ExtrapolationPolicy::DecayToData (dip held for start_cells, then a linear decay to the nearest-data value over decay_cells) — conservative, never silent unbounded natural-dip into a data void. Pass ExtrapolationPolicy::NaturalDip to opt back into the legacy unbounded linear extension (appropriate only when the regional dip is KNOWN to continue). Stack path only; the classic 2-surface path is unchanged.

Source

pub fn with_sw_gas(self, sw_gas: f64) -> Self

Set a gas-cap connate-water override applied to gas-zone cells in a two-contact in_place split (R3), so a single shared SW cube does not over-state gas-cap OGIP. No effect on a single-contact (no-GOC) column.

Source

pub fn with_logs(self, samples: Vec<PetroSample>) -> Self

Attach positioned petro samples (TVD, φ, Sw) so build() populates cells from upscaled logs instead of constant priors; cells with no samples keep the priors. Empty = priors everywhere.

Source

pub fn with_areal_trend(self, trend: TrendSurface) -> Self

Attach an areal trend multiplier field (external-drift-lite): a gridded lateral shape resampled to the model lattice, mean-normalized, and applied per-column to NTG (and φ if the trend flags it) after population. The trend gives lateral shape; the prior/log gives the level (decision_staticmodel_regen_seam). See TrendSurface.

Superseded (deprecation-tracked). This is the interim post-population multiplier hook; the fuller path is PropertyPipeline with crate::model::Gaussian::with_trend, which steers the simulation itself by collocated (Markov-1) cokriging rather than scaling a populated cube. It is retained for the simple “lateral NTG/φ shape on a constant-prior fill” case and is still on the build + MC-template path; a hard #[deprecated] waits until the pipeline subsumes this use (organize wave P10 review, task_petekstatic_organize).

Source

pub fn with_inputs_ref(self, inputs_ref: impl Into<String>) -> Self

Label the provenance record with the input-bundle identity.

Source

pub fn with_spec(self, spec: BuildSpec) -> Self

Install a whole declarative BuildSpec in one call — the spec analog of chaining the individual with_* sugar (each of which mutates the same internal spec). Values are identical either way, so the build — and every determinism contract over it — is unchanged.

Source

pub fn with_memory_budget(self, budget: MemoryBudget) -> Self

Declare the memory budget (ruling R5). Below it build() stays on the in-core path (byte-identical); above it the model spills its geometry + cubes to a petekTools store and reads through mmap windows. Pass MemoryBudget::unlimited to force in-core, or MemoryBudget::bytes to force spill at a test scale.

Source

pub fn with_spill_dir(self, dir: impl Into<PathBuf>) -> Self

Where a spilled build writes its store (ruling R5). Default: the platform temp dir. The store is removed on model drop unless Self::with_spill_persist.

Source

pub fn with_spill_persist(self, persist: bool) -> Self

Keep a spilled model’s store file past drop (a caller-owned location) instead of the default temp-file cleanup.

Source

pub fn with_georef( self, origin_x: f64, origin_y: f64, spacing_x: f64, spacing_y: f64, ) -> Self

Register the built model’s world georeference: the world (x, y) of column (0, 0)’s centroid and the world column spacing. The upstream seam (which knows the source horizon lattice / CRS) supplies this so the view bundles emit ONE consistent world frame — the map raster overlays the world outline + wells, and a world fence / bore trace sections correctly (it maps world traces through the same xy↔ij as well registration).

Non-finite / non-positive spacing is ignored (the model stays in the local degenerate frame). This does not alter the grid geometry (GRV is still the area-scaled local square); it only labels the local column lattice with its world frame. See Georef.

Source

pub fn with_boundary(self, ring: Vec<[f64; 2]>) -> Self

Register the world areal boundary ring for a horizon-stack build — the study-area closure the map bundle overlays (world [x, y], first == last point to close). The from_horizon_stack path has no wireframe to source a boundary from, so without this it would emit the degenerate unit square while the frame and wells are world coordinates, collapsing the viewer’s content extent (task_petekstatic_zoned_fixes finding 2). When omitted, the build still emits a world-extent rectangle derived from the georef (never the unit square against a world frame); pass the real closure ring here for the true outline shape. Ignored on the from_wireframe / flat paths (they carry the wireframe’s own boundary).

Source

pub fn with_property(self, pipeline: PropertyPipeline) -> Self

Attach a per-property geostatistical pipeline (P5): build() runs it after the base population, upscaling its positioned logs and SGS-propagating the cube — overwriting that property’s prior/log cube with the conditioned, simulated field. Multiple pipelines model properties one at a time (each is its own PropertyPipeline). The pipeline’s crate::model::PropertyReport lands on the model’s Provenance::property_reports.

Source

pub fn with_zone_property( self, zone_name: impl Into<String>, pipeline: PropertyPipeline, ) -> Self

Attach a geostatistical pipeline scoped to one zone of a horizon-stack build (P8 per-zone population, task_petekstatic_multizone_2): after the base population and any whole-model Self::with_property pipelines, build() runs it restricted to zone_name’s k-range (PropertyPipeline::apply_in_zone), so each zone gets its own variogram/trend/log-conditioning and only that zone’s slice is overwritten. Attach one pipeline per zone; the pipeline’s report lands on Provenance::property_reports. A zone_name absent from the built zone table is a StaticError::InvalidInput at build time.

Source

pub fn with_zone_priors( self, zone_name: impl Into<String>, priors: ConstantPriors, ) -> Self

Override the constant priors inside one zone of a horizon-stack build (P8 per-zone population): after the base population build() overwrites PORO/NTG/SW across zone_name’s k-range with priors — the per-zone distribution level a stack zone owns (a sand zone vs a shale zone). A Self::with_zone_property pipeline on the same zone then simulates over this baseline. A zone_name absent from the built zone table is a StaticError::InvalidInput at build time.

Source

pub fn with_tie_settings(self, ties: TieSettings) -> Self

Set how the well ties are applied (TieSettings: datum substitution — the default — or bounded-radius locality). Thin sugar over the spec; both the ties and their settings are read at build() time, so the call order vs StaticModelBuilder::with_well_ties does not matter here.

Source

pub fn with_well_ties(self, ties: Vec<WellTie>) -> Self

Attach explicit well ties for a horizon-stack build (P8 per-horizon ties, task_petekstatic_multizone_2): each WellTie’s measured tops tie their mapped horizons to the well (the top is added as a hard control at the well’s node and the surface re-solved), and every tie’s pre-tie residual (measured − untied model surface) is recorded in crate::model::Provenance::well_ties and surfaced in the map bundle’s wells[].ties. Tops for a tops-only horizon are recorded as QC residuals against the pick-conditioned drape. Honoured on the from_horizon_stack path.

Source

pub fn with_sugar_cube(self, sugar_cube: bool) -> Self

Opt into sugar-cube section rendering (flat-box cells) — default false, where the section bundle carries per-edge cell depths and the viewer draws dip-following trapezoids. The engine geometry is corner-point either way; this only sets the section view’s sugar_cube flag (and flattens its edge arrays).

Source

pub fn add_top_control(&mut self, ip: usize, jp: usize, depth_m: f64)

Add a top-surface depth control point on the (ni+1) x (nj+1) node lattice — the new datum the next build() re-converges to honour.

Source

pub fn control_count(&self) -> usize

Number of control points currently honoured.

Source

pub fn build(&self) -> Result<StaticModel, StaticError>

Converge the model at the current control set: cold minimum-curvature solve → conformable layering → population → a populated StaticModel.

§Errors

StaticError if the surface solve, layering, or population fails.

Trait Implementations§

Source§

impl Clone for StaticModelBuilder

Source§

fn clone(&self) -> StaticModelBuilder

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for StaticModelBuilder

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<SS, SP> SupersetOf<SS> for SP
where SS: SubsetOf<SP>,

Source§

fn to_subset(&self) -> Option<SS>

The inverse inclusion map: attempts to construct self from the equivalent element of its superset. Read more
Source§

fn is_in_subset(&self) -> bool

Checks if self is actually part of its subset T (and can be converted to it).
Source§

fn to_subset_unchecked(&self) -> SS

Use with care! Same as self.to_subset but without any property checks. Always succeeds.
Source§

fn from_subset(element: &SS) -> SP

The inclusion map: converts self to the equivalent element of its superset.
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<G1, G2> Within<G2> for G1
where G2: Contains<G1>,

Source§

fn is_within(&self, b: &G2) -> bool