Performance Guide

Best practices for building efficient large-scale optimization models
Published

May 28, 2026

1 Introduction

Optyx is designed to handle large optimization problems efficiently. With v1.3.0, Optyx has made massive strides in automatic acceleration for typical high-dimensional optimization problems.

By the end of this tutorial, you’ll understand: - How vectorized gradients achieve near-parity CQP cold starts (≤2x overhead vs SciPy) - The new scale of Sparse LP (100,000+ variables) - Native structure flattening for deep trees - Power-user utilities for edge cases


2 The Vectorized Fast Paths

In older versions of Optyx (or when writing models with raw for loops), expressions are built piece-by-piece, resulting in large in-memory tree structures. While Optyx has an iterative engine capable of evaluating arbitrarily deep trees, vectorizing your model remains the most important step for getting massive speedups.

Optyx looks for common expressions and triggers Vectorized Gradients: O(1) mathematical formulation of gradients that compile perfectly to fast numpy code.

2.1 Equivalence Table & Benchmarks

Loop Pattern Vectorized Equivalent Cold Overhead vs SciPy (n=1000)
sum(c[i]*x[i] for i ...) c @ x ~1.1x (LP at parity)
sum(x[i]**2 for i ...) x.dot(x) ~1.8x (CQP near-parity)
double loop with Q x.dot(Q @ x) ~2x (quadratic form)

When Optyx encounters objects like DotProduct or QuadraticForm, the Jacobian compilation bypasses Python iteration entirely and applies pre-compiled numerical patterns like nabla: 2x or nabla: Q+Q.T.

To take advantage: 1. Always declare grouped variables as VectorVariable or VariableDict 2. Apply operations over the entire vector at once (x.sum(), c @ x)


3 Scale Out: Sparse Constraints

If you scale your linear programming models up to 100,000+ continuous variables, constructing 10_000 separate subject_to() constraint expressions incurs memory and CPU overhead.

For industrial-scale modeling, use direct matrix constraints with subject_to(...) and push scipy.sparse representations straight into the solver backend.

import numpy as np
from scipy import sparse as sp
from optyx import Problem, VectorVariable, as_matrix

n = 10_000
m = 1_000
density = 0.01

# A large, sparse constraint matrix
A_sparse = as_matrix(
    sp.random(m, n, density=density, format='csr', random_state=42),
    storage='sparse',
)
b = np.random.rand(m)
c = np.random.rand(n)

x = VectorVariable('x', n, lb=0, ub=1)

prob = Problem(name='sparse_lp')
prob.maximize(c @ x)

# Passes directly to HiGHS
prob.subject_to(A_sparse @ x <= b)

print(f"Variables: {n:,}, Constraints: {m:,}, Non-zeros: {A_sparse.data.nnz:,}")
# For 100,000+ variables, the same pattern applies — just scale up n and m
Variables: 10,000, Constraints: 1,000, Non-zeros: 100,000

as_matrix() also accepts storage='auto' and storage='dense'. The default storage='auto' keeps sparse inputs sparse and can convert large, mostly-zero dense arrays into CSR storage when you want the matrix-block path without manually building a SciPy sparse matrix first.


4 Automatic Flattening for Loops

If you absolutely must write for loops to construct expressions, Optyx v1.3.0 handles the structural overhead smoothly.

Previously, obj = x[0] + x[1] + ... + x[100] created 100 nested operations. The compiler now detects sequence operations and collapses them into O(1) structures (NarySum and NaryProduct), keeping memory layout entirely flat.

from optyx import VectorVariable
from optyx.core.autodiff import _estimate_tree_depth

# Building an expression in a loop
x = VectorVariable("x", 100)
obj = x[0] ** 2
for i in range(1, 100):
    obj = obj + x[i] ** 2

# Flattening keeps depth bounded regardless of sequential assignment length
depth = _estimate_tree_depth(obj)
print(f"Expression tree depth: {depth}") # Extremely small now!
Expression tree depth: 100

5 Power User: Recursion Limit Override

In rare cases where you construct complex, asymmetric deep trees, the framework automatically uses an iterative fallback. If needed, you might want to temporarily increase Python’s recursion limit:

from optyx import increased_recursion_limit

# Context manager restores the original limit when done
with increased_recursion_limit(5000):
    # Code that might need deep recursion
    pass
WarningUse with Caution

Very high limits can cause stack overflow crashes. The automatic iterative flatten logic usually prevents this issue.


6 Next Steps