Identifying active constraints with Ampl#
Description: This notebook demonstrates how to inspect the status of constraints in an AMPL model using the astatus() method provided by amplpy. It shows how to identify which constraints are currently active (i.e., participating in the optimization) and filter out those that have been dropped, presolved, or otherwise excluded
Tags: tutorials
Notebook author: Marcos Dominguez Velad <marcos@ampl.com>
Model author: N/A
# Install dependencies
%pip install -q amplpy matplotlib networkx
# Google Colab & Kaggle integration
from amplpy import AMPL, ampl_notebook
ampl = ampl_notebook(
modules=["highs"], # modules to install
license_uuid="default", # license to use
) # instantiate AMPL object and register magics
Identifying Active Constraints using amplpy
#
When working with optimization models in Amplpy, it is often useful to know which constraints are currently active, i.e., those that are not dropped. amplpy
provides a convenient method for checking the status of constraints using the astatus()
function.
The astatus()
function returns the AMPL-level status of a constraint, such as:
Status |
Description |
---|---|
|
Constraint is in the problem (active) |
|
Removed by the |
|
Eliminated by presolve |
|
Fixed by |
|
Defined variable; substituted out |
|
Not used in the current problem |
For detailed information, see the official documentation: 👉 amplpy.Constraint.astatus
Example: Getting and Printing Active Constraints#
In the example below, we use a helper function get_active_constraints()
that scans all constraints in the model and filters only those whose status is not 'drop'
. A second function, print_constraints_status()
, is used to display the status of each constraint, making it easy to debug or audit the model.
# Return a list of tuples for every active constraint in the problem
def get_active_constraints(model):
status_cons = [ (name, index, con, con.astatus())
for name, constraint_family in model.get_constraints()
for index, con in constraint_family ]
active_cons = [ (name, index, con, status)
for name, index, con, status in status_cons if status != 'drop']
return active_cons
# Print the status of all constraints in the model
def print_constraints_status(model):
for name, constraint_family in model.get_constraints():
print(f"++ Constraint: {name}, status:")
for index, con in constraint_family:
if index:
print(f"-{name}[{index}]: {con.astatus()}")
else:
print(f"-{name}: {con.astatus()}")
The example includes a small production planning problem. After loading the model and its data, we explicitly drop a family of constraints (Resource_Limit
). When we then call get_active_constraints()
, only the remaining constraints will be listed as active.
This allows us to programmatically determine which constraints are currently affecting the solution process.
%%writefile m.mod
# Sets
set PRODUCTS; # Set of products
set RESOURCES; # Set of resources
# Parameters
param profit{PRODUCTS}; # Profit per unit of each product
param demand{PRODUCTS}; # Maximum demand for each product
param available{RESOURCES}; # Available amount of each resource
param usage{RESOURCES,PRODUCTS}; # Resource usage per unit of product
param min_production{PRODUCTS} default 0; # Minimum production requirements
# Variables
var Make{p in PRODUCTS} >= max(0, min_production[p]), <= demand[p]; # Production amount
# Objective: Maximize total profit
maximize Total_Profit:
sum{p in PRODUCTS} profit[p] * Make[p];
# Constraints: Don't exceed available resources
subject to Resource_Limit{r in RESOURCES}:
sum{p in PRODUCTS} usage[r,p] * Make[p] <= available[r];
# Must meet minimum production requirements
subject to Minimum_Production{p in PRODUCTS}:
Make[p] >= min_production[p];
# Maintain product mix ratio
subject to Product_Mix_Ratio:
Make['A'] >= 0.3 * sum{p in PRODUCTS} Make[p];
Overwriting m.mod
# Simply load data for the problem
def load_data(model):
# Sets
PRODUCTS = ["A", "B", "C"]
RESOURCES = ["Machine", "Labor", "Material"]
# Parameters
profit = {"A": 12.50, "B": 8.75, "C": 10.00}
demand = {"A": 100, "B": 150, "C": 80}
available = {"Machine": 2000, "Labor": 1500, "Material": 5000}
usage = {
("Machine", "A"): 5,
("Machine", "B"): 3,
("Machine", "C"): 4,
("Labor", "A"): 2,
("Labor", "B"): 4,
("Labor", "C"): 1,
("Material", "A"): 8,
("Material", "B"): 6,
("Material", "C"): 7,
}
min_production = {"A": 20, "B": 0, "C": 10}
# Load into Amplpy
model.set["PRODUCTS"] = PRODUCTS
model.set["RESOURCES"] = RESOURCES
model.param["profit"] = profit
model.param["demand"] = demand
model.param["available"] = available
model.param["usage"] = usage
model.param["min_production"] = min_production
from amplpy import AMPL
# Return a list of tuples for every active constraint in the problem
# Each tuple has name, index (optional in case of non-indexed constraints),
# the constraint object, and the status associated to the constraints
def get_active_constraints(model):
status_cons = [
(name, index, con, con.astatus())
for name, constraint_family in model.get_constraints()
for index, con in constraint_family
]
# for name, index, status in status_cons:
# print(name, index, status)
active_cons = [
(name, index, con, status)
for name, index, con, status in status_cons
if status != "drop"
]
return active_cons
def print_constraints_status(model):
for name, constraint_family in model.get_constraints():
print(f"++ Constraint: {name}, status:")
for index, con in constraint_family:
if index:
print(f"-{name}[{index}]: {con.astatus()}")
else:
print(f"-{name}: {con.astatus()}")
# Create Ampl object
model = AMPL()
# Read model and load data
model.read("m.mod")
load_data(model)
# Drop Resource_Limit constraints
model.eval("drop Resource_Limit;")
# Get active constraints and print them
active_cons = get_active_constraints(model)
for name, index, _, _ in active_cons:
if not index:
print(f"{name} is an active constraint")
else:
print(f"{name}[{index}] is an active constraint")
Minimum_Production[A] is an active constraint
Minimum_Production[B] is an active constraint
Minimum_Production[C] is an active constraint
Product_Mix_Ratio is an active constraint
# Show status after solving
model.solve(solver="highs")
assert model.solve_result == "solved", model.solve_result
print()
print_constraints_status(model)
HiGHS 1.8.1:HiGHS 1.8.1: optimal solution; objective 3362.5
0 simplex iterations
0 barrier iterations
++ Constraint: Minimum_Production, status:
-Minimum_Production[A]: pre
-Minimum_Production[B]: pre
-Minimum_Production[C]: pre
++ Constraint: Product_Mix_Ratio, status:
-Product_Mix_Ratio: in
++ Constraint: Resource_Limit, status:
-Resource_Limit[Labor]: drop
-Resource_Limit[Machine]: drop
-Resource_Limit[Material]: drop