Skip to content

Garden Optimization Example

Download Notebook


The Garden Optimization problem finds the optimal placement of plants in a rectangular garden modeled as a graph. Plants have friendly, neutral, or antagonistic relationships, and the goal is to maximize friendly adjacencies while respecting species counts and size-based placement constraints.

import getpass
import os

from dotenv import load_dotenv
from luna_quantum.algorithms import SCIP

from luna_usecases.garden_optimization import (
    GardenOptimizationCollection,
    GardenOptimizationData,
    GardenOptimizationFormulation,
    GardenOptimizationInstance,
)

load_dotenv()
if "LUNA_API_KEY" not in os.environ:
    os.environ["LUNA_API_KEY"] = getpass.getpass("Enter your Luna API key: ")

Create Data

Generate a small garden optimization instance with a 2x3 grid and 4 plant species using the generate_random() factory method.

data = GardenOptimizationData.generate_random(
    n_rows=2,
    n_cols=3,
    n_species=4,
    seed=42,
)
print(data.to_string())
Garden Optimization Data:
  Pots: 6
  Edges: 7
  Species: 4
  Counts: [1, 2, 1, 2]

Plot Data

Visualize the garden graph layout.

data.plot()

<Axes: title={'center': 'Garden Optimization — 6 pots, 7 edges, 4 species'}>
png

Create Formulation

Minimize antagonistic adjacencies while satisfying placement constraints: one plant per pot, species counts, and size-based row assignment.

formulation = GardenOptimizationFormulation()
print(formulation.to_string(data))
Garden Optimization Formulation:
  Pots: 6
  Species: 4

Decision Variables:
  x[p,s] in {0,1} for p = 0, ..., 5, s = 0, ..., 3
  x[p,s] = 1 if a plant of species s is placed in pot p
  Total: 24 binary variables

Objective:
  minimize sum_{(p1,p2) in E} sum_{s1,s2} compatibility[s1][s2] * x[p1,s1] * x[p2,s2]

Constraints:
  1. One plant per pot (6 constraints):
     sum_s x[p,s] == 1  for all p = 0, ..., 5
  2. Species count met (4 constraints):
     sum_p x[p,s] == count[s]  for all s = 0, ..., 3

Create Instance

Combine data and formulation into a solvable instance.

instance = GardenOptimizationInstance(data=data, formulation=formulation)
print(instance.to_string())
Data:Garden Optimization Data:
  Pots: 6
  Edges: 7
  Species: 4
  Counts: [1, 2, 1, 2]
Formulation:Garden Optimization Formulation:
  Pots: 6
  Species: 4

Decision Variables:
  x[p,s] in {0,1} for p = 0, ..., 5, s = 0, ..., 3
  x[p,s] = 1 if a plant of species s is placed in pot p
  Total: 24 binary variables

Objective:
  minimize sum_{(p1,p2) in E} sum_{s1,s2} compatibility[s1][s2] * x[p1,s1] * x[p2,s2]

Constraints:
  1. One plant per pot (6 constraints):
     sum_s x[p,s] == 1  for all p = 0, ..., 5
  2. Species count met (4 constraints):
     sum_p x[p,s] == count[s]  for all s = 0, ..., 3

Formulate Model

Translate the instance into a mathematical optimization model.

model = instance.formulate()

Solve and Interpret

Solve the model with SCIP and interpret the raw result into a use-case-specific solution.

scip = SCIP()
job = scip.run(model)
sol = job.result()
uc_solution = instance.interpret(sol)
print(uc_solution.to_string())
/Users/maximilianjanetschek/PycharmProjects/luna-usecases/.venv/lib/python3.13/site-packages/rich/live.py:260:
UserWarning: install "ipywidgets" for Jupyter support
  warnings.warn('install "ipywidgets" for Jupyter support')






2026-05-29 11:33:29 INFO     Sleeping for 5.0 seconds. Waiting and checking a function in a loop.


2026-05-29 11:33:36 INFO     Sleeping for 10.0 seconds. Waiting and checking a function in a loop.


2026-05-29 11:33:47 INFO     Sleeping for 15.0 seconds. Waiting and checking a function in a loop.




Garden Optimization Solution:
  Friendly pairs: 5
  Antagonistic pairs: 0
  Valid: True
  Assignment: {'(0, 0)': 2, '(0, 1)': 1, '(0, 2)': 3, '(1, 0)': 1, '(1, 1)': 0, '(1, 2)': 3}

Plot Solution

Visualize the plant placement in the garden.

uc_solution.plot(data)

<Axes: title={'center': 'Garden Optimization Solution — friendly=5, antagonistic=0, valid=True'}>
png

Collections

Generate benchmark collections of garden optimization instances for batch processing.

collection = GardenOptimizationCollection.from_random(
    min_rows=2,
    max_rows=4,
    n_cols=3,
    n_species=3,
    num_instances=2,
    seed=42,
)
model = collection.instances[0].formulate()
print(model)
Model: garden_optimization<garden_optimization>
Minimize
  -x_0_0 * x_1_1 + x_0_0 * x_1_2 - x_0_0 * x_3_1 + x_0_0 * x_3_2 - x_0_1 * x_1_0
  + x_0_1 * x_1_2 - x_0_1 * x_3_0 + x_0_1 * x_3_2 + x_0_2 * x_1_0
  + x_0_2 * x_1_1 + x_0_2 * x_3_0 + x_0_2 * x_3_1 - x_1_0 * x_2_1
  + x_1_0 * x_2_2 - x_1_0 * x_4_1 + x_1_0 * x_4_2 - x_1_1 * x_2_0
  + x_1_1 * x_2_2 - x_1_1 * x_4_0 + x_1_1 * x_4_2 + x_1_2 * x_2_0
  + x_1_2 * x_2_1 + x_1_2 * x_4_0 + x_1_2 * x_4_1 - x_2_0 * x_5_1
  + x_2_0 * x_5_2 - x_2_1 * x_5_0 + x_2_1 * x_5_2 + x_2_2 * x_5_0
  + x_2_2 * x_5_1 - x_3_0 * x_4_1 + x_3_0 * x_4_2 - x_3_1 * x_4_0
  + x_3_1 * x_4_2 + x_3_2 * x_4_0 + x_3_2 * x_4_1 - x_4_0 * x_5_1
  + x_4_0 * x_5_2 - x_4_1 * x_5_0 + x_4_1 * x_5_2 + x_4_2 * x_5_0
  + x_4_2 * x_5_1
Subject To
  one_plant_pot_0: x_0_0 + x_0_1 + x_0_2 == 1
  one_plant_pot_1: x_1_0 + x_1_1 + x_1_2 == 1
  one_plant_pot_2: x_2_0 + x_2_1 + x_2_2 == 1
  one_plant_pot_3: x_3_0 + x_3_1 + x_3_2 == 1
  one_plant_pot_4: x_4_0 + x_4_1 + x_4_2 == 1
  one_plant_pot_5: x_5_0 + x_5_1 + x_5_2 == 1
  species_count_0: x_0_0 + x_1_0 + x_2_0 + x_3_0 + x_4_0 + x_5_0 == 4
  species_count_1: x_0_1 + x_1_1 + x_2_1 + x_3_1 + x_4_1 + x_5_1 == 1
  species_count_2: x_0_2 + x_1_2 + x_2_2 + x_3_2 + x_4_2 + x_5_2 == 1
Binary
  x_0_0 x_0_1 x_0_2 x_1_0 x_1_1 x_1_2 x_2_0 x_2_1 x_2_2 x_3_0 x_3_1 x_3_2 x_4_0
  x_4_1 x_4_2 x_5_0 x_5_1 x_5_2