[
  {
    "path": ".github/workflows/CompatHelper.yml",
    "content": "name: CompatHelper\non:\n  schedule:\n    - cron: 0 0 * * *\n  workflow_dispatch:\njobs:\n  CompatHelper:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Pkg.add(\"CompatHelper\")\n        run: julia -e 'using Pkg; Pkg.add(\"CompatHelper\")'\n      - name: CompatHelper.main()\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          COMPATHELPER_PRIV: ${{ secrets.DOCUMENTER_KEY }}\n        run: julia -e 'using CompatHelper; CompatHelper.main()'"
  },
  {
    "path": ".github/workflows/Documentation.yml",
    "content": "name: Documentation\non:\n  push:\n    branches: [master]\n    tags: '*'\n  pull_request:\n    types: [opened, synchronize, reopened]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v2\n      - uses: julia-actions/setup-julia@latest\n        with:\n          version: '1'\n      - name: Install dependencies\n        run: julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()'\n      - name: Build and deploy\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # For authentication with GitHub Actions token\n          DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} # For authentication with SSH deploy key\n        run: julia --project=docs/ docs/make.jl"
  },
  {
    "path": ".github/workflows/TagBot.yml",
    "content": "name: TagBot\non:\n  issue_comment:\n    types:\n      - created\n  workflow_dispatch:\njobs:\n  TagBot:\n    if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot'\n    runs-on: ubuntu-latest\n    steps:\n      - uses: JuliaRegistries/TagBot@v1\n        with:\n          token: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\non:\n  push:\n    branches:\n      - master\n      - /^release-.*$/\n  pull_request:\n    types: [opened, synchronize, reopened]\njobs:\n  test:\n    name: Julia ${{ matrix.julia-version }} - ${{ matrix.os }} - ${{ matrix.julia-arch }} - ${{ github.event_name }}\n    runs-on: ${{ matrix.os }}\n    strategy:\n      matrix:\n        julia-version: ['lts', '1', 'pre']\n        julia-arch: [x64]\n        os: [ubuntu-latest, windows-latest, macOS-latest]\n            \n    steps:\n      - uses: actions/checkout@v4\n      - uses: julia-actions/setup-julia@v2\n        with:\n          version: ${{ matrix.julia-version }}\n          arch: ${{ matrix.julia-arch }}\n      - uses: julia-actions/cache@v2\n      - uses: julia-actions/julia-buildpkg@v1\n      - uses: julia-actions/julia-runtest@v1\n        with:\n          annotate: true\n      - uses: julia-actions/julia-processcoverage@v1\n      - uses: codecov/codecov-action@v1\n        with:\n          file: lcov.info\n"
  },
  {
    "path": ".gitignore",
    "content": "# Julia\n*.jl.cov\n*.jl.*.cov\n*.jl.mem\nManifest.toml\n\n/dat\n/exp\n/.vscode\n/*.jl"
  },
  {
    "path": "CITATION.bib",
    "content": "@TechReport{Tulip.jl,\n    title = {{Tulip}.jl: an open-source interior-point linear optimization\n    solver with abstract linear algebra},\n    url = {https://www.gerad.ca/fr/papers/G-2019-36},\n    Journal = {Les Cahiers du Gerad},\n    Author = {Anjos, Miguel F. and Lodi, Andrea and Tanneau, Mathieu},\n    year = {2019}\n}"
  },
  {
    "path": "LICENSE.md",
    "content": "Copyright (c) 2018-2019: Mathieu Tanneau\n\nTulip.jl is licensed under the [MPL version 2.0](https://www.mozilla.org/MPL/2.0/).\n\n## License\n\n    Mozilla Public License Version 2.0\n    ==================================\n\n    1. Definitions\n    --------------\n\n    1.1. \"Contributor\"\n        means each individual or legal entity that creates, contributes to\n        the creation of, or owns Covered Software.\n\n    1.2. \"Contributor Version\"\n        means the combination of the Contributions of others (if any) used\n        by a Contributor and that particular Contributor's Contribution.\n\n    1.3. \"Contribution\"\n        means Covered Software of a particular Contributor.\n\n    1.4. \"Covered Software\"\n        means Source Code Form to which the initial Contributor has attached\n        the notice in Exhibit A, the Executable Form of such Source Code\n        Form, and Modifications of such Source Code Form, in each case\n        including portions thereof.\n\n    1.5. \"Incompatible With Secondary Licenses\"\n        means\n\n        (a) that the initial Contributor has attached the notice described\n            in Exhibit B to the Covered Software; or\n\n        (b) that the Covered Software was made available under the terms of\n            version 1.1 or earlier of the License, but not also under the\n            terms of a Secondary License.\n\n    1.6. \"Executable Form\"\n        means any form of the work other than Source Code Form.\n\n    1.7. \"Larger Work\"\n        means a work that combines Covered Software with other material, in\n        a separate file or files, that is not Covered Software.\n\n    1.8. \"License\"\n        means this document.\n\n    1.9. \"Licensable\"\n        means having the right to grant, to the maximum extent possible,\n        whether at the time of the initial grant or subsequently, any and\n        all of the rights conveyed by this License.\n\n    1.10. \"Modifications\"\n        means any of the following:\n\n        (a) any file in Source Code Form that results from an addition to,\n            deletion from, or modification of the contents of Covered\n            Software; or\n\n        (b) any new file in Source Code Form that contains any Covered\n            Software.\n\n    1.11. \"Patent Claims\" of a Contributor\n        means any patent claim(s), including without limitation, method,\n        process, and apparatus claims, in any patent Licensable by such\n        Contributor that would be infringed, but for the grant of the\n        License, by the making, using, selling, offering for sale, having\n        made, import, or transfer of either its Contributions or its\n        Contributor Version.\n\n    1.12. \"Secondary License\"\n        means either the GNU General Public License, Version 2.0, the GNU\n        Lesser General Public License, Version 2.1, the GNU Affero General\n        Public License, Version 3.0, or any later versions of those\n        licenses.\n\n    1.13. \"Source Code Form\"\n        means the form of the work preferred for making modifications.\n\n    1.14. \"You\" (or \"Your\")\n        means an individual or a legal entity exercising rights under this\n        License. For legal entities, \"You\" includes any entity that\n        controls, is controlled by, or is under common control with You. For\n        purposes of this definition, \"control\" means (a) the power, direct\n        or indirect, to cause the direction or management of such entity,\n        whether by contract or otherwise, or (b) ownership of more than\n        fifty percent (50%) of the outstanding shares or beneficial\n        ownership of such entity.\n\n    2. License Grants and Conditions\n    --------------------------------\n\n    2.1. Grants\n\n    Each Contributor hereby grants You a world-wide, royalty-free,\n    non-exclusive license:\n\n    (a) under intellectual property rights (other than patent or trademark)\n        Licensable by such Contributor to use, reproduce, make available,\n        modify, display, perform, distribute, and otherwise exploit its\n        Contributions, either on an unmodified basis, with Modifications, or\n        as part of a Larger Work; and\n\n    (b) under Patent Claims of such Contributor to make, use, sell, offer\n        for sale, have made, import, and otherwise transfer either its\n        Contributions or its Contributor Version.\n\n    2.2. Effective Date\n\n    The licenses granted in Section 2.1 with respect to any Contribution\n    become effective for each Contribution on the date the Contributor first\n    distributes such Contribution.\n\n    2.3. Limitations on Grant Scope\n\n    The licenses granted in this Section 2 are the only rights granted under\n    this License. No additional rights or licenses will be implied from the\n    distribution or licensing of Covered Software under this License.\n    Notwithstanding Section 2.1(b) above, no patent license is granted by a\n    Contributor:\n\n    (a) for any code that a Contributor has removed from Covered Software;\n        or\n\n    (b) for infringements caused by: (i) Your and any other third party's\n        modifications of Covered Software, or (ii) the combination of its\n        Contributions with other software (except as part of its Contributor\n        Version); or\n\n    (c) under Patent Claims infringed by Covered Software in the absence of\n        its Contributions.\n\n    This License does not grant any rights in the trademarks, service marks,\n    or logos of any Contributor (except as may be necessary to comply with\n    the notice requirements in Section 3.4).\n\n    2.4. Subsequent Licenses\n\n    No Contributor makes additional grants as a result of Your choice to\n    distribute the Covered Software under a subsequent version of this\n    License (see Section 10.2) or under the terms of a Secondary License (if\n    permitted under the terms of Section 3.3).\n\n    2.5. Representation\n\n    Each Contributor represents that the Contributor believes its\n    Contributions are its original creation(s) or it has sufficient rights\n    to grant the rights to its Contributions conveyed by this License.\n\n    2.6. Fair Use\n\n    This License is not intended to limit any rights You have under\n    applicable copyright doctrines of fair use, fair dealing, or other\n    equivalents.\n\n    2.7. Conditions\n\n    Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted\n    in Section 2.1.\n\n    3. Responsibilities\n    -------------------\n\n    3.1. Distribution of Source Form\n\n    All distribution of Covered Software in Source Code Form, including any\n    Modifications that You create or to which You contribute, must be under\n    the terms of this License. You must inform recipients that the Source\n    Code Form of the Covered Software is governed by the terms of this\n    License, and how they can obtain a copy of this License. You may not\n    attempt to alter or restrict the recipients' rights in the Source Code\n    Form.\n\n    3.2. Distribution of Executable Form\n\n    If You distribute Covered Software in Executable Form then:\n\n    (a) such Covered Software must also be made available in Source Code\n        Form, as described in Section 3.1, and You must inform recipients of\n        the Executable Form how they can obtain a copy of such Source Code\n        Form by reasonable means in a timely manner, at a charge no more\n        than the cost of distribution to the recipient; and\n\n    (b) You may distribute such Executable Form under the terms of this\n        License, or sublicense it under different terms, provided that the\n        license for the Executable Form does not attempt to limit or alter\n        the recipients' rights in the Source Code Form under this License.\n\n    3.3. Distribution of a Larger Work\n\n    You may create and distribute a Larger Work under terms of Your choice,\n    provided that You also comply with the requirements of this License for\n    the Covered Software. If the Larger Work is a combination of Covered\n    Software with a work governed by one or more Secondary Licenses, and the\n    Covered Software is not Incompatible With Secondary Licenses, this\n    License permits You to additionally distribute such Covered Software\n    under the terms of such Secondary License(s), so that the recipient of\n    the Larger Work may, at their option, further distribute the Covered\n    Software under the terms of either this License or such Secondary\n    License(s).\n\n    3.4. Notices\n\n    You may not remove or alter the substance of any license notices\n    (including copyright notices, patent notices, disclaimers of warranty,\n    or limitations of liability) contained within the Source Code Form of\n    the Covered Software, except that You may alter any license notices to\n    the extent required to remedy known factual inaccuracies.\n\n    3.5. Application of Additional Terms\n\n    You may choose to offer, and to charge a fee for, warranty, support,\n    indemnity or liability obligations to one or more recipients of Covered\n    Software. However, You may do so only on Your own behalf, and not on\n    behalf of any Contributor. You must make it absolutely clear that any\n    such warranty, support, indemnity, or liability obligation is offered by\n    You alone, and You hereby agree to indemnify every Contributor for any\n    liability incurred by such Contributor as a result of warranty, support,\n    indemnity or liability terms You offer. You may include additional\n    disclaimers of warranty and limitations of liability specific to any\n    jurisdiction.\n\n    4. Inability to Comply Due to Statute or Regulation\n    ---------------------------------------------------\n\n    If it is impossible for You to comply with any of the terms of this\n    License with respect to some or all of the Covered Software due to\n    statute, judicial order, or regulation then You must: (a) comply with\n    the terms of this License to the maximum extent possible; and (b)\n    describe the limitations and the code they affect. Such description must\n    be placed in a text file included with all distributions of the Covered\n    Software under this License. Except to the extent prohibited by statute\n    or regulation, such description must be sufficiently detailed for a\n    recipient of ordinary skill to be able to understand it.\n\n    5. Termination\n    --------------\n\n    5.1. The rights granted under this License will terminate automatically\n    if You fail to comply with any of its terms. However, if You become\n    compliant, then the rights granted under this License from a particular\n    Contributor are reinstated (a) provisionally, unless and until such\n    Contributor explicitly and finally terminates Your grants, and (b) on an\n    ongoing basis, if such Contributor fails to notify You of the\n    non-compliance by some reasonable means prior to 60 days after You have\n    come back into compliance. Moreover, Your grants from a particular\n    Contributor are reinstated on an ongoing basis if such Contributor\n    notifies You of the non-compliance by some reasonable means, this is the\n    first time You have received notice of non-compliance with this License\n    from such Contributor, and You become compliant prior to 30 days after\n    Your receipt of the notice.\n\n    5.2. If You initiate litigation against any entity by asserting a patent\n    infringement claim (excluding declaratory judgment actions,\n    counter-claims, and cross-claims) alleging that a Contributor Version\n    directly or indirectly infringes any patent, then the rights granted to\n    You by any and all Contributors for the Covered Software under Section\n    2.1 of this License shall terminate.\n\n    5.3. In the event of termination under Sections 5.1 or 5.2 above, all\n    end user license agreements (excluding distributors and resellers) which\n    have been validly granted by You or Your distributors under this License\n    prior to termination shall survive termination.\n\n    ************************************************************************\n    *                                                                      *\n    *  6. Disclaimer of Warranty                                           *\n    *  -------------------------                                           *\n    *                                                                      *\n    *  Covered Software is provided under this License on an \"as is\"       *\n    *  basis, without warranty of any kind, either expressed, implied, or  *\n    *  statutory, including, without limitation, warranties that the       *\n    *  Covered Software is free of defects, merchantable, fit for a        *\n    *  particular purpose or non-infringing. The entire risk as to the     *\n    *  quality and performance of the Covered Software is with You.        *\n    *  Should any Covered Software prove defective in any respect, You     *\n    *  (not any Contributor) assume the cost of any necessary servicing,   *\n    *  repair, or correction. This disclaimer of warranty constitutes an   *\n    *  essential part of this License. No use of any Covered Software is   *\n    *  authorized under this License except under this disclaimer.         *\n    *                                                                      *\n    ************************************************************************\n\n    ************************************************************************\n    *                                                                      *\n    *  7. Limitation of Liability                                          *\n    *  --------------------------                                          *\n    *                                                                      *\n    *  Under no circumstances and under no legal theory, whether tort      *\n    *  (including negligence), contract, or otherwise, shall any           *\n    *  Contributor, or anyone who distributes Covered Software as          *\n    *  permitted above, be liable to You for any direct, indirect,         *\n    *  special, incidental, or consequential damages of any character      *\n    *  including, without limitation, damages for lost profits, loss of    *\n    *  goodwill, work stoppage, computer failure or malfunction, or any    *\n    *  and all other commercial damages or losses, even if such party      *\n    *  shall have been informed of the possibility of such damages. This   *\n    *  limitation of liability shall not apply to liability for death or   *\n    *  personal injury resulting from such party's negligence to the       *\n    *  extent applicable law prohibits such limitation. Some               *\n    *  jurisdictions do not allow the exclusion or limitation of           *\n    *  incidental or consequential damages, so this exclusion and          *\n    *  limitation may not apply to You.                                    *\n    *                                                                      *\n    ************************************************************************\n\n    8. Litigation\n    -------------\n\n    Any litigation relating to this License may be brought only in the\n    courts of a jurisdiction where the defendant maintains its principal\n    place of business and such litigation shall be governed by laws of that\n    jurisdiction, without reference to its conflict-of-law provisions.\n    Nothing in this Section shall prevent a party's ability to bring\n    cross-claims or counter-claims.\n\n    9. Miscellaneous\n    ----------------\n\n    This License represents the complete agreement concerning the subject\n    matter hereof. If any provision of this License is held to be\n    unenforceable, such provision shall be reformed only to the extent\n    necessary to make it enforceable. Any law or regulation which provides\n    that the language of a contract shall be construed against the drafter\n    shall not be used to construe this License against a Contributor.\n\n    10. Versions of the License\n    ---------------------------\n\n    10.1. New Versions\n\n    Mozilla Foundation is the license steward. Except as provided in Section\n    10.3, no one other than the license steward has the right to modify or\n    publish new versions of this License. Each version will be given a\n    distinguishing version number.\n\n    10.2. Effect of New Versions\n\n    You may distribute the Covered Software under the terms of the version\n    of the License under which You originally received the Covered Software,\n    or under the terms of any subsequent version published by the license\n    steward.\n\n    10.3. Modified Versions\n\n    If you create software not governed by this License, and you want to\n    create a new license for such software, you may create and use a\n    modified version of this License if you rename the license and remove\n    any references to the name of the license steward (except to note that\n    such modified license differs from this License).\n\n    10.4. Distributing Source Code Form that is Incompatible With Secondary\n    Licenses\n\n    If You choose to distribute Source Code Form that is Incompatible With\n    Secondary Licenses under the terms of this version of the License, the\n    notice described in Exhibit B of this License must be attached.\n\n    Exhibit A - Source Code Form License Notice\n    -------------------------------------------\n\n      This Source Code Form is subject to the terms of the Mozilla Public\n      License, v. 2.0. If a copy of the MPL was not distributed with this\n      file, You can obtain one at http://mozilla.org/MPL/2.0/.\n\n    If it is not possible or desirable to put the notice in a particular\n    file, then You may include the notice in a location (such as a LICENSE\n    file in a relevant directory) where a recipient would be likely to look\n    for such a notice.\n\n    You may add additional accurate notices of copyright ownership.\n\n    Exhibit B - \"Incompatible With Secondary Licenses\" Notice\n    ---------------------------------------------------------\n\n      This Source Code Form is \"Incompatible With Secondary Licenses\", as\n      defined by the Mozilla Public License, v. 2.0.\n"
  },
  {
    "path": "NEWS.md",
    "content": "# Tulip release notes\n\n# v0.7.1 (January 8, 2021)\n\n## Bug fixes\n* Fix a bug in `add_variable!` and the handling of zero coefficients(#77, #79)\n* Fix some type instabilities (#71, #76)\n* Fix docs URL in README (#70)\n\n## New features\n* Tulip's version is exposed via `Tulip.version()` (#81)\n* Multiple centrality corrections in MPC algorithm (#75)\n* Support reading `.gz` and `.bz2` files (#72)\n\n## Others\n* Move CI to GitHub actions (#73)\n* Use new convention for test-specific dependencies (#80)\n* Bump deps (#71,#74)\n\n# v0.5.1 (August 15, 2020)\n* Fix URLs following migration to jump.dev (#51)\n* Fix bug in constraint/variable deletion (#52, #53)\n\n# v0.5.0\n* Support the MOI attribute `Name` (#47)\n* Simplify the user interface for choosing between different linear solvers (#48)\n\n# v0.4.0 (April 25, 2020)\n* Re-write data structure and interface (#44)\n* Add presolve module (#45)\n* Move `UnitBlockAngular` code to a separate package (#46)\n\n# v0.3.0 (February 29, 2020)\n\n* Improved documentation for parameter management (#43)\n* More flexible management of linear solvers (#37, #40)\n    * Introduce two new parameters to choose linear solver\n        * `ls_backend` specifies which backend is used to solve linear systems\n        * `ls_system` specifies which linear system (augmented system or normal equations) is solved\n    * Generic tests for custom linear solvers\n    * Performance improvements when using CHOLMOD\n* Improved MPS reader (#36)\n\n# v0.2.0 (December 26, 2019)\n\n* Switch type unions to constant in the MOI wrapper (#32)\n* Free MPS format reader (#33).\nSome `.mps` files in fixed MPS format may no longer be readable, due to, e.g., \n    * spaces in constraint/variable names\n    * empty name field in RHS section\n* Re-write of the linear algebra layer (#34)\n* Integration of [LDLFactorizations.jl](https://github.com/JuliaSmoothOptimizers/LDLFactorizations.jl) (#35) for solving problems in arbitrary precision.\n\n# v0.1.1 (October 25, 2019)\n\n* Support for MOI v0.9.5 (#31)\n* Catch exceptions during solving (#29)\n    * Numerical trouble during factorization\n    * Iteration and memory limits\n    * User interruptions\n* Create a `CITATION.bib` file (#26)\n* Describe problem formulations in the docs (#26)"
  },
  {
    "path": "Project.toml",
    "content": "name = \"Tulip\"\nuuid = \"6dd1b50a-3aae-11e9-10b5-ef983d2400fa\"\nversion = \"0.9.8\"\nauthors = [\"Mathieu Tanneau <mathieu.tanneau@gmail.com>\"]\n\n[deps]\nCodecBzip2 = \"523fee87-0ab8-5b00-afb7-3ecf72e48cfd\"\nCodecZlib = \"944b1d66-785c-5afd-91f1-9de20f533193\"\nKrylov = \"ba0b0d4f-ebba-5204-a429-3ac8c609bfb7\"\nLDLFactorizations = \"40e66cde-538c-5869-a4ad-c39174c6795b\"\nLinearAlgebra = \"37e2e46d-f89d-539d-b4ee-838fcccc9c8e\"\nLinearOperators = \"5c8ed15e-5a4c-59e4-a42b-c7e8811fb125\"\nLogging = \"56ddb016-857b-54e1-b83d-db4d58db5568\"\nMathOptInterface = \"b8f27783-ece8-5eb3-8dc8-9495eed66fee\"\nPrintf = \"de0858da-6303-5e67-8744-51eddeeeb8d7\"\nQPSReader = \"10f199a5-22af-520b-b891-7ce84a7b1bd0\"\nSparseArrays = \"2f01184e-e22b-5df5-ae63-d93ebab69eaf\"\nTOML = \"fa267f1f-6049-4f14-aa54-33bafae1ed76\"\nTest = \"8dfed614-e22c-5e08-85e1-65c5234f0b40\"\nTimerOutputs = \"a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f\"\n\n[compat]\nCodecBzip2 = \"0.7.2, 0.8\"\nCodecZlib = \"0.7.0\"\nKrylov = \"0.10\"\nLDLFactorizations = \"0.10\"\nLinearOperators = \"2.0\"\nMathOptInterface = \"1\"\nQPSReader = \"0.2\"\nTOML = \"1\"\nTimerOutputs = \"0.5.6\"\njulia = \"1.10\"\n"
  },
  {
    "path": "README.md",
    "content": "# Tulip\n\n[![DOI](https://zenodo.org/badge/131298750.svg)](https://zenodo.org/badge/latestdoi/131298750)\n[![](https://github.com/ds4dm/Tulip.jl/workflows/CI/badge.svg?branch=master)](https://github.com/ds4dm/Tulip.jl/actions?query=workflow%3ACI)\n[![](https://codecov.io/github/ds4dm/Tulip.jl/coverage.svg?branch=master)](https://codecov.io/github/ds4dm/Tulip.jl?branch=master)\n\n[Tulip](https://github.com/ds4dm/Tulip.jl) is an open-source interior-point solver for linear optimization, written in pure Julia.\nIt implements the homogeneous primal-dual interior-point algorithm with multiple centrality corrections, and therefore handles unbounded and infeasible problems.\nTulip’s main feature is that its algorithmic framework is disentangled from linear algebra implementations.\nThis allows to seamlessly integrate specialized routines for structured problems.\n\n## License\n\nTulip is licensed under the [MPL 2.0 license](https://github.com/ds4dm/Tulip.jl/blob/master/LICENSE.md).\n\n## Installation\n\nInstall Tulip using the Julia package manager:\n\n```julia\nimport Pkg\nPkg.add(\"Tulip\")\n```\n\n## Usage\n\nThe recommended way of using Tulip is through [JuMP](https://github.com/jump-dev/JuMP.jl) or [MathOptInterface](https://github.com/jump-dev/MathOptInterface.jl) (MOI).\n\nThe low-level interface is still under development and is likely change in the future.\nThe JuMP/MOI interface is more stable and regularly tested.\n\n### Using with JuMP\n\nTulip can be used with JuMP in indirect and [direct](https://jump.dev/JuMP.jl/stable/manual/models/#Direct-mode) modes.\nLinear objectives, linear constraints and lower/upper bounds on variables are supported.\n\n```julia\nusing JuMP\nimport Tulip\n\n# With a JuMP-level cache\nmodel = Model(Tulip.Optimizer)\n# Direct mode (faster incremental modifications)\nmodel = direct_model(Tulip.Optimizer())\n# You can also add the optimizer later\nmodel = Model()\n...\nset_optimizer(model, Tulip.Optimizer)\n```\n\nTo use a non-default numeric type (e.g., `BigFloat`), use `JuMP.GenericModel{T}` (requires JuMP v1.13):\n```julia\nusing JuMP\nimport Tulip\nmodel = JuMP.GenericModel{BigFloat}(Tulip.Optimizer{BigFloat})\n```\nNote that The correct syntax is `JuMP.GenericModel{T}(Tulip.Optimizer{T})`, i.e.,\nthe type parameter **must** match between Tulip and JuMP.\n```julia\n# Both examples below will error when calling optimize!\n# because the JuMP- and Tulip-level numerical types are different\nmodel = JuMP.GenericModel{BigFloat}(Tulip.Optimizer)\nmodel = JuMP.GenericModel{Float64}(Tulip.Optimizer{BigFloat})\n```\n\n\n### Using with MOI\n\nThe MOI-level interface is not recommended for most users, and is considered an advanced feature.\n\nThe type `Tulip.Optimizer` is parametrized by the model's arithmetic, for example, `Float64` or `BigFloat`.\nThis allows to solve problem in higher numerical precision.\nSee the documentation for more details.\n\n```julia\nimport MathOptInterface as MOI\nimport Tulip\nmodel = Tulip.Optimizer{Float64}()   # Create a model in Float64 precision\nmodel = Tulip.Optimizer()            # Defaults to the above call\nmodel = Tulip.Optimizer{BigFloat}()  # Create a model in BigFloat precision\n```\n\n## Solver parameters\n\nSee the [documentation](https://ds4dm.github.io/Tulip.jl/stable/reference/options/) for a full list of parameters.\n\nTo set parameters in JuMP, use:\n```julia\nusing JuMP, Tulip\nmodel = Model(Tulip.Optimizer)\nset_attribute(model, \"IPM_IterationsLimit\", 200)\n```\n\nTo set parameters in MathOptInterface, use:\n```julia\nusing Tulip\nimport MathOptInterface as MOI\nmodel = Tulip.Optimizer{Float64}()\nMOI.set(model, MOI.RawOptimizerAttribute(\"IPM_IterationsLimit\"), 200)\n```\n\nTo set parameters in the Tulip API, use:\n```julia\nusing Tulip\nmodel = Tulip.Model{Float64}()\nTulip.set_parameter(model, \"IPM_IterationsLimit\", 200)\n```\n\n## Command-line executable\n\nSee [app building instructions](https://github.com/ds4dm/Tulip.jl/blob/master/app/README.md).\n\n## Citing `Tulip.jl`\n\nIf you use Tulip in your work, we kindly ask that you cite the following [reference](https://doi.org/10.1007/s12532-020-00200-8) (preprint available [here](https://arxiv.org/abs/2006.08814)).\n\n```\n@Article{Tulip.jl,\n  author   = {Tanneau, Mathieu and Anjos, Miguel F. and Lodi, Andrea},\n  journal  = {Mathematical Programming Computation},\n  title    = {Design and implementation of a modular interior-point solver for linear optimization},\n  year     = {2021},\n  issn     = {1867-2957},\n  month    = feb,\n  doi      = {10.1007/s12532-020-00200-8},\n  language = {en},\n  url      = {https://doi.org/10.1007/s12532-020-00200-8},\n  urldate  = {2021-03-07},\n}\n```\n"
  },
  {
    "path": "app/.gitignore",
    "content": "# Julia\nManifest.toml\n/*.so\n/tulip_cl\n"
  },
  {
    "path": "app/Project.toml",
    "content": "name = \"TulipCL\"\nuuid = \"c587cd3b-9b28-46da-94c0-0791404bab14\"\nauthors = [\"mtanneau <mathieu.tanneau@gmail.com>\"]\nversion = \"0.1.0\"\n\n[deps]\nArgParse = \"c7e460c6-2fb9-53a9-8c5b-16f535851c63\"\nPrintf = \"de0858da-6303-5e67-8744-51eddeeeb8d7\"\nQPSReader = \"10f199a5-22af-520b-b891-7ce84a7b1bd0\"\nTulip = \"6dd1b50a-3aae-11e9-10b5-ef983d2400fa\"\n"
  },
  {
    "path": "app/README.md",
    "content": "# TulipCL\n\nApp for building a command-line executable of the [Tulip.jl](https://github.com/ds4dm/Tulip.jl) interior-point solver.\nAll commands shown below are executed in the `Tulip.jl/app/` directory.\n\n## Installation instructions\n\n1. Download and install Julia (version 1.3.1 or newer).\n\n1. Install `PackageCompiler`\n    ```bash\n    $ julia -e 'using Pkg; Pkg.add(\"PackageCompiler\")'\n    ```\n\n1. Instantiate the current environment\n    ```bash\n    $ julia --project=. -e 'using Pkg; Pkg.instantiate()'\n\n1. Build the command-line executable\n    ```julia\n    $ julia -q --project=.\n    julia> using PackageCompiler\n    julia> create_app(\".\", \"tulip_cl\", force=true, precompile_execution_file=\"precompile_app.jl\", app_name=\"tulip_cl\");\n    julia> exit()\n    ```\n    The executable will be located at `tulip_cl/bin/tulip_cl`.\n\n    While building the command-line executable, a precompilation script is executed wherein a small number of problems will be solved, thereby producing a number of logs. This process is normal.\n    For more information on how to build the app, take a look at the [PackageCompiler documentation](https://julialang.github.io/PackageCompiler.jl/dev/apps/).\n\n### Using a different version of Tulip\n\nFollowing the completion of step 3. above, this directory will contain a `Manifest.toml` that specifies the version of all packages, including that of `Tulip`.\nBy default, this will be the most recently tagged version.\n\nTo build the executable with a different version/branch of Tulip follow these instructions, tag the particular version/branch before creating the app.\n\nFor instance, to try out a local development branch, running\n```julia\njulia> ]\npkg> dev ..\n```\nwill use the current state of the Tulip.jl repository.\n\nFinally, follow the last step in the above installation guide to generate the command-line executable.\n\n## Running the command-line executable\n\nOnce the build step is performed, the executable can be called from the command line as follows:\n```bash\ncd tulip_cl/bin\n./tulip_cl [options] finst\n```\nwhere `finst` is the problem file.\n\nFor instance,\n```bash\ntulip_cl --Threads 1 --TimeLimit 3600 afiro.mps\n```\nwill solve the problem `afiro.mps` using one thread and up to 1 hour of computing time.\n\nCurrently, possible user options are\n\n| Option name | Type | Default | Description |\n|-------------|------|---------|-------------|\n| `Presolve`  | `Int`     | `1`   | Set to `0` to disable presolve, `1` to activate it |\n| `Threads`   | `Int`     | `1`   | Maximum number of threads |\n| `TimeLimit` | `Float64` | `Inf` | Time limit, in seconds |\n| `IterationsLimit` | `Int` | `500` | Maximum number of barrier iterations |\n| `Method` | `String` | `HSD` | Interior-point method |\n\nFor more information, run `tulip_cl --help`, or look at Tulip's [documentation](https://ds4dm.github.io/Tulip.jl/stable/) for more details on parameters.\n"
  },
  {
    "path": "app/precompile_app.jl",
    "content": "using TulipCL\n\nconst EXDIR = joinpath(dirname(pathof(TulipCL.Tulip)), \"../examples/dat\")\n\n# Run all example problems\nfor finst in readdir(EXDIR)\n    empty!(ARGS)\n    append!(ARGS, [\"--Threads\", \"1\", \"--TimeLimit\", \"10.0\", \"--Presolve\", \"1\", \"--Method\", \"HSD\", joinpath(EXDIR, finst)])\n    TulipCL.julia_main()\nend\n\nconst NETLIB = TulipCL.Tulip.QPSReader.fetch_netlib()\n\nfor finst in readdir(NETLIB)[1:5], ipm in [\"HSD\", \"MPC\"]\n    empty!(ARGS)\n    append!(ARGS, [\"--Threads\", \"1\", \"--TimeLimit\", \"10.0\", \"--Presolve\", \"1\", \"--Method\", ipm, joinpath(NETLIB, finst)])\n    TulipCL.julia_main()\nend\n"
  },
  {
    "path": "app/src/TulipCL.jl",
    "content": "module TulipCL\n\nusing Printf\n\nimport Tulip\nconst TLP = Tulip\n\nusing ArgParse\n\nfunction julia_main()::Cint\n    try\n        tulip_cl()\n    catch\n        Base.invokelatest(Base.display_error, Base.catch_stack())\n        return 1\n    end\n    return 0\nend\n\nfunction parse_commandline(cl_args)\n\n    s = ArgParseSettings()\n\n    @add_arg_table! s begin\n        \"--TimeLimit\"\n            help = \"Time limit, in seconds.\"\n            arg_type = Float64\n            default = Inf\n        \"--IterationsLimit\"\n            help = \"Maximum number of iterations\"\n            arg_type = Int\n            default = 500\n        \"--Threads\"\n            help = \"Maximum number of threads.\"\n            arg_type = Int\n            default = 1\n        \"--Presolve\"\n            help = \"Presolve level\"\n            arg_type = Int\n            default = 1\n        \"--Method\"\n            help = \"Interior-point method (HSD or MPC)\"\n            arg_type = String\n            default = \"HSD\"\n        \"finst\"\n            help = \"Name of instance file. Only Free MPS format is supported.\"\n            required = true\n    end\n\n    return parse_args(cl_args, s)\nend\n\nfunction tulip_cl()\n    parsed_args = parse_commandline(ARGS)\n\n    # Read model and solve\n    finst::String = parsed_args[\"finst\"]\n\n    m = TLP.Model{Float64}()\n    t = @elapsed TLP.load_problem!(m, finst)\n\n    println(\"Julia version: \", VERSION)\n    println(\"Tulip version: \", Tulip.version())\n    println(\"Problem file : \", finst)\n    @printf(\"Reading time : %.2fs\\n\\n\", t)\n\n    # Set parameters\n    m.params.OutputLevel = 1\n    m.params.IPM.TimeLimit = parsed_args[\"TimeLimit\"]\n    m.params.Threads = parsed_args[\"Threads\"]\n    m.params.Presolve.Level = parsed_args[\"Presolve\"]\n    m.params.IPM.IterationsLimit = parsed_args[\"IterationsLimit\"]\n\n    if parsed_args[\"Method\"] == \"HSD\"\n        m.params.IPM.Factory = Tulip.Factory(Tulip.HSD)\n    elseif parsed_args[\"Method\"] == \"MPC\"\n        m.params.IPM.Factory = Tulip.Factory(Tulip.MPC)\n    else\n        error(\"Invalid value for Method: $(parsed_args[\"Method\"]) (must be HSD or MPC)\")\n    end\n\n    TLP.optimize!(m)\n\n    return nothing\nend\n\nif abspath(PROGRAM_FILE) == @__FILE__\n    tulip_cl()\nend\n\nend # module\n"
  },
  {
    "path": "docs/.gitignore",
    "content": "build/\nManifest.toml"
  },
  {
    "path": "docs/Project.toml",
    "content": "[deps]\nDocumenter = \"e30172f5-a6a5-5a46-863b-614d45cd2de4\"\nJuMP = \"4076af6c-e467-56ae-b986-b466b2749572\"\nMathOptInterface = \"b8f27783-ece8-5eb3-8dc8-9495eed66fee\"\nTulip = \"6dd1b50a-3aae-11e9-10b5-ef983d2400fa\"\n"
  },
  {
    "path": "docs/make.jl",
    "content": "using Documenter, Tulip\n\nconst _FAST = findfirst(isequal(\"--fast\"), ARGS) !== nothing\n\nmakedocs(\n    sitename = \"Tulip.jl\",\n    authors = \"Mathieu Tanneau\",\n    format = Documenter.HTML(prettyurls = get(ENV, \"CI\", nothing) == \"true\"),\n    doctest = !_FAST,\n    pages = [\n        \"Home\" => \"index.md\",\n        \"Tutorials\" => Any[\n            \"tutorials/lp_example.md\",\n        ],\n        \"User manual\" => Any[\n            \"Problem formulation\" => \"manual/formulation.md\",\n            \"Algorithms\" => Any[\n                \"Homogeneous Self-Dual\" => \"manual/IPM/HSD.md\",\n                \"Predictor-Corrector\" => \"manual/IPM/MPC.md\"\n            ],\n            \"Setting options\" => \"manual/options.md\"\n        ],\n        \"Reference\" => Any[\n            \"Presolve\" => \"reference/Presolve/presolve.md\",\n            \"KKT\" => [\n                \"reference/KKT/kkt.md\",\n                \"reference/KKT/tlp_cholmod.md\",\n                \"reference/KKT/tlp_dense.md\",\n                \"reference/KKT/tlp_krylov.md\",\n                \"reference/KKT/tlp_ldlfact.md\",\n            ],\n            \"Julia API\" => \"reference/API.md\",\n            \"Attributes\" => \"reference/attributes.md\",\n            \"Options\" => \"reference/options.md\"\n        ],\n    ]\n)\n\ndeploydocs(\n    repo = \"github.com/ds4dm/Tulip.jl.git\"\n)\n"
  },
  {
    "path": "docs/src/index.md",
    "content": "```@meta\nCurrentModule = Tulip\n```\n\n# Tulip.jl\n\n## Overview\n\nTulip is an open-source interior-point solver for linear programming.\n\n\n## Installation\n\nTulip is 100% Julia, so install it just like any Julia package:\n```julia\njulia> ]\npkg> add Tulip\n```\n\nNo additional building step is required.\n\n\n## Citing `Tulip.jl`\n\nIf you use Tulip in your work, we kindly ask that you cite the following reference.\nThe PDF is freely available [here](https://www.gerad.ca/fr/papers/G-2019-36/view), and serves as a user manual for advanced users.\n\n```\n@TechReport{Tulip.jl,\n    title = {{Tulip}.jl: an open-source interior-point linear optimization\n    solver with abstract linear algebra},\n    url = {https://www.gerad.ca/fr/papers/G-2019-36},\n    Journal = {Les Cahiers du Gerad},\n    Author = {Anjos, Miguel F. and Lodi, Andrea and Tanneau, Mathieu},\n    year = {2019}\n}\n```"
  },
  {
    "path": "docs/src/manual/IPM/HSD.md",
    "content": "# Homogeneous Self-Dual algorithm\n\n```@docs\nTulip.HSD\n```\n\n## References\n\n* Anjos, M.F.; Lodi, A.; Tanneau, M.\n    [Design and implementation of a modular interior-point solver for linear optimization](https://arxiv.org/abs/2006.08814)"
  },
  {
    "path": "docs/src/manual/IPM/MPC.md",
    "content": "# Predictor-Corrector algorithm\n\n```@docs\nTulip.MPC\n```\n\n## References\n\n* Mehrotra, S.\n    [On the Implementation of a Primal-Dual Interior Point Method](https://doi.org/10.1137/0802028)\n    SIAM Journal on Optimization, 1992, 2, 575-601\n"
  },
  {
    "path": "docs/src/manual/formulation.md",
    "content": "# Problem formulation\n\n## Model input\n\nTulip takes as input LP problems of the form\n```math\n    \\begin{array}{rrcll}\n    (P) \\ \\ \\ \n    \\displaystyle \\min_{x} && c^{T}x & + \\ c_{0}\\\\\n    s.t.\n    & l^{b}_{i} \\leq & a_{i}^{T} x & \\leq u^{b}_{i} & \\forall i = 1, ..., m\\\\\n    & l^{x}_{j} \\leq & x_{j} & \\leq u^{x}_{j} & \\forall j = 1, ..., n\\\\\n    \\end{array}\n```\nwhere ``l^{b,x}, u^{b, x} \\in \\mathbb{R} \\cup \\{ - \\infty, + \\infty \\}``, i.e., some of the bounds may be infinite.\n\nThis original formulation is then converted to standard form.\n\n## Standard form\n\nInternally, Tulip solves LPs of the form\n```math\n    \\begin{array}{rl}\n    (P) \\ \\ \\ \n    \\displaystyle \\min_{x}\n    & c^{T} x + \\ c_{0}\\\\\n    s.t.\n    & A x = b\\\\\n    & l \\leq x \\leq u\\\\\n    \\end{array}\n```\nwhere ``x, c \\in \\mathbb{R}^{n}``, ``A \\in \\mathbb{R}^{m \\times n}``, ``b \\in \\mathbb{R}^{m}``,\nand  ``l, u \\in (\\mathbb{R} \\cup \\{-\\infty, +\\infty \\})^{n}``, i.e., some bounds may be infinite.\n\nThe original problem is automatically reformulated into standard form before the optimization is performed.\nThis transformation is transparent to the user."
  },
  {
    "path": "docs/src/manual/options.md",
    "content": "```@meta\nCurrentModule = Tulip\n```\n\n# Options management\n\n!!! info\n    This part of the documentation is under construction\n\nSee [Options reference](@ref) for a list of all available options and their signification.\n\n## Handling options within JuMP\n\n## Handling options within MOI\n\n## Handling options directly\n\n"
  },
  {
    "path": "docs/src/reference/API.md",
    "content": "```@autodocs\nModules = [Tulip]\nPages = [\"tulip_julia_api.jl\"]\n```"
  },
  {
    "path": "docs/src/reference/KKT/kkt.md",
    "content": "```@meta\nCurrentModule = Tulip.KKT\n```\n\n# Overview\n\nThe `KKT` module provides a modular, customizable interface for developing and selecting various approaches to solve the KKT systems.\n\n## KKT backends\n\n```@docs\nAbstractKKTBackend\n```\n\n```@docs\nDefaultKKTBackend\n```\n\n## KKT systems\n\nAll formulations below refer to a linear program in primal-dual standard form\n```math\n    \\begin{array}{rl}\n    (P) \\ \\ \\ \n    \\displaystyle \\min_{x}\n    & c^{\\top} x\\\\\n    s.t.\n    & A x = b\\\\\n    & l \\leq x \\leq u\n    \\end{array}\n    \\quad \\quad \\quad\n    \\begin{array}{rl}\n    (D) \\ \\ \\ \n    \\displaystyle \\max_{y, z}\n    & b^{\\top} y + l^{\\top}z^{l} - u^{\\top}z^{u}\\\\\n    s.t.\n    & A^{\\top}y + z^{l} - z^{u} = c\\\\\n    & z^{l}, z^{u} \\geq 0\n    \\end{array}\n```\n\n```@docs\nAbstractKKTSystem\n```\n\n```@docs\nDefaultKKTSystem\n```\n\n```@docs\nK2\n```\n\n```@docs\nK1\n```\n\n## KKT solvers\n\n```@docs\nAbstractKKTSolver\n```\n\nCustom linear solvers should (preferably) inherit from the `AbstractKKTSolver` class,\nand extend the following functions:\n\n```@docs\nsetup\n```\n\n```@docs\nupdate!\n```\n\n```@docs\nsolve!\n```"
  },
  {
    "path": "docs/src/reference/KKT/tlp_cholmod.md",
    "content": "```@meta\nCurrentModule = Tulip.KKT\n```\n\n# TlpCholmod\n\n```@docs\nTlpCholmod.Backend\n```\n\n```@docs\nTlpCholmod.CholmodSolver\n```"
  },
  {
    "path": "docs/src/reference/KKT/tlp_dense.md",
    "content": "```@meta\nCurrentModule = Tulip.KKT.TlpDense\n```\n\n# TlpDense\n\n```@docs\nTlpDense.Backend\n```\n\n```@docs\nTlpDense.DenseSolver\n```"
  },
  {
    "path": "docs/src/reference/KKT/tlp_krylov.md",
    "content": "```@meta\nCurrentModule = Tulip.KKT.TlpKrylov\n```\n\n# TlpKrylov\n\n!!! warning\n    Iterative methods are still an experimental feature.\n    Some numerical and performance issues should be expected.\n\n\n```@docs\nTlpKrylov.Backend\n```\n\n```@docs\nTlpKrylov.AbstractKrylovSolver\n```\n\n```@docs\nTlpKrylov.SPDSolver\n```\n\n```@docs\nTlpKrylov.SIDSolver\n```\n\n```@docs\nTlpKrylov.SQDSolver\n```"
  },
  {
    "path": "docs/src/reference/KKT/tlp_ldlfact.md",
    "content": "```@meta\nCurrentModule = Tulip.KKT.TlpLDLFactorizations\n```\n\n# TlpLDLFactorizations\n\n```@docs\nTlpLDLFactorizations.Backend\n```\n\n```@docs\nTlpLDLFactorizations.LDLFactSolver\n```"
  },
  {
    "path": "docs/src/reference/Presolve/presolve.md",
    "content": ""
  },
  {
    "path": "docs/src/reference/attributes.md",
    "content": "```@meta\nCurrentModule = Tulip\n```\n\n# Attribute reference\n\nAttributes are queried using [`get_attribute`](@ref) and set using [`set_attribute`](@ref).\n\n## Model attributes\n\n| Name                              | Type      | Description                   \n|:----------------------------------|:----------|:------------------------------\n| [`ModelName`](@ref)               | `String`  | Name of the model\n| [`NumberOfConstraints`](@ref)     | `Int`     | Number of constraints in the model\n| [`NumberOfVariables`](@ref)       | `Int`     | Number of variables in the model\n| [`ObjectiveValue`](@ref)          | `T`       | Objective value of the current primal solution\n| [`DualObjectiveValue`](@ref)      | `T`       | Objective value of the current dual solution\n| [`ObjectiveConstant`](@ref)       | `T`       | Value of the objective constant\n| [`ObjectiveSense`](@ref)          |           | Optimization sense\n| [`Status`](@ref)                  |           | Model status\n| [`BarrierIterations`](@ref)       | `Int`     | Number of barrier iterations\n| [`SolutionTime`](@ref)            | `Float64` | Solution time, in seconds\n\n## Variable attributes\n\n| Name                               | Type     | Description                   \n|:-----------------------------------|:---------|:------------------------------\n| [`VariableLowerBound`](@ref)       | `T`      | Variable lower bound \n| [`VariableUpperBound`](@ref)       | `T`      | Variable upper bound \n| [`VariableObjectiveCoeff`](@ref)   | `T`      | Variable objective coefficient \n| [`VariableName`](@ref)             | `String` | Variable name \n\n## Constraint attributes\n\n| Name                               | Type     | Description                   \n|:-----------------------------------|:---------|:------------------------------\n| [`ConstraintLowerBound`](@ref)     | `T`      | Constraint lower bound \n| [`ConstraintUpperBound`](@ref)     | `T`      | Constraint upper bound\n| [`ConstraintName`](@ref)           | `String` | Constraint name \n\n\n\n## Reference\n\n### Model attributes\n\n```@autodocs\nModules = [Tulip]\nPages = [\"src/attributes.jl\"]\nFilter = t -> typeof(t) === DataType && t <: Tulip.AbstractModelAttribute\n```\n\n### Variable attributes\n\n```@autodocs\nModules = [Tulip]\nPages = [\"src/attributes.jl\"]\nFilter = t -> typeof(t) === DataType && t <: Tulip.AbstractVariableAttribute\n```\n\n### Constraint attributes\n\n```@autodocs\nModules = [Tulip]\nPages = [\"src/attributes.jl\"]\nFilter = t -> typeof(t) === DataType && t <: Tulip.AbstractConstraintAttribute\n```"
  },
  {
    "path": "docs/src/reference/options.md",
    "content": "```@meta\nCurrentModule = Tulip\n```\n\n# Options reference\n\nParameters can be queried and set through the [`get_parameter`](@ref) and [`set_parameter`](@ref) functions.\n\nIn all that follows, ``\\epsilon`` refers to the numerical precision, which is given by `eps(Tv)` where `Tv` is the arithmetic of the current model.\nFor instance, in double-precision arithmetic, i.e., `Tv=Float64`, we have ``\\epsilon \\simeq 10^{-16}``.\n\n```@docs\nFactory\n```\n\n## IPM\n\n### Tolerances\n\nNumerical tolerances for the interior-point algorithm.\n\n| Parameter | Description | Default |\n|:----------|:------------|:--------|\n| `TolerancePFeas` | Primal feasibility tolerance | ``\\sqrt{\\epsilon}``\n| `ToleranceDFeas` | Dual feasibility tolerance | ``\\sqrt{\\epsilon}``\n| `ToleranceRGap`  | Relative optimality gap tolerance | ``\\sqrt{\\epsilon}``\n| `ToleranceIFeas` | Infeasibility tolerance | ``\\sqrt{\\epsilon}``\n\n### Algorithmic parameters\n\n| Parameter | Description | Default |\n|:----------|:------------|:--------|\n| `BarrierAlgorithm` | Interior-point algorithm | `1` |\n| `CorrectionLimit` | Maximum number of additional centrality corrections | `5` |\n| `StepDampFactor` | Step | `0.9995` |\n| `GammaMin` | Minimum value of ``\\gamma`` for computing correctors | `0.1`\n| `CentralityOutlierThreshold` | Relative threshold for computing extra centrality corrections | `0.1`\n| `PRegMin` | Minimum value of primal regularization | ``\\sqrt{\\epsilon}`` |\n| `DRegMin` | Minimum value of dual regularization | ``\\sqrt{\\epsilon}``\n\n### Stopping criterion\n\n| Parameter | Description | Default |\n|:----------|:------------|:--------|\n| `IterationsLimit` | Maximum number of barrier iterations | `100` |\n| `TimeLimit` | Time limit, in seconds | `Inf` |\n\n\n\n\n\n\n\n\n## KKT\n\n| Parameter | Description | Default |\n|:----------|:------------|:--------|\n| `Backend` | See [KKT backends](@ref) | [`KKT.DefaultKKTBackend`](@ref) |\n| `System` | See [KKT systems](@ref) | [`KKT.DefaultKKTSystem`](@ref) |\n\n## Linear Algebra\n\nThese parameters control the linear algebra implementation\n\n| Parameter | Description | Default |\n|:----------|:------------|:--------|\n| `MatrixFactory` | See [`Factory`](@ref) | `Factory(SparseMatrixCSC)`\n\n## Presolve\n\nThese parameters control the execution of the presolve phase.\nThey should be called as `\"Presolve_<Param>\"`.\n\n## Others\n\n| Parameter | Description | Default |\n|:----------|:------------|:--------|\n| `OutputLevel` | Controls the solver's output | `0` |\n| `Threads` | Maximum number of threads | `1` |\n| `Presolve` | Presolve (no presolve if set to ≤ 0) | `1` |"
  },
  {
    "path": "docs/src/tutorials/lp_example.md",
    "content": "# Toy example\n\nTulip can be accessed in 3 ways:\nthrough [JuMP](https://github.com/jump-dev/JuMP.jl),\nthrough [MathOptInterface](https://github.com/jump-dev/MathOptInterface.jl),\nor directly.\n\nThis tutorial illustrates, for each case, how to build a model, solve it,\nand query the solution value.\nIn all cases, we consider the small LP\n```math\n\\begin{array}{rrrl}\n    (LP) \\ \\ \\ \n    \\displaystyle Z^{*} = \\min_{x, y} & -2x & - y\\\\\n    s.t.\n    &  x & - y & \\geq -2\\\\\n    & 2x &-  y & \\leq  4\\\\\n    &  x &+ 2y & \\leq  7\\\\\n    &  x,&   y & \\geq  0\\\\\n\\end{array}\n```\nwhose optimal value and solution are ``Z^{*} = -8`` and ``(x^{*}, y^{*}) = (3, 2)``.\n\n## JuMP\n\nThe default `Model(Tulip.Optimizer)` uses `Float64` arithmetic.\nTo use a different numeric type, use `JuMP.GenericModel{T}`:\n\n```julia\nusing JuMP\nimport Tulip\nmodel = JuMP.GenericModel{BigFloat}(Tulip.Optimizer{BigFloat})\n```\n\n```jldoctest; output = false\nusing Printf\nusing JuMP\nimport Tulip\n\n# Instantiate JuMP model\nlp = Model(Tulip.Optimizer)\n\n# Create variables\n@variable(lp, x >= 0)\n@variable(lp, y >= 0)\n\n# Add constraints\n@constraint(lp, row1, x - y >= -2)\n@constraint(lp, row2, 2*x - y <= 4)\n@constraint(lp, row3, x + 2*y <= 7)\n\n# Set the objective\n@objective(lp, Min, -2*x - y)\n\n# Set some parameters\nset_optimizer_attribute(lp, \"OutputLevel\", 0)  # disable output\nset_optimizer_attribute(lp, \"Presolve_Level\", 0)     # disable presolve\n\n# Solve the problem\noptimize!(lp)\n\n# Check termination status\nst = termination_status(lp)\nprintln(\"Termination status: $st\")\n\n# Query solution value\nobjval = objective_value(lp)\nx_ = value(x)\ny_ = value(y)\n\n@printf \"Z* = %.4f\\n\" objval\n@printf \"x* = %.4f\\n\" x_\n@printf \"y* = %.4f\\n\" y_\n\n# output\n\nTermination status: OPTIMAL\nZ* = -8.0000\nx* = 3.0000\ny* = 2.0000\n```\n\n## MOI\n\n```jldoctest; output = false\nusing Printf\n\nimport MathOptInterface\nconst MOI = MathOptInterface\n\nimport Tulip\n\nlp = Tulip.Optimizer{Float64}()\n\n# Create variables\nx = MOI.add_variable(lp)\ny = MOI.add_variable(lp)\n\n# Set variable bounds\nMOI.add_constraint(lp, x, MOI.GreaterThan(0.0))  # x >= 0\nMOI.add_constraint(lp, y, MOI.GreaterThan(0.0))  # y >= 0\n\n# Add constraints\nrow1 = MOI.add_constraint(lp,\n    MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1.0, -1.0], [x, y]), 0.0),\n    MOI.GreaterThan(-2.0)\n)\nrow2 = MOI.add_constraint(lp,\n    MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([2.0, -1.0], [x, y]), 0.0),\n    MOI.LessThan(4.0)\n)\nrow3 = MOI.add_constraint(lp,\n    MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1.0,  2.0], [x, y]), 0.0),\n    MOI.LessThan(7.0)\n) \n\n# Set the objective\nMOI.set(lp,\n    MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(),\n    MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([-2.0, -1.0], [x, y]), 0.0)\n)\nMOI.set(lp, MOI.ObjectiveSense(), MOI.MIN_SENSE)\n\n# Set some parameters\nMOI.set(lp, MOI.Silent(), true)               # disable output\nMOI.set(lp, MOI.RawOptimizerAttribute(\"Presolve_Level\"), 0)  # disable presolve\n\n# Solve the problem\nMOI.optimize!(lp)\n\n# Check status\nst = MOI.get(lp, MOI.TerminationStatus())\nprintln(\"Termination status: $st\")\n\n# Query solution value\nobjval = MOI.get(lp, MOI.ObjectiveValue())\nx_ = MOI.get(lp, MOI.VariablePrimal(), x)\ny_ = MOI.get(lp, MOI.VariablePrimal(), y)\n\n@printf \"Z* = %.4f\\n\" objval\n@printf \"x* = %.4f\\n\" x_\n@printf \"y* = %.4f\\n\" y_\n\n# output\n\nTermination status: OPTIMAL\nZ* = -8.0000\nx* = 3.0000\ny* = 2.0000\n```\n\n## Tulip\n\n!!! warning\n    Tulip's low-level API should not be considered stable nor complete.\n    The recommended way to use Tulip is through JuMP/MOI as shown above.\n\n\n```jldoctest; output = false\nusing Printf\nimport Tulip\n\n# Instantiate Tulip object\nlp = Tulip.Model{Float64}()\npb = lp.pbdata  # Internal problem data\n\n# Create variables\nx = Tulip.add_variable!(pb, Int[], Float64[], -2.0, 0.0, Inf, \"x\")\ny = Tulip.add_variable!(pb, Int[], Float64[], -1.0, 0.0, Inf, \"y\")\n\n# Add constraints\nrow1 = Tulip.add_constraint!(pb, [x, y], [1.0, -1.0], -2.0, Inf, \"row1\")\nrow2 = Tulip.add_constraint!(pb, [x, y], [2.0, -1.0], -Inf, 4.0, \"row2\")\nrow3 = Tulip.add_constraint!(pb, [x, y], [1.0,  2.0], -Inf, 7.0, \"row3\")\n\n# Set the objective\n# Nothing to do here as objective is already declared\n\n# Set some parameters\nTulip.set_parameter(lp, \"OutputLevel\", 0)  # disable output\nTulip.set_parameter(lp, \"Presolve_Level\", 0)     # disable presolve\n\n# Solve the problem\nTulip.optimize!(lp)\n\n# Check termination status\nst = Tulip.get_attribute(lp, Tulip.Status())\nprintln(\"Termination status: $st\")\n\n# Query solution value\nobjval = Tulip.get_attribute(lp, Tulip.ObjectiveValue())\nx_ = lp.solution.x[x]\ny_ = lp.solution.x[y]\n\n@printf \"Z* = %.4f\\n\" objval\n@printf \"x* = %.4f\\n\" x_\n@printf \"y* = %.4f\\n\" y_\n\n# output\n\nTermination status: Trm_Optimal\nZ* = -8.0000\nx* = 3.0000\ny* = 2.0000\n```"
  },
  {
    "path": "examples/.gitignore",
    "content": "dat/"
  },
  {
    "path": "examples/freevars.jl",
    "content": "using LinearAlgebra\nusing SparseArrays\nusing Test\n\nimport Tulip\nTLP = Tulip\n\nINSTANCE_DIR = joinpath(@__DIR__, \"dat\")\n\nfunction ex_freevars(::Type{Tv};\n    atol::Tv = 100 * sqrt(eps(Tv)),\n    rtol::Tv = 100 * sqrt(eps(Tv)),\n    kwargs...\n) where{Tv}\n    #=\n    Example with free variables\n\n    min       x1 +   x2 + x3\n    s.t.    2 x1 +   x2      >= 2\n              x1 + 2 x2      >= 2\n              x1 +   x2 + x3 >= 0\n    =#\n    m = TLP.Model{Tv}()\n    m.params.OutputLevel = 1\n    # Set optional parameters\n    for (k, val) in kwargs\n        TLP.set_parameter(m, String(k), val)\n    end\n\n    # Read problem and solve\n    TLP.load_problem!(m, joinpath(INSTANCE_DIR, \"lpex_freevars.mps\"))\n    TLP.optimize!(m)\n\n    # Check status\n    @test TLP.get_attribute(m, TLP.Status()) == TLP.Trm_Optimal\n    z = TLP.get_attribute(m, TLP.ObjectiveValue())\n    @test isapprox(z, 0, atol=atol, rtol=rtol)\n    @test m.solution.primal_status == TLP.Sln_Optimal\n    @test m.solution.dual_status   == TLP.Sln_Optimal\n\n    # Check optimal solution\n    x1 = m.solution.x[1]\n    x2 = m.solution.x[2]\n    x3 = m.solution.x[3]\n\n    # Check primal feasibility (note there's no unique solution)\n    @test 2*x1 +   x2      >= 2 - atol\n    @test   x1 + 2*x2      >= 2 - atol\n    @test   x1 +   x2 + x3 >= -atol\n    \n    # Free variables should have zero reduced cost\n    s1 = m.solution.s_lower[1] - m.solution.s_upper[1]\n    s2 = m.solution.s_lower[2] - m.solution.s_upper[2]\n    s3 = m.solution.s_lower[3] - m.solution.s_upper[3]\n    @test isapprox(s1, 0, atol=atol, rtol=rtol)\n    @test isapprox(s2, 0, atol=atol, rtol=rtol)\n    @test isapprox(s3, 0, atol=atol, rtol=rtol)\nend\n\nif abspath(PROGRAM_FILE) == @__FILE__\n    ex_freevars(Float64)\nend"
  },
  {
    "path": "examples/infeasible.jl",
    "content": "using LinearAlgebra\nusing SparseArrays\nusing Test\n\nimport Tulip\nTLP = Tulip\n\nINSTANCE_DIR = joinpath(@__DIR__, \"dat\")\n\nfunction ex_infeasible(::Type{Tv};\n    atol::Tv = 100 * sqrt(eps(Tv)),\n    rtol::Tv = 100 * sqrt(eps(Tv)),\n    kwargs...\n) where{Tv}\n    #=\n    Infeasible example\n\n    min     x1 + x2\n    s.t.    x1 + x2 =  1\n            x1 - x2 =  0\n                 x2 =  1\n            x1, x2  >= 0\n    =#\n    m = TLP.Model{Tv}()\n    m.params.OutputLevel = 1\n    # Set optional parameters\n    for (k, val) in kwargs\n        TLP.set_parameter(m, String(k), val)\n    end\n\n    # Read problem from .mps file and solve\n    TLP.load_problem!(m, joinpath(INSTANCE_DIR, \"lpex_inf.mps\"))\n    TLP.optimize!(m)\n\n    # Check status\n    @test TLP.get_attribute(m, TLP.Status()) == TLP.Trm_PrimalInfeasible\n    @test m.solution.primal_status == TLP.Sln_Unknown\n    @test m.solution.dual_status   == TLP.Sln_InfeasibilityCertificate\n    z = TLP.get_attribute(m, TLP.ObjectiveValue())\n    @test z == zero(Tv)  # Primal infeasible --> solution is zero\n\n    # Check unbounded dual ray\n    y1 = m.solution.y_lower[1] - m.solution.y_upper[1]\n    y2 = m.solution.y_lower[2] - m.solution.y_upper[2]\n    y3 = m.solution.y_lower[3] - m.solution.y_upper[3]\n    s1 = m.solution.s_lower[1] - m.solution.s_upper[1]\n    s2 = m.solution.s_lower[2] - m.solution.s_upper[2]\n\n    @test y1 + y3 >= atol  # dual cost should be > 0\n    @test isapprox(y1 + y2      + s1, 0, atol=atol, rtol=rtol)\n    @test isapprox(y1 - y2 + y3 + s2, 0, atol=atol, rtol=rtol)\n    @test s1 >= -atol\n    @test s2 >= -atol\nend\n\nif abspath(PROGRAM_FILE) == @__FILE__\n    ex_infeasible(Float64)\nend\n"
  },
  {
    "path": "examples/optimal.jl",
    "content": "using LinearAlgebra\nusing SparseArrays\nusing Test\n\nimport Tulip\nTLP = Tulip\n\nINSTANCE_DIR = joinpath(@__DIR__, \"dat\")\n\nfunction ex_optimal(::Type{Tv};\n    atol::Tv = 100 * sqrt(eps(Tv)),\n    rtol::Tv = 100 * sqrt(eps(Tv)),\n    kwargs...\n) where{Tv}\n\n    #=\n    Bounded example\n\n    min     x1 + 2*x2\n    s.t.    x1 + x2 =  1\n            x1 - x2 =  0\n            0 <= x1 <= 1\n            0 <= x2 <= 1\n    =#\n    m = TLP.Model{Tv}()\n    m.params.OutputLevel = 1\n    # Set optional parameters\n    for (k, val) in kwargs\n        TLP.set_parameter(m, String(k), val)\n    end\n\n    # Read problem and solve\n    TLP.load_problem!(m, joinpath(INSTANCE_DIR, \"lpex_opt.mps\"))\n    TLP.optimize!(m)\n\n    # Check status\n    @test TLP.get_attribute(m, TLP.Status()) == TLP.Trm_Optimal\n    z = TLP.get_attribute(m, TLP.ObjectiveValue())\n    @test isapprox(z, 3 // 2, atol=atol, rtol=rtol)\n    @test m.solution.primal_status == TLP.Sln_Optimal\n    @test m.solution.dual_status   == TLP.Sln_Optimal\n\n    # Check primal solution\n    x1 = m.solution.x[1]\n    x2 = m.solution.x[2]\n    Ax1 = m.solution.Ax[1]\n    Ax2 = m.solution.Ax[2]\n\n    @test isapprox(x1, 1 // 2, atol=atol, rtol=rtol)\n    @test isapprox(x2, 1 // 2, atol=atol, rtol=rtol)\n    @test isapprox(Ax1, 1, atol=atol, rtol=rtol)\n    @test isapprox(Ax2, 0, atol=atol, rtol=rtol)\n\n    # Check duals\n    y1 = m.solution.y_lower[1] - m.solution.y_upper[1]\n    y2 = m.solution.y_lower[2] - m.solution.y_upper[2]\n    s1 = m.solution.s_lower[1] - m.solution.s_upper[1]\n    s2 = m.solution.s_lower[2] - m.solution.s_upper[2]\n    @test isapprox(y1,  3 // 2, atol=atol, rtol=rtol)\n    @test isapprox(y2, -1 // 2, atol=atol, rtol=rtol)\n    @test isapprox(s1, 0, atol=atol, rtol=rtol)\n    @test isapprox(s2, 0, atol=atol, rtol=rtol)\nend\n\nif abspath(PROGRAM_FILE) == @__FILE__\n    ex_optimal(Float64)\nend"
  },
  {
    "path": "examples/optimal_other_type.jl",
    "content": "using Tulip\nimport MathOptInterface\nconst MOI = MathOptInterface\nusing Test\n\nconst T = Float32\n\nlp = Tulip.Optimizer{T}()\n\n# Create variables\nx = MOI.add_variable(lp)\ny = MOI.add_variable(lp)\n\n# Set variable bounds\nMOI.add_constraint(lp, x, MOI.GreaterThan(T(0)))  # x >= 0\nMOI.add_constraint(lp, y, MOI.GreaterThan(T(0)))  # y >= 0\n\n# Add constraints\nrow1 = MOI.add_constraint(lp,\n    MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(T[1.0, -1.0], [x, y]), T(0)),\n    MOI.GreaterThan(T(-2))\n)\nrow2 = MOI.add_constraint(lp,\n    MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(T[2.0, -1.0], [x, y]), T(0)),\n    MOI.LessThan(T(4))\n)\nrow3 = MOI.add_constraint(lp,\n    MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(T[1.0,  2.0], [x, y]), T(0)),\n    MOI.LessThan(T(7))\n) \n\n# Set the objective\nMOI.set(lp,\n    MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float32}}(),\n    MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(T[-2.0, -1.0], [x, y]), T(0))\n)\nMOI.set(lp, MOI.ObjectiveSense(), MOI.MIN_SENSE)\n\nMOI.optimize!(lp)\n\nobjval = MOI.get(lp, MOI.ObjectiveValue())\nx_ = MOI.get(lp, MOI.VariablePrimal(), x)\ny_ = MOI.get(lp, MOI.VariablePrimal(), y)\n\n@test objval ≈ -8\n@test x_ ≈ 3\n@test y_ ≈ 2\n@test objval isa Float32\n@test x_ isa Float32\n@test y_ isa Float32\n"
  },
  {
    "path": "examples/unbounded.jl",
    "content": "using LinearAlgebra\nusing SparseArrays\nusing Test\n\nimport Tulip\nTLP = Tulip\n\nINSTANCE_DIR = joinpath(@__DIR__, \"dat\")\n\nfunction ex_unbounded(::Type{Tv};\n    atol::Tv = 100 * sqrt(eps(Tv)),\n    rtol::Tv = 100 * sqrt(eps(Tv)),\n    kwargs...\n) where{Tv}\n    #=\n    Unbounded example\n\n    min     -x1 + -x2\n            x1 - x2 =  1\n            x1, x2  >= 0\n    =#\n    m = TLP.Model{Tv}()\n    m.params.OutputLevel = 1\n    # Set optional parameters\n    for (k, val) in kwargs\n        TLP.set_parameter(m, String(k), val)\n    end\n\n    # Read problem from .mps file and solve\n    TLP.load_problem!(m, joinpath(INSTANCE_DIR, \"lpex_ubd.mps\"))\n    TLP.optimize!(m)\n\n    # Check status\n    @test TLP.get_attribute(m, TLP.Status()) == TLP.Trm_DualInfeasible\n    @test m.solution.primal_status == TLP.Sln_InfeasibilityCertificate\n    @test m.solution.dual_status   == TLP.Sln_Unknown\n\n    # Check unbounded ray\n    x1 = m.solution.x[1]\n    x2 = m.solution.x[2]\n    Ax1 = m.solution.Ax[1]\n\n    @test x1 >= -atol\n    @test x2 >= -atol\n    @test isapprox(Ax1, 0, atol=atol, rtol=rtol)\n    @test -x1 - x2 <= -atol\n\n\n    zp = TLP.get_attribute(m, TLP.ObjectiveValue())\n    @test zp == (-x1 - x2)\n\n    zd = TLP.get_attribute(m, TLP.DualObjectiveValue())\n    @test zd == zero(Tv)\n\nend\n\nif abspath(PROGRAM_FILE) == @__FILE__\n    ex_unbounded(Float64)\nend\n"
  },
  {
    "path": "src/IPM/HSD/HSD.jl",
    "content": "\"\"\"\n    HSD\n\nSolver for the homogeneous self-dual algorithm.\n\"\"\"\nmutable struct HSD{T, Tv, Tb, Ta, Tk} <: AbstractIPMOptimizer{T}\n\n    # Problem data, in standard form\n    dat::IPMData{T, Tv, Tb, Ta}\n\n    # =================\n    #   Book-keeping\n    # =================\n    niter::Int                        # Number of IPM iterations\n    solver_status::TerminationStatus  # Optimization status\n    primal_status::SolutionStatus     # Primal solution status\n    dual_status::SolutionStatus       # Dual   solution status\n\n    primal_objective::T    # Primal objective value: (c'x) / τ\n    dual_objective::T      # Dual objective value: (b'y + l' zl - u'zu) / τ\n\n    timer::TimerOutput\n\n    #=====================\n        Working memory\n    =====================#\n    pt::Point{T, Tv}       # Current primal-dual iterate\n    res::Residuals{T, Tv}  # Residuals at current iterate\n    kkt::Tk\n    regP::Tv  # primal regularization\n    regD::Tv  # dual regularization\n    regG::T   # gap regularization\n\n    function HSD(\n        dat::IPMData{T, Tv, Tb, Ta}, kkt_options::KKTOptions{T}\n    ) where{T, Tv<:AbstractVector{T}, Tb<:AbstractVector{Bool}, Ta<:AbstractMatrix{T}}\n\n        m, n = dat.nrow, dat.ncol\n        p = sum(dat.lflag) + sum(dat.uflag)\n\n        # Allocate some memory\n        pt  = Point{T, Tv}(m, n, p, hflag=true)\n        res = Residuals(\n            tzeros(Tv, m), tzeros(Tv, n), tzeros(Tv, n),\n            tzeros(Tv, n), zero(T),\n            zero(T), zero(T), zero(T), zero(T), zero(T)\n        )\n\n        # Initial regularizations\n        regP = tones(Tv, n)\n        regD = tones(Tv, m)\n        regG = one(T)\n\n        kkt = KKT.setup(dat.A, kkt_options.System, kkt_options.Backend)\n        Tk = typeof(kkt)\n\n        return new{T, Tv, Tb, Ta, Tk}(dat,\n            0, Trm_Unknown, Sln_Unknown, Sln_Unknown,\n            T(Inf), T(-Inf),\n            TimerOutput(),\n            pt, res, kkt, regP, regD, regG\n        )\n    end\n\nend\n\ninclude(\"step.jl\")\n\n\n\"\"\"\n    compute_residuals!(::HSD, res, pt, A, b, c, uind, uval)\n\nIn-place computation of primal-dual residuals at point `pt`.\n\"\"\"\n# TODO: check whether having just hsd as argument makes things slower\n# TODO: Update solution status\nfunction compute_residuals!(hsd::HSD{T}\n) where{T}\n\n    pt, res = hsd.pt, hsd.res\n    dat = hsd.dat\n\n    # Primal residual\n    # rp = t*b - A*x\n    axpby!(pt.τ, dat.b, zero(T), res.rp)\n    mul!(res.rp, dat.A, pt.x, -one(T), one(T))\n\n    # Lower-bound residual\n    # rl_j = τ*l_j - (x_j - xl_j)  if l_j ∈ R\n    #      = 0                     if l_j = -∞\n    @. res.rl = (- pt.x + pt.xl + pt.τ * dat.l) * dat.lflag\n\n    # Upper-bound residual\n    # ru_j = τ*u_j - (x_j + xu_j)  if u_j ∈ R\n    #      = 0                     if u_j = +∞\n    @. res.ru = (- pt.x - pt.xu + pt.τ * dat.u) * dat.uflag\n\n    # Dual residual\n    # rd = t*c - A'y - zl + zu\n    axpby!(pt.τ, dat.c, zero(T), res.rd)\n    mul!(res.rd, transpose(dat.A), pt.y, -one(T), one(T))\n    @. res.rd += pt.zu .* dat.uflag - pt.zl .* dat.lflag\n\n    # Gap residual\n    # rg = c'x - (b'y + l'zl - u'zu) + k\n    res.rg = pt.κ + (dot(dat.c, pt.x) - (\n        dot(dat.b, pt.y)\n        + dot(dat.l .* dat.lflag, pt.zl)\n        - dot(dat.u .* dat.uflag, pt.zu)\n    ))\n\n    # Residuals norm\n    res.rp_nrm = norm(res.rp, Inf)\n    res.rl_nrm = norm(res.rl, Inf)\n    res.ru_nrm = norm(res.ru, Inf)\n    res.rd_nrm = norm(res.rd, Inf)\n    res.rg_nrm = norm(res.rg, Inf)\n\n    # Compute primal and dual bounds\n    hsd.primal_objective = dot(dat.c, pt.x) / pt.τ + dat.c0\n    hsd.dual_objective = (\n        dot(dat.b, pt.y)\n        + dot(dat.l .* dat.lflag, pt.zl)\n        - dot(dat.u .* dat.uflag, pt.zu)\n    ) / pt.τ + dat.c0\n\n    return nothing\nend\n\n\n\"\"\"\n    update_solver_status!()\n\nUpdate status and return true if solver should stop.\n\"\"\"\nfunction update_solver_status!(hsd::HSD{T}, ϵp::T, ϵd::T, ϵg::T, ϵi::T) where{T}\n    hsd.solver_status = Trm_Unknown\n\n    pt, res = hsd.pt, hsd.res\n    dat = hsd.dat\n\n    ρp = max(\n        res.rp_nrm / (pt.τ * (one(T) + norm(dat.b, Inf))),\n        res.rl_nrm / (pt.τ * (one(T) + norm(dat.l .* dat.lflag, Inf))),\n        res.ru_nrm / (pt.τ * (one(T) + norm(dat.u .* dat.uflag, Inf)))\n    )\n    ρd = res.rd_nrm / (pt.τ * (one(T) + norm(dat.c, Inf)))\n    ρg = abs(hsd.primal_objective - hsd.dual_objective) / (one(T) + abs(hsd.dual_objective))\n\n    # Check for feasibility\n    if ρp <= ϵp\n        hsd.primal_status = Sln_FeasiblePoint\n    else\n        hsd.primal_status = Sln_Unknown\n    end\n\n    if ρd <= ϵd\n        hsd.dual_status = Sln_FeasiblePoint\n    else\n        hsd.dual_status = Sln_Unknown\n    end\n\n    # Check for optimal solution\n    if ρp <= ϵp && ρd <= ϵd && ρg <= ϵg\n        hsd.primal_status = Sln_Optimal\n        hsd.dual_status   = Sln_Optimal\n        hsd.solver_status = Trm_Optimal\n        return nothing\n    end\n\n    # Check for infeasibility certificates\n    if max(\n        norm(dat.A * pt.x, Inf),\n        norm((pt.x .- pt.xl) .* dat.lflag, Inf),\n        norm((pt.x .+ pt.xu) .* dat.uflag, Inf)\n    ) * (norm(dat.c, Inf) / max(1, norm(dat.b, Inf))) < - ϵi * dot(dat.c, pt.x)\n        # Dual infeasible, i.e., primal unbounded\n        hsd.primal_status = Sln_InfeasibilityCertificate\n        hsd.solver_status = Trm_DualInfeasible\n        return nothing\n    end\n\n    δ = dat.A' * pt.y .+ (pt.zl .* dat.lflag) .- (pt.zu .* dat.uflag)\n    if norm(δ, Inf) * max(\n        norm(dat.l .* dat.lflag, Inf),\n        norm(dat.u .* dat.uflag, Inf),\n        norm(dat.b, Inf)\n    ) / (max(one(T), norm(dat.c, Inf)))  < (dot(dat.b, pt.y) + dot(dat.l .* dat.lflag, pt.zl)- dot(dat.u .* dat.uflag, pt.zu)) * ϵi\n        # Primal infeasible\n        hsd.dual_status = Sln_InfeasibilityCertificate\n        hsd.solver_status = Trm_PrimalInfeasible\n        return nothing\n    end\n\n    return nothing\nend\n\n\n\"\"\"\n    optimize!\n\n\"\"\"\nfunction ipm_optimize!(hsd::HSD{T}, params::IPMOptions{T}) where{T}\n    # TODO: pre-check whether model needs to be re-optimized.\n    # This should happen outside of this function\n    dat = hsd.dat\n\n    # Initialization\n    TimerOutputs.reset_timer!(hsd.timer)\n    tstart = time()\n    hsd.niter = 0\n\n    # Print information about the problem\n    if params.OutputLevel > 0\n        @printf \"\\nOptimizer info (HSD)\\n\"\n        @printf \"Constraints  : %d\\n\" dat.nrow\n        @printf \"Variables    : %d\\n\" dat.ncol\n        bmin, bmax = extrema(dat.b)\n        @printf \"RHS          : [%+.2e, %+.2e]\\n\" bmin bmax\n        lmin, lmax = extrema(dat.l .* dat.lflag)\n        @printf \"Lower bounds : [%+.2e, %+.2e]\\n\" lmin lmax\n        lmin, lmax = extrema(dat.u .* dat.uflag)\n        @printf \"Upper bounds : [%+.2e, %+.2e]\\n\" lmin lmax\n\n\n        @printf \"\\nLinear solver options\\n\"\n        @printf \"  %-12s : %s\\n\" \"Arithmetic\" KKT.arithmetic(hsd.kkt)\n        @printf \"  %-12s : %s\\n\" \"Backend\" KKT.backend(hsd.kkt)\n        @printf \"  %-12s : %s\\n\" \"System\" KKT.linear_system(hsd.kkt)\n    end\n\n    # IPM LOG\n    if params.OutputLevel > 0\n        @printf \"\\n%4s  %14s  %14s  %8s %8s %8s  %7s  %4s\\n\" \"Itn\" \"PObj\" \"DObj\" \"PFeas\" \"DFeas\" \"GFeas\" \"Mu\" \"Time\"\n    end\n\n    # Set starting point\n    hsd.pt.x   .= zero(T)\n    hsd.pt.xl  .= one(T) .* dat.lflag\n    hsd.pt.xu  .= one(T) .* dat.uflag\n\n    hsd.pt.y   .= zero(T)\n    hsd.pt.zl  .= one(T) .* dat.lflag\n    hsd.pt.zu  .= one(T) .* dat.uflag\n\n    hsd.pt.τ   = one(T)\n    hsd.pt.κ   = one(T)\n\n    update_mu!(hsd.pt)\n\n    # Main loop\n    # Iteration 0 corresponds to the starting point.\n    # Therefore, there is no numerical factorization before the first log is printed.\n    # If the maximum number of iterations is set to 0, the only computation that occurs\n    # is computing the residuals at the initial point.\n    @timeit hsd.timer \"Main loop\" while(true)\n\n        # I.A - Compute residuals at current iterate\n        @timeit hsd.timer \"Residuals\" compute_residuals!(hsd)\n\n        update_mu!(hsd.pt)\n\n        # I.B - Log\n        # TODO: Put this in a logging function\n        ttot = time() - tstart\n        if params.OutputLevel > 0\n            # Display log\n            @printf \"%4d\" hsd.niter\n\n            # Objectives\n            ϵ = dat.objsense ? one(T) : -one(T)\n            @printf \"  %+14.7e\" ϵ * hsd.primal_objective\n            @printf \"  %+14.7e\" ϵ * hsd.dual_objective\n\n            # Residuals\n            @printf \"  %8.2e\" max(hsd.res.rp_nrm, hsd.res.ru_nrm)\n            @printf \" %8.2e\" hsd.res.rd_nrm\n            @printf \" %8.2e\" hsd.res.rg_nrm\n\n            # Mu\n            @printf \"  %7.1e\" hsd.pt.μ\n\n            # Time\n            @printf \"  %.2f\" ttot\n\n            print(\"\\n\")\n        end\n\n        # TODO: check convergence status\n        # TODO: first call an `compute_convergence status`,\n        #   followed by a check on the solver status to determine whether to stop\n        # In particular, user limits should be checked last (if an optimal solution is found,\n        # we want to report optimal, not user limits)\n        @timeit hsd.timer \"update status\" update_solver_status!(hsd,\n            params.TolerancePFeas,\n            params.ToleranceDFeas,\n            params.ToleranceRGap,\n            params.ToleranceIFeas\n        )\n\n        if (\n            hsd.solver_status == Trm_Optimal\n            || hsd.solver_status == Trm_PrimalInfeasible\n            || hsd.solver_status == Trm_DualInfeasible\n        )\n            break\n        elseif hsd.niter >= params.IterationsLimit\n            hsd.solver_status = Trm_IterationLimit\n            break\n        elseif ttot >= params.TimeLimit\n            hsd.solver_status = Trm_TimeLimit\n            break\n        end\n\n\n        # TODO: step\n        # For now, include the factorization in the step function\n        # Q: should we use more arguments here?\n        try\n            @timeit hsd.timer \"Step\" compute_step!(hsd, params)\n        catch err\n\n            if isa(err, PosDefException) || isa(err, SingularException)\n                # Numerical trouble while computing the factorization\n                hsd.solver_status = Trm_NumericalProblem\n\n            elseif isa(err, OutOfMemoryError)\n                # Out of memory\n                hsd.solver_status = Trm_MemoryLimit\n\n            elseif isa(err, InterruptException)\n                hsd.solver_status = Trm_Unknown\n            else\n                # Unknown error: rethrow\n                rethrow(err)\n            end\n\n            break\n        end\n\n        hsd.niter += 1\n\n    end\n\n    # TODO: print message based on termination status\n    params.OutputLevel > 0 && println(\"Solver exited with status $((hsd.solver_status))\")\n\n    return nothing\n\nend\n"
  },
  {
    "path": "src/IPM/HSD/step.jl",
    "content": "\"\"\"\n    compute_step!(hsd, params)\n\nCompute next IP iterate for the HSD formulation.\n\n# Arguments\n- `hsd`: The HSD optimizer model\n- `params`: Optimization parameters\n\"\"\"\nfunction compute_step!(hsd::HSD{T, Tv}, params::IPMOptions{T}) where{T, Tv<:AbstractVector{T}}\n\n    # Names\n    dat = hsd.dat\n    pt = hsd.pt\n    res = hsd.res\n\n    m, n, p = pt.m, pt.n, pt.p\n\n    A = dat.A\n    b = dat.b\n    c = dat.c\n\n    # Compute scaling\n    θl = (pt.zl ./ pt.xl) .* dat.lflag\n    θu = (pt.zu ./ pt.xu) .* dat.uflag\n    θinv = θl .+ θu\n\n    # Update regularizations\n    hsd.regP .= max.(params.PRegMin, hsd.regP ./ 10)\n    hsd.regD .= max.(params.DRegMin, hsd.regD ./ 10)\n    hsd.regG  = max( params.PRegMin, hsd.regG  / 10)\n\n    # Update factorization\n    nbump = 0\n    while nbump <= 3\n        try\n            @timeit hsd.timer \"Factorization\" KKT.update!(hsd.kkt, θinv, hsd.regP, hsd.regD)\n            break\n        catch err\n            isa(err, PosDefException) || isa(err, ZeroPivotException) || rethrow(err)\n\n            # Increase regularization\n            hsd.regD .*= 100\n            hsd.regP .*= 100\n            hsd.regG  *= 100\n            nbump += 1\n            @warn \"Increase regularizations to $(hsd.regG)\"\n        end\n    end\n    # TODO: throw a custom error for numerical issues\n    nbump < 3 || throw(PosDefException(0))  # factorization could not be saved\n\n    # Search directions\n    # Predictor\n    Δ  = Point{T, Tv}(m, n, p, hflag=true)\n    Δc = Point{T, Tv}(m, n, p, hflag=true)\n\n    # Compute hx, hy, hz from first augmented system solve\n    hx = tzeros(Tv, n)\n    hy = tzeros(Tv, m)\n    ξ_ = @. (dat.c - ((pt.zl / pt.xl) * dat.l) * dat.lflag - ((pt.zu / pt.xu) * dat.u) * dat.uflag)\n    @timeit hsd.timer \"Newton\" begin\n      @timeit hsd.timer \"KKT\" KKT.solve!(hx, hy, hsd.kkt, dat.b, ξ_)\n    end\n\n    # Recover h0 = ρg + κ / τ - c'hx + b'hy - u'hz\n    # Some of the summands may take large values,\n    # so care must be taken for numerical stability\n    h0 = (\n          dot(dat.l .* dat.lflag, (dat.l .* θl) .* dat.lflag)\n        + dot(dat.u .* dat.uflag, (dat.u .* θu) .* dat.uflag)\n        - dot((@. (c + (θl * dat.l) * dat.lflag + (θu * dat.u) * dat.uflag)), hx)\n        + dot(b, hy)\n        + pt.κ / pt.τ\n        + hsd.regG\n    )\n\n    # Affine-scaling direction\n    @timeit hsd.timer \"Newton\" solve_newton_system!(Δ, hsd, hx, hy, h0,\n        # Right-hand side of Newton system\n        res.rp, res.rl, res.ru, res.rd, res.rg,\n        .-(pt.xl .* pt.zl) .* dat.lflag,\n        .-(pt.xu .* pt.zu) .* dat.uflag,\n        .-pt.τ  * pt.κ\n    )\n\n    # Step length for affine-scaling direction\n    α = max_step_length(pt, Δ)\n    γ = (one(T) - α)^2 * min(one(T) - α, params.GammaMin)\n    η = one(T) - γ\n\n    # Mehrotra corrector\n    @timeit hsd.timer \"Newton\" solve_newton_system!(Δ, hsd, hx, hy, h0,\n        # Right-hand side of Newton system\n        η .* res.rp, η .* res.rl, η .* res.ru, η .* res.rd, η * res.rg,\n        (.-pt.xl .* pt.zl .+ γ * pt.μ .- Δ.xl .* Δ.zl) .* dat.lflag,\n        (.-pt.xu .* pt.zu .+ γ * pt.μ .- Δ.xu .* Δ.zu) .* dat.uflag,\n        -pt.τ  * pt.κ  + γ * pt.μ  - Δ.τ  * Δ.κ\n    )\n    α = max_step_length(pt, Δ)\n\n    # Extra corrections\n    ncor = 0\n    while ncor < params.CorrectionLimit && α < T(999 // 1000)\n        α_ = α\n        ncor += 1\n\n        # Compute extra-corrector\n        αc = compute_higher_corrector!(Δc,\n            hsd, γ,\n            hx, hy, h0,\n            Δ, α_, params.CentralityOutlierThreshold\n        )\n        if αc > α_\n            # Use corrector\n            Δ.x  .= Δc.x\n            Δ.xl .= Δc.xl\n            Δ.xu .= Δc.xu\n            Δ.y  .= Δc.y\n            Δ.zl .= Δc.zl\n            Δ.zu .= Δc.zu\n            Δ.τ   = Δc.τ\n            Δ.κ   = Δc.κ\n            α = αc\n        end\n\n        if αc < T(11 // 10) * α_\n            break\n        end\n\n        # if (1.0 - η * αc) >= 0.9*(1.0 - η * α_)\n        #     # not enough improvement, step correcting\n        #     break\n        # end\n\n    end\n\n    # Update current iterate\n    α *= params.StepDampFactor\n    pt.x  .+= α .* Δ.x\n    pt.xl .+= α .* Δ.xl\n    pt.xu .+= α .* Δ.xu\n    pt.y  .+= α .* Δ.y\n    pt.zl .+= α .* Δ.zl\n    pt.zu .+= α .* Δ.zu\n    pt.τ   += α  * Δ.τ\n    pt.κ   += α  * Δ.κ\n    update_mu!(pt)\n\n    return nothing\nend\n\n\n\"\"\"\n    solve_newton_system!(Δ, hsd, hx, hy, h0, ξp, ξd, ξu, ξg, ξxs, ξwz, ξtk)\n\nSolve the Newton system\n```math\n\\\\begin{bmatrix}\n    A & & & R_{d} & & & -b\\\\\\\\\n    I & -I & & & & & -l\\\\\\\\\n    I & & -I & & & & -u\\\\\\\\\n    -R_{p} & & & A^{T} & I & -I & -c\\\\\\\\\n    -c^{T} & & & b^{T} & l^{T} & -u^{T} & ρ_{g} & -1\\\\\\\\\n    & Z_{l} & & & X_{l}\\\\\\\\\n    & & Z_{u} & & & X_{u}\\\\\\\\\n    &&&&&& κ & τ\n\\\\end{bmatrix}\n\\\\begin{bmatrix}\n    Δ x\\\\\\\\\n    Δ x_{l}\\\\\\\\\n    Δ x_{u}\\\\\\\\\n    Δ y\\\\\\\\\n    Δ z_{l} \\\\\\\\\n    Δ z_{u} \\\\\\\\\n    Δ τ\\\\\\\\\n    Δ κ\\\\\\\\\n\\\\end{bmatrix}\n=\n\\\\begin{bmatrix}\n    ξ_p\\\\\\\\\n    ξ_l\\\\\\\\\n    ξ_u\\\\\\\\\n    ξ_d\\\\\\\\\n    ξ_g\\\\\\\\\n    ξ_{xz}^{l}\\\\\\\\\n    ξ_{xz}^{u}\\\\\\\\\n    ξ_tk\n\\\\end{bmatrix}\n```\n\n# Arguments\n- `Δ`: Search direction, modified\n- `hsd`: The HSD optimizer\n- `hx, hy, hz, h0`: Terms obtained in the preliminary augmented system solve\n- `ξp, ξd, ξu, ξg, ξxs, ξwz, ξtk`: Right-hand side vectors\n\"\"\"\nfunction solve_newton_system!(Δ::Point{T, Tv},\n    hsd::HSD{T, Tv},\n    # Information from initial augmented system solve\n    hx::Tv, hy::Tv, h0::T,\n    # Right-hand side\n    ξp::Tv, ξl::Tv, ξu::Tv, ξd::Tv, ξg::T, ξxzl::Tv, ξxzu::Tv, ξtk::T\n) where{T, Tv<:AbstractVector{T}}\n\n    pt = hsd.pt\n    dat = hsd.dat\n\n    # I. Solve augmented system\n    @timeit hsd.timer \"ξd_\"  begin\n        ξd_ = copy(ξd)\n        @. ξd_ += -((ξxzl + pt.zl .* ξl) ./ pt.xl) .* dat.lflag + ((ξxzu - pt.zu .* ξu) ./ pt.xu) .* dat.uflag\n    end\n    @timeit hsd.timer \"KKT\" KKT.solve!(Δ.x, Δ.y, hsd.kkt, ξp, ξd_)\n\n    # II. Recover Δτ, Δx, Δy\n    # Compute Δτ\n    @timeit hsd.timer \"ξg_\" ξg_ = (ξg + ξtk / pt.τ\n        - dot((ξxzl ./ pt.xl) .* dat.lflag, dat.l .* dat.lflag)            # l'(Xl)^-1 * ξxzl\n        + dot((ξxzu ./ pt.xu) .* dat.uflag, dat.u .* dat.uflag)\n        - dot(((pt.zl ./ pt.xl) .* ξl) .* dat.lflag, dat.l .* dat.lflag)\n        - dot(((pt.zu ./ pt.xu) .* ξu) .* dat.uflag, dat.u .* dat.uflag)  #\n    )\n\n    @timeit hsd.timer \"Δτ\" Δ.τ = (\n        ξg_\n        + dot((@. (dat.c\n            + ((pt.zl / pt.xl) * dat.l) * dat.lflag\n            + ((pt.zu / pt.xu) * dat.u) * dat.uflag))\n        , Δ.x)\n        - dot(dat.b, Δ.y)\n    ) / h0\n\n\n    # Compute Δx, Δy\n    @timeit hsd.timer \"Δx\" Δ.x .+= Δ.τ .* hx\n    @timeit hsd.timer \"Δy\" Δ.y .+= Δ.τ .* hy\n\n    # III. Recover Δxl, Δxu\n    @timeit hsd.timer \"Δxl\" begin\n        @. Δ.xl = (-ξl + Δ.x - Δ.τ .* (dat.l .* dat.lflag)) * dat.lflag\n    end\n    @timeit hsd.timer \"Δxu\" begin\n        @. Δ.xu = ( ξu - Δ.x + Δ.τ .* (dat.u .* dat.uflag)) * dat.uflag\n    end\n\n    # IV. Recover Δzl, Δzu\n    @timeit hsd.timer \"Δzl\" @. Δ.zl = ((ξxzl - pt.zl .* Δ.xl) ./ pt.xl) .* dat.lflag\n    @timeit hsd.timer \"Δzu\" @. Δ.zu = ((ξxzu - pt.zu .* Δ.xu) ./ pt.xu) .* dat.uflag\n\n    # V. Recover Δκ\n    Δ.κ = (ξtk - pt.κ * Δ.τ) / pt.τ\n\n    # Check Newton residuals\n    # @printf \"Newton residuals:\\n\"\n    # @printf \"|rp|   = %16.8e\\n\" norm(dat.A * Δ.x + hsd.regD .* Δ.y - dat.b .* Δ.τ - ξp, Inf)\n    # @printf \"|rl|   = %16.8e\\n\" norm((Δ.x - Δ.xl - (dat.l .* Δ.τ)) .* dat.lflag - ξl, Inf)\n    # @printf \"|ru|   = %16.8e\\n\" norm((Δ.x + Δ.xu - (dat.u .* Δ.τ)) .* dat.uflag - ξu, Inf)\n    # @printf \"|rd|   = %16.8e\\n\" norm(-hsd.regP .* Δ.x + dat.A'Δ.y + Δ.zl - Δ.zu - dat.c .* Δ.τ - ξd, Inf)\n    # @printf \"|rg|   = %16.8e\\n\" norm(-dat.c'Δ.x + dat.b'Δ.y + dot(dat.l .* dat.lflag, Δ.zl) - dot(dat.u .* dat.uflag, Δ.zu) + hsd.regG * Δ.τ - Δ.κ - ξg, Inf)\n    # @printf \"|rxzl| = %16.8e\\n\" norm(pt.zl .* Δ.xl + pt.xl .* Δ.zl - ξxzl, Inf)\n    # @printf \"|rxzu| = %16.8e\\n\" norm(pt.zu .* Δ.xu + pt.xu .* Δ.zu - ξxzu, Inf)\n    # @printf \"|rtk|  = %16.8e\\n\" norm(pt.κ * Δ.τ + pt.τ * Δ.κ - ξtk, Inf)\n\n    return nothing\nend\n\n\n\"\"\"\n    max_step_length(x, dx)\n\nCompute the maximum value `a ≥ 0` such that `x + a*dx ≥ 0`, where `x ≥ 0`.\n\"\"\"\nfunction max_step_length(x::Vector{T}, dx::Vector{T}) where{T}\n    n = size(x, 1)\n    n == size(dx, 1) || throw(DimensionMismatch())\n    a = T(Inf)\n\n    @inbounds for i in 1:n\n        if dx[i] < zero(T)\n            if (-x[i] / dx[i]) < a\n                a = (-x[i] / dx[i])\n            end\n        end\n    end\n    return a\nend\n\n\"\"\"\n    max_step_length(pt, δ)\n\nCompute maximum length of homogeneous step.\n\"\"\"\nfunction max_step_length(pt::Point{T, Tv}, δ::Point{T, Tv}) where{T, Tv<:AbstractVector{T}}\n    axl = max_step_length(pt.xl, δ.xl)\n    axu = max_step_length(pt.xu, δ.xu)\n    azl = max_step_length(pt.zl, δ.zl)\n    azu = max_step_length(pt.zu, δ.zu)\n\n    at = δ.τ < zero(T) ? (-pt.τ / δ.τ) : oneunit(T)\n    ak = δ.κ < zero(T) ? (-pt.κ / δ.κ) : oneunit(T)\n\n    α = min(one(T), axl, axu, azl, azu, at, ak)\n\n    return α\nend\n\n\n\"\"\"\n    compute_higher_corrector!(Δc, hsd, γ, hx, hy, h0, Δ, α, β)\n\nCompute higher-order corrected direction.\n\nRequires the solution of one Newton system.\n\n# Arguments\n- `Δc`: Corrected search direction, modified in-place\n- `hsd`: The HSD optimizer\n- `γ`:\n- `hx, hy, h0`: Terms obtained from the preliminary augmented system solve\n- `Δ`: Current predictor direction\n- `α`: Maximum step length in predictor direction\n- `β`: Relative threshold for centrality outliers\n\"\"\"\nfunction compute_higher_corrector!(Δc::Point{T, Tv},\n    hsd::HSD{T, Tv}, γ::T,\n    hx::Tv, hy::Tv, h0::T,\n    Δ::Point{T, Tv}, α::T, β::T,\n) where{T, Tv<:AbstractVector{T}}\n    # TODO: Sanity checks\n    pt = hsd.pt\n    dat = hsd.dat\n\n    # Tentative step length\n    α_ = min(one(T), T(2)*α)\n\n    # Tentative cross products\n    vl = ((pt.xl .+ α_ .* Δ.xl) .* (pt.zl .+ α_ .* Δ.zl)) .* dat.lflag\n    vu = ((pt.xu .+ α_ .* Δ.xu) .* (pt.zu .+ α_ .* Δ.zu)) .* dat.uflag\n    vt = (pt.τ   + α_  * Δ.τ)   * (pt.κ   + α_  * Δ.κ)\n\n    # Compute target cross-products\n    mu_l = β * pt.μ * γ\n    mu_u = γ * pt.μ / β\n    for i in 1:pt.n\n        dat.lflag[i] || continue\n        if vl[i] < mu_l\n            vl[i] = mu_l - vl[i]\n        elseif vl[i] > mu_u\n            vl[i] = mu_u - vl[i]\n        else\n            vl[i] = zero(T)\n        end\n    end\n    for i in 1:pt.n\n        dat.uflag[i] || continue\n        if vu[i] < mu_l\n            vu[i] = mu_l - vu[i]\n        elseif vu[i] > mu_u\n            vu[i] = mu_u - vu[i]\n        else\n            vu[i] = zero(T)\n        end\n    end\n    if vt < mu_l\n        vt = mu_l - vt\n    elseif vt > mu_u\n        vt = mu_u - vt\n    else\n        vt = zero(T)\n    end\n\n    # Shift target cross-product to satisfy `v' * e = 0`\n    δ = (sum(vl) + sum(vu) + vt) / (pt.p + 1)\n    vl .-= δ\n    vu .-= δ\n    vt  -= δ\n\n    # Compute corrector\n    @timeit hsd.timer \"Newton\" solve_newton_system!(Δc, hsd, hx, hy, h0,\n        # Right-hand sides\n        tzeros(Tv, pt.m), tzeros(Tv, pt.n), tzeros(Tv, pt.n), tzeros(Tv, pt.n), zero(T),\n        vl,\n        vu,\n        vt\n    )\n\n    # Update corrector\n    Δc.x  .+= Δ.x\n    Δc.xl .+= Δ.xl\n    Δc.xu .+= Δ.xu\n    Δc.y  .+= Δ.y\n    Δc.zl .+= Δ.zl\n    Δc.zu .+= Δ.zu\n    Δc.τ   += Δ.τ\n    Δc.κ   += Δ.κ\n\n    # Compute corrected step-length\n    αc = max_step_length(pt, Δc)\n    return αc\nend\n"
  },
  {
    "path": "src/IPM/IPM.jl",
    "content": "using Printf\n\n\"\"\"\n    AbstractIPMOptimizer\n\nAbstraction layer for IPM solvers.\n\nAn IPM solver implements an interior-point algorithm.\nCurrently supported:\n    * Homogeneous self-dual (HSD)\n\"\"\"\nabstract type AbstractIPMOptimizer{T} end\n\ninclude(\"ipmdata.jl\")\ninclude(\"point.jl\")\ninclude(\"residuals.jl\")\ninclude(\"options.jl\")\n\n\n\"\"\"\n    ipm_optimize!(ipm)\n\nRun the interior-point optimizer of `ipm`.\n\"\"\"\nfunction ipm_optimize! end\n\ninclude(\"HSD/HSD.jl\")\ninclude(\"MPC/MPC.jl\")\n"
  },
  {
    "path": "src/IPM/MPC/MPC.jl",
    "content": "\"\"\"\n    MPC\n\nImplements Mehrotra's Predictor-Corrector interior-point algorithm.\n\"\"\"\nmutable struct MPC{T, Tv, Tb, Ta, Tk} <: AbstractIPMOptimizer{T}\n\n    # Problem data, in standard form\n    dat::IPMData{T, Tv, Tb, Ta}\n\n    # =================\n    #   Book-keeping\n    # =================\n    niter::Int                        # Number of IPM iterations\n    solver_status::TerminationStatus  # Optimization status\n    primal_status::SolutionStatus     # Primal solution status\n    dual_status::SolutionStatus       # Dual   solution status\n\n    primal_objective::T  # Primal bound: c'x\n    dual_objective::T    # Dual bound: b'y + l' zl - u'zu\n\n    timer::TimerOutput\n\n    #=====================\n        Working memory\n    =====================#\n    pt::Point{T, Tv}       # Current primal-dual iterate\n    res::Residuals{T, Tv}  # Residuals at current iterate\n\n    Δ::Point{T, Tv}   # Predictor\n    Δc::Point{T, Tv}  # Corrector\n\n    # Step sizes\n    αp::T\n    αd::T\n\n    # Newton system RHS\n    ξp::Tv\n    ξl::Tv\n    ξu::Tv\n    ξd::Tv\n    ξxzl::Tv\n    ξxzu::Tv\n\n    # KKT solver\n    kkt::Tk\n    regP::Tv  # Primal regularization\n    regD::Tv  # Dual regularization\n\n    function MPC(\n        dat::IPMData{T, Tv, Tb, Ta}, kkt_options::KKTOptions{T}\n    ) where{T, Tv<:AbstractVector{T}, Tb<:AbstractVector{Bool}, Ta<:AbstractMatrix{T}}\n\n        m, n = dat.nrow, dat.ncol\n        p = sum(dat.lflag) + sum(dat.uflag)\n\n        # Working memory\n        pt  = Point{T, Tv}(m, n, p, hflag=false)\n        res = Residuals(\n            tzeros(Tv, m), tzeros(Tv, n), tzeros(Tv, n),\n            tzeros(Tv, n), zero(T),\n            zero(T), zero(T), zero(T), zero(T), zero(T)\n        )\n        Δ  = Point{T, Tv}(m, n, p, hflag=false)\n        Δc = Point{T, Tv}(m, n, p, hflag=false)\n\n        # Newton RHS\n        ξp = tzeros(Tv, m)\n        ξl = tzeros(Tv, n)\n        ξu = tzeros(Tv, n)\n        ξd = tzeros(Tv, n)\n        ξxzl = tzeros(Tv, n)\n        ξxzu = tzeros(Tv, n)\n\n        # Initial regularizations\n        regP = tones(Tv, n)\n        regD = tones(Tv, m)\n\n        kkt = KKT.setup(dat.A, kkt_options.System, kkt_options.Backend)\n        Tk = typeof(kkt)\n\n        return new{T, Tv, Tb, Ta, Tk}(dat,\n            0, Trm_Unknown, Sln_Unknown, Sln_Unknown,\n            T(Inf), T(-Inf),\n            TimerOutput(),\n            pt, res, Δ, Δc, zero(T), zero(T),\n            ξp, ξl, ξu, ξd, ξxzl, ξxzu,\n            kkt, regP, regD\n        )\n    end\n\nend\n\ninclude(\"step.jl\")\n\n\"\"\"\n    compute_residuals!(::MPC)\n\nIn-place computation of primal-dual residuals at point `pt`.\n\"\"\"\nfunction compute_residuals!(mpc::MPC{T}) where{T}\n\n    pt, res = mpc.pt, mpc.res\n    dat = mpc.dat\n\n    # Primal residual\n    # rp = b - A*x\n    res.rp .= dat.b\n    mul!(res.rp, dat.A, pt.x, -one(T), one(T))\n\n    # Lower-bound residual\n    # rl_j = l_j - (x_j - xl_j)  if l_j ∈ R\n    #      = 0                   if l_j = -∞\n    @. res.rl = ((dat.l + pt.xl) - pt.x) * dat.lflag\n\n    # Upper-bound residual\n    # ru_j = u_j - (x_j + xu_j)  if u_j ∈ R\n    #      = 0                   if u_j = +∞\n    @. res.ru = (dat.u - (pt.x + pt.xu)) * dat.uflag\n\n    # Dual residual\n    # rd = c - (A'y + zl - zu)\n    res.rd .= dat.c\n    mul!(res.rd, transpose(dat.A), pt.y, -one(T), one(T))\n    @. res.rd += pt.zu .* dat.uflag - pt.zl .* dat.lflag\n\n    # Residuals norm\n    res.rp_nrm = norm(res.rp, Inf)\n    res.rl_nrm = norm(res.rl, Inf)\n    res.ru_nrm = norm(res.ru, Inf)\n    res.rd_nrm = norm(res.rd, Inf)\n\n    # Compute primal and dual bounds\n    mpc.primal_objective = dot(dat.c, pt.x) + dat.c0\n    mpc.dual_objective   = (\n        dot(dat.b, pt.y)\n        + dot(dat.l .* dat.lflag, pt.zl)\n        - dot(dat.u .* dat.uflag, pt.zu)\n    ) + dat.c0\n\n    return nothing\nend\n\n\n\"\"\"\n    update_solver_status!()\n\nUpdate status and return true if solver should stop.\n\"\"\"\nfunction update_solver_status!(mpc::MPC{T}, ϵp::T, ϵd::T, ϵg::T, ϵi::T) where{T}\n    mpc.solver_status = Trm_Unknown\n\n    pt, res = mpc.pt, mpc.res\n    dat = mpc.dat\n\n    ρp = max(\n        res.rp_nrm / (one(T) + norm(dat.b, Inf)),\n        res.rl_nrm / (one(T) + norm(dat.l .* dat.lflag, Inf)),\n        res.ru_nrm / (one(T) + norm(dat.u .* dat.uflag, Inf))\n    )\n    ρd = res.rd_nrm / (one(T) + norm(dat.c, Inf))\n    ρg = abs(mpc.primal_objective - mpc.dual_objective) / (one(T) + abs(mpc.primal_objective))\n\n    # Check for feasibility\n    if ρp <= ϵp\n        mpc.primal_status = Sln_FeasiblePoint\n    else\n        mpc.primal_status = Sln_Unknown\n    end\n\n    if ρd <= ϵd\n        mpc.dual_status = Sln_FeasiblePoint\n    else\n        mpc.dual_status = Sln_Unknown\n    end\n\n    # Check for optimal solution\n    if ρp <= ϵp && ρd <= ϵd && ρg <= ϵg\n        mpc.primal_status = Sln_Optimal\n        mpc.dual_status   = Sln_Optimal\n        mpc.solver_status = Trm_Optimal\n        return nothing\n    end\n\n    # TODO: Primal/Dual infeasibility detection\n    # Check for infeasibility certificates\n    if max(\n        norm(dat.A * pt.x, Inf),\n        norm((pt.x .- pt.xl) .* dat.lflag, Inf),\n        norm((pt.x .+ pt.xu) .* dat.uflag, Inf)\n    ) * (norm(dat.c, Inf) / max(1, norm(dat.b, Inf))) < - ϵi * dot(dat.c, pt.x)\n        # Dual infeasible, i.e., primal unbounded\n        mpc.primal_status = Sln_InfeasibilityCertificate\n        mpc.solver_status = Trm_DualInfeasible\n        return nothing\n    end\n\n    δ = dat.A' * pt.y .+ (pt.zl .* dat.lflag) .- (pt.zu .* dat.uflag)\n    if norm(δ, Inf) * max(\n        norm(dat.l .* dat.lflag, Inf),\n        norm(dat.u .* dat.uflag, Inf),\n        norm(dat.b, Inf)\n    ) / (max(one(T), norm(dat.c, Inf)))  < (dot(dat.b, pt.y) + dot(dat.l .* dat.lflag, pt.zl)- dot(dat.u .* dat.uflag, pt.zu)) * ϵi\n        # Primal infeasible\n        mpc.dual_status = Sln_InfeasibilityCertificate\n        mpc.solver_status = Trm_PrimalInfeasible\n        return nothing\n    end\n\n    return nothing\nend\n\n\n\"\"\"\n    optimize!\n\n\"\"\"\nfunction ipm_optimize!(mpc::MPC{T}, params::IPMOptions{T}) where{T}\n    # TODO: pre-check whether model needs to be re-optimized.\n    # This should happen outside of this function\n    dat = mpc.dat\n\n    # Initialization\n    TimerOutputs.reset_timer!(mpc.timer)\n    tstart = time()\n    mpc.niter = 0\n\n    # Print information about the problem\n    if params.OutputLevel > 0\n        @printf \"\\nOptimizer info (MPC)\\n\"\n        @printf \"Constraints  : %d\\n\" dat.nrow\n        @printf \"Variables    : %d\\n\" dat.ncol\n        bmin, bmax = extrema(dat.b)\n        @printf \"RHS          : [%+.2e, %+.2e]\\n\" bmin bmax\n        lmin, lmax = extrema(dat.l .* dat.lflag)\n        @printf \"Lower bounds : [%+.2e, %+.2e]\\n\" lmin lmax\n        lmin, lmax = extrema(dat.u .* dat.uflag)\n        @printf \"Upper bounds : [%+.2e, %+.2e]\\n\" lmin lmax\n\n\n        @printf \"\\nLinear solver options\\n\"\n        @printf \"  %-12s : %s\\n\" \"Arithmetic\" KKT.arithmetic(mpc.kkt)\n        @printf \"  %-12s : %s\\n\" \"Backend\" KKT.backend(mpc.kkt)\n        @printf \"  %-12s : %s\\n\" \"System\" KKT.linear_system(mpc.kkt)\n    end\n\n    # IPM LOG\n    if params.OutputLevel > 0\n        @printf \"\\n%4s  %14s  %14s  %8s %8s %8s  %7s  %4s\\n\" \"Itn\" \"PObj\" \"DObj\" \"PFeas\" \"DFeas\" \"GFeas\" \"Mu\" \"Time\"\n    end\n\n    # Set starting point\n    @timeit mpc.timer \"Initial point\" compute_starting_point(mpc)\n\n    # Main loop\n    # Iteration 0 corresponds to the starting point.\n    # Therefore, there is no numerical factorization before the first log is printed.\n    # If the maximum number of iterations is set to 0, the only computation that occurs\n    # is computing the residuals at the initial point.\n    @timeit mpc.timer \"Main loop\" while(true)\n\n        # I.A - Compute residuals at current iterate\n        @timeit mpc.timer \"Residuals\" compute_residuals!(mpc)\n\n        update_mu!(mpc.pt)\n\n        # I.B - Log\n        # TODO: Put this in a logging function\n        ttot = time() - tstart\n        if params.OutputLevel > 0\n            # Display log\n            @printf \"%4d\" mpc.niter\n\n            # Objectives\n            ϵ = dat.objsense ? one(T) : -one(T)\n            @printf \"  %+14.7e\" ϵ * mpc.primal_objective\n            @printf \"  %+14.7e\" ϵ * mpc.dual_objective\n\n            # Residuals\n            @printf \"  %8.2e\" max(mpc.res.rp_nrm, mpc.res.rl_nrm, mpc.res.ru_nrm)\n            @printf \" %8.2e\" mpc.res.rd_nrm\n            @printf \" %8s\" \"--\"\n\n            # Mu\n            @printf \"  %7.1e\" mpc.pt.μ\n\n            # Time\n            @printf \"  %.2f\" ttot\n\n            print(\"\\n\")\n        end\n\n        # TODO: check convergence status\n        # TODO: first call an `compute_convergence status`,\n        #   followed by a check on the solver status to determine whether to stop\n        # In particular, user limits should be checked last (if an optimal solution is found,\n        # we want to report optimal, not user limits)\n        @timeit mpc.timer \"update status\" update_solver_status!(mpc,\n            params.TolerancePFeas,\n            params.ToleranceDFeas,\n            params.ToleranceRGap,\n            params.ToleranceIFeas\n        )\n\n        if (\n            mpc.solver_status == Trm_Optimal\n            || mpc.solver_status == Trm_PrimalInfeasible\n            || mpc.solver_status == Trm_DualInfeasible\n        )\n            break\n        elseif mpc.niter >= params.IterationsLimit\n            mpc.solver_status = Trm_IterationLimit\n            break\n        elseif ttot >= params.TimeLimit\n            mpc.solver_status = Trm_TimeLimit\n            break\n        end\n\n\n        # TODO: step\n        # For now, include the factorization in the step function\n        # Q: should we use more arguments here?\n        try\n            @timeit mpc.timer \"Step\" compute_step!(mpc, params)\n        catch err\n\n            if isa(err, PosDefException) || isa(err, SingularException)\n                # Numerical trouble while computing the factorization\n                mpc.solver_status = Trm_NumericalProblem\n\n            elseif isa(err, OutOfMemoryError)\n                # Out of memory\n                mpc.solver_status = Trm_MemoryLimit\n\n            elseif isa(err, InterruptException)\n                mpc.solver_status = Trm_Unknown\n            else\n                # Unknown error: rethrow\n                rethrow(err)\n            end\n\n            break\n        end\n        mpc.niter += 1\n    end\n\n    # TODO: print message based on termination status\n    params.OutputLevel > 0 && println(\"Solver exited with status $((mpc.solver_status))\")\n\n    return nothing\nend\n\nfunction compute_starting_point(mpc::MPC{T}) where{T}\n\n    pt = mpc.pt\n    dat = mpc.dat\n    m, n, p = pt.m, pt.n, pt.p\n\n    KKT.update!(mpc.kkt, zeros(T, n), ones(T, n), T(1e-6) .* ones(T, m))\n\n    # Get initial iterate\n    KKT.solve!(zeros(T, n), pt.y, mpc.kkt, false .* mpc.dat.b, mpc.dat.c)  # For y\n    KKT.solve!(pt.x, zeros(T, m), mpc.kkt, mpc.dat.b, false .* mpc.dat.c)  # For x\n\n    # I. Recover positive primal-dual coordinates\n    δx = one(T) + max(\n        zero(T),\n        (-3 // 2) * minimum((pt.x .- dat.l) .* dat.lflag),\n        (-3 // 2) * minimum((dat.u .- pt.x) .* dat.uflag)\n    )\n    @. pt.xl  = ((pt.x - dat.l) + δx) * dat.lflag\n    @. pt.xu  = ((dat.u - pt.x) + δx) * dat.uflag\n\n    z = dat.c - dat.A' * pt.y\n    #=\n        We set zl, zu such that `z = zl - zu`\n\n         lⱼ |  uⱼ |    zˡⱼ |     zᵘⱼ |\n        ----+-----+--------+---------+\n        yes | yes | ¹/₂ zⱼ | ⁻¹/₂ zⱼ |\n        yes |  no |     zⱼ |      0  |\n         no | yes |     0  |     -zⱼ |\n         no |  no |     0  |      0  |\n        ----+-----+--------+---------+\n    =#\n    @. pt.zl = ( z / (dat.lflag + dat.uflag)) * dat.lflag\n    @. pt.zu = (-z / (dat.lflag + dat.uflag)) * dat.uflag\n\n    δz = one(T) + max(zero(T), (-3 // 2) * minimum(pt.zl), (-3 // 2) * minimum(pt.zu))\n    pt.zl[dat.lflag] .+= δz\n    pt.zu[dat.uflag] .+= δz\n\n    mpc.pt.τ   = one(T)\n    mpc.pt.κ   = zero(T)\n\n    # II. Balance complementarity products\n    μ = dot(pt.xl, pt.zl) + dot(pt.xu, pt.zu)\n    dx = μ / ( 2 * (sum(pt.zl) + sum(pt.zu)))\n    dz = μ / ( 2 * (sum(pt.xl) + sum(pt.xu)))\n\n    pt.xl[dat.lflag] .+= dx\n    pt.xu[dat.uflag] .+= dx\n    pt.zl[dat.lflag] .+= dz\n    pt.zu[dat.uflag] .+= dz\n\n    # Update centrality parameter\n    update_mu!(mpc.pt)\n\n    return nothing\nend\n"
  },
  {
    "path": "src/IPM/MPC/step.jl",
    "content": "\"\"\"\n    compute_step!(ipm, params)\n\nCompute next IP iterate for the MPC formulation.\n\n# Arguments\n- `ipm`: The MPC optimizer model\n- `params`: Optimization parameters\n\"\"\"\nfunction compute_step!(mpc::MPC{T, Tv}, params::IPMOptions{T}) where{T, Tv<:AbstractVector{T}}\n\n    # Names\n    dat = mpc.dat\n    pt = mpc.pt\n    res = mpc.res\n\n    m, n, p = pt.m, pt.n, pt.p\n\n    A = dat.A\n    b = dat.b\n    c = dat.c\n\n    # Compute scaling\n    θl = (pt.zl ./ pt.xl) .* dat.lflag\n    θu = (pt.zu ./ pt.xu) .* dat.uflag\n    θinv = θl .+ θu\n\n    # Update regularizations\n    mpc.regP ./= 10\n    mpc.regD ./= 10\n    clamp!(mpc.regP, sqrt(eps(T)), one(T))\n    clamp!(mpc.regD, sqrt(eps(T)), one(T))\n\n    # Update factorization\n    nbump = 0\n    while nbump <= 3\n        try\n            @timeit mpc.timer \"Factorization\" KKT.update!(mpc.kkt, θinv, mpc.regP, mpc.regD)\n            break\n        catch err\n            isa(err, PosDefException) || isa(err, ZeroPivotException) || rethrow(err)\n\n            # Increase regularization\n            mpc.regD .*= 100\n            mpc.regP .*= 100\n            nbump += 1\n            @warn \"Increase regularizations to $(mpc.regP[1])\"\n        end\n    end\n    # TODO: throw a custom error for numerical issues\n    nbump < 3 || throw(PosDefException(0))  # factorization could not be saved\n\n    # II. Compute search direction\n    Δ  = mpc.Δ\n    Δc = mpc.Δc\n\n    # Affine-scaling direction and associated step size\n    @timeit mpc.timer \"Predictor\" compute_predictor!(mpc::MPC)\n    mpc.αp, mpc.αd = max_step_length_pd(mpc.pt, mpc.Δ)\n\n    # TODO: if step size is large enough, skip corrector\n\n    # Corrector\n    @timeit mpc.timer \"Corrector\" compute_corrector!(mpc::MPC)\n    mpc.αp, mpc.αd = max_step_length_pd(mpc.pt, mpc.Δc)\n    # TODO: the following is not needed if there are no additional corrections\n    copyto!(Δ.x, Δc.x)\n    copyto!(Δ.xl, Δc.xl)\n    copyto!(Δ.xu, Δc.xu)\n    copyto!(Δ.y, Δc.y)\n    copyto!(Δ.zl, Δc.zl)\n    copyto!(Δ.zu, Δc.zu)\n\n    # Extra centrality corrections\n    ncor = 0\n    ncor_max = params.CorrectionLimit\n\n    # Zero out the Newton RHS. This only needs to be done once.\n    # TODO: not needed if no additional corrections\n    rmul!(mpc.ξp, zero(T))\n    rmul!(mpc.ξl, zero(T))\n    rmul!(mpc.ξu, zero(T))\n    rmul!(mpc.ξd, zero(T))\n\n    @timeit mpc.timer \"Extra corr\" while ncor < ncor_max\n        compute_extra_correction!(mpc)\n\n        # TODO: function to compute step size given Δ and Δc\n        # This would avoid copying data around\n        αp_c, αd_c = max_step_length_pd(mpc.pt, mpc.Δc)\n\n        if αp_c >= 1.01 * mpc.αp && αd_c >= 1.01 * mpc.αd\n            mpc.αp = αp_c\n            mpc.αd = αd_c\n\n            # Δ ⟵ Δc\n            copyto!(Δ.x, Δc.x)\n            copyto!(Δ.xl, Δc.xl)\n            copyto!(Δ.xu, Δc.xu)\n            copyto!(Δ.y, Δc.y)\n            copyto!(Δ.zl, Δc.zl)\n            copyto!(Δ.zu, Δc.zu)\n\n            ncor += 1\n        else\n            # Not enough improvement: abort\n            break\n        end\n    end\n\n    # Update current iterate\n    mpc.αp *= params.StepDampFactor\n    mpc.αd *= params.StepDampFactor\n    pt.x  .+= mpc.αp .* Δ.x\n    pt.xl .+= mpc.αp .* Δ.xl\n    pt.xu .+= mpc.αp .* Δ.xu\n    pt.y  .+= mpc.αd .* Δ.y\n    pt.zl .+= mpc.αd .* Δ.zl\n    pt.zu .+= mpc.αd .* Δ.zu\n    update_mu!(pt)\n\n    return nothing\nend\n\n\n\"\"\"\n    solve_newton_system!(Δ, mpc, ξp, ξd, ξu, ξg, ξxs, ξwz, ξtk)\n\nSolve the Newton system\n```math\n\\\\begin{bmatrix}\n    A & & & R_{d} & & \\\\\\\\\n    I & -I & & & & \\\\\\\\\n    I & & I & & & \\\\\\\\\n    -R_{p} & & & A^{T} & I & -I \\\\\\\\\n    & Z_{l} & & & X_{l}\\\\\\\\\n    & & Z_{u} & & & X_{u}\\\\\\\\\n\\\\end{bmatrix}\n\\\\begin{bmatrix}\n    Δ x\\\\\\\\\n    Δ x_{l}\\\\\\\\\n    Δ x_{u}\\\\\\\\\n    Δ y\\\\\\\\\n    Δ z_{l} \\\\\\\\\n    Δ z_{u}\n\\\\end{bmatrix}\n=\n\\\\begin{bmatrix}\n    ξ_p\\\\\\\\\n    ξ_l\\\\\\\\\n    ξ_u\\\\\\\\\n    ξ_d\\\\\\\\\n    ξ_{xz}^{l}\\\\\\\\\n    ξ_{xz}^{u}\n\\\\end{bmatrix}\n```\n\n# Arguments\n- `Δ`: Search direction, modified\n- `mpc`: The MPC optimizer\n- `hx, hy, hz, h0`: Terms obtained in the preliminary augmented system solve\n- `ξp, ξd, ξu, ξg, ξxs, ξwz, ξtk`: Right-hand side vectors\n\"\"\"\nfunction solve_newton_system!(Δ::Point{T, Tv},\n    mpc::MPC{T, Tv},\n    # Right-hand side\n    ξp::Tv, ξl::Tv, ξu::Tv, ξd::Tv, ξxzl::Tv, ξxzu::Tv\n) where{T, Tv<:AbstractVector{T}}\n\n    pt = mpc.pt\n    dat = mpc.dat\n\n    # I. Solve augmented system\n    @timeit mpc.timer \"ξd_\"  begin\n        ξd_ = copy(ξd)\n        @. ξd_ += -((ξxzl + pt.zl .* ξl) ./ pt.xl) .* dat.lflag + ((ξxzu - pt.zu .* ξu) ./ pt.xu) .* dat.uflag\n    end\n    @timeit mpc.timer \"KKT\" KKT.solve!(Δ.x, Δ.y, mpc.kkt, ξp, ξd_)\n\n    # II. Recover Δxl, Δxu\n    @timeit mpc.timer \"Δxl\" begin\n        @. Δ.xl = (-ξl + Δ.x) * dat.lflag\n    end\n    @timeit mpc.timer \"Δxu\" begin\n        @. Δ.xu = ( ξu - Δ.x) * dat.uflag\n    end\n\n    # III. Recover Δzl, Δzu\n    @timeit mpc.timer \"Δzl\" @. Δ.zl = ((ξxzl - pt.zl .* Δ.xl) ./ pt.xl) .* dat.lflag\n    @timeit mpc.timer \"Δzu\" @. Δ.zu = ((ξxzu - pt.zu .* Δ.xu) ./ pt.xu) .* dat.uflag\n\n    # IV. Set Δτ, Δκ to zero\n    Δ.τ = zero(T)\n    Δ.κ = zero(T)\n\n    # Check Newton residuals\n    # @printf \"Newton residuals:\\n\"\n    # @printf \"|rp|   = %16.8e\\n\" norm(dat.A * Δ.x - ξp, Inf)\n    # @printf \"|rl|   = %16.8e\\n\" norm((Δ.x - Δ.xl) .* dat.lflag - ξl, Inf)\n    # @printf \"|ru|   = %16.8e\\n\" norm((Δ.x + Δ.xu) .* dat.uflag - ξu, Inf)\n    # @printf \"|rd|   = %16.8e\\n\" norm(dat.A'Δ.y + Δ.zl - Δ.zu - ξd, Inf)\n    # @printf \"|rxzl| = %16.8e\\n\" norm(pt.zl .* Δ.xl + pt.xl .* Δ.zl - ξxzl, Inf)\n    # @printf \"|rxzu| = %16.8e\\n\" norm(pt.zu .* Δ.xu + pt.xu .* Δ.zu - ξxzu, Inf)\n\n    return nothing\nend\n\n\"\"\"\n    max_step_length_pd(pt, δ)\n\nCompute maximum primal-dual step length.\n\"\"\"\nfunction max_step_length_pd(pt::Point{T, Tv}, δ::Point{T, Tv}) where{T, Tv<:AbstractVector{T}}\n    axl = max_step_length(pt.xl, δ.xl)\n    axu = max_step_length(pt.xu, δ.xu)\n    azl = max_step_length(pt.zl, δ.zl)\n    azu = max_step_length(pt.zu, δ.zu)\n\n    αp = min(one(T), axl, axu)\n    αd = min(one(T), azl, azu)\n\n    return αp, αd\nend\n\n\n\"\"\"\n    compute_predictor!(mpc::MPC) -> Nothing\n\"\"\"\nfunction compute_predictor!(mpc::MPC)\n\n    # Newton RHS\n    copyto!(mpc.ξp, mpc.res.rp)\n    copyto!(mpc.ξl, mpc.res.rl)\n    copyto!(mpc.ξu, mpc.res.ru)\n    copyto!(mpc.ξd, mpc.res.rd)\n    @. mpc.ξxzl = -(mpc.pt.xl .* mpc.pt.zl) .* mpc.dat.lflag\n    @. mpc.ξxzu = -(mpc.pt.xu .* mpc.pt.zu) .* mpc.dat.uflag\n\n    # Compute affine-scaling direction\n    @timeit mpc.timer \"Newton\" solve_newton_system!(mpc.Δ, mpc,\n        mpc.ξp, mpc.ξl, mpc.ξu, mpc.ξd, mpc.ξxzl, mpc.ξxzu\n    )\n\n    # TODO: check Newton system residuals, perform iterative refinement if needed\n    return nothing\nend\n\n\"\"\"\n    compute_corrector!(mpc::MPC) -> Nothing\n\"\"\"\nfunction compute_corrector!(mpc::MPC{T, Tv}) where{T, Tv<:AbstractVector{T}}\n    dat = mpc.dat\n    pt = mpc.pt\n    Δ = mpc.Δ\n    Δc = mpc.Δc\n\n    # Step length for affine-scaling direction\n    αp_aff, αd_aff = mpc.αp, mpc.αd\n    μₐ = (\n        dot((@. ((pt.xl + αp_aff * Δ.xl) * dat.lflag)), pt.zl .+ αd_aff .* Δ.zl)\n        + dot((@. ((pt.xu + αp_aff * Δ.xu) * dat.uflag)), pt.zu .+ αd_aff .* Δ.zu)\n    ) / pt.p\n    σ = clamp((μₐ / pt.μ)^3, sqrt(eps(T)), one(T) - sqrt(eps(T)))\n\n    # Newton RHS\n    # compute_predictor! was called ⟹ ξp, ξl, ξu, ξd are already set\n    @. mpc.ξxzl = (σ * pt.μ .- Δ.xl .* Δ.zl .- pt.xl .* pt.zl) .* dat.lflag\n    @. mpc.ξxzu = (σ * pt.μ .- Δ.xu .* Δ.zu .- pt.xu .* pt.zu) .* dat.uflag\n\n    # Compute corrector\n    @timeit mpc.timer \"Newton\" solve_newton_system!(mpc.Δc, mpc,\n        mpc.ξp, mpc.ξl, mpc.ξu, mpc.ξd, mpc.ξxzl, mpc.ξxzu\n    )\n\n    # TODO: check Newton system residuals, perform iterative refinement if needed\n    return nothing\nend\n\n\"\"\"\n    compute_extra_correction!(mpc) -> Nothing\n\"\"\"\nfunction compute_extra_correction!(mpc::MPC{T, Tv};\n    δ::T = T(3 // 10),\n    γ::T = T(1 // 10),\n) where{T, Tv<:AbstractVector{T}}\n    pt = mpc.pt\n    Δ  = mpc.Δ\n    Δc = mpc.Δc\n    dat = mpc.dat\n\n    # Tentative step sizes and centrality parameter\n    αp, αd = mpc.αp, mpc.αd\n    αp_ = min(αp + δ, one(T))\n    αd_ = min(αd + δ, one(T))\n\n    g  = dot(pt.xl, pt.zl) + dot(pt.xu, pt.zu)\n    gₐ = dot((@. ((pt.xl + mpc.αp * Δ.xl) * dat.lflag)), pt.zl .+ mpc.αd .* Δ.zl) +\n        dot((@. ((pt.xu + mpc.αp * Δ.xu) * dat.uflag)), pt.zu .+ mpc.αd .* Δ.zu)\n    μ = (gₐ / g) * (gₐ / g) * (gₐ / pt.p)\n\n    # Newton RHS\n    # ξp, ξl, ξu, ξd are already at zero\n    @timeit mpc.timer \"target\" begin\n        compute_target!(mpc.ξxzl, pt.xl, Δ.xl, pt.zl, Δ.zl, αp_, αd_, γ, μ)\n        compute_target!(mpc.ξxzu, pt.xu, Δ.xu, pt.zu, Δ.zu, αp_, αd_, γ, μ)\n    end\n\n    @timeit mpc.timer \"Newton\" solve_newton_system!(Δc, mpc,\n        mpc.ξp, mpc.ξl, mpc.ξu, mpc.ξd, mpc.ξxzl, mpc.ξxzu\n    )\n\n    # Δc ⟵ Δp + Δc\n    axpy!(one(T), Δ.x, Δc.x)\n    axpy!(one(T), Δ.xl, Δc.xl)\n    axpy!(one(T), Δ.xu, Δc.xu)\n    axpy!(one(T), Δ.y, Δc.y)\n    axpy!(one(T), Δ.zl, Δc.zl)\n    axpy!(one(T), Δ.zu, Δc.zu)\n\n    # TODO: check Newton residuals\n    return nothing\nend\n\n\"\"\"\n    compute_target!(t, x, z, γ, μ)\n\nCompute centrality target.\n\"\"\"\nfunction compute_target!(\n    t::Vector{T},\n    x::Vector{T},\n    δx::Vector{T},\n    z::Vector{T},\n    δz::Vector{T},\n    αp::T,\n    αd::T,\n    γ::T,\n    μ::T\n) where{T}\n\n    n = length(t)\n\n    tmin = μ * γ\n    tmax = μ / γ\n\n    @inbounds for j in 1:n\n        v = (x[j] + αp * δx[j]) * (z[j] + αd * δz[j])\n        if v < tmin\n            t[j] = tmin - v\n        elseif v > tmax\n            t[j] = tmax - v\n        else\n            t[j] = zero(T)\n        end\n    end\n\n    return nothing\nend\n"
  },
  {
    "path": "src/IPM/ipmdata.jl",
    "content": "\"\"\"\n    IPMData{T, Tv, Ta}\n\nHolds data about an interior point method.\n\nThe problem is represented as\n```\nmin   c'x + c0\ns.t.  A x = b\n      l ≤ x ≤ u\n```\nwhere `l`, `u` may take infinite values.\n\"\"\"\nstruct IPMData{T, Tv, Tb, Ta}\n\n    # Problem size\n    nrow::Int\n    ncol::Int\n\n    # Objective\n    objsense::Bool  # min (true) or max (false)\n    c0::T\n    c::Tv\n\n    # Constraint matrix\n    A::Ta\n\n    # RHS\n    b::Tv\n\n    # Variable bounds (may contain infinite values)\n    l::Tv\n    u::Tv\n    # Variable bound flags (we template with `Tb` to ease GPU support)\n    # These should be vectors of the same type as `l`, `u`, but `Bool` eltype.\n    # They should not be passed as arguments, but computed at instantiation as\n    # `lflag = isfinite.(l)` and `uflag = isfinite.(u)`\n    lflag::Tb\n    uflag::Tb\n\n    function IPMData(\n        A::Ta, b::Tv, objsense::Bool, c::Tv, c0::T, l::Tv, u::Tv\n    ) where{T, Tv<:AbstractVector{T}, Ta<:AbstractMatrix{T}}\n        nrow, ncol = size(A)\n\n        lflag = isfinite.(l)\n        uflag = isfinite.(u)\n        Tb = typeof(lflag)\n\n        return new{T, Tv, Tb, Ta}(\n            nrow, ncol,\n            objsense, c0, c,\n            A, b, l, u, lflag, uflag\n        )\n    end\nend\n\n# TODO: extract IPM data from presolved problem\n\"\"\"\n    IPMData(pb::ProblemData, options::MatrixOptions)\n\nExtract problem data to standard form.\n\"\"\"\nfunction IPMData(pb::ProblemData{T}, mfact::Factory) where{T}\n\n    # Problem size\n    m, n = pb.ncon, pb.nvar\n\n    # Extract right-hand side and slack variables\n    nzA = 0          # Number of non-zeros in A\n    b = zeros(T, m)  # RHS\n    sind = Int[]     # Slack row index\n    sval = T[]       # Slack coefficient\n    lslack = T[]     # Slack lower bound\n    uslack = T[]     # Slack upper bound\n\n    for (i, (lb, ub)) in enumerate(zip(pb.lcon, pb.ucon))\n        if lb == ub\n            # Equality row\n            b[i] = lb\n\n        elseif -T(Inf) == lb && T(Inf) == ub\n            # Free row\n            push!(sind, i)\n            push!(sval, one(T))\n            push!(lslack, -T(Inf))\n            push!(uslack, T(Inf))\n            b[i] = zero(T)\n\n        elseif -T(Inf) == lb && isfinite(ub)\n            # a'x <= b --> a'x + s = b\n            push!(sind, i)\n            push!(sval, one(T))\n            push!(lslack, zero(T))\n            push!(uslack, T(Inf))\n            b[i] = ub\n\n        elseif isfinite(lb) && ub == Inf\n            # a'x >= b --> a'x - s = b\n            push!(sind, i)\n            push!(sval, -one(T))\n            push!(lslack, zero(T))\n            push!(uslack, T(Inf))\n            b[i] = lb\n\n        elseif isfinite(lb) && isfinite(ub)\n            # lb <= a'x <= ub\n            # Two options:\n            # --> a'x + s = ub, 0 <= s <= ub - lb\n            # --> a'x - s = lb, 0 <= s <= ub - lb \n            push!(sind, i)\n            push!(sval, one(T))\n            push!(lslack, zero(T))\n            push!(uslack, ub - lb)\n            b[i] = ub\n\n        else\n            error(\"Invalid bounds for row $i: [$lb, $ub]\")\n        end\n\n        # This line assumes that there are no dupplicate coefficients in Arows\n        # Numerical zeros will also be counted as non-zeros\n        nzA += length(pb.arows[i].nzind)\n    end\n\n    nslack = length(sind)\n\n    # Objective\n    c = [pb.obj; zeros(T, nslack)]\n    c0 = pb.obj0\n    if !pb.objsense\n        # Flip objective for maximization problem\n        c .= .-c\n        c0 = -c0\n    end\n\n    # Instantiate A\n    aI = Vector{Int}(undef, nzA + nslack)\n    aJ = Vector{Int}(undef, nzA + nslack)\n    aV = Vector{T}(undef, nzA + nslack)\n\n    # populate non-zero coefficients by column\n    nz_ = 0\n    for (j, col) in enumerate(pb.acols)\n        for (i, aij) in zip(col.nzind, col.nzval)\n            nz_ += 1\n\n            aI[nz_] = i\n            aJ[nz_] = j\n            aV[nz_] = aij\n        end\n    end\n    # populate slack coefficients\n    for (j, (i, a)) in enumerate(zip(sind, sval))\n        nz_ += 1\n        aI[nz_] = i\n        aJ[nz_] = n + j\n        aV[nz_] = a\n    end\n\n    # At this point, we should have nz_ == nzA + nslack\n    # If not, this means the data between rows and columns in `pb`\n    # do not match each other\n    nz_ == (nzA + nslack) || error(\"Found $(nz_) non-zero coeffs (expected $(nzA + nslack))\")\n\n    A = construct_matrix(mfact.T, m, n + nslack, aI, aJ, aV, mfact.options...)\n\n    # Variable bounds\n    l = [pb.lvar; lslack]\n    u = [pb.uvar; uslack]\n    \n    return IPMData(A, b, pb.objsense, c, c0, l, u)\nend\n"
  },
  {
    "path": "src/IPM/options.jl",
    "content": "Base.@kwdef mutable struct IPMOptions{T}\n\n    OutputLevel::Int = 0\n\n    # User limits\n    IterationsLimit::Int = 100\n    TimeLimit::Float64 = Inf\n\n    # Numerical tolerances\n    TolerancePFeas::T = sqrt(eps(T))  # primal feasibility\n    ToleranceDFeas::T = sqrt(eps(T))  # dual feasibility\n    ToleranceRGap::T = sqrt(eps(T))  # optimality\n    ToleranceIFeas::T = sqrt(eps(T))  # infeasibility\n\n    # Algorithmic parameters\n    CorrectionLimit::Int = 3  # Maximum number of centrality corrections\n    StepDampFactor::T = T(9_995 // 10_000)  # Damp step size by this much\n    GammaMin::T = T(1 // 10)\n    CentralityOutlierThreshold::T = T(1 // 10)  # Relative threshold for centrality outliers\n\n    PRegMin::T = sqrt(eps(T))  # primal\n    DRegMin::T = sqrt(eps(T))  # dual\n\n    Factory::Factory{<:AbstractIPMOptimizer} = Factory(HSD)\nend\n"
  },
  {
    "path": "src/IPM/point.jl",
    "content": "\"\"\"\n    Point{T, Tv}\n\nPrimal-dual point.\n\"\"\"\nmutable struct Point{T, Tv}\n    # Dimensions\n    m::Int  # Number of constraints\n    n::Int  # Number of variables\n    p::Int  # Total number of finite variable bounds (lower and upper)\n    hflag::Bool  # Is homogeneous embedding used?\n\n    # Primal variables\n     x::Tv  # Original variables\n    xl::Tv  # Lower-bound slack: `x - xl == l` (zero if `l == -∞`)\n    xu::Tv  # Upper-bound slack: `x + xu == u` (zero if `u == +∞`)\n\n    # Dual variables\n     y::Tv  # Dual variables\n    zl::Tv  # Lower-bound dual, zero if `l == -∞`\n    zu::Tv  # Upper-bound dual, zero if `u == +∞`\n\n    # HSD variables, only used with homogeneous form\n    # Otherwise, one must ensure that (τ, κ) = (1, 0)\n    τ::T\n    κ::T\n\n    # Centrality parameter\n    μ::T\n\n    # Constructor\n    Point{T, Tv}(m, n, p; hflag::Bool) where{T, Tv<:AbstractVector{T}} = new{T, Tv}(\n        m, n, p, hflag,\n        # Primal variables\n        tzeros(Tv, n), tzeros(Tv, n), tzeros(Tv, n),\n        # Dual variables\n        tzeros(Tv, m), tzeros(Tv, n), tzeros(Tv, n),\n        # Homogeneous variables\n        one(T), one(T),\n        # Centrality parameter\n        one(T)\n    )\nend\n\nfunction update_mu!(pt::Point)\n    pt.μ = (dot(pt.xl, pt.zl) + dot(pt.xu, pt.zu) + pt.hflag * (pt.τ * pt.κ)) / (pt.p + pt.hflag)\n    return nothing\nend"
  },
  {
    "path": "src/IPM/residuals.jl",
    "content": "\"\"\"\n    Residuals{T, Tv}\n\nData structure for IPM residual vectors.\n\"\"\"\nmutable struct Residuals{T, Tv}\n    # Primal residuals\n    rp::Tv  # rp = τ*b - A*x\n    rl::Tv  # rl = τ*l - (x - xl)\n    ru::Tv  # ru = τ*u - (x + xu)\n\n    # Dual residuals\n    rd::Tv  # rd = τ*c - (A'y + zl - zu)\n    rg::T  # rg = c'x - (b'y + l'zl - u'zu) + κ\n\n    # Residuals' norms\n    rp_nrm::T  # |rp|\n    rl_nrm::T  # |rl|\n    ru_nrm::T  # |ru|\n    rd_nrm::T  # |rd|\n    rg_nrm::T  # |rg|\nend"
  },
  {
    "path": "src/Interfaces/MOI/MOI_wrapper.jl",
    "content": "import MathOptInterface as MOI\n\n# ==============================================================================\n#           HELPER FUNCTIONS\n# ==============================================================================\n\n\"\"\"\n    MOITerminationStatus(st::TerminationStatus)\n\nConvert a Tulip `TerminationStatus` into a `MOI.TerminationStatusCode`.\n\"\"\"\nfunction MOITerminationStatus(st::TerminationStatus)::MOI.TerminationStatusCode\n    if st == Trm_NotCalled\n        return MOI.OPTIMIZE_NOT_CALLED\n    elseif st == Trm_Optimal\n        return MOI.OPTIMAL\n    elseif st == Trm_PrimalInfeasible\n        return MOI.INFEASIBLE\n    elseif st == Trm_DualInfeasible\n        return MOI.DUAL_INFEASIBLE\n    elseif st == Trm_IterationLimit\n        return MOI.ITERATION_LIMIT\n    elseif st == Trm_TimeLimit\n        return MOI.TIME_LIMIT\n    elseif st == Trm_MemoryLimit\n        return MOI.MEMORY_LIMIT\n    else\n        return MOI.OTHER_ERROR\n    end\nend\n\n\"\"\"\n    MOISolutionStatus(st::SolutionStatus)\n\nConvert a Tulip `SolutionStatus` into a `MOI.ResultStatusCode`.\n\"\"\"\nfunction MOISolutionStatus(st::SolutionStatus)::MOI.ResultStatusCode\n    if st == Sln_Unknown\n        return MOI.UNKNOWN_RESULT_STATUS\n    elseif st == Sln_Optimal || st == Sln_FeasiblePoint\n        return MOI.FEASIBLE_POINT\n    elseif st == Sln_InfeasiblePoint\n        return MOI.INFEASIBLE_POINT\n    elseif st == Sln_InfeasibilityCertificate\n        return MOI.INFEASIBILITY_CERTIFICATE\n    else\n        return MOI.OTHER_RESULT_STATUS\n    end\nend\n\n\"\"\"\n    _bounds(s)\n\n\"\"\"\n_bounds(s::MOI.EqualTo{T}) where{T} = s.value, s.value\n_bounds(s::MOI.LessThan{T}) where{T}  = T(-Inf), s.upper\n_bounds(s::MOI.GreaterThan{T}) where{T}  = s.lower, T(Inf)\n_bounds(s::MOI.Interval{T}) where{T}  = s.lower, s.upper\n\nconst SCALAR_SETS{T} = Union{\n    MOI.LessThan{T},\n    MOI.GreaterThan{T},\n    MOI.EqualTo{T},\n    MOI.Interval{T}\n} where{T}\n\n@enum(ObjType, _SINGLE_VARIABLE, _SCALAR_AFFINE)\n\n\n# ==============================================================================\n# ==============================================================================\n#\n#               S U P P O R T E D    M O I    F E A T U R E S\n#\n# ==============================================================================\n# ==============================================================================\n\n\"\"\"\n    Optimizer{T}\n\nWrapper for MOI.\n\"\"\"\nmutable struct Optimizer{T} <: MOI.AbstractOptimizer\n    inner::Model{T}\n\n    objective_sense::Union{Nothing,MOI.OptimizationSense}\n    _obj_type::Union{Nothing,ObjType}\n\n    # Map MOI Variable/Constraint indices to internal indices\n    var_counter::Int  # Should never be reset\n    con_counter::Int  # Should never be reset\n    var_indices_moi::Vector{MOI.VariableIndex}\n    var_indices::Dict{MOI.VariableIndex, Int}\n    con_indices_moi::Vector{MOI.ConstraintIndex}\n    con_indices::Dict{MOI.ConstraintIndex{MOI.ScalarAffineFunction{T}, <:SCALAR_SETS{T}}, Int}\n\n    # Variable and constraint names\n    name2var::Dict{String, Set{MOI.VariableIndex}}\n    name2con::Dict{String, Set{MOI.ConstraintIndex}}\n\n    # Keep track of bound constraints\n    var2bndtype::Dict{MOI.VariableIndex, Set{Type{<:MOI.AbstractScalarSet}}}\n\n    # Tulip.Model does not record solution time...\n    solve_time::Float64\n\n    function Optimizer{T}(;kwargs...) where{T}\n        m = new{T}(\n            Model{T}(),\n            nothing,  # objective_sense\n            nothing,  # _obj_type\n            # Variable and constraint counters\n            0, 0,\n            # Index mapping\n            MOI.VariableIndex[], Dict{MOI.VariableIndex, Int}(),\n            MOI.ConstraintIndex[], Dict{MOI.ConstraintIndex{MOI.ScalarAffineFunction, <:SCALAR_SETS{T}}, Int}(),\n            # Name -> index mapping\n            Dict{String, Set{MOI.VariableIndex}}(),\n            Dict{String, Set{MOI.ConstraintIndex}}(),\n            Dict{MOI.VariableIndex, Set{Type{<:MOI.AbstractScalarSet}}}(),\n            0.0\n        )\n\n        for (k, v) in kwargs\n            set_parameter(m.inner, string(k), v)\n        end\n\n        return m\n    end\nend\n\nOptimizer(;kwargs...) = Optimizer{Float64}(;kwargs...)\n\nfunction MOI.empty!(m::Optimizer)\n    # Inner model\n    empty!(m.inner)\n    m.objective_sense = nothing\n    m._obj_type = nothing\n    # Reset index mappings\n    m.var_indices_moi = MOI.VariableIndex[]\n    m.con_indices_moi = MOI.ConstraintIndex[]\n    m.var_indices = Dict{MOI.VariableIndex, Int}()\n    m.con_indices = Dict{MOI.ConstraintIndex, Int}()\n\n    # Reset name mappings\n    m.name2var = Dict{String, Set{MOI.VariableIndex}}()\n    m.name2con = Dict{String, Set{MOI.ConstraintIndex}}()\n\n    # Reset bound tracking\n    m.var2bndtype  = Dict{MOI.VariableIndex, Set{MOI.ConstraintIndex}}()\n\n    m.solve_time = 0.0\n    return nothing\nend\n\nfunction MOI.is_empty(m::Optimizer)\n    m.objective_sense === nothing || return false\n    m._obj_type === nothing || return false\n    m.inner.pbdata.nvar == 0 || return false\n    m.inner.pbdata.ncon == 0 || return false\n\n    length(m.var_indices) == 0 || return false\n    length(m.var_indices_moi) == 0 || return false\n    length(m.con_indices) == 0 || return false\n    length(m.con_indices_moi) == 0 || return false\n\n    length(m.name2var) == 0 || return false\n    length(m.name2con) == 0 || return false\n\n    length(m.var2bndtype) == 0 || return false\n\n    return true\nend\n\nfunction MOI.optimize!(m::Optimizer)\n    t_solve = @elapsed optimize!(m.inner)\n    m.solve_time = t_solve\n    return nothing\nend\n\nMOI.supports_incremental_interface(::Optimizer) = true\n\nfunction MOI.copy_to(dest::Optimizer, src::MOI.ModelLike)\n    return MOI.Utilities.default_copy_to(dest, src)\nend\n\n\n# ==============================================================================\n#           I. Optimizer attributes\n# ==============================================================================\n# ==============================================================================\n#           II. Model attributes\n# ==============================================================================\ninclude(\"./attributes.jl\")\n\n# ==============================================================================\n#           III. Variables\n# ==============================================================================\ninclude(\"./variables.jl\")\n\n# ==============================================================================\n#           IV. Constraints\n# ==============================================================================\ninclude(\"./constraints.jl\")\n\n# ==============================================================================\n#           V. Objective\n# ==============================================================================\ninclude(\"./objective.jl\")\n"
  },
  {
    "path": "src/Interfaces/MOI/attributes.jl",
    "content": "# =============================================\n#   Supported attributes\n# =============================================\nconst SUPPORTED_OPTIMIZER_ATTR = Union{\n    MOI.NumberOfThreads,\n    MOI.RawOptimizerAttribute,\n    MOI.SolverName,\n    MOI.SolverVersion,\n    MOI.SolveTimeSec,\n    MOI.Silent,\n    MOI.TimeLimitSec,\n}\n\nMOI.supports(::Optimizer, ::A) where{A<:SUPPORTED_OPTIMIZER_ATTR} = true\n\n\n# =============================================\n#   1. Optimizer attributes\n# =============================================\n\n#\n#   NumberOfThreads\n#\nMOI.get(m::Optimizer, ::MOI.NumberOfThreads) = m.inner.params.Threads\n\nfunction MOI.set(m::Optimizer, ::MOI.NumberOfThreads, n::Int)\n    # TODO: use lower-level API\n    m.inner.params.Threads = n\n    return nothing\nend\n\n#\n#   SolverName\n#\nMOI.get(::Optimizer, ::MOI.SolverName) = \"Tulip\"\n\n#\n#   SolverVersion\n#\nMOI.get(::Optimizer, ::MOI.SolverVersion) = string(Tulip.version())\n\n#\n#   SolveTimeSec\n#\nMOI.get(m::Optimizer, ::MOI.SolveTimeSec) = m.solve_time\n\n#\n#   Silent\n#\nMOI.get(m::Optimizer, ::MOI.Silent) = m.inner.params.OutputLevel <= 0\n\nfunction MOI.set(m::Optimizer, ::MOI.Silent, flag::Bool)\n    m.inner.params.OutputLevel = 1 - flag\n    # TODO: make a decision about LogLevel\n    return nothing\nend\n\n#\n#   TimeLimitSec\n#\nfunction MOI.get(m::Optimizer, ::MOI.TimeLimitSec)\n    value = m.inner.params.IPM.TimeLimit\n    return value == Inf ? nothing : value\nend\n\nfunction MOI.set(m::Optimizer, ::MOI.TimeLimitSec, t::Union{Real,Nothing})\n    m.inner.params.IPM.TimeLimit = convert(Float64, something(t, Inf))\n    return nothing\nend\n\n#\n#   RawParameter\n#\nMOI.get(m::Optimizer, attr::MOI.RawOptimizerAttribute) = get_parameter(m.inner, attr.name)\n\nMOI.set(m::Optimizer, attr::MOI.RawOptimizerAttribute, val) = set_parameter(m.inner, attr.name, val)\n\n\n# =============================================\n#   2. Model attributes\n# =============================================\nconst SUPPORTED_MODEL_ATTR = Union{\n    MOI.Name,\n    MOI.ObjectiveSense,\n    MOI.NumberOfVariables,\n    MOI.ListOfVariableIndices,\n    MOI.ListOfConstraintIndices,\n    MOI.NumberOfConstraints,\n    # ListOfConstraints,  # TODO\n    MOI.ObjectiveFunctionType,\n    MOI.ObjectiveValue,\n    MOI.DualObjectiveValue,\n    MOI.RelativeGap,\n    # MOI.SolveTime,  # TODO\n    MOI.SimplexIterations,\n    MOI.BarrierIterations,\n    MOI.RawSolver,\n    MOI.RawStatusString,\n    MOI.ResultCount,\n    MOI.TerminationStatus,\n    MOI.PrimalStatus,\n    MOI.DualStatus\n}\n\nMOI.supports(::Optimizer, ::SUPPORTED_MODEL_ATTR) = true\n\n#\n#   ListOfModelAttributesSet\n#\nfunction MOI.get(m::Optimizer{T}, ::MOI.ListOfModelAttributesSet) where {T}\n    ret = MOI.AbstractModelAttribute[]\n    if !isempty(m.inner.pbdata.name)\n        push!(ret, MOI.Name())\n    end\n    if m.objective_sense !== nothing\n        push!(ret, MOI.ObjectiveSense())\n    end\n    if m._obj_type == _SINGLE_VARIABLE\n        push!(ret, MOI.ObjectiveFunction{MOI.VariableIndex}())\n    elseif m._obj_type == _SCALAR_AFFINE\n        push!(ret, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}())\n    end\n    return ret\nend\n\n#\n#   ListOfVariableIndices\n#\nfunction MOI.get(m::Optimizer, ::MOI.ListOfVariableIndices)\n    return copy(m.var_indices_moi)\nend\n\n#\n#   Name\n#\nMOI.get(m::Optimizer, ::MOI.Name) = m.inner.pbdata.name\nMOI.set(m::Optimizer, ::MOI.Name, name) = (m.inner.pbdata.name = name)\n\n#\n#   NumberOfVariables\n#\nMOI.get(m::Optimizer, ::MOI.NumberOfVariables) = m.inner.pbdata.nvar\n\n#\n#   ObjectiveFunctionType\n#\nfunction MOI.get(m::Optimizer{T}, ::MOI.ObjectiveFunctionType) where{T}\n    if m._obj_type == _SINGLE_VARIABLE\n        return MOI.VariableIndex\n    end\n    return MOI.ScalarAffineFunction{T}\nend\n\n#\n#   ObjectiveSense\n#\nfunction MOI.get(m::Optimizer, ::MOI.ObjectiveSense)\n    return something(m.objective_sense, MOI.FEASIBILITY_SENSE)\nend\n\nfunction MOI.set(m::Optimizer, ::MOI.ObjectiveSense, s::MOI.OptimizationSense)\n    m.objective_sense = s\n    if s == MOI.MIN_SENSE || s == MOI.FEASIBILITY_SENSE\n        m.inner.pbdata.objsense = true\n    else\n        @assert s == MOI.MAX_SENSE\n        m.inner.pbdata.objsense = false\n    end\n    return nothing\nend\n\n#\n#   ObjectiveValue\n#\nfunction MOI.get(m::Optimizer{T}, attr::MOI.ObjectiveValue) where{T}\n    MOI.check_result_index_bounds(m, attr)\n    raw_z = get_attribute(m.inner, ObjectiveValue())\n    is_feas = MOI.get(m, MOI.ObjectiveSense()) == MOI.FEASIBILITY_SENSE\n    return raw_z * !is_feas\nend\n\n#\n#   DualObjectiveValue\n#\nfunction MOI.get(m::Optimizer{T}, attr::MOI.DualObjectiveValue) where{T}\n    MOI.check_result_index_bounds(m, attr)\n    return get_attribute(m.inner, DualObjectiveValue())\nend\n\nMOI.get(m::Optimizer, ::MOI.ObjectiveBound) = MOI.get(m, MOI.DualObjectiveValue())\n\n#\n#   RawSolver\n#\nMOI.get(m::Optimizer, ::MOI.RawSolver) = m.inner\n\n#\n#   RelativeGap\n#\nfunction MOI.get(m::Optimizer{T}, ::MOI.RelativeGap) where{T}\n    # TODO: dispatch a function call on m.inner\n    zp = m.inner.solver.primal_objective\n    zd = m.inner.solver.dual_objective\n    return (abs(zp - zd) / (T(1 // 10^6)) + abs(zd))\nend\n\n#\n#   RawStatusString\n#\nfunction MOI.get(m::Optimizer, ::MOI.RawStatusString)\n    return string(m.inner.status)\nend\n\n#\n#   ResultCount\n#\nfunction MOI.get(m::Optimizer, ::MOI.ResultCount)\n    st = MOI.get(m, MOI.TerminationStatus())\n\n    if (st == MOI.OPTIMIZE_NOT_CALLED\n        || st == MOI.OTHER_ERROR\n        || st == MOI.MEMORY_LIMIT\n    )\n        return 0\n    end\n    return 1\nend\n\n#\n#   SimplexIterations\n#\nMOI.get(::Optimizer, ::MOI.SimplexIterations) = 0\n\n#\n#   BarrierIterations\n#\n# TODO: use inner query\nMOI.get(m::Optimizer, ::MOI.BarrierIterations) = get_attribute(m.inner, BarrierIterations())\n\n#\n#   TerminationStatus\n#\n# TODO: use inner query\nfunction MOI.get(m::Optimizer, ::MOI.TerminationStatus)\n    return MOITerminationStatus(get_attribute(m.inner, Status()))\nend\n\n#\n#   PrimalStatus\n#\n# TODO: use inner query\nfunction MOI.get(m::Optimizer, attr::MOI.PrimalStatus)\n    attr.result_index == 1 || return MOI.NO_SOLUTION\n\n    if isnothing(m.inner.solution)\n        return MOI.NO_SOLUTION\n    else\n        MOISolutionStatus(m.inner.solution.primal_status)\n    end\nend\n\n#\n#   DualStatus\n#\n# TODO: use inner query\nfunction MOI.get(m::Optimizer, attr::MOI.DualStatus)\n    attr.result_index == 1 || return MOI.NO_SOLUTION\n\n    if isnothing(m.inner.solution)\n        return MOI.NO_SOLUTION\n    else\n        MOISolutionStatus(m.inner.solution.dual_status)\n    end\nend\n"
  },
  {
    "path": "src/Interfaces/MOI/constraints.jl",
    "content": "# =============================================\n#   1. Supported constraints and attributes\n# =============================================\n\n\"\"\"\n    SUPPORTED_CONSTR_ATTR\n\nList of supported MOI `ConstraintAttribute`.\n\"\"\"\nconst SUPPORTED_CONSTR_ATTR = Union{\n    MOI.ConstraintName,\n    MOI.ConstraintPrimal,\n    MOI.ConstraintDual,\n    # MOI.ConstraintPrimalStart,\n    # MOI.ConstraintDualStart, # once dual warm-start is supported\n    # MOI.ConstraintBasisStatus,  # once cross-over is supported\n    MOI.ConstraintFunction,\n    MOI.ConstraintSet\n}\n\nMOI.supports(::Optimizer, ::A, ::Type{<:MOI.ConstraintIndex}) where{A<:SUPPORTED_CONSTR_ATTR} = true\n\nfunction MOI.get(\n    m::Optimizer,\n    ::MOI.ListOfConstraintAttributesSet{F,S},\n) where {F,S}\n    ret = MOI.AbstractConstraintAttribute[]\n    for set in values(m.name2con)\n        if any(ci -> ci isa MOI.ConstraintIndex{F,S}, set)\n            push!(ret, MOI.ConstraintName())\n            break\n        end\n    end\n    return ret\nend\n\n_type_tuple(::MOI.ConstraintIndex{F,S}) where {F,S} = (F, S)\n\nfunction MOI.get(m::Optimizer, ::MOI.ListOfConstraintTypesPresent)\n    ret = Tuple{Type,Type}[]\n    append!(ret, unique!(_type_tuple.(m.con_indices_moi)))\n    for set in values(m.var2bndtype)\n        for S in set\n           push!(ret, (MOI.VariableIndex, S))\n        end\n    end\n    unique!(ret)\n    return ret\nend\n\n# MOI boilerplate\nfunction MOI.supports(::Optimizer, ::MOI.ConstraintName, ::Type{<:MOI.ConstraintIndex{<:MOI.VariableIndex}})\n    throw(MOI.VariableIndexConstraintNameError())\nend\n\n# Variable bounds\nfunction MOI.supports_constraint(\n    ::Optimizer{T}, ::Type{MOI.VariableIndex}, ::Type{S}\n) where {T, S<:SCALAR_SETS{T}}\n    return true\nend\n\n# Linear constraints\nfunction MOI.supports_constraint(\n    ::Optimizer{T}, ::Type{MOI.ScalarAffineFunction{T}}, ::Type{S}\n) where {T, S<:SCALAR_SETS{T}}\n    return true\nend\n\n\nfunction MOI.is_valid(\n    m::Optimizer{T},\n    c::MOI.ConstraintIndex{MOI.VariableIndex, S}\n) where{T, S <:SCALAR_SETS{T}}\n    v = MOI.VariableIndex(c.value)\n    MOI.is_valid(m, v) || return false\n    res = S ∈ m.var2bndtype[v]\n    return res\nend\n\nfunction MOI.is_valid(\n    m::Optimizer{T},\n    c::MOI.ConstraintIndex{MOI.ScalarAffineFunction{T}, S}\n) where{T, S<:SCALAR_SETS{T}}\n    return haskey(m.con_indices, c)\nend\n\n# =============================================\n#   2. Add constraints\n# =============================================\n\n# TODO: make it clear that only finite bounds can be given in input.\n# To relax variable bounds, one should delete the associated bound constraint.\nfunction MOI.add_constraint(\n    m::Optimizer{T},\n    v::MOI.VariableIndex,\n    s::MOI.LessThan{T}\n) where {T}\n\n    # Check that variable exists\n    MOI.throw_if_not_valid(m, v)\n    # Check if upper bound already exists\n    if MOI.LessThan{T} ∈ m.var2bndtype[v]\n        throw(MOI.UpperBoundAlreadySet{MOI.LessThan{T}, MOI.LessThan{T}}(v))\n    elseif MOI.EqualTo{T} ∈ m.var2bndtype[v]\n        throw(MOI.UpperBoundAlreadySet{MOI.EqualTo{T}, MOI.LessThan{T}}(v))\n    elseif MOI.Interval{T} ∈ m.var2bndtype[v]\n        throw(MOI.UpperBoundAlreadySet{MOI.Interval{T}, MOI.LessThan{T}}(v))\n    end\n\n    # Update inner model\n    j = m.var_indices[v]  # inner index\n    set_attribute(m.inner, VariableUpperBound(), j, s.upper)\n\n    # Update bound tracking\n    push!(m.var2bndtype[v], MOI.LessThan{T})\n\n    return MOI.ConstraintIndex{MOI.VariableIndex, MOI.LessThan{T}}(v.value)\nend\n\nfunction MOI.add_constraint(\n    m::Optimizer{T},\n    v::MOI.VariableIndex,\n    s::MOI.GreaterThan{T}\n) where{T}\n\n    # Check that variable exists\n    MOI.throw_if_not_valid(m, v)\n    # Check if lower bound already exists\n    if MOI.GreaterThan{T} ∈ m.var2bndtype[v]\n        throw(MOI.LowerBoundAlreadySet{MOI.GreaterThan{T}, MOI.GreaterThan{T}}(v))\n    elseif MOI.EqualTo{T} ∈ m.var2bndtype[v]\n        throw(MOI.LowerBoundAlreadySet{MOI.EqualTo{T}, MOI.GreaterThan{T}}(v))\n    elseif MOI.Interval{T} ∈ m.var2bndtype[v]\n        throw(MOI.LowerBoundAlreadySet{MOI.Interval{T}, MOI.GreaterThan{T}}(v))\n    end\n\n    # Update inner model\n    j = m.var_indices[v]  # inner index\n    set_attribute(m.inner, VariableLowerBound(), j, s.lower)\n\n    # Update upper-bound\n    push!(m.var2bndtype[v], MOI.GreaterThan{T})\n\n    return MOI.ConstraintIndex{MOI.VariableIndex, MOI.GreaterThan{T}}(v.value)\nend\n\nfunction MOI.add_constraint(\n    m::Optimizer{T},\n    v::MOI.VariableIndex,\n    s::MOI.EqualTo{T}\n) where{T}\n\n    # Check that variable exists\n    MOI.throw_if_not_valid(m, v)\n    # Check if a bound already exists\n    if MOI.LessThan{T} ∈ m.var2bndtype[v]\n        throw(MOI.UpperBoundAlreadySet{MOI.LessThan{T}, MOI.EqualTo{T}}(v))\n    elseif MOI.GreaterThan{T} ∈ m.var2bndtype[v]\n        throw(MOI.LowerBoundAlreadySet{MOI.GreaterThan{T}, MOI.EqualTo{T}}(v))\n    elseif MOI.EqualTo{T} ∈ m.var2bndtype[v]\n        throw(MOI.UpperBoundAlreadySet{MOI.EqualTo{T}, MOI.EqualTo{T}}(v))\n    elseif MOI.Interval{T} ∈ m.var2bndtype[v]\n        throw(MOI.UpperBoundAlreadySet{MOI.Interval{T}, MOI.EqualTo{T}}(v))\n    end\n\n    # Update inner model\n    j = m.var_indices[v]  # inner index\n    set_attribute(m.inner, VariableLowerBound(), j, s.value)\n    set_attribute(m.inner, VariableUpperBound(), j, s.value)\n\n    # Update bound tracking\n    push!(m.var2bndtype[v], MOI.EqualTo{T})\n\n    return MOI.ConstraintIndex{MOI.VariableIndex, MOI.EqualTo{T}}(v.value)\nend\n\nfunction MOI.add_constraint(\n    m::Optimizer{T},\n    v::MOI.VariableIndex,\n    s::MOI.Interval{T}\n) where{T}\n\n    # Check that variable exists\n    MOI.throw_if_not_valid(m, v)\n    # Check if a bound already exists\n    if MOI.LessThan{T} ∈ m.var2bndtype[v]\n        throw(MOI.UpperBoundAlreadySet{MOI.LessThan{T}, MOI.Interval{T}}(v))\n    elseif MOI.GreaterThan{T} ∈ m.var2bndtype[v]\n        throw(MOI.LowerBoundAlreadySet{MOI.GreaterThan{T}, MOI.Interval{T}}(v))\n    elseif MOI.EqualTo{T} ∈ m.var2bndtype[v]\n        throw(MOI.UpperBoundAlreadySet{MOI.EqualTo{T}, MOI.Interval{T}}(v))\n    elseif MOI.Interval{T} ∈ m.var2bndtype[v]\n        throw(MOI.UpperBoundAlreadySet{MOI.Interval{T}, MOI.Interval{T}}(v))\n    end\n\n    # Update variable bounds\n    j = m.var_indices[v]  # inner index\n    set_attribute(m.inner, VariableLowerBound(), j, s.lower)\n    set_attribute(m.inner, VariableUpperBound(), j, s.upper)\n\n    # Update bound tracking\n    push!(m.var2bndtype[v], MOI.Interval{T})\n\n    return MOI.ConstraintIndex{MOI.VariableIndex, MOI.Interval{T}}(v.value)\nend\n\n# General linear constraints\nfunction MOI.add_constraint(\n    m::Optimizer{T},\n    f::MOI.ScalarAffineFunction{T},\n    s::SCALAR_SETS{T}\n) where{T}\n    # Check that constant term is zero\n    if !iszero(f.constant)\n        throw(MOI.ScalarFunctionConstantNotZero{T, typeof(f), typeof(s)}(f.constant))\n    end\n\n    # Convert to canonical form\n    fc = MOI.Utilities.canonical(f)\n\n    # Extract row\n    nz = length(fc.terms)\n    rind = Vector{Int}(undef, nz)\n    rval = Vector{T}(undef, nz)\n    lb, ub = _bounds(s)\n    for (k, t) in enumerate(fc.terms)\n        rind[k] = m.var_indices[t.variable]\n        rval[k] = t.coefficient\n    end\n\n    # Update inner model\n    i = add_constraint!(m.inner.pbdata, rind, rval, lb, ub)\n\n    # Create MOI index\n    m.con_counter += 1\n    cidx = MOI.ConstraintIndex{typeof(f), typeof(s)}(m.con_counter)\n\n    # Update constraint tracking\n    m.con_indices[cidx] = i\n    push!(m.con_indices_moi, cidx)\n\n    return cidx\nend\n\n# =============================================\n#   3. Delete constraints\n# =============================================\nfunction MOI.delete(\n    m::Optimizer{T},\n    c::MOI.ConstraintIndex{MOI.VariableIndex, S}\n) where{T, S<:SCALAR_SETS{T}}\n\n    # Sanity check\n    MOI.throw_if_not_valid(m, c)\n    v = MOI.VariableIndex(c.value)\n\n    # Update inner model\n    j = m.var_indices[v]\n    if S == MOI.LessThan{T}\n        # Remove upper-bound\n        set_attribute(m.inner, VariableUpperBound(), j, T(Inf))\n    elseif S == MOI.GreaterThan{T}\n        # Remove lower bound\n        set_attribute(m.inner, VariableLowerBound(), j, T(-Inf))\n    else\n        # Set variable to free\n        set_attribute(m.inner, VariableLowerBound(), j, T(-Inf))\n        set_attribute(m.inner, VariableUpperBound(), j, T(Inf))\n    end\n\n    # Delete tracking of bounds\n    delete!(m.var2bndtype[v], S)\n\n    return nothing\nend\n\nfunction MOI.delete(\n    m::Optimizer{T},\n    c::MOI.ConstraintIndex{MOI.ScalarAffineFunction{T}, S}\n) where{T, S<:SCALAR_SETS{T}}\n    MOI.throw_if_not_valid(m, c)\n\n    # Update inner model\n    i = m.con_indices[c]\n    old_name = get_attribute(m.inner, ConstraintName(), i)\n    delete_constraint!(m.inner.pbdata, i)\n\n    # Update index tracking\n    for c_ in m.con_indices_moi[i+1:end]\n        m.con_indices[c_] -= 1\n    end\n    deleteat!(m.con_indices_moi, i)\n    delete!(m.con_indices, c)\n\n    # Update name tracking\n    if old_name != \"\" && haskey(m.name2con, old_name)\n        s = m.name2con[old_name]\n        delete!(s, c)\n        length(s) == 0 && delete!(m.name2con, old_name)\n    end\n\n    return nothing\nend\n\n# =============================================\n#   4. Modify constraints\n# =============================================\nfunction MOI.modify(\n    m::Optimizer{T},\n    c::MOI.ConstraintIndex{MOI.ScalarAffineFunction{T}, S},\n    chg::MOI.ScalarCoefficientChange{T}\n) where{T, S<:SCALAR_SETS{T}}\n    MOI.is_valid(m, c) || throw(MOI.InvalidIndex(c))\n    MOI.is_valid(m, chg.variable) || throw(MOI.InvalidIndex(chg.variable))\n\n    # Update inner problem\n    i = m.con_indices[c]\n    j = m.var_indices[chg.variable]\n    v = chg.new_coefficient\n\n    set_coefficient!(m.inner.pbdata, i, j, v)\n    return nothing\nend\n\n# =============================================\n#   5. Get/set constraint attributes\n# =============================================\n\n#\n#   ListOfConstraintIndices\n#\nfunction MOI.get(\n    m::Optimizer{T},\n    ::MOI.ListOfConstraintIndices{MOI.VariableIndex, S}\n) where{T, S<:SCALAR_SETS{T}}\n    indices = MOI.ConstraintIndex{MOI.VariableIndex, S}[]\n\n    for (var, bounds_set) in m.var2bndtype\n        S ∈ bounds_set && push!(indices, MOI.ConstraintIndex{MOI.VariableIndex, S}(var.value))\n    end\n    return sort!(indices, by = v -> v.value)\nend\n\nfunction MOI.get(\n    m::Optimizer{T},\n    ::MOI.ListOfConstraintIndices{MOI.ScalarAffineFunction{T}, S}\n) where{T, S<:SCALAR_SETS{T}}\n    indices = [\n        cidx\n        for cidx in keys(m.con_indices) if isa(cidx,\n            MOI.ConstraintIndex{MOI.ScalarAffineFunction{T}, S}\n        )\n    ]\n    return sort!(indices, by = v -> v.value)\nend\n\n#\n#   NumberOfConstraints\n#\nfunction MOI.get(\n    m::Optimizer{T},\n    ::MOI.NumberOfConstraints{MOI.VariableIndex, S}\n) where{T, S<:SCALAR_SETS{T}}\n    ncon = 0\n    for (v, bound_sets) in m.var2bndtype\n        ncon += S ∈ bound_sets\n    end\n    return ncon\nend\n\nfunction MOI.get(\n    m::Optimizer{T},\n    ::MOI.NumberOfConstraints{MOI.ScalarAffineFunction{T}, S}\n) where{T, S<:SCALAR_SETS{T}}\n    ncon = 0\n\n    for cidx in keys(m.con_indices)\n        ncon += isa(cidx, MOI.ConstraintIndex{MOI.ScalarAffineFunction{T}, S})\n    end\n\n    return ncon\nend\n\n#\n#   ConstraintName\n#\n\nfunction MOI.get(\n    m::Optimizer{T}, ::MOI.ConstraintName,\n    c::MOI.ConstraintIndex{MOI.ScalarAffineFunction{T}, S}\n) where {T, S<:SCALAR_SETS{T}}\n    MOI.throw_if_not_valid(m, c)\n\n    # Get name from inner model\n    i = m.con_indices[c]\n    return get_attribute(m.inner, ConstraintName(), i)\nend\n\nfunction MOI.set(::Optimizer, ::MOI.ConstraintName, ::MOI.ConstraintIndex{<:MOI.VariableIndex}, ::String)\n    throw(MOI.VariableIndexConstraintNameError())\nend\n\nfunction MOI.set(\n    m::Optimizer{T}, ::MOI.ConstraintName,\n    c::MOI.ConstraintIndex{MOI.ScalarAffineFunction{T}, S},\n    name::String\n) where{T, S<:SCALAR_SETS{T}}\n    MOI.throw_if_not_valid(m, c)\n\n    s = get!(m.name2con, name, Set{MOI.ConstraintIndex}())\n\n    # Update inner model\n    i = m.con_indices[c]\n    old_name = get_attribute(m.inner, ConstraintName(), i)\n    set_attribute(m.inner, ConstraintName(), i, name)\n\n    # Update constraint name tracking\n    push!(s, c)\n    # Delete old name\n    s_old = get(m.name2con, old_name, Set{MOI.ConstraintIndex}())\n    if length(s_old) == 0\n        # Constraint previously didn't have name --> ignore\n    elseif length(s_old) == 1\n        delete!(m.name2con, old_name)\n    else\n        delete!(s_old, c)\n    end\n    return nothing\nend\n\nfunction MOI.get(m::Optimizer, CIType::Type{<:MOI.ConstraintIndex}, name::String)\n    s = get(m.name2con, name, Set{MOI.ConstraintIndex}())\n    if length(s) == 0\n        return nothing\n    elseif length(s) == 1\n        c = first(s)\n        return isa(c, CIType) ? c : nothing\n    else\n        error(\"Duplicate constraint name detected: $(name)\")\n    end\n    return nothing\nend\n\n#\n#   ConstraintFunction\n#\nfunction MOI.get(\n    m::Optimizer{T}, ::MOI.ConstraintFunction,\n    c::MOI.ConstraintIndex{MOI.VariableIndex, S}\n) where {T, S<:SCALAR_SETS{T}}\n    MOI.throw_if_not_valid(m, c)  # Sanity check\n\n    return MOI.VariableIndex(c.value)\nend\n\nfunction MOI.set(\n    ::Optimizer{T}, ::MOI.ConstraintFunction,\n    c::MOI.ConstraintIndex{MOI.VariableIndex, S},\n    ::MOI.VariableIndex,\n) where {T, S<:SCALAR_SETS{T}}\n    return throw(MOI.SettingVariableIndexNotAllowed())\nend\n\nfunction MOI.get(\n    m::Optimizer{T}, ::MOI.ConstraintFunction,\n    c::MOI.ConstraintIndex{MOI.ScalarAffineFunction{T}, S}\n) where {T, S<:SCALAR_SETS{T}}\n    MOI.throw_if_not_valid(m, c)  # Sanity check\n\n    # Get row from inner model\n    i = m.con_indices[c]\n    row = m.inner.pbdata.arows[i]\n    nz = length(row.nzind)\n\n    # Map inner indices to MOI indices\n    terms = Vector{MOI.ScalarAffineTerm{T}}(undef, nz)\n    for (k, (j, v)) in enumerate(zip(row.nzind, row.nzval))\n        terms[k] = MOI.ScalarAffineTerm{T}(v, m.var_indices_moi[j])\n    end\n\n    return MOI.ScalarAffineFunction(terms, zero(T))\nend\n\n# TODO\nfunction MOI.set(\n    m::Optimizer{T}, ::MOI.ConstraintFunction,\n    c::MOI.ConstraintIndex{MOI.ScalarAffineFunction{T}, S},\n    f::MOI.ScalarAffineFunction{T}\n) where{T, S<:SCALAR_SETS{T}}\n    MOI.throw_if_not_valid(m, c)\n    iszero(f.constant) || throw(MOI.ScalarFunctionConstantNotZero{T, typeof(f), S}(f.constant))\n\n    fc = MOI.Utilities.canonical(f)\n\n    # Update inner model\n    # TODO: use inner query\n    i = m.con_indices[c]\n    # Set old row to zero\n    f_old = MOI.get(m, MOI.ConstraintFunction(), c)\n    for term in f_old.terms\n        j = m.var_indices[term.variable]\n        set_coefficient!(m.inner.pbdata, i, j, zero(T))\n    end\n    # Set new row coefficients\n    for term in fc.terms\n        j = m.var_indices[term.variable]\n        set_coefficient!(m.inner.pbdata, i, j, term.coefficient)\n    end\n\n    # Done\n\n    return nothing\nend\n\n#\n#   ConstraintSet\n#\nfunction MOI.get(\n    m::Optimizer{T}, ::MOI.ConstraintSet,\n    c::MOI.ConstraintIndex{MOI.VariableIndex, MOI.LessThan{T}}\n) where{T}\n    # Sanity check\n    MOI.throw_if_not_valid(m, c)\n    v = MOI.VariableIndex(c.value)\n\n    # Get inner bounds\n    j  = m.var_indices[v]\n    ub = m.inner.pbdata.uvar[j]\n\n    return MOI.LessThan(ub)\nend\n\nfunction MOI.get(\n    m::Optimizer{T}, ::MOI.ConstraintSet,\n    c::MOI.ConstraintIndex{MOI.VariableIndex, MOI.GreaterThan{T}}\n) where{T}\n    # Sanity check\n    MOI.throw_if_not_valid(m, c)\n    v = MOI.VariableIndex(c.value)\n\n    # Get inner bounds\n    j  = m.var_indices[v]\n    lb = m.inner.pbdata.lvar[j]\n\n    return MOI.GreaterThan(lb)\nend\n\nfunction MOI.get(\n    m::Optimizer{T}, ::MOI.ConstraintSet,\n    c::MOI.ConstraintIndex{MOI.VariableIndex, MOI.EqualTo{T}}\n) where{T}\n    # Sanity check\n    MOI.throw_if_not_valid(m, c)\n    v = MOI.VariableIndex(c.value)\n\n    # Get inner bounds\n    j  = m.var_indices[v]\n    ub = m.inner.pbdata.uvar[j]\n\n    return MOI.EqualTo(ub)\nend\n\nfunction MOI.get(\n    m::Optimizer{T}, ::MOI.ConstraintSet,\n    c::MOI.ConstraintIndex{MOI.VariableIndex, MOI.Interval{T}}\n) where{T}\n    # Sanity check\n    MOI.throw_if_not_valid(m, c)\n    v = MOI.VariableIndex(c.value)\n\n    # Get inner bounds\n    j  = m.var_indices[v]\n    lb = m.inner.pbdata.lvar[j]\n    ub = m.inner.pbdata.uvar[j]\n\n    return MOI.Interval(lb, ub)\nend\n\nfunction MOI.get(\n    m::Optimizer{T}, ::MOI.ConstraintSet,\n    c::MOI.ConstraintIndex{MOI.ScalarAffineFunction{T}, S}\n) where{T, S<:SCALAR_SETS{T}}\n    MOI.throw_if_not_valid(m, c)  # Sanity check\n\n    # Get inner bounds\n    i = m.con_indices[c]\n    lb = m.inner.pbdata.lcon[i]\n    ub = m.inner.pbdata.ucon[i]\n\n    if S == MOI.LessThan{T}\n        return MOI.LessThan(ub)\n    elseif S == MOI.GreaterThan{T}\n        return MOI.GreaterThan(lb)\n    elseif S == MOI.EqualTo{T}\n        return MOI.EqualTo(lb)\n    elseif S == MOI.Interval{T}\n        return MOI.Interval(lb, ub)\n    end\nend\n\nfunction MOI.set(\n    m::Optimizer{T}, ::MOI.ConstraintSet,\n    c::MOI.ConstraintIndex{MOI.VariableIndex, S},\n    s::S\n) where{T, S<:SCALAR_SETS{T}}\n    # Sanity check\n    MOI.throw_if_not_valid(m, c)\n    v = MOI.VariableIndex(c.value)\n\n    # Update inner bounds\n    # Bound key does not need to be updated\n    j = m.var_indices[v]\n    if S == MOI.LessThan{T}\n        set_attribute(m.inner, VariableUpperBound(), j, s.upper)\n    elseif S == MOI.GreaterThan{T}\n        set_attribute(m.inner, VariableLowerBound(), j, s.lower)\n    elseif S == MOI.EqualTo{T}\n        set_attribute(m.inner, VariableLowerBound(), j, s.value)\n        set_attribute(m.inner, VariableUpperBound(), j, s.value)\n    elseif S == MOI.Interval{T}\n        set_attribute(m.inner, VariableLowerBound(), j, s.lower)\n        set_attribute(m.inner, VariableUpperBound(), j, s.upper)\n    else\n        error(\"Unknown type for ConstraintSet: $S.\")\n    end\n\n    return nothing\nend\n\nfunction MOI.set(\n    m::Optimizer{T}, ::MOI.ConstraintSet,\n    c::MOI.ConstraintIndex{MOI.ScalarAffineFunction{T}, S},\n    s::S\n) where{T, S<:SCALAR_SETS{T}}\n    MOI.throw_if_not_valid(m, c)\n\n    # Update inner bounds\n    i = m.con_indices[c]\n    if S == MOI.LessThan{T}\n        set_attribute(m.inner, ConstraintUpperBound(), i, s.upper)\n    elseif S == MOI.GreaterThan{T}\n        set_attribute(m.inner, ConstraintLowerBound(), i, s.lower)\n    elseif S == MOI.EqualTo{T}\n        set_attribute(m.inner, ConstraintLowerBound(), i, s.value)\n        set_attribute(m.inner, ConstraintUpperBound(), i, s.value)\n    elseif S == MOI.Interval{T}\n        set_attribute(m.inner, ConstraintLowerBound(), i, s.lower)\n        set_attribute(m.inner, ConstraintUpperBound(), i, s.upper)\n    else\n        error(\"Unknown type for ConstraintSet: $S.\")\n    end\n\n    return nothing\nend\n\n#\n#   ConstraintPrimal\n#\nfunction MOI.get(\n    m::Optimizer{T}, attr::MOI.ConstraintPrimal,\n    c::MOI.ConstraintIndex{MOI.VariableIndex, S}\n) where{T, S<:SCALAR_SETS{T}}\n    MOI.throw_if_not_valid(m, c)\n    MOI.check_result_index_bounds(m, attr)\n\n    # Query row primal\n    j = m.var_indices[MOI.VariableIndex(c.value)]\n    return m.inner.solution.x[j]\nend\n\nfunction MOI.get(\n    m::Optimizer{T}, attr::MOI.ConstraintPrimal,\n    c::MOI.ConstraintIndex{MOI.ScalarAffineFunction{T}, S}\n) where{T, S<:SCALAR_SETS{T}}\n    MOI.throw_if_not_valid(m, c)\n    MOI.check_result_index_bounds(m, attr)\n\n    # Query from inner model\n    i = m.con_indices[c]\n    return m.inner.solution.Ax[i]\nend\n\n#\n#   ConstraintDual\n#\nfunction MOI.get(\n    m::Optimizer{T}, attr::MOI.ConstraintDual,\n    c::MOI.ConstraintIndex{MOI.VariableIndex, S}\n) where{T, S<:SCALAR_SETS{T}}\n    MOI.throw_if_not_valid(m, c)\n    MOI.check_result_index_bounds(m, attr)\n\n    # Get variable index\n    j = m.var_indices[MOI.VariableIndex(c.value)]\n\n    # Extract reduced cost\n    if S == MOI.LessThan{T}\n        return -m.inner.solution.s_upper[j]\n    elseif S == MOI.GreaterThan{T}\n        return m.inner.solution.s_lower[j]\n    else\n        return m.inner.solution.s_lower[j] - m.inner.solution.s_upper[j]\n    end\nend\n\nfunction MOI.get(\n    m::Optimizer{T}, attr::MOI.ConstraintDual,\n    c::MOI.ConstraintIndex{MOI.ScalarAffineFunction{T}, S}\n) where{T, S<:SCALAR_SETS{T}}\n    MOI.throw_if_not_valid(m, c)\n    MOI.check_result_index_bounds(m, attr)\n\n    # Get dual from inner model\n    i = m.con_indices[c]\n    if isa(S, MOI.LessThan)\n        return -m.inner.solution.y_upper[i]\n    elseif isa(S, MOI.GreaterThan)\n        return m.inner.solution.y_lower[i]\n    else\n        return m.inner.solution.y_lower[i] - m.inner.solution.y_upper[i]\n    end\nend\n"
  },
  {
    "path": "src/Interfaces/MOI/objective.jl",
    "content": "# =============================================\n#   1. Supported objectives\n# =============================================\nfunction MOI.supports(\n    ::Optimizer{T},\n    ::MOI.ObjectiveFunction{F}\n) where{T, F<:Union{MOI.VariableIndex, MOI.ScalarAffineFunction{T}}}\n    return true\nend\n\n# =============================================\n#   2. Get/set objective function\n# =============================================\nfunction MOI.get(\n    m::Optimizer{T},\n    ::MOI.ObjectiveFunction{F}\n) where{T,F}\n    obj = MOI.get(m, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}())\n    return convert(F, obj)\nend\n\nfunction MOI.get(\n    m::Optimizer{T},\n    ::MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}\n) where{T}\n    # Objective coeffs\n    terms = MOI.ScalarAffineTerm{T}[]\n    for (j, cj) in enumerate(m.inner.pbdata.obj)\n        !iszero(cj) && push!(terms, MOI.ScalarAffineTerm(cj, m.var_indices_moi[j]))\n    end\n\n    # Constant term\n    c0 = m.inner.pbdata.obj0\n\n    return MOI.ScalarAffineFunction(terms, c0)\nend\n\n# TODO: use inner API\nfunction MOI.set(\n    m::Optimizer{T},\n    ::MOI.ObjectiveFunction{F},\n    f::F\n) where{T, F <: MOI.VariableIndex}\n\n    MOI.set(\n        m, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(),\n        convert(MOI.ScalarAffineFunction{T}, f)\n    )\n    m._obj_type = _SINGLE_VARIABLE\n    return nothing\nend\n\nfunction MOI.set(\n    m::Optimizer{T},\n    ::MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}},\n    f::MOI.ScalarAffineFunction{T}\n) where{T}\n\n    # Sanity checks\n    isfinite(f.constant) || error(\"Objective constant term must be finite\")\n    for t in f.terms\n        MOI.throw_if_not_valid(m, t.variable)\n    end\n\n    # Update inner model\n    m.inner.pbdata.obj .= zero(T) # Reset inner objective to zero\n    for t in f.terms\n        j = m.var_indices[t.variable]\n        m.inner.pbdata.obj[j] += t.coefficient  # there may be dupplicates\n    end\n    set_attribute(m.inner, ObjectiveConstant(), f.constant)  # objective offset\n\n    m._obj_type = _SCALAR_AFFINE\n\n    return nothing\nend\n\n# =============================================\n#   3. Modify objective\n# =============================================\nfunction MOI.modify(\n    m::Optimizer{T},\n    c::MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}},\n    chg::MOI.ScalarCoefficientChange{T}\n) where{T}\n    # Sanity checks\n    v = chg.variable\n    MOI.throw_if_not_valid(m, v)\n\n    # Update inner model\n    j = m.var_indices[v]\n    m.inner.pbdata.obj[j] = chg.new_coefficient  # TODO: use inner API\n    m._obj_type = _SCALAR_AFFINE\n    return nothing\nend\n\nfunction MOI.modify(\n    m::Optimizer{T},\n    ::MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}},\n    chg::MOI.ScalarConstantChange{T}\n) where{T}\n    isfinite(chg.new_constant) || error(\"Objective constant term must be finite\")\n    m.inner.pbdata.obj0 = chg.new_constant\n    m._obj_type = _SCALAR_AFFINE\n    return nothing\nend\n"
  },
  {
    "path": "src/Interfaces/MOI/variables.jl",
    "content": "# =============================================\n#   1. Supported variable attributes\n# =============================================\n\n\"\"\"\n    SUPPORTED_VARIABLE_ATTR\n\nList of supported `MOI.VariableAttribute`.\n* `MOI.VariablePrimal`\n\"\"\"\nconst SUPPORTED_VARIABLE_ATTR = Union{\n    MOI.VariableName,\n    # MOI.VariablePrimalStart,\n    MOI.VariablePrimal\n}\n\nMOI.supports(::Optimizer, ::MOI.VariableName, ::Type{MOI.VariableIndex}) = true\n\n\n# =============================================\n#   2. Add variables\n# =============================================\nfunction MOI.is_valid(m::Optimizer, x::MOI.VariableIndex)\n    return haskey(m.var_indices, x)\nend\n\nfunction MOI.add_variable(m::Optimizer{T}) where{T}\n    # TODO: dispatch a function call to m.inner instead of m.inner.pbdata\n    m.var_counter += 1\n    x = MOI.VariableIndex(m.var_counter)\n    j = Tulip.add_variable!(m.inner.pbdata, Int[], T[], zero(T), T(-Inf), T(Inf))\n\n    # Update tracking of variables\n    m.var_indices[x] = j\n    m.var2bndtype[x] = Set{Type{<:MOI.AbstractScalarSet}}()\n    push!(m.var_indices_moi, x)\n\n    return x\nend\n\n# =============================================\n#   3. Delete variables\n# =============================================\nfunction MOI.delete(m::Optimizer, v::MOI.VariableIndex)\n    MOI.throw_if_not_valid(m, v)\n\n    # Update inner model\n    j = m.var_indices[v]\n    old_name = get_attribute(m.inner, VariableName(), j)\n    delete_variable!(m.inner.pbdata, j)\n\n    # Remove bound tracking\n    delete!(m.var2bndtype, v)\n\n    # Name update\n    if old_name != \"\"\n        s = m.name2var[old_name]\n        delete!(s, v)\n        length(s) == 0 && delete!(m.name2var, old_name)\n    end\n\n    # Update indices correspondence\n    deleteat!(m.var_indices_moi, j)\n    delete!(m.var_indices, v)\n    for v_ in m.var_indices_moi[j:end]\n        m.var_indices[v_] -= 1\n    end\n    return nothing\nend\n\n# =============================================\n#   4. Get/set variable attributes\n# =============================================\n\nfunction MOI.get(m::Optimizer, ::Type{MOI.VariableIndex}, name::String)\n    s = get(m.name2var, name, Set{MOI.VariableIndex}())\n    if length(s) == 0\n        return nothing\n    elseif length(s) == 1\n        return first(s)\n    else\n        error(\"Duplicate variable name detected: $(name)\")\n    end\nend\n\nfunction MOI.get(m::Optimizer, ::MOI.VariableName, v::MOI.VariableIndex)\n    MOI.throw_if_not_valid(m, v)\n\n    # Get name from inner model\n    j = m.var_indices[v]\n    return get_attribute(m.inner, VariableName(), j)\nend\n\nfunction MOI.set(m::Optimizer, ::MOI.VariableName, v::MOI.VariableIndex, name::String)\n    # Check that variable does exist\n    MOI.throw_if_not_valid(m, v)\n\n    # Update inner model\n    j = m.var_indices[v]\n    old_name = get_attribute(m.inner, VariableName(), j)\n    if name == old_name\n        return  # It's the same name!\n    end\n    set_attribute(m.inner, VariableName(), j, name)\n\n    s = get!(m.name2var, name, Set{MOI.VariableIndex}())\n\n    # Update names mapping\n    push!(s, v)\n    # Delete old name\n    s_old = get(m.name2var, old_name, Set{MOI.ConstraintIndex}())\n    if length(s_old) == 0\n        # Variable didn't have name before\n    elseif length(s_old) == 1\n        # Delete this from mapping\n        delete!(m.name2var, old_name)\n    else\n        delete!(s_old, v)\n    end\n    return nothing\nend\n\nfunction MOI.get(m::Optimizer{T},\n    attr::MOI.VariablePrimal,\n    x::MOI.VariableIndex\n) where{T}\n    MOI.throw_if_not_valid(m, x)\n    MOI.check_result_index_bounds(m, attr)\n\n    # Query inner solution\n    j = m.var_indices[x]\n    return m.inner.solution.x[j]\nend\n"
  },
  {
    "path": "src/Interfaces/tulip_julia_api.jl",
    "content": "using QPSReader\nusing TimerOutputs: tottime\n\n# TODO: user-facing API in Julia\n# Other APIs should wrap this one\n# TODO: docstrings\n# TODO: define Traits on attributes (e.g.: IsModifiable, IsNumeric, etc..)\n#   for error messages\n\n\n\"\"\"\n    load_problem!(m::Model{T}, fname::String)\n\nRead a model from file `fname` and load it into model `m`.\n\nOnly free MPS files are currently supported.\n\"\"\"\nfunction load_problem!(m::Model{T}, fname::String) where{T}\n    Base.empty!(m)\n\n    dat = with_logger(Logging.NullLogger()) do\n        _open(fname) do io\n            readqps(io, mpsformat=:free)\n        end\n    end\n\n    # TODO: avoid allocations when T is Float64\n    objsense = !(dat.objsense == :max)\n    load_problem!(m.pbdata,\n        dat.name,\n        objsense, T.(dat.c), T(dat.c0),\n        sparse(dat.arows, dat.acols, T.(dat.avals), dat.ncon, dat.nvar),\n        T.(dat.lcon), T.(dat.ucon),\n        T.(dat.lvar), T.(dat.uvar),\n        dat.connames, dat.varnames\n    )\n\n    return m\nend\n\n\"\"\"\n    get_attribute(model::Model, ::ModelName)\n\nQuery the `ModelName` attribute from `model`\n\"\"\"\nget_attribute(m::Model, ::ModelName) = m.pbdata.name\n\n\"\"\"\n    set_attribute(model::Model, ::ModelName, name::String)\n\nSet the `ModelName` attribute in `model`\n\"\"\"\nset_attribute(m::Model, ::ModelName, name::String) = (m.pbdata.name = name; return nothing)\n\n\"\"\"\n    get_attribute(model::Model, ::Status)\n\nQuery the `Status` attribute from `model`\n\"\"\"\nfunction get_attribute(m::Model, ::Status)\n    return m.status\nend\n\n\"\"\"\n    get_attribute(model::Model, ::SolutionTime)\n\nQuery the `SolutionTime` attribute from `model`\n\"\"\"\nfunction get_attribute(m::Model, ::SolutionTime)\n    if isnothing(m.solver)\n        return 0\n    else\n      local ns = tottime(m.solver.timer)\n      return ns * 1e-9\n    end\nend\n\n\"\"\"\n    get_attribute(model::Model, ::BarrierIterations)\n\nQuery the `BarrierIterations` attribute from `model`\n\"\"\"\nfunction get_attribute(m::Model, ::BarrierIterations)\n    if isnothing(m.solver)\n        return 0\n    else\n        return m.solver.niter\n    end\nend\n\n\"\"\"\n    set_attribute(m::Model{T}, ::VariableLowerBound, j::Int, lb::T)\n\nSet the lower bound of variable `j` in model `m` to `lb`.\n\"\"\"\nfunction set_attribute(m::Model{T}, ::VariableLowerBound, j::Int, lb::T) where{T}\n    # sanity checks\n    1 <= j <= m.pbdata.nvar || error(\"Invalid variable index $j\")\n\n    # Update bound\n    m.pbdata.lvar[j] = lb\n    return nothing\nend\n\n\"\"\"\n    get_attribute(m::Model{T}, ::VariableLowerBound, j::Int)\n\nQuery the lower bound of variable `j` in model `m`.\n\"\"\"\nfunction get_attribute(m::Model, ::VariableLowerBound, j::Int)\n    # sanity checks\n    1 <= j <= m.pbdata.nvar || error(\"Invalid variable index $j\")\n\n    # Update bound\n    return m.pbdata.lvar[j]\nend\n\n\"\"\"\n    set_attribute(m::Model{T}, ::VariableUpperBound, j::Int, ub::T)\n\nSet the upper bound of variable `j` in model `m` to `ub`.\n\"\"\"\nfunction set_attribute(m::Model{T}, ::VariableUpperBound, j::Int, ub::T) where{T}\n    # sanity checks\n    1 <= j <= m.pbdata.nvar || error(\"Invalid variable index $j\")\n\n    # Update bound\n    m.pbdata.uvar[j] = ub\n    return nothing\nend\n\n\"\"\"\n    set_attribute(m::Model{T}, ::ConstraintLowerBound, i::Int, lb::T)\n\nSet the lower bound of constraint `i` in model `m` to `lb`.\n\"\"\"\nfunction set_attribute(m::Model{T}, ::ConstraintLowerBound, i::Int, lb::T) where{T}\n    # sanity checks\n    1 <= i <= m.pbdata.ncon || error(\"Invalid constraint index $i\")\n\n    # Update bound\n    m.pbdata.lcon[i] = lb\n    return nothing\nend\n\n\"\"\"\n    set_attribute(m::Model{T}, ::ConstraintUpperBound, i::Int, ub::T)\n\nSet the upper bound of constraint `i` in model `m` to `ub`.\n\"\"\"\nfunction set_attribute(m::Model{T}, ::ConstraintUpperBound, i::Int, ub::T) where{T}\n    # sanity checks\n    1 <= i <= m.pbdata.ncon || error(\"Invalid constraint index $i\")\n\n    # Update bound\n    m.pbdata.ucon[i] = ub\n    return nothing\nend\n\n\"\"\"\n    get_attribute(m::Model, ::VariableName, j::Int)\n\nQuery the name of variable `j` in model `m`\n\"\"\"\nfunction get_attribute(m::Model, ::VariableName, j::Int)\n    1 <= j <= m.pbdata.nvar || error(\"Invalid variable index $j\")\n    return m.pbdata.var_names[j]\nend\n\n\"\"\"\n    set_attribute(m::Model, ::VariableName, j::Int, name::String)\n\nSet the name of variable `j` in model `m` to `name`.\n\"\"\"\nfunction set_attribute(m::Model, ::VariableName, j::Int, name::String)\n    1 <= j <= m.pbdata.nvar || error(\"Invalid variable index $j\")\n    # TODO: ensure that no two variables have the same name\n    m.pbdata.var_names[j] = name\n    return nothing\nend\n\n\"\"\"\n    get_attribute(m::Model, ::ConstraintName, i::Int)\n\nQuery the name of constraint `i` in model `m`\n\"\"\"\nfunction get_attribute(m::Model, ::ConstraintName, i::Int)\n    1 <= i <= m.pbdata.ncon || error(\"Invalid constraint index $i\")\n    return m.pbdata.con_names[i]\nend\n\n\"\"\"\n    set_attribute(m::Model, ::ConstraintName, i::Int, name::String)\n\nSet the name of constraint `i` in model `m` to `name`.\n\"\"\"\nfunction set_attribute(m::Model, ::ConstraintName, i::Int, name::String)\n    1 <= i <= m.pbdata.ncon || error(\"Invalid constraint index $i\")\n    m.pbdata.con_names[i] = name\n    return nothing\nend\n\n# TODO: Set/get parameters\n\"\"\"\n    get_parameter(m::Model, pname::String)\n\nQuery the value of parameter `pname` in model `m`.\n\"\"\"\nfunction get_parameter(m::Model, pname::String)\n    return getfield(m.params, Symbol(pname))\nend\n\n\"\"\"\n    set_parameter(m::Model, pname::String, val)\n\nSet the value of parameter `pname` in model `m` to `val`\n\"\"\"\nfunction set_parameter(m::Model, pname::String, val)\n    if length(pname) > 4 && pname[1:4] == \"IPM_\"\n        setfield!(m.params.IPM, Symbol(pname[5:end]), val)\n    elseif length(pname) > 4 && pname[1:4] == \"KKT_\"\n        setfield!(m.params.KKT, Symbol(pname[5:end]), val)\n    elseif length(pname) > 10 && pname[1:9] == \"Presolve_\"\n        setfield!(m.params.Presolve, Symbol(pname[10:end]), val)\n    elseif hasfield(typeof(m.params), Symbol(pname))\n        setfield!(m.params, Symbol(pname), val)\n    else\n        error(\"Unknown option: $pname\")\n    end\n    return nothing\nend\n\n\n# TODO: Query solution value\nget_attribute(m::Model, ::ObjectiveConstant) = m.pbdata.obj0\nset_attribute(m::Model{T}, ::ObjectiveConstant, obj0::T) where{T} = (m.pbdata.obj0 = obj0; return nothing)\n\n\"\"\"\n    get_attribute(model::Model, ::ObjectiveValue)\n\nQuery the `ObjectiveValue` attribute from `model`\n\"\"\"\nfunction get_attribute(m::Model{T}, ::ObjectiveValue) where{T}\n    if isnothing(m.solution)\n        error(\"Model has no solution\")\n    end\n\n    pst = m.solution.primal_status\n\n    if pst != Sln_Unknown\n        z  = dot(m.solution.x, m.pbdata.obj)\n        # If solution is a ray, ignore constant objective term\n        is_ray = m.solution.is_primal_ray\n        z0 = !is_ray * m.pbdata.obj0\n        return (z + z0)\n    else\n        # No solution, return zero\n        return zero(T)\n    end\nend\n\n\"\"\"\n    get_attribute(model::Model, ::DualObjectiveValue)\n\nQuery the `DualObjectiveValue` attribute from `model`\n\"\"\"\nfunction get_attribute(m::Model{T}, ::DualObjectiveValue) where{T}\n    if isnothing(m.solution)\n        error(\"Model has no solution\")\n    end\n\n    dst = m.solution.dual_status\n\n    if dst != Sln_Unknown\n        yl = m.solution.y_lower\n        yu = m.solution.y_upper\n        sl = m.solution.s_lower\n        su = m.solution.s_upper\n\n        bl = m.pbdata.lcon\n        bu = m.pbdata.ucon\n        xl = m.pbdata.lvar\n        xu = m.pbdata.uvar\n\n        z = (\n              dot(yl, Diagonal(isfinite.(bl)), bl)\n            - dot(yu, Diagonal(isfinite.(bu)), bu)\n            + dot(sl, Diagonal(isfinite.(xl)), xl)\n            - dot(su, Diagonal(isfinite.(xu)), xu)\n        )\n\n        # If problem is maximization, we need to negate the dual value\n        #   to comply with MOI duality convention\n        z = m.pbdata.objsense ? z : -z\n\n        # If solution is a ray, ignore constant objective term\n        is_ray = m.solution.is_dual_ray\n        z0 = !is_ray * m.pbdata.obj0\n        return (z + z0)\n    else\n        # No solution, return zero\n        return zero(T)\n    end\nend\n"
  },
  {
    "path": "src/KKT/Cholmod/cholmod.jl",
    "content": "module TlpCholmod\n\nusing LinearAlgebra\nusing SparseArrays\nusing SparseArrays.CHOLMOD\n\nusing ..KKT: AbstractKKTBackend, AbstractKKTSolver\nusing ..KKT: AbstractKKTSystem, K1, K2\nimport ..KKT: setup, update!, solve!, backend, linear_system\n\n\"\"\"\n    Backend\n\nCHOLMOD backend for solving linear systems.\n\nSee [`CholmodSolver`](@ref) for further details.\n\"\"\"\nstruct Backend <: AbstractKKTBackend end\n\n\"\"\"\n    CholmodSolver{T,S<:AbstractKKTSystem}\n\nCHOLMOD-based KKT solver.\n\n# Supported arithmetics\n* `Float64`\n\n# Supported systems\n* [`K2`](@ref) via ``LDLᵀ`` factorization\n* [`K1`](@ref) via Cholesky (``LLᵀ``) factorization\n\n# Examples\n\n* To solve the augmented system with CHOLMOD's ``LDL^{T}`` factorization:\n```julia\nset_parameter(tlp_model, \"KKT_Backend\", Tulip.KKT.TlpCholmod.Backend())\nset_parameter(tlp_model, \"KKT_System\", Tulip.KKT.K2())\n```\n\n* To solve the normal equations system with CHOLMOD's Cholesky factorization:\n```julia\nset_parameter(tlp_model, \"KKT_Backend\", Tulip.KKT.TlpCholmod.Backend())\nset_parameter(tlp_model, \"KKT_System\", Tulip.KKT.K1())\n```\n\"\"\"\nmutable struct CholmodSolver{T,S} <: AbstractKKTSolver{T}\n    # Problem data\n    m::Int\n    n::Int\n    A::SparseMatrixCSC{T,Int}\n\n    # Workspace\n    # TODO: store K as CHOLMOD.Sparse instead of SparseMatrixCSC\n    θ::Vector{T}               # Diagonal scaling\n    regP::Vector{T}            # Primal regularization\n    regD::Vector{T}            # Dual regularization\n    K::SparseMatrixCSC{T,Int}  # KKT matrix\n    F::CHOLMOD.Factor{T}       # Factorization\n    ξ::Vector{T}               # RHS of KKT system\nend\n\nbackend(::CholmodSolver) = \"CHOLMOD\"\n\n# Convert to sparse matrix if other type is used\nsetup(A, system, backend::Backend) = setup(convert(SparseMatrixCSC, A), system, backend)\n\ninclude(\"spd.jl\")  # Normal equations\ninclude(\"sqd.jl\")  # Augmented system\n\nend  # module\n"
  },
  {
    "path": "src/KKT/Cholmod/spd.jl",
    "content": "const CholmodSPD = CholmodSolver{Float64,K1}\n\nlinear_system(::CholmodSPD) = \"Normal equations (K1)\"\n\nfunction setup(A::SparseMatrixCSC{Float64}, ::K1, ::Backend)\n    m, n = size(A)\n\n    θ = ones(Float64, n)\n    regP = ones(Float64, n)\n    regD = ones(Float64, m)\n    ξ = zeros(Float64, m)\n\n    # TODO: analyze + in-place A*D*A' product\n    K = sparse(A * A') + spdiagm(0 => regD)\n\n    # TODO: PSD-ness checks\n    F = cholesky(Symmetric(K))\n\n    return CholmodSolver{Float64,K1}(m, n, A, θ, regP, regD, K, F, ξ)\nend\n\nfunction update!(kkt::CholmodSPD, θ, regP, regD)\n    m, n = kkt.m, kkt.n\n\n    # Sanity checks\n    length(θ)  == n || throw(DimensionMismatch(\n        \"length(θ)=$(length(θ)) but KKT solver has n=$n.\"\n    ))\n    length(regP) == n || throw(DimensionMismatch(\n        \"length(regP)=$(length(regP)) but KKT solver has n=$n\"\n    ))\n    length(regD) == m || throw(DimensionMismatch(\n        \"length(regD)=$(length(regD)) but KKT solver has m=$m\"\n    ))\n\n    copyto!(kkt.θ, θ)\n    copyto!(kkt.regP, regP)\n    copyto!(kkt.regD, regD)\n\n    # Form normal equations matrix\n    # TODO: use in-place update of S\n    D = inv(Diagonal(kkt.θ .+ kkt.regP))\n    kkt.K = (kkt.A * D * kkt.A') + spdiagm(0 => kkt.regD)\n\n    # Update factorization\n    cholesky!(kkt.F, Symmetric(kkt.K), check=false)\n    issuccess(kkt.F) || throw(PosDefException(0))\n\n    return nothing\nend\n\nfunction solve!(dx, dy, kkt::CholmodSPD, ξp, ξd)\n    m, n = kkt.m, kkt.n\n\n    D = inv(Diagonal(kkt.θ .+ kkt.regP))\n    copyto!(kkt.ξ, ξp)\n    mul!(kkt.ξ, kkt.A, D * ξd, true, true)\n\n    # Solve normal equations\n    # CHOLMOD doesn't have in-place solve, so this line will allocate\n    dy .= kkt.F \\ kkt.ξ\n\n    # Recover dx\n    copyto!(dx, ξd)\n    mul!(dx, kkt.A', dy, 1.0, -1.0)\n    lmul!(D, dx)\n\n    # TODO: iterative refinement\n    return nothing\nend\n"
  },
  {
    "path": "src/KKT/Cholmod/sqd.jl",
    "content": "const CholmodSQD = CholmodSolver{Float64,K2}\n\nlinear_system(::CholmodSQD) = \"Augmented system (K2)\"\n\nfunction setup(A::SparseMatrixCSC{Float64,Int}, ::K2, ::Backend)\n    m, n = size(A)\n\n    θ = ones(Float64, n)\n    regP = ones(Float64, n)\n    regD = ones(Float64, m)\n    ξ = zeros(Float64, m+n)\n\n    K = [\n        spdiagm(0 => -θ)  A';\n        spzeros(Float64, m, n) spdiagm(0 => ones(m))\n    ]\n\n    # TODO: Symbolic factorization only\n    F = ldlt(Symmetric(K))\n\n    return CholmodSolver{Float64,K2}(m, n, A, θ, regP, regD, K, F, ξ)\nend\n\nfunction update!(kkt::CholmodSQD, θ, regP, regD)\n    m, n = kkt.m, kkt.n\n\n    # Sanity checks\n    length(θ)  == n || throw(DimensionMismatch(\n        \"length(θ)=$(length(θ)) but KKT solver has n=$n.\"\n    ))\n    length(regP) == n || throw(DimensionMismatch(\n        \"length(regP)=$(length(regP)) but KKT solver has n=$n\"\n    ))\n    length(regD) == m || throw(DimensionMismatch(\n        \"length(regD)=$(length(regD)) but KKT solver has m=$m\"\n    ))\n\n    copyto!(kkt.θ, θ)\n    copyto!(kkt.regP, regP)\n    copyto!(kkt.regD, regD)\n\n    # Update KKT matrix\n    # K is stored as upper-triangular, and only its diagonal is changed\n    @inbounds for j in 1:kkt.n\n        k = kkt.K.colptr[1+j] - 1\n        kkt.K.nzval[k] = -kkt.θ[j] - regP[j]\n    end\n    @inbounds for i in 1:kkt.m\n        k = kkt.K.colptr[1+kkt.n+i] - 1\n        kkt.K.nzval[k] = regD[i]\n    end\n\n    ldlt!(kkt.F, Symmetric(kkt.K))\n    return nothing\nend\n\nfunction solve!(dx, dy, kkt::CholmodSQD, ξp, ξd)\n    m, n = kkt.m, kkt.n\n\n    # Setup right-hand side\n    @views copyto!(kkt.ξ[1:n], ξd)\n    @views copyto!(kkt.ξ[(n+1):end], ξp)\n\n    # Solve augmented system\n    # CHOLMOD doesn't have in-place solve, so this line will allocate\n    δ = kkt.F \\ kkt.ξ\n\n    # Recover dx, dy\n    @views copyto!(dx, δ[1:n])\n    @views copyto!(dy, δ[(n+1):end])\n\n    # TODO: iterative refinement\n    return nothing\nend\n"
  },
  {
    "path": "src/KKT/Dense/lapack.jl",
    "content": "module TlpDense\n\nusing LinearAlgebra\nusing LinearAlgebra:BlasReal\n\nusing ..KKT: AbstractKKTBackend, AbstractKKTSolver\nusing ..KKT: AbstractKKTSystem, K1, K2\nimport ..KKT: setup, update!, solve!, backend, linear_system\n\n\"\"\"\n    Backend\n\nDense linear algebra backend for solving linear systems.\n\nSee [`DenseSolver`](@ref) for further details.\n\"\"\"\nstruct Backend <: AbstractKKTBackend end\n\n\"\"\"\n    DenseSolver{T}\n\nDense linear algebra-based KKT solver.\n\n# Supported arithmetics\n\nAll arithmetics are supported.\n\nBLAS/LAPACK routines are used automatically with `Float32` and `Float64` arithmetic.\n\n# Supported systems\n* [`K1`](@ref) via Cholesky factorization\n\"\"\"\nmutable struct DenseSolver{T} <: AbstractKKTSolver{T}\n    # Problem data\n    m::Int\n    n::Int\n    A::Matrix{T}\n\n    # Workspace\n    _A::Matrix{T}    # Place-holder for scaled copy of A\n    θ::Vector{T}     # Diagonal scaling\n    regP::Vector{T}  # Primal regularization\n    regD::Vector{T}  # Dual regularization\n    K::Matrix{T}     # KKT matrix\n    ξ::Vector{T}     # RHS of KKT system\nend\n\nbackend(::DenseSolver) = \"Julia.LinearAlgebra\"\nbackend(::DenseSolver{<:BlasReal}) = \"LAPACK $(LinearAlgebra.BLAS.vendor())\"\nlinear_system(::DenseSolver) = \"Normal equations (K1)\"\n\nfunction setup(A::Matrix{T}, ::K1, ::Backend) where{T}\n    m, n = size(A)\n\n    _A = Matrix{T}(undef, m, n)\n    θ = ones(T, n)\n    regP = ones(T, n)\n    regD = ones(T, m)\n    K = Matrix{T}(undef, m, m)\n    ξ = zeros(T, m)\n\n    return DenseSolver{T}(m, n, A, _A, θ, regP, regD, K, ξ)\nend\n\nfunction update!(kkt::DenseSolver, θ, regP, regD)\n    m, n = kkt.m, kkt.n\n\n    # Sanity checks\n    length(θ)  == n || throw(DimensionMismatch(\n        \"length(θ)=$(length(θ)) but KKT solver has n=$n.\"\n    ))\n    length(regP) == n || throw(DimensionMismatch(\n        \"length(regP)=$(length(regP)) but KKT solver has n=$n\"\n    ))\n    length(regD) == m || throw(DimensionMismatch(\n        \"length(regD)=$(length(regD)) but KKT solver has m=$m\"\n    ))\n\n    copyto!(kkt.θ, θ)\n    copyto!(kkt.regP, regP)\n    copyto!(kkt.regD, regD)\n\n    # Re-compute normal equations matrix\n    # There's no function that does S = A*D*A', so we cache a copy of A\n    copyto!(kkt._A, kkt.A)\n    D = sqrt(inv(Diagonal(kkt.θ .+ kkt.regP)))\n    rmul!(kkt._A, D)  # B = A * √D\n    mul!(kkt.K, kkt._A, transpose(kkt._A), true, false)  # Now K = A*D*A'\n    # Finally, add dual regularizations to the diagonal\n    @inbounds for i in 1:kkt.m\n        kkt.K[i, i] += kkt.regD[i]\n    end\n\n    # In-place Cholesky factorization\n    cholesky!(Symmetric(kkt.K))\n\n    return nothing\nend\n\nfunction solve!(dx, dy, kkt::DenseSolver{T}, ξp, ξd) where{T}\n    m, n = kkt.m, kkt.n\n\n    # Set-up right-hand side\n    D = inv(Diagonal(kkt.θ .+ kkt.regP))\n    copyto!(dy, ξp)\n    mul!(dy, kkt.A, D * ξd, true, true)\n\n    # Solve normal equations\n    ldiv!(UpperTriangular(kkt.K)', dy)\n    ldiv!(UpperTriangular(kkt.K) , dy)\n\n    # Recover dx\n    copyto!(dx, ξd)\n    mul!(dx, kkt.A', dy, one(T), -one(T))\n    lmul!(D, dx)\n\n    # TODO: Iterative refinement\n    return nothing\nend\n\nend  # module\n"
  },
  {
    "path": "src/KKT/KKT.jl",
    "content": "module KKT\n\nusing LinearAlgebra\nusing SparseArrays\n\nusing LinearOperators\n\nexport AbstractKKTSystem, AbstractKKTBackend, AbstractKKTSolver\nexport KKTOptions\n\n\"\"\"\n    AbstractKKTSystem\n\nAbstract type for KKT systems\n\"\"\"\nabstract type AbstractKKTSystem end\n\ninclude(\"systems.jl\")\n\n\"\"\"\n    AbstractKKTBackend\n\nAbstract type for KKT backend, i.e., the actual linear solver.\n\"\"\"\nabstract type AbstractKKTBackend end\n\n\"\"\"\n    DefaultKKTBackend\n\nDefault setting for KKT backend.\n\nCurrently defaults to [`TlpCholmod.Backend`](@ref) for `Float64` arithmetic,\n    and [`TlpLDLFact.Backend`](@ref) otherwise.\n\"\"\"\nstruct DefaultKKTBackend <: AbstractKKTBackend end\n\n\"\"\"\n    AbstractKKTSolver{T}\n\nAbstract container for solving KKT systems in arithmetic `T`.\n\"\"\"\nabstract type AbstractKKTSolver{T} end\n\n\"\"\"\n    KKTOptions{T}\n\nKKT solver options.\n\"\"\"\nBase.@kwdef mutable struct KKTOptions{T}\n    Backend::AbstractKKTBackend = DefaultKKTBackend()\n    System::AbstractKKTSystem = DefaultKKTSystem()\nend\n\n\"\"\"\n    setup(A, system, backend; kwargs...)\n\nInstantiate a KKT solver object.\n\"\"\"\nfunction setup end\n\n#\n# Specialized implementations should extend the functions below\n#\n\n\"\"\"\n    update!(kkt, θinv, regP, regD)\n\nUpdate internal data and factorization/pre-conditioner.\n\nAfter this call, `kkt` can be used to solve the augmented system\n```\n    [-(Θ⁻¹ + Rp)   Aᵀ] [dx] = [ξd]\n    [   A          Rd] [dy]   [ξp]\n```\nfor given right-hand sides `ξd` and `ξp`.\n\n# Arguments\n* `kkt::AbstractKKTSolver{T}`: the KKT solver object\n* `θinv::AbstractVector{T}`: ``θ⁻¹``\n* `regP::AbstractVector{T}`: primal regularizations\n* `regD::AbstractVector{T}`: dual regularizations\n\"\"\"\nfunction update! end\n\n\"\"\"\n    solve!(dx, dy, kkt, ξp, ξd)\n\nSolve the symmetric quasi-definite augmented system\n```\n    [-(Θ⁻¹ + Rp)   Aᵀ] [dx] = [ξd]\n    [   A          Rd] [dy]   [ξp]\n```\nand over-write `dx`, `dy` with the result.\n\n# Arguments\n- `dx, dy`: Vectors of unknowns, modified in-place\n- `kkt`: Linear solver for the augmented system\n- `ξp, ξd`: Right-hand-side vectors\n\"\"\"\nfunction solve! end\n\n\"\"\"\n    arithmetic(kkt::AbstractKKTSolver)\n\nReturn the arithmetic used by the solver.\n\"\"\"\narithmetic(::AbstractKKTSolver{T}) where T = T\n\n\"\"\"\n    backend(kkt)\n\nReturn the name of the solver's backend.\n\"\"\"\nbackend(::AbstractKKTSolver) = \"Unkown\"\n\n\"\"\"\n    linear_system(kkt)\n\nReturn which system is solved by the kkt solver.\n\"\"\"\nlinear_system(::AbstractKKTSolver) = \"Unkown\"\n\n# Generic tests\ninclude(\"Test/test.jl\")\n\n# Custom linear solvers\ninclude(\"Dense/lapack.jl\")\ninclude(\"Cholmod/cholmod.jl\")\ninclude(\"LDLFactorizations/ldlfact.jl\")\nconst TlpLDLFact = TlpLDLFactorizations\ninclude(\"Krylov/krylov.jl\")\n\n# Default backend and system choices\nfunction setup(A, ::DefaultKKTSystem, ::DefaultKKTBackend)\n    T = eltype(A)\n    if T == Float64\n        return setup(A, K2(), TlpCholmod.Backend())\n    else\n        return setup(A, K2(), TlpLDLFact.Backend())\n    end\nend\n\nend  # module\n"
  },
  {
    "path": "src/KKT/Krylov/defs.jl",
    "content": "const _KRYLOV_SPD = Union{\n    Krylov.CgWorkspace,\n    Krylov.CrWorkspace,\n    Krylov.CarWorkspace,\n}\n\nconst _KRYLOV_SID = Union{\n    Krylov.MinresWorkspace,\n    Krylov.MinaresWorkspace,\n    Krylov.MinresQlpWorkspace,\n    Krylov.SymmlqWorkspace\n}\n\nconst _KRYLOV_SQD = Union{\n    Krylov.TricgWorkspace,\n    Krylov.TrimrWorkspace,\n}\n\nconst _KRYLOV_LN = Union{\n    Krylov.LnlqWorkspace,\n    Krylov.CraigWorkspace,\n    Krylov.CraigmrWorkspace,\n}\n\nconst _KRYLOV_LS = Union{\n    Krylov.LslqWorkspace,\n    Krylov.LsqrWorkspace,\n    Krylov.LsmrWorkspace,\n}\n"
  },
  {
    "path": "src/KKT/Krylov/krylov.jl",
    "content": "module TlpKrylov\n\nusing LinearAlgebra\n\nusing Krylov\nusing LinearOperators\nconst LO = LinearOperators\n\nusing ..KKT: AbstractKKTBackend, AbstractKKTSolver\nusing ..KKT: AbstractKKTSystem, K1, K2\nimport ..KKT: arithmetic, backend, linear_system\nimport ..KKT: setup, update!, solve!\n\ninclude(\"defs.jl\")\n\n\"\"\"\n    Backend{KS<:Krylov.KrylovWorkspace,V<:AbstractVector}\n\n[Krylov.jl](https://github.com/JuliaSmoothOptimizers/Krylov.jl)-based backend for solving linear systems.\n\nThe type is parametrized by:\n* `KS<:Krylov.KrylovWorkspace`: workspace type for the Krylov method.\n    Also defines the Krylov method to be used.\n* `V<:AbstractVector`: the vector storage type used within the Krylov method.\n    This should be set to `Vector{T}` (for arithmetic `T`) unless, e.g., one uses a GPU.\n\nSee the [Krylov.jl documentation](https://juliasmoothoptimizers.github.io/Krylov.jl/dev/inplace/) for further details.\n\n# Example usage\n\nAll the following examples assume everything runs on a CPU in `Float64` arithmetic.\n* To use the conjugate gradient:\n```julia\nbackend = KKT.TlpKrylov.Backend(Krylov.CgWorkspace, Vector{Float64})\n```\n* To use MINRES:\n```julia\nbackend = KKT.TlpKrylov.Backend(Krylov.MinresWorkspace, Vector{Float64})\n```\n\"\"\"\nstruct Backend{KW,V} <: AbstractKKTBackend\n    krylov_workspace::Type{KW}\n    vector_storage::Type{V}\nend\n\n\"\"\"\n    AbstractKrylovSolver{T}\n\nAbstract type for Kyrlov-based linear solvers.\n\"\"\"\nabstract type AbstractKrylovSolver{T} <: AbstractKKTSolver{T} end\n\ninclude(\"spd.jl\")\ninclude(\"sid.jl\")\ninclude(\"sqd.jl\")\n\nend  # module\n"
  },
  {
    "path": "src/KKT/Krylov/sid.jl",
    "content": "\"\"\"\n    SIDSolver\n\"\"\"\nmutable struct SIDSolver{T,V,Ta,KL,KW} <: AbstractKrylovSolver{T}\n    # Problem data\n    m::Int\n    n::Int\n    A::Ta\n\n    # IPM-related workspace\n    θ::Vector{T}\n    regP::Vector{T}\n    regD::Vector{T}\n\n    # Krylov-related workspace\n    Θp::Diagonal{T,V}\n    Θd::Diagonal{T,V}\n    ξ::V\n    opK::KL\n\n    # Krylov solver & related options\n    atol::T\n    rtol::T\n    krylov_workspace::KW\n\n    # TODO: preconditioner\nend\n\nbackend(kkt::SIDSolver) = \"$(typeof(kkt.krylov_workspace))\"\nlinear_system(kkt::SIDSolver) = \"K2\"\n\nfunction setup(A, ::K2, backend::Backend{KW,V}) where{KW<:_KRYLOV_SID,V}\n    Ta = typeof(A)\n    T = eltype(A)\n    T == eltype(V) || error(\"eltype(A)=$T incompatible with eltype of Krylov vector storage $V.\")\n\n    m, n = size(A)\n\n    # Workspace\n    θ = ones(T, n)\n    regP = ones(T, n)\n    regD = ones(T, m)\n\n    Θp = Diagonal(V(undef, n))\n    Θd = Diagonal(V(undef, m))\n    ξ  = V(undef, m+n)\n\n    # Define linear operator for the augmented system\n    # This linear operator is symmetric indefinite\n    opK = LO.LinearOperator(T, m+n, m+n, true, false,\n        (u, w, α, β) -> begin\n            @views u1 = u[1:n]\n            @views u2 = u[(n+1):(m+n)]\n            @views w1 = w[1:n]\n            @views w2 = w[n+1:n+m]\n\n            mul!(u1, Θp, w1, α, β)\n            mul!(u1, A', w2, α, one(T))\n\n            mul!(u2, A , w1, α, β)\n            mul!(u2, Θd, w2, α, one(T))\n            u\n        end\n    )\n\n    # Allocate Krylov solver's workspace\n    atol = sqrt(eps(T))\n    rtol = sqrt(eps(T))\n    krylov_workspace = KW(m+n, m+n, V)\n\n    return SIDSolver{T,V,Ta,typeof(opK),typeof(krylov_workspace)}(\n        m, n, A,\n        θ, regP, regD,\n        Θp, Θd, ξ,\n        opK,\n        atol, rtol,\n        krylov_workspace\n    )\nend\n\nfunction update!(kkt::SIDSolver, θ, regP, regD)\n\n    copyto!(kkt.θ, θ)\n    copyto!(kkt.regP, regP)\n    copyto!(kkt.regD, regD)\n\n    copyto!(kkt.Θd.diag, regD)\n    copyto!(kkt.Θp.diag, -(kkt.θ .+ kkt.regP))\n\n    return nothing\nend\n\nfunction solve!(dx, dy, kkt::SIDSolver{T}, ξp, ξd) where{T}\n    m, n = kkt.m, kkt.n\n\n    @views copyto!(kkt.ξ[1:m], ξp)\n    @views copyto!(kkt.ξ[(m+1):(m+n)], ξd)\n\n    # Solve the augmented system\n    krylov_solve!(kkt.krylov_workspace, kkt.opK, kkt.ξ; atol=kkt.atol, rtol=kkt.rtol)\n\n    # Recover dx, dy\n    copyto!(dx, kkt.krylov_workspace.x[1:n])\n    copyto!(dy, kkt.krylov_workspace.x[(n+1):(m+n)])\n\n    # TODO: iterative refinement (?)\n    return nothing\nend\n"
  },
  {
    "path": "src/KKT/Krylov/spd.jl",
    "content": "\"\"\"\n    SPDSolver\n\"\"\"\nmutable struct SPDSolver{T,V,Ta,KL,KS} <: AbstractKrylovSolver{T}\n    # Problem data\n    m::Int\n    n::Int\n    A::Ta\n\n    # IPM-related workspace\n    θ::Vector{T}\n    regP::Vector{T}\n    regD::Vector{T}\n\n    # Krylov-related workspace\n    D::Diagonal{T,V}\n    Rd::Diagonal{T,V}\n    ξ::V\n    opK::KL\n\n    # Krylov solver & related options\n    atol::T\n    rtol::T\n    krylov_solver::KS\n\n    # TODO: preconditioner\nend\n\nbackend(kkt::SPDSolver) = \"$(typeof(kkt.krylov_solver))\"\nlinear_system(kkt::SPDSolver) = \"K1\"\n\nfunction setup(A, ::K1, backend::Backend{KS,V}) where{KS<:Union{_KRYLOV_SPD,_KRYLOV_SID},V}\n    Ta = typeof(A)\n    T = eltype(A)\n    T == eltype(V) || error(\"eltype(A)=$T incompatible with eltype of Krylov vector storage $V.\")\n\n    m, n = size(A)\n\n    # Workspace\n    θ = ones(T, n)\n    regP = ones(T, n)\n    regD = ones(T, m)\n\n    D  = Diagonal(V(undef, n))\n    Rd = Diagonal(V(undef, m))\n    ξ  = V(undef, m)\n\n    # Define linear operator for normal equations system\n    # We need to allocate one temporary vector\n    # This linear operator is symmetric definite positive,\n    #   so we only need to define\n    #   u ⟵ α (A×D×A'+Rd) × w + β u\n    #   i.e., u = α(ADA')×w + (αRd)×w + βu\n    vtmp = V(undef, n)\n    opK = LO.LinearOperator(T, m, m, true, true,\n        (u, w, α, β) -> begin\n            mul!(vtmp, A', w)\n            lmul!(D, vtmp)\n            mul!(u, A, vtmp, α, β)\n            mul!(u, Rd, w, α, one(T))\n            u\n        end\n    )\n\n    # Allocate Krylov solver's workspace\n    atol = sqrt(eps(T))\n    rtol = sqrt(eps(T))\n    krylov_solver = KS(m, m, V)\n\n    return SPDSolver{T,V,Ta,typeof(opK),typeof(krylov_solver)}(\n        m, n, A,\n        θ, regP, regD,\n        D, Rd, ξ,\n        opK,\n        atol, rtol,\n        krylov_solver\n    )\nend\n\nfunction update!(kkt::SPDSolver, θ, regP, regD)\n\n    copyto!(kkt.θ, θ)\n    copyto!(kkt.regP, regP)\n    copyto!(kkt.regD, regD)\n\n    copyto!(kkt.Rd.diag, regD)\n    copyto!(kkt.D.diag, inv.(kkt.θ .+ kkt.regP))\n\n    return nothing\nend\n\nfunction solve!(dx, dy, kkt::SPDSolver{T}, ξp, ξd) where{T}\n    m, n = kkt.m, kkt.n\n\n    copyto!(kkt.ξ, ξp)\n\n    mul!(kkt.ξ, kkt.A, kkt.D * ξd, true, true)\n\n    # Solve the normal equations\n    krylov_solve!(kkt.krylov_solver, kkt.opK, kkt.ξ; atol=kkt.atol, rtol=kkt.rtol)\n    copyto!(dy, kkt.krylov_solver.x)\n\n    # Recover dx\n    copyto!(dx, ξd)\n    mul!(dx, kkt.A', dy, one(T), -one(T))\n    lmul!(kkt.D, dx)\n\n    # TODO: iterative refinement (?)\n    return nothing\nend\n"
  },
  {
    "path": "src/KKT/Krylov/sqd.jl",
    "content": "\"\"\"\n    SQDSolver\n\"\"\"\nmutable struct SQDSolver{T,V,Ta,KS} <: AbstractKrylovSolver{T}\n    # Problem data\n    m::Int\n    n::Int\n    A::Ta\n\n    # IPM-related workspace\n    θ::Vector{T}\n    regP::Vector{T}\n    regD::Vector{T}\n\n    # Krylov-related workspace\n    Θp::Diagonal{T,V}\n    Θp⁻¹::Diagonal{T,V}\n    Θd::Diagonal{T,V}\n    Θd⁻¹::Diagonal{T,V}\n    ξp::V\n    ξd::V\n\n    # Krylov solver & related options\n    atol::T\n    rtol::T\n    krylov_solver::KS\n\n    # TODO: preconditioner\nend\n\nbackend(kkt::SQDSolver) = \"$(typeof(kkt.krylov_solver))\"\nlinear_system(kkt::SQDSolver) = \"K2\"\n\nfunction setup(A, ::K2, backend::Backend{KS,V}) where{KS<:_KRYLOV_SQD,V}\n    Ta = typeof(A)\n    T = eltype(A)\n    T == eltype(V) || error(\"eltype(A)=$T incompatible with eltype of Krylov vector storage $V.\")\n\n    m, n = size(A)\n\n    # Workspace\n    θ = ones(T, n)\n    regP = ones(T, n)\n    regD = ones(T, m)\n\n    Θp   = Diagonal(V(undef, n))\n    Θp⁻¹ = Diagonal(V(undef, n))\n    Θd   = Diagonal(V(undef, m))\n    Θd⁻¹ = Diagonal(V(undef, m))\n    ξp  = V(undef, m)\n    ξd  = V(undef, n)\n\n    # Allocate Krylov solver's workspace\n    atol = sqrt(eps(T))\n    rtol = sqrt(eps(T))\n    krylov_solver = KS(m, n, V)\n\n    return SQDSolver{T,V,Ta,typeof(krylov_solver)}(\n        m, n, A,\n        θ, regP, regD,\n        Θp, Θp⁻¹, Θd, Θd⁻¹,\n        ξp, ξd,\n        atol, rtol,\n        krylov_solver\n    )\nend\n\nfunction update!(kkt::SQDSolver, θ, regP, regD)\n\n    copyto!(kkt.θ, θ)\n    copyto!(kkt.regP, regP)\n    copyto!(kkt.regD, regD)\n\n    copyto!(kkt.Θp.diag, -(kkt.θ .+ kkt.regP))\n    copyto!(kkt.Θp⁻¹.diag, inv.(kkt.θ .+ kkt.regP))  # Θp⁻¹ will be negated by tricg/trimr\n    copyto!(kkt.Θd.diag, kkt.regD)\n    copyto!(kkt.Θd⁻¹.diag, inv.(kkt.regD))\n\n    return nothing\nend\n\nfunction solve!(dx, dy, kkt::SQDSolver{T}, ξp, ξd) where{T}\n    copyto!(kkt.ξp, ξp)\n    copyto!(kkt.ξd, ξd)\n\n    # Solve the augmented system\n    krylov_solve!(kkt.krylov_solver, kkt.A, kkt.ξp, kkt.ξd;\n        M=kkt.Θd⁻¹,\n        N=kkt.Θp⁻¹,\n        atol=kkt.atol,\n        rtol=kkt.rtol\n    )\n\n    # Recover dx, dy\n    copyto!(dx, kkt.krylov_solver.y)\n    copyto!(dy, kkt.krylov_solver.x)\n\n    # TODO: iterative refinement (?)\n    return nothing\nend\n"
  },
  {
    "path": "src/KKT/LDLFactorizations/ldlfact.jl",
    "content": "module TlpLDLFactorizations\n\nusing LinearAlgebra\nusing SparseArrays\n\nusing LDLFactorizations\nconst LDLF = LDLFactorizations\n\nusing ..KKT: AbstractKKTBackend, AbstractKKTSolver\nusing ..KKT: AbstractKKTSystem, K1, K2\nimport ..KKT: setup, update!, solve!, backend, linear_system\n\n\n\"\"\"\n    Backend\n\nLDLFactorizations backend for solving linear systems.\n\nSee [`LDLFactSolver`](@ref) for further details.\n\"\"\"\nstruct Backend <: AbstractKKTBackend end\n\n\"\"\"\n    LDLFactSolver{T,S<:AbstractKKTSystem}\n\n[`LDLFactorizations.jl`](https://github.com/JuliaSmoothOptimizers/LDLFactorizations.jl)-based KKT solver.\n\n# Supported arithmetics\n* All arithmetics are supported\n\n# Supported systems\n* [`K2`](@ref) via ``LDLᵀ`` factorization\n\n# Examples\n\nTo solve the augmented system with LDLFactorizations' ``LDL^{T}`` factorization:\n```julia\nset_parameter(tlp_model, \"KKT_Backend\", Tulip.KKT.TlpLDLFact.Backend())\nset_parameter(tlp_model, \"KKT_System\", Tulip.KKT.K2())\n```\n\"\"\"\nmutable struct LDLFactSolver{T,S} <: AbstractKKTSolver{T}\n    # Problem data\n    m::Int\n    n::Int\n    A::SparseMatrixCSC{T,Int}\n\n    # Workspace\n    θ::Vector{T}                             # Diagonal scaling\n    regP::Vector{T}                          # Primal regularization\n    regD::Vector{T}                          # Dual regularization\n    K::SparseMatrixCSC{T,Int}                # KKT matrix\n    F::LDLF.LDLFactorization{T,Int,Int,Int}  # Factorization\n    ξ::Vector{T}                             # RHS of KKT system\nend\n\nbackend(::LDLFactSolver) = \"LDLFactorizations\"\nlinear_system(::LDLFactSolver) = \"Augmented system (K2)\"\n\n# Convert A to sparse matrix if needed\nsetup(A, system, backend::Backend) = setup(convert(SparseMatrixCSC, A), system, backend)\n\nfunction setup(A::SparseMatrixCSC{T,Int}, ::K2, ::Backend) where{T}\n    m, n = size(A)\n\n    θ = ones(T, n)\n    regP = ones(T, n)\n    regD = ones(T, m)\n    ξ = zeros(T, m+n)\n\n    K = [\n        spdiagm(0 => -θ)  A';\n        spzeros(T, m, n) spdiagm(0 => ones(T, m))\n    ]\n\n    # TODO: Symbolic factorization only\n    F = LDLF.ldl_analyze(Symmetric(K))\n\n    return LDLFactSolver{T,K2}(m, n, A, θ, regP, regD, K, F, ξ)\nend\n\nfunction update!(kkt::LDLFactSolver{T,K2}, θ, regP, regD) where{T}\n    m, n = kkt.m, kkt.n\n\n    # Sanity checks\n    length(θ)  == n || throw(DimensionMismatch(\n        \"length(θ)=$(length(θ)) but KKT solver has n=$n.\"\n    ))\n    length(regP) == n || throw(DimensionMismatch(\n        \"length(regP)=$(length(regP)) but KKT solver has n=$n\"\n    ))\n    length(regD) == m || throw(DimensionMismatch(\n        \"length(regD)=$(length(regD)) but KKT solver has m=$m\"\n    ))\n\n    copyto!(kkt.θ, θ)\n    copyto!(kkt.regP, regP)\n    copyto!(kkt.regD, regD)\n\n    # Update KKT matrix\n    # K is stored as upper-triangular, and only its diagonal is changed\n    @inbounds for j in 1:kkt.n\n        k = kkt.K.colptr[1+j] - 1\n        kkt.K.nzval[k] = -kkt.θ[j] - regP[j]\n    end\n    @inbounds for i in 1:kkt.m\n        k = kkt.K.colptr[1+kkt.n+i] - 1\n        kkt.K.nzval[k] = regD[i]\n    end\n\n    # Update factorization\n    try\n        LDLF.ldl_factorize!(Symmetric(kkt.K), kkt.F)\n    catch err\n        isa(err, LDLF.SQDException) && throw(PosDefException(-1))\n        rethrow(err)\n    end\n\n    return nothing\nend\n\nfunction solve!(dx, dy, kkt::LDLFactSolver{T,K2}, ξp, ξd) where{T}\n    m, n = kkt.m, kkt.n\n\n    # Setup right-hand side\n    @views copyto!(kkt.ξ[1:n], ξd)\n    @views copyto!(kkt.ξ[(n+1):end], ξp)\n\n    # Solve augmented system\n    # CHOLMOD doesn't have in-place solve, so this line will allocate\n    LDLF.ldiv!(kkt.F, kkt.ξ)\n\n    # Recover dx, dy\n    @views copyto!(dx, kkt.ξ[1:n])\n    @views copyto!(dy, kkt.ξ[(n+1):end])\n\n    # TODO: iterative refinement\n    return nothing\nend\n\nend  # module\n"
  },
  {
    "path": "src/KKT/Test/test.jl",
    "content": "using Test\nusing LinearAlgebra\n\n\"\"\"\n    run_ls_tests(A, kkt; atol)\n\n\n\"\"\"\nfunction run_ls_tests(\n    A::AbstractMatrix{T},\n    kkt::AbstractKKTSolver{T};\n    atol::T=sqrt(eps(T))\n) where{T}\n\n    # Check that required methods are implemented\n    @testset \"Required methods\" begin\n        Tls = typeof(kkt)\n        V = Vector{T}\n        @test hasmethod(update!, Tuple{Tls, V, V, V})\n        @test hasmethod(solve!, Tuple{V, V, Tls, V, V})\n    end\n\n    m, n = size(A)\n\n    # Factorization/pre-conditionner update\n    θ = ones(T, n)\n    regP = ones(T, n)\n    regD = ones(T, m)\n    update!(kkt, θ, regP, regD)\n\n    # Solve linear system\n    ξp = ones(T, m)\n    ξd = ones(T, n)\n    dx = zeros(T, n)\n    dy = zeros(T, m)\n    solve!(dx, dy, kkt, ξp, ξd)\n\n    # Check residuals\n    rp = A * dx + regD .* dy - ξp\n    rd = -dx .*(θ + regP) + A' * dy - ξd\n    @testset \"Residuals\" begin\n        @test norm(rp, Inf) <= atol\n        @test norm(rd, Inf) <= atol\n    end\n\n    return nothing\nend"
  },
  {
    "path": "src/KKT/systems.jl",
    "content": "\"\"\"\n    DefaultKKTSystem\n\nDefault KKT system setting. Currently equivalent to [`K2`](@ref)\n\"\"\"\nstruct DefaultKKTSystem <: AbstractKKTSystem end\n\n@doc raw\"\"\"\n    K2 <: AbstractKKTSystem\n\nAugmented system\n```math\n    \\begin{bmatrix}\n        -(\\Theta^{-1} + R_{p}) & A^{\\top}\\\\\n        A & R_{d}\n    \\end{bmatrix}\n    \\begin{bmatrix}\n        \\Delta x\\\\\n        \\Delta y\n    \\end{bmatrix}\n    =\n    \\begin{bmatrix}\n        \\xi_d\\\\\n        \\xi_p\n    \\end{bmatrix}\n```\nwhere\n* ``\\Theta^{-1} = X^{-l}Z^{l} + X^{-u} Z^{u}``\n* ``R_{p}, R_{d}`` are current primal and dual regularizations\n* ``\\xi_{d}, \\xi_{p}`` are given right-hand sides\n\"\"\"\nstruct K2 <: AbstractKKTSystem end\n\n@doc raw\"\"\"\n    K1 <: AbstractKKTSystem\n\nNormal equations system\n```math\n    \\begin{array}{rl}\n    \\left(\n        A (\\Theta^{-1} + R_{p})^{-1} A^{\\top} + R_{d}\n    \\right)\n    \\Delta y\n    & =\n    \\xi_{p} + A (Θ^{-1} + R_{p})^{-1} \\xi_{d}\\\\\n    \\Delta x &= (Θ^{-1} + R_{p})^{-1} (A^{\\top} \\Delta y - \\xi_{d})\n    \\end{array}\n```\nwhere\n* ``\\Theta^{-1} = X^{-l}Z^{l} + X^{-u} Z^{u}``\n* ``R_{p}, R_{d}`` are current primal and dual regularizations\n* ``\\xi_{d}, \\xi_{p}`` are the augmented system's right-hand side\n\"\"\"\nstruct K1 <: AbstractKKTSystem end\n"
  },
  {
    "path": "src/LinearAlgebra/LinearAlgebra.jl",
    "content": "module TLPLinearAlgebra\n\nusing LinearAlgebra\nusing SparseArrays\nexport construct_matrix\n\nimport ..Tulip.Factory\n\n\"\"\"\n    construct_matrix(Ta, m, n, aI, aJ, aV)\n\nConstruct matrix given matrix type `Ta`, size `m, n`, and data in COO format.\n\"\"\"\nfunction construct_matrix end\n\nfunction construct_matrix(\n    ::Type{Matrix}, m::Int, n::Int,\n    aI::Vector{Int}, aJ::Vector{Int}, aV::Vector{T}\n) where{T}\n    A = zeros(T, m, n)\n    # TODO: may be more efficient to first sort indices so that\n    # A is accessed in column-major order.\n    for(i, j, v) in zip(aI, aJ, aV)\n        A[i, j] = v\n    end\n    return A\nend\n\nconstruct_matrix(\n    ::Type{SparseMatrixCSC}, m::Int, n::Int,\n    aI::Vector{Int}, aJ::Vector{Int}, aV::Vector{T}\n) where{T} = sparse(aI, aJ, aV, m, n)\n\nend  # module"
  },
  {
    "path": "src/Presolve/Presolve.jl",
    "content": "Base.@kwdef mutable struct PresolveOptions{T}\n    Level::Int = 1  # Presolve level\n\n    TolerancePFeas::T = sqrt(eps(T))  # Primal feasibility tolerance\n    ToleranceDFeas::T = sqrt(eps(T))  # Dual   feasibility tolerance\nend\n\n\"\"\"\n    PresolveTransformation{T}\n\nAbstract type for pre-solve transformations.\n\"\"\"\nabstract type PresolveTransformation{T} end\n\n\"\"\"\n    PresolveData{T}\n\nStores information about an LP in the form\n```\n    min     c'x + c0\n    s.t.    lr ⩽ Ax ⩽ ur\n            lc ⩽  x ⩽ uc\n```\nwhose dual writes\n```\n    max     lr'y⁺ - ur'y⁻ + lc's⁺ - uc's⁻\n    s.t.     A'y⁺ -  A'y⁻ +    s⁺ -    s⁻ = c\n               y⁺,     y⁻,     s⁺,     s⁻ ⩾ 0\n```\n\"\"\"\nmutable struct PresolveData{T}\n    updated::Bool\n    status::TerminationStatus\n    options::PresolveOptions{T}\n\n    # Original problem\n    pb0::ProblemData{T}\n    # Reduced problem\n    # Nothing until the reduced problem is extracted\n    pb_red::Union{Nothing, ProblemData{T}}\n    solution::Solution{T}  # only used if presolve solves the problem\n\n    # Presolved data\n\n    # Active rows and columns\n    rowflag::Vector{Bool}\n    colflag::Vector{Bool}\n\n    # Non-zeros in rows and columns\n    nzrow::Vector{Int}\n    nzcol::Vector{Int}\n\n    # Objective\n    objsense::Bool\n    obj::Vector{T}\n    obj0::T\n\n    # Current number of constraints/variables in presolved problem\n    nrow::Int\n    ncol::Int\n\n    # Primal bounds\n    lrow::Vector{T}\n    urow::Vector{T}\n    lcol::Vector{T}\n    ucol::Vector{T}\n\n    # Dual bounds\n    ly::Vector{T}\n    uy::Vector{T}\n    ls::Vector{T}\n    us::Vector{T}\n\n    # Scaling\n    row_scaling::Vector{T}\n    col_scaling::Vector{T}\n    # TODO: objective and RHS scaling\n\n    # Old <-> new index mapping\n    # Instantiated only after pre-solve is performed\n    new_con_idx::Vector{Int}\n    new_var_idx::Vector{Int}\n    old_con_idx::Vector{Int}\n    old_var_idx::Vector{Int}\n\n    # Singletons\n    row_singletons::Vector{Int}  # Row singletons\n    free_col_singletons::Vector{Int}  # (implied) free column singletons\n\n    # TODO: set of transformations for pre-post crush\n    ops::Vector{PresolveTransformation{T}}\n\n    function PresolveData(\n        pb::ProblemData{T},\n        options::PresolveOptions{T}=PresolveOptions{T}()\n    ) where{T}\n        ps = new{T}()\n\n        ps.updated = false\n        ps.status = Trm_Unknown\n        ps.options = options\n\n        ps.pb0 = pb\n        ps.pb_red = nothing\n        ps.solution = Solution{T}(pb.ncon, pb.nvar)\n\n        ps.nrow = pb.ncon\n        ps.ncol = pb.nvar\n\n        # All rows and columns are active\n        ps.rowflag = trues(ps.nrow)\n        ps.colflag = trues(ps.ncol)\n\n        # Number of non-zeros in rows/columns\n        ps.nzrow = zeros(Int, ps.nrow)\n        ps.nzcol = zeros(Int, ps.ncol)\n        for (j, col) in enumerate(pb.acols)\n            for (i, aij) in zip(col.nzind, col.nzval)\n                ps.nzcol[j] += !iszero(aij)\n                ps.nzrow[i] += !iszero(aij)\n            end\n        end\n\n        # Objective\n        ps.objsense = pb.objsense\n        if pb.objsense\n            ps.obj  = copy(pb.obj)\n            ps.obj0 = pb.obj0\n        else\n            # Maximization problem: negate the objective for pre-solve\n            # This will be undone when extracting the reduced problem\n            ps.obj  = -copy(pb.obj)\n            ps.obj0 = -pb.obj0\n        end\n\n        # Copy primal bounds\n        ps.lrow = copy(pb.lcon)\n        ps.urow = copy(pb.ucon)\n        ps.lcol = copy(pb.lvar)\n        ps.ucol = copy(pb.uvar)\n\n        # Set dual bounds\n        ps.ly = Vector{T}(undef, ps.nrow)\n        ps.uy = Vector{T}(undef, ps.nrow)\n        ps.ls = Vector{T}(undef, ps.ncol)\n        ps.us = Vector{T}(undef, ps.ncol)\n        for (i, (lc, uc)) in enumerate(zip(ps.lrow, ps.urow))\n            ps.ly[i] = (uc == T( Inf)) ? zero(T) : T(-Inf)\n            ps.uy[i] = (lc == T(-Inf)) ? zero(T) : T( Inf)\n        end\n        for (j, (lv, uv)) in enumerate(zip(ps.lcol, ps.ucol))\n            ps.ls[j] = (uv == T( Inf)) ? zero(T) : T(-Inf)\n            ps.us[j] = (lv == T(-Inf)) ? zero(T) : T( Inf)\n        end\n\n        # Scalings\n        ps.row_scaling = ones(T, ps.nrow)\n        ps.col_scaling = ones(T, ps.ncol)\n\n        # Index mappings\n        ps.new_con_idx = Int[]\n        ps.new_var_idx = Int[]\n        ps.old_con_idx = Int[]\n        ps.old_var_idx = Int[]\n\n        # Singletons\n        ps.row_singletons = Int[]\n        ps.free_col_singletons = Int[]\n\n        ps.ops = PresolveTransformation{T}[]\n\n        return ps\n    end\nend\n\n# Extract pre-solved problem data, to be passed to the IPM solver\nfunction extract_reduced_problem!(ps::PresolveData{T}) where{T}\n\n    pb = ProblemData{T}()\n\n    pb.ncon = sum(ps.rowflag)\n    pb.nvar = sum(ps.colflag)\n\n    pb.objsense = ps.objsense\n    if pb.objsense\n        pb.obj0 = ps.obj0\n        pb.obj = ps.obj[ps.colflag]\n    else\n        pb.obj0 = -ps.obj0\n        pb.obj = -ps.obj[ps.colflag]\n    end\n\n    # Primal bounds\n    pb.lvar = ps.lcol[ps.colflag]\n    pb.uvar = ps.ucol[ps.colflag]\n    pb.lcon = ps.lrow[ps.rowflag]\n    pb.ucon = ps.urow[ps.rowflag]\n\n    # Extract new rows\n    pb.arows = Vector{Row{T}}(undef, pb.ncon)\n    inew = 0\n    for (iold, row) in enumerate(ps.pb0.arows)\n        ps.rowflag[iold] || continue\n\n        inew += 1\n        # Compute new row\n        rind = Vector{Int}(undef, ps.nzrow[iold])\n        rval = Vector{T}(undef, ps.nzrow[iold])\n\n        k = 0\n        for (jold, aij) in zip(row.nzind, row.nzval)\n            ps.colflag[jold] || continue\n            iszero(aij) && continue\n\n            # Set new coefficient\n            k += 1\n            rind[k] = ps.new_var_idx[jold]\n            rval[k] = aij\n        end\n\n        # Set new row\n        pb.arows[inew] = Row{T}(rind, rval)\n    end\n\n    # Extract new columns\n    pb.acols = Vector{Col{T}}(undef, pb.nvar)\n    jnew = 0\n    for (jold, col) in enumerate(ps.pb0.acols)\n        ps.colflag[jold] || continue\n\n        jnew += 1\n        # Compute new row\n        cind = Vector{Int}(undef, ps.nzcol[jold])\n        cval = Vector{T}(undef, ps.nzcol[jold])\n\n        k = 0\n        for (iold, aij) in zip(col.nzind, col.nzval)\n            ps.rowflag[iold] || continue\n            iszero(aij) && continue\n\n            # Set new coefficient\n            k += 1\n            cind[k] = ps.new_con_idx[iold]\n            cval[k] = aij\n        end\n\n        # Set new column\n        pb.acols[jnew] = Col{T}(cind, cval)\n    end\n\n    # Variable and constraint names\n    # TODO: we don't need these\n    pb.var_names = ps.pb0.var_names[ps.colflag]\n    pb.con_names = ps.pb0.con_names[ps.rowflag]\n\n    # Scaling\n    rscale = zeros(T, ps.nrow)\n    cscale = zeros(T, ps.ncol)\n\n    # Compute norm of each row and column\n    # TODO: use a parameter p and do norm(.., p)\n    p = 2\n    for (i, row) in enumerate(pb.arows)\n        r = norm(row.nzval, p)\n        rscale[i] = r > zero(T) ? r : one(T)\n    end\n    for (j, col) in enumerate(pb.acols)\n        r = norm(col.nzval, p)\n        cscale[j] = r > zero(T) ? r : one(T)\n    end\n\n    map!(sqrt, cscale, cscale)\n    map!(sqrt, rscale, rscale)\n\n    # Rows\n    for (i, row) in enumerate(pb.arows)\n        # Scale row coefficients\n        for (k, j) in enumerate(row.nzind)\n            row.nzval[k] /= (rscale[i] * cscale[j])\n        end\n        # Scale row bounds\n        pb.lcon[i] /= rscale[i]\n        pb.ucon[i] /= rscale[i]\n    end\n    # Columns\n    for (j, col) in enumerate(pb.acols)\n        # Scale column coefficients\n        for (k, i) in enumerate(col.nzind)\n            col.nzval[k] /= (rscale[i] * cscale[j])\n        end\n        # Scale objective and variable bounds\n        pb.obj[j]  /= cscale[j]\n        pb.lvar[j] *= cscale[j]\n        pb.uvar[j] *= cscale[j]\n    end\n\n    # Record scaling\n    @debug \"Scaling info\" extrema(rscale) extrema(cscale)\n    ps.row_scaling = rscale\n    ps.col_scaling = cscale\n\n    # Done\n    ps.pb_red = pb\n    return nothing\nend\n\ninclude(\"empty_row.jl\")\ninclude(\"empty_column.jl\")\ninclude(\"fixed_variable.jl\")\ninclude(\"row_singleton.jl\")\ninclude(\"forcing_row.jl\")\ninclude(\"free_column_singleton.jl\")\ninclude(\"dominated_column.jl\")\n\n\n\"\"\"\n    postsolve!\n\nPerform post-solve.\n\"\"\"\nfunction postsolve!(sol::Solution{T}, sol_::Solution{T}, ps::PresolveData{T}) where{T}\n\n    # Check dimensions\n    (sol_.m, sol_.n) == (ps.nrow, ps.ncol) || error(\n        \"Inner solution has size $((sol_.m, sol_.n)) but presolved problem has size $((ps.nrow, ps.ncol))\"\n    )\n    (sol.m, sol.n) == (ps.pb0.ncon, ps.pb0.nvar) || error(\n        \"Solution has size $((sol.m, sol.n)) but original problem has size $((ps.pb0.ncon, ps.pb0.nvar))\"\n    )\n\n    # Copy solution status and objective values\n    sol.primal_status = sol_.primal_status\n    sol.dual_status = sol_.dual_status\n    sol.is_primal_ray = sol_.is_primal_ray\n    sol.is_dual_ray = sol_.is_dual_ray\n    sol.z_primal = sol_.z_primal\n    sol.z_dual = sol_.z_dual\n\n    # Extract and un-scale inner solution components\n    # TODO: create a PresolveTransformation for scaling\n    for (j_, j) in enumerate(ps.old_var_idx)\n        sol.x[j] = sol_.x[j_] / ps.col_scaling[j_]\n        sol.s_lower[j] = sol_.s_lower[j_] * ps.col_scaling[j_]\n        sol.s_upper[j] = sol_.s_upper[j_] * ps.col_scaling[j_]\n    end\n    for (i_, i) in enumerate(ps.old_con_idx)\n        sol.y_lower[i] = sol_.y_lower[i_] / ps.row_scaling[i_]\n        sol.y_upper[i] = sol_.y_upper[i_] / ps.row_scaling[i_]\n    end\n\n    # Reverse transformations\n    for op in Iterators.reverse(ps.ops)\n        postsolve!(sol, op)\n    end\n\n    # Compute row primals\n    for (i, row) in enumerate(ps.pb0.arows)\n        sol.Ax[i] = zero(T)\n        for (j, aij) in zip(row.nzind, row.nzval)\n            sol.Ax[i] += aij * sol.x[j]\n        end\n    end\n\n    # Done\n    return nothing\nend\n\n\n\"\"\"\n    presolve(pb::ProblemData)\n\nPerform pre-solve.\n\"\"\"\nfunction presolve!(ps::PresolveData{T}) where{T}\n\n    # Check bound consistency on all rows/columns\n    st = bounds_consistency_checks!(ps)\n    ps.status == Trm_PrimalInfeasible && return ps.status\n\n    # I. Remove all fixed variables, empty rows and columns\n    # remove_fixed_variables!(ps)\n    remove_empty_rows!(ps)\n    remove_empty_columns!(ps)\n\n    # TODO: check status for potential early return\n    ps.status == Trm_Unknown || return ps.status\n\n    # Identify row singletons\n    ps.row_singletons = [i for (i, nz) in enumerate(ps.nzrow) if ps.rowflag[i] && nz == 1]\n\n    # II. Passes\n    ps.updated = true\n    npasses = 0  # TODO: maximum number of passes\n    while ps.updated && ps.status == Trm_Unknown\n        npasses += 1\n        ps.updated = false\n        @debug \"Presolve pass $npasses\" ps.nrow ps.ncol\n\n        bounds_consistency_checks!(ps)\n        ps.status == Trm_Unknown || return ps.status\n        remove_empty_columns!(ps)\n        ps.status == Trm_Unknown || return ps.status\n\n\n        # Remove all fixed variables\n        # TODO: remove empty variables as well\n        remove_row_singletons!(ps)\n        ps.status == Trm_Unknown || return ps.status\n        remove_fixed_variables!(ps)\n        ps.status == Trm_Unknown || return ps.status\n\n        # Remove forcing & dominated constraints\n        remove_row_singletons!(ps)\n        ps.status == Trm_Unknown || return ps.status\n        remove_forcing_rows!(ps)\n        ps.status == Trm_Unknown || return ps.status\n\n        # Remove free and implied free column singletons\n        remove_row_singletons!(ps)\n        ps.status == Trm_Unknown || return ps.status\n        remove_free_column_singletons!(ps)\n        ps.status == Trm_Unknown || return ps.status\n\n        # TODO: remove column singleton with doubleton equation\n\n        # Dual reductions\n        remove_row_singletons!(ps)\n        ps.status == Trm_Unknown || return ps.status\n        remove_dominated_columns!(ps)\n        ps.status == Trm_Unknown || return ps.status\n    end\n\n    remove_empty_columns!(ps)\n\n    @debug(\"Presolved problem info\",\n        ps.pb0.ncon, ps.nrow,\n        ps.pb0.nvar, ps.ncol,\n        sum(ps.nzcol[ps.colflag]), sum(ps.nzrow[ps.rowflag])\n    )\n\n    # TODO: check problem dimensions and declare optimality if problem is empty\n    if ps.nrow == 0 && ps.ncol == 0\n        # Problem is empty: declare optimality now\n        ps.status = Trm_Optimal\n\n        # Resize solution\n        resize!(ps.solution, 0, 0)\n        ps.solution.primal_status = Sln_Optimal\n        ps.solution.dual_status = Sln_Optimal\n        ps.solution.is_primal_ray = false\n        ps.solution.is_dual_ray = false\n        ps.solution.z_primal = ps.obj0\n        ps.solution.z_dual = ps.obj0\n    end\n\n    # Old <-> new index mapping\n    compute_index_mapping!(ps)\n\n    # TODO: extract reduced problem (?)\n\n    # Done.\n    return ps.status\nend\n\nfunction compute_index_mapping!(ps::PresolveData)\n    ps.new_con_idx = Vector{Int}(undef, ps.pb0.ncon)\n    ps.new_var_idx = Vector{Int}(undef, ps.pb0.nvar)\n    ps.old_con_idx = Vector{Int}(undef, ps.nrow)\n    ps.old_var_idx = Vector{Int}(undef, ps.ncol)\n\n    inew = 0\n    for iold in 1:ps.pb0.ncon\n        if ps.rowflag[iold]\n            inew += 1\n            ps.new_con_idx[iold] = inew\n            ps.old_con_idx[inew] = iold\n        else\n            ps.new_con_idx[iold] = 0\n        end\n    end\n    jnew = 0\n    for jold in 1:ps.pb0.nvar\n        if ps.colflag[jold]\n            jnew += 1\n            ps.new_var_idx[jold] = jnew\n            ps.old_var_idx[jnew] = jold\n        else\n            ps.new_var_idx[jold] = 0\n        end\n    end\n\n    return nothing\nend\n\n\"\"\"\n    bounds_consistency_checks(ps)\n\nCheck that all primal & dual bounds are consistent.\n\nTODO: If not, declare primal/dual infeasibility and extract ray.\n\"\"\"\nfunction bounds_consistency_checks!(ps::PresolveData{T}) where{T}\n    # Check primal bounds\n    for (i, (l, u)) in enumerate(zip(ps.lrow, ps.urow))\n        if ps.rowflag[i] && l > u\n            # Problem is primal infeasible\n            @debug \"Row $i is primal infeasible\"\n            ps.status = Trm_PrimalInfeasible\n            ps.updated = true\n\n            # Resize problem\n            compute_index_mapping!(ps)\n            resize!(ps.solution, ps.nrow, ps.ncol)\n            ps.solution.x .= zero(T)\n            ps.solution.y_lower .= zero(T)\n            ps.solution.y_upper .= zero(T)\n            ps.solution.s_lower .= zero(T)\n            ps.solution.s_upper .= zero(T)\n\n            # Farkas ray: y⁺_i = y⁻_i = 1 (any > 0 value works)\n            ps.solution.primal_status = Sln_Unknown\n            ps.solution.dual_status = Sln_InfeasibilityCertificate\n            ps.solution.is_primal_ray = false\n            ps.solution.is_dual_ray = true\n            ps.solution.z_primal = ps.solution.z_dual = T(Inf)\n            i_ = ps.new_con_idx[i]\n            ps.solution.y_lower[i_] = one(T)\n            ps.solution.y_upper[i_] = one(T)\n\n            return\n        end\n    end\n    for (j, (l, u)) in enumerate(zip(ps.lcol, ps.ucol))\n        if ps.colflag[j] && l > u\n            # Primal is primal infeasible\n            @debug \"Column $j is primal infeasible\"\n            ps.status = Trm_PrimalInfeasible\n            ps.updated = true\n\n            # Resize problem\n            compute_index_mapping!(ps)\n            resize!(ps.solution, ps.nrow, ps.ncol)\n            ps.solution.x .= zero(T)\n            ps.solution.y_lower .= zero(T)\n            ps.solution.y_upper .= zero(T)\n            ps.solution.s_lower .= zero(T)\n            ps.solution.s_upper .= zero(T)\n\n            # Farkas ray: y⁺_i = y⁻_i = 1 (any > 0 value works)\n            ps.solution.primal_status = Sln_Unknown\n            ps.solution.dual_status = Sln_InfeasibilityCertificate\n            ps.solution.is_primal_ray = false\n            ps.solution.is_dual_ray = true\n            ps.solution.z_primal = ps.solution.z_dual = T(Inf)\n            j_ = ps.new_var_idx[j]\n            ps.solution.s_lower[j_] = one(T)\n            ps.solution.s_upper[j_] = one(T)\n\n            return\n        end\n    end\n\n    # TODO: Check dual bounds\n\n    return nothing\nend\n\n\"\"\"\n    remove_empty_rows!(ps::PresolveData)\n\nRemove all empty rows.\n\nCalled once at the beginning of the presolve procedure.\nIf an empty row is created later, it is removed on the spot.\n\"\"\"\nfunction remove_empty_rows!(ps::PresolveData{T}) where{T}\n    nempty = 0\n    for i in 1:ps.pb0.ncon\n        (ps.rowflag[i] && (ps.nzrow[i] == 0)) || continue\n        @debug \"Remove empty row $i\"\n\n        remove_empty_row!(ps, i)\n    end\n    return nothing\nend\n\n\"\"\"\n    remove_empty_columns!(ps::PresolveData)\n\nRemove all empty columns.\n\nCalled once at the beginning of the presolve procedure.\nIf an empty column is created later, it is removed on the spot.\n\"\"\"\nfunction remove_empty_columns!(ps::PresolveData{T}) where{T}\n    for j in 1:ps.pb0.nvar\n        remove_empty_column!(ps, j)\n        ps.status == Trm_Unknown || break\n    end\n    return nothing\nend\n\n\"\"\"\n    remove_fixed_variables!(ps::PresolveData)\n\nRemove all fixed variables.\n\"\"\"\nfunction remove_fixed_variables!(ps::PresolveData{T}) where{T}\n    for (j, flag) in enumerate(ps.colflag)\n        flag || continue\n        remove_fixed_variable!(ps, j)\n    end\n    return nothing\nend\n\nfunction remove_row_singletons!(ps::PresolveData{T}) where{T}\n    nsingletons = 0\n    for i in ps.row_singletons\n        remove_row_singleton!(ps, i)\n    end\n    ps.row_singletons = Int[]\n    return nothing\nend\n\n\"\"\"\n    remove_forcing_rows!\n\nRemove forcing and dominated row\n\"\"\"\nfunction remove_forcing_rows!(ps::PresolveData)\n    for (i, flag) in enumerate(ps.rowflag)\n        flag && remove_forcing_row!(ps, i)\n    end\n    return nothing\nend\n\n\"\"\"\n    remove_free_column_singletons!(ps)\n\n\"\"\"\nfunction remove_free_column_singletons!(ps::PresolveData)\n    for (j, flag) in enumerate(ps.colflag)\n        remove_free_column_singleton!(ps, j)\n    end\n    return nothing\nend\n\nfunction remove_dominated_columns!(ps::PresolveData{T}) where{T}\n    # Strengthen dual bounds with column singletons\n    for (j, (l, u)) in enumerate(zip(ps.lcol, ps.ucol))\n        (ps.colflag[j] && ps.nzcol[j] == 1) || continue\n\n        col = ps.pb0.acols[j]\n        # Find non-zero index\n        nz = 0\n        i, aij = 0, zero(T)\n        for (i_, a_) in zip(col.nzind, col.nzval)\n            if ps.rowflag[i_] && !iszero(a_)\n                nz += 1; nz <= 1 || break\n                i = i_\n                aij = a_\n            end\n        end\n\n        nz == 1 || (@error \"Expected singleton but column $j has $nz non-zeros\"; continue)\n        iszero(aij) && continue  # empty column\n\n        # Strengthen dual bounds\n        #=\n\n        =#\n        cj = ps.obj[j]\n        y_ = cj / aij\n        if !isfinite(l) && !isfinite(u)\n            # Free variable. Should not happen but handle anyway\n            # TODO\n        elseif isfinite(l) && !isfinite(u)\n            # Lower-bounded variable: `aij * yi ≤ cj`\n            if aij > zero(T)\n                # yi ≤ cj / aij\n                @debug \"Col $j forces y$i <= $y_\"\n                ps.uy[i] = min(ps.uy[i],  y_)\n            else\n                # yi ≥ cj / aij\n                @debug \"Col $j forces y$i >= $y_\"\n                ps.ly[i] = max(ps.ly[i],  y_)\n            end\n\n        elseif !isfinite(l) && isfinite(u)\n            # Upper-bounded variable: `aij * yi ≥ cj`\n            if aij > zero(T)\n                # yi ≥ cj / aij\n                @debug \"Col $j forces y$i >= $y_\"\n                ps.ly[i] = max(ps.ly[i],  y_)\n            else\n                # yi ≤ cj / aij\n                @debug \"Col $j forces y$i <= $y_\"\n                ps.uy[i] = min(ps.uy[i],  y_)\n            end\n        end\n\n        # TODO: dual feasibility check (?)\n    end\n\n    for (j, flag) in enumerate(ps.colflag)\n        remove_dominated_column!(ps, j)\n        ps.status == Trm_Unknown || break\n    end\n    return nothing\nend\n"
  },
  {
    "path": "src/Presolve/dominated_column.jl",
    "content": "struct DominatedColumn{T} <: PresolveTransformation{T}\n    j::Int\n    x::T  # Primal value\n    cj::T  # Objective\n    col::Col{T}  # Column\nend\n\nfunction remove_dominated_column!(ps::PresolveData{T}, j::Int; tol::T=100*sqrt(eps(T))) where{T}\n    ps.colflag[j] || return nothing\n\n    # Compute implied bounds on reduced cost: `ls ≤ s ≤ us`\n    ls = us = zero(T)\n    col = ps.pb0.acols[j]\n    for (i, aij) in zip(col.nzind, col.nzval)\n        (ps.rowflag[i] && !iszero(aij)) || continue\n\n        ls += aij * ( (aij >= zero(T)) ? ps.ly[i] : ps.uy[i] )\n        us += aij * ( (aij >= zero(T)) ? ps.uy[i] : ps.ly[i] )\n    end\n\n    # Check if column is dominated\n    cj = ps.obj[j]\n    if cj - us > tol\n        # Reduced cost is always positive => fix to lower bound (or problem is unbounded)\n        lb = ps.lcol[j]\n        @debug \"Fixing dominated column $j to its lower bound $lb\"\n\n        if !isfinite(lb)\n            # Problem is dual infeasible\n            @debug \"Column $j is (lower) unbounded\"\n            ps.status = Trm_DualInfeasible\n            ps.updated = true\n\n            # Resize problem\n            compute_index_mapping!(ps)\n            resize!(ps.solution, ps.nrow, ps.ncol)\n            ps.solution.x .= zero(T)\n            ps.solution.y_lower .= zero(T)\n            ps.solution.y_upper .= zero(T)\n            ps.solution.s_lower .= zero(T)\n            ps.solution.s_upper .= zero(T)\n\n            # Unbounded ray: xj = -1\n            ps.solution.primal_status = Sln_InfeasibilityCertificate\n            ps.solution.dual_status = Sln_Unknown\n            ps.solution.is_primal_ray = true\n            ps.solution.is_dual_ray = false\n            ps.solution.z_primal = ps.solution.z_dual = -T(Inf)\n            j_ = ps.new_var_idx[j]\n            ps.solution.x[j_] = -one(T)\n\n            return nothing\n        end\n\n        # Update objective\n        ps.obj0 += cj * lb\n\n        # Extract column and update rows\n        col_ = Col{T}(Int[], T[])\n        for (i, aij) in zip(col.nzind, col.nzval)\n            ps.rowflag[i] || continue\n\n            push!(col_.nzind, i)\n            push!(col_.nzval, aij)\n\n            # Update bounds and non-zeros\n            ps.lrow[i] -= aij * lb\n            ps.urow[i] -= aij * lb\n            ps.nzrow[i] -= 1\n\n            ps.nzrow[i] == 1 && push!(ps.row_singletons, i)\n        end\n\n        # Remove variable from problem\n        push!(ps.ops, DominatedColumn(j, lb, cj, col_))\n        ps.colflag[j] = false\n        ps.ncol -= 1\n        ps.updated = true\n\n    elseif cj - ls < -tol\n        # Reduced cost is always negative => fix to upper bound (or problem is unbounded)\n        ub = ps.ucol[j]\n        \n        if !isfinite(ub)\n            # Problem is unbounded\n            @debug \"Column $j is (upper) unbounded\"\n\n            ps.status = Trm_DualInfeasible\n            ps.updated = true\n\n            # Resize solution\n            compute_index_mapping!(ps)\n            resize!(ps.solution, ps.nrow, ps.ncol)\n            ps.solution.x .= zero(T)\n            ps.solution.y_lower .= zero(T)\n            ps.solution.y_upper .= zero(T)\n            ps.solution.s_lower .= zero(T)\n            ps.solution.s_upper .= zero(T)\n\n            # Unbounded ray: xj = -1\n            ps.solution.primal_status = Sln_InfeasibilityCertificate\n            ps.solution.dual_status = Sln_Unknown\n            ps.solution.is_primal_ray = true\n            ps.solution.is_dual_ray = false\n            ps.solution.z_primal = ps.solution.z_dual = -T(Inf)\n            j_ = ps.new_var_idx[j]\n            ps.solution.x[j_] = one(T)\n\n            return nothing\n        end\n\n        @debug \"Fixing dominated column $j to its upper bound $ub\"\n\n        # Update objective\n        ps.obj0 += cj * ub\n\n        # Extract column and update rows\n        col_ = Col{T}(Int[], T[])\n        for (i, aij) in zip(col.nzind, col.nzval)\n            ps.rowflag[i] || continue\n\n            push!(col_.nzind, i)\n            push!(col_.nzval, aij)\n\n            # Update bounds and non-zeros\n            ps.lrow[i] -= aij * ub\n            ps.urow[i] -= aij * ub\n            ps.nzrow[i] -= 1\n\n            ps.nzrow[i] == 1 && push!(ps.row_singletons, i)\n        end\n\n        # Remove variable from problem\n        push!(ps.ops, DominatedColumn(j, ub, cj, col_))\n        ps.colflag[j] = false\n        ps.ncol -= 1\n        ps.updated = true\n    end\n\n    return nothing\nend\n\nfunction postsolve!(sol::Solution{T}, op::DominatedColumn{T}) where{T}\n    # Primal value\n    sol.x[op.j] = op.x\n\n    # Reduced cost\n    s = sol.is_dual_ray ? zero(T) : op.cj\n    for (i, aij) in zip(op.col.nzind, op.col.nzval)\n        s -= aij * (sol.y_lower[i] - sol.y_upper[i])\n    end\n\n    sol.s_lower[op.j] = pos_part(s)\n    sol.s_upper[op.j] = neg_part(s)\n\n    return nothing\nend"
  },
  {
    "path": "src/Presolve/empty_column.jl",
    "content": "struct EmptyColumn{T} <: PresolveTransformation{T}\n    j::Int  # variable index\n    x::T  # Variable value\n    s::T  # Reduced cost\nend\n\nfunction remove_empty_column!(ps::PresolveData{T}, j::Int) where{T}\n    # Sanity check\n    (ps.colflag[j] && (ps.nzcol[j] == 0)) || return nothing\n\n    # Remove column\n    lb, ub = ps.lcol[j], ps.ucol[j]\n    cj = ps.obj[j]\n    @debug \"Removing empty column $j\" cj lb ub\n\n    ϵ = ps.options.ToleranceDFeas\n\n    if cj > ϵ\n        if isfinite(lb)\n            # Set variable to lower bound\n            # Update objective constant\n            ps.obj0 += lb * cj\n            push!(ps.ops, EmptyColumn(j, lb, cj))\n        else\n            # Problem is dual infeasible\n            @debug \"Column $j is (lower) unbounded\"\n            ps.status = Trm_DualInfeasible\n            ps.updated = true\n\n            # Resize problem\n            compute_index_mapping!(ps)\n            resize!(ps.solution, ps.nrow, ps.ncol)\n            ps.solution.x .= zero(T)\n            ps.solution.y_lower .= zero(T)\n            ps.solution.y_upper .= zero(T)\n            ps.solution.s_lower .= zero(T)\n            ps.solution.s_upper .= zero(T)\n\n            # Unbounded ray: xj = -1\n            ps.solution.primal_status = Sln_InfeasibilityCertificate\n            ps.solution.dual_status = Sln_Unknown\n            ps.solution.is_primal_ray = true\n            ps.solution.is_dual_ray = false\n            ps.solution.z_primal = ps.solution.z_dual = -T(Inf)\n            j_ = ps.new_var_idx[j]\n            ps.solution.x[j_] = -one(T)\n\n            return nothing\n        end\n    elseif cj < -ϵ\n        if isfinite(ub)\n            # Set variable to upper bound\n            # Update objective constant\n            ps.obj0 += ub * cj\n            push!(ps.ops, EmptyColumn(j, ub, cj))\n        else\n            # Problem is dual infeasible\n            @debug \"Column $j is (upper) unbounded\"\n            ps.status = Trm_DualInfeasible\n            ps.updated = true\n\n            # Resize problem\n            compute_index_mapping!(ps)\n            resize!(ps.solution, ps.nrow, ps.ncol)\n            ps.solution.x .= zero(T)\n            ps.solution.y_lower .= zero(T)\n            ps.solution.y_upper .= zero(T)\n            ps.solution.s_lower .= zero(T)\n            ps.solution.s_upper .= zero(T)\n\n            # Unbounded ray: xj = 1\n            ps.solution.primal_status = Sln_InfeasibilityCertificate\n            ps.solution.dual_status = Sln_Unknown\n            ps.solution.is_primal_ray = true\n            ps.solution.is_dual_ray = false\n            ps.solution.z_primal = ps.solution.z_dual = -T(Inf)\n            j_ = ps.new_var_idx[j]\n            ps.solution.x[j_] = one(T)\n\n            return\n        end\n    else\n        # Any feasible value works\n        if isfinite(lb)\n            push!(ps.ops, EmptyColumn(j, lb, zero(T)))\n        elseif isfinite(ub)\n            push!(ps.ops, EmptyColumn(j, ub, zero(T)))\n        else\n            # Free variable with zero coefficient and empty column\n            push!(ps.ops, EmptyColumn(j, zero(T), zero(T)))\n        end\n    end\n\n    # Book=keeping\n    ps.colflag[j] = false\n    ps.updated = true\n    ps.ncol -= 1\n    return nothing\nend\n\nfunction postsolve!(sol::Solution{T}, op::EmptyColumn{T}) where{T}\n    sol.x[op.j] = op.x\n    sol.s_lower[op.j] = pos_part(op.s)\n    sol.s_upper[op.j] = neg_part(op.s)\n    return nothing\nend\n"
  },
  {
    "path": "src/Presolve/empty_row.jl",
    "content": "# TODO: this is redundant with forcing constraint checks\n#   => an empty row is automatically redundant or infeasible\n\nstruct EmptyRow{T} <: PresolveTransformation{T}\n    i::Int  # row index\n    y::T  # dual multiplier\nend\n\nfunction remove_empty_row!(ps::PresolveData{T}, i::Int) where{T}\n    # Sanity checks\n    (ps.rowflag[i] && ps.nzrow[i] == 0) || return nothing\n\n    # Check bounds\n    lb, ub = ps.lrow[i], ps.urow[i]\n    ϵ = ps.options.TolerancePFeas\n\n    if ub < -ϵ\n        # Infeasible\n        @debug \"Row $i is primal infeasible\"\n        ps.status = Trm_PrimalInfeasible\n        ps.updated = true\n\n        # Resize problem\n        compute_index_mapping!(ps)\n        resize!(ps.solution, ps.nrow, ps.ncol)\n        ps.solution.x .= zero(T)\n        ps.solution.y_lower .= zero(T)\n        ps.solution.y_upper .= zero(T)\n        ps.solution.s_lower .= zero(T)\n        ps.solution.s_upper .= zero(T)\n\n        # Farkas ray: y⁺_i = 1 (any > 0 value works)\n        ps.solution.primal_status = Sln_Unknown\n        ps.solution.dual_status = Sln_InfeasibilityCertificate\n        ps.solution.is_primal_ray = false\n        ps.solution.is_dual_ray = true\n        ps.solution.z_primal = ps.solution.z_dual = T(Inf)\n        i_ = ps.new_con_idx[i]\n        ps.solution.y_upper[i_] = one(T)\n        return\n    elseif lb > ϵ\n        @debug \"Row $i is primal infeasible\"\n        ps.status = Trm_PrimalInfeasible\n        ps.updated = true\n\n        # Resize problem\n        compute_index_mapping!(ps)\n        resize!(ps.solution, ps.nrow, ps.ncol)\n        ps.solution.x .= zero(T)\n        ps.solution.y_lower .= zero(T)\n        ps.solution.y_upper .= zero(T)\n        ps.solution.s_lower .= zero(T)\n        ps.solution.s_upper .= zero(T)\n\n        # Farkas ray: y⁺_i = 1 (any > 0 value works)\n        ps.solution.primal_status = Sln_Unknown\n        ps.solution.dual_status = Sln_InfeasibilityCertificate\n        ps.solution.is_primal_ray = false\n        ps.solution.is_dual_ray = true\n        ps.solution.z_primal = ps.solution.z_dual = T(Inf)\n        i_ = ps.new_con_idx[i]\n        ps.solution.y_lower[i_] = one(T)\n        return\n    else\n        push!(ps.ops, EmptyRow(i, zero(T)))\n    end\n\n    # Book-keeping\n    ps.updated = true\n    ps.rowflag[i] = false\n    ps.nrow -= 1\nend\n\nfunction postsolve!(sol::Solution{T}, op::EmptyRow{T}) where{T}\n    sol.y_lower[op.i] = pos_part(op.y)\n    sol.y_upper[op.i] = neg_part(op.y)\n    return nothing\nend\n"
  },
  {
    "path": "src/Presolve/fixed_variable.jl",
    "content": "struct FixedVariable{T} <: PresolveTransformation{T}\n    j::Int  # variable index\n    x::T  # primal value\n    c::T  # current objective coeff\n    col::Col{T}  # current column\nend\n\nfunction remove_fixed_variable!(ps::PresolveData, j::Int)\n    ps.colflag[j] || return nothing  # Column was already removed\n    \n    # Check bounds\n    lb, ub = ps.lcol[j], ps.ucol[j]\n\n    # TODO: use tolerance\n    if lb == ub\n        @debug \"Fix variable $j to $lb\"\n\n        col = ps.pb0.acols[j]\n        cj = ps.obj[j]\n\n        # Remove column\n        ps.colflag[j] = false\n        ps.ncol -= 1\n        ps.updated = true\n\n        # TODO: make this more efficient\n        push!(ps.ops, FixedVariable(j, lb, cj, Col(\n            [i for i in col.nzind if ps.rowflag[i]],\n            [aij for (i, aij) in zip(col.nzind, col.nzval) if ps.rowflag[i]]\n        )))\n\n        # Update objective constant\n        ps.obj0 += cj * lb\n\n        # Update rows\n        for (i, aij) in zip(col.nzind, col.nzval)\n            ps.rowflag[i] || continue  # This row is no longer in the problem\n            iszero(aij) && continue  # Skip if coefficient is zero\n\n            # Update row bounds\n            ps.lrow[i] -= aij * lb\n            ps.urow[i] -= aij * lb\n\n            ps.nzrow[i] -= 1\n\n            # Check row\n            if ps.nzrow[i] == 0\n                remove_empty_row!(ps, i)\n            elseif ps.nzrow == 1\n                push!(ps.row_singletons, i)\n            end\n        end  # row update\n    end\n\n    # Done\n    return nothing\nend\n\nfunction postsolve!(sol::Solution{T}, op::FixedVariable{T}) where{T}\n    sol.x[op.j] = op.x\n    s = sol.is_dual_ray ? zero(T) : op.c\n    for (i, aij) in zip(op.col.nzind, op.col.nzval)\n        s -= aij * (sol.y_lower[i] - sol.y_upper[i])\n    end\n    sol.s_lower[op.j] = pos_part(s)\n    sol.s_upper[op.j] = neg_part(s)\n    return nothing\nend"
  },
  {
    "path": "src/Presolve/forcing_row.jl",
    "content": "struct ForcingRow{T} <: PresolveTransformation{T}\n    i::Int  # Row index\n    at_lower::Bool  # Whether row is forced to its lower bound (false means upper)\n    row::Row{T}  # Row\n    cols::Vector{Col{T}}  # Columns of variables in forcing row\n    xs::Vector{T}  # Primal values of variables in the row (upper or lower bound)\n    cs::Vector{T}  # Objective coeffs of variables in forcing row\nend\n\nstruct DominatedRow{T} <: PresolveTransformation{T}\n    i::Int  # Row index\nend\n\nfunction remove_forcing_row!(ps::PresolveData{T}, i::Int) where{T}\n    ps.rowflag[i] || return\n    ps.nzrow[i] == 1 && return  # skip row singletons \n\n    # Implied row bounds\n    row = ps.pb0.arows[i]\n    l_ = u_ = zero(T)\n    for (j, aij) in zip(row.nzind, row.nzval)\n        ps.colflag[j] || continue\n        if aij < zero(T)\n            l_ += aij * ps.ucol[j]\n            u_ += aij * ps.lcol[j]\n        else\n            l_ += aij * ps.lcol[j]\n            u_ += aij * ps.ucol[j]\n        end\n\n        isfinite(l_) || isfinite(u_) || break  # infinite bounds\n    end\n\n    l, u = ps.lrow[i], ps.urow[i]\n    if l <= l_ <= u_ <= u\n        # Constraint is dominated\n        @debug \"Row $i is dominated\"\n        ps.rowflag[i] = false\n        ps.updated = true\n        ps.nrow -= 1\n\n        push!(ps.ops, DominatedRow{T}(i))\n        # Update column non-zeros\n        for (j, aij) in zip(row.nzind, row.nzval)\n            ps.colflag[j] || continue\n            ps.nzcol[j] -= !iszero(aij)\n        end\n\n    elseif l_ == u\n        # Row is forced to its upper bound\n        @debug \"Row $i is forced to its upper bound\"  (l, u) (l_, u_)\n        # Record tranformation\n        row_ = Row(\n            [  j for (j, aij) in zip(row.nzind, row.nzval) if ps.colflag[j]],\n            [aij for (j, aij) in zip(row.nzind, row.nzval) if ps.colflag[j]]\n        )\n        cols_ = Col{T}[]\n        xs = T[]\n        cs = T[]\n        for (j, aij) in zip(row.nzind, row.nzval)\n            ps.colflag[j] || continue\n            # Extract column j\n            col = ps.pb0.acols[j]\n\n            col_ = Col{T}(Int[], T[])\n\n            # Fix variable to its bound\n            # TODO: put this in a function and mutualize with fixed variables\n            if aij > 0\n                # Set xj to its lower bound\n                xj_ = ps.lcol[j]\n            else\n                # Set xj to its upper bound\n                xj_ = ps.ucol[j]\n            end\n\n            for (k, akj) in zip(col.nzind, col.nzval)\n                ps.rowflag[k] || continue\n\n                # Update column j\n                push!(col_.nzind, k)\n                push!(col_.nzval, akj)\n\n                # Update row k\n                ps.nzrow[k] -= 1\n                ps.lrow[k] -= akj * xj_\n                ps.urow[k] -= akj * xj_\n\n                # ps.nzrow[k] == 0 && remove_empty_row!(ps, k)\n                ps.nzrow[k] == 1 && push!(ps.row_singletons, k)\n            end\n            \n            cj = ps.obj[j]\n            push!(cols_, col_)\n            push!(xs, xj_)\n            push!(cs, cj)\n\n            # Remove variable from problem\n            ps.colflag[j] = false\n            ps.ncol -= 1\n        end\n\n        # Record transformation\n        push!(ps.ops, ForcingRow(i, true, row_, cols_, xs, cs))\n\n        # Book-keeping\n        ps.rowflag[i] = false\n        ps.nrow -= 1\n        ps.updated = true\n\n    elseif u_ == l\n        # Row is forced to its lower bound\n        @debug \"Row $i is forced to its lower bound\" (l, u) (l_, u_)\n        # Record tranformation\n        row_ = Row(\n            [  j for (j, aij) in zip(row.nzind, row.nzval) if ps.colflag[j]],\n            [aij for (j, aij) in zip(row.nzind, row.nzval) if ps.colflag[j]]\n        )\n        cols_ = Col{T}[]\n        xs = T[]\n        cs = T[]\n        for (j, aij) in zip(row.nzind, row.nzval)\n            ps.colflag[j] || continue\n            # Extract column j\n            col = ps.pb0.acols[j]\n\n            col_ = Col{T}(Int[], T[])\n\n            # Fix variable to its bound\n            # TODO: put this in a function and mutualize with fixed variables\n            if aij > 0\n                # Set xj to its upper bound\n                xj_ = ps.ucol[j]\n            else\n                # Set xj to its lower bound\n                xj_ = ps.lcol[j]\n            end\n\n            for (k, akj) in zip(col.nzind, col.nzval)\n                ps.rowflag[k] || continue\n\n                # Update column j\n                push!(col_.nzind, k)\n                push!(col_.nzval, akj)\n\n                # Update row k\n                ps.nzrow[k] -= 1\n                ps.lrow[k] -= akj * xj_\n                ps.urow[k] -= akj * xj_\n\n                # ps.nzrow[k] == 0 && remove_empty_row!(ps, k)\n                ps.nzrow[k] == 1 && push!(ps.row_singletons, k)\n            end\n            \n            cj = ps.obj[j]\n            push!(cols_, col_)\n            push!(xs, xj_)\n            push!(cs, cj)\n\n            # Remove variable from problem\n            ps.colflag[j] = false\n            ps.ncol -= 1\n        end\n\n        # Record transformation\n        push!(ps.ops, ForcingRow(i, false, row_, cols_, xs, cs))\n\n        # Book-keeping\n        ps.rowflag[i] = false\n        ps.nrow -= 1\n        ps.updated = true\n    end\n    # TODO: handle infeasible row cases\n\n    return nothing\nend\n\nfunction postsolve!(sol::Solution{T}, op::DominatedRow{T}) where{T}\n    sol.y_lower[op.i] = zero(T)\n    sol.y_upper[op.i] = zero(T)\n    return nothing\nend\n\n# TODO: postsolve of forcing rows\nfunction postsolve!(sol::Solution{T}, op::ForcingRow{T}) where{T}\n\n    # Primal\n    for (j, xj) in zip(op.row.nzind, op.xs)\n        sol.x[j] = xj\n    end\n\n    # Dual\n    z = similar(op.cs)\n    for (l, (j, cj, col)) in enumerate(zip(op.row.nzind, op.cs, op.cols))\n        z[l] = cj\n        for (k, akj) in zip(col.nzind, col.nzval)\n            z[l] -= akj * (sol.y_lower[k] - sol.y_upper[k])\n        end\n    end\n\n    # First, compute yi\n    y = op.at_lower ? maximum(z ./ op.row.nzval) : minimum(z ./ op.row.nzval)\n    sol.y_lower[op.i] = pos_part(y)\n    sol.y_upper[op.i] = neg_part(y)\n\n    # Extract reduced costs\n    for (j, aij, zj) in zip(op.row.nzind, op.row.nzval, z)\n        s = zj - aij * y\n        sol.s_lower[j] = pos_part(s)\n        sol.s_upper[j] = neg_part(s)\n    end\n    return nothing\nend"
  },
  {
    "path": "src/Presolve/free_column_singleton.jl",
    "content": "struct FreeColumnSingleton{T} <: PresolveTransformation{T}\n    i::Int  # Row index\n    j::Int  # Column index\n    l::T  # Row lower bound\n    u::T  # Row upper bound\n    aij::T\n    y::T  # Dual variable\n    row::Row{T}\nend\n\nfunction remove_free_column_singleton!(ps::PresolveData{T}, j::Int) where{T}\n\n    ps.colflag[j] && ps.nzcol[j] == 1 || return nothing  # only column singletons\n\n    col = ps.pb0.acols[j]\n\n    # Find non-zero index\n    # TODO: put this in a function\n    nz = 0\n    i, aij = 0, zero(T)\n    for (i_, a_) in zip(col.nzind, col.nzval)\n        if ps.rowflag[i_]\n            nz += !iszero(a_); nz <= 1 || break\n\n            i = i_\n            aij = a_\n        end\n    end\n\n    # Not a singleton\n    nz == 1 || error(\"Expected singletons but column $j has $nz non-zeros\")\n\n    if iszero(aij) || iszero(i)\n        return nothing  # column is actually empty\n    end\n\n    row = ps.pb0.arows[i]\n    lr, ur = ps.lrow[i], ps.urow[i]\n\n    # Detect if xj is implied free\n    l, u = ps.lcol[j], ps.ucol[j]\n    if isfinite(l) || isfinite(u)\n        # Not a free variable, compute implied bounds\n        if aij > zero(T)\n            l_, u_ = lr, ur\n            for (k, aik) in zip(row.nzind, row.nzval)\n                (ps.colflag[k] && k != j) || continue\n                # Update bounds\n                if aik > 0\n                    l_ -= aik * ps.ucol[k]\n                    u_ -= aik * ps.lcol[k]\n                else\n                    l_ -= aik * ps.lcol[k]\n                    u_ -= aik * ps.ucol[k]\n                end\n            end\n            l_ /= aij\n            u_ /= aij\n        else\n            l_, u_ = ur, lr\n            for (k, aik) in zip(row.nzind, row.nzval)\n                (ps.colflag[k] && k != j) || continue\n                # Update bounds\n                if aik > 0\n                    l_ -= aik * ps.lcol[k]\n                    u_ -= aik * ps.ucol[k]\n                else\n                    l_ -= aik * ps.ucol[k]\n                    u_ -= aik * ps.lcol[k]\n                end\n            end\n            l_ /= aij\n            u_ /= aij\n        end\n        @debug \"\"\"Column singleton $j\n            Original bounds: [$l, $u]\n             Implied bounds: [$(l_), $(u_)]\n        \"\"\"\n        l <= l_ <= u_ <= u || return nothing  # Not implied free\n    end\n\n    # Remove (implied) free column\n    @debug \"Removing (implied) free column singleton $j\"\n    y = ps.obj[j] / aij  # dual of row i\n\n    # Update objective\n    ps.obj0 += (y >= zero(T)) ? y * lr : y * ur\n    row_ = Row{T}(Int[], T[])\n    for (j_, aij_) in zip(row.nzind, row.nzval)\n        ps.colflag[j_] && (j_ != j) || continue\n\n        push!(row_.nzind, j_)\n        push!(row_.nzval, aij_)\n        ps.obj[j_] -= y * aij_\n\n        # Update number of non-zeros in column\n        ps.nzcol[j_] -= 1\n    end\n\n    # Book-keeping\n    push!(ps.ops, FreeColumnSingleton(i, j, lr, ur, aij, y, row_))\n    ps.rowflag[i] = false  # remove row\n    ps.colflag[j] = false  # remove column\n    ps.nrow -= 1\n    ps.ncol -= 1\n    ps.updated = true\n\n    return nothing\nend\n\nfunction postsolve!(sol::Solution{T}, op::FreeColumnSingleton{T}) where{T}\n    # Dual\n    y = op.y\n    sol.y_lower[op.i] = pos_part(y)\n    sol.y_upper[op.i] = neg_part(y)\n    sol.s_lower[op.j] = zero(T)\n    sol.s_upper[op.j] = zero(T)\n\n    # Primal\n    sol.x[op.j] = sol.is_primal_ray ? zero(T) : (y >= zero(T) ? op.l : op.u)\n    for (k, aik) in zip(op.row.nzind, op.row.nzval)\n        sol.x[op.j] -= aik * sol.x[k]\n    end\n    sol.x[op.j] /= op.aij\n    \n    return nothing\nend"
  },
  {
    "path": "src/Presolve/row_singleton.jl",
    "content": "struct RowSingleton{T} <: PresolveTransformation{T}\n    i::Int  # Row index\n    j::Int  # Column index\n    aij::T  # Row coefficient\n    force_lower::Bool  # Whether row was forcing the lower bound\n    force_upper::Bool  # Whether row was forcing the upper bound\nend\n\n\nfunction remove_row_singleton!(ps::PresolveData{T}, i::Int) where{T}\n    # Sanity checks\n    (ps.rowflag[i] && ps.nzrow[i] == 1) || return nothing\n\n    row = ps.pb0.arows[i]\n\n    # Find non-zero coefficient and its column index\n    nz = 0\n    j, aij = 0, zero(T)\n    for (j_, aij_) in zip(row.nzind, row.nzval)\n        if ps.colflag[j_] && !iszero(aij_)\n            nz += 1; nz <= 1 || break  # not a row singleton\n\n            j = j_\n            aij = aij_\n        end\n    end\n\n    if nz > 1\n        @error \"Row $i was marked as row singleton but has $nz non-zeros\"\n        return nothing\n    end\n\n    if iszero(aij)\n        # Row is actually empty\n        # It will be removed at the next forcing constraints check\n        return nothing\n    end\n\n    # Compute implied bounds\n    if aij > zero(T)\n        l = ps.lrow[i] / aij\n        u = ps.urow[i] / aij\n    else\n        l = ps.urow[i] / aij\n        u = ps.lrow[i] / aij\n    end\n\n    # Compare to variable bounds\n    # TODO: what if bounds are incompatible?\n    lb, ub = ps.lcol[j], ps.ucol[j]\n    force_lower = (l >= lb)\n    force_upper = (u <= ub)\n    if l >= lb\n        ps.lcol[j] = l\n    end\n    if u <= ub\n        ps.ucol[j] = u\n    end\n\n    # Book-keeping\n    push!(ps.ops, RowSingleton(i, j, aij, force_lower, force_upper))\n    ps.rowflag[i] = false\n    ps.updated = true\n    ps.nrow -= 1\n    ps.nzcol[j] -= 1\n\n    # Check if we created a fixed/empty column\n    if ps.lcol[j] == ps.ucol[j]\n        remove_fixed_variable!(ps, j)\n        return nothing\n    end\n    if ps.nzcol[j] == 0\n        # TODO: remove empty column\n    elseif ps.nzcol[j] == 1\n        # TODO: track column singleton\n    end\n\n    return nothing\nend\n\nfunction postsolve!(sol::Solution{T}, op::RowSingleton{T}) where{T}\n\n    if op.force_lower\n        if op.aij > zero(T)\n            sol.y_lower[op.i] = sol.s_lower[op.j] / op.aij\n        else\n            sol.y_upper[op.i] = sol.s_lower[op.j] / abs(op.aij)\n        end\n        sol.s_lower[op.j] = zero(T)\n    end\n    if op.force_upper\n        if op.aij > zero(T)\n            sol.y_upper[op.i] = sol.s_upper[op.j] / op.aij\n        else\n            sol.y_lower[op.i] = sol.s_upper[op.j] / abs(op.aij)\n        end\n        sol.s_upper[op.j] = zero(T)\n    end\n\n    return nothing\nend"
  },
  {
    "path": "src/Tulip.jl",
    "content": "module Tulip\n\nusing LinearAlgebra\nusing Logging\nusing Printf\nusing SparseArrays\nusing TOML\n\nusing TimerOutputs\n\n# Read Tulip version from Project.toml file\nconst TULIP_VERSION = let project = joinpath(@__DIR__, \"..\", \"Project.toml\")\n    Base.include_dependency(project)\n    VersionNumber(TOML.parsefile(project)[\"version\"])\nend\nversion() = TULIP_VERSION\n\ninclude(\"utils.jl\")\n\n# Linear algebra\ninclude(\"LinearAlgebra/LinearAlgebra.jl\")\nimport .TLPLinearAlgebra.construct_matrix\nconst TLA = TLPLinearAlgebra\n\n# KKT solvers\ninclude(\"KKT/KKT.jl\")\nusing .KKT\n\n# Commons data structures\n# TODO: put this in a module\n\ninclude(\"status.jl\")    # Termination and solution statuses\ninclude(\"problemData.jl\")\ninclude(\"solution.jl\")\ninclude(\"attributes.jl\")\n\n# Presolve module\ninclude(\"Presolve/Presolve.jl\")\n\n# IPM solvers\ninclude(\"./IPM/IPM.jl\")\n\n\ninclude(\"parameters.jl\")\n\n# Model\ninclude(\"model.jl\")\n\n# Interfaces\ninclude(\"Interfaces/tulip_julia_api.jl\")\ninclude(\"Interfaces/MOI/MOI_wrapper.jl\")\n\nend # module\n"
  },
  {
    "path": "src/attributes.jl",
    "content": "abstract type AbstractAttribute end\n\n# ==============================================================================\n#\n#      Model attributes\n#\n# ==============================================================================\nabstract type AbstractModelAttribute <: AbstractAttribute end\n\n\"\"\"\n    ModelName\n\nThe name of the model.\n\n**Type:** `String`\n\n**Modifiable:** Yes\n\n### Examples\n\n```julia\nTulip.set_attribute(model, Tulip.ModelName(), \"lp_example\")\nTulip.get_attribute(model, Tulip.ModelName())\n```\n\"\"\"\nstruct ModelName <: AbstractModelAttribute end\n\n\"\"\"\n    NumberOfConstraints\n\nNumber of constraints in the model.\n\n**Type:** `Int`\n\n**Modifiable:** No\n\n### Examples\n\n```julia\nTulip.get_attribute(model, Tulip.NumberOfConstraints())\n```\n\"\"\"\nstruct NumberOfConstraints <: AbstractModelAttribute end\n\n\"\"\"\n    NumberOfVariables\n\nNumber of variables in the model.\n\n**Type:** `Int`\n\n**Modifiable:** No\n\n### Examples\n\n```julia\nTulip.get_attribute(model, Tulip.NumberOfVariables())\n```\n\"\"\"\nstruct NumberOfVariables <: AbstractModelAttribute end\n\n\"\"\"\n    ObjectiveValue\n\nObjective value of the current primal solution.\n\n**Type:** `T`\n\n**Modifiable:** No\n\n### Examples\n\n```julia\nTulip.get_attribute(model, Tulip.ObjectiveValue())\n```\n\"\"\"\nstruct ObjectiveValue <: AbstractModelAttribute end\n\n\"\"\"\n    DualObjectiveValue\n\nObjective value of the current dual solution.\n\n**Type:** `T`\n\n**Modifiable:** No\n\n### Examples\n\n```julia\nTulip.get_attribute(model, Tulip.DualObjectiveValue())\n```\n\"\"\"\nstruct DualObjectiveValue <: AbstractModelAttribute end\n\n\"\"\"\n    ObjectiveConstant\n\nConstant objective offset, defaults to zero.\n\n**Type:** `T`\n\n**Modifiable:** Yes\n\n### Examples\n\n```julia\nTulip.set_attribute(model, Tulip.ObjectiveConstant(), zero(T))\nTulip.get_attribute(model, Tulip.ObjectiveConstant())\n```\n\"\"\"\nstruct ObjectiveConstant <: AbstractModelAttribute end\n\n\"\"\"\n    ObjectiveSense\n\n\"\"\"\nstruct ObjectiveSense <: AbstractModelAttribute end\n\n\"\"\"\n    Status\n\nModel status\n\n### Type:\n\n**Modifiable:** No\n\n### Examples\n\n```julia\nTulip.get(model, Tulip.Status())\n```\n\"\"\"\nstruct Status <: AbstractModelAttribute end\n\n\"\"\"\n    BarrierIterations\n\nNumber of iterations of the barrier algorithm in the last call.\n\nThis number may be zero in the following cases:\n* the model has been solved yet\n* presolve solved the model\n* the initial solution was optimal\n\n**Type:** `Int`\n\n**Modifiable:** No\n\n### Examples\n\n```julia\nTulip.get_attribute(model, Tulip.BarrierIterations())\n```\n\"\"\"\nstruct BarrierIterations <: AbstractModelAttribute end\n\n\"\"\"\n    SolutionTime\n\nTotal solution time, in seconds.\n\n**Type:** `Float64`\n\n**Modifiable:** No\n\n### Examples\n\n```julia\nTulip.get_attribute(model, Tulip.SolutionTime())\n```\n\"\"\"\nstruct SolutionTime <: AbstractModelAttribute end\n\n\n# ==============================================================================\n#\n#      Variable attributes\n#\n# ==============================================================================\nabstract type AbstractVariableAttribute <: AbstractAttribute end\n\n\"\"\"\n    VariableLowerBound\n\nVariable lower bound.\n\n**Type:** `T`\n\n**Modifiable:** Yes\n\n### Examples\n\n```julia\nTulip.set_attribute(model, Tulip.VariableLowerBound(), 1, zero(T))\nTulip.get_attribute(model, Tulip.VariableLowerBound(), 1)\n```\n\"\"\"\nstruct VariableLowerBound <: AbstractVariableAttribute end\n\n\"\"\"\n    VariableUpperBound\n\nVariable upper bound.\n\n**Type:** `T`\n\n**Modifiable:** Yes\n\n### Examples\n\n```julia\nTulip.set_attribute(model, Tulip.VariableUpperBound(), 1, one(T))\nTulip.get_attribute(model, Tulip.VariableUpperBound(), 1)\n```\n\"\"\"\nstruct VariableUpperBound <: AbstractVariableAttribute end\n\n\"\"\"\n    VariableObjectiveCoeff\n\nObjective coefficient of the variable.\n\n**Type:** `T`\n\n**Modifiable:** Yes\n\n### Examples\n\n```julia\nTulip.set_attribute(model, Tulip.VariableObjectiveCoeff(), 1, one(T))\nTulip.get_attribute(model, Tulip.VariableObjectiveCoeff(), 1)\n```\n\"\"\"\nstruct VariableObjectiveCoeff <: AbstractVariableAttribute end\n\n\"\"\"\n    VariableName\n\nName of the variable.\n\n**Type:** `String`\n\n**Modifiable:** Yes\n\n### Examples\n\n```julia\nTulip.set_attribute(model, Tulip.VariableName(), 1, \"x1\")\nTulip.get_attribute(model, Tulip.VariableName(), 1)\n```\n\"\"\"\nstruct VariableName <: AbstractVariableAttribute end\n\n\n# ==============================================================================\n#\n#      Constraint attributes\n#\n# ==============================================================================\nabstract type AbstractConstraintAttribute <: AbstractAttribute end\n\n\"\"\"\n    ConstraintLowerBound\n\nConstraint lower bound.\n\n**Type:** `T`\n\n**Modifiable:** Yes\n\n### Examples\n\n```julia\nTulip.set_attribute(model, Tulip.ConstraintLowerBound(), 1, zero(T))\nTulip.get_attribute(model, Tulip.ConstraintLowerBound(), 1)\n```\n\"\"\"\nstruct ConstraintLowerBound <: AbstractConstraintAttribute end\n\n\"\"\"\n    ConstraintUpperBound\n\nConstraint upper bound.\n\n**Type:** `T`\n\n**Modifiable:** Yes\n\n### Examples\n\n```julia\nTulip.set_attribute(model, Tulip.ConstraintUpperBound(), 1, one(T))\nTulip.get_attribute(model, Tulip.ConstraintUpperBound(), 1)\n```\n\"\"\"\nstruct ConstraintUpperBound <: AbstractConstraintAttribute end\n\n\"\"\"\n    ConstraintName\n\nName of the constraint.\n\n**Type:** `String`\n\n**Modifiable:** Yes\n\n### Examples\n\n```julia\nTulip.set_attribute(model, Tulip.ConstraintName(), 1, \"c1\")\nTulip.get_attribute(model, Tulip.ConstraintName(), 1)\n```\n\"\"\"\nstruct ConstraintName <: AbstractConstraintAttribute end"
  },
  {
    "path": "src/model.jl",
    "content": "mutable struct Model{T}\n\n    # Parameters\n    params::Parameters{T}\n\n    # TODO: model status\n    #= Use an enum\n        * Empty\n        * Modified\n        * OptimizationInProgress (optimize! is being called)\n        * Solved (optimize! was called and the problem has not been modified since)\n            TODO: some modifications should not change the solution status, e.g.:\n                * changing names\n                * changing objective constant\n    =#\n    status::TerminationStatus\n\n    # Problem data\n    pbdata::ProblemData{T}\n\n    # Presolved problem\n    # If presolved is disabled, this will point to m.pbdata\n    presolve_data::Union{Nothing, PresolveData{T}}\n\n    # IPM solver\n    # If required, the problem is transformed to standard form\n    # when instantiating the IPMSolver object.\n    solver::Union{Nothing, AbstractIPMOptimizer{T}}\n\n    # Problem solution (in original space)\n    solution::Union{Nothing, Solution{T}}\n\n    Model{T}() where{T} = new{T}(\n        Parameters{T}(), Trm_NotCalled, ProblemData{T}(),\n        nothing, nothing, nothing\n    )\nend\n\n# TODO\n# Basic functionalities (e.g., copy, empty, reset) should go in this file\n# Interface-like should go in Interfaces\n#=\n    * optimize!\n    * empty!\n    * querying/setting parameters & attributes\n    * build/modify problem through Model object\n    * solution query\n=#\n\nimport Base.empty!\n\nfunction Base.empty!(m::Model{T}) where{T}\n    m.pbdata = ProblemData{T}()\n    m.status = Trm_NotCalled\n    m.presolve_data = nothing\n    m.solver = nothing\n    m.solution = nothing\n\n    return nothing\nend\n\n\"\"\"\n    optimize!(model::Model{T})\n\nSolve the optimization problem.\n\"\"\"\nfunction optimize!(model::Model{T}) where{T}\n\n    # Set number of threads\n    model.params.Threads >= 1 || error(\n        \"Number of threads must be > 0 (is $(model.params.Threads))\"\n    )\n    BLAS.set_num_threads(model.params.Threads)\n\n    # Print initial stats\n    if model.params.OutputLevel > 0\n        @printf \"\\nProblem info\\n\"\n        @printf \"  Name        : %s\\n\" model.pbdata.name\n        @printf \"  Constraints : %d\\n\" model.pbdata.ncon\n        @printf \"  Variables   : %d\\n\" model.pbdata.nvar\n        @printf \"  Non-zeros   : %d\\n\" sum(length.([col.nzind for col in model.pbdata.acols]))\n    end\n\n    pb_ = model.pbdata\n    # Presolve\n    # TODO: improve the if-else\n    ps_options = model.params.Presolve\n    if ps_options.Level > 0\n        model.presolve_data = PresolveData(model.pbdata, ps_options)\n        t_ = @elapsed st = presolve!(model.presolve_data)\n        model.status = st\n\n        if model.params.OutputLevel > 0\n            ps = model.presolve_data\n            nz0 = mapreduce(col -> length(col.nzind), +, model.pbdata.acols)\n            nz = sum(ps.nzrow[ps.rowflag])\n            @printf \"\\nReduced problem info\\n\"\n            @printf \"  Constraints : %d  (removed %d)\\n\" ps.nrow (ps.pb0.ncon - ps.nrow)\n            @printf \"  Variables   : %d  (removed %d)\\n\" ps.ncol (ps.pb0.nvar - ps.ncol)\n            @printf \"  Non-zeros   : %d  (removed %d)\\n\" nz (nz0 - nz)\n            @printf \"Presolve time : %.3fs\\n\" t_\n        end\n\n        # Check presolve status\n        if st == Trm_Optimal || st == Trm_PrimalInfeasible || st == Trm_DualInfeasible || st == Trm_PrimalDualInfeasible\n            model.params.OutputLevel > 0 && println(\"Presolve solved the problem.\")\n\n            # Perform post-solve\n            sol0 = Solution{T}(model.pbdata.ncon, model.pbdata.nvar)\n            postsolve!(sol0, model.presolve_data.solution, model.presolve_data)\n            model.solution = sol0\n\n            # Book-keeping\n            # TODO: have a ModelStatus that indicates that model was solved by presolve\n            model.solver = nothing\n\n            # Done.\n            return\n        end\n\n        # Presolve was not able to solve the problem\n        extract_reduced_problem!(model.presolve_data)\n        pb_ = model.presolve_data.pb_red\n    end\n\n    # Extract data in IPM form\n    dat = IPMData(pb_, model.params.MatrixFactory)\n\n    # Instantiate the IPM solver\n    model.params.IPM.OutputLevel = model.params.OutputLevel\n    model.solver = instantiate(model.params.IPM.Factory, dat, model.params.KKT)\n\n    # Solve the problem\n    # TODO: add a try-catch for error handling\n    ipm_optimize!(model.solver, model.params.IPM)\n\n    # Recover solution in original space\n    sol_inner = Solution{T}(pb_.ncon, pb_.nvar)\n    _extract_solution!(sol_inner, pb_, model.solver)\n\n    # Post-solve\n    if ps_options.Level > 0\n        sol_outer = Solution{T}(model.pbdata.ncon, model.pbdata.nvar)\n        postsolve!(sol_outer, sol_inner, model.presolve_data)\n        model.solution = sol_outer\n    else\n        model.solution = sol_inner\n    end\n\n    model.status = model.solver.solver_status\n\n    # Done.\n    return nothing\nend\n\nfunction _extract_solution!(sol::Solution{T},\n    pb::ProblemData{T}, ipm::AbstractIPMOptimizer{T}\n) where{T}\n\n    m, n = pb.ncon, pb.nvar\n\n    # Extract column information\n    # TODO: check for ray vs vertex\n    sol.primal_status = ipm.primal_status\n    sol.dual_status = ipm.dual_status\n\n    is_primal_ray = (sol.primal_status == Sln_InfeasibilityCertificate)\n    is_dual_ray = (sol.dual_status == Sln_InfeasibilityCertificate)\n    sol.is_primal_ray = is_primal_ray\n    sol.is_dual_ray = is_dual_ray\n    τ_ = (is_primal_ray || is_dual_ray) ? one(T) : inv(ipm.pt.τ)\n\n    @. sol.x = ipm.pt.x[1:n] * τ_\n    @. sol.s_lower = ipm.pt.zl[1:n] * τ_\n    @. sol.s_upper = ipm.pt.zu[1:n] * τ_\n\n    # Extract row information\n    @. sol.y_lower = pos_part.(ipm.pt.y) * τ_\n    @. sol.y_upper = neg_part.(ipm.pt.y) * τ_\n\n    # Compute row primal\n    for (i, row) in enumerate(pb.arows)\n        ax = zero(T)\n        for (j, aij) in zip(row.nzind, row.nzval)\n            ax += aij * sol.x[j]\n        end\n        sol.Ax[i] = ax\n    end\n\n    # Primal and dual objectives\n    if sol.primal_status == Sln_InfeasibilityCertificate\n        # Unbounded ray\n        sol.z_primal = -T(Inf)\n        sol.z_dual = -T(Inf)\n    elseif sol.primal_status == Sln_Optimal || sol.primal_status == Sln_FeasiblePoint\n        sol.z_primal = ipm.primal_objective\n    else\n        # Unknown solution status\n        sol.z_primal = NaN\n    end\n\n    if sol.dual_status == Sln_InfeasibilityCertificate\n        # Farkas proof of infeasibility\n        sol.z_primal = T(Inf)\n        sol.z_dual = T(Inf)\n    elseif sol.dual_status == Sln_Optimal || sol.dual_status == Sln_FeasiblePoint\n        # Dual solution is feasible\n        sol.z_dual = ipm.dual_objective\n    else\n        # Unknown solution status\n        sol.z_dual = NaN\n    end\n\n    return nothing\nend\n"
  },
  {
    "path": "src/parameters.jl",
    "content": "\"\"\"\n    Parameters{T}\n\n\"\"\"\nBase.@kwdef mutable struct Parameters{T}\n    # Model-wise parameters\n    Threads::Int = 1\n    OutputLevel::Int = 0\n\n    # Linear algebra (MatrixOptions)\n    MatrixFactory::Factory{<:AbstractMatrix} = Factory(SparseMatrixCSC)\n\n    # Presolve\n    Presolve::PresolveOptions{T} = PresolveOptions{T}()\n\n    # IPM\n    IPM::IPMOptions{T} = IPMOptions{T}()\n    \n    # KKT\n    KKT::KKTOptions{T} = KKTOptions{T}()\nend"
  },
  {
    "path": "src/problemData.jl",
    "content": "using SparseArrays\n\nmutable struct RowOrCol{T}\n    nzind::Vector{Int}\n    nzval::Vector{T}\nend\n\nconst Row = RowOrCol\nconst Col = RowOrCol\n\n\"\"\"\n    ProblemData{T}\n\nData structure for storing problem data in precision `T`.\n\nThe LP is represented in canonical form\n\n```math\n\\\\begin{array}{rl}\n    \\\\displaystyle \\\\min_{x} \\\\ \\\\ \\\\ & c^{T} x + c_{0} \\\\\\\\\n    s.t. \\\\ \\\\ \\\\ & l_{r} \\\\leq A x \\\\leq u_{r} \\\\\\\\\n    & l_{c} \\\\leq x \\\\leq u_{c}\n\\\\end{array}\n```\n\"\"\"\nmutable struct ProblemData{T}\n\n    name::String\n\n    # Dimensions\n    ncon::Int  # Number of rows\n    nvar::Int  # Number of columns (i.e. variables)\n\n    # Objective\n    # TODO: objective sense\n    objsense::Bool  # true is min, false is max\n    obj::Vector{T}\n    obj0::T  # Constant objective offset\n\n    # Constraint matrix\n    # We store both rows and columns. It is redundant but simplifies access.\n    # TODO: put this in its own data structure? (would allow more flexibility in modelling)\n    arows::Vector{Row{T}}\n    acols::Vector{Col{T}}\n\n    # TODO: Data structures for QP\n    # qrows\n    # qcols\n\n    # Bounds\n    lcon::Vector{T}\n    ucon::Vector{T}\n    lvar::Vector{T}\n    uvar::Vector{T}\n\n    # Names\n    con_names::Vector{String}\n    var_names::Vector{String}\n\n    # Only allow empty problems to be instantiated for now\n    ProblemData{T}(pbname::String=\"\") where {T} = new{T}(\n        pbname, 0, 0,\n        true, T[], zero(T),\n        Row{T}[], Col{T}[],\n        T[], T[], T[], T[],\n        String[], String[]\n    )\nend\n\nimport Base.empty!\n\nfunction Base.empty!(pb::ProblemData{T}) where{T}\n\n    pb.name = \"\"\n\n    pb.ncon = 0\n    pb.nvar = 0\n\n    pb.objsense = true\n    pb.obj = T[]\n    pb.obj0 = zero(T)\n\n    pb.arows = Row{T}[]\n    pb.acols = Col{T}[]\n\n    pb.lcon = T[]\n    pb.ucon = T[]\n    pb.lvar = T[]\n    pb.uvar = T[]\n\n    pb.con_names = String[]\n    pb.var_names = String[]\n\n    return pb\nend\n\n#=\n    TODO:\n    * Creation\n        *[x] Add single constraint\n        *[ ] Add multiple constraints\n        *[x] Add single variable\n        *[ ] Add multiple variables\n        *[x] Load entire problem\n    * Modification\n        *[x] Empty model\n        *[x] Delete single constraint\n        *[x] Delete multiple constraints (fallback)\n        *[x] Delete single variable\n        *[x] Delete multiple variables (fallback)\n        *[x] Change single coefficient\n        *[ ] Change multiple coefficients\n    * Attributes\n        *[ ] Query model attributes\n            *[ ] MOI-supported attributes\n            *[ ] Other attributes\n=#\n\n# =============================\n#     Problem creation\n# =============================\n\n\"\"\"\n    add_constraint!(pb, rind, rval, l, u; [name, issorted])\n\nAdd one linear constraint to the problem.\n\n# Arguments\n* `pb::ProblemData{T}`: the problem to which the new row is added\n* `rind::Vector{Int}`: column indices in the new row\n* `rval::Vector{T}`: non-zero values in the new row\n* `l::T`\n* `u::T`\n* `name::String`: row name (defaults to `\"\"`)\n* `issorted::Bool`: indicates whether the row indices are already issorted.\n\"\"\"\nfunction add_constraint!(pb::ProblemData{T},\n    rind::Vector{Int}, rval::Vector{T},\n    l::T, u::T,\n    name::String=\"\";\n    issorted::Bool=false\n)::Int where{T}\n    # Sanity checks\n    nz = length(rind)\n    nz == length(rval) || throw(DimensionMismatch(\n        \"Cannot add a row with $nz indices but $(length(rval)) non-zeros\"\n    ))\n\n    # Go through through rval to check all coeffs are finite and remove zeros.\n    _rind = Vector{Int}(undef, nz)\n    _rval = Vector{T}(undef, nz)\n    _nz = 0\n    for (j, aij) in zip(rind, rval)\n        if !iszero(aij)\n            isfinite(aij) || error(\"Invalid row coefficient: $(aij)\")\n            _nz += 1\n            _rind[_nz] = j\n            _rval[_nz] = aij\n        end\n    end\n    resize!(_rind, _nz)\n    resize!(_rval, _nz)\n\n    # TODO: combine dupplicate indices\n\n    # Increment row counter\n    pb.ncon += 1\n    push!(pb.lcon, l)\n    push!(pb.ucon, u)\n    push!(pb.con_names, name)\n\n    # Create new row\n    if issorted\n        row = Row{T}(_rind, _rval)\n    else\n        # Sort indices first\n        p = sortperm(_rind)\n        row = Row{T}(_rind[p], _rval[p])\n    end\n    push!(pb.arows, row)\n\n    # Update column coefficients\n    for (j, aij) in zip(_rind, _rval)\n        push!(pb.acols[j].nzind, pb.ncon)\n        push!(pb.acols[j].nzval, aij)\n    end\n\n    # Done\n    return pb.ncon\nend\n\n\"\"\"\n    add_variable!(pb, cind, cval, obj, l, u, [name])\n\nAdd one variable to the problem.\n\n# Arguments\n* `pb::ProblemData{T}`: the problem to which the new column is added\n* `cind::Vector{Int}`: row indices in the new column\n* `cval::Vector{T}`: non-zero values in the new column\n* `obj::T`: objective coefficient\n* `l::T`: column lower bound\n* `u::T`: column upper bound\n* `name::String`: column name (defaults to `\"\"`)\n* `issorted::Bool`: indicates whether the column indices are already issorted.\n\"\"\"\nfunction add_variable!(pb::ProblemData{T},\n    cind::Vector{Int}, cval::Vector{T},\n    obj::T, l::T, u::T,\n    name::String=\"\";\n    issorted::Bool=false\n)::Int where{T}\n    # Sanity checks\n    nz = length(cind)\n    nz == length(cval) || throw(DimensionMismatch(\n        \"Cannot add a column with $nz indices but $(length(cval)) non-zeros\"\n    ))\n\n    # Go through through cval to check all coeffs are finite and remove zeros.\n    _cind = Vector{Int}(undef, nz)\n    _cval = Vector{T}(undef, nz)\n    _nz = 0\n    for (j, aij) in zip(cind, cval)\n        if !iszero(aij)\n            isfinite(aij) || error(\"Invalid column coefficient: $(aij)\")\n            _nz += 1\n            _cind[_nz] = j\n            _cval[_nz] = aij\n        end\n    end\n    resize!(_cind, _nz)\n    resize!(_cval, _nz)\n\n    # Increment column counter\n    pb.nvar += 1\n    push!(pb.lvar, l)\n    push!(pb.uvar, u)\n    push!(pb.obj, obj)\n    push!(pb.var_names, name)\n\n    # TODO: combine dupplicate indices\n\n    # Create a new column\n    if issorted\n        col = Col{T}(_cind, _cval)\n    else\n        # Sort indices\n        p = sortperm(_cind)\n        col = Col{T}(_cind[p], _cval[p])\n    end\n    push!(pb.acols, col)\n\n    # Update row coefficients\n    for (i, aij) in zip(_cind, _cval)\n        push!(pb.arows[i].nzind, pb.nvar)\n        push!(pb.arows[i].nzval, aij)\n    end\n\n    # Done\n    return pb.nvar\nend\n\n\"\"\"\n    load_problem!(pb, )\n\nLoad entire problem.\n\"\"\"\nfunction load_problem!(pb::ProblemData{T},\n    name::String,\n    objsense::Bool, obj::Vector{T}, obj0::T,\n    A::SparseMatrixCSC,\n    lcon::Vector{T}, ucon::Vector{T},\n    lvar::Vector{T}, uvar::Vector{T},\n    con_names::Vector{String}, var_names::Vector{String}\n) where{T}\n    empty!(pb)\n\n    # Sanity checks\n    ncon, nvar = size(A)\n    ncon == length(lcon) || error(\"\")\n    ncon == length(ucon) || error(\"\")\n    ncon == length(con_names) || error(\"\")\n    nvar == length(obj)\n    isfinite(obj0) || error(\"Objective offset $obj0 is not finite\")\n    nvar == length(lvar) || error(\"\")\n    nvar == length(uvar) || error(\"\")\n\n    # Copy data\n    pb.name = name\n    pb.ncon = ncon\n    pb.nvar = nvar\n    pb.objsense = objsense\n    pb.obj = copy(obj)\n    pb.obj0 = obj0\n    pb.lcon = copy(lcon)\n    pb.ucon = copy(ucon)\n    pb.lvar = copy(lvar)\n    pb.uvar = copy(uvar)\n    pb.con_names = copy(con_names)\n    pb.var_names = copy(var_names)\n\n    # Load coefficients\n    pb.acols = Vector{Col{T}}(undef, nvar)\n    pb.arows = Vector{Row{T}}(undef, ncon)\n    for j in 1:nvar\n        col = A[:, j]\n        pb.acols[j] = Col{T}(col.nzind, col.nzval)\n    end\n\n    At = sparse(A')\n    for i in 1:ncon\n        row = At[:, i]\n        pb.arows[i] = Row{T}(row.nzind, row.nzval)\n    end\n\n    return pb\nend\n\n# =============================\n#     Problem modification\n# =============================\n\n\"\"\"\n    delete_constraint!(pb::ProblemData, rind::Int)\n\nDelete a single constraint from problem `pb`.\n\"\"\"\nfunction delete_constraint!(pb::ProblemData{T}, rind::Int) where{T}\n    # Sanity checks\n    1 <= rind <= pb.ncon || error(\"Invalid row index $rind\")\n\n    # Delete row name and bounds\n    deleteat!(pb.con_names, rind)\n    deleteat!(pb.lcon, rind)\n    deleteat!(pb.ucon, rind)\n\n    # Update columns\n    for (j, col) in enumerate(pb.acols)\n        # Search for row in that column\n        rg = searchsorted(col.nzind, rind)\n        if rg.start > length(col.nzind)\n            # Nothing to do\n            continue\n        else\n            if col.nzind[rg.start] == rind\n                # Delete row from column\n                deleteat!(col.nzind, rg.start)\n                deleteat!(col.nzval, rg.start)\n            end\n\n            # Decrement subsequent row indices\n            col.nzind[rg.start:end] .-= 1\n        end\n    end\n\n    # Delete row\n    deleteat!(pb.arows, rind)\n\n    # Update row counter\n    pb.ncon -= 1\n    return nothing\nend\n\n\"\"\"\n    delete_constraints!(pb::ProblemData, rinds)\n\nDelete rows in collection `rind` from problem `pb`.\n\n# Arguments\n* `pb::ProblemData`\n* `rinds`: collection of row indices to be removed\n\"\"\"\nfunction delete_constraints!(pb::ProblemData{T}, rinds) where{T}\n    # TODO: don't use fallback\n    for i in rinds\n        delete_constraint!(pb, i)\n    end\n    return nothing\nend\n\n\"\"\"\n    delete_variable!(pb, cind)\n\nDelete a single column from problem `pb`.\n\"\"\"\nfunction delete_variable!(pb::ProblemData{T}, cind::Int) where{T}\n    # Sanity checks\n    1 <= cind <= pb.nvar || error(\"Invalid column index $cind\")\n\n    # Delete column name, objective and bounds\n    deleteat!(pb.var_names, cind)\n    deleteat!(pb.obj,  cind)\n    deleteat!(pb.lvar, cind)\n    deleteat!(pb.uvar, cind)\n\n    # Update rows\n    for (i, row) in enumerate(pb.arows)\n        # Search for column in that row\n        rg = searchsorted(row.nzind, cind)\n        if rg.start > length(row.nzind)\n            # Nothing to do\n            continue\n        else\n            if row.nzind[rg.start] == cind\n                # Column appears in row\n                deleteat!(row.nzind, rg.start)\n                deleteat!(row.nzval, rg.start)\n            end\n\n            # Decrement subsequent column indices\n            row.nzind[rg.start:end] .-= 1\n        end\n    end\n\n    # Delete column\n    deleteat!(pb.acols, cind)\n\n    # Update column counter\n    pb.nvar -= 1\n    return nothing\nend\n\n\"\"\"\n    delete_variables!(pb::ProblemData, cinds)\n\nDelete a collection of columns from problem `pb`.\n\n# Arguments\n* `pb::ProblemData`\n* `cinds`: collection of row indices to be removed\n\"\"\"\nfunction delete_variables!(pb::ProblemData{T}, cinds) where{T}\n    # TODO: don't use fallback\n    for j in cinds\n        delete_variable!(pb, j)\n    end\n    return nothing\nend\n\n\"\"\"\n    set_coefficient!(pb, i, j, v)\n\nSet the coefficient `(i, j)` to value `v`.\n\n# Arguments\n* `pb::ProblemData{T}`: the problem whose coefficient\n* `i::Int`: row index\n* `j::Int`: column index\n* `v::T`: coefficient value\n\"\"\"\nfunction set_coefficient!(pb::ProblemData{T}, i::Int, j::Int, v::T) where{T}\n    # Sanity checks\n    1 <= i <= pb.ncon && 1 <= j <= pb.nvar || error(\n        \"Cannot access coeff $((i, j)) in a model of size ($(pb.ncon), $(pb.nvar))\"\n    )\n\n    # Update row and column\n    _set_coefficient!(pb.arows[i], j, v)\n    _set_coefficient!(pb.acols[j], i, v)\n\n    return nothing\nend\n\n\"\"\"\n    _set_coefficient!(roc::RowOrCol{T}, ind::Int, v::T)\n\nSet coefficient to value `v`.\n\"\"\"\nfunction _set_coefficient!(roc::RowOrCol{T}, ind::Int, v::T) where{T}\n    # Check if index already exists\n    k = searchsortedfirst(roc.nzind, ind)\n\n    if (1 <= k <= length(roc.nzind)) && roc.nzind[k] == ind\n        # This coefficient was a non-zero before\n        if iszero(v)\n            deleteat!(roc.nzind, k)\n            deleteat!(roc.nzval, k)\n        else\n            roc.nzval[k] = v\n        end\n    else\n        # Only add coeff if non-zero\n        if !iszero(v)\n            insert!(roc.nzind, k, ind)\n            insert!(roc.nzval, k, v)\n        end\n    end\n\n    return nothing\nend\n\n# =============================\n#     Problem queries\n# =============================\n\n# TODO\n"
  },
  {
    "path": "src/solution.jl",
    "content": "mutable struct Solution{T}\n    m::Int\n    n::Int\n\n    primal_status::SolutionStatus\n    dual_status::SolutionStatus\n    is_primal_ray::Bool\n    is_dual_ray::Bool\n\n    z_primal::T\n    z_dual::T\n\n    x::Vector{T}\n    Ax::Vector{T}\n    y_lower::Vector{T}\n    y_upper::Vector{T}\n    s_lower::Vector{T}\n    s_upper::Vector{T}\n\n    Solution{T}(m, n) where{T} = new{T}(\n        m, n, Sln_Unknown, Sln_Unknown, false, false,\n        zero(T), zero(T),\n        zeros(T, n), zeros(T, m),\n        zeros(T, m), zeros(T, m),\n        zeros(T, n), zeros(T, n)\n    )\nend\n\nimport Base.resize!\n\n\nfunction Base.resize!(sol::Solution, m::Int, n::Int)\n    m >= 0 || throw(ArgumentError(\"m must be >= 0\"))\n    n >= 0 || throw(ArgumentError(\"n must be >= 0\"))\n    \n    sol.m = m\n    sol.n = n\n\n    resize!(sol.x, n)\n    resize!(sol.Ax, m)\n\n    resize!(sol.y_lower, m)\n    resize!(sol.y_upper, m)\n    resize!(sol.s_lower, n)\n    resize!(sol.s_upper, n)\n\n    return sol\nend"
  },
  {
    "path": "src/status.jl",
    "content": "\"\"\"\n    TerminationStatus\n\n- `Success`: No error occured\n- `PrimalInfeasibleNoResult`: Problem is proved to be primal infeasible,\n    but no result (e.g. certificate of infeasibility) is available.\n- `DualInfeasibleNoResult`: Problem is proved to be primal infeasible,\nbut no result (e.g. certificate of infeasibility) is available.\n- `IterationLimit`: Maximum number of iterations reached.\n- `TimeLimit`: Time limit reached.\n- `MemoryLimit`: Memory limit reached.\n- `NumericalProblem`: Numerical problem encountered, e.g. failure of the\n    Cholesky decomposition.\n\"\"\"\n@enum(TerminationStatus,\n    Trm_NotCalled,\n    Trm_Unknown,\n    # OK statuses\n    Trm_Optimal,\n    Trm_PrimalInfeasible,\n    Trm_DualInfeasible,\n    Trm_PrimalDualInfeasible,\n    # Limits\n    Trm_IterationLimit,\n    Trm_TimeLimit,\n    # Errors\n    Trm_MemoryLimit,\n    Trm_NumericalProblem\n)\n\n\"\"\"\n    SolutionStatus\n\nSolution Status code\n- `Sln_Unknown`: Unknown status\n- `Sln_Optimal`: The current solution is optimal.\n- `Sln_FeasiblePoint`: The current solution is feasible.\n- `Sln_InfeasiblePoint`: The current solution is not feasible.\n- `Sln_InfeasibilityCertificate`: The current solution is a certificate of \n    infeasibility. The primal solution is a certificate of dual infeasibility, \n    while the dual solution is a certificate of primal infeasibility.\n\"\"\"\n@enum(SolutionStatus,\n    Sln_Unknown,\n    Sln_Optimal,\n    Sln_FeasiblePoint,\n    Sln_InfeasiblePoint,\n    Sln_InfeasibilityCertificate\n)"
  },
  {
    "path": "src/utils.jl",
    "content": "using CodecBzip2\nusing CodecZlib\n\n\"\"\"\n    _open(f, fname)\n\nOpen a file with decompression stream as required.\n\"\"\"\nfunction _open(f::Function, fname::String)\n\n    ext = Symbol(split(fname, \".\")[end])\n\n    if ext == :gz\n        return Base.open(f, CodecZlib.GzipDecompressorStream, fname, \"r\")\n    elseif ext == :bz2\n        return Base.open(f, CodecBzip2.Bzip2DecompressorStream, fname, \"r\")\n    else\n        return Base.open(f, fname, \"r\")\n    end\nend\n\n# Positive and negative part of a number\npos_part(x::T) where{T} = x >= zero(T) ? x : zero(T)\nneg_part(x::T) where{T} = x >= zero(T) ? zero(T) : -x\n\n\n@inline tones(Tv, n)  = fill!(Tv(undef, n),  one(eltype(Tv)))\n@inline tzeros(Tv, n) = fill!(Tv(undef, n), zero(eltype(Tv)))\n\n\"\"\"\n    Factory{T}\n\nFactory-like struct for passing options to lower-level components.\n\"\"\"\nstruct Factory{T}\n    T::Type{T}\n    options::Base.Iterators.Pairs\n\n    # Constructors\n    Factory(::Type{T}; kwargs...) where{T} = new{T}(T, kwargs)\n    Factory{T}(;kwargs...) where{T} = new{T}(T, kwargs)\nend\n\ninstantiate(f::Factory{T}, args...; kwargs...) where{T} = T(args...; kwargs..., f.options...)\n"
  },
  {
    "path": "test/Core/problemData.jl",
    "content": "function run_tests_pbdata(::Type{T}) where{T}\n\n    @testset \"Creation\" begin\n        pb = TLP.ProblemData{T}(\"test\")\n\n        @test pb.name == \"test\"\n\n        check_problem_size(pb, 0, 0)\n        @test pb.objsense\n        @test iszero(pb.obj0)\n\n        # Add two columns\n        #=\n            min     x1 + 2 x2\n            s.t.    0 ⩽ x1 ⩽ ∞\n                    1 ⩽ x2 ⩽ ∞\n        =#\n        TLP.add_variable!(pb, Int[], T[],     one(T), zero(T), T(Inf), \"x1\")\n        TLP.add_variable!(pb, Int[], T[], 2 * one(T),  one(T), T(Inf), \"x2\")\n\n        check_problem_size(pb, 0, 2)\n        col1, col2 = pb.acols[1], pb.acols[2]\n        @test pb.obj == [one(T), 2*one(T)]\n        @test pb.lvar == [zero(T), one(T)]\n        @test pb.uvar == [T(Inf), T(Inf)]\n        @test length(col1.nzind) == length(col1.nzval) == 0\n        @test length(col2.nzind) == length(col2.nzval) == 0\n        @test pb.var_names == [\"x1\", \"x2\"]\n\n        # Add two constraints\n        #=\n            min     x1 + 2 x2\n            s.t.    -∞ ⩽  -x1 +   x2 ⩽ 1\n                    -1 ⩽ 2 x1 - 2 x2 ⩽ 0\n                    0 ⩽ x1 ⩽ ∞\n                    1 ⩽ x2 ⩽ ∞\n        =#\n        TLP.add_constraint!(pb, [1, 2], T.([-1, 1]), T(-Inf), one(T), \"row1\")\n        TLP.add_constraint!(pb, [1, 2], T.([2, -2]), -one(T), zero(T), \"row2\")\n\n        # Check dimensions\n        check_problem_size(pb, 2, 2)\n\n        # Check coefficients\n        row1, row2 = pb.arows[1], pb.arows[2]\n        @test row1.nzind == [1, 2]\n        @test row1.nzval == T.([-1, 1])\n        @test row2.nzind == [1, 2]\n        @test row2.nzval == T.([2, -2])\n        @test col1.nzind == [1, 2]\n        @test col1.nzval == T.([-1, 2])\n        @test col2.nzind == [1, 2]\n        @test col2.nzval == T.([1, -2])\n\n        # Check row bounds\n        @test pb.lcon == [T(-Inf), -one(T)]\n        @test pb.ucon == [one(T), zero(T)]\n        # Check names\n        @test pb.con_names == [\"row1\", \"row2\"]\n        @test pb.var_names == [\"x1\", \"x2\"]\n\n        empty!(pb)\n        @test pb.name == \"\"\n        @test iszero(pb.obj0)\n        check_problem_size(pb, 0, 0)\n    end\n\n    @testset \"Delete\" begin\n        pb = TLP.ProblemData{T}(\"test\")\n        #=\n            min     x1 + 2 x2 + 3 x3\n            s.t.    1 ⩽ 1 * x1 ⩽ 10\n                    2 ⩽ 2 * x2 ⩽ 20\n                    3 ⩽ 3 * x3 ⩽ 30\n\n                    11 ⩽ x1 ⩽ 110\n                    22 ⩽ x2 ⩽ 220\n                    33 ⩽ x3 ⩽ 330\n        =#\n\n        TLP.add_variable!(pb, Int[], T[],     one(T), 11 * one(T), 110 * one(T), \"x1\")\n        TLP.add_variable!(pb, Int[], T[], 2 * one(T), 22 * one(T), 220 * one(T), \"x2\")\n        TLP.add_variable!(pb, Int[], T[], 3 * one(T), 33 * one(T), 330 * one(T), \"x3\")\n\n        TLP.add_constraint!(pb, [1], T.([1]), 1 * one(T), 10 * one(T), \"row1\")\n        TLP.add_constraint!(pb, [2], T.([2]), 2 * one(T), 20 * one(T), \"row2\")\n        TLP.add_constraint!(pb, [3], T.([3]), 3 * one(T), 30 * one(T), \"row3\")\n\n        # Delete row 1 and check remaining problem\n        TLP.delete_constraint!(pb, 1)\n\n        @test pb.ncon == 2\n        @test pb.nvar == 3\n        row2, row3 = pb.arows\n        @test pb.con_names == [\"row2\", \"row3\"]\n        @test pb.lcon == T.([2, 3])\n        @test pb.ucon == T.([20, 30])\n\n        @test row2.nzind == [2]\n        @test row2.nzval == [T(2)]\n\n        @test row3.nzind == [3]\n        @test row3.nzval == [T(3)]\n\n        # Delete variable 2\n        TLP.delete_variable!(pb, 2)\n        @test pb.ncon == 2\n        @test pb.nvar == 2\n        col1, col3 = pb.acols\n        @test pb.var_names == [\"x1\", \"x3\"]\n        @test pb.lvar == T.([11, 33])\n        @test pb.uvar == T.([110, 330])\n\n        @test col1.nzind == []\n        @test col1.nzval == T[]\n\n        @test col3.nzind == [2]\n        @test col3.nzval == [T(3)]\n    end\n\n    return nothing\nend\n\nfunction check_problem_size(pb::TLP.ProblemData, ncon::Int, nvar::Int)\n    @test pb.ncon == ncon\n    @test pb.nvar == nvar\n\n    @test length(pb.obj) == nvar\n\n    @test length(pb.arows) == ncon\n    @test length(pb.acols) == nvar\n\n    @test length(pb.lcon) == ncon\n    @test length(pb.ucon) == ncon\n    @test length(pb.lvar) == nvar\n    @test length(pb.uvar) == nvar\n\n    @test length(pb.con_names) == ncon\n    @test length(pb.var_names) == nvar\n    return nothing\nend\n\nfunction test_pbdata_checkcoeff(::Type{T}) where{T}\n\n    @testset \"Zero in row\" begin\n        pb = TLP.ProblemData{T}(\"test\")\n\n        Tulip.add_variable!(pb, Int[], T[], T(1), zero(T), one(T), \"x1\")\n        Tulip.add_variable!(pb, Int[], T[], T(2), zero(T), one(T), \"x2\")\n        Tulip.add_variable!(pb, Int[], T[], T(3), zero(T), one(T), \"x3\")\n        Tulip.add_constraint!(pb, [1, 2, 3], T[1, 0, 0], zero(T), one(T), \"c1\")\n        Tulip.add_constraint!(pb, [1, 2, 3], T[0, 1, 2], zero(T), one(T), \"c2\")\n\n        @test length(pb.arows) == 2\n        @test pb.arows[1].nzind == [1]\n        @test pb.arows[1].nzval == T[1]\n        @test pb.arows[2].nzind == [2, 3]\n        @test pb.arows[2].nzval == T[1, 2]\n\n        @test length(pb.acols) == 3\n        @test pb.acols[1].nzind == [1]\n        @test pb.acols[1].nzval == T[1]\n        @test pb.acols[2].nzind == [2]\n        @test pb.acols[2].nzval == T[1]\n        @test pb.acols[3].nzind == [2]\n        @test pb.acols[3].nzval == T[2]\n    end\n\n    @testset \"Zero in col\" begin\n        pb = TLP.ProblemData{T}(\"test\")\n\n        Tulip.add_constraint!(pb, Int[], T[], zero(T), one(T), \"c1\")\n        Tulip.add_constraint!(pb, Int[], T[], zero(T), one(T), \"c2\")\n        Tulip.add_variable!(pb, [1, 2], T[1, 0], T(1), zero(T), one(T), \"x1\")\n        Tulip.add_variable!(pb, [1, 2], T[0, 1], T(2), zero(T), one(T), \"x2\")\n        Tulip.add_variable!(pb, [1, 2], T[0, 2], T(3), zero(T), one(T), \"x3\")\n\n        @test length(pb.arows) == 2\n        @test pb.arows[1].nzind == [1]\n        @test pb.arows[1].nzval == T[1]\n        @test pb.arows[2].nzind == [2, 3]\n        @test pb.arows[2].nzval == T[1, 2]\n\n        @test length(pb.acols) == 3\n        @test pb.acols[1].nzind == [1]\n        @test pb.acols[1].nzval == T[1]\n        @test pb.acols[2].nzind == [2]\n        @test pb.acols[2].nzval == T[1]\n        @test pb.acols[3].nzind == [2]\n        @test pb.acols[3].nzval == T[2]\n    end\nend\n\n@testset \"ProblemData\" begin\n    for T in TvTYPES\n        @testset \"$T\" begin\n            run_tests_pbdata(T)\n            test_pbdata_checkcoeff(T)\n        end\n    end\nend\n"
  },
  {
    "path": "test/IPM/HSD.jl",
    "content": "function run_tests_hsd(T::Type)\n\n    Tv = Vector{T}\n\n    params = TLP.IPMOptions{T}()\n    kkt_options = TLP.KKTOptions{T}()\n\n    @testset \"step length\" begin\n        m, n, p = 2, 2, 1\n        pt = TLP.Point{T, Tv}(m, n, p, hflag=true)\n        pt.x  .= one(T)\n        pt.xl .= one(T)\n        pt.xu .= one(T)\n        pt.y  .= zero(T)\n        pt.zl .= zero(T)\n        pt.zu .= zero(T)\n        pt.τ   = one(T)\n        pt.κ   = one(T)\n        pt.μ   = one(T)\n\n        d = TLP.Point{T, Tv}(m, n, p, hflag=true)\n        d.x  .= one(T)\n        d.xl .= one(T)\n        d.xu .= one(T)\n        d.y  .= zero(T)\n        d.zl .= zero(T)\n        d.zu .= zero(T)\n        d.τ   = one(T)\n        d.κ   = one(T)\n        d.μ   = one(T)\n\n        # Max step length for a single (x, d)\n        @inferred TLP.max_step_length(pt.x, d.x)\n        @test TLP.max_step_length(ones(T, 1), ones(T, 1)) == T(Inf)\n        @test TLP.max_step_length(ones(T, 1), -ones(T, 1)) ≈ one(T)\n        @test TLP.max_step_length(zeros(T, 1), -ones(T, 1)) ≈ zero(T)\n        @test TLP.max_step_length(zeros(T, 1), ones(T, 1)) == T(Inf)\n\n        # Max step length for the whole primal-dual point\n        @inferred TLP.max_step_length(pt, d)\n        @test TLP.max_step_length(pt, d) ≈ one(T)\n    end\n\n    # Simple example:\n    #=\n        min     x1 - x2\n        s.τ.    x1 + x2 = 1\n                x1 - x2 = 0\n                0 <= x1 <= 2\n                0 <= x2 <= 2\n    =#\n    m, n = 2, 2\n    p = 2 * n\n    A = Matrix{T}([\n        [1    1];\n        [1   -1]\n    ])\n    b = Vector{T}([1,  0])\n    c = Vector{T}([1, -1])\n    c0 = zero(T)\n    l = Vector{T}([0, 0])\n    u = Vector{T}([2, 2])\n    dat = Tulip.IPMData(A, b, true, c, c0, l, u)\n\n    hsd = TLP.HSD(dat, kkt_options)\n\n    # Primal-dual optimal solution\n    # x1 = x2 = 0.5; xl = 0.5; xu = 1.5; τ = 1\n    # y1 = 0, y2 = 1; zl = zu = 0; κ = 0\n    hsd.pt.x  .= T.([1 // 2, 1 // 2])\n    hsd.pt.xl .= T.([1 // 2, 1 // 2])\n    hsd.pt.xu .= T.([3 // 2, 3 // 2])\n    hsd.pt.y  .= T.([0, 1])\n    hsd.pt.zl .= T.([0, 0])\n    hsd.pt.zu .= T.([0, 0])\n\n    hsd.pt.τ  = 1\n    hsd.pt.κ  = 0\n    hsd.pt.μ  = 0\n\n    ϵ = sqrt(eps(T))\n    TLP.compute_residuals!(hsd)\n\n    @testset \"Convergence\" begin\n        hsd.solver_status = TLP.Trm_Unknown\n        TLP.update_solver_status!(hsd, ϵ, ϵ, ϵ, ϵ)\n        @test hsd.solver_status == TLP.Trm_Optimal\n\n        # TODO: dual infeasible\n\n        # TODO: primal infeasible\n\n        # TODO: ill-posed\n\n    end\nend\n\nfunction test_hsd_residuals(T::Type)\n    # Simple example:\n    #=\n        min     x1 - x2\n        s.τ.    x1 + x2 = 1\n                x1 - x2 = 0\n                0 <= x1 <= 2\n                0 <= x2 <= 2\n    =#\n    kkt_options = TLP.KKTOptions{T}()\n    A = Matrix{T}([\n        [1    1];\n        [1   -1]\n    ])\n    b = Vector{T}([1,  0])\n    c = Vector{T}([1, -1])\n    c0 = zero(T)\n    l = Vector{T}([0, 0])\n    u = Vector{T}([2, 2])\n    dat = Tulip.IPMData(A, b, true, c, c0, l, u)\n\n    hsd = TLP.HSD(dat, kkt_options)\n    pt = hsd.pt\n    res = hsd.res\n\n    # Primal-dual solution\n    x  = pt.x  .= T[3, 5]\n    xl = pt.xl .= T[1, 8]\n    xu = pt.xu .= T[2, 1]\n    y  = pt.y  .= T[10, -2]\n    zl = pt.zl .= T[2, 1]\n    zu = pt.zu .= T[5, 7]\n    τ  = pt.τ  = T(1//2)\n    κ  = pt.κ  = T(1//10)\n    μ  = pt.μ  = 0\n\n    TLP.compute_residuals!(hsd)\n\n    @test res.rp ≈ (τ .* b) - A * x\n    @test res.rl ≈ (τ .* l) - (x - xl)\n    @test res.ru ≈ (τ .* u) - (x + xu)\n    @test res.rd ≈ (τ .* c) - A' * y - zl + zu\n    @test res.rg ≈ c'x - (b'y + l'zl - u'zu) + κ\n\n    @test res.rp_nrm == norm(res.rp, Inf)\n    @test res.rl_nrm == norm(res.rl, Inf)\n    @test res.ru_nrm == norm(res.ru, Inf)\n    @test res.rd_nrm == norm(res.rd, Inf)\n    @test res.rg_nrm == norm(res.rg, Inf)\n\n    return nothing\nend\n\n@testset \"HSD\" begin\n    @testset \"$T\" for T in TvTYPES\n        run_tests_hsd(T)\n        test_hsd_residuals(T)\n    end\nend\n"
  },
  {
    "path": "test/IPM/MPC.jl",
    "content": "function run_tests_mpc(T::Type)\n\n    Tv = Vector{T}\n\n    params = TLP.IPMOptions{T}()\n    kkt_options = TLP.KKTOptions{T}()\n\n    @testset \"step length\" begin\n        m, n, p = 2, 2, 1\n        pt = TLP.Point{T, Tv}(m, n, p, hflag=false)\n        pt.x  .= one(T)\n        pt.xl .= one(T)\n        pt.xu .= one(T)\n        pt.y  .= zero(T)\n        pt.zl .= zero(T)\n        pt.zu .= zero(T)\n        pt.τ   = one(T)\n        pt.κ   = one(T)\n        pt.μ   = one(T)\n\n        d = TLP.Point{T, Tv}(m, n, p, hflag=false)\n        d.x  .= one(T)\n        d.xl .= one(T)\n        d.xu .= one(T)\n        d.y  .= zero(T)\n        d.zl .= zero(T)\n        d.zu .= zero(T)\n        d.τ   = one(T)\n        d.κ   = one(T)\n        d.μ   = one(T)\n\n        # Max step length for a single (x, d)\n        @inferred TLP.max_step_length(pt.x, d.x)\n        @test TLP.max_step_length(ones(T, 1), ones(T, 1)) == T(Inf)\n        @test TLP.max_step_length(ones(T, 1), -ones(T, 1)) ≈ one(T)\n        @test TLP.max_step_length(zeros(T, 1), -ones(T, 1)) ≈ zero(T)\n        @test TLP.max_step_length(zeros(T, 1), ones(T, 1)) == T(Inf)\n\n        # Max step length for the whole primal-dual point\n        @inferred TLP.max_step_length(pt, d)\n        @test TLP.max_step_length(pt, d) ≈ one(T)\n    end\n\n    # Simple example:\n    #=\n        min     x1 - x2\n        s.τ.    x1 + x2 = 1\n                x1 - x2 = 0\n                0 <= x1 <= 2\n                0 <= x2 <= 2\n    =#\n    m, n = 2, 2\n    p = 2 * n\n    A = Matrix{T}([\n        [1    1];\n        [1   -1]\n    ])\n    b = Vector{T}([1,  0])\n    c = Vector{T}([1, -1])\n    c0 = zero(T)\n    l = Vector{T}([0, 0])\n    u = Vector{T}([2, 2])\n    dat = Tulip.IPMData(A, b, true, c, c0, l, u)\n\n    ipm = TLP.MPC(dat, kkt_options)\n\n    # Primal-dual optimal solution\n    # x1 = x2 = 0.5; xl = 0.5; xu = 1.5; τ = 1\n    # y1 = 0, y2 = 1; zl = zu = 0; κ = 0\n    ipm.pt.x  .= T.([1 // 2, 1 // 2])\n    ipm.pt.xl .= T.([1 // 2, 1 // 2])\n    ipm.pt.xu .= T.([3 // 2, 3 // 2])\n    ipm.pt.y  .= T.([0, 1])\n    ipm.pt.zl .= T.([0, 0])\n    ipm.pt.zu .= T.([0, 0])\n\n    ipm.pt.τ  = 1\n    ipm.pt.κ  = 0\n    ipm.pt.μ  = 0\n\n    ϵ = sqrt(eps(T))\n    TLP.compute_residuals!(ipm)\n\n    @testset \"Convergence\" begin\n        ipm.solver_status = TLP.Trm_Unknown\n        TLP.update_solver_status!(ipm, ϵ, ϵ, ϵ, ϵ)\n        @test ipm.solver_status == TLP.Trm_Optimal\n\n        # TODO: dual infeasible\n\n        # TODO: primal infeasible\n\n        # TODO: ill-posed\n\n    end\nend\n\nfunction test_mpc_residuals(T::Type)\n    # Simple example:\n    #=\n        min     x1 - x2\n        s.τ.    x1 + x2 = 1\n                x1 - x2 = 0\n                0 <= x1 <= 2\n                0 <= x2 <= 2\n    =#\n    kkt_options = TLP.KKTOptions{T}()\n    A = Matrix{T}([\n        [1    1];\n        [1   -1]\n    ])\n    b = Vector{T}([1,  0])\n    c = Vector{T}([1, -1])\n    c0 = zero(T)\n    l = Vector{T}([0, 0])\n    u = Vector{T}([2, 2])\n    dat = Tulip.IPMData(A, b, true, c, c0, l, u)\n\n    ipm = TLP.MPC(dat, kkt_options)\n    pt = ipm.pt\n    res = ipm.res\n\n    # Primal-dual solution\n    x  = pt.x  .= T[3, 5]\n    xl = pt.xl .= T[1, 8]\n    xu = pt.xu .= T[2, 1]\n    y  = pt.y  .= T[10, -2]\n    zl = pt.zl .= T[2, 1]\n    zu = pt.zu .= T[5, 7]\n    pt.τ  = 1\n    pt.κ  = 0\n    pt.μ  = 0\n\n    TLP.compute_residuals!(ipm)\n\n    @test res.rp ≈ b - A*x\n    @test res.rl ≈ l - (x - xl)\n    @test res.ru ≈ u - (x + xu)\n    @test res.rd ≈ c - A' * y - zl + zu\n    @test res.rg == 0\n\n    @test res.rp_nrm == norm(res.rp, Inf)\n    @test res.rl_nrm == norm(res.rl, Inf)\n    @test res.ru_nrm == norm(res.ru, Inf)\n    @test res.rd_nrm == norm(res.rd, Inf)\n    @test res.rg_nrm == norm(res.rg, Inf)\n\n    return nothing\nend\n\n@testset \"MPC\" begin\n    @testset \"$T\" for T in TvTYPES\n        run_tests_mpc(T)\n        test_mpc_residuals(T)\n    end\nend\n"
  },
  {
    "path": "test/Interfaces/MOI_wrapper.jl",
    "content": "#  Copyright 2018-2019: Mathieu Tanneau\n#  This Source Code Form is subject to the terms of the Mozilla Public\n#  License, v. 2.0. If a copy of the MPL was not distributed with this\n#  file, You can obtain one at https://mozilla.org/MPL/2.0/.\n\nusing Test\n\nimport MathOptInterface as MOI\nimport Tulip\n\n@testset \"Direct optimizer\" begin\n    model = Tulip.Optimizer()\n    MOI.set(model, MOI.Silent(), true)\n    MOI.Test.runtests(\n        model,\n        MOI.Test.Config(\n            Float64;\n            atol = 1e-6,\n            rtol = 1e-6,\n            exclude = Any[MOI.ConstraintBasisStatus, MOI.VariableBasisStatus],\n        ),\n    )\nend\n\n@testset \"MOI Bridged\" begin\n    model = MOI.Bridges.full_bridge_optimizer(Tulip.Optimizer(), Float64)\n    MOI.set(model, MOI.Silent(), true)\n    MOI.Test.runtests(\n        model,\n        MOI.Test.Config(\n            Float64;\n            atol = 1e-6,\n            rtol = 1e-6,\n            exclude = Any[MOI.ConstraintBasisStatus, MOI.VariableBasisStatus],\n        ),\n        exclude=[\n            r\"^test_conic_NormInfinityCone_INFEASIBLE$\",\n            r\"^test_conic_NormOneCone_INFEASIBLE$\",\n        ],\n    )\nend\n\n# Run the MOI tests with HSD and MPC algorithms\n@testset \"MOI Linear tests - $ipm\" for ipm in [Tulip.HSD, Tulip.MPC]\n    model = Tulip.Optimizer()\n    model.inner.params.IPM.Factory = Tulip.Factory(ipm)\n    MOI.set(model, MOI.Silent(), true)\n    MOI.Test.runtests(\n        model,\n        MOI.Test.Config(\n            Float64;\n            atol = 1e-6,\n            rtol = 1e-6,\n            exclude = Any[MOI.ConstraintBasisStatus, MOI.VariableBasisStatus],\n        ),\n        include=[\"linear\"],\n    )\nend\n\n@testset \"Cached optimizer\" begin\n    inner = MOI.Utilities.CachingOptimizer(\n        MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()),\n        Tulip.Optimizer(),\n    )\n    model = MOI.Bridges.full_bridge_optimizer(inner, Float64)\n    MOI.set(model, MOI.Silent(), true)\n    MOI.Test.runtests(\n        model,\n        MOI.Test.Config(\n            Float64;\n            atol = 1e-6,\n            rtol = 1e-6,\n            exclude = Any[MOI.ConstraintBasisStatus, MOI.VariableBasisStatus],\n        ),\n        exclude=[\n            r\"^test_conic_NormInfinityCone_INFEASIBLE$\",\n            r\"^test_conic_NormOneCone_INFEASIBLE$\",\n        ],\n    )\nend\n\n@testset \"test_attribute_TimeLimitSec\" begin\n    model = Tulip.Optimizer()\n    @test MOI.supports(model, MOI.TimeLimitSec())\n    @test MOI.get(model, MOI.TimeLimitSec()) === nothing\n    MOI.set(model, MOI.TimeLimitSec(), 0.0)\n    @test MOI.get(model, MOI.TimeLimitSec()) == 0.0\n    MOI.set(model, MOI.TimeLimitSec(), nothing)\n    @test MOI.get(model, MOI.TimeLimitSec()) === nothing\n    MOI.set(model, MOI.TimeLimitSec(), 1.0)\n    @test MOI.get(model, MOI.TimeLimitSec()) == 1.0\nend\n"
  },
  {
    "path": "test/Interfaces/julia_api.jl",
    "content": "import Tulip\nusing Test\n\nfunction test_reader()\n    lp = Tulip.Model{Float64}()\n\n    Tulip.load_problem!(lp, joinpath(@__DIR__, \"lp.mps\"))\n    check_data(lp)\n\n    Tulip.load_problem!(lp, joinpath(@__DIR__, \"lp.mps.gz\"))\n    check_data(lp)\n\n    Tulip.load_problem!(lp, joinpath(@__DIR__, \"lp.mps.bz2\"))\n    check_data(lp)\n\nend\n\nfunction check_data(lp)\n\n    pb = lp.pbdata\n\n    @test pb.name == \"LP1\"\n\n    @test pb.ncon == 2\n    @test pb.nvar == 2\n\n    @test pb.objsense\n    @test pb.obj0 == 0.0\n    @test pb.obj == [1.0, 2.0]\n\n    @test pb.lvar == [0.0, 0.0]\n    @test pb.uvar == [1.0, 1.0]\n    @test pb.lcon == [1.0, 0.0]\n    @test pb.ucon == [1.0, 0.0]\n\n    @test pb.con_names == [\"ROW1\", \"ROW2\"]\n    @test pb.var_names == [\"X1\", \"X2\"]\n\n    col1, col2 = pb.acols[1], pb.acols[2]\n    @test col1.nzind == [1, 2]\n    @test col1.nzval == [1.0, 1.0]\n    @test col2.nzind == [1, 2]\n    @test col2.nzval == [1.0, -1.0]\n\n    row1, row2 = pb.arows[1], pb.arows[2]\n    @test row1.nzind == [1, 2]\n    @test row1.nzval == [1.0, 1.0]\n    @test row2.nzind == [1, 2]\n    @test row2.nzval == [1.0, -1.0]\n\n\n\nend\n\n@testset \"Reader\" begin\n    test_reader()\nend\n"
  },
  {
    "path": "test/Interfaces/lp.mps",
    "content": "NAME          LP1                                                             \n\n*   Problem:\n*   min     x1 + 2*x2\n*   s.t.    x1 +   x2 = 1\n*           x1 -   x2 = 0\n*           0 <= x1, x2, <= 1\n\nROWS     \n E  ROW1\n E  ROW2\n N  COST\nCOLUMNS\n    X1        ROW1                1.   ROW2                1.\n    X1        COST                1.\n    X2        ROW1                1.   ROW2               -1.\n    X2        COST                2.\nRHS\n    B         ROW1                1.   ROW2                0.\nBOUNDS\n UP BND1      X1                  1.\n UP BND1      X2                  1.\nENDATA"
  },
  {
    "path": "test/KKT/Cholmod/cholmod.jl",
    "content": "@testset \"CHOLMOD\" begin\n\n    A = SparseMatrixCSC{Float64, Int}([\n        1 0 1 0;\n        0 1 0 1\n    ])\n\n    @testset \"LDL\" begin\n        kkt = KKT.setup(A, KKT.K2(), KKT.TlpCholmod.Backend())\n        KKT.run_ls_tests(A, kkt)\n    end\n\n    @testset \"Cholesky\" begin\n        kkt = KKT.setup(A, KKT.K1(), KKT.TlpCholmod.Backend())\n        KKT.run_ls_tests(A, kkt)\n    end\nend\n"
  },
  {
    "path": "test/KKT/Dense/lapack.jl",
    "content": "@testset \"LAPACK\" begin\n    for T in TvTYPES\n        @testset \"$T\" begin\n            A = Matrix{T}([\n                1 0 1 0;\n                0 1 0 1\n            ])\n            kkt = KKT.setup(A, KKT.K1(), KKT.TlpDense.Backend())\n            KKT.run_ls_tests(A, kkt)\n        end\n    end\nend\n"
  },
  {
    "path": "test/KKT/KKT.jl",
    "content": "const KKT = Tulip.KKT\n\ninclude(\"Dense/lapack.jl\")\ninclude(\"Cholmod/cholmod.jl\")\ninclude(\"LDLFactorizations/ldlfact.jl\")\ninclude(\"Krylov/krylov.jl\")\n"
  },
  {
    "path": "test/KKT/Krylov/krylov.jl",
    "content": "using Krylov\n\n@testset \"Krylov\" begin\n    include(\"spd.jl\")\n    include(\"sid.jl\")\n    include(\"sqd.jl\")\nend\n"
  },
  {
    "path": "test/KKT/Krylov/sid.jl",
    "content": "function test_krylov_sid(T, ksolver)\n    A = SparseMatrixCSC{T, Int}([\n        1 0 1 0;\n        0 1 0 1\n    ])\n\n    kkt = KKT.setup(A, KKT.K2(), KKT.TlpKrylov.Backend(ksolver, Vector{T}))\n    KKT.run_ls_tests(A, kkt)\n\n    return nothing\nend\n\n@testset \"SID\" begin\n    for T in TvTYPES, ksolver in [MinresWorkspace, MinaresWorkspace, MinresQlpWorkspace, SymmlqWorkspace]\n        @testset \"$ksolver ($T)\" begin\n            test_krylov_sid(T, ksolver)\n        end\n    end\nend\n"
  },
  {
    "path": "test/KKT/Krylov/spd.jl",
    "content": "function test_krylov_spd(T, ksolver)\n    A = SparseMatrixCSC{T, Int}([\n        1 0 1 0;\n        0 1 0 1\n    ])\n\n    kkt = KKT.setup(A, KKT.K1(), KKT.TlpKrylov.Backend(ksolver, Vector{T}))\n    KKT.run_ls_tests(A, kkt)\n\n    return nothing\nend\n\n@testset \"SPD\" begin\n    for T in TvTYPES, ksolver in [CgWorkspace, CarWorkspace]\n        @testset \"$ksolver ($T)\" begin\n            test_krylov_spd(T, ksolver)\n        end\n    end\nend\n"
  },
  {
    "path": "test/KKT/Krylov/sqd.jl",
    "content": "function test_krylov_sqd(T, ksolver)\n    A = SparseMatrixCSC{T, Int}([\n        1 0 1 0;\n        0 1 0 1\n    ])\n\n    kkt = KKT.setup(A, KKT.K2(), KKT.TlpKrylov.Backend(ksolver, Vector{T}))\n    KKT.run_ls_tests(A, kkt)\n\n    return nothing\nend\n\n@testset \"SQD\" begin\n    for T in TvTYPES, ksolver in [TricgWorkspace, TrimrWorkspace]\n        @testset \"$ksolver ($T)\" begin\n            test_krylov_sqd(T, ksolver)\n        end\n    end\nend\n"
  },
  {
    "path": "test/KKT/LDLFactorizations/ldlfact.jl",
    "content": "@testset \"LDLFact\" begin\n    for T in TvTYPES\n        @testset \"$T\" begin\n            A = SparseMatrixCSC{T, Int}([\n                1 0 1 0;\n                0 1 0 1\n            ])\n            kkt = KKT.setup(A, KKT.K2(), KKT.TlpLDLFact.Backend())\n            KKT.run_ls_tests(A, kkt)\n        end\n    end\nend\n"
  },
  {
    "path": "test/Presolve/empty_column.jl",
    "content": "function emtpy_column_tests(T::Type)\n\n    # We test all the following combinations:\n    #=\n        min          c * x\n        s.t.    lb ≤     x ≤ ub\n\n\n        ------------------------------\n                    |        c       \n        (lb, ub)    | +1  | -1  |  0\n        ------------------------------\n        (-∞,  u)    | -∞  |  u  |  u \n        (-∞, +∞)    | -∞  | +∞  |  0 \n        ( l,  u)    |  l  |  u  |  l \n        ( l, +∞)    |  l  | +∞  |  l \n        ------------------------------\n    =#\n    function build_problem(l, u, c)\n        pb = Tulip.ProblemData{T}()\n\n        Tulip.load_problem!(pb, \"Test\", \n            true, [c], zero(T),\n            spzeros(T, 0, 1), T[], T[], [l], [u], String[], [\"x\"]\n        )\n        return pb\n    end\n\n    L = T.([-Inf,  -1])\n    U = T.([   1, Inf])\n    C = T.([-1, 0, 1])\n    for l in L, u in U, c in C\n        @testset \"$((l, u, c))\" begin\n            pb = build_problem(l, u, c)\n\n            ps = Tulip.PresolveData(pb)\n\n            # Remove empty variable\n            Tulip.remove_empty_column!(ps, 1)\n\n            if c > 0 && !isfinite(l)\n                @test ps.status == Tulip.Trm_DualInfeasible\n                @test ps.colflag[1]\n                @test ps.ncol == 1\n\n                sol = ps.solution\n                @test sol.primal_status == Tulip.Sln_InfeasibilityCertificate\n                @test sol.m == 0 && sol.n == 1\n                @test sol.x[1] < 0\n            elseif c < 0 && !isfinite(u)\n                @test ps.status == Tulip.Trm_DualInfeasible\n                @test ps.colflag[1]\n                @test ps.ncol == 1\n\n                sol = ps.solution\n                @test sol.primal_status == Tulip.Sln_InfeasibilityCertificate\n                @test sol.m == 0 && sol.n == 1\n                @test sol.x[1] > 0\n            else\n                @test ps.status == Tulip.Trm_Unknown\n                @test !ps.colflag[1]\n                @test ps.ncol == 0\n                @test ps.updated\n\n                # Check that operation was recorded correctly\n                @test length(ps.ops) == 1\n                op = ps.ops[1]\n                @test isa(op, Tulip.EmptyColumn)\n                @test op.j == 1\n            end \n        end  # testset\n    end  # loop\n\n    return\nend\n\n@testset \"Empty column\" begin\n    for T in TvTYPES\n        @testset \"$T\" begin emtpy_column_tests(T) end\n    end\nend"
  },
  {
    "path": "test/Presolve/empty_row.jl",
    "content": "function empty_row_tests(T::Type)\n\n    # Build the following model\n    #=\n        min     x + y\n        s.t.   -1 ⩽ 0 * x + 0 * y + 0 * z ⩽ 1\n                1 ⩽ 0 * x + 0 * y + 0 * z ⩽ 2\n    =#\n    pb = Tulip.ProblemData{T}()\n\n    m, n = 2, 3\n    A = spzeros(T, m, n)\n\n    b = ones(T, m)\n    c = ones(T, n)\n\n    Tulip.load_problem!(pb, \"test\",\n        true, c, zero(T),\n        A, T.([-1, 1]), T.([1, 2]), zeros(T, n), fill(T(Inf), n),\n        [\"c1\", \"c2\"], [\"x\", \"y\", \"z\"]\n    )\n\n    ps = Tulip.PresolveData(pb)\n\n    @test !ps.updated\n    @test ps.nzrow[1] == ps.nzrow[2] == 0\n\n    # Remove first empty row\n    Tulip.remove_empty_row!(ps, 1)\n\n    @test ps.updated\n    @test ps.status == Tulip.Trm_Unknown\n    @test ps.nrow == 1\n    @test !ps.rowflag[1] && ps.rowflag[2]\n    @test length(ps.ops) == 1\n\n    op = ps.ops[1]\n    @test isa(op, Tulip.EmptyRow{T})\n    @test op.i == 1\n    @test iszero(op.y)\n\n    # Remove second empty row\n    # This should detect infeasibility\n    Tulip.remove_empty_row!(ps, 2)\n\n    @test ps.status == Tulip.Trm_PrimalInfeasible\n    @test ps.nrow == 1\n    @test !ps.rowflag[1] && ps.rowflag[2]\n    @test length(ps.ops) == 1\n\n    # Check solution status & objective value\n    sol = ps.solution\n    @test sol.dual_status == Tulip.Sln_InfeasibilityCertificate\n    @test sol.z_primal == sol.z_dual == T(Inf)\n\n    # Check Farkas ray\n    #   (current problem only has 1 row)\n    @test sol.y_lower[1] >  zero(T)\n\n    return\nend\n\n\nfunction test_empty_row_1(T::Type)\n    # Empty row with l > 0\n    #=\n        min     x\n        s.t.   1 ⩽ 0 * x ⩽ 2\n        x >= 0\n    =#\n    pb = Tulip.ProblemData{T}()\n\n    m, n = 1, 1\n    A = spzeros(T, m, n)\n    c = ones(T, n)\n\n    Tulip.load_problem!(pb, \"test\",\n        true, c, zero(T),\n        A, T.([1]), T.([2]), zeros(T, n), fill(T(Inf), n),\n        [\"c1\"], [\"x\"]\n    )\n\n    ps = Tulip.PresolveData(pb)\n    Tulip.remove_empty_row!(ps, 1)\n\n    @test ps.status == Tulip.Trm_PrimalInfeasible\n    @test ps.nrow == 1\n    @test ps.rowflag[1]\n    @test length(ps.ops) == 0\n\n    # Check solution status & objective value\n    sol = ps.solution\n    @test sol.dual_status == Tulip.Sln_InfeasibilityCertificate\n    @test sol.z_primal == sol.z_dual == T(Inf)\n\n    # Check Farkas ray\n    #   (current problem only has 1 row)\n    @test sol.y_lower[1] >  zero(T)\n\n    return nothing\nend\n\nfunction test_empty_row_2(T::Type)\n    # Empty row with u < 0\n    #=\n        min     x\n        s.t.   -2 ⩽ 0 * x ⩽ -1\n        x >= 0\n    =#\n    pb = Tulip.ProblemData{T}()\n\n    m, n = 1, 1\n    A = spzeros(T, m, n)\n    c = ones(T, n)\n\n    Tulip.load_problem!(pb, \"test\",\n        true, c, zero(T),\n        A, T.([-2]), T.([-1]), zeros(T, n), fill(T(Inf), n),\n        [\"c1\"], [\"x\"]\n    )\n\n    ps = Tulip.PresolveData(pb)\n    Tulip.remove_empty_row!(ps, 1)\n\n    @test ps.status == Tulip.Trm_PrimalInfeasible\n    @test ps.nrow == 1\n    @test ps.rowflag[1]\n    @test length(ps.ops) == 0\n\n    # Check solution status & objective value\n    sol = ps.solution\n    @test sol.dual_status == Tulip.Sln_InfeasibilityCertificate\n    @test sol.z_primal == sol.z_dual == T(Inf)\n\n    # Check Farkas ray\n    #   (current problem only has 1 row)\n    @test sol.y_upper[1] >  zero(T)\n\n    return nothing\nend\n\nfunction test_empty_row_tolerances(T::Type)\n    # Adapted from https://github.com/ds4dm/Tulip.jl/issues/98\n    #=\n        min     x + y + z\n        s.t.    x + y + z == 1\n                x == ¹/₃\n                y == ¹/₃\n                z == ¹/₃\n                x, y, z, ≥ 0\n\n        In the absence of numerical tolerances, x, y, and z get eliminated,\n        but rouding errors cause the first constraint to be 0 == ϵ ≈ 1e-16,\n        thereby rendering the problem infeasible.\n    =#\n    pb = Tulip.ProblemData{T}()\n\n    m, n = 4, 3\n    A = sparse(\n        [1, 1, 1, 2, 3, 4],\n        [1, 2, 3, 1, 2, 3],\n        T[1, 1, 1, 1, 1, 1],\n        m, n\n    )\n    c = ones(T, n)\n\n    Tulip.load_problem!(pb, \"test\",\n        true, c, zero(T),\n        A, T[1, 1//3, 1//3, 1//3], T[1, 1//3, 1//3, 1//3],\n        zeros(T, n), fill(T(Inf), n),\n        [\"row1\", \"row2\", \"row3\", \"row4\"], [\"x\", \"y\", \"z\"]\n    )\n\n    ps = Tulip.PresolveData(pb)\n    Tulip.presolve!(ps)\n\n    @test ps.status == Tulip.Trm_Optimal\n    @test ps.nrow == 0\n    @test ps.ncol == 0\n\n    # Check solution status & objective value\n    sol = ps.solution\n    @test sol.primal_status == Tulip.Sln_Optimal\n    @test sol.dual_status == Tulip.Sln_Optimal\n    @test sol.z_primal ≈ 1\n    @test sol.z_dual   ≈ 1\n\n    return nothing\nend\n\n@testset \"Empty row\" begin\n    for T in TvTYPES\n        @testset \"$T\" begin\n            empty_row_tests(T)\n            test_empty_row_1(T)\n            test_empty_row_2(T)\n            test_empty_row_tolerances(T)\n        end\n    end\nend\n"
  },
  {
    "path": "test/Presolve/fixed_variable.jl",
    "content": "\"\"\"\nRemove fixed variables with explicit zeros.\n\"\"\"\nfunction test_fixed_variable_with_zeros(T::Type)\n\n    pb = Tulip.ProblemData{T}()\n\n    m, n = 3, 2\n    arows = [1, 1, 2, 2, 3, 3]\n    acols = [1, 2, 1, 2, 1, 2]\n    avals = T.([\n        11, 0,\n        0, 21,\n        31, 0\n    ])\n    A = sparse(arows, acols, avals, m, n)\n\n    Tulip.load_problem!(pb, \"Test\",\n        true, T.(collect(1:n)), zero(T),\n        A, \n        zeros(T, m), ones(T, m),\n        ones(T, n), ones(T, n),\n        [\"\" for _ in 1:m], [\"\" for _ in 1:n]\n    )\n\n    ps = Tulip.PresolveData(pb)\n\n    @test ps.nzrow == [1, 1, 1]\n    @test ps.nzcol == [2, 1]\n\n    Tulip.remove_fixed_variable!(ps, 1)\n    @test ps.colflag == [false, true]\n    @test ps.obj0 == T(1)\n    @test ps.nzrow == [0, 1, 0]\n\n    Tulip.remove_fixed_variable!(ps, 2)\n    @test ps.colflag == [false, false]\n    @test ps.obj0 == T(3)\n    @test ps.nzrow == [0, 0, 0]\n\n\n    return nothing\nend\n\n@testset \"Fixed variable\" begin\n    for T in TvTYPES\n        @testset \"$T\" begin\n            test_fixed_variable_with_zeros(T)\n        end\n    end\nend"
  },
  {
    "path": "test/Presolve/presolve.jl",
    "content": "include(\"./empty_column.jl\")\ninclude(\"./empty_row.jl\")\ninclude(\"fixed_variable.jl\")"
  },
  {
    "path": "test/Project.toml",
    "content": "[deps]\nKrylov = \"ba0b0d4f-ebba-5204-a429-3ac8c609bfb7\"\nLinearAlgebra = \"37e2e46d-f89d-539d-b4ee-838fcccc9c8e\"\nMathOptInterface = \"b8f27783-ece8-5eb3-8dc8-9495eed66fee\"\nSparseArrays = \"2f01184e-e22b-5df5-ae63-d93ebab69eaf\"\nTOML = \"fa267f1f-6049-4f14-aa54-33bafae1ed76\"\nTest = \"8dfed614-e22c-5e08-85e1-65c5234f0b40\"\n\n[compat]\nKrylov = \"0.10\"\nMathOptInterface= \"1\"\n"
  },
  {
    "path": "test/examples.jl",
    "content": "const examples_dir = joinpath(@__FILE__, \"../../examples\")\n\n@testset \"Optimal\" begin\n    include(joinpath(examples_dir, \"optimal.jl\"))\n    for T in TvTYPES\n        @testset \"$T\" begin\n            ex_optimal(T; OutputLevel=1, IPM_Factory=Tulip.Factory(Tulip.HSD))\n            ex_optimal(T; OutputLevel=1, IPM_Factory=Tulip.Factory(Tulip.MPC))\n        end\n    end\nend\n@testset \"Free vars\" begin\n    include(joinpath(examples_dir, \"freevars.jl\"))\n    for T in TvTYPES\n        @testset \"$T\" begin\n            ex_freevars(T; OutputLevel=1, IPM_Factory=Tulip.Factory(Tulip.HSD))\n            ex_freevars(T; OutputLevel=1, IPM_Factory=Tulip.Factory(Tulip.MPC))\n        end\n    end\nend\n@testset \"PrimalInfeas\" begin\n    include(joinpath(examples_dir, \"infeasible.jl\"))\n    for T in TvTYPES\n        @testset \"$T\" begin ex_infeasible(T, OutputLevel=0) end\n    end\nend\n@testset \"DualInfeas\" begin\n    include(joinpath(examples_dir, \"unbounded.jl\"))\n    for T in TvTYPES\n        @testset \"$T\" begin ex_unbounded(T, OutputLevel=0) end\n    end\nend\n\n@testset \"Optimal Float32\" begin\n    include(joinpath(examples_dir, \"optimal_other_type.jl\"))\nend\n"
  },
  {
    "path": "test/runtests.jl",
    "content": "using LinearAlgebra\nusing SparseArrays\nusing Test\nusing TOML\n\nusing Tulip\nTLP = Tulip\n\nconst TvTYPES = [Float32, Float64, BigFloat]\n\n# Check That Tulip.version() matches what's in the Project.toml\ntlp_ver = Tulip.version()\ntoml_ver = VersionNumber(TOML.parsefile(joinpath(@__DIR__, \"..\", \"Project.toml\"))[\"version\"])\n@test tlp_ver == toml_ver\n\n@testset \"Tulip\" begin\n\n@testset \"Unit tests\" begin\n\n    @testset \"Core\" begin\n        include(\"Core/problemData.jl\")\n    end\n\n    @testset \"IPM\" begin\n        include(\"IPM/HSD.jl\")\n        include(\"IPM/MPC.jl\")\n    end\n\n    @testset \"KKT\" begin\n        include(\"KKT/KKT.jl\")\n    end\n\n    @testset \"Presolve\" begin\n        include(\"Presolve/presolve.jl\")\n    end\nend  # UnitTest\n\n@testset \"Examples\" begin\n    include(\"examples.jl\")\nend\n\n@testset \"Interfaces\" begin\n    include(\"Interfaces/julia_api.jl\")\nend\n\n@testset \"MOI\" begin\n    include(\"Interfaces/MOI_wrapper.jl\")\nend\n\n# @testset \"Convex Problem Depot tests\" begin\n#     for T in TvTYPES\n#         @testset \"$T\" begin\n#             Convex.ProblemDepot.run_tests(;  exclude=[r\"mip\", r\"exp\", r\"socp\", r\"sdp\"], T = T) do problem\n#                 Convex.solve!(problem, () -> Tulip.Optimizer{T}())\n#             end\n#         end\n#     end\n# end\n\nend  # Tulip tests\n"
  }
]