Skip to content

Commit 2047a7f

Browse files
committed
add Release Notes for v0.5.0
1 parent 5357135 commit 2047a7f

File tree

2 files changed

+370
-0
lines changed

2 files changed

+370
-0
lines changed

content/en/blog/releases/_index.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
---
2+
title: Releases
3+
---
Lines changed: 367 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,367 @@
1+
---
2+
title: "SolverForge 0.5.0: Zero-Erasure Constraint Solving"
3+
date: 2026-01-15
4+
draft: false
5+
tags: [rust, release]
6+
description: >
7+
Introducing SolverForge 0.5.0 - a general-purpose constraint solver written in native Rust with zero-erasure architecture and the SERIO incremental scoring engine.
8+
---
9+
10+
{{< alert title="Major Release" color="success" >}}
11+
SolverForge 0.5.0 represents a complete architectural rewrite. It is no longer a WASM compiler or a wrapper around the JVM.
12+
This is a native Rust constraint solver built from the ground up with zero-erasure design and the SERIO incremental scoring engine.
13+
{{< /alert >}}
14+
15+
We're excited to announce **SolverForge 0.5.0**, a complete rewrite of SolverForge as a native Rust constraint solver. This isn't a wrapper around an existing solver or a bridge between languages, but a ground-up implementation built on a new architecture powered by the SERIO (Scoring Engine for Real-time Incremental Optimization) engine - our zero-erasure implementation inspired by Timefold's BAVET engine.
16+
17+
After [exploring FFI complexity](/blog/technical/2025/12/30/why-java-interop-is-difficult/), [performance bottlenecks in Python-Java bridges](/blog/technical/2025/12/07/order-picking-quickstart-jpype-performance/) and the [architectural constraints of cross-language constraint solving](/blog/technical/2025/12/06/python-constraint-solver-architecture/), we made a fundamental choice: build something different.
18+
The result is a general-purpose constraint solver in Rust and it is blazing fast.
19+
20+
While this release is labeled beta as the API continues to mature, SolverForge 0.5.0 is production-capable and represents a major architectural milestone in the project's evolution.
21+
22+
## What is SolverForge?
23+
24+
SolverForge is a constraint solver for planning and scheduling problems. It tackles complex optimization challenges like employee scheduling, vehicle routing, resource allocation, and task assignment—problems where you need to satisfy hard constraints while optimizing for quality metrics.
25+
26+
Inspired by [Timefold](https://timefold.ai/) (formerly OptaPlanner), SolverForge takes a fundamentally different architectural approach centered on **zero-erasure design**. Rather than relying on dynamic dispatch and runtime polymorphism, SolverForge preserves concrete types throughout the solver pipeline, enabling aggressive compiler optimizations and predictable performance characteristics.
27+
28+
At its core is the **SERIO engine**—Scoring Engine for Real-time Incremental Optimization—which efficiently propagates constraint changes through the solution space as the solver explores candidate moves.
29+
30+
## Zero-Erasure Architecture
31+
32+
The zero-erasure philosophy shapes every layer of SolverForge. Here's what it means in practice:
33+
34+
- **No trait objects**: No `Box<dyn Trait>` or `Arc<dyn Trait>` in hot paths
35+
- **No runtime dispatch**: All generics resolved at compile time via monomorphization
36+
- **No hidden allocations**: Moves, scores, and constraints are stack-allocated
37+
- **Predictable performance**: No garbage collection pauses, no vtable lookups
38+
39+
Traditional constraint solvers often use polymorphism to handle different problem types dynamically. This flexibility comes at a cost: heap allocations, pointer indirection, and unpredictable cache behavior. In constraint solving, where the inner loop evaluates millions of moves per second, these costs compound quickly.
40+
41+
SolverForge's zero-erasure design means the compiler knows the concrete types of your entities, variables, scores, and constraints at compile time. It can inline aggressively, eliminate dead code, and generate cache-friendly machine code tailored to your specific problem structure.
42+
43+
```rust
44+
// Zero-erasure move evaluation - fully monomorphized
45+
fn evaluate_move<M: Move<Solution>>(
46+
move_: &M,
47+
director: &mut TypedScoreDirector<Solution, Score>
48+
) -> Score {
49+
// No dynamic dispatch, no allocations, no boxing
50+
director.do_and_process_move(move_)
51+
}
52+
```
53+
54+
This isn't just a performance optimization—it fundamentally changes how you reason about solver behavior. Costs are visible in the type system. There are no surprise heap allocations or dynamic dispatch overhead hiding in framework abstractions.
55+
56+
## The SERIO Engine
57+
58+
SERIO—Scoring Engine for Real-time Incremental Optimization—is SolverForge's constraint evaluation engine. It powers the ConstraintStream API, which lets you define constraints declaratively using fluent builders:
59+
60+
```rust
61+
use solverforge::stream::{ConstraintFactory, joiner};
62+
63+
fn define_constraints() -> impl ConstraintSet<Schedule, HardSoftScore> {
64+
let factory = ConstraintFactory::<Schedule, HardSoftScore>::new();
65+
66+
let required_skill = factory
67+
.clone()
68+
.for_each(|s: &Schedule| s.shifts.as_slice())
69+
.join(
70+
|s: &Schedule| s.employees.as_slice(),
71+
joiner::equal_bi(
72+
|shift: &Shift| shift.employee_id,
73+
|emp: &Employee| Some(emp.id),
74+
),
75+
)
76+
.filter(|shift: &Shift, emp: &Employee| {
77+
!emp.skills.contains(&shift.required_skill)
78+
})
79+
.penalize(HardSoftScore::ONE_HARD)
80+
.as_constraint("Required skill");
81+
82+
let no_overlap = factory
83+
.for_each_unique_pair(
84+
|s: &Schedule| s.shifts.as_slice(),
85+
joiner::equal(|shift: &Shift| shift.employee_id),
86+
)
87+
.filter(|a: &Shift, b: &Shift| {
88+
a.employee_id.is_some() && a.start < b.end && b.start < a.end
89+
})
90+
.penalize(HardSoftScore::ONE_HARD)
91+
.as_constraint("No overlap");
92+
93+
(required_skill, no_overlap)
94+
}
95+
```
96+
97+
The key to SERIO's efficiency is **incremental scoring**. When the solver considers a move (like reassigning a shift to a different employee), SERIO doesn't re-evaluate every constraint from scratch. Instead, it tracks which constraint matches are affected by the change and recalculates only those.
98+
99+
Under the zero-erasure design, these incremental updates happen without heap allocations or dynamic dispatch. The constraint evaluation pipeline is fully monomorphized—each constraint stream compiles to specialized code for your exact entity types and filter predicates.
100+
101+
## Developer Experience in 0.5.0
102+
103+
Version 0.5.0 brings significant improvements to the developer experience, making it easier to define problems and monitor solver progress.
104+
105+
### Fluent API & Macros
106+
107+
Domain models are defined using derive macros that generate the boilerplate:
108+
109+
```rust
110+
use solverforge::prelude::*;
111+
112+
#[problem_fact]
113+
pub struct Employee {
114+
pub id: i64,
115+
pub name: String,
116+
pub skills: Vec<String>,
117+
}
118+
119+
#[planning_entity]
120+
pub struct Shift {
121+
#[planning_id]
122+
pub id: i64,
123+
pub required_skill: String,
124+
#[planning_variable]
125+
pub employee_id: Option<i64>,
126+
}
127+
128+
#[planning_solution]
129+
pub struct Schedule {
130+
#[problem_fact_collection]
131+
pub employees: Vec<Employee>,
132+
#[planning_entity_collection]
133+
pub shifts: Vec<Shift>,
134+
#[planning_score]
135+
pub score: Option<HardSoftScore>,
136+
}
137+
```
138+
139+
The `#[planning_solution]` macro now generates helper methods for basic variable problems, including:
140+
- Entity count accessors (`shift_count()`, `employee_count()`)
141+
- List operation methods for manipulating planning entities
142+
- A `solve()` method that sets up the solver with sensible defaults
143+
144+
This reduces boilerplate and makes simple problems trivial to solve while still allowing full customization for complex scenarios.
145+
146+
### Console Output
147+
148+
With the `console` feature enabled, SolverForge displays beautiful real-time progress:
149+
150+
```
151+
____ _ _____
152+
/ ___| ___ | |_ _____ _ __ | ___|__ _ __ __ _ ___
153+
\___ \ / _ \| \ \ / / _ \ '__|| |_ / _ \| '__/ _` |/ _ \
154+
___) | (_) | |\ V / __/ | | _| (_) | | | (_| | __/
155+
|____/ \___/|_| \_/ \___|_| |_| \___/|_| \__, |\___|
156+
|___/
157+
v0.5.0 - Zero-Erasure Constraint Solver
158+
159+
0.000s ▶ Solving │ 14 entities │ 5 values │ scale 9.799 x 10^0
160+
0.001s ▶ Construction Heuristic started
161+
0.002s ◀ Construction Heuristic ended │ 1ms │ 14 steps │ 14,000/s │ 0hard/-50soft
162+
0.002s ▶ Late Acceptance started
163+
1.002s ⚡ 12,456 steps │ 445,000/s │ -2hard/8soft
164+
2.003s ⚡ 24,891 steps │ 448,000/s │ 0hard/12soft
165+
30.001s ◀ Late Acceptance ended │ 30.00s │ 104,864 steps │ 456,000/s │ 0hard/15soft
166+
30.001s ■ Solving complete │ 0hard/15soft │ FEASIBLE
167+
```
168+
169+
The `verbose-logging` feature adds DEBUG-level progress updates (approximately once per second during local search), giving insight into solver behavior without overwhelming the terminal.
170+
171+
### Shadow Variables
172+
173+
Shadow variables are derived values that depend on genuine planning variables. For example, in vehicle routing, a vehicle's arrival time at a location depends on which locations come before it in the route.
174+
175+
Version 0.5.0 adds first-class support for shadow variables:
176+
177+
```rust
178+
#[planning_entity]
179+
pub struct Visit {
180+
#[planning_variable]
181+
pub vehicle_id: Option<i64>,
182+
183+
#[shadow_variable]
184+
pub arrival_time: Option<i64>, // Computed based on route position
185+
}
186+
```
187+
188+
The new `ShadowAwareScoreDirector` tracks shadow variable dependencies and updates them automatically when genuine variables change. The `filter_with_solution()` method on uni-streams allows constraints to access shadow variables during evaluation:
189+
190+
```rust
191+
factory
192+
.for_each(|s: &Schedule| s.visits.as_slice())
193+
.filter_with_solution(|solution: &Schedule, visit: &Visit| {
194+
// Access shadow variable through solution
195+
visit.arrival_time.unwrap() > solution.time_window_end
196+
})
197+
.penalize(HardSoftScore::ONE_HARD)
198+
.as_constraint("Late arrival")
199+
```
200+
201+
### Event-Based Solving
202+
203+
The new `solve_with_events()` API provides real-time feedback during solving:
204+
205+
```rust
206+
use solverforge::{SolverManager, SolverEvent};
207+
208+
let (job_id, receiver) = SolverManager::global().solve_with_events(schedule);
209+
210+
for event in receiver {
211+
match event {
212+
SolverEvent::BestSolutionChanged { solution, score } => {
213+
println!("New best: {}", score);
214+
update_dashboard(&solution);
215+
}
216+
SolverEvent::PhaseStarted { phase_name } => {
217+
println!("Starting {}", phase_name);
218+
}
219+
SolverEvent::SolvingEnded { final_solution, .. } => {
220+
println!("Done!");
221+
break;
222+
}
223+
}
224+
}
225+
```
226+
227+
This enables building interactive UIs, progress bars, and real-time solution dashboards that update as the solver finds better solutions.
228+
229+
## Phase Builders
230+
231+
SolverForge 0.5.0 introduces fluent builders for configuring solver phases:
232+
233+
```rust
234+
use solverforge::prelude::*;
235+
236+
let solver = SolverManager::builder()
237+
.with_phase_factory(|config| {
238+
vec![
239+
Box::new(BasicConstructionPhaseBuilder::new()),
240+
Box::new(BasicLocalSearchPhaseBuilder::new()
241+
.with_late_acceptance(400)),
242+
]
243+
})
244+
.build()?;
245+
```
246+
247+
Available phase builders include:
248+
- **BasicConstructionPhaseBuilder**: First Fit construction for basic variables
249+
- **BasicLocalSearchPhaseBuilder**: Hill climbing, simulated annealing, tabu search, late acceptance
250+
- **ListConstructionPhaseBuilder**: Construction heuristics for list variables
251+
- **KOptPhaseBuilder**: K-opt local search for tour optimization (TSP, VRP)
252+
253+
Each phase builder integrates with the new stats system (`PhaseStats`, `SolverStats`), providing structured access to solve metrics like step count, score calculation speed, and time spent per phase.
254+
255+
## Breaking Changes
256+
257+
Version 0.5.0 includes one breaking change to enable shadow variable support:
258+
259+
**Solution-aware filter traits**: Uni-stream filters can now optionally access the solution using `filter_with_solution()`. This enables constraints to reference shadow variables and other solution-level computed state.
260+
261+
```rust
262+
// Before: Filter receives only the entity
263+
.filter(|shift: &Shift| shift.employee_id.is_some())
264+
265+
// After: Same syntax still works
266+
.filter(|shift: &Shift| shift.employee_id.is_some())
267+
268+
// New: Can also access solution for shadow variables
269+
.filter_with_solution(|solution: &Schedule, shift: &Shift| {
270+
// Access shadow variables through solution context
271+
shift.arrival_time.unwrap() < solution.deadline
272+
})
273+
```
274+
275+
The standard `filter()` method remains unchanged for simple predicates. Bi/Tri/Quad/Penta stream filters (after joins) continue to receive only the entity tuples without the solution reference.
276+
277+
{{< alert title="Note" color="info" >}}
278+
The API split between `filter()` and `filter_with_solution()` is temporary. Version 0.5.1 will unify these into a single `filter()` method that accepts both closure signatures, eliminating this distinction.
279+
{{< /alert >}}
280+
281+
If you're upgrading from 0.4.0 and only using entity-level filters, no changes are required.
282+
283+
## What's Still Beta
284+
285+
{{< alert title="Beta Status" color="warning" >}}
286+
While SolverForge 0.5.0 is production-capable, some areas are still maturing:
287+
288+
- **API stability**: Core APIs are stable, but we may introduce minor breaking changes based on feedback
289+
- **Documentation**: API docs are comprehensive, but tutorials and guides are still being developed
290+
- **Ecosystem**: Quickstarts and examples are growing but not yet comprehensive
291+
{{< /alert >}}
292+
293+
The [component status table](https://github.com/solverforge/solverforge#component-status) in the README tracks what's complete:
294+
295+
| Component | Status |
296+
|-----------|--------|
297+
| Score types | Complete |
298+
| Domain model macros | Complete |
299+
| ConstraintStream API | Complete |
300+
| SERIO incremental scoring | Complete |
301+
| Construction heuristics | Complete |
302+
| Local search | Complete |
303+
| Exhaustive search | Complete |
304+
| Partitioned search | Complete |
305+
| VND | Complete |
306+
| Move system | Complete |
307+
| Termination | Complete |
308+
| SolverManager | Complete |
309+
| SolutionManager | Complete |
310+
| Console output | Complete |
311+
| Benchmarking | Complete |
312+
313+
Core solver functionality is complete and well-tested. The beta label reflects that we're still gathering real-world feedback on ergonomics and API design.
314+
315+
## Getting Started
316+
317+
Add SolverForge to your `Cargo.toml`:
318+
319+
```toml
320+
[dependencies]
321+
solverforge = { version = "0.5", features = ["console"] }
322+
```
323+
324+
Try the **[Employee Scheduling Quickstart](https://github.com/solverforge/solverforge-quickstarts)**, which demonstrates a complete employee scheduling problem with shifts, skills, and availability constraints. It's the fastest way to see SolverForge in action and understand the workflow for defining problems, constraints, and solving.
325+
326+
The quickstarts repository will continue to grow with more examples covering different problem types and solver features.
327+
328+
## Python Bindings Coming Soon
329+
330+
While SolverForge is now a native Rust solver, we remain committed to multi-language accessibility. **Python bindings are under active development** at [github.com/solverforge/solverforge-py](https://github.com/solverforge/solverforge-py) and will be released later this month (late January 2026).
331+
332+
The architectural shift to native Rust was a major undertaking, and we chose to focus on getting the core solver right before building language bridges. The Python bindings will provide idiomatic Python APIs backed by SolverForge's zero-erasure engine, giving Python developers native constraint solving performance with familiar syntax.
333+
334+
This gives us the best of both worlds: predictable, high-performance solving in Rust, with accessible bindings for the broader Python ecosystem.
335+
336+
## What's Next
337+
338+
Beyond Python bindings, the quickstart roadmap includes:
339+
340+
- **Employee Scheduling**: ✓ Available now
341+
- **Vehicle Routing**: Next in pipeline
342+
- More domain-specific examples as the ecosystem grows
343+
344+
We're also working on:
345+
- Expanded documentation and tutorials
346+
- Additional constraint stream operations
347+
- Performance benchmarks comparing different solver configurations
348+
- Community-contributed problem templates
349+
350+
## Looking Ahead
351+
352+
Version 0.5.0 represents a turning point for SolverForge. The zero-erasure architecture and SERIO engine provide a foundation for building a high-performance, accessible constraint solver that works across languages while maintaining Rust's performance and safety guarantees.
353+
354+
We invite you to try SolverForge 0.5.0, explore the [quickstarts](https://github.com/solverforge/solverforge-quickstarts), and share your feedback. Whether you're scheduling employees, routing vehicles, or optimizing resource allocation, SolverForge provides the tools to model and solve your constraints efficiently.
355+
356+
The journey from FFI experiments to native Rust solver has been challenging, but the result is a constraint solver built on solid architectural foundations. We're excited to see what you build with it.
357+
358+
---
359+
360+
**Further reading:**
361+
- [SolverForge on GitHub](https://github.com/solverforge/solverforge)
362+
- [Quickstarts Repository](https://github.com/solverforge/solverforge-quickstarts)
363+
- [API Documentation](https://docs.rs/solverforge)
364+
- [Python Bindings (Coming Soon)](https://github.com/solverforge/solverforge-py)
365+
- [Why Java Interop is Difficult](/blog/technical/2025/12/30/why-java-interop-is-difficult/)
366+
- [JPype Performance Challenges](/blog/technical/2025/12/07/order-picking-quickstart-jpype-performance/)
367+
- [Python Architecture Lessons](/blog/technical/2025/12/06/python-constraint-solver-architecture/)

0 commit comments

Comments
 (0)