Eigenvalue Optimization

Eigenvalue Optimization taken from: GRANSO demo example 4.

Problem Description

We have \(M=A+BXC\), where the matrices \(A\in R^{N,N},B\in R^{N,M}\) and \(C\in R^{P,N}\) are given, \(X\in R^{M,P}\) is the matrix form optimization variable.

We have the nonconvex, nonsmooth, and constrained optimization problem

\[\min_{X}\max| \mathrm{Im} (\Lambda(A+BXC))|,\]
\[\text{s.t. }\alpha(A+BXC)+\xi \leq 0,\]

where \(\mathrm{Im}(\cdot)\) is the imaginary part of complex number, \(\xi\) is the stability margin, and \(\Lambda(\cdot)\) is the spectrum of a square matrix \(\cdot\), and \(\alpha(\cdot)\) is the spectral abscissa of a square matrix, i.e., the maximum real part of its eigenvalues.

Modules Importing

Import all necessary modules and add PyGRANSO src folder to system path.

[1]:
import time
import torch
from pygranso.pygranso import pygranso
from pygranso.pygransoStruct import pygransoStruct
import scipy.io
from torch import linalg as LA

Data Initialization

Specify torch device, and read the data from a provided file.

Use GPU for this problem. If no cuda device available, please set device = torch.device(‘cpu’)

[2]:
device = torch.device('cuda')

file = "/home/buyun/Documents/GitHub/PyGRANSO/examples/data/spec_radius_opt_data.mat"
mat = scipy.io.loadmat(file)
mat_struct = mat['sys']
mat_struct = mat_struct[0,0]
# All the user-provided data (vector/matrix/tensor) must be in torch tensor format.
# As PyTorch tensor is single precision by default, one must explicitly set `dtype=torch.double`.
# Also, please make sure the device of provided torch tensor is the same as opts.torch_device.
A = torch.from_numpy(mat_struct['A']).to(device=device, dtype=torch.double)
B = torch.from_numpy(mat_struct['B']).to(device=device, dtype=torch.double)
C = torch.from_numpy(mat_struct['C']).to(device=device, dtype=torch.double)
p = B.shape[1]
m = C.shape[0]
stability_margin = 1

Function Set-Up

Encode the optimization variables, and objective and constraint functions.

Note: please strictly follow the format of comb_fn, which will be used in the PyGRANSO main algortihm.

[3]:
# variables and corresponding dimensions.
var_in = {"X": [p,m] }

def user_fn(X_struct,A,B,C,stability_margin):
    # user defined variable, matirx form. torch tensor
    X = X_struct.X

    # objective function
    M           = A + B@X@C
    [D,_]       = LA.eig(M)
    f = torch.max(D.imag)

    # inequality constraint, matrix form
    ci = pygransoStruct()
    ci.c1 = torch.max(D.real) + stability_margin

    # equality constraint
    ce = None

    return [f,ci,ce]

comb_fn = lambda X_struct : user_fn(X_struct,A,B,C,stability_margin)

User Options

Specify user-defined options for PyGRANSO

[4]:
opts = pygransoStruct()
opts.torch_device = device
opts.maxit = 200
opts.x0 = torch.zeros(p*m,1).to(device=device, dtype=torch.double)
# print for every 10 iterations. default: 1
opts.print_frequency = 10

Main Algorithm

[5]:
start = time.time()
soln = pygranso(var_spec = var_in,combined_fn = comb_fn,user_opts = opts)
end = time.time()
print("Total Wall Time: {}s".format(end - start))


╔═════ QP SOLVER NOTICE ════════════════════════════════════════════════════════════════════════╗
║  PyGRANSO requires a quadratic program (QP) solver that has a quadprog-compatible interface,  ║
║  the default is osqp. Users may provide their own wrapper for the QP solver.                  ║
║  To disable this notice, set opts.quadprog_info_msg = False                                   ║
╚═══════════════════════════════════════════════════════════════════════════════════════════════╝
═════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗
PyGRANSO: A PyTorch-enabled port of GRANSO with auto-differentiation                                             ║
Version 1.0.0                                                                                                    ║
Licensed under the AGPLv3, Copyright (C) 2021 Tim Mitchell and Buyun Liang                                       ║
═════════════════════════════════════════════════════════════════════════════════════════════════════════════════╣
Problem specifications:                                                                                          ║
 # of variables                     :   200                                                                      ║
 # of inequality constraints        :     1                                                                      ║
 # of equality constraints          :     0                                                                      ║
═════╦═══════════════════════════╦════════════════╦═════════════════╦═══════════════════════╦════════════════════╣
     ║ <--- Penalty Function --> ║                ║ Total Violation ║ <--- Line Search ---> ║ <- Stationarity -> ║
Iter ║    Mu    │      Value     ║    Objective   ║   Ineq   │  Eq  ║ SD │ Evals │     t    ║ Grads │    Value   ║
═════╬═══════════════════════════╬════════════════╬═════════════════╬═══════════════════════╬════════════════════╣
   0 ║ 1.000000 │  16.2063030241 ║  13.7635444107 ║ 2.442759 │   -  ║ -  │     1 │ 0.000000 ║     1 │ 28.28938   ║
  10 ║ 1.000000 │  14.3591621233 ║  12.9268286638 ║ 1.432333 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 0.035476   ║
  20 ║ 1.000000 │  13.7108148212 ║  12.6560766192 ║ 1.054738 │   -  ║ S  │     2 │ 0.500000 ║     1 │ 0.039916   ║
  30 ║ 1.000000 │  12.9781024806 ║  12.2692651748 ║ 0.708837 │   -  ║ S  │     5 │ 0.062500 ║     1 │ 0.044753   ║
  40 ║ 1.000000 │  12.7159660797 ║  12.0997226021 ║ 0.616243 │   -  ║ S  │     4 │ 0.125000 ║     1 │ 0.024262   ║
  50 ║ 1.000000 │  12.4124519256 ║  11.9469063299 ║ 0.465546 │   -  ║ S  │     4 │ 0.125000 ║     1 │ 0.075202   ║
  60 ║ 1.000000 │  12.2512294611 ║  11.8943509436 ║ 0.356879 │   -  ║ S  │     3 │ 0.250000 ║     1 │ 0.019409   ║
  70 ║ 1.000000 │  12.0659735242 ║  11.7780158864 ║ 0.287958 │   -  ║ S  │    10 │ 0.001953 ║     1 │ 0.070258   ║
  80 ║ 1.000000 │  11.8824770848 ║  11.7428078694 ║ 0.139669 │   -  ║ S  │     8 │ 0.007812 ║     1 │ 0.175003   ║
  90 ║ 1.000000 │  11.7399810978 ║  11.6639720989 ║ 0.076009 │   -  ║ S  │     9 │ 0.003906 ║     1 │ 0.053027   ║
 100 ║ 0.900000 │  10.5003036483 ║  11.6188807980 ║ 0.043311 │   -  ║ S  │     5 │ 0.062500 ║     1 │ 0.117694   ║
 110 ║ 0.900000 │  10.4423597970 ║  11.5640150059 ║ 0.034746 │   -  ║ S  │     4 │ 0.125000 ║     1 │ 0.013471   ║
 120 ║ 0.900000 │  10.3449935197 ║  11.4944372442 ║ 0.000000 │   -  ║ S  │    13 │ 2.44e-04 ║     2 │ 0.025507   ║
 130 ║ 0.282430 │  3.23466473336 ║  11.4529973517 ║ 0.000000 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 0.007158   ║
 140 ║ 0.282430 │  3.21863830760 ║  11.3958203830 ║ 1.22e-04 │   -  ║ S  │     5 │ 0.062500 ║     1 │ 0.128867   ║
 150 ║ 0.282430 │  3.20396111202 ║  11.3442848504 ║ 0.000000 │   -  ║ S  │     3 │ 0.250000 ║     1 │ 0.008419   ║
 160 ║ 0.135085 │  1.52510826840 ║  11.2899754166 ║ 0.000000 │   -  ║ S  │     3 │ 0.250000 ║     1 │ 0.004253   ║
 170 ║ 0.135085 │  1.52101010769 ║  11.2596378106 ║ 0.000000 │   -  ║ S  │     3 │ 0.250000 ║     1 │ 0.003956   ║
 180 ║ 0.135085 │  1.51543064130 ║  11.2162756983 ║ 2.78e-04 │   -  ║ S  │    10 │ 0.001953 ║     2 │ 0.003658   ║
 190 ║ 0.135085 │  1.51290817405 ║  11.1996613267 ║ 0.000000 │   -  ║ S  │     4 │ 0.125000 ║     1 │ 0.031055   ║
═════╬═══════════════════════════╬════════════════╬═════════════════╬═══════════════════════╬════════════════════╣
     ║ <--- Penalty Function --> ║                ║ Total Violation ║ <--- Line Search ---> ║ <- Stationarity -> ║
Iter ║    Mu    │      Value     ║    Objective   ║   Ineq   │  Eq  ║ SD │ Evals │     t    ║ Grads │    Value   ║
═════╬═══════════════════════════╬════════════════╬═════════════════╬═══════════════════════╬════════════════════╣
 200 ║ 0.135085 │  1.50914930836 ║  11.1699372446 ║ 2.56e-04 │   -  ║ S  │     4 │ 0.125000 ║     1 │ 0.007627   ║
═════╩═══════════════════════════╩════════════════╩═════════════════╩═══════════════════════╩════════════════════╣
Optimization results:                                                                                            ║
F = final iterate, B = Best (to tolerance), MF = Most Feasible                                                   ║
═════╦═══════════════════════════╦════════════════╦═════════════════╦═══════════════════════╦════════════════════╣
   F ║          │                ║  11.1699372446 ║ 2.56e-04 │   -  ║    │       │          ║       │            ║
   B ║          │                ║  11.1727028185 ║ 0.000000 │   -  ║    │       │          ║       │            ║
  MF ║          │                ║  11.1727028185 ║ 0.000000 │   -  ║    │       │          ║       │            ║
═════╩═══════════════════════════╩════════════════╩═════════════════╩═══════════════════════╩════════════════════╣
Iterations:              200                                                                                     ║
Function evaluations:    940                                                                                     ║
PyGRANSO termination code: 4 --- max iterations reached.                                                         ║
═════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝
Total Wall Time: 63.8348708152771s

LBFGS

(Optional) LBFGS and feasibility related options

[6]:
opts = pygransoStruct()
opts.torch_device = device
opts.maxit = 200
opts.x0 = torch.zeros(p*m,1).to(device=device, dtype=torch.double)
# print for every 10 iterations. default: 1
opts.print_frequency = 10

# Limited-memory mode is generally not recommended for nonsmooth
# problems, such as this one, but it can nonetheless enabled if
# desired/necessary.  opts.limited_mem_size == 0 is off, that is,
# limited-memory mode is disabled.
# Note that this example has 200 variables.
opts.limited_mem_size = 40

start = time.time()
soln = pygranso(var_spec = var_in,combined_fn = comb_fn,user_opts = opts)
end = time.time()
print("Total Wall Time: {}s".format(end - start))


╔═════ QP SOLVER NOTICE ════════════════════════════════════════════════════════════════════════╗
║  PyGRANSO requires a quadratic program (QP) solver that has a quadprog-compatible interface,  ║
║  the default is osqp. Users may provide their own wrapper for the QP solver.                  ║
║  To disable this notice, set opts.quadprog_info_msg = False                                   ║
╚═══════════════════════════════════════════════════════════════════════════════════════════════╝
═════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗
PyGRANSO: A PyTorch-enabled port of GRANSO with auto-differentiation                                             ║
Version 1.0.0                                                                                                    ║
Licensed under the AGPLv3, Copyright (C) 2021 Tim Mitchell and Buyun Liang                                       ║
═════════════════════════════════════════════════════════════════════════════════════════════════════════════════╣
Problem specifications:                                                                                          ║
 # of variables                     :   200                                                                      ║
 # of inequality constraints        :     1                                                                      ║
 # of equality constraints          :     0                                                                      ║
═════════════════════════════════════════════════════════════════════════════════════════════════════════════════╣
Limited-memory mode enabled with size = 40.                                                                     NOTE: limited-memory mode is generally NOT                                                                      recommended for nonsmooth problems.                                                                              ║
═════╦═══════════════════════════╦════════════════╦═════════════════╦═══════════════════════╦════════════════════╣
     ║ <--- Penalty Function --> ║                ║ Total Violation ║ <--- Line Search ---> ║ <- Stationarity -> ║
Iter ║    Mu    │      Value     ║    Objective   ║   Ineq   │  Eq  ║ SD │ Evals │     t    ║ Grads │    Value   ║
═════╬═══════════════════════════╬════════════════╬═════════════════╬═══════════════════════╬════════════════════╣
   0 ║ 1.000000 │  16.2063030241 ║  13.7635444107 ║ 2.442759 │   -  ║ -  │     1 │ 0.000000 ║     1 │ 28.28938   ║
  10 ║ 1.000000 │  14.3590982418 ║  12.9268454090 ║ 1.432253 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 0.035468   ║
  20 ║ 1.000000 │  13.7119743682 ║  12.6563067111 ║ 1.055668 │   -  ║ S  │     2 │ 0.500000 ║     1 │ 0.039956   ║
  30 ║ 1.000000 │  12.9752525099 ║  12.2671269517 ║ 0.708126 │   -  ║ S  │     5 │ 0.062500 ║     1 │ 0.045205   ║
  40 ║ 1.000000 │  12.6717866764 ║  12.0429605789 ║ 0.628826 │   -  ║ S  │     3 │ 0.250000 ║     1 │ 0.031327   ║
  50 ║ 1.000000 │  12.3403054991 ║  11.9386934418 ║ 0.401612 │   -  ║ S  │     8 │ 0.007812 ║     1 │ 0.117209   ║
  60 ║ 0.656100 │  8.08864961489 ║  11.8470983658 ║ 0.315768 │   -  ║ S  │     5 │ 0.062500 ║     1 │ 0.030719   ║
  70 ║ 0.656100 │  7.97717659130 ║  11.7882459184 ║ 0.242908 │   -  ║ S  │     4 │ 0.125000 ║     1 │ 0.084896   ║
  80 ║ 0.656100 │  7.91636065311 ║  11.7836971688 ║ 0.185077 │   -  ║ S  │     8 │ 0.007812 ║     1 │ 0.210933   ║
  90 ║ 0.656100 │  7.84633419033 ║  11.7390684596 ║ 0.144331 │   -  ║ S  │    10 │ 0.001953 ║     1 │ 0.075811   ║
 100 ║ 0.656100 │  7.82020840675 ║  11.7146451438 ║ 0.134230 │   -  ║ S  │     7 │ 0.015625 ║     1 │ 0.997461   ║
 110 ║ 0.656100 │  7.73870524820 ║  11.6845430750 ║ 0.072477 │   -  ║ S  │    16 │ 3.05e-05 ║     2 │ 0.105903   ║
 120 ║ 0.348678 │  4.11993702707 ║  11.6850459505 ║ 0.045613 │   -  ║ S  │     9 │ 0.003906 ║     1 │ 1.875145   ║
 130 ║ 0.348678 │  4.07187449794 ║  11.6744530383 ║ 0.001244 │   -  ║ S  │     7 │ 0.015625 ║     1 │ 0.243701   ║
 140 ║ 0.088629 │  1.03429514743 ║  11.6698902042 ║ 0.000000 │   -  ║ S  │     7 │ 0.015625 ║     1 │ 0.296043   ║
 150 ║ 0.088629 │  1.03185217107 ║  11.6423262483 ║ 0.000000 │   -  ║ S  │    10 │ 0.001953 ║     1 │ 0.874035   ║
 160 ║ 0.088629 │  1.03096424783 ║  11.6323078635 ║ 0.000000 │   -  ║ S  │    13 │ 2.44e-04 ║     1 │ 2.910258   ║
 170 ║ 0.079766 │  0.92740841750 ║  11.6265484798 ║ 0.000000 │   -  ║ S  │     9 │ 0.003906 ║     1 │ 1.112655   ║
 180 ║ 0.038152 │  0.44340125020 ║  11.6219531579 ║ 0.000000 │   -  ║ S  │     8 │ 0.007812 ║     1 │ 5.646952   ║
 190 ║ 0.011973 │  0.13908925791 ║  11.6173799564 ║ 0.000000 │   -  ║ S  │    12 │ 4.88e-04 ║     1 │ 0.457258   ║
═════╬═══════════════════════════╬════════════════╬═════════════════╬═══════════════════════╬════════════════════╣
     ║ <--- Penalty Function --> ║                ║ Total Violation ║ <--- Line Search ---> ║ <- Stationarity -> ║
Iter ║    Mu    │      Value     ║    Objective   ║   Ineq   │  Eq  ║ SD │ Evals │     t    ║ Grads │    Value   ║
═════╬═══════════════════════════╬════════════════╬═════════════════╬═══════════════════════╬════════════════════╣
 200 ║ 0.011973 │  0.13903006842 ║  11.6124361757 ║ 0.000000 │   -  ║ S  │    15 │ 4.27e-04 ║     1 │ 1.732721   ║
═════╩═══════════════════════════╩════════════════╩═════════════════╩═══════════════════════╩════════════════════╣
Optimization results:                                                                                            ║
F = final iterate, B = Best (to tolerance), MF = Most Feasible                                                   ║
═════╦═══════════════════════════╦════════════════╦═════════════════╦═══════════════════════╦════════════════════╣
   F ║          │                ║  11.6124361757 ║ 0.000000 │   -  ║    │       │          ║       │            ║
   B ║          │                ║  11.6124361757 ║ 0.000000 │   -  ║    │       │          ║       │            ║
  MF ║          │                ║  11.6124361757 ║ 0.000000 │   -  ║    │       │          ║       │            ║
═════╩═══════════════════════════╩════════════════╩═════════════════╩═══════════════════════╩════════════════════╣
Iterations:              200                                                                                     ║
Function evaluations:    1667                                                                                    ║
PyGRANSO termination code: 4 --- max iterations reached.                                                         ║
═════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝
Total Wall Time: 121.89363980293274s
[7]:
# We can also tune PyGRANSO to more aggressively favor satisfying
# feasibility over minimizing the objective.  Set feasibility_bias to
# true to adjust the following three steering parameters away from
# their default values.  For more details on these parameters, type
# import pygransoOptionsAdvanced
# help(pygransoOptionsAdvanced)
import numpy as np
opts = pygransoStruct()
opts.torch_device = device
feasibility_bias = True
if feasibility_bias:
    opts.steering_ineq_margin = np.inf    # default is 1e-6
    opts.steering_c_viol = 0.9         # default is 0.1
    opts.steering_c_mu = 0.1           # default is 0.9

[8]:
opts.maxit = 200
opts.x0 = torch.zeros(p*m,1).to(device=device, dtype=torch.double)
# print for every 10 iterations. default: 1
opts.print_frequency = 10

start = time.time()
soln = pygranso(var_spec = var_in,combined_fn = comb_fn,user_opts = opts)
end = time.time()
print("Total Wall Time: {}s".format(end - start))


╔═════ QP SOLVER NOTICE ════════════════════════════════════════════════════════════════════════╗
║  PyGRANSO requires a quadratic program (QP) solver that has a quadprog-compatible interface,  ║
║  the default is osqp. Users may provide their own wrapper for the QP solver.                  ║
║  To disable this notice, set opts.quadprog_info_msg = False                                   ║
╚═══════════════════════════════════════════════════════════════════════════════════════════════╝
═════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗
PyGRANSO: A PyTorch-enabled port of GRANSO with auto-differentiation                                             ║
Version 1.0.0                                                                                                    ║
Licensed under the AGPLv3, Copyright (C) 2021 Tim Mitchell and Buyun Liang                                       ║
═════════════════════════════════════════════════════════════════════════════════════════════════════════════════╣
Problem specifications:                                                                                          ║
 # of variables                     :   200                                                                      ║
 # of inequality constraints        :     1                                                                      ║
 # of equality constraints          :     0                                                                      ║
═════╦═══════════════════════════╦════════════════╦═════════════════╦═══════════════════════╦════════════════════╣
     ║ <--- Penalty Function --> ║                ║ Total Violation ║ <--- Line Search ---> ║ <- Stationarity -> ║
Iter ║    Mu    │      Value     ║    Objective   ║   Ineq   │  Eq  ║ SD │ Evals │     t    ║ Grads │    Value   ║
═════╬═══════════════════════════╬════════════════╬═════════════════╬═══════════════════════╬════════════════════╣
   0 ║ 1.000000 │  16.2063030241 ║  13.7635444107 ║ 2.442759 │   -  ║ -  │     1 │ 0.000000 ║     1 │ 28.28938   ║
  10 ║ 0.100000 │  2.59802946760 ║  13.5350279096 ║ 1.244527 │   -  ║ S  │     4 │ 0.125000 ║     1 │ 0.021953   ║
  20 ║ 0.100000 │  2.19206992212 ║  13.1633300318 ║ 0.875737 │   -  ║ S  │     3 │ 0.250000 ║     1 │ 0.022714   ║
  30 ║ 0.100000 │  1.78040333523 ║  13.1123346136 ║ 0.469170 │   -  ║ S  │     8 │ 0.039062 ║     1 │ 0.002272   ║
  40 ║ 0.100000 │  1.63141686731 ║  13.0073413214 ║ 0.330683 │   -  ║ S  │     3 │ 0.250000 ║     1 │ 0.009815   ║
  50 ║ 0.100000 │  1.29948247148 ║  12.8983541430 ║ 0.009647 │   -  ║ S  │     2 │ 0.500000 ║     1 │ 0.010893   ║
  60 ║ 0.100000 │  1.27442548686 ║  12.7442548686 ║ 0.000000 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 0.002405   ║
  70 ║ 0.100000 │  1.26238129642 ║  12.6231352723 ║ 6.78e-05 │   -  ║ S  │     2 │ 0.500000 ║     1 │ 0.067058   ║
  80 ║ 0.100000 │  1.23475648659 ║  12.3467732983 ║ 7.92e-05 │   -  ║ S  │     3 │ 0.250000 ║     1 │ 0.015258   ║
  90 ║ 0.010000 │  0.12213094355 ║  12.2130943549 ║ 0.000000 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 0.001674   ║
 100 ║ 0.010000 │  0.12162995721 ║  12.1629957213 ║ 0.000000 │   -  ║ S  │     3 │ 0.250000 ║     1 │ 8.06e-04   ║
 110 ║ 0.010000 │  0.12111674332 ║  12.1116743320 ║ 0.000000 │   -  ║ S  │     4 │ 0.625000 ║     1 │ 0.005923   ║
 120 ║ 1.00e-04 │  0.00120773471 ║  12.0773470783 ║ 0.000000 │   -  ║ S  │     6 │ 32.00000 ║     1 │ 1.21e-05   ║
 130 ║ 1.00e-04 │  0.00120566280 ║  12.0566279594 ║ 0.000000 │   -  ║ S  │     6 │ 0.031250 ║     1 │ 0.002570   ║
 140 ║ 1.00e-06 │  1.2047252e-05 ║  12.0472519152 ║ 0.000000 │   -  ║ S  │     7 │ 64.00000 ║     1 │ 2.51e-05   ║
 150 ║ 1.00e-06 │  1.2038928e-05 ║  12.0389283638 ║ 0.000000 │   -  ║ S  │     3 │ 0.250000 ║     2 │ 4.64e-05   ║
 160 ║ 1.00e-06 │  1.2034884e-05 ║  12.0348842608 ║ 0.000000 │   -  ║ S  │     6 │ 5.000000 ║     2 │ 6.50e-05   ║
 170 ║ 1.00e-06 │  1.2034518e-05 ║  12.0345176862 ║ 0.000000 │   -  ║ S  │     1 │ 1.000000 ║     6 │ 1.50e-05   ║
 180 ║ 1.00e-06 │  1.2034430e-05 ║  12.0344302791 ║ 0.000000 │   -  ║ S  │     2 │ 2.000000 ║    14 │ 1.47e-06   ║
 190 ║ 1.00e-06 │  1.2032775e-05 ║  12.0327747718 ║ 0.000000 │   -  ║ S  │     1 │ 1.000000 ║     9 │ 2.30e-05   ║
═════╬═══════════════════════════╬════════════════╬═════════════════╬═══════════════════════╬════════════════════╣
     ║ <--- Penalty Function --> ║                ║ Total Violation ║ <--- Line Search ---> ║ <- Stationarity -> ║
Iter ║    Mu    │      Value     ║    Objective   ║   Ineq   │  Eq  ║ SD │ Evals │     t    ║ Grads │    Value   ║
═════╬═══════════════════════════╬════════════════╬═════════════════╬═══════════════════════╬════════════════════╣
 200 ║ 1.00e-09 │  1.2031511e-08 ║  12.0315112210 ║ 0.000000 │   -  ║ S  │     1 │ 1.000000 ║    10 │ 9.40e-07   ║
═════╩═══════════════════════════╩════════════════╩═════════════════╩═══════════════════════╩════════════════════╣
Optimization results:                                                                                            ║
F = final iterate, B = Best (to tolerance), MF = Most Feasible                                                   ║
═════╦═══════════════════════════╦════════════════╦═════════════════╦═══════════════════════╦════════════════════╣
   F ║          │                ║  12.0315112210 ║ 0.000000 │   -  ║    │       │          ║       │            ║
   B ║          │                ║  12.0314985954 ║ 0.000000 │   -  ║    │       │          ║       │            ║
  MF ║          │                ║  12.0314985954 ║ 0.000000 │   -  ║    │       │          ║       │            ║
═════╩═══════════════════════════╩════════════════╩═════════════════╩═══════════════════════╩════════════════════╣
Iterations:              200                                                                                     ║
Function evaluations:    653                                                                                     ║
PyGRANSO termination code: 4 --- max iterations reached.                                                         ║
═════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝
Total Wall Time: 44.79011344909668s

In my testing, with default parameters, PyGRANSO will first obtain a feasible solution at iter ~= 160 and will reduce the objective to 11.60 by the time it attains max iteration count of 200.

With feasibility_bias = True, in my testing, PyGRANSO will obtain its first feasible solution earlier, at iter ~= 60, but it will ultimately have reduced the objective value less, only to 12.21, by the end of its 200 maximum allowed iterations.