Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/Project.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[deps]
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
ModelAnalyzer = "d1179b25-476b-425c-b826-c7787f0fff83"
8 changes: 8 additions & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using Documenter, ModelAnalyzer

makedocs(; sitename = "ModelAnalyzer.jl documentation")

deploydocs(;
repo = "github.com/jump-dev/ModelAnalyzer.jl.git",
push_preview = true,
)
24 changes: 24 additions & 0 deletions docs/src/analyzer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@

# ModelAnalyzer main API

All the analysis modules in `ModelAnalyzer` follow the same main API.
The main function to perform an analysis is:

```@docs
ModelAnalyzer.analyze
```

Once the analysis is performed, the resulting data structure can be summarized
using:

```@docs
ModelAnalyzer.summarize
```

Alternatively, you can also query the types of issues found in the analysis
and summarize them individually. The following functions are useful for this:

```@docs
ModelAnalyzer.list_of_issue_types
ModelAnalyzer.list_of_issues
```
33 changes: 33 additions & 0 deletions docs/src/feasibility.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@

# Feasibility Analysis

This module provides functionality to perform feasibility analysis on a JuMP model.
This module follows the main API and is activated by the struct:

```@docs
ModelAnalyzer.Feasibility.Analyzer
```

The analysis will return issues of the abstract type:

```@docs
ModelAnalyzer.Feasibility.AbstractFeasibilityIssue
```
Specifically, the possible issues are:

```@docs
ModelAnalyzer.Feasibility.PrimalViolation
ModelAnalyzer.Feasibility.DualViolation
ModelAnalyzer.Feasibility.ComplemetarityViolation
ModelAnalyzer.Feasibility.DualObjectiveMismatch
ModelAnalyzer.Feasibility.PrimalObjectiveMismatch
ModelAnalyzer.Feasibility.PrimalDualMismatch
ModelAnalyzer.Feasibility.PrimalDualSolverMismatch
```

These issues are saved in the data structure that is returned from the
`ModelAnalyzer.analyze` function:

```@docs
ModelAnalyzer.Feasibility.Data
```
114 changes: 114 additions & 0 deletions docs/src/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
```@meta
CurrentModule = ModelAnalyzer
DocTestSetup = quote
using ModelAnalyzer
end
```

# ModelAnalyzer.jl

This package provides tools for analyzing (and debugging) [JuMP](https://github.com/jump-dev/JuMP.jl) models.

Three main functionalities are provided:

1. **Numerical Analysis**: Check for numerical issues in the model, such as large and small coefficients,
empty constraints, non-convex quadratic functions.

2. **Feasibility Analysis**: Given an optimized model, or a candidate solution, check if the solutions is
feasible and optimal (when possible). This includes checking the feasibility of the primal model and
also the dual model (if available). Complementary slackness conditions are also checked (if applicable).

3. **Infeasibility Analysis**: Given an unsolved of solved model, three steps are made to check for
infeasibility:
- Check bounds, integers and binaries consistency is also checked at this point.
- Propagate bounds in constraints individually, to check if each constraint is infeasible
given the current variable bounds. This is only done if bounds are ok.
- Run an IIS (Irreducible Inconsistent Subsystem / irreducible infeasible sets)
resolver algorithm to find a minimal infeasible subset of constraints.
This is only done if no issues are found in the previous two steps.

## Installation

You can install the package using the Julia package manager. In the Julia REPL, run:

```julia
using Pkg
Pkg.add("https://github.com/jump-dev/ModelAnalyzer.jl")
```

## Usage

### Basic usage

Here is a simple example of how to use the package:

```julia
using JuMP
using ModelAnalyzer
using HiGHS # or any other supported solver
# Create a simple JuMP model
model = Model(HiGHS.Optimizer)
@variable(model, x >= 0)
@variable(model, y >= 0)
@constraint(model, c1, 2x + 3y == 5)
@constraint(model, c2, x + 2y <= 3)
@objective(model, Min, x + y)
# Optimize the model
optimize!(model)

# either

# Perform a numerical analysis of the model
data = ModelAnalyzer.analyze(ModelAnalyzer.Numerical.Analyzer(), model)
# print report
ModelAnalyzer.summarize(data)

# or

# Check for solution feasibility and optimality
data = ModelAnalyzer.analyze(ModelAnalyzer.Feasibility.Analyzer(), model)
# print report
ModelAnalyzer.summarize(data)

# or

# Infeasibility analysis (if the model was infeasible)
data = ModelAnalyzer.analyze(
ModelAnalyzer.Infeasibility.Analyzer(),
model,
optimizer = HiGHS.Optimizer,
)
# print report
ModelAnalyzer.summarize(data)
```

The `ModelAnalyzer.analyze(...)` function can always take the keyword arguments:
* `verbose = false` to condense the print output.
* `max_issues = n` to limit the maximum number of issues to report for each type.

For certain analysis modes, the `summarize` function can take additional arguments.

### Advanced usage

After any `ModelAnalyzer.analyze(...)` call is performed, the resulting data structure
can be summarized using `ModelAnalyzer.summarize(data)` as show above,
or it can be further inspected programmatically.

```julia
# given a `data` object obtained from `ModelAnalyzer.analyze(...)`

# query the types os issues found in the analysis
list = ModelAnalyzer.list_of_issue_types(data)

# information about the types of issues found can be printed out
ModelAnalyzer.summarize(data, list[1])

# for each issue type, you can get the actual issues found in the analysis
issues = ModelAnalyzer.list_of_issues(data, list[1])

# the list of issues of the given type can be summarized with:
ModelAnalyzer.summarize(data, issues)

# infdividual issues can also be summarized
ModelAnalyzer.summarize(data, issues[1])
```
31 changes: 31 additions & 0 deletions docs/src/infeasibility.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@

# Infeasibility Analysis

This module provides functionality to perform infeasibility analysis on a JuMP model.
This module follows the main API and is activated by the struct:

```@docs
ModelAnalyzer.Infeasibility.Analyzer
```

The analysis will return issues of the abstract type:

```@docs
ModelAnalyzer.Infeasibility.AbstractInfeasibilitylIssue
```

Specifically, the possible issues are:

```@docs
ModelAnalyzer.Infeasibility.InfeasibleBounds
ModelAnalyzer.Infeasibility.InfeasibleIntegrality
ModelAnalyzer.Infeasibility.InfeasibleConstraintRange
ModelAnalyzer.Infeasibility.IrreducibleInfeasibleSubset
```

These issues are saved in the data structure that is returned from the
`ModelAnalyzer.analyze` function:

```@docs
ModelAnalyzer.Infeasibility.Data
```
43 changes: 43 additions & 0 deletions docs/src/numerical.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@

# Numerical Analysis

This module provides functionality to perform numerical analysis on a JuMP model.
This module follows the main API and is activated by the struct:

```@docs
ModelAnalyzer.Numerical.Analyzer
```

The analysis will return issues of the abstract type:

```@docs
ModelAnalyzer.Numerical.AbstractNumericalIssue
```

Specifically the possible issues are:

```@docs
ModelAnalyzer.Numerical.VariableNotInConstraints
ModelAnalyzer.Numerical.EmptyConstraint
ModelAnalyzer.Numerical.VariableBoundAsConstraint
ModelAnalyzer.Numerical.DenseConstraint
ModelAnalyzer.Numerical.SmallMatrixCoefficient
ModelAnalyzer.Numerical.LargeMatrixCoefficient
ModelAnalyzer.Numerical.SmallBoundCoefficient
ModelAnalyzer.Numerical.LargeBoundCoefficient
ModelAnalyzer.Numerical.SmallRHSCoefficient
ModelAnalyzer.Numerical.LargeRHSCoefficient
ModelAnalyzer.Numerical.SmallObjectiveCoefficient
ModelAnalyzer.Numerical.LargeObjectiveCoefficient
ModelAnalyzer.Numerical.SmallObjectiveQuadraticCoefficient
ModelAnalyzer.Numerical.LargeObjectiveQuadraticCoefficient
ModelAnalyzer.Numerical.NonconvexQuadraticConstraint
ModelAnalyzer.Numerical.SmallMatrixQuadraticCoefficient
ModelAnalyzer.Numerical.LargeMatrixQuadraticCoefficient
```

These issues are saved in the data structure that is returned from the `ModelAnalyzer.analyze` function:

```@docs
ModelAnalyzer.Numerical.Data
```
57 changes: 53 additions & 4 deletions src/ModelAnalyzer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,59 @@ abstract type AbstractData end

abstract type AbstractAnalyzer end

"""
analyze(analyzer::AbstractAnalyzer, model::JuMP.Model; kwargs...)

Analyze a JuMP model using the specified analyzer.
Depending on the analyzer, this keyword arguments might vary.
This function will return an instance of `AbstractData` which contains the
results of the analysis that can be further summarized or queried for issues.

See [`summarize`](@ref), [`list_of_issues`](@ref), and
[`list_of_issue_types`](@ref).
"""
function analyze end

"""
summarize([io::IO,] AbstractData; verbose = true, max_issues = typemax(Int), kwargs...)

Print a summary of the analysis results contained in `AbstractData` to the
specified IO stream. If no IO stream is provided, it defaults to `stdout`.
The `verbose` flag controls whether to print detailed information about each
issue (if `true`) or a concise summary (if `false`). The `max_issues` argument
controls the maximum number of issues to display in the summary. If there are
more issues than `max_issues`, only the first `max_issues` will be displayed.

summarize([io::IO,] ::Type{T}; verbose = true) where {T<:AbstractIssue}

This variant allows summarizing information of a specific type `T` (which must
be a subtype of `AbstractIssue`). In the verbose case it will provide a text
explaning the issue. In the non-verbose case it will provide just the issue
name.

summarize([io::IO,] issue::AbstractIssue; verbose = true)

This variant allows summarizing a single issue instance of type `AbstractIssue`.
"""
function summarize end

"""
list_of_issue_types(data::AbstractData)

Return a vector of `DataType` containing the types of issues found in the
analysis results contained in `data`.
"""
function list_of_issue_types end

"""
list_of_issues(data::AbstractData, issue_type::Type{T}) where {T<:AbstractIssue}

Return a vector of instances of `T` (which must be a subtype of `AbstractIssue`)
found in the analysis results contained in `data`. This allows you to retrieve
all instances of a specific issue type from the analysis results.
"""
function list_of_issues end

function summarize(io::IO, ::Type{T}; verbose = true) where {T<:AbstractIssue}
if verbose
return _verbose_summarize(io, T)
Expand Down Expand Up @@ -49,10 +102,6 @@ function summarize(data::AbstractData; kwargs...)
return summarize(stdout, data; kwargs...)
end

function analyze end
function list_of_issues end
function list_of_issue_types end

function _verbose_summarize end
function _summarize end

Expand Down
Loading