ommx 3.0.0-beta.5

Open Mathematical prograMming eXchange (OMMX)
Documentation
# Instance

The [`Instance`](crate::Instance) type represents a complete optimization problem with objective, variables,
and constraints. All variables used in the objective and constraints must be defined in the
decision variables map.

```rust
use ommx::{Instance, DecisionVariable, VariableID, Constraint, ConstraintID, Function, Sense, Linear, linear, coeff};
use maplit::btreemap;
use std::collections::BTreeMap;

// Create decision variables
let decision_variables = btreemap! {
    VariableID::from(1) => DecisionVariable::binary(),
    VariableID::from(2) => DecisionVariable::continuous(),
};

// Create objective function: minimize x1 + 2*x2
let objective = Function::from((linear!(1) + (coeff!(2.0) * linear!(2))?)?);

// Create constraints
let constraints = btreemap! {
    // x1 + x2 = 1
    ConstraintID::from(1) => Constraint::equal_to_zero(
        Function::from(((linear!(1) + linear!(2))? + Linear::from(coeff!(-1.0)))?)
    ),
    // x2 <= 5
    ConstraintID::from(2) => Constraint::less_than_or_equal_to_zero(
        Function::from((linear!(2) + Linear::from(coeff!(-5.0)))?)
    ),
};

// Create the instance
let instance = Instance::new(
    Sense::Minimize,
    objective,
    decision_variables,
    constraints,
)?;

assert_eq!(instance.sense(), Sense::Minimize);
assert_eq!(instance.decision_variables().len(), 2);
assert_eq!(instance.constraints().len(), 2);
# Ok::<(), Box<dyn std::error::Error>>(())
```

The `new` method validates that all variable IDs used in the objective function and
constraints are defined in the decision variables map, returning an error if any
undefined variables are referenced.

## Incremental decision-variable construction

When the instance should allocate variable IDs, use its typed `new_*` methods.
Each method receives the complete bound, modeling label, optional fixed value,
and tolerance before changing the instance:

```rust
use ommx::{ATol, Bound, DecisionVariableLabel, Instance, Kind, VariableID};

let mut instance = Instance::default();
let id = instance.new_integer(
    Bound::new(0.2, 3.8)?,
    DecisionVariableLabel {
        name: Some("count".to_string()),
        ..Default::default()
    },
    Some(2.0),
    ATol::default(),
)?;

assert_eq!(id, VariableID::from(0));
assert_eq!(instance.decision_variables()[&id].kind(), Kind::Integer);
assert_eq!(
    instance.decision_variables()[&id].bound(),
    Bound::new(1.0, 3.0)?,
);
assert_eq!(instance.variable_labels().name(id), Some("count"));
assert_eq!(instance.fixed_decision_variable_value(id), Some(2.0));
# Ok::<(), Box<dyn std::error::Error>>(())
```

For `Integer` and `SemiInteger`, finite endpoints are normalized to
`ceil(lower - atol)` and `floor(upper + atol)`. If that normalized interval
contains no integer, `new_integer` returns an error, while `new_semi_integer`
uses `[0, 0]` to preserve the semi-integer zero alternative.

The normalized row, label, and fixed value are committed together under the
returned ID. Invalid bounds, inconsistent fixed values, and a maximum existing
decision-variable ID of `u64::MAX` that prevents assignment of a larger
automatic ID return [`DecisionVariableError`](crate::DecisionVariableError)
without leaving a partially created variable in the instance. Use
[`Instance::new_decision_variable`](crate::Instance::new_decision_variable)
when the kind is selected dynamically.