This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Reference

API reference and quick lookup guides

Quick reference guides and API documentation.

In This Section

Import Summary

// Domain modeling
use solverforge_core::domain::{
    DomainClass, DomainModel, FieldDescriptor, FieldType,
    PlanningAnnotation, PrimitiveType, ScoreType,
};

// Constraints
use solverforge_core::{
    Collector, Constraint, ConstraintSet, Joiner,
    StreamComponent, WasmFunction,
};

// Scores
use solverforge_core::{
    HardSoftScore, HardMediumSoftScore, SimpleScore, BendableScore,
    Score,  // trait
};

// Solver
use solverforge_core::{
    SolveRequest, SolveResponse, SolverConfig, TerminationConfig,
    EnvironmentMode, MoveThreadCount,
};

// WASM
use solverforge_core::wasm::{
    Expr, FieldAccessExt, Expression,
    WasmModuleBuilder, PredicateDefinition, HostFunctionRegistry,
};

// Errors
use solverforge_core::{SolverForgeError, SolverForgeResult};

// Service
use solverforge_service::{EmbeddedService, ServiceConfig};

Crate Structure

solverforge_core
├── domain           # Domain model definitions
│   ├── DomainModel
│   ├── DomainClass
│   ├── FieldDescriptor
│   ├── FieldType
│   ├── PlanningAnnotation
│   └── PrimitiveType
├── constraints      # Constraint streams
│   ├── StreamComponent
│   ├── Collector
│   ├── Joiner
│   └── WasmFunction
├── score           # Score types
│   ├── Score (trait)
│   ├── HardSoftScore
│   ├── HardMediumSoftScore
│   └── BendableScore
├── solver          # Solver configuration
│   ├── SolverConfig
│   ├── TerminationConfig
│   └── EnvironmentMode
├── wasm            # WASM generation
│   ├── Expression
│   ├── Expr
│   ├── WasmModuleBuilder
│   └── PredicateDefinition
└── error           # Error types
    ├── SolverForgeError
    └── SolverForgeResult

1 - API Quick Reference

Cheat sheet for common SolverForge APIs

Domain Model

DomainModel

DomainModel::builder()
    .add_class(class)
    .build()                    // Build without validation
    .build_validated()?         // Build with validation

DomainClass

DomainClass::new("ClassName")
    .with_annotation(PlanningAnnotation::PlanningEntity)
    .with_field(field)

FieldDescriptor

FieldDescriptor::new("fieldName", FieldType::...)
    .with_planning_annotation(PlanningAnnotation::...)

Field Types

TypeRust
PrimitivesFieldType::Primitive(PrimitiveType::String)
ObjectFieldType::object("ClassName")
ListFieldType::list(element_type)
SetFieldType::set(element_type)
ArrayFieldType::array(element_type)
MapFieldType::map(key_type, value_type)
ScoreFieldType::Score(ScoreType::HardSoft)

Primitive Types

TypeRust
BooleanPrimitiveType::Bool
Int (32-bit)PrimitiveType::Int
Long (64-bit)PrimitiveType::Long
FloatPrimitiveType::Float
DoublePrimitiveType::Double
StringPrimitiveType::String
DatePrimitiveType::Date
DateTimePrimitiveType::DateTime

Planning Annotations

AnnotationRust
PlanningIdPlanningAnnotation::PlanningId
PlanningEntityPlanningAnnotation::PlanningEntity
PlanningSolutionPlanningAnnotation::PlanningSolution
PlanningVariablePlanningAnnotation::planning_variable(vec!["id"])
PlanningVariable (nullable)PlanningAnnotation::planning_variable_unassigned(vec![])
PlanningListVariablePlanningAnnotation::planning_list_variable(vec![])
PlanningScorePlanningAnnotation::planning_score()
PlanningScore (bendable)PlanningAnnotation::planning_score_bendable(2, 3)
ValueRangeProviderPlanningAnnotation::value_range_provider("id")
ProblemFactCollectionPropertyPlanningAnnotation::ProblemFactCollectionProperty
PlanningEntityCollectionPropertyPlanningAnnotation::PlanningEntityCollectionProperty
PlanningPinPlanningAnnotation::PlanningPin
InverseRelationShadowPlanningAnnotation::inverse_relation_shadow("var")

Constraint Streams

StreamComponent

OperationRust
forEachStreamComponent::for_each("Class")
forEach (unassigned)StreamComponent::for_each_including_unassigned("Class")
forEachUniquePairStreamComponent::for_each_unique_pair("Class")
forEachUniquePair (joiners)StreamComponent::for_each_unique_pair_with_joiners("Class", joiners)
filterStreamComponent::filter(WasmFunction::new("pred"))
joinStreamComponent::join("Class")
join (joiners)StreamComponent::join_with_joiners("Class", joiners)
ifExistsStreamComponent::if_exists("Class")
ifNotExistsStreamComponent::if_not_exists("Class")
groupByStreamComponent::group_by(keys, collectors)
groupBy (key only)StreamComponent::group_by_key(key)
groupBy (collect only)StreamComponent::group_by_collector(collector)
mapStreamComponent::map(mappers)
map (single)StreamComponent::map_single(mapper)
flattenLastStreamComponent::flatten_last()
expandStreamComponent::expand(mappers)
complementStreamComponent::complement("Class")
penalizeStreamComponent::penalize("1hard/0soft")
penalize (weigher)StreamComponent::penalize_with_weigher("1hard", weigher)
rewardStreamComponent::reward("1soft")

Joiners

JoinerRust
equalJoiner::equal(map)
equal (separate)Joiner::equal_with_mappings(left, right)
lessThanJoiner::less_than(map, comparator)
greaterThanJoiner::greater_than(map, comparator)
overlappingJoiner::overlapping(start, end)
filteringJoiner::filtering(filter)

Collectors

CollectorRust
countCollector::count()
countDistinctCollector::count_distinct()
sumCollector::sum(map)
averageCollector::average(map)
minCollector::min(map, comparator)
maxCollector::max(map, comparator)
toListCollector::to_list()
toSetCollector::to_set()
loadBalanceCollector::load_balance(map)
loadBalance (with load)Collector::load_balance_with_load(map, load)
composeCollector::compose(collectors, combiner)
conditionallyCollector::conditionally(pred, collector)
collectAndThenCollector::collect_and_then(collector, mapper)

Scores

Score Types

TypeCreateParse
SimpleScoreSimpleScore::of(-5)SimpleScore::parse("-5")
HardSoftScoreHardSoftScore::of(-2, 10)HardSoftScore::parse("-2hard/10soft")
HardMediumSoftScoreHardMediumSoftScore::of(-1, 5, 10)HardMediumSoftScore::parse(...)
BendableScoreBendableScore::of(hard_vec, soft_vec)N/A

Score Methods

score.is_feasible()     // hard >= 0
score + other           // Addition
score - other           // Subtraction
-score                  // Negation

Solver Configuration

SolverConfig

SolverConfig::new()
    .with_solution_class("Schedule")
    .with_entity_class("Shift")
    .with_environment_mode(EnvironmentMode::Reproducible)
    .with_random_seed(42)
    .with_move_thread_count(MoveThreadCount::Auto)
    .with_termination(termination)

TerminationConfig

TerminationConfig::new()
    .with_spent_limit("PT5M")
    .with_unimproved_spent_limit("PT30S")
    .with_best_score_feasible(true)
    .with_best_score_limit("0hard/-100soft")
    .with_step_count_limit(1000)
    .with_move_count_limit(10000)

WASM Expressions

Expr Builder

ExpressionRust
IntegerExpr::int(42)
BooleanExpr::bool(true)
NullExpr::null()
ParameterExpr::param(0)
Field accessexpr.get("Class", "field")
EqualExpr::eq(left, right)
Not equalExpr::ne(left, right)
Less thanExpr::lt(left, right)
Greater thanExpr::gt(left, right)
ANDExpr::and(left, right)
ORExpr::or(left, right)
NOTExpr::not(expr)
Is nullExpr::is_null(expr)
Is not nullExpr::is_not_null(expr)
AddExpr::add(left, right)
SubtractExpr::sub(left, right)
MultiplyExpr::mul(left, right)
DivideExpr::div(left, right)
List containsExpr::list_contains(list, elem)
String equalsExpr::string_equals(left, right)
Ranges overlapExpr::ranges_overlap(s1, e1, s2, e2)
If-then-elseExpr::if_then_else(cond, then, else)

WasmModuleBuilder

WasmModuleBuilder::new()
    .with_domain_model(model)
    .with_host_functions(HostFunctionRegistry::with_standard_functions())
    .with_initial_memory(16)
    .with_max_memory(Some(256))
    .add_predicate(PredicateDefinition::from_expression(name, arity, expr))
    .build()?                    // Vec<u8>
    .build_base64()?            // String

2 - Error Handling

Handle SolverForgeError types and troubleshoot common issues

SolverForge uses the SolverForgeError enum for all error types.

SolverForgeError

use solverforge_core::{SolverForgeError, SolverForgeResult};

fn solve_problem() -> SolverForgeResult<String> {
    // ... operations that may fail
    Ok("solution".to_string())
}

match solve_problem() {
    Ok(solution) => println!("Success: {}", solution),
    Err(e) => eprintln!("Error: {}", e),
}

Error Variants

Serialization

JSON serialization/deserialization errors:

SolverForgeError::Serialization(String)

Common causes:

  • Invalid JSON in problem data
  • Malformed score strings
  • Type mismatches

Example:

let score = HardSoftScore::parse("invalid")?;
// Error: Serialization error: Invalid HardSoftScore format...

Http

HTTP communication errors with the solver service:

SolverForgeError::Http(String)

Common causes:

  • Service not running
  • Network timeout
  • Connection refused

Solver

Errors returned by the solver service:

SolverForgeError::Solver(String)

Common causes:

  • Invalid constraint configuration
  • WASM execution failure
  • Memory allocation failure

WasmGeneration

Errors during WASM module generation:

SolverForgeError::WasmGeneration(String)

Common causes:

  • Invalid expression tree
  • Unknown field access
  • Missing domain model

Bridge

Language binding bridge errors:

SolverForgeError::Bridge(String)

Common causes:

  • Handle invalidation
  • Type conversion failures

Validation

Domain model validation errors:

SolverForgeError::Validation(String)

Common causes:

  • Missing @PlanningSolution class
  • No @PlanningEntity classes
  • Missing @PlanningVariable on entities
  • Missing @PlanningScore field

Example:

let model = DomainModel::builder()
    .add_class(DomainClass::new("Shift"))  // No annotations!
    .build_validated()?;
// Error: Validation error: No @PlanningSolution class found

Configuration

Configuration errors:

SolverForgeError::Configuration(String)

Common causes:

  • Invalid termination config
  • Invalid environment mode

Service

Embedded service lifecycle errors:

SolverForgeError::Service(String)

Common causes:

  • Java not found
  • Service startup timeout
  • Port already in use

Io

Standard I/O errors:

SolverForgeError::Io(std::io::Error)

Common causes:

  • File not found
  • Permission denied

Other

Generic errors:

SolverForgeError::Other(String)

Error Conversion

SolverForgeError automatically converts from common error types:

// From serde_json::Error
let err: SolverForgeError = serde_json::from_str::<i32>("bad")
    .unwrap_err()
    .into();

// From std::io::Error
let err: SolverForgeError = std::fs::read("nonexistent")
    .unwrap_err()
    .into();

Using SolverForgeResult

The type alias simplifies return types:

use solverforge_core::SolverForgeResult;

fn build_model() -> SolverForgeResult<DomainModel> {
    let model = DomainModel::builder()
        .add_class(/* ... */)
        .build_validated()?;  // Returns SolverForgeResult
    Ok(model)
}

Error Handling Patterns

Match on Variants

match result {
    Ok(solution) => { /* handle success */ }
    Err(SolverForgeError::Validation(msg)) => {
        eprintln!("Model validation failed: {}", msg);
    }
    Err(SolverForgeError::Http(msg)) => {
        eprintln!("Service communication failed: {}", msg);
    }
    Err(e) => {
        eprintln!("Other error: {}", e);
    }
}

Propagate with ?

fn solve() -> SolverForgeResult<SolveResponse> {
    let model = build_model()?;
    let wasm = build_wasm(&model)?;
    let response = send_request(&wasm)?;
    Ok(response)
}

Convert to String

let error_message = format!("{}", error);

Troubleshooting

“Service not running”

Error: Http error: connection refused

Fix: Start the solver service:

let service = EmbeddedService::start(ServiceConfig::new())?;

“WASM generation failed”

Error: WasmGeneration error: Unknown class 'Shift'

Fix: Ensure domain model is set:

WasmModuleBuilder::new()
    .with_domain_model(model)  // Required!

“Validation error: No solution class”

Error: Validation error: No @PlanningSolution class found

Fix: Add PlanningSolution annotation:

DomainClass::new("Schedule")
    .with_annotation(PlanningAnnotation::PlanningSolution)

“Invalid score format”

Error: Serialization error: Invalid HardSoftScore format

Fix: Use correct format: "0hard/-5soft" not "0/-5"