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

Integration of LPdiag tool #704

Merged
merged 72 commits into from
Nov 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
9797a99
Initial commit of MCA tool
glatterf42 Apr 14, 2023
c05ba51
LPdiag enhancements
marek-iiasa Apr 17, 2023
7fea85e
Move lp_diag files to better locations
glatterf42 Apr 19, 2023
6981156
[lp_diag] Add several features
marek-iiasa Apr 19, 2023
3a3a4e9
[lp_diag] Update readme.txt
marek-iiasa Apr 19, 2023
65d00c4
[lp_diag] Modify default wrk-dir to be user-independent
marek-iiasa Apr 19, 2023
b1ac60d
[lp_diag] Add temporary doc
marek-iiasa Apr 19, 2023
8a7f080
Remove old file structure
glatterf42 Apr 20, 2023
c7ecebd
Apply black, isort, flake8, mypy
glatterf42 Apr 20, 2023
3c63dbe
Resolve some flake8 errors
glatterf42 Apr 20, 2023
2a4765e
Add ability to call from the command line
glatterf42 Apr 21, 2023
1f61d0f
Avoid overwriting cli args passed to the script
glatterf42 Apr 21, 2023
1f64fbb
Enforce max line length of 88
glatterf42 Apr 21, 2023
df55828
Make LPdiag class importable to satisfy mypy
glatterf42 Apr 21, 2023
eebaa46
Use LPdiag to satisfy flake8
glatterf42 Apr 21, 2023
1186320
Change import to maybe satisfy mypy
glatterf42 Apr 21, 2023
1b3f2a2
Empty __init__ and change import to satisfy mypy
glatterf42 Apr 21, 2023
fe1fbea
Remove blank line at the end of file for flake8
glatterf42 Apr 21, 2023
e71517b
[lp_diag] Update .gitignore
marek-iiasa Jun 5, 2023
00616a3
[lp_diag] Refactor rd_mps()
marek-iiasa Jun 6, 2023
421f884
[lp_diag] Modify rd_mps() to reduce the complexity
marek-iiasa Jun 7, 2023
c65931f
[lp_diag] Reduce complexity to satisfy flake8
marek-iiasa Jun 7, 2023
3d96563
[lp_diag] Adjust main.py
marek-iiasa Jun 8, 2023
e8d4ae5
[lp_diag] Satisfy mypy per fn_out, f_out
marek-iiasa Jun 8, 2023
3bb81d5
[lp_diag] Satisfy mypy & pycharm in main.py
marek-iiasa Jun 8, 2023
f1a9ec2
[lp_diag] Add initial draft of docs
marek-iiasa Jun 27, 2023
c6f0808
[lp_diag] Diverse improvements
marek-iiasa Jun 27, 2023
d448604
[lp_diag] Change location of test-mps files
marek-iiasa Jul 4, 2023
1237431
[lp_diag] Refactor and improveme
marek-iiasa Jul 4, 2023
0b0229d
[lp_diag] Conform to flake8
marek-iiasa Jul 4, 2023
cba978d
[lp_diag] Sort import statements
marek-iiasa Jul 4, 2023
8ca4457
[lp_diag] Improve work-dir diagnostics
marek-iiasa Jul 4, 2023
cd2c57c
[lp_diag] Add to message_ix/cli.py
marek-iiasa Jul 4, 2023
0cdb467
[lp_diag] Modify message_ix/cli.py
marek-iiasa Jul 4, 2023
011407c
[lp_diag] Comment pieces of message_ix/cli.py
marek-iiasa Jul 5, 2023
637cf26
Correct typos in output generation
marek-iiasa Jul 5, 2023
dd434f1
[lp_diag] First complete version of the doc embedded in the message-i…
marek-iiasa Jul 5, 2023
4df0b37
[lp_diag] Correct spelling in doc
marek-iiasa Jul 9, 2023
70c49b8
[lp_diag] Correct typo
marek-iiasa Jul 27, 2023
929a9b3
[lp_diag] Reformat to satisfy black
marek-iiasa Jul 27, 2023
d2227d7
[lp_diag] Satisfy flake8
marek-iiasa Aug 9, 2023
fbe04f0
Add basic tests for reading in MPS files
measrainsey Oct 3, 2023
0d78269
Remove scratch code
measrainsey Oct 3, 2023
82c94a6
Fix for linting
measrainsey Oct 3, 2023
faf1f63
Clean up type comparisons as requested by ruff
glatterf42 Oct 25, 2023
5c24d98
Shorten too long lines
glatterf42 Oct 25, 2023
841ef96
Shorten more too long lines
glatterf42 Oct 25, 2023
fd96495
Enhance LPdiag tool
glatterf42 Oct 30, 2023
ce27097
Satisfy mypy by renaming variables
glatterf42 Oct 30, 2023
c0a8a2f
Add more tests including necessary data
glatterf42 Oct 30, 2023
84ab118
Cover some more lines with tests
glatterf42 Oct 30, 2023
3780215
Update documentation
glatterf42 Oct 31, 2023
e48cf16
Add #704 to release notes
glatterf42 Oct 31, 2023
5d14049
Restructure test data
glatterf42 Nov 16, 2023
7e49e61
Consolidate LPdiag .gitignore at top level
glatterf42 Nov 16, 2023
4b255de
Consolidate README files in one doc file
glatterf42 Nov 16, 2023
802af83
Ensure tests work as intended
glatterf42 Nov 16, 2023
82d3ba6
Update lp_diag.py to correctly identify columns
glatterf42 Nov 23, 2023
e75a081
Remove duplicate key in coverage config
glatterf42 Nov 23, 2023
cc3435f
Add tests/tools/__init__.py
khaeru Nov 24, 2023
922fa6e
Use the test_data_path fixture in test_lpdiag
khaeru Nov 24, 2023
f292aae
Parametrize test_lpdiag.test_error_cases
khaeru Nov 24, 2023
1bfce5c
Rename lp_diag.lpdiag → lp_diag.cli
khaeru Nov 24, 2023
12b2c73
Rename lp_diag/lp_diag.py → lp_diag/__init__.py
khaeru Nov 24, 2023
daeccb2
Rename test_lpdiag → test_lp_diag
khaeru Nov 24, 2023
f17f727
Convert lp_diag.cli.main to a click.Command
khaeru Nov 24, 2023
deb771a
Apply numpydoc style to docstrings in .lp_diag
khaeru Nov 24, 2023
1e688f5
Add § "API reference" to doc/tools/lp_diag
khaeru Nov 24, 2023
46e5e07
Un-hard-wrap lines in doc/tools/lp_diag.rst
khaeru Nov 24, 2023
51fdfa3
Add --{lo,up}-tail options for "lp-diag" CLI command
khaeru Nov 24, 2023
50dce2a
Use a working default value for --mps
khaeru Nov 24, 2023
2a6c423
Use message_ix_cli fixture to test .lp_diag.cli
khaeru Nov 24, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ __pycache__
*.pyc
*.RData*
*.tmp
*.swp
*#
*~
#*
Expand Down Expand Up @@ -59,3 +60,7 @@ prof/

# macOS
.DS_Store

# LPdiag
message_ix/tests/data/lp_diag/diet_output.txt
message_ix/tools/lp_diag/data/mps/*
1 change: 1 addition & 0 deletions RELEASE_NOTES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Code that imports from the old locations will continue to work, but will raise :
All changes
-----------

- Add the :doc:`LPdiag tool <tools/lpdiag>` to diagnose and analyze numerical issues in linear programming (LP) problems stored in MPS-format files (:pull:`704`).
- Drop support for Python 3.7, which `reached end-of-life on 2023-06-27 <https://peps.python.org/pep-0537/#lifespan>`__ (:pull:`738`).
:mod:`message_ix` now requires Python version 3.8 or greater.
- Rename :mod:`message_ix.report` (:pull:`761`).
Expand Down
181 changes: 181 additions & 0 deletions doc/tools/lp_diag.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
.. currentmodule:: message_ix.tools.lp_diag

:mod:`.lp_diag`: basic diagnostics of linear program (LP) problems
******************************************************************

.. contents::
:local:

Description
===========

``LPdiag`` provides basic information about the LP programming problems defined by corresponding MPS-format files.
The diagnostics focuses on the implied numerical properties of the underlying optimization problem.

In this context, the term ``outlier`` denotes the model entities having values in either lower or upper tail of the corresponding value distribution.
The tails are defined by the corresponding orders of magnitudes defined as :math:`int(alog(abs(val)))`, where ``val`` stands for the value of the corresponding coefficient.
The default values of the tails are equal to :math:`(-6, 6)`, respectively; they can be redefined, if desired.

The rule of thumb says: the maximum and minimum orders of magnitudes of the LP matrix coefficients passed to optimization should differ by at most four.
``LPdiag`` helps to achieve such a goal by providing info on outliers.
Such info can be used e.g., for:

- reconsideration of measurement units of the corresponding variables and relations,
- consideration of replacing `small` (in relations to other coefficients in the same row or column) elements by zero,
- splitting the corresponding rows and/or columns,
- verification of the coefficients' values.

Features
--------

The current ``LPdiag`` version provides the following information:

- characteristics of the problem (including numbers of rows, columns, non-zero coefficients and distributions of their values),
- distributions of diverse values characterizing the LP matrix,
- location (row and column) of each outlier,
- ranges of values of other coefficients in each such row or column, as well as the corresponding bounds (LHS, RHS for rows, lower and upper bounds for columns).

The functionality of ``LPdiag`` will be gradually enhanced to meet actual needs of the ``message_ix`` modelers.

Usage
=====

The tool analyzes provided MPS-format files.
We provide several small MPS files for testing local installations in :file:`message_ix/tests/data/lp_diag/`, as well as becoming familiar with ``LPdiag``.
The small MPS files are structured as follows:

- :file:`aez.mps`: agro-ecological zones, medium size.
- :file:`diet.mps`: classical small LP.
- :file:`jg_korh.mps`: tiny testing problem.
- :file:`lotfi.mps`: classical medium size.
- :file:`error_{*}.mps`: various MPS-specs testing error-handling logic in the code.

Hints on generating MPS files are provided below.
Feel free to store arbitrary large MPS files in :file:`message_ix/tools/lp_diag/data/mps/`, but note that these should not be committed to GitHub.

We suggest the following steps for becoming familiar with ``LPdiag`` and then use it for analysis of actual MPS files:

- becoming familiar with ``LPdiag``,
- prepare MPS file,
- actual analysis.

We outline each of these steps below.

Becoming familiar with ``LPdiag``
---------------------------------

Note that ``LPdiag`` should be run at the terminal prompt.

- Navigate to the folder ``message_ix/tools/lp_diag``.
- For initial testing run the following command, which will run analysis of the default (pre-specified) MPS provided in the test_mps folder.
Other provided MPS example can be run by using the ``--mps`` option explained below.::

message-ix lp-diag

- To display the available ``LPdiag`` options run::

$ message-ix lp-diag --help
Usage: message-ix lp-diag [OPTIONS]

Diagnostics of basic properties of LP problems stored in the MPS format.

Examples:
message-ix lp-diag
message-ix lp-diag --help
message-ix lp-diag --mps aez.mps --outp foo.txt

Options:
--wdir PATH Working directory.
--mps PATH MPS file name or path.
-L, --lo-tail INTEGER Magnitude order of the lower tail (default: -7).
-U, --up-tail INTEGER Magnitude order of the upper tail (default: 5).
--outp PATH Path for file output.
--help Show this message and exit.

Further details about the optional parameters:

- :program:`--wdir`: specification of the desired work-directory (by default the work-directory is the same, in which ``LPdiag`` is located).
- :program:`--mps`: name of the MPS file to be analysed; if the file is not located in the work-directory, then the name should include the path to the file (see the example above).
- :program:`--outp`: name of the file to which the output shall be redirected.
By default the output is listed to the stdout, i.e., to the terminal window unless the redirection is included in the command.
Optionally, the output can be redirected to a specified file.
Such redirection can be specified by either using the ``--outp file_name`` option, as illustrated by the second example shown above (in the output resulting from using the ``-h`` option), or by including the redirection in the corresponding command, e.g.,::

message-ix lp-diag -h > foo.txt

- :program:`--lo-tail`, :program:`--up-tail`: These are passed to :meth:`.LPdiag.print_statistics`.
To obtain the numbers of coefficients at every magnitude in the MPS file, specify equal or overlapping values::

message-ix lp-diag -L 0 -U 0 --mps file.mps


Generation of the MPS file in the :mod:`message_ix` environment
---------------------------------------------------------------

The MPS-format is the oldest but still widely used for specification of the LP problems.
Most modeling environments provide various ways of the MPS file generation.

In the ``message_ix`` environment one can generate the MPS file e.g., upon solving a :class:`message_ix.Scenario` by defining in :meth:`message_ix.Scenario.solve` the ``writemps`` option together with the desired name of the MPS file.
The MPS file will then be generated and deposited in the :file:`message_ix/model/` directory.
Details are available in the `GAMS-Documentation <https://www.gams.com/latest/docs/S_CPLEX.html#CPLEXwritemps>`__

Example of specification of the corresponding option:

.. code-block:: python

scenario.solve(solve_options={"writemps": "<file_name>.mps"})


Actual analysis
---------------

For actual analysis one needs to specify the corresponding MPS file in a command run (still in the directory ``message_ix/tools/lp_diag``)::

message-ix lp-diag --mps loc/name

…where ``loc`` and ``name`` stand for the path to the directory where the MPS-file is located, and ``name`` stands for the corresponding file-name, respectively.
Other option(s) can be included in the command, as explained above.

If the output redirection is desired (e.g., for results to be shared or composed of many lines), then run::

message-ix lp-diag --mps loc/name --outp outfile.txt

Extensions in the file names are optional.
An alternative way of output redirection is explained above.


Summary of the provided analysis results
========================================

The results are composed of the following elements:

- Info on the work-directory.
- Info during reading the MPS file:

- Should a syntax error occur during reading the file, then the corresponding exception is thrown with the corresponding details.
- Basic info during processing of each MPS section.
- Basic attributes of the read MPS.
- Distribution of values of the objective (goal function) coefficients.
- Distribution of :math:`abs(val)` of the matrix elements.
- Distribution of values of :math:`int(log10(abs(values)))`.
- Distribution of values of :math:`int(log10(abs(values)))` sorted by magnitudes of values (magnitudes of zero-occurrences skipped).
- For each (lower and upper) tail of the matrix coefficient values of the corresponding sub-matrix:

- Distributions of diverse values (:math:`value, abs(val), log10(abs(val))`) of the matrix elements.
- For each order of magnitude: number of elements
- Row-wise location of each outlier with:

1. info on other coefficients in the same row, and
2. order of magnitude of the row's LHS and RHS.
- Column-wise location of each outlier with:

1. info on other coefficients in the same column, and
2. order of magnitude of the column's lower and upper bounds.
- The processing start- and end-times.


API reference
=============

.. automodule:: message_ix.tools.lp_diag
:members:
2 changes: 2 additions & 0 deletions message_ix/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import message_ix
import message_ix.tools.add_year.cli
import message_ix.tools.lp_diag.cli

# Override the docstring of the ixmp CLI so that it masquerades as the
# message_ix CLI
Expand Down Expand Up @@ -154,6 +155,7 @@ def dl(branch, tag, path):

# Add subcommands
main.add_command(message_ix.tools.add_year.cli.main)
main.add_command(message_ix.tools.lp_diag.cli.main)

try:
import message_ix.testing.nightly
Expand Down
Loading