Skip to content

Example 9: Integer Manhattan facility location ​

1. Overview ​

This bounded context chooses one integer facility coordinate minimizing the sum of Manhattan distances to six settlements. The Kotlin and Rust implementations use different absolute-value helper APIs and therefore have different explicit coordinate bounds.

The supplied settlement coordinates are:

SettlementXsYs
192
221
338
43-2
559
64-2

1. Dependent Contexts ​

  1. None. The example is a self-contained location model.

2. Concepts / Entities ​

1. Settlement ​

A settlement is a fixed observed point in the coordinate plane.

Xs: observed x-coordinate of settlement s.

Ys: observed y-coordinate of settlement s.

2. Facility ​

The facility is the point selected by the solver.

X: selected facility x-coordinate.

Y: selected facility y-coordinate.


3. Variables ​

1. Decision Variables ​

X,Y: facility coordinates, coordinate units, integer domain. Kotlin creates scalar IntVar("x") and IntVar("y") without explicit bounds; Rust creates integer combinations with −100≤X,Y≤100.

2. Auxiliary Variables ​

DXs,DYs: non-negative absolute-distance components generated by exampleAbsoluteSlack in Kotlin or AbsFunction in Rust. They are mechanism/function outputs, not user-selected decisions.


4. Predicates ​

1. Settlement Coordinate Predicate ​

Observed(s): settlement s has fixed coordinates (Xs,Ys) in the input data.

2. Coordinate-Bound Predicate ​

RustBounded: true for the Rust implementation, which configures both coordinates in [−100,100]; false for the current Kotlin implementation.


5. Sets ​

1. Settlement Category ​

S: universal settlement set, S={1,…,6} for the sample.

SObserved: settlements with supplied observed coordinates; for this sample SObserved=S.

2. Coordinate Components ​

DX: x-axis absolute-distance components indexed by S.

DY: y-axis absolute-distance components indexed by S.

3. Entity Pairs / Relations ​

DistanceTo: relation pairing facility (X,Y) with each settlement s to form its Manhattan distance.


6. Intermediate Values ​

1. X-Axis Absolute Distance ​

Description: The non-negative horizontal distance between the selected facility and settlement s. The helper creates the auxiliary linearization constraints.

DXs=|X−Xs|,∀s∈S.

2. Y-Axis Absolute Distance ​

Description: The non-negative vertical distance between the selected facility and settlement s.

DYs=|Y−Ys|,∀s∈S.

3. Settlement Distance ​

Description: Manhattan distance from the facility to settlement s.

Distances=DXs+DYs,∀s∈S.

4. Total Distance ​

Description: Sum of the Manhattan distances to all settlements; this is the minimized expression.

TotalDistance=∑s∈SDistances.

7. Assertions ​

1. Absolute Components Are Non-Negative ​

Description: Each helper output measures an absolute difference and cannot be negative.

∀s∈S(DXs≥0∧DYs≥0).

2. Manhattan Decomposition ​

Description: Each settlement distance equals the sum of its horizontal and vertical components.

∀s∈S(Distances=|X−Xs|+|Y−Ys|).

3. Sample Optimum Set ​

Description: The coordinate-wise integer medians characterize the optimum for the supplied six points.

X∈{3,4},Y∈{1,2},TotalDistance=32.

8. Constraints ​

1. Facility Integer Domain (设施整数定义域约束) ​

Description: The selected facility coordinates are integers. Kotlin has no explicit geographic bounds; Rust additionally bounds them for the absolute-value mechanism.

s.t.X,Y∈Z,

and, for Rust only,

s.t.−100≤X≤100,−100≤Y≤100.

2. Absolute-Distance Linearization (绝对距离线性化约束) ​

Description: The source helper generates mechanism constraints whose semantic relation is the absolute value of each coordinate difference. No hand-written user-level inequality is added in Demo9.kt or demo9.rs.

s.t.DXs=|X−Xs|,DYs=|Y−Ys|,∀s∈S.

3. Distance Definition (距离定义约束) ​

Description: Each settlement's Manhattan distance is the sum of its two components.

s.t.Distances=DXs+DYs,∀s∈S.

9. Objective Function (if applicable) ​

Description: Minimize total Manhattan distance.

minTotalDistance.

10. Algorithm References ​

The page references helper mechanisms, but neither helper has a separate algorithm document in this example context.

Algorithm NameFile PathReferenced InBrief Description
exampleAbsoluteSlackospf-kotlin-example/src/main/.../ExampleModeling.ktSection 6.1–6.2Kotlin adapter around SlackFunction with both positive and negative deviations.
AbsFunctionospf-rust-example/src/core/demo9.rsSection 6.1–6.2Rust absolute-value function whose mechanism constraints are generated during conversion.

11. Ubiquitous Language ​

TermSymbolDefinition
Settlements∈SFixed point with observed coordinates.
Facility(X,Y)Integer point selected by the model.
Component distanceDXs,DYsAbsolute difference on one coordinate axis.
Manhattan distanceDistancesSum of the two component distances.

12. Design Decisions ​

DecisionAlternativesRationaleDate
Use helper functions for absolute valuesHand-write a new Big-M formulation in each demoThe current Kotlin and Rust APIs generate the mechanism constraints2026-09-08
Keep Kotlin coordinates unboundedAdd an arbitrary geographic boxDemo9.kt does not add bounds; documenting bounds would change its model2026-09-08
Record Rust bounds explicitlyClaim both implementations have the same domaindemo9.rs uses VariableRange::bounded(-100.0, 100.0)2026-09-08

Minimal current model-building snippets ​

The settlement data comes from Demo9.kt/demo9.rs. The snippets intentionally show the source helper calls; the data and converter declarations are omitted as source-owned setup.

kotlin
// `settlements` and `flt64Converter` come from Demo9.kt/ExampleModeling.kt.
val metaModel = LinearMetaModel<Flt64>("demo9", converter = flt64Converter)
val x = IntVar("x")
val y = IntVar("y")
metaModel.add(x)
metaModel.add(y)
val dx = LinearIntermediateSymbols1<Flt64>("dx", Shape1(settlements.size)) { i, _ ->
    exampleAbsoluteSlack(
        type = UInteger,
        x = flt64Linear(x),
        y = flt64Constant(settlements[i].x),
        name = "dx_$i"
    )
}
val dy = LinearIntermediateSymbols1<Flt64>("dy", Shape1(settlements.size)) { i, _ ->
    exampleAbsoluteSlack(
        type = UInteger,
        x = flt64Linear(y),
        y = flt64Constant(settlements[i].y),
        name = "dy_$i"
    )
}
val distance = LinearIntermediateSymbols1<Flt64>("distance", Shape1(settlements.size)) { i, _ ->
    LinearExpressionSymbol(dx[i] + dy[i], name = "distance_$i")
}
metaModel.add(dx)
metaModel.add(dy)
metaModel.add(distance)
metaModel.minimize(sum(distance[_a]), "total distance")
rust
// `settlements` comes from demo9.rs; Rust intentionally keeps the source bounds.
let mut model = MetaModel::<f64>::new("demo9");
let x: VariableCombination1D<Integer> =
    VariableCombination1D::with_range_generator(Shape::new([1]), "x", |_, _| {
    VariableRange::bounded(-100.0, 100.0)
});
let y: VariableCombination1D<Integer> =
    VariableCombination1D::with_range_generator(Shape::new([1]), "y", |_, _| {
    VariableRange::bounded(-100.0, 100.0)
});
let x_idx = model.register_combination(&x)?;
let y_idx = model.register_combination(&y)?;
let dx_fn = SymbolCombination::new(Shape::new([settlements.len()]), "dx", |i, _| {
    AbsFunction::named(
        &format!("dx_{}", settlements[i].name),
        ospf_rust_core::symbol::flatten::Linear::new(
            vec![ospf_rust_core::symbol::flatten::LinearMonomial::new(1.0, x_idx[0])],
            -settlements[i].x,
        ),
    )
});
model.add_symbol_combination(&dx_fn)?;
let dy_fn = SymbolCombination::new(Shape::new([settlements.len()]), "dy", |i, _| {
    AbsFunction::named(
        &format!("dy_{}", settlements[i].name),
        ospf_rust_core::symbol::flatten::Linear::new(
            vec![ospf_rust_core::symbol::flatten::LinearMonomial::new(1.0, y_idx[0])],
            -settlements[i].y,
        ),
    )
});
model.add_symbol_combination(&dy_fn)?;
let dx_idx: Vec<_> = (0..settlements.len()).map(|i|
    dx_fn.symbol_polynomial(i).monomials()[0].var_index()
).collect();
let dy_idx: Vec<_> = (0..settlements.len()).map(|i|
    dy_fn.symbol_polynomial(i).monomials()[0].var_index()
).collect();
let distance_expr = flat_map1_indexed("distance", settlements, |i, settlement| {
    ospf_rust_core::symbol::flatten::Linear::new(vec![
        ospf_rust_core::symbol::flatten::LinearMonomial::new(1.0, dx_idx[i]),
        ospf_rust_core::symbol::flatten::LinearMonomial::new(1.0, dy_idx[i]),
    ], 0.0)
}, |_, settlement| settlement.name.clone());
model.add_symbol_combination(&distance_expr)?;
let mut objective = Vec::new();
for i in 0..settlements.len() {
    for monomial in distance_expr.symbol_polynomial(i).monomials() {
        objective.push((monomial.var_index(), *monomial.coefficient()));
    }
}
model.add_linear_objective(&objective, "distance");
model.set_objective_category(ObjectiveCategory::Minimum);
// AbsFunction 机制会在模型转换阶段自动生成绝对值线性化约束。

13. Change Log ​

VersionChangeReason
2026-09-08Reorganized the page into the domain-model template; documented helper-generated constraints, the Rust-only bounds, and Kotlin/Rust tabsMake the two current implementations comparable without claiming identical domains

Source and verification ​