Skip to content

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