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.2.0                                                                                                    ║
Licensed under the AGPLv3, Copyright (C) 2021-2022 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.3596190745 ║  12.9268516490 ║ 1.432767 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 0.035504   ║
  20 ║ 1.000000 │  13.7030952290 ║  12.6546385386 ║ 1.048457 │   -  ║ S  │     2 │ 0.500000 ║     1 │ 0.039570   ║
  30 ║ 1.000000 │  12.8620021270 ║  12.2684505963 ║ 0.593552 │   -  ║ S  │     4 │ 0.125000 ║     1 │ 0.048312   ║
  40 ║ 1.000000 │  12.6579998061 ║  12.1060352760 ║ 0.551965 │   -  ║ S  │     8 │ 0.007812 ║     1 │ 0.035025   ║
  50 ║ 0.900000 │  11.1959291986 ║  11.9651110834 ║ 0.427329 │   -  ║ S  │     7 │ 0.015625 ║     1 │ 0.019151   ║
  60 ║ 0.900000 │  11.0023393275 ║  11.8890675575 ║ 0.302179 │   -  ║ S  │     4 │ 0.125000 ║     1 │ 0.024557   ║
  70 ║ 0.900000 │  10.7433220769 ║  11.7789577618 ║ 0.142260 │   -  ║ S  │     5 │ 0.062500 ║     1 │ 0.025465   ║
  80 ║ 0.590490 │  7.01603991319 ║  11.7158332169 ║ 0.097958 │   -  ║ S  │     3 │ 0.250000 ║     1 │ 0.013271   ║
  90 ║ 0.590490 │  6.92114034141 ║  11.6658843316 ║ 0.032552 │   -  ║ S  │     8 │ 0.007812 ║     1 │ 0.086947   ║
 100 ║ 0.590490 │  6.87608100731 ║  11.6215913630 ║ 0.013648 │   -  ║ S  │     5 │ 0.062500 ║     1 │ 0.005812   ║
 110 ║ 0.590490 │  6.83585456951 ║  11.5622624393 ║ 0.008454 │   -  ║ S  │     4 │ 0.125000 ║     1 │ 0.039093   ║
 120 ║ 0.590490 │  6.78145717776 ║  11.4799718129 ║ 0.002649 │   -  ║ S  │    12 │ 4.88e-04 ║     1 │ 8.554103   ║
 130 ║ 0.590490 │  6.75685012537 ║  11.4427850182 ║ 0.000000 │   -  ║ S  │     6 │ 0.031250 ║     1 │ 0.008980   ║
 140 ║ 0.282430 │  3.22489665521 ║  11.4062621765 ║ 0.003431 │   -  ║ S  │     9 │ 0.003906 ║     2 │ 0.003855   ║
 150 ║ 0.282430 │  3.20049021475 ║  11.3319954231 ║ 0.000000 │   -  ║ S  │     7 │ 0.140625 ║     1 │ 0.097628   ║
 160 ║ 0.282430 │  3.18442979945 ║  11.2751302117 ║ 0.000000 │   -  ║ S  │     7 │ 0.015625 ║     2 │ 0.001811   ║
 170 ║ 0.282430 │  3.17160540367 ║  11.2297227945 ║ 0.000000 │   -  ║ S  │     5 │ 0.062500 ║     1 │ 0.012334   ║
 180 ║ 0.282430 │  3.15443814807 ║  11.1689385869 ║ 0.000000 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 0.003354   ║
 190 ║ 0.282430 │  3.14459744559 ║  11.1340955510 ║ 0.000000 │   -  ║ S  │     7 │ 0.015625 ║     1 │ 0.019961   ║
═════╬═══════════════════════════╬════════════════╬═════════════════╬═══════════════════════╬════════════════════╣
     ║ <--- Penalty Function --> ║                ║ Total Violation ║ <--- Line Search ---> ║ <- Stationarity -> ║
Iter ║    Mu    │      Value     ║    Objective   ║   Ineq   │  Eq  ║ SD │ Evals │     t    ║ Grads │    Value   ║
═════╬═══════════════════════════╬════════════════╬═════════════════╬═══════════════════════╬════════════════════╣
 200 ║ 0.282430 │  3.13379717527 ║  11.0958549673 ║ 0.000000 │   -  ║ S  │     3 │ 0.250000 ║     1 │ 0.003635   ║
═════╩═══════════════════════════╩════════════════╩═════════════════╩═══════════════════════╩════════════════════╣
F = final iterate, B = Best (to tolerance), MF = Most Feasible                                                   ║
Optimization results:                                                                                            ║
═════╦═══════════════════════════╦════════════════╦═════════════════╦═══════════════════════╦════════════════════╣
   F ║          │                ║  11.0958549673 ║ 0.000000 │   -  ║    │       │          ║       │            ║
   B ║          │                ║  11.0958549673 ║ 0.000000 │   -  ║    │       │          ║       │            ║
  MF ║          │                ║  11.0958549673 ║ 0.000000 │   -  ║    │       │          ║       │            ║
═════╩═══════════════════════════╩════════════════╩═════════════════╩═══════════════════════╩════════════════════╣
Iterations:              200                                                                                     ║
Function evaluations:    977                                                                                     ║
PyGRANSO termination code: 4 --- max iterations reached.                                                         ║
═════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝
Total Wall Time: 73.09481072425842s

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.2.0                                                                                                    ║
Licensed under the AGPLv3, Copyright (C) 2021-2022 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.3596190745 ║  12.9268516490 ║ 1.432767 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 0.035504   ║
  20 ║ 1.000000 │  13.7030952290 ║  12.6546385386 ║ 1.048457 │   -  ║ S  │     2 │ 0.500000 ║     1 │ 0.039570   ║
  30 ║ 1.000000 │  12.8620021270 ║  12.2684505963 ║ 0.593552 │   -  ║ S  │     4 │ 0.125000 ║     1 │ 0.048312   ║
  40 ║ 1.000000 │  12.6579998061 ║  12.1060352760 ║ 0.551965 │   -  ║ S  │     8 │ 0.007812 ║     1 │ 0.035025   ║
  50 ║ 1.000000 │  12.3635830043 ║  11.9482819495 ║ 0.415301 │   -  ║ S  │     3 │ 0.250000 ║     1 │ 0.024637   ║
  60 ║ 1.000000 │  12.1866328732 ║  11.8573351538 ║ 0.329298 │   -  ║ S  │     6 │ 0.031250 ║     1 │ 0.049341   ║
  70 ║ 1.000000 │  12.0726567906 ║  11.8152322843 ║ 0.257425 │   -  ║ S  │     9 │ 0.003906 ║     1 │ 0.087016   ║
  80 ║ 0.900000 │  10.8099812281 ║  11.7322840024 ║ 0.250926 │   -  ║ S  │     6 │ 0.031250 ║     1 │ 0.053814   ║
  90 ║ 0.900000 │  10.7423772961 ║  11.6888546812 ║ 0.222408 │   -  ║ S  │    10 │ 0.001953 ║     1 │ 1.097996   ║
 100 ║ 0.900000 │  10.6934954335 ║  11.6498556823 ║ 0.208625 │   -  ║ S  │     8 │ 0.023438 ║     1 │ 0.104976   ║
 110 ║ 0.900000 │  10.6737493784 ║  11.6419405074 ║ 0.196003 │   -  ║ S  │    10 │ 0.001953 ║     1 │ 0.486377   ║
 120 ║ 0.590490 │  7.01684023305 ║  11.6335924856 ║ 0.147320 │   -  ║ S  │    15 │ 1.83e-04 ║     1 │ 13.80324   ║
 130 ║ 0.590490 │  6.99652751546 ║  11.6311862406 ║ 0.128428 │   -  ║ S  │     9 │ 0.003906 ║     1 │ 0.166842   ║
 140 ║ 0.590490 │  6.98605019503 ║  11.6116595852 ║ 0.129481 │   -  ║ S  │    14 │ 1.22e-04 ║     2 │ 0.201047   ║
 150 ║ 0.590490 │  6.96537265265 ║  11.6137692506 ║ 0.107558 │   -  ║ S  │    17 │ 1.07e-04 ║     1 │ 390.6339   ║
 160 ║ 0.590490 │  6.94088780302 ║  11.6615791338 ║ 0.054842 │   -  ║ S  │    12 │ 4.88e-04 ║     1 │ 0.955100   ║
 170 ║ 0.590490 │  6.92412727036 ║  11.6075095895 ║ 0.070009 │   -  ║ S  │    14 │ 1.22e-04 ║     1 │ 0.127275   ║
 180 ║ 0.282430 │  3.33270686212 ║  11.5989449670 ║ 0.056822 │   -  ║ S  │    11 │ 9.77e-04 ║     1 │ 3.557972   ║
 190 ║ 0.282430 │  3.32413687788 ║  11.6020951504 ║ 0.047363 │   -  ║ S  │    16 │ 3.05e-05 ║     1 │ 3.739864   ║
═════╬═══════════════════════════╬════════════════╬═════════════════╬═══════════════════════╬════════════════════╣
     ║ <--- Penalty Function --> ║                ║ Total Violation ║ <--- Line Search ---> ║ <- Stationarity -> ║
Iter ║    Mu    │      Value     ║    Objective   ║   Ineq   │  Eq  ║ SD │ Evals │     t    ║ Grads │    Value   ║
═════╬═══════════════════════════╬════════════════╬═════════════════╬═══════════════════════╬════════════════════╣
 200 ║ 0.098477 │  1.14935778481 ║  11.6713215456 ║ 0.000000 │   -  ║ S  │     9 │ 0.003906 ║     1 │ 11.56244   ║
═════╩═══════════════════════════╩════════════════╩═════════════════╩═══════════════════════╩════════════════════╣
F = final iterate, B = Best (to tolerance), MF = Most Feasible                                                   ║
Optimization results:                                                                                            ║
═════╦═══════════════════════════╦════════════════╦═════════════════╦═══════════════════════╦════════════════════╣
   F ║          │                ║  11.6713215456 ║ 0.000000 │   -  ║    │       │          ║       │            ║
   B ║          │                ║  11.6713215456 ║ 0.000000 │   -  ║    │       │          ║       │            ║
  MF ║          │                ║  11.6713215456 ║ 0.000000 │   -  ║    │       │          ║       │            ║
═════╩═══════════════════════════╩════════════════╩═════════════════╩═══════════════════════╩════════════════════╣
Iterations:              200                                                                                     ║
Function evaluations:    1835                                                                                    ║
PyGRANSO termination code: 4 --- max iterations reached.                                                         ║
═════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝
Total Wall Time: 138.43107748031616s
[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.2.0                                                                                                    ║
Licensed under the AGPLv3, Copyright (C) 2021-2022 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.59973391273 ║  13.5351593895 ║ 1.246218 │   -  ║ S  │     4 │ 0.125000 ║     1 │ 0.021935   ║
  20 ║ 0.100000 │  2.16477288097 ║  13.0916138399 ║ 0.855611 │   -  ║ S  │     2 │ 0.500000 ║     1 │ 0.023498   ║
  30 ║ 0.100000 │  1.79745295456 ║  13.0992702725 ║ 0.487526 │   -  ║ S  │     7 │ 0.015625 ║     1 │ 0.004077   ║
  40 ║ 0.100000 │  1.58635141808 ║  13.0513266695 ║ 0.281219 │   -  ║ S  │     8 │ 0.007812 ║     1 │ 0.004015   ║
  50 ║ 0.100000 │  1.42581950214 ║  13.0841956594 ║ 0.117400 │   -  ║ S  │     3 │ 0.250000 ║     1 │ 0.006101   ║
  60 ║ 0.100000 │  1.29645227633 ║  12.8945466090 ║ 0.006998 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 0.017645   ║
  70 ║ 0.100000 │  1.27161142274 ║  12.7161142274 ║ 0.000000 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 0.001977   ║
  80 ║ 0.100000 │  1.24712647142 ║  12.4712647142 ║ 0.000000 │   -  ║ S  │     7 │ 0.015625 ║     1 │ 0.023924   ║
  90 ║ 0.100000 │  1.23218670220 ║  12.2655470686 ║ 0.005632 │   -  ║ S  │     2 │ 2.000000 ║     1 │ 0.004044   ║
 100 ║ 0.100000 │  1.21443402902 ║  12.1443402902 ║ 0.000000 │   -  ║ S  │     2 │ 0.500000 ║     1 │ 0.006803   ║
 110 ║ 0.100000 │  1.20645480447 ║  12.0614085408 ║ 3.14e-04 │   -  ║ S  │     7 │ 0.015625 ║     1 │ 0.001226   ║
 120 ║ 0.100000 │  1.19765253592 ║  11.9765253592 ║ 0.000000 │   -  ║ S  │     6 │ 0.031250 ║     1 │ 0.062770   ║
 130 ║ 0.100000 │  1.19273267652 ║  11.9273267652 ║ 0.000000 │   -  ║ S  │     6 │ 0.031250 ║     1 │ 0.014048   ║
 140 ║ 0.100000 │  1.18358712018 ║  11.8358712018 ║ 0.000000 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 0.002047   ║
 150 ║ 0.100000 │  1.17691829216 ║  11.7691829216 ║ 0.000000 │   -  ║ S  │     5 │ 0.062500 ║     1 │ 0.107164   ║
 160 ║ 0.100000 │  1.17305505323 ║  11.7305505323 ║ 0.000000 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 0.001760   ║
 170 ║ 0.100000 │  1.16987321991 ║  11.6987321991 ║ 0.000000 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 0.001199   ║
 180 ║ 0.010000 │  0.11670949970 ║  11.6709499699 ║ 0.000000 │   -  ║ S  │     2 │ 0.500000 ║     1 │ 3.70e-04   ║
 190 ║ 0.010000 │  0.11654589936 ║  11.6545899360 ║ 0.000000 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 2.54e-04   ║
═════╬═══════════════════════════╬════════════════╬═════════════════╬═══════════════════════╬════════════════════╣
     ║ <--- Penalty Function --> ║                ║ Total Violation ║ <--- Line Search ---> ║ <- Stationarity -> ║
Iter ║    Mu    │      Value     ║    Objective   ║   Ineq   │  Eq  ║ SD │ Evals │     t    ║ Grads │    Value   ║
═════╬═══════════════════════════╬════════════════╬═════════════════╬═══════════════════════╬════════════════════╣
 200 ║ 0.010000 │  0.11630786772 ║  11.6307867715 ║ 0.000000 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 0.001599   ║
═════╩═══════════════════════════╩════════════════╩═════════════════╩═══════════════════════╩════════════════════╣
F = final iterate, B = Best (to tolerance), MF = Most Feasible                                                   ║
Optimization results:                                                                                            ║
═════╦═══════════════════════════╦════════════════╦═════════════════╦═══════════════════════╦════════════════════╣
   F ║          │                ║  11.6307867715 ║ 0.000000 │   -  ║    │       │          ║       │            ║
   B ║          │                ║  11.6307867715 ║ 0.000000 │   -  ║    │       │          ║       │            ║
  MF ║          │                ║  11.6307867715 ║ 0.000000 │   -  ║    │       │          ║       │            ║
═════╩═══════════════════════════╩════════════════╩═════════════════╩═══════════════════════╩════════════════════╣
Iterations:              200                                                                                     ║
Function evaluations:    670                                                                                     ║
PyGRANSO termination code: 4 --- max iterations reached.                                                         ║
═════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝
Total Wall Time: 51.2608208656311s

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.