Skip to content

Example 6: Bounded-multiplicity knapsack ​

1. Overview ​

This bounded context chooses an integer quantity of each cargo type under per-type stock limits and a total-weight limit, maximising total value.

1. Dependent Contexts ​

  1. Core integer linear optimisation context (non-negative integer variables, linear expressions, constraints, and solver adapter).

The Kotlin and Rust snippets are model-building fragments. Cargo data and solver setup come from the linked Demo6 implementations.


2. Concepts / Entities ​

1. Cargo Type ​

A cargo type can be selected several times, but its stock is finite.

Weightc : weight of one unit of cargo type c.

Valuec : value of one unit of cargo type c.

AmountcMax : maximum available number of units of cargo type c.

The current data are:

CargoWeightValueAvailable amount
11610
22105
32202

The total weight limit is WeightMax=8.


3. Variables ​

1. Decision Variables ​

xc : cargo-count variable, item-count quantity, domain Z≥0, number of selected units of cargo type c, ∀c∈C.

2. Auxiliary Variables ​

None. Total value and total weight are registered linear expressions/intermediate values.


4. Predicates ​

1. Cargo Quantity Status ​

Predicates classify cargo types by whether they contribute to the plan.

SelectedType(c) : cargo type c has a positive selected quantity, xc>0.

UnusedType(c) : cargo type c has no selected units, xc=0.

AtStockLimit(c) : all available units of cargo type c are selected, xc=AmountcMax.


5. Sets ​

1. Cargo Type Category ​

C : universal set of cargo types.

CSelectedType : subset satisfying SelectedType, CSelectedType={c∈C∣xc>0}, cargo types contributing at least one unit.

CUnusedType : subset satisfying UnusedType, CUnusedType={c∈C∣xc=0}, cargo types not selected.

2. Entity Pairs / Relations ​

No pair relation is required; each stock rule applies to one cargo type.


6. Intermediate Values ​

1. Total Value ​

Description: Total value is the sum of unit values multiplied by the selected count of each cargo type.

Value(x)=∑c∈CValuecxc.

2. Total Weight ​

Description: Total weight is the sum of unit weights multiplied by the selected count of each cargo type.

Weight(x)=∑c∈CWeightcxc.

7. Assertions ​

1. Integer Non-negative Counts ​

Description: A cargo type can contribute zero or more whole units only.

∀c∈C(xc∈Z≥0).

2. Stock-Bounded Count ​

Description: Every selected count is no greater than the recorded stock amount.

∀c∈C(0≤xc≤AmountcMax).

8. Constraints ​

1. Total Weight Capacity ​

[总重量容量上限]: the total weight of all selected units must not exceed the knapsack capacity.

s.t.Weight(x)=∑c∈CWeightcxc≤WeightMax=8.

2. Cargo Stock Limit ​

[货物库存上限]: the selected quantity of every cargo type must not exceed its available stock.

s.t.xc≤AmountcMax,∀c∈C.

The lower bound xc≥0 is supplied by the Kotlin UIntVariable1 and Rust UInteger variable domains; it is not a separate lower-bound row in either current source.


9. Objective Function (if applicable) ​

Description: maximise the value of the selected cargo units.

maxValue(x)=∑c∈CValuecxc.

A displayed optimum is x=(4,0,2), with total weight 8 and total value 64.


10. Algorithm References ​

No standalone algorithm document is referenced. This is a direct bounded-integer knapsack formulation.

Algorithm NameFile PathReferenced InBrief Description
Not applicable——No domain-specific algorithm is needed.

11. Ubiquitous Language ​

TermSymbolDefinition
Cargo typec∈CItem type that may be selected multiple times.
Unit weightWeightcWeight of one unit of type c.
Unit valueValuecValue of one unit of type c.
Selected countxcWhole-unit quantity selected for type c.
Stock limitAmountcMaxAvailable quantity of type c.
Weight capacityWeightMaxMaximum total selected weight.

12. Design Decisions ​

DecisionAlternativesRationaleDate
Use non-negative integer count variablesBinary variablesThe source uses UIntVariable1/UInteger and permits multiple units of one type.Current implementation
Encode stock as an upper boundOmit stock or expand each unit into a binary itemKotlin sets each variable range upper bound; Rust adds one upper-bound constraint per cargo type.Current implementation
Keep this model distinct from Example 5Reuse 0–1 subset languageExample 6 permits repeated units, so “subset” alone would be misleading.Current implementation

Minimal current implementation fragments ​

The following are non-standalone fragments from Demo6. cargos, maxWeight, model setup, converter, and solver setup are supplied by the linked source.

kotlin
// Fragment from Demo6.initVariable/initSymbol/initObject/initConstraint.
// Data source: Demo6's private cargos list and maxWeight.
val x = UIntVariable1("x", Shape1(cargos.size))
val cargoValue = LinearExpressionSymbol(
    sum(cargos) { c -> c.value * x[c] },
    name = "value"
)
val cargoWeight = LinearExpressionSymbol(
    sum(cargos) { c -> c.weight * x[c] },
    name = "weight"
)
metaModel.add(x)
metaModel.add(cargoValue)
metaModel.add(cargoWeight)
for (c in cargos) {
    x[c].range.ls(c.amount)
}
metaModel.maximize(cargoValue, "value")
metaModel.addConstraint(
    cargoWeight leq maxWeight,
    name = "weight"
)
rust
// Fragment from demo6.rs::IntegerKnapsackModel::register/add_constraints.
// Data source: build_cargos and max_weight in demo6.rs.
let x = VariableCombination1D::new(Shape::new([cargos.len()]), "x");
let x_idx = model.register_combination(&x)?;
let cargo_value = flat_map1_indexed(
    "cargo_value",
    cargos,
    |i, cargo| {
        ospf_rust_core::symbol::flatten::Linear::new(
            vec![ospf_rust_core::symbol::flatten::LinearMonomial::new(
                cargo.value,
                x_idx[i],
            )],
            0.0,
        )
    },
    |_, cargo| cargo.name.clone(),
);
model.add_symbol_combination(&cargo_value)?;
let cargo_weight = flat_map1_indexed(
    "cargo_weight",
    cargos,
    |i, cargo| {
        ospf_rust_core::symbol::flatten::Linear::new(
            vec![ospf_rust_core::symbol::flatten::LinearMonomial::new(
                cargo.weight,
                x_idx[i],
            )],
            0.0,
        )
    },
    |_, cargo| cargo.name.clone(),
);
model.add_symbol_combination(&cargo_weight)?;
let val_coeffs = extract_coeffs(&cargo_value[0]);
model.add_linear_objective(&val_coeffs, "value");
model.set_objective_category(ObjectiveCategory::Maximum);
let wt_coeffs = extract_coeffs(&cargo_weight[0]);
model.add_linear_constraint(
    &wt_coeffs,
    ConstraintRelation::LessEqual,
    max_weight,
    "weight",
)?;
for (i, cargo) in cargos.iter().enumerate() {
    model.add_linear_constraint(
        &[(x_idx[i], 1.0)],
        ConstraintRelation::LessEqual,
        cargo.max_amount,
        &format!("upper_{}", i),
    )?;
}

Source and verification ​

The Rust counterpart uses the same bounded-integer mathematics and data, but its Rust MetaModel, variable-combination, and symbol-combination APIs are independent of the Kotlin API.

The source uses UIntVariable1/UInteger rather than a binary variable, sets the per-type upper bounds, and applies the total-weight inequality. The fragments above are model-building excerpts rather than complete runnable programs.


13. Change Log ​

VersionChangeReason
1.0Reorganised the example into the domain-model template and added quantified stock assertions, named constraints, and Kotlin/Rust fragments.Make bounded multiplicity distinct from the 0–1 model.