Reference

A practical map of the SolverForge workspace and the companion repos around it.

Crate & Runtime Map

Use this page when you need to decide which crate or companion repository should own a piece of work.

Default starting point

For most application code, depend on solverforge and stay on the facade until you have a concrete reason to go lower-level.

This map is aligned with the published solverforge 0.22.0 crate and current release workspace.

The facade re-exports the normal modeling, scoring, projection, configuration, custom-search, and retained runtime surface, including solverforge_constraints, Projection, ProjectionSink, SolverConfig, PhaseConfig, MoveSelectorConfig, SearchContext, CustomSearchPhase, ScalarGroup, ScalarAssignmentRule, ScalarGroupLimits, SharedNodeDiagnostics, SharedNodeId, SharedNodeOperation, direct cross-join .group_by(|left, right| key, collector), cross-join .project(|left, right| row) projected scoring, row-aware chained cross joins through Bi, Tri, Quad, Penta, and an arity-free Chain, direct cross-join grouped complements, symmetric and directed projected self-joins, projected grouped complements, score weight helpers such as fixed_weight and hard_weight, collector helpers such as count, sum, load_balance, consecutive_runs, collect_vec, and indexed_presence, plus advanced grouped-scalar assignment, conflict-repair extension types, phase and move-label telemetry, bounded applied-move and candidate-pull traces, candidate-trace provenance types, and CVRP profile helpers such as domain = "cvrp", route_hooks, savings_hooks, savings_metric_class, route_distance, route_feasible, and savings_feasible. The grouped-scalar construction cursors, runtime model, provider, and candidate-metric registries are solver-internal; applications reach grouped construction through ScalarGroup::assignment(...), group_name, and grouped_scalar_move_selector rather than by importing those types. That keeps app code on solverforge unless it needs to implement lower-level solver internals directly.

Workspace crates

Crate Owns Reach for it when…
solverforge the public facade and re-exports you are building an app and want the normal public API
solverforge-core score types, descriptors, domain traits, and dynamic capability contracts you are extending lower-level abstractions or implementing core-facing helpers
solverforge-macros #[planning_solution], #[planning_entity], #[problem_fact], #[solverforge_constraints] you need derive behavior, model glue, or constraint-function compilation
solverforge-scoring constraint streams, typed relational operators and terminals, incremental scoring, and shared grouped node state you are working directly on scoring internals or advanced scoring extensions
solverforge-config TOML/YAML parsing, resolved selector policy, candidate-trace config, acceptors, and builders you need direct config construction or parsing outside the stock solve path
solverforge-solver runtime compiler, phases, cursor selectors, retained lifecycle, and telemetry you are building custom runtime behavior beyond facade-level use
solverforge-bridge dynamic binding contracts, logical IDs, scores, explicit slot capabilities, and the compiled runner you are implementing or maintaining a binding layer
solverforge-console tracing-driven console output you want the standard terminal UX or progress formatting
solverforge-cvrp CVRP-specific helpers and distance utilities your problem is route-centric and the domain benefits from these helpers

Relational scoring internals

Fluent cross joins and same-collection unique combinations score through constraint::relational::OperatorTerminal. Cross joins nest JoinNodes; same-collection combinations use SelfJoinNode with an identity-preserving filter. The old cross_bi_incremental, cross_tri_incremental, and per-arity self-join incremental constraint modules are no longer the implementation. Every grouped stream — unary, cross-join, projected, and complemented — lowers onto the one stream::relational::operator::GroupNode accumulator; chained terminals over a shared group share that operator owner through constraint::relational::shared_terminal, scored and explained per terminal.

Named cross Bi/Tri/Quad/Penta streams adapt the common tree. Cross Penta::join returns stream::chain::Chain; subsequent Chain::join calls extend it without a fixed arity ceiling. Each chained condition compiles independently, receives the whole prior borrowed row, and can use a key domain different from earlier relationships. Chain filters and weights receive the typed row rather than a new positional closure signature for every arity. Every continuation — Bi::join, Tri::join, Quad::join, Penta::join, and Chain::join — uses equal_on with the whole prior borrowed row on the left and &Leaf<'_, NewEntity> on the right. Select bindings through nested .left / .right and read fields through .entity. equal_bi with plain entity closures belongs to the first unary join and fails at a continuation (E0631). The cross-Penta continuation drops its authored penta filter; apply the row filter on the returned Chain. Same-source PentaConstraintStream is terminal.

Fluent right-hand targets implement CollectionExtract, including filtered unary streams. Arbitrary compatible derived producers can feed either low-level JoinNode input, but are not interchangeable fluent collection targets. For the first unary keyed equality cross-join, the left unary filter becomes source membership before key extraction and pair creation. Original source indexes and incremental metadata are preserved; post-join filters remain joined row filters. This left-source pushdown does not apply to comparison, overlap, composed-condition, predicate, or same-source self-join dispatch.

Low-level stream::relational::operator types compose collection, filter, merge, project, group, join, self-join, existence, flatten, and complement producers. Derived payloads belong to evaluation or retained-state owners; borrowed views and generational row identities preserve lineage without retaining solution references. Group and membership changes can replace rows during either retraction or insertion, so consumers process both removed and inserted handles from each notification.

Operator::retract and Operator::insert emit into caller-owned &mut Vec<RowHandle> buffers. Nodes pass their reused scratch pairs through their children rather than allocating a returned change object per hop; buffers can still grow when more handles are needed. Relational operators reuse retired link, flattened child-handle, and hash-bucket vectors, and internal index maps use the private stream/relational/hash.rs deterministic fast hasher while ordered and interval side-tables use generation-checked HandleMaps; v0.19.12 kept occupied hash buckets and surviving existence match lists in place during unlinking, and v0.19.13 replaced the linked existence engine with per-key occurrence counts (dense tables for usize planning-id keys, hash tables otherwise), leaving JoinerExistenceNode as the compiled-plan semi/anti engine for residual predicates and moving complement default restoration to retained plan keys (KeyOf). Buffer reuse does not bypass old-key removal, generational checks, residual predicates, or duplicate-notification handling, and does not promise public iteration order or allocation-free updates. Exact ViewEqualPlan candidates rely on their equality bucket rather than re-extracting both keys; composed plans still evaluate residual conditions. See Joiners and Existence & Flattening.

Dense existence count updates mark a touched key at most once per event; the touched list stays unique and membership settles from final counts. Hash-key storage, residual-predicate dispatch, and public authoring methods are unchanged. Grouped-scalar default local search forages with AcceptedCount(256) — the list-model policy — so a step sweeps until the limit is accepted and applies the best.

Specialized unary and projected pair families remain, while grouped shapes — including direct cross and projected grouped complements — run on the shared kernel above. Existence and keyed flattened terminals, and unary complemented groups, use the shared OperatorTerminal. Reusable grouped-node sharing remains distinct from compiler-owned sharing of arbitrary derived consumers. Check the tagged scoring implementation when extending internals.

Companion repositories

Repo Owns Use it when…
solverforge-cli scaffolding and code generation you are starting a new app or extending a generated shell
solverforge-py the tagged v0.6.15 Python source line, compiled-model PyO3 binding, retained diagnostics, native row-domain enforcement, and embedded UI asset bridge you want Python model authoring backed by the Rust solver engine
solverforge-ui retained-job frontend controls and scheduling-facing components you need a web UI around a retained solve lifecycle
solverforge-maps road networks, routing, matrices, and map-backed planning helpers you need route costs, geometry, or spatial planning support

Practical dependency rules

  • Start with solverforge-cli to scaffold the app shell.
  • Start with the PyPI solverforge package when the application surface is Python and you do not need a generated Rust web/API/CLI shell. PyPI publishes solverforge 0.6.15; use the matching solverforge-py v0.6.15 tag for source and example development on SolverForge 0.22.0.
  • Keep application code on the solverforge facade unless a lower-level crate unlocks something you actually need.
  • Keep scalar/list model declarations in the planning_model! manifest and variable attributes; solver config consumes declared hooks rather than inferring nearby or construction-order behavior.
  • Use scalar variables for direct assignments and list variables for ordered membership; there is no separate chained-scalar sequence model.
  • Keep reusable grouped constraint work inside a #[solverforge_constraints] function; do not add app-level caches or public sharing helpers.
  • Add solverforge-ui only if the product needs retained-job UI flows.
  • Add solverforge-maps only if routing or map-backed costs are part of the planning model.
  • Use solverforge-py for Python classes, decorators, explicit scalar/list metadata, callbacks, candidate metrics, retained jobs and diagnostics, and embedded UI assets over the native Rust engine.
  • Reach into solverforge-solver directly only when configuration and the public facade stop being enough.
  • Reach into solverforge-scoring module-scoped names such as stream::projected::DirectedBi or constraint::projected::DirectedBi only when you are extending scoring internals; application constraints should use the fluent join(equal_bi(...)) surface.

Typical stacks

Scenario Typical stack
service or CLI planner solverforge-cli scaffold + solverforge
Python-authored planner PyPI solverforge package or the solverforge-py v0.6.15 source tag
web app with retained lifecycle UI solverforge-cli + solverforge + solverforge-ui
routing or fleet optimization solverforge-cli + solverforge + solverforge-maps
research or advanced runtime work solverforge plus selected lower-level crates

What not to do

  • Do not start on the lowest-level crates unless the public API is clearly insufficient.
  • Do not push app-specific business rules into SolverForge crates just because the extension point exists.
  • Do not treat solverforge-ui or solverforge-maps as part of this repository’s workspace; they are companion repos with their own release lines.

    See also

  • SolverForge docs
  • SolverForge Python docs
  • Integration Boundaries
  • Modeling Cheat Sheet