⬅ All snippets

LP Visualizer


…for two variables

Imports

from typing import List

import matplotlib.pyplot as plt
import numpy as np

Code

OP = {
    "<": np.less,
    ">": np.greater,
    "==": np.equal,
    "<=": np.less_equal,
    ">=": np.greater_equal,
}
LATEX_OP = {
    "<=": "\leq",
    ">=": "\geq",
    "==": "\eq",
    "<": "<",
    ">": ">",
}


class Constraint:
    """
    Assumption:
        y INEQ x constant
    """

    def __init__(self, c_y: int, op: str, c_x: int, c: int):
        self.c_x = c_x
        self.c_y = c_y
        self.c = c
        self.op = op

    def to_line(self, x):
        return ((x * self.c_x) + self.c) / self.c_y

    def to_feasible(self, x, y):
        return OP[self.op](self.c_y * y, (self.c_x * x + self.c))

    def _str_constant(self):
        xy_str = []
        for constant in [self.c_x, self.c_y]:
            if abs(constant) == 1:
                ret = "" if constant > 0 else "-"
            else:
                ret = str(constant)

            xy_str.append(ret)

        if self.c_x != 0:
            xy_str[0] += "x_1"
        if self.c_y != 0:
            xy_str[1] += "x_2"

        return xy_str

    def __str__(self):
        constant_str = ""

        if self.c > 0:
            constant_str = f" + {self.c}"
        elif self.c < 0:
            constant_str = f" - {-self.c}"

        x_str, y_str = self._str_constant()

        return f"${y_str}{LATEX_OP[self.op]} {x_str}{constant_str}$"


def make_plot(
    min_xy: int,
    max_xy: int,
    constraints: List[Constraint],
    steps_feasible: int = 500,
    steps_lines: int = 2000,
):
    d = np.linspace(min_xy, max_xy, steps_feasible)
    x, y = np.meshgrid(d, d)
    fig, ax = plt.subplots(figsize=(10, 10))

    feasible_region = constraints[0].to_feasible(x, y)
    for c in constraints[1:]:
        feasible_region &= c.to_feasible(x, y)

    ax.imshow(
        feasible_region.astype(int),
        extent=(x.min(), x.max(), y.min(), y.max()),
        origin="lower",
        cmap="Greys",
        alpha=0.5,
    )

    x = np.linspace(min_xy, max_xy, steps_lines)

    for c in constraints:
        if c.c_y == 0:
            ax.plot(np.ones_like(x) * (-c.c / c.c_x), x, label=str(c))
        else:
            ax.plot(x, c.to_line(x), label=str(c))

    ax.legend(loc="upper right", borderaxespad=0.0, prop={"size": 14})
    ax.set_xlabel(r"$x_1$")
    ax.set_ylabel(r"$x_2$")

    return ax

Usage

ax = make_plot(-1, 9, [
        Constraint(5, "<=", -2, 17),
        Constraint(2, "<=", -3, 10),
        Constraint(0, "<=", 1, 0),
        Constraint(1, ">=", 0, 0),
    ],
)

ax.set_ylim(-1, 6)
ax

Output

· · ·