Skip to main content

Solve a Problem

Once you have defined your optimization problem by creating components and building a model, you can solve it using the Optimizer class.

Creating an Optimizer

The optimizer is initialized with a model and, optionally, an initial guess and bounds:

import amigo as am

# Simplest form: uses default values from add_input()
opt = am.Optimizer(model)

# With a custom initial guess
x = model.create_vector()
x["comp.x"] = 5.0
opt = am.Optimizer(model, x)

# With custom initial guess and bounds
lower = model.create_vector()
upper = model.create_vector()
lower["comp.x"] = -10.0
upper["comp.x"] = 10.0
opt = am.Optimizer(model, x, lower=lower, upper=upper)

Choosing a Linear Solver

The optimizer uses a direct linear solver for the KKT system. If Intel MKL is available, the PARDISO solver is used automatically. Otherwise, it falls back to SciPy's sparse LU factorization.

# Explicit solver selection (optional)
from amigo.optimizer import PardisoSolver, DirectScipySolver

problem = model.get_problem()
solver = PardisoSolver(problem)
opt = am.Optimizer(model, x, solver=solver)

Available solvers:

SolverDescriptionRequirements
PardisoSolverIntel MKL PARDISO (LDL^T with inertia detection)pypardiso
DirectScipySolverSciPy sparse LU factorizationNone (built-in)
DirectCudaSolverNVIDIA cuDSS on GPUCUDA build
DirectPetscSolverPETSc distributed solverpetsc4py

Running Optimization

opt.optimize()

This runs the interior-point optimization loop and updates the model with the optimal solution.

With Options

Pass a dictionary of options to customize the solver behavior:

data = opt.optimize({
"max_iterations": 500,
"convergence_tolerance": 1e-8,
"barrier_strategy": "heuristic",
})

The optimize() method returns a dictionary containing convergence information and iteration history:

data = opt.optimize()

print(f"Converged: {data['converged']}")
print(f"Iterations: {len(data['iterations'])}")
print(f"Final residual: {data['iterations'][-1]['residual']:.6e}")

Optimizer Options Reference

Core Options

OptionDefaultDescription
max_iterations100Maximum number of optimization iterations
convergence_tolerance1e-8KKT residual tolerance for convergence
initial_barrier_param1.0Initial value of the barrier parameter μ\mu
fraction_to_boundary0.95Base fraction-to-boundary parameter τ\tau

Barrier Strategy

OptionDefaultDescription
barrier_strategy"heuristic"Strategy for updating μ\mu: "heuristic", "monotone", or "quality_function"
monotone_barrier_fraction0.1Factor for barrier reduction in monotone mode
heuristic_barrier_gamma0.1γ\gamma parameter for heuristic (LOQO) barrier
heuristic_barrier_r0.95rr parameter for heuristic barrier
progress_based_barrierTrueOnly reduce μ\mu when making sufficient progress
barrier_progress_tol10.0Tolerance factor for barrier subproblem progress
verbose_barrierFalsePrint barrier parameter updates

Heuristic (LOQO) is the default and works well for most problems. It reduces the barrier parameter based on the current complementarity and a nonlinear formula.

Monotone uses a fixed μ\mu until the subproblem is solved to sufficient accuracy, then reduces μ\mu by the monotone_barrier_fraction factor. This is more conservative but can be more robust for difficult problems.

Quality function implements the Nocedal-Wachter-Waltz (2009) algorithm, which adaptively chooses μ\mu by solving a one-dimensional optimization problem. It provides safeguarded nonmonotone barrier reduction and is recommended for challenging nonlinear problems.

Quality Function Options

These options only apply when barrier_strategy is set to "quality_function":

OptionDefaultDescription
quality_function_sigma_max1000.0Maximum centering parameter
quality_function_golden_iters12Golden section search iterations
quality_function_kappa_free0.9999Nonmonotone acceptance threshold
quality_function_l_max5Window size for nonmonotone history
quality_function_norm_scalingTrueScale KKT components by element count
OptionDefaultDescription
use_armijo_line_searchTrueUse Armijo sufficient decrease condition
armijo_constant1e-4Armijo constant cc for sufficient decrease
max_line_search_iterations10Maximum backtracking steps
backtracking_factor0.5Step length reduction factor per backtrack
second_order_correctionTrueApply second-order correction on rejected steps

Step Control

OptionDefaultDescription
adaptive_tauTrueUse adaptive fraction-to-boundary rule: τ=max(τmin,1μ)\tau = \max(\tau_\text{min}, 1 - \mu)
tau_min0.99Minimum fraction-to-boundary value
equal_primal_dual_stepFalseForce equal step lengths for primal and dual
check_update_stepFalseVerify step direction validity

Regularization

OptionDefaultDescription
regularization_eps_x1e-8Primal regularization ϵx\epsilon_x
regularization_eps_z1e-8Dual regularization ϵz\epsilon_z
adaptive_regularizationTrueIncrease regularization on factorization failure
max_regularization1e-2Maximum regularization value
regularization_increase_factor10.0Factor to increase regularization

Convexification

OptionDefaultDescription
block_psd_convexificationFalseUse block PSD projection for Hessian convexification
block_psd_hub_threshold50Hub degree threshold for block detection
curvature_probe_convexificationFalseUse three-layer inertia regularization
max_inertia_corrections8Maximum refactorizations for inertia correction

Block PSD convexification performs eigenvalue decomposition on blocks of the Hessian matrix and clips negative eigenvalues. This is recommended for trajectory optimization problems where the Hessian has a block-diagonal structure.

Curvature probe convexification uses a three-layer approach: (1) per-variable regularization from the Hessian diagonal, (2) targeted inertia correction on nonconvex primal variables, and (3) global inertia correction as a fallback.

tip

For nonlinear trajectory optimization, block_psd_convexification: True with barrier_strategy: "quality_function" is a strong combination.

Multiplier Initialization

OptionDefaultDescription
init_least_squares_multipliersTrueInitialize multipliers via least-squares solve
init_affine_step_multipliersFalseInitialize multipliers via affine scaling step

Acceptable Convergence

OptionDefaultDescription
acceptable_tolNoneAcceptable residual threshold. Default: 100x convergence_tolerance
acceptable_iter10Number of stagnating iterations before accepting

When the optimizer detects that the residual is no longer decreasing (stagnation), it can accept the current point if the residual is below acceptable_tol. This prevents the optimizer from iterating indefinitely on problems with numerical noise near the solution.

Advanced Options

OptionDefaultDescription
gamma_penalty1e3Penalty parameter for constraint violations
record_components[]List of component names to record at each iteration
continuation_controlNoneContinuation component control parameter
zero_hessian_variables[]Variable names with zero Hessian (linear variables)
regularization_eps_x_zero_hessian1.0Strong regularization for zero-Hessian variables
nonconvex_constraints[]Constraint names for selective dual regularization
max_consecutive_rejections5Rejected steps before barrier increase
barrier_increase_factor5.0Factor to increase barrier when stuck
convex_eps_z_coeff1.0Coefficient for dual regularization: ϵz=Czμ\epsilon_z = C_z \cdot \mu

Accessing Results

After optimization, extract the solution from the model:

# Get optimal values by component and variable name
x_opt = model.get_input("comp.x")
f_opt = model.get_objective("comp.f")
constraint = model.get_constraint("comp.g")

print(f"Optimal x: {x_opt}")
print(f"Objective: {f_opt}")
print(f"Constraint: {constraint}")

For vector variables across multiple instances, use the initial guess vector (which is updated in-place after optimization):

import numpy as np

# The x vector is updated in-place after opt.optimize()
q_solution = np.array(x["dynamics.q[:, 0]"])
u_solution = np.array(x["dynamics.u[:, 0]"])
note

Wrap results in np.array() when you need to perform NumPy operations on them (fancy indexing, broadcasting, etc.).

Iteration Output

Set print_level or inspect the returned data to monitor convergence:

data = opt.optimize({"max_iterations": 200})

# Iteration history
for it in data["iterations"]:
print(f"Iter {it['iteration']:3d} "
f"residual={it['residual']:.4e} "
f"mu={it['barrier_param']:.4e} "
f"alpha_x={it['alpha_x']:.4f}")

The iteration data dictionary contains:

  • residual: KKT residual norm
  • barrier_param: Current barrier parameter μ\mu
  • alpha_x: Primal step length
  • alpha_z: Dual step length
  • objective: Current objective value
  • complementarity: Complementarity measure

Complete Workflow

import amigo as am
import numpy as np

# 1. Define component
class MyComponent(am.Component):
def __init__(self):
super().__init__()
self.add_input("x", value=0.0, lower=-10.0, upper=10.0)
self.add_objective("f")
self.add_constraint("g", upper=0.0)

def compute(self):
x = self.inputs["x"]
self.objective["f"] = x * x
self.constraints["g"] = x - 5.0

# 2. Build model
model = am.Model("optimization")
model.add_component("comp", 1, MyComponent())
model.build_module()
model.initialize()

# 3. Set initial guess and bounds
x = model.create_vector()
x["comp.x"] = 3.0

# 4. Solve
opt = am.Optimizer(model, x)
data = opt.optimize({
"convergence_tolerance": 1e-10,
"max_iterations": 200,
})

# 5. Extract solution
x_opt = model.get_input("comp.x")
f_opt = model.get_objective("comp.f")

print(f"Converged: {data['converged']}")
print(f"Solution: x = {x_opt:.6f}, f(x) = {f_opt:.6f}")

See the Optimizer API documentation for the complete class reference.