Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Start trying to explain convergence failures #22

Open
wants to merge 37 commits into
base: main
Choose a base branch
from

Conversation

Robbybp
Copy link
Owner

@Robbybp Robbybp commented Apr 22, 2024

I initially intended to compute condition numbers of the Jacobian and KKT matrix, but I got distracted by computing errors in surrogate models. With this branch, we can produce a new "surrogate error" file with:

python run_alamo_sweep.py --compute-surrogate-error
python run_nn_sweep.py --compute-surrogate-error

This option computes the maximum relative error between a simulation of the surrogate model and a simulation of full-space reactor model at the point where optimization problem stops (whether or not it converged successfully). Using this option, I can get the following plots:

The transparent blocks in these grids (e.g. outside the training region with the NN) correspond to cases where either the full-space reactor model or the reactor surrogate fail to simulate. These would be worth investigating further.

@sbugosen
Copy link
Collaborator

sbugosen commented Apr 27, 2024

@Robbybp
I tested 4 successful instances for every formulation. Plot shown below. I'll push the script soon. The neural network is showing a similar behavior to that of the full space formulation, which is kind of unfortunate and unexpected. I'm trying to find an explanation for this. The nn formulation has the largest number of variables and constraints, could this be a starting point for an explanation?

Maybe we have high condition number in nn due to: function is not as smooth as the alamo polynomials + higher chance for numerical instability due to number of parameters + overtrained nn provides divergent values for small variations in the input?

conditionnums

@sbugosen
Copy link
Collaborator

sbugosen commented Apr 27, 2024

The divergence for Subset 1 occurs at iteration 13 of the full space formulation solve. Highest residual occurs for the bypass_rejoin enthalpy mixing equations. Though I would like to do the sophisticated analysis on making the Jacobian a square matrix, finding its block triangular form, and calculate the condition number for each resulting block to identify the culprit. I'll try to do this.

@Robbybp
Copy link
Owner Author

Robbybp commented Apr 28, 2024

Really nice results, thanks. I'm impressed that the ALAMO and implicit function condition numbers track well.

The neural network is showing a similar behavior to that of the full space formulation, which is kind of unfortunate and unexpected.

I don't think this is unfortunate or unexpected. It's just interesting. It's interesting that the NN formulation sometimes tracks the full-space, and sometimes doesn't.

Can you plot this for a case where full-space, NN, and implicit fail?

@sbugosen
Copy link
Collaborator

sbugosen commented Apr 28, 2024

Can you plot this for a case where full-space, NN, and implicit fail?

These are the plots for 4 unsuccessful instances. The implicit function failed due to eval errors, that's why the curve for this formulation stops early. Notice now the magnitude of the condition number. Tomorrow I'll get started on block triangularization of the Jacobian.

conditionnumunsuccesssful

@Robbybp
Copy link
Owner Author

Robbybp commented Apr 28, 2024

Some nicer plots for surrogate error:

@Robbybp
Copy link
Owner Author

Robbybp commented Apr 28, 2024

These are the plots for 4 unsuccessful instances. The implicit function failed due to eval errors, that's why the curve for this formulation stops early. Notice now the magnitude of the condition number.

Thanks. From these plots, it seems like we jump into a "bad region" of high condition number, then never recover. I wonder if there is some difference between the jump that occurs around iteration 15 for the failing vs. successful cases.

@sbugosen
Copy link
Collaborator

sbugosen commented Apr 29, 2024

Hi Robert, I'm running this code:

P = 1947379.0 
X = 0.94
m = make_optimization_model(X, P, initialize=True)
solver = config.get_optimization_solver(iters=50)
results = solver.solve(m, tee=True)
m.fs.REF_OUT_expanded.pressure_equality[0].deactivate()
nlp = PyomoNLP(m)
jac = nlp.evaluate_jacobian()
row_partition, col_partition = triangularize.block_triangularize(jac)

The output for col_partition (similar output to row_partition) is:

[18, 20, 9, 22, 23, 24, 25, 26, 15, 28, 29, 32, 46, 44, 48, 49, 50, 51, 52, 53, 54, 55, 73, 75, 100, 77, 78, 79, 80, 81, 82, 83, 84, 124, 140, 129, 142, 143, 144, 145, 146, 138, 148, 149, 150, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 367, 381, 346, 383, 379, 385, 386, 387, 352, 389, 390, 411, 831, 830, 832, 404, 405, 406, 407, 409, 424, 429, 431, 414, 435, 421, 400, 401, 402, 403, 415, 416, 417, 418, 420, 466, 467, 444, 469, 470, 471, 472, 473, 450, 475, 476, 6, 7, 8, 529, 10, 11, 12, 13, 14, 535, 16, 17, 539, 550, 19, 540, 21, 542, 543, 544, 545, 546, 27, 548, 549, 565, 576, 45, 566, 47, 568, 569, 570, 571, 572, 573, 574, 575, 122, 98, 99, 584, 101, 102, 103, 104, 105, 106, 107, 108, 593, 604, 74, 594, 76, 596, 597, 598, 599, 600, 601, 602, 603, 125, 126, 127, 128, 627, 130, 131, 132, 133, 134, 135, 136, 137, 637, 648, 139, 638, 141, 640, 641, 642, 643, 644, 147, 646, 647, 650, 661, 151, 651, 652, 653, 654, 655, 656, 657, 658, 659, 660, 343, 344, 345, 792, 347, 348, 349, 350, 351, 798, 353, 354, 817, 828, 380, 818, 382, 820, 384, 822, 823, 824, 388, 826, 827, 426, 427, 428, 835, 430, 423, 432, 433, 434, 840, 436, 437, 844, 438, 843, 412, 413, 846, 425, 847, 848, 849, 419, 851, 852, 441, 442, 443, 861, 445, 446, 447, 448, 449, 867, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 886, 884, 885, 468, 887, 888, 889, 890, 891, 893, 894, 581, 582, 583, 567, 585, 586, 587, 588, 589, 590, 591, 592, 624, 625, 626, 595, 628, 629, 630, 631, 632, 633, 634, 635, 789, 790, 791, 639, 793, 794, 795, 796, 797, 645, 799, 800, 422, 833, 834, 819, 836, 821, 837, 838, 839, 825, 841, 842, 526, 527, 528, 845, 530, 531, 532, 533, 534, 850, 536, 537, 858, 859, 860, 541, 862, 863, 864, 865, 866, 547, 868, 869, 871, 872, 873, 874, 875, 876, 877, 878, 879, 880, 881, 882], [485], [513], [487], [515], [488], [516], [489], [517], [490], [518], [491], [519], [410], [494], [522], [493], [521], [408], [61, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 606, 62, 607, 608, 609, 610, 611, 612, 613, 614, 615, 616], [87], [86], [88], ...

How can I reconstruct the block triangular form of the Jacobin matrix with this information?

@sbugosen
Copy link
Collaborator

sbugosen commented Apr 29, 2024

An example plot of the condition number heat map. It is correlated with the unsuccessful instances mostly.

Screenshot 2024-04-29 at 12 26 05 PM

@Robbybp
Copy link
Owner Author

Robbybp commented Apr 29, 2024

Typically, I would get the condition number of diagonal blocks with the following:

results = solver.solve(m, tee=True)
# FIX DEGREES OF FREEDOM HERE!
nlp = PyomoNLP(m)
igraph = IncidenceGraphInterface(m, include_inequality=False)
vblocks, cblocks = igraph.block_triangularize()
submatrices = [
    nlp.extract_submatrix_jacobian(vb, cb) for vb, cb in zip(vblocks, cblocks)
]
for i, submat in enumerate(submatrices):
    cond = np.linalg.cond(submat.toarray())
    # Print or store condition number as necessary

Hope this helps.

In your particular example, I'm a little confused about why block_triangularize is working, when you seemingly haven't fixed any degrees of freedom. I assume you just left this out of the example? I also don't know why you would have to deactivate any equations.

@Robbybp
Copy link
Owner Author

Robbybp commented Apr 29, 2024

An example plot of the condition number heat map. It is correlated with the unsuccessful instances mostly.

Thanks! Is there a way to make the color bar scale logarithmic?

@sbugosen
Copy link
Collaborator

@Robbybp

Can you run the following commands please?

python cond_run_fullspace_sweep.py

python plot_condition_num_grid.py \          
                data/fullspace-sweep.csv \
                --title="Full-space"

@sbugosen
Copy link
Collaborator

sbugosen commented Apr 29, 2024

Typically, I would get the condition number of diagonal blocks with the following:

results = solver.solve(m, tee=True)
# FIX DEGREES OF FREEDOM HERE!
nlp = PyomoNLP(m)
igraph = IncidenceGraphInterface(m, include_inequality=False)
vblocks, cblocks = igraph.block_triangularize()
submatrices = [
    nlp.extract_submatrix_jacobian(vb, cb) for vb, cb in zip(vblocks, cblocks)
]
for i, submat in enumerate(submatrices):
    cond = np.linalg.cond(submat.toarray())
    # Print or store condition number as necessary

Hope this helps.

In your particular example, I'm a little confused about why block_triangularize is working, when you seemingly haven't fixed any degrees of freedom. I assume you just left this out of the example? I also don't know why you would have to deactivate any equations.

No, the code worked. I stopped the full space solve at iteration 50, and had to deactivate the pressure equality to have 898 variables and 898 constraints. I did not have to fix any degree of freedom after stopping the solve. Thank you!! I'll try that code.

@Robbybp
Copy link
Owner Author

Robbybp commented Apr 29, 2024

898 variables and 898 constraints

Shouldn't we have 895 constraints and variables after fixing degrees of freedom in the full-space model?

@sbugosen
Copy link
Collaborator

898 variables and 898 constraints

Shouldn't we have 895 constraints and variables after fixing degrees of freedom in the full-space model?

I see what happened. No, I did not fix any degree of freedom. That's why I have 898 variables in that Jacobian. I had 899 constraints (895 equalities, 4 inequalities), and I mistakenly deactivated the pressure equality to make them match. I forgot that the Jacobian build only needed the 895 equality constraints, which in turn would need 895 variables only, with our 3 DOF fixed. Thanks.

@Robbybp
Copy link
Owner Author

Robbybp commented Apr 29, 2024

Just tested the condition number parameter sweep plot with your latest modifications. Looks great! Very good agreement between unsuccessful instances and high condition numbers.

@sbugosen
Copy link
Collaborator

sbugosen commented Apr 29, 2024

    if args.optimize:
        P = 1947379.0 
        X = 0.94
        m = make_optimization_model(X, P, initialize=True)
        solver = config.get_optimization_solver(iters=35)
        print(X,P)
        results = solver.solve(m, tee=True)
        m.fs.reformer_bypass.split_fraction[0, "bypass_outlet"].fix(m.fs.reformer_bypass.split_fraction[0, "bypass_outlet"].value)
        m.fs.reformer_mix.steam_inlet.flow_mol.fix(m.fs.reformer_mix.steam_inlet.flow_mol[0].value)
        m.fs.feed.outlet.flow_mol.fix(m.fs.feed.outlet.flow_mol[0].value)
        nlp = PyomoNLP(m)
        igraph = IncidenceGraphInterface(m, include_inequality=False)
        vblocks, cblocks = igraph.block_triangularize()

        for i, (vblock, cblock) in enumerate(zip(vblocks, cblocks)):
            submatrix = nlp.extract_submatrix_jacobian(vblock, cblock)
            cond = np.linalg.cond(submatrix.toarray())
            if cond > 1e10:
                print(f"block {i}: {cond}")
                for var in vblock:
                   print(f"Variable:  {var.name}")
                for con in cblock:
                   print(f"Constraint:  {con.name}")

Just did this test for the full space formulation, and got these results:

block 382: 93982054238.5497
Variable:  fs.reformer.control_volume.properties_out[0.0].temperature
Variable:  fs.reformer.lagrange_mult[0.0,C]
Variable:  fs.reformer.lagrange_mult[0.0,H]
Variable:  fs.reformer.lagrange_mult[0.0,O]
Variable:  fs.reformer.control_volume.properties_out[0.0].log_mole_frac_phase_comp[Vap,CH4]
Variable:  fs.reformer.control_volume.properties_out[0.0].log_mole_frac_phase_comp[Vap,C2H6]
Variable:  fs.reformer.control_volume.properties_out[0.0].log_mole_frac_phase_comp[Vap,C3H8]
Variable:  fs.reformer.control_volume.properties_out[0.0].log_mole_frac_phase_comp[Vap,C4H10]
Variable:  fs.reformer.control_volume.properties_out[0.0].log_mole_frac_phase_comp[Vap,O2]
Variable:  fs.reformer.control_volume.properties_out[0.0].flow_mol
Variable:  fs.reformer.control_volume.properties_out[0.0].mole_frac_phase_comp[Vap,H2O]
Variable:  fs.reformer.control_volume.properties_out[0.0].mole_frac_phase_comp[Vap,CO2]
Variable:  fs.reformer.control_volume.properties_out[0.0].mole_frac_phase_comp[Vap,CO]
Variable:  fs.reformer.control_volume.properties_out[0.0].mole_frac_phase_comp[Vap,N2]
Variable:  fs.reformer.control_volume.properties_out[0.0].mole_frac_phase_comp[Vap,Ar]
Variable:  fs.reformer.control_volume.properties_out[0.0].mole_frac_comp[H2]
Variable:  fs.reformer.control_volume.properties_out[0.0].flow_mol_phase[Vap]
Variable:  fs.reformer.control_volume.properties_out[0.0].mole_frac_phase_comp[Vap,H2]
Variable:  fs.reformer.control_volume.properties_out[0.0].mole_frac_comp[CO]
Variable:  fs.reformer.control_volume.properties_out[0.0].mole_frac_comp[H2O]
Variable:  fs.reformer.control_volume.properties_out[0.0].mole_frac_comp[CO2]
Variable:  fs.reformer.control_volume.properties_out[0.0].mole_frac_comp[CH4]
Variable:  fs.reformer.control_volume.properties_out[0.0].mole_frac_comp[C2H6]
Variable:  fs.reformer.control_volume.properties_out[0.0].mole_frac_comp[C3H8]
Variable:  fs.reformer.control_volume.properties_out[0.0].mole_frac_comp[C4H10]
Variable:  fs.reformer.control_volume.properties_out[0.0].mole_frac_comp[N2]
Variable:  fs.reformer.control_volume.properties_out[0.0].mole_frac_comp[O2]
Variable:  fs.reformer.control_volume.properties_out[0.0].mole_frac_comp[Ar]
Variable:  fs.reformer.control_volume.properties_out[0.0].log_mole_frac_phase_comp[Vap,H2]
Variable:  fs.reformer.control_volume.properties_out[0.0].log_mole_frac_phase_comp[Vap,CO]
Variable:  fs.reformer.control_volume.properties_out[0.0].log_mole_frac_phase_comp[Vap,H2O]
Variable:  fs.reformer.control_volume.properties_out[0.0].log_mole_frac_phase_comp[Vap,CO2]
Variable:  fs.reformer.control_volume.properties_out[0.0].mole_frac_phase_comp[Vap,CH4]
Variable:  fs.reformer.control_volume.properties_out[0.0].mole_frac_phase_comp[Vap,C2H6]
Variable:  fs.reformer.control_volume.properties_out[0.0].mole_frac_phase_comp[Vap,C3H8]
Variable:  fs.reformer.control_volume.properties_out[0.0].mole_frac_phase_comp[Vap,C4H10]
Variable:  fs.reformer.control_volume.properties_out[0.0].mole_frac_phase_comp[Vap,O2]
Constraint:  fs.reformer.gibbs_minimization[0.0,Vap,H2]
Constraint:  fs.reformer.gibbs_minimization[0.0,Vap,CO]
Constraint:  fs.reformer.gibbs_minimization[0.0,Vap,H2O]
Constraint:  fs.reformer.gibbs_minimization[0.0,Vap,CO2]
Constraint:  fs.reformer.gibbs_minimization[0.0,Vap,CH4]
Constraint:  fs.reformer.gibbs_minimization[0.0,Vap,C2H6]
Constraint:  fs.reformer.gibbs_minimization[0.0,Vap,C3H8]
Constraint:  fs.reformer.gibbs_minimization[0.0,Vap,C4H10]
Constraint:  fs.reformer.gibbs_minimization[0.0,Vap,O2]
Constraint:  fs.reformer.conv_constraint
Constraint:  fs.reformer.control_volume.element_balances[0.0,H]
Constraint:  fs.reformer.control_volume.element_balances[0.0,C]
Constraint:  fs.reformer.control_volume.element_balances[0.0,O]
Constraint:  fs.reformer.control_volume.element_balances[0.0,N]
Constraint:  fs.reformer.control_volume.element_balances[0.0,Ar]
Constraint:  fs.reformer.control_volume.properties_out[0.0].sum_mole_frac_out
Constraint:  fs.reformer.control_volume.properties_out[0.0].total_flow_balance
Constraint:  fs.reformer.control_volume.properties_out[0.0].component_flow_balances[H2]
Constraint:  fs.reformer.control_volume.properties_out[0.0].component_flow_balances[CO]
Constraint:  fs.reformer.control_volume.properties_out[0.0].component_flow_balances[H2O]
Constraint:  fs.reformer.control_volume.properties_out[0.0].component_flow_balances[CO2]
Constraint:  fs.reformer.control_volume.properties_out[0.0].component_flow_balances[CH4]
Constraint:  fs.reformer.control_volume.properties_out[0.0].component_flow_balances[C2H6]
Constraint:  fs.reformer.control_volume.properties_out[0.0].component_flow_balances[C3H8]
Constraint:  fs.reformer.control_volume.properties_out[0.0].component_flow_balances[C4H10]
Constraint:  fs.reformer.control_volume.properties_out[0.0].component_flow_balances[N2]
Constraint:  fs.reformer.control_volume.properties_out[0.0].component_flow_balances[O2]
Constraint:  fs.reformer.control_volume.properties_out[0.0].component_flow_balances[Ar]
Constraint:  fs.reformer.control_volume.properties_out[0.0].log_mole_frac_phase_comp_eqn[Vap,H2]
Constraint:  fs.reformer.control_volume.properties_out[0.0].log_mole_frac_phase_comp_eqn[Vap,CO]
Constraint:  fs.reformer.control_volume.properties_out[0.0].log_mole_frac_phase_comp_eqn[Vap,H2O]
Constraint:  fs.reformer.control_volume.properties_out[0.0].log_mole_frac_phase_comp_eqn[Vap,CO2]
Constraint:  fs.reformer.control_volume.properties_out[0.0].log_mole_frac_phase_comp_eqn[Vap,CH4]
Constraint:  fs.reformer.control_volume.properties_out[0.0].log_mole_frac_phase_comp_eqn[Vap,C2H6]
Constraint:  fs.reformer.control_volume.properties_out[0.0].log_mole_frac_phase_comp_eqn[Vap,C3H8]
Constraint:  fs.reformer.control_volume.properties_out[0.0].log_mole_frac_phase_comp_eqn[Vap,C4H10]
Constraint:  fs.reformer.control_volume.properties_out[0.0].log_mole_frac_phase_comp_eqn[Vap,O2]
block 524: 136900850220.33492
Variable:  fs.reformer_recuperator.delta_temperature_in[0.0]
Variable:  fs.reformer_recuperator.hot_side.properties_out[0.0].temperature
Variable:  fs.reformer_recuperator.cold_side.heat[0.0]
Variable:  fs.reformer_recuperator.delta_temperature_out[0.0]
Variable:  fs.reformer_recuperator.hot_side.heat[0.0]
Variable:  fs.reformer_recuperator.cold_side.properties_out[0.0].temperature
Constraint:  fs.reformer_recuperator.delta_temperature_in_equation[0.0]
Constraint:  fs.reformer_recuperator.delta_temperature_out_equation[0.0]
Constraint:  fs.reformer_recuperator.unit_heat_balance[0.0]
Constraint:  fs.reformer_recuperator.heat_transfer_equation[0.0]
Constraint:  fs.reformer_recuperator.hot_side.enthalpy_balances[0.0]
Constraint:  fs.reformer_recuperator.cold_side.enthalpy_balances[0.0]

Are you aware of any formal theorem that might say something like "The condition number of a block matrix is at least the largest condition number of one its blocks"? Or something similar? I wonder if there is formal theory that might help explain these results even more.

Also, would applying this procedure be a good method to identify what unit operation we have to replace with surrogates/implicit functions? Simply see at every iteration what block is giving the highest condition number?

@Robbybp
Copy link
Owner Author

Robbybp commented Apr 29, 2024

Are you aware of any formal theorem that might say something like "The condition number of a block matrix is at least the largest condition number of one its blocks"?

I thought this would be fairly straightforward, but tried for a little bit and didn't come up with anything. This post https://mathoverflow.net/questions/265887/bounding-the-minimum-singular-value-of-a-block-triangular-matrix provides bounds on the minimum singular value, but they involve the off-diagonal block. It looks like, while the determinant of a block triangular matrix can easily be characterized in terms of the diagonal blocks, the singular values cannot.

@sbugosen
Copy link
Collaborator

sbugosen commented May 3, 2024

To run the full space sweep with condition number calculation do:

python run_fullspace_sweep.py --calc_condition_number, else, python run_fullspace_sweep.py

To plot the condition number grid do:

python plot_condition_num_grid.py data/fullspace-sweep.csv

for key in config.PARAM_SWEEP_KEYS:
if key not in ("X", "P", "Termination"):
for key in df_keys:
if key not in ("X", "P", "Termination", "Condition Number"):
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In these cases where we hit an error, we probably do want to use INVALID for the condition number, right? The solution hasn't been loaded into the model, so any condition number we calculate won't be meaningful (it will just be computed at the initial guess). (Also, we're not calculating and appending condition number here, so I think this will raise an error when we try to convert to data frame.)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for this Robert, I made the changes.

svi/auto_thermal_reformer/run_fullspace_sweep.py Outdated Show resolved Hide resolved
Copy link
Owner Author

@Robbybp Robbybp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before merging this PR, I'd like to separate data generation from plotting. I'm imagining a script like generate_iterate_data.py that runs the optimization for a specified instance or subset of instances, and writes out a CSV file for each instance with all the information we need from each iterate. Then we can have a separate script like plot_iterate_data.py with options for what to plot.

Comment on lines 147 to 148
for iters in range(1, full_subset[2] + 1):
full_list.append(full(X=full_subset[0], P=full_subset[1], iters=iters))
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't need to re-run the optimization to compute condition number each iteration. We can use ConditioningCallback to make this easier:

solver = config.get_optimization_solver()
from svi.cyipopt import ConditioningCallback
# Set an intermediate callback that calculates condition number each iteration
callback = ConditioningCallback()
solver.config.intermediate_callback = callback
solver.solve(model, tee=True)
full_list = callback.condition_numbers

Copy link
Collaborator

@sbugosen sbugosen May 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! Thank you, I was modifying this script to include the ConditioningCallback, I'll push it soon. I did not include it in run_fullspace_sweep.py because it took too long to run the 64 instances, but I'll include it in condition_num_plots.py.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just as personal reminder of our conversation, an example would be to plot iterations vs constraint residual of sum of mole fractions in reformer recuperator.

@Robbybp
Copy link
Owner Author

Robbybp commented May 5, 2024

Before merging this PR, I'd like to separate data generation from plotting.

This is a fairly significant change to this PR, so I'm not in a hurry to get this merged. If I were you, I'd focus on constructing the KKT matrix and collecting useful information from it for now.

@sbugosen
Copy link
Collaborator

sbugosen commented May 5, 2024

To get condition number vs iteration plots:

For unsuccessful instances:
python condition_num_plots.py --unsuccessful
For successful instances:
python condition_num_plots.py

@sbugosen
Copy link
Collaborator

sbugosen commented May 5, 2024

If I were you, I'd focus on constructing the KKT matrix and collecting useful information from it for now.

Sounds good.

@@ -0,0 +1,194 @@
# ___________________________________________________________________________
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this script is superseded by generate_iterate_data.py and plot_iterate_condition_numbers.py.

@@ -0,0 +1,85 @@
# ___________________________________________________________________________
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This script has not been superseded by any of my (more) recent commits to this PR.

@@ -0,0 +1,143 @@
# ___________________________________________________________________________
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This script has not been superseded by any of my (more) recent commits to this PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants