Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ resolver = "3"
members = ["crates/geo", "crates/cde", "crates/optimizer", "crates/ironnest", "crates/py"]

[workspace.package]
version = "0.6.1" # bump deliberately; re-bless goldens if placement code changed
version = "0.6.2" # bump deliberately; re-bless goldens if placement code changed
edition = "2024" # jagua-rs lineage; edition 2024 needs Rust ≥ 1.85
rust-version = "1.88" # MSRV floor: i_overlay 7.x (docs/03 §3.5); jagua's cast_signed floor was 1.87
# (stable 1.87). We build on the pinned 1.96.0 — see rust-toolchain.toml.
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,11 @@ A complete, end-to-end deterministic nester, shipping as a PyPI wheel.
`prepare()` part library for part-in-part workloads (~M× faster); and a dev-profile build fix that
cut the test suite ~12×. All opt-in and cross-platform byte-identical
([`docs/04`](docs/04-performance-remediation-plan.md)).
- **Max densify performance (v0.6.2).** Production path `strategy=nfp` + `restarts=8` +
`separation_effort=max` + `column_weight=3`: densify-once multi-start, Fast densify/leftover
budgets under Max, and pose-only snapshots — ~100× wall cut on the remnant corpus with remnant
quality held ([`docs/04`](docs/04-performance-remediation-plan.md) §4e). Full/Fast defaults and
goldens unchanged.

**Next:** Phase 5 — integration into the first consumer (a plasma CAD/CAM application). See
[`docs/00-ironnest-architecture-and-plan.md`](docs/00-ironnest-architecture-and-plan.md) (the plan)
Expand Down
45 changes: 41 additions & 4 deletions crates/cde/src/entities/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,19 +43,26 @@ impl Layout {
layout
}

/// Replaces the current container with a new one, rebuilding the collision detection engine accordingly.
pub fn swap_container(&mut self, container: Container) {
/// Replaces the current container with a new one, rebuilding the collision detection engine
/// accordingly. Returns the previous container (so callers can restore without re-cloning a
/// retained original — densify's shrink/restore loop uses this).
pub fn swap_container(&mut self, container: Container) -> Container {
let cde_snapshot = self.cde.save();
// rebuild the CDE
self.container = container;
let old = std::mem::replace(&mut self.container, container);
// rebuild the CDE from the new container's base, then re-register dynamic hazards
self.cde = self.container.base_cde.as_ref().clone();
for hazard in cde_snapshot.dynamic_hazards {
// re-register all dynamic hazards from the previous CDE snapshot
self.cde.register_hazard(hazard);
}
old
}

/// Saves the current state of the layout to be potentially restored to later.
///
/// Prefer [`save_poses`] when the container is known not to change between save and restore
/// (separator / densify / leftover insert) — full `save` clones the container even though
/// [`restore`] never reloads it.
#[must_use]
pub fn save(&self) -> LayoutSnapshot {
LayoutSnapshot {
Expand All @@ -65,6 +72,18 @@ impl Layout {
}
}

/// Saves only the movable state (placed items + dynamic CDE hazards). Use when the container
/// is fixed for the lifetime of the snapshot (GLS separator, densify keep-best, leftover
/// insert). Byte-identical restore to [`save`]+[`restore`] for that use — container is never
/// written by [`restore`] / [`restore_poses`].
#[must_use]
pub fn save_poses(&self) -> PoseSnapshot {
PoseSnapshot {
placed_items: self.placed_items.clone(),
cde_snapshot: self.cde.save(),
}
}

/// Restores the layout to a previous state using a snapshot.
pub fn restore(&mut self, layout_snapshot: &LayoutSnapshot) {
assert_eq!(self.container.id, layout_snapshot.container.id);
Expand All @@ -76,6 +95,14 @@ impl Layout {
debug_assert!(assertions::snapshot_matches_layout(self, layout_snapshot));
}

/// Restores movable state from a [`PoseSnapshot`] (container must already match).
pub fn restore_poses(&mut self, snapshot: &PoseSnapshot) {
self.placed_items.clone_from(&snapshot.placed_items);
self.cde.restore(&snapshot.cde_snapshot);

debug_assert!(assertions::layout_qt_matches_fresh_qt(self));
}

/// Places an item in the layout at a specific position by applying a transformation.
/// Returns the unique key for the placed item.
pub fn place_item(&mut self, item: &Item, d_transformation: DTransformation) -> PItemKey {
Expand Down Expand Up @@ -159,6 +186,16 @@ pub struct LayoutSnapshot {
pub cde_snapshot: CDESnapshot,
}

/// Movable layout state only (placed items + dynamic CDE hazards). Cheaper than
/// [`LayoutSnapshot`] because it skips cloning the container — which [`Layout::restore`] never
/// reloads anyway. Use with [`Layout::save_poses`] / [`Layout::restore_poses`] on the sep/densify
/// path (docs/04 0c).
#[derive(Clone, Debug)]
pub struct PoseSnapshot {
pub placed_items: SlotMap<PItemKey, PlacedItem>,
pub cde_snapshot: CDESnapshot,
}

impl LayoutSnapshot {
/// Equivalent to [`Layout::density`]
pub fn density(&self, instance: &impl Instance) -> Scalar {
Expand Down
3 changes: 3 additions & 0 deletions crates/cde/src/entities/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ pub use layout::Layout;
#[doc(inline)]
pub use layout::LayoutSnapshot;

#[doc(inline)]
pub use layout::PoseSnapshot;

#[doc(inline)]
pub use placed_item::PlacedItem;

Expand Down
10 changes: 6 additions & 4 deletions crates/optimizer/examples/density_remnant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

//! Throwaway remnant probe (docs/04 Tier D): the goal is to leave the largest usable REMNANT, i.e.
//! compact the parts into the smallest region. A FIXED part set that all fits, nested Full vs Max,
//! reporting the used bounding box (smaller = bigger remnant) and writing SVGs to /tmp so the two
//! layouts can be compared by eye. Instant/std-trig are output/generation only (bench waiver).
//! Throwaway remnant probe (docs/04 Tier D / §4e): the goal is to leave the largest usable REMNANT,
//! i.e. compact the parts into the smallest region. A FIXED part set that all fits, nested Full vs
//! Max at K=1 and K=8 (production path), reporting the used bounding box (smaller = bigger remnant)
//! and writing SVGs to /tmp. Max at K>1 uses densify-once (explore Full, densify NestScore winner
//! only) so K=8 wall ≈ K=1 densify + cheap multi-start. Instant/std-trig are output/generation only
//! (bench waiver).

use std::fmt::Write as _;

Expand Down
Loading
Loading