{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Using LHS Value to Debug An Infeasible Problem\n", "\n", "This notebook aims to show how to use the left-hand side (LHS) value (`Constraint.e`) to debug an infeasible problem.\n", "\n", "The LHS value is the value of the left-hand side of the equation. It is useful to check which constraints are violated." ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "execution": { "iopub.execute_input": "2026-05-02T08:23:36.127831Z", "iopub.status.busy": "2026-05-02T08:23:36.127295Z", "iopub.status.idle": "2026-05-02T08:23:37.212098Z", "shell.execute_reply": "2026-05-02T08:23:37.211771Z" } }, "outputs": [], "source": [ "import ams" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "execution": { "iopub.execute_input": "2026-05-02T08:23:37.213398Z", "iopub.status.busy": "2026-05-02T08:23:37.213284Z", "iopub.status.idle": "2026-05-02T08:23:37.215466Z", "shell.execute_reply": "2026-05-02T08:23:37.215206Z" } }, "outputs": [], "source": [ "ams.config_logger(stream_level=20)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "First, let's solve a DCOPF." ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "execution": { "iopub.execute_input": "2026-05-02T08:23:37.216695Z", "iopub.status.busy": "2026-05-02T08:23:37.216620Z", "iopub.status.idle": "2026-05-02T08:23:37.325590Z", "shell.execute_reply": "2026-05-02T08:23:37.325324Z" } }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "Working directory: \"/Users/jinningwang/work/ams/examples/demonstration\"\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "> Loaded config from file \"/Users/jinningwang/.ams/ams.rc\"\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Parsing input file \"/Users/jinningwang/work/ams/ams/cases/matpower/case14.m\"...\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "CASE14 Power flow data for IEEE 14 bus test case.\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Input file parsed in 0.0033 seconds.\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Zero Line parameters detected, adjusted to default values: rate_a, rate_b, rate_c.\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "System set up in 0.0017 seconds.\n" ] } ], "source": [ "sp = ams.load(ams.get_case('matpower/case14.m'),\n", " no_output=True)" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "execution": { "iopub.execute_input": "2026-05-02T08:23:37.326675Z", "iopub.status.busy": "2026-05-02T08:23:37.326577Z", "iopub.status.idle": "2026-05-02T08:23:37.377477Z", "shell.execute_reply": "2026-05-02T08:23:37.377254Z" } }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "Entering data check for \n" ] }, { "name": "stderr", "output_type": "stream", "text": [ " -> Data check passed\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Building system matrices\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ " formulation: codegen=15/15\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Parsing OModel for \n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Evaluating OModel for \n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Finalizing OModel for \n" ] }, { "name": "stderr", "output_type": "stream", "text": [ " initialized in 0.0403 seconds.\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ " solved as optimal in 0.0070 seconds, converged in 11 iterations with CLARABEL.\n" ] }, { "data": { "text/plain": [ "True" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "sp.DCOPF.run(solver='CLARABEL')" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "execution": { "iopub.execute_input": "2026-05-02T08:23:37.378559Z", "iopub.status.busy": "2026-05-02T08:23:37.378468Z", "iopub.status.idle": "2026-05-02T08:23:37.380394Z", "shell.execute_reply": "2026-05-02T08:23:37.380191Z" } }, "outputs": [ { "data": { "text/plain": [ "array([2.21, 0.38, 0. , 0. , 0. ])" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "sp.DCOPF.pg.v.round(2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Then, we can lower down the generator maximum output to create an infeasible problem." ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "execution": { "iopub.execute_input": "2026-05-02T08:23:37.381411Z", "iopub.status.busy": "2026-05-02T08:23:37.381343Z", "iopub.status.idle": "2026-05-02T08:23:37.383265Z", "shell.execute_reply": "2026-05-02T08:23:37.383052Z" } }, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "sp.StaticGen.set(src='pmax', attr='v', idx=[1, 2, 3, 4, 5], value=0.5)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Remember to update the corresponding parameters in the routine." ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "execution": { "iopub.execute_input": "2026-05-02T08:23:37.384372Z", "iopub.status.busy": "2026-05-02T08:23:37.384281Z", "iopub.status.idle": "2026-05-02T08:23:37.386504Z", "shell.execute_reply": "2026-05-02T08:23:37.386277Z" } }, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "sp.DCOPF.update('pmax')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now, we can see that the problem is infeasible." ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "execution": { "iopub.execute_input": "2026-05-02T08:23:37.387611Z", "iopub.status.busy": "2026-05-02T08:23:37.387521Z", "iopub.status.idle": "2026-05-02T08:23:37.391506Z", "shell.execute_reply": "2026-05-02T08:23:37.391287Z" } }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "DCOPF failed as infeasible in 9 iterations with CLARABEL!\n" ] }, { "data": { "text/plain": [ "False" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "sp.DCOPF.run(solver='CLARABEL')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Then, we can turn off some constraints and sovle it again." ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "execution": { "iopub.execute_input": "2026-05-02T08:23:37.392512Z", "iopub.status.busy": "2026-05-02T08:23:37.392430Z", "iopub.status.idle": "2026-05-02T08:23:37.394501Z", "shell.execute_reply": "2026-05-02T08:23:37.394312Z" } }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "Turn off constraints: pglb, pgub\n" ] }, { "data": { "text/plain": [ "True" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "sp.DCOPF.disable(['pglb', 'pgub'])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that since there are some constraints touched, the OModel will be updated automatically." ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "execution": { "iopub.execute_input": "2026-05-02T08:23:37.395426Z", "iopub.status.busy": "2026-05-02T08:23:37.395357Z", "iopub.status.idle": "2026-05-02T08:23:37.404858Z", "shell.execute_reply": "2026-05-02T08:23:37.404639Z" } }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "Entering data check for \n" ] }, { "name": "stderr", "output_type": "stream", "text": [ " -> Data check passed\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Disabled constraints: pglb, pgub\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ " formulation: codegen=15/15\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Finalizing OModel for \n" ] }, { "name": "stderr", "output_type": "stream", "text": [ " initialized in 0.0022 seconds.\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ " solved as optimal in 0.0048 seconds, converged in 9 iterations with CLARABEL.\n" ] }, { "data": { "text/plain": [ "True" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "sp.DCOPF.run(solver='CLARABEL')" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "execution": { "iopub.execute_input": "2026-05-02T08:23:37.405993Z", "iopub.status.busy": "2026-05-02T08:23:37.405921Z", "iopub.status.idle": "2026-05-02T08:23:37.407873Z", "shell.execute_reply": "2026-05-02T08:23:37.407673Z" } }, "outputs": [ { "data": { "text/plain": [ "array([ 2.31448356, 0.39836206, -0.04094854, -0.04094854, -0.04094854])" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "sp.DCOPF.pg.v" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Why `Constraint.e` is always slack-from-zero\n", "\n", "Every routine constraint is authored in the **LHS-zero form** —\n", "all terms on the left, `<= 0` / `== 0` / `>= 0` on the right\n", "(post-v1.2.3; see the `is_eq` retirement migration). CVXPY then\n", "canonicalizes every inequality internally to `lhs - rhs <= 0`\n", "**regardless of operator direction**:\n", "\n", "```python\n", "import cvxpy as cp\n", "a = cp.Variable(3, name='a'); b = cp.Variable(3, name='b')\n", "(a <= b)._expr # → a - b\n", "(b >= a)._expr # → a - b (same! __ge__ swaps operands)\n", "```\n", "\n", "`Constraint.e` (and `Constraint.v`) returns this canonical\n", "`lhs - rhs` value — i.e. the slack from zero. So:\n", "\n", "* **negative** = constraint is respected, with that much margin\n", "* **positive** = constraint is violated by that amount\n", "* **zero** = constraint is exactly tight\n", "\n", "The diagnostic is uniform across every constraint in every\n", "routine. For an infeasible problem, scanning each constraint's\n", "`.e` for positive entries pinpoints exactly which constraints\n", "fail and by how much. CVXPY rejects strict `<` / `>` (raises\n", "`NotImplementedError`), so only `<=`, `>=`, and `==` reach the\n", "solver — the canonicalization is total." ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "execution": { "iopub.execute_input": "2026-05-02T08:23:37.408884Z", "iopub.status.busy": "2026-05-02T08:23:37.408821Z", "iopub.status.idle": "2026-05-02T08:23:37.411148Z", "shell.execute_reply": "2026-05-02T08:23:37.410946Z" } }, "outputs": [ { "data": { "text/plain": [ "array([ 1.81448356, -0.10163794, -0.54094854, -0.54094854, -0.54094854])" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "sp.DCOPF.pgub.e" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "execution": { "iopub.execute_input": "2026-05-02T08:23:37.412106Z", "iopub.status.busy": "2026-05-02T08:23:37.412037Z", "iopub.status.idle": "2026-05-02T08:23:37.413937Z", "shell.execute_reply": "2026-05-02T08:23:37.413769Z" } }, "outputs": [ { "data": { "text/plain": [ "array([-2.31448356, -0.39836206, 0.04094854, 0.04094854, 0.04094854])" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "sp.DCOPF.pglb.e" ] } ], "metadata": { "kernelspec": { "display_name": "ams", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.0" } }, "nbformat": 4, "nbformat_minor": 2 }