Skip to content

Archived LunaModel 0.5.9 documentation

You are reading the old LunaModel 0.5.9 documentation. The regular documentation describes LunaModel >=0.6.0; use the latest documentation unless you explicitly need the old release.

Modeling Basics

This guide walks you through creating your first optimization models with LunaModel.

Your First Model

Let's start with a simple example: selecting items with maximum value subject to a weight constraint (a knapsack problem).

Problem Description

You have 3 items with the following properties:

Item Value Weight
A 10 2
B 15 3
C 8 1

You can carry at most 4 units of weight. Which items should you select?

Create the Model

Python
from luna_model import Model, Vtype, Sense

model = Model()

Define Variables

Create a binary variable for each item (1 = selected, 0 = not selected):

Python
item_a = model.add_variable("item_a", vtype=Vtype.BINARY)
item_b = model.add_variable("item_b", vtype=Vtype.BINARY)
item_c = model.add_variable("item_c", vtype=Vtype.BINARY)

Add Constraints

Ensure total weight doesn't exceed capacity:

Python
# Weight constraint: 2*a + 3*b + 1*c <= 4
model.add_constraint(2*item_a + 3*item_b + 1*item_c <= 4)

Set Objective

Maximize total value:

Python
# Objective: maximize 10*a + 15*b + 8*c
model.set_objective(
    10*item_a + 15*item_b + 8*item_c,
    sense=Sense.MAX
)

Inspect the Model

Python
print(model)
print(f"Number of variables: {model.num_variables()}")
print(f"Number of constraints: {model.num_constraints()}")

Complete Example

Python
from luna_model import Model, Vtype, Sense

# Create model
model = Model()

# Variables
item_a = model.add_variable("item_a", vtype=Vtype.BINARY)
item_b = model.add_variable("item_b", vtype=Vtype.BINARY)
item_c = model.add_variable("item_c", vtype=Vtype.BINARY)

# Constraint
model.add_constraint(2*item_a + 3*item_b + 1*item_c <= 4)

# Objective
model.set_objective(10*item_a + 15*item_b + 8*item_c, sense=Sense.MAX)

print(model)

Common Modeling Patterns

Pattern 1 - Selection Problems

Choose k items from n options:

Python
# Create n binary variables
n = 5
items = [model.add_variable(f"x_{i}", vtype=Vtype.BINARY) for i in range(n)]

# Select exactly k items
k = 3
model.add_constraint(sum(items) == k)

Pattern 2 - Assignment Problems

Assign n tasks to m workers:

Python
n_tasks = 4
n_workers = 3

# assignment[i][j] = 1 if task i assigned to worker j
assignment = []
for i in range(n_tasks):
    row = [model.add_variable(f"assign_{i}_{j}", vtype=Vtype.BINARY) 
           for j in range(n_workers)]
    assignment.append(row)

# Each task assigned to exactly one worker
for i in range(n_tasks):
    model.add_constraint(sum(assignment[i]) == 1)

# Worker capacity constraints (max 2 tasks per worker)
for j in range(n_workers):
    model.add_constraint(sum(assignment[i][j] for i in range(n_tasks)) <= 2)

Pattern 3 - Ordering/Sequencing

Sequence items in order:

Python
n = 4

# position[i][j] = 1 if item i is in position j
position = []
for i in range(n):
    row = [model.add_variable(f"pos_{i}_{j}", vtype=Vtype.BINARY) 
           for j in range(n)]
    position.append(row)

# Each item in exactly one position
for i in range(n):
    model.add_constraint(sum(position[i]) == 1)

# Each position has exactly one item
for j in range(n):
    model.add_constraint(sum(position[i][j] for i in range(n)) == 1)

Pattern 4 - Resource Allocation

Allocate resources with upper and lower bounds:

Python
# Number of resources to allocate
n = 3
resources = [model.add_variable(f"resource_{i}", 
                                vtype=Vtype.INTEGER, 
                                lower=10, 
                                upper=100) 
             for i in range(n)]

# Total resource constraint
model.add_constraint(sum(resources) <= 250)

# Minimum allocation per resource
for r in resources:
    model.add_constraint(r >= 10)

Working with Dictionaries

For more complex models, use dictionaries to organize variables:

Python
# Product and location dimensions
products = ['A', 'B', 'C']
locations = ['NYC', 'LA', 'CHI']

# Create variables using dictionary
inventory = {}
for p in products:
    for loc in locations:
        var_name = f"inv_{p}_{loc}"
        inventory[(p, loc)] = model.add_variable(var_name, 
                                                  vtype=Vtype.INTEGER, 
                                                  lower=0)

# Add constraints by product
for p in products:
    total = sum(inventory[(p, loc)] for loc in locations)
    model.add_constraint(total >= 100)  # Minimum total inventory

# Add constraints by location
for loc in locations:
    total = sum(inventory[(p, loc)] for p in products)
    model.add_constraint(total <= 500)  # Warehouse capacity

Building Expressions Incrementally

For large models, build expressions step by step:

Python
# Initialize empty expression
total_cost = 0

# Add terms incrementally
n = 100
for i in range(n):
    x = model.add_variable(f"x_{i}", vtype=Vtype.BINARY)
    cost = 5 + i * 0.1  # Cost varies by item
    total_cost = total_cost + cost * x

# Set as objective
model.objective = total_cost

Quadratic Models

LunaModel supports quadratic objectives and constraints:

Python
model = Model()

x = model.add_variable("x", vtype=Vtype.REAL, lower=-10, upper=10)
y = model.add_variable("y", vtype=Vtype.REAL, lower=-10, upper=10)

# Quadratic objective: minimize x^2 + y^2
model.objective = x*x + y*y

# Quadratic constraint: x^2 + y^2 <= 25 (circle)
model.add_constraint(x*x + y*y <= 25)

# Linear constraint: x + y >= 2
model.add_constraint(x + y >= 2)

Model Inspection

After building a model, inspect its properties:

Python
# Basic statistics
print(f"Variables: {model.num_variables}")
print(f"Constraints: {model.num_constraints}")
print(f"Objective sense: {model.sense}")

# Iterate over variables
for var in model.variables():
    print(f"{var.name}: {var.vtype}, bounds={var.bounds}")

# Iterate over constraints
for i, constraint in enumerate(model.constraints):
    print(f"Constraint {i}: {constraint}")

# View objective
print(f"Objective: {model.objective}")

Model Validation

Check for common modeling errors:

Python
# Check for variables without bounds
for var in model.variables():
    if var.vtype == Vtype.INTEGER or var.vtype == Vtype.REAL:
        lower, upper = var.bounds
        if lower is None or upper is None:
            print(f"Warning: {var.name} has unbounded range")

# Check for empty objective
if model.objective is None:
    print("Warning: No objective set")

# Check for infeasible constraints (simple checks)
# e.g., x <= 5 and x >= 10

Example: Production Planning

Let's solve a complete production planning problem:

Python
from luna_model import Model, Vtype, Sense

model = Model()

# Products to manufacture
products = ['Widget', 'Gadget', 'Doohickey']

# Production variables (units to produce)
production = {}
for p in products:
    production[p] = model.add_variable(
        f"produce_{p}",
        vtype=Vtype.INTEGER,
        lower=0,
        upper=1000
    )

# Resource requirements (hours per unit)
labor = {'Widget': 2, 'Gadget': 3, 'Doohickey': 1}
machine = {'Widget': 1, 'Gadget': 2, 'Doohickey': 1}

# Resource availability (hours)
labor_available = 500
machine_available = 400

# Resource constraints
model.add_constraint(
    sum(labor[p] * production[p] for p in products) <= labor_available
)
model.add_constraint(
    sum(machine[p] * production[p] for p in products) <= machine_available
)

# Demand constraints (minimum production)
demand = {'Widget': 50, 'Gadget': 30, 'Doohickey': 40}
for p in products:
    model.add_constraint(production[p] >= demand[p])

# Profit per unit
profit = {'Widget': 12, 'Gadget': 20, 'Doohickey': 8}

# Maximize profit
model.set_objective(
    sum(profit[p] * production[p] for p in products),
    sense=Sense.MAX
)

print(model)

Tips for Effective Modeling

  1. Start simple: Build a minimal model first, then add complexity
  2. Use meaningful names: num_workers is better than x1
  3. Document constraints: Add comments explaining business rules
  4. Organize variables: Use lists or dictionaries for related variables
  5. Validate inputs: Check bounds and parameter values make sense
  6. Test with small instances: Debug with 3-5 variables before scaling up

Common Pitfalls

Pitfall 1 - Unbounded Variables

Python
# Bad: Unbounded integer can grow arbitrarily
x = model.add_variable("x", vtype=Vtype.INTEGER)

# Good: Set reasonable bounds
x = model.add_variable("x", vtype=Vtype.INTEGER, lower=0, upper=1000)

Pitfall 2 - Wrong Variable Type

Python
# Bad: Using REAL when BINARY is appropriate
is_selected = model.add_variable("selected", vtype=Vtype.REAL, lower=0, upper=1)

# Good: Use correct type
is_selected = model.add_variable("selected", vtype=Vtype.BINARY)

Pitfall 3 - Forgetting Constraints

Python
# Don't forget to add logical constraints
x = model.add_variable("x", vtype=Vtype.BINARY)
y = model.add_variable("y", vtype=Vtype.BINARY)

# If x then y (y depends on x)
model.add_constraint(y <= x)  # Don't forget this!

Next Steps