Identifying active constraints with Ampl#

identify_active_constraints.ipynb Open In Colab Open In Deepnote Open In Kaggle Open In Gradient Open In SageMaker Studio Lab Powered by 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

in

Constraint is in the problem (active)

drop

Removed by the drop command

pre

Eliminated by presolve

fix

Fixed by fix command

sub

Defined variable; substituted out

unused

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