Skip to content

Adding IPOPT as an alternative solver for MAgPIE#871

Open
georg-schroeter wants to merge 16 commits intomagpiemodel:developfrom
georg-schroeter:ipopt_part2
Open

Adding IPOPT as an alternative solver for MAgPIE#871
georg-schroeter wants to merge 16 commits intomagpiemodel:developfrom
georg-schroeter:ipopt_part2

Conversation

@georg-schroeter
Copy link
Copy Markdown
Contributor

@georg-schroeter georg-schroeter commented Mar 13, 2026

🐦 Description of this PR 🐦

While MAgPIE and all pre- and post-processing libraries are open source, executing the MAgPIE core model so far relies on CONOPT, a proprietary solver inside the GAMS framework. This isn't ideal in several ways:

  • executing MAgPIE currently requires both a GAMS and a CONOPT4 license, raising the barrier for external users to use the model or validate results.
  • having only one solver available makes the validation of numerical results difficult, since we can't easily distinguish between changes in the model and the solver behavior.
  • relying on a single solver during development contributes to a multi-faceted lock-in effect, where e.g. problematic formulations are "ok" if the particular solver can resolve them, or potential multiple optima aren't discovered if the solver reliably picks the same local optimum.

This PR tries to alleviate parts of this by adding the option to solve MAgPIE with the open source nlp solver IPOPT instead of the proprietory CONOPT4. Note however that this isn't (yet) an equivalent drop-in solution without any downsides. First, this required several changes in the core model and post-processing formulations (see #869). Second, while we ultimately got it to work properly, it takes much longer than CONOPT4 (by about a factor 12x right now) which can be partially attributed to CONOPT's extensive preprocessing of the model, cutting its size by about 60% after removing the pre- and post-triangular parts. We might be able to cut that in the future by improving the model formulation, but it might also be increased further by future changes in the input our output related parts.

🔧 Checklist for PR creator 🔧

If a point is not applicable, check the checkbox anyway and write "non-applicable" next to the checkbox.

  • Label pull request from the label list.

    • Low risk: Simple bugfixes (missing files, updated documentation, typos) or changes in start or output scripts
    • Medium risk: Uncritical changes in the model core (e.g. moderate modifications in non-default realizations)
    • High risk: Critical changes in model core or default settings (e.g. changing a model default or adjusting a core mechanic in the model)
  • Self-review own code

    • No hard coded numbers and cluster/country/region names.
    • The new code doesn't contain declared but unused parameters or variables.
    • magpie4 R library has been updated accordingly and backwards compatible where necessary.
    • scenario_config.csv has been updated accordingly (important if default.cfg has been updated)
  • Document changes

    • Add changes to CHANGELOG.md
    • Where relevant, put In-code documentation comments
    • Properly address updates in interfaces in the module documentations
    • run goxygen::goxygen() and verify the modified code is properly documented
  • Perform test runs

    • Low risk:
      • Run a compilation check via Rscript start.R --> "compilation check"
    • Medium risk:
      • Run default run via Rscript start.R --> "default"
      • Check logs for errors/warnings
      • Fill in performance section below
    • High risk:
      • Run test runs via Rscript start.R --> "test runs"
      • Check logs for errors/warnings
      • Default run from the PR target branch for comparison
      • Provide relevant comparison plots (land-use, emissions, food prices, land-use intensity,...)
      • Fill in performance section below
  • Reporting produces no errors and no new warnings

  • Get two approving reviews (at least one from RSE)

📉 Performance 📈

  • This PR's default : 29.1 min (CONOPT), 416 min (IPOPT)
  • For comparison: runtimes of weekly test: 35.2 min

🚨 Checklist for reviewer 🚨

  • PR is labeled correctly
  • Code changes look reasonable
    • No hard coded numbers and cluster/country/region names.
    • No unnecessary increase in module interfaces
    • model behavior/performance is satisfactory.
  • Changes are properly documented
    • CHANGELOG is updated correctly
    • Updates in interfaces have been properly addressed in the module documentations
    • In-code documentation looks appropriate

@georg-schroeter georg-schroeter added enhancement New feature or request Medium risk labels Mar 13, 2026
@georg-schroeter
Copy link
Copy Markdown
Contributor Author

Now to the actual interesting part, the comparison plots between CONOPT and IPOPT solves. Separate post to keep the top about the technical changes and don't clutter the PR template.

  1. The "usual" tau and area plots:
Productivity_Landuse_Intensity_Indicator_Tau(32) Resources_Land_Cover_Cropland(18) Resources_Land_Cover_Pastures_and_Rangelands(15) Resources_Land_Cover_Forest(5) Resources_Land_Cover_Other_Land(1)
  1. Emissions:
Emissions_CO2_Land(2) Emissions_N2O_Land(3) Emissions_CH4_Land
  1. Costs and food expenditure shares:
SDG_SDG02_Food_expenditure_share(1) Costs(2)
  1. Prices:
Prices_Bioenergy Prices_Food_Expenditure_Index Prices_Index2020_Agriculture_Producer_All_products same plot, but starting in 2015: Prices_Index2020_Agriculture_Producer_All_products(1) Prices_Index2020_Agriculture_Producer_Sugar_cane Prices_Index2020_Agriculture_Producer_Temperate_cereals Prices_Index2020_Agriculture_Producer_Maize

@georg-schroeter georg-schroeter marked this pull request as ready for review March 18, 2026 16:28
@georg-schroeter
Copy link
Copy Markdown
Contributor Author

georg-schroeter commented Mar 18, 2026

Observations from the plots:

  • Pasture (and forest/other land as a side effect because total area is constant) dynamics show the only visible difference in the "usual" plots, while croparea and tau are identical. The difference in pasture area actually increases with higher numerical tolerance for IPOPT, which in my eyes points towards an issue with the implementation due to the lack of a clear driver:
Resources_Land_Cover_Pastures_and_Rangelands(14)
  • Comparing recent release versions / weekly tests shows a somewhat similar pattern, where pasture area is much more volatile than other areas between input data revisions and scenarios.
  • From inspecting the code around pasture, the issue might be that in the absence of a strong demand for pasture in any timestep, there isn't really a difference between converting pasture or other land to cropland/forest, while there is a steep cost associated with establishing pasture when needed again.
  • The other observation is around prices and price indices. As can be seen from the plots, prices (especially producer prices) are reproducible by IPOPT whenever the production volume is high enough, however our current postprocessing and reporting seems to rely a bit too much on CONOPT's behavior for low volume commodities. This is especially the case for aggregated price indices and for the historical period (which I left out in most of the above plots because they're way too high), due to what I assume as low/no croparea clusters. Before the postprocessing/reporting isn't made more robust to extreme prices for low volume commodities, price indices can't be used from IPOPT runs.

In total I have to say that I'm positively surprised how little differences there are, which I think is great news :)

Copy link
Copy Markdown
Contributor

@pascal-sauer pascal-sauer left a comment

Choose a reason for hiding this comment

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

Seeing the code duplication now I think we need a better solution for the food demand model.

@@ -0,0 +1,2 @@
name,type,reason
vm_landdiff,input,questionnaire
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This was just copied from the other realization (nlp_apr17). Does anyone know where this is coming from and if it's still needed?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The reason is that the lp_nlp_apr17 realization minimizes vm_landdiff instead of vm_cost_glo in its second solve while respecting the vm_cost_glo optimum as an upper bound.

@georg-schroeter
Copy link
Copy Markdown
Contributor Author

The implementation settings were simplified, and unused options (additional optfile, conditional solve settings) removed.

The duplication of the food demand model with ipopt as a solver was reverted, instead an upcoming rewrite of the module will change the food demand model realization to use the general optimization settings.

Copy link
Copy Markdown
Contributor

@pascal-sauer pascal-sauer left a comment

Choose a reason for hiding this comment

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

Nice and simple PR, looks good! Thanks @georg-schroeter !

);

p80_modelstat(t) = magpie.modelstat;
p80_num_nonopt(t) = magpie.numNOpt;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What is happening here, what are these variables? I assume this was copied from the conopt realization, so maybe @flohump knows?

Copy link
Copy Markdown
Contributor

@pascal-sauer pascal-sauer Mar 27, 2026

Choose a reason for hiding this comment

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

Okay found it in the gams docs:

numNOpt (integer): Number of nonoptimalities

    Available: Attribute statement (use after solve)

    This model attribute returns the number of nonoptimalities after a solve.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

A quick search on github indicates that we never check p80_num_nonopt or do anything with it, do we actually need? If p80_num_nonopt > 0 then this would be reflected in p80_modelstat, no?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

From inspecting the full.lst of several IPOPT runs, "nonopt" entries do appear, and can e.g. happen when values slightly out of bound are moved in-bound again, causing a solved equation to be slightly above the given tolerance which makes them count as non-optimal.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Okay, so you think there's enough upside to keep this in the code? My tendency is rather to delete if in doubt, but no strong feelings here

Copy link
Copy Markdown
Contributor

@pascal-sauer pascal-sauer left a comment

Choose a reason for hiding this comment

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

How would the user switch to optfile2? Would that realistically ever be done? My feeling is no, so I'd much prefer to agree on/find the one configuration that works best

put 'quality_function_max_section_steps 4' /;
put 'nlp_scaling_method gradient-based' /;
put 'nlp_scaling_max_gradient 100' /;
put 'acceptable_tol 1e-6' /;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Huh, I wasn't even aware of the acceptable concept/feature of Ipopt. Did someone (not a LLM) think this through? This only makes sense if acceptable_tol > tol, right? Our usual tol (which is feasible) is 1e-7, so 1e-6 does not make sense here. Also, 1e-6 is the default, I'd not specify default settings explicitly unless there is a reason for it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm in favor of dropping the acceptable stuff

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Our tol is 1e-8 so considerably lower. "Solved to acceptable level" followed by a "warm start" from that point actually happened sometimes with my other IPOPT runs as well. Since it's the default value, it should be removed anyway.

georg-schroeter and others added 2 commits April 1, 2026 14:44
Co-authored-by: Pascal Sauer <156898545+pascal-sauer@users.noreply.github.com>
@georg-schroeter
Copy link
Copy Markdown
Contributor Author

More Plots, now also comparing the two optfiles. Generally speaking, IPOPT with optfile 2 seems to have the better agreement with the CONOPT results, likely due to adaptive mu picking lower mu values.

Resources_Land_Cover_Cropland(19) Resources_Land_Cover_Cropland_Croparea_Bioenergy_crops Resources_Land_Cover_Pastures_and_Rangelands(16) Resources_Land_Cover_Forest(6) Resources_Land_Cover_Other_Land(2) Emissions_CO2_Land_Land_use_Change Emissions_CH4_Land_Agriculture Emissions_N2O_Land_Agriculture(2) Productivity_Yield_Bioenergy_crops Prices_Bioenergy(1) Productivity_Landuse_Intensity_Indicator_Tau(33) Prices_Index2020_Agriculture_Food_products Resources_Nitrogen_Pollution_Surplus(1) Resources_Timber_operations_Harvested_area_for_timber_Total Prices_Index2020_Agriculture_Producer_All_products(2)

@pascal-sauer
Copy link
Copy Markdown
Contributor

Not related to this PR, but It's really hard to see the red line in the plots. Maybe we should think about making the lines a little transparent, so it's easier to see overlaps?

Copy link
Copy Markdown
Contributor

@pascal-sauer pascal-sauer left a comment

Choose a reason for hiding this comment

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

I suggest some cleanup for optfile2, but good to go otherwise :)

put 'quality_function_max_section_steps 4' /;
put 'nlp_scaling_method gradient-based' /;
put 'nlp_scaling_max_gradient 100' /;
put 'acceptable_tol 1e-6' /;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The other acceptable_* should also be deleted, no?

@@ -50,9 +47,6 @@ put 'honor_original_bounds yes' /;
put 'max_iter 10000' /;
put 'linear_solver mumps' /;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is the default, no need to specify

@@ -50,9 +47,6 @@ put 'honor_original_bounds yes' /;
put 'max_iter 10000' /;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is this relevant enough to keep?

@@ -40,7 +38,6 @@ put 'mu_oracle quality-function' /;
put 'quality_function_max_section_steps 4' /;
put 'nlp_scaling_method gradient-based' /;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Delete as this is the default

@@ -40,7 +38,6 @@ put 'mu_oracle quality-function' /;
put 'quality_function_max_section_steps 4' /;
put 'nlp_scaling_method gradient-based' /;
put 'nlp_scaling_max_gradient 100' /;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Delete as this is the default

@pascal-sauer
Copy link
Copy Markdown
Contributor

pascal-sauer commented Apr 2, 2026

Oh and as I was heavily involved in this PR I think we should get another RSE review, can you have a look @tscheypidi ?

@pascal-sauer pascal-sauer requested a review from tscheypidi April 2, 2026 12:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request Medium risk

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants