Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/FabianeloV/Metodo-simplex/llms.txt

Use this file to discover all available pages before exploring further.

The backend is a single FastAPI application defined in backend/app/main.py. It mounts seven routers under a shared /api/v1 prefix, uses Pydantic v2 for all request and response validation, and delegates every computation to a dedicated engine class in app/core/. NumPy handles the tableau arithmetic; SymPy handles symbolic differentiation for the polynomial and expression-based solvers; and python-dotenv loads environment-specific configuration at startup.

Application setup

The FastAPI instance is created with explicit metadata and interactive documentation URLs that are served on every environment.
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI(
    title="API de Optimización",
    description="API REST para resolver Programación Lineal (Simplex) y "
                "Programación Entera Binaria (Branch & Bound).",
    version="1.0.0",
    docs_url="/docs",
    redoc_url="/redoc",
)
The interactive Swagger UI is available at http://localhost:8000/docs and ReDoc at http://localhost:8000/redoc while the development server is running. Both are auto-generated from the Pydantic schemas with no extra configuration.

CORS configuration

The middleware explicitly allows the Vite dev server, a local Create-React-App port, and the production GitHub Pages deployment.
app.add_middleware(
    CORSMiddleware,
    allow_origins=[
        "http://localhost:5173",
        "http://localhost:3000",
        "https://fabianelov.github.io",
    ],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)
When deploying to a different production domain, add its origin to allow_origins or read the list from an environment variable via python-dotenv.

Router structure

All seven routers are registered in main.py with the same /api/v1 prefix. Each router lives in its own file under app/api/routes/ and exposes at least a POST /solve endpoint and a GET /health health-check endpoint.
app.include_router(simplex_router,   prefix="/api/v1")
app.include_router(binary_router,    prefix="/api/v1")
app.include_router(integer_router,   prefix="/api/v1")
app.include_router(bisection_router, prefix="/api/v1")
app.include_router(newton_router,    prefix="/api/v1")
app.include_router(gradient_router,  prefix="/api/v1")
app.include_router(kkt_router,       prefix="/api/v1")
Router fileMount pathSolve endpoint
routes/simplex.py/api/v1POST /api/v1/simplex/solve
routes/binary.py/api/v1POST /api/v1/binary/solve
routes/integer.py/api/v1POST /api/v1/integer/solve
routes/bisection.py/api/v1POST /api/v1/bisection/solve
routes/newton.py/api/v1POST /api/v1/newton/solve
routes/gradient.py/api/v1POST /api/v1/gradient/solve
routes/kkt.py/api/v1POST /api/v1/kkt/solve
A root GET / endpoint returns a simple liveness message and the docs path:
@app.get("/", tags=["root"])
def root() -> dict:
    return {"message": "Optimización Simplex API", "docs": "/docs"}

Engine class pattern

Every solver follows the same pattern: a class in app/core/ accepts all problem parameters in its constructor and exposes a single .solve() method that returns a result dataclass. This isolates computation from HTTP concerns completely. The SimplexEngine is the reference implementation:
class SimplexEngine:
    """Solves a linear programme using the Big-M method for mixed constraints."""

    BIG_M = 1e6

    def __init__(
        self,
        objective: list[float],
        goal: Literal["max", "min"],
        constraint_coeffs: list[list[float]],
        inequalities: list[Literal["<=", ">=", "="]],
        rhs_values: list[float],
    ) -> None:
        ...
        self._build_tableau()

    def solve(self) -> SimplexResult:
        ...
The engine converts minimization to maximization internally (by negating the objective), normalizes rows with a negative RHS, and then builds an augmented tableau with slack, surplus, and artificial variables via the Big-M penalty method. The pivot loop runs for at most MAX_ITER = 200 steps, detecting unbounded problems (no valid pivot row) and infeasible problems (artificial variables remaining in the basis at a non-zero value) before extracting the final solution.
@dataclass
class SimplexResult:
    status: Literal["optimal", "unbounded", "infeasible"]
    objective_value: float = 0.0
    variables: dict[str, float] = field(default_factory=dict)
    iterations: int = 0
    tableau_headers: list[str] = field(default_factory=list)
    tableau_rows: list[dict] = field(default_factory=list)
    message: str = ""
    iteration_tableaux: list[dict] = field(default_factory=list)
Per-iteration tableau snapshots are stored in iteration_tableaux so the frontend can replay each pivot step in the TableauTable molecule component.

Pydantic v2 schemas

All schemas live in app/models/schemas.py and use Pydantic v2 syntax (field_validator, Field with min_length/max_length). Cross-field validation — such as ensuring every constraint has the same number of coefficients as the objective vector — is performed inside @field_validator methods before the engine is ever instantiated.

Core LP schemas

class Constraint(BaseModel):
    coefficients: list[float] = Field(..., min_length=2, max_length=5)
    inequality: Literal["<=", ">=", "="]
    rhs: float

    @field_validator("coefficients")
    @classmethod
    def check_length(cls, v: list[float]) -> list[float]:
        if not (2 <= len(v) <= 5):
            raise ValueError("Los coeficientes deben estar entre 2 y 5 elementos")
        return v


class SimplexRequest(BaseModel):
    objective: list[float] = Field(..., min_length=2, max_length=5)
    goal: Literal["max", "min"]
    constraints: list[Constraint] = Field(..., min_length=1)

    @field_validator("constraints")
    @classmethod
    def same_size(cls, v: list[Constraint], info) -> list[Constraint]:
        if "objective" in (info.data or {}):
            n = len(info.data["objective"])
            for c in v:
                if len(c.coefficients) != n:
                    raise ValueError(
                        f"Las restricciones deben tener {n} coeficientes "
                        "para coincidir con la función objetivo"
                    )
        return v

Simplex response schema

class TableauRow(BaseModel):
    basic_variable: str
    values: list[float]

class IterationTableau(BaseModel):
    iteration: int
    entering: str | None = None
    leaving: str | None = None
    tableau_headers: list[str]
    tableau_rows: list[TableauRow]

class SimplexResponse(BaseModel):
    status: Literal["optimal", "unbounded", "infeasible"]
    objective_value: float | None = None
    variables: dict[str, float] | None = None
    iterations: int | None = None
    tableau_headers: list[str] | None = None
    tableau_rows: list[TableauRow] | None = None
    message: str | None = None
    graphical: GraphicalData | None = None
    iteration_tableaux: list[IterationTableau] | None = None

Binary / Integer B&B schemas

The Branch & Bound solvers return tree node data so the frontend can render the B&B exploration tree.
class BBNode(BaseModel):
    node_id: int
    parent_id: int | None = None
    depth: int
    fixed_vars: dict[str, int]
    lp_value: float | None = None
    lp_vars: dict[str, float] | None = None
    status: str
    branched_on: str | None = None
    edge_label: str | None = None

class BinaryResponse(BaseModel):
    status: Literal["optimal", "infeasible", "limit"]
    objective_value: float | None = None
    variables: dict[str, int] | None = None
    nodes_explored: int
    nodes: list[BBNode]
    message: str

Gradient and graphical multi-variable schemas

class GradientRequest(BaseModel):
    expression: str
    variables: list[str] = Field(..., min_length=2, max_length=3)
    x0: list[float] = Field(..., min_length=2, max_length=3)
    goal: Literal["max", "min"]
    step_size: float = Field(default=0.1, gt=0)
    tolerance: float = Field(default=1e-6, gt=0, le=1.0)
    max_iterations: int = Field(default=100, ge=1, le=500)

class SurfaceData(BaseModel):
    x: list[float]
    y: list[float]
    z: list[list[float | None]]

API example

POST http://localhost:8000/api/v1/simplex/solve
Content-Type: application/json

{
  "objective":   [3, 2, 5],
  "goal":        "max",
  "constraints": [
    { "coefficients": [1, 0, 1], "inequality": "<=", "rhs": 430 },
    { "coefficients": [0, 1, 1], "inequality": "<=", "rhs": 460 },
    { "coefficients": [1, 1, 0], "inequality": "<=", "rhs": 420 }
  ]
}
{
  "status":          "optimal",
  "objective_value": 1350.0,
  "variables":       { "x1": 0.0, "x2": 100.0, "x3": 230.0 },
  "iterations":      3,
  "message":         "Se encontró una solución óptima después de 3 iteración(es)."
}

Dependencies

All backend dependencies are pinned to exact versions in requirements.txt.
fastapi==0.115.0
uvicorn[standard]==0.30.6
pydantic==2.9.2
numpy==2.1.1
sympy==1.13.3
python-dotenv==1.0.1
httpx==0.27.2
PackageRole
fastapiASGI web framework, automatic OpenAPI generation
uvicorn[standard]ASGI server with WebSocket and HTTP/2 support
pydanticRequest/response validation and serialization (v2)
numpyTableau arithmetic in all LP and B&B engines
sympySymbolic differentiation for bisection, Newton, gradient, and KKT solvers
python-dotenvLoads .env for backend environment settings such as allowed origins and server configuration
httpxAsync HTTP client available for inter-service calls or tests

Starting the development server

cd backend
python -m venv .venv
source .venv/bin/activate        # Windows: .venv\Scripts\activate
pip install -r requirements.txt
cp .env.example .env
uvicorn app.main:app --reload --port 8000
Health-check the running server:
curl http://localhost:8000/api/v1/simplex/health

Build docs developers (and LLMs) love