From 786b606b56a6a9d002d4c310c1c35c325c8a6ed8 Mon Sep 17 00:00:00 2001 From: "Documenter.jl" Date: Tue, 7 Nov 2023 15:33:17 +0000 Subject: [PATCH] build based on 748e73a --- previews/PR1107/.documenter-siteinfo.json | 1 + previews/PR1107/api/algorithm/index.html | 7 + previews/PR1107/api/algos/index.html | 33 + previews/PR1107/api/benders/index.html | 56 + previews/PR1107/api/branching/index.html | 7 + previews/PR1107/api/colgen/index.html | 70 + previews/PR1107/api/presolve/index.html | 7 + previews/PR1107/api/storage.jl | 31 + previews/PR1107/api/storage/index.html | 7 + previews/PR1107/api/treesearch/index.html | 7 + previews/PR1107/assets/documenter.js | 874 ++++++ previews/PR1107/assets/img/bdec.png | Bin 0 -> 2127 bytes previews/PR1107/assets/img/dw_master.svg | 3 + previews/PR1107/assets/img/dw_origform.svg | 3 + previews/PR1107/assets/img/dw_sp.svg | 3 + previews/PR1107/assets/img/dwdec.png | Bin 0 -> 2363 bytes .../assets/img/dynamic_sparse_arrays.svg | 2716 +++++++++++++++++ previews/PR1107/assets/js/init.js | 6 + .../PR1107/assets/themes/documenter-dark.css | 7 + .../PR1107/assets/themes/documenter-light.css | 9 + previews/PR1107/assets/themeswap.js | 84 + previews/PR1107/assets/warner.js | 52 + .../PR1107/dynamic_sparse_arrays/index.html | 8 + previews/PR1107/index.html | 7 + previews/PR1107/man/algorithm/index.html | 103 + .../PR1107/man/blockdecomposition/index.html | 25 + previews/PR1107/man/callbacks/index.html | 7 + previews/PR1107/man/config/index.html | 11 + previews/PR1107/man/decomposition/index.html | 38 + previews/PR1107/qa/index.html | 28 + previews/PR1107/search_index.js | 3 + previews/PR1107/siteinfo.js | 1 + previews/PR1107/start/2echelons_VRP.jl | 1 + previews/PR1107/start/advanced_demo.jl | 932 ++++++ .../PR1107/start/advanced_demo/index.html | 1485 +++++++++ previews/PR1107/start/custom_data.jl | 248 ++ previews/PR1107/start/custom_data/index.html | 166 + previews/PR1107/start/cuts.jl | 122 + previews/PR1107/start/cuts/index.html | 181 ++ previews/PR1107/start/identical_sp.jl | 100 + previews/PR1107/start/identical_sp/index.html | 137 + previews/PR1107/start/initial_columns.jl | 83 + .../PR1107/start/initial_columns/index.html | 74 + previews/PR1107/start/other_pbs/index.html | 7 + previews/PR1107/start/pricing.jl | 108 + previews/PR1107/start/pricing/index.html | 63 + previews/PR1107/start/start.jl | 153 + previews/PR1107/start/start/index.html | 111 + previews/PR1107/start/tree2.dot | 6 + 49 files changed, 8191 insertions(+) create mode 100644 previews/PR1107/.documenter-siteinfo.json create mode 100644 previews/PR1107/api/algorithm/index.html create mode 100644 previews/PR1107/api/algos/index.html create mode 100644 previews/PR1107/api/benders/index.html create mode 100644 previews/PR1107/api/branching/index.html create mode 100644 previews/PR1107/api/colgen/index.html create mode 100644 previews/PR1107/api/presolve/index.html create mode 100644 previews/PR1107/api/storage.jl create mode 100644 previews/PR1107/api/storage/index.html create mode 100644 previews/PR1107/api/treesearch/index.html create mode 100644 previews/PR1107/assets/documenter.js create mode 100644 previews/PR1107/assets/img/bdec.png create mode 100644 previews/PR1107/assets/img/dw_master.svg create mode 100644 previews/PR1107/assets/img/dw_origform.svg create mode 100644 previews/PR1107/assets/img/dw_sp.svg create mode 100644 previews/PR1107/assets/img/dwdec.png create mode 100644 previews/PR1107/assets/img/dynamic_sparse_arrays.svg create mode 100644 previews/PR1107/assets/js/init.js create mode 100644 previews/PR1107/assets/themes/documenter-dark.css create mode 100644 previews/PR1107/assets/themes/documenter-light.css create mode 100644 previews/PR1107/assets/themeswap.js create mode 100644 previews/PR1107/assets/warner.js create mode 100644 previews/PR1107/dynamic_sparse_arrays/index.html create mode 100644 previews/PR1107/index.html create mode 100644 previews/PR1107/man/algorithm/index.html create mode 100644 previews/PR1107/man/blockdecomposition/index.html create mode 100644 previews/PR1107/man/callbacks/index.html create mode 100644 previews/PR1107/man/config/index.html create mode 100644 previews/PR1107/man/decomposition/index.html create mode 100644 previews/PR1107/qa/index.html create mode 100644 previews/PR1107/search_index.js create mode 100644 previews/PR1107/siteinfo.js create mode 100644 previews/PR1107/start/2echelons_VRP.jl create mode 100644 previews/PR1107/start/advanced_demo.jl create mode 100644 previews/PR1107/start/advanced_demo/index.html create mode 100644 previews/PR1107/start/custom_data.jl create mode 100644 previews/PR1107/start/custom_data/index.html create mode 100644 previews/PR1107/start/cuts.jl create mode 100644 previews/PR1107/start/cuts/index.html create mode 100644 previews/PR1107/start/identical_sp.jl create mode 100644 previews/PR1107/start/identical_sp/index.html create mode 100644 previews/PR1107/start/initial_columns.jl create mode 100644 previews/PR1107/start/initial_columns/index.html create mode 100644 previews/PR1107/start/other_pbs/index.html create mode 100644 previews/PR1107/start/pricing.jl create mode 100644 previews/PR1107/start/pricing/index.html create mode 100644 previews/PR1107/start/start.jl create mode 100644 previews/PR1107/start/start/index.html create mode 100644 previews/PR1107/start/tree2.dot diff --git a/previews/PR1107/.documenter-siteinfo.json b/previews/PR1107/.documenter-siteinfo.json new file mode 100644 index 000000000..8f5664969 --- /dev/null +++ b/previews/PR1107/.documenter-siteinfo.json @@ -0,0 +1 @@ +{"documenter":{"julia_version":"1.9.3","generation_timestamp":"2023-11-07T15:33:04","documenter_version":"1.0.1"}} \ No newline at end of file diff --git a/previews/PR1107/api/algorithm/index.html b/previews/PR1107/api/algorithm/index.html new file mode 100644 index 000000000..7533f834c --- /dev/null +++ b/previews/PR1107/api/algorithm/index.html @@ -0,0 +1,7 @@ + +Algorithm API · Coluna.jl

Algorithm API

Danger

This is WIP. The API will change in future releases.

An algorithm is a procedure that given a model and and input performs some operations and returns an output.

Missing docstring.

Missing docstring for run!. Check Documenter's build log for details.

Parameters of an algorithm may contain its child algorithms which used by it. Therefore, the algoirthm tree is formed, in which the root is the algorithm called to solver the model (root algorithm should be an optimization algorithm, see below).

TODO: explain why the parent algorithm must manage the records/storages of child algorithm.

Algorithms are divided into two types : "manager algorithms" and "worker algorithms". Worker algorithms just continue the calculation. They do not store and restore units as they suppose it is done by their master algorithms. Manager algorithms may divide the calculation flow into parts. Therefore, they store and restore units to make sure that their child worker algorithms have units prepared. A worker algorithm cannot have child manager algorithms.

Examples of manager algorithms : TreeSearchAlgorithm (which covers both BCP algorithm and diving algorithm), conquer algorithms, strong branching, branching rule algorithms (which create child nodes). Examples of worker algorithms : column generation, SolveIpForm, SolveLpForm, cut separation, pricing algorithms, etc.

Optimization algorithms

Optimization algorithms return an OptimizationState.

Missing docstring.

Missing docstring for OptimizationState. Check Documenter's build log for details.

Conventions

TODO: WIP

  • infeasible: infinite bounds, no solution, infeasible termination status.
diff --git a/previews/PR1107/api/algos/index.html b/previews/PR1107/api/algos/index.html new file mode 100644 index 000000000..057c2bdfd --- /dev/null +++ b/previews/PR1107/api/algos/index.html @@ -0,0 +1,33 @@ + +Algorithms · Coluna.jl

Algorithms

Danger

Work in progress.

Parameters of an algorithm

From a user perspective, the algorithms are objects that contains a set of parameters.

The object must inherit from Coluna.AlgoAPI.AbstractAlgorithm. We usually provide a keyword constructor to define default values for parameters and therefore ease the definition of the object.

struct MyCustomAlgorithm <: Coluna.AlgoAPI.AbstractAlgorithm
+    param1::Int
+    param2::Float64
+    child_algo::Coluna.AlgoAPI.AbstractAlgorithm
+end
+
+# Help the user to define the algorithm:
+function MyCustomAlgorithm(;
+    param1 = 1,
+    param2 = 2,
+    child_algo = AnotherAlgorithm()
+) 
+    return MyCustomAlgorithm(param1, param2, child_algo)
+end

Algorithms can use other algorithms. They are organized as a tree structure.

** Example for the TreeSearchAlgorithm **:

graph TD + TreeSearchAlgorithm ---> ColCutGenConquer + TreeSearchAlgorithm ---> ClassicBranching + ColCutGenConquer --> ColumnGeneration + ColCutGenConquer --> RestrictedHeuristicMaster + RestrictedHeuristicMaster --> SolveIpForm#2 + ColumnGeneration --> SolveLpForm#1 + ColumnGeneration --> SolveIpForm#1 + ColumnGeneration --> CutCallbacks +
Coluna.AlgoAPI.AbstractAlgorithmType

Supertype for algorithms parameters. Data structures that inherit from this type are intented for the users. The convention is to define the data structure together with a constructor that contains only kw args.

For instance:

    struct MyAlgorithmParams <: AbstractAlgorithmParams
+        param1::Int
+        param2::Int
+        MyAlgorithmParams(; param1::Int = 1, param2::Int = 2) = new(param1, param2)
+    end
source

Init

Parameters checking

When Coluna starts, it initializes the algorithms chosen by the user. A most important step is to check the consistency of the parameters supplied by the user and the compatibility of the algorithms with the model that will be received (usually MathProg.Reformulation). Algorithms usually have many parameters and are sometimes interdependent and nested. It is crucial to ensure that the user-supplied parameters are correct and give hints to fix them otherwise.

The entry-point of the parameter consistency checking is the following method:

Coluna.Algorithm.check_alg_parametersFunction
check_alg_parameters(top_algo, reform) -> Vector{Tuple{Symbol, AbstractAlgorithm, Any}}

Checks the consistency of the parameters of the top algorithm and its children algorithms. Returns a vector of tuples (name of the parameter, algorithm, value of the parameter) that lists all the inconsistencies found in the algorithms tree.

source

Developer of an algorithm must implement the following methods:

Missing docstring.

Missing docstring for Coluna.Algorithm.check_parameter. Check Documenter's build log for details.

Units usage

Missing docstring.

Missing docstring for Coluna.AlgoAPI.get_child_algorithms. Check Documenter's build log for details.

Missing docstring.

Missing docstring for Coluna.AlgoAPI.get_units_usage. Check Documenter's build log for details.

Run

diff --git a/previews/PR1107/api/benders/index.html b/previews/PR1107/api/benders/index.html new file mode 100644 index 000000000..fb142c188 --- /dev/null +++ b/previews/PR1107/api/benders/index.html @@ -0,0 +1,56 @@ + +Benders · Coluna.jl

Benders cut generation

Coluna provides an interface and generic functions to implement a Benders cut generation algorithm.

In this section, we are first going to present the generic functions, the implementation with some theory backgrounds and then give the references of the interface. The default implementation is based on the paper of

You can find the generic functions and the interface in the Benders submodule and the default implementation in the Algorithm submodule at src/Algorithm/benders.

Context

The Benders submodule provides an interface and generic functions to implement a benders cut generation algorithm. The implementation depends on an object called context.

Coluna.Benders.AbstractBendersContextType

Supertype for the objects to which belongs the implementation of the Benders cut generation and that stores any kind of information during the execution of the Bender cut generation algorithm.

source

Benders provides two types of context:

Coluna.Algorithm.BendersPrinterContextType
BendersPrinterContext(reformulation, algo_params) -> BendersPrinterContext

Creates a context to run the default implementation of the Benders algorithm together with a printer that prints information about the algorithm execution.

source

Generic functions

Generic functions are the core of the Benders cut generation algorithm. There are three generic functions:

See ...

See ...

These functions are independent of any other submodule of Coluna. You can use them to implement your own Benders cut generation algorithm.

Reformulation

The default implementation works with a reformulated problem contained in MathProg.Reformulation where master and subproblems are MathProg.Formulation objects.

The master has the following form:

\[\begin{aligned} +\min \quad& cx + \sum_{k \in K} \eta_k & &\\ +\text{s.t.} \quad& Ax \geq a & & (1) \\ + & \text{< benders cuts>} & & (2) \\ + & l_1 \leq x \leq u_1 & & (3) \\ + & \eta_k \in \mathbb{R} & \forall k \in K \quad& (4) +\end{aligned}\]

where $x$ are first-stage variables, $\eta_k$ is the second-stage cost variable for the subproblem $k$, constraints $(1)$ are the first-stage constraints, constraints $(2)$ are the Benders cuts, constraints $(3)$ are the bounds on the first-stage variables, and expression $(4)$ shows that second-stage variables are free.

The subproblems have the following form:

\[\begin{aligned} +\min \quad& fy + {\color{gray} \mathbf{1}z' + \mathbf{1}z''} &&& \\ +\text{s.t.} \quad& Dy {\color{gray} + z'} \geq d - B\bar{x} && (5) \quad& {\color{blue}(\pi)} \\ + & Ey {\color{gray} + z''} \geq e && (6) \quad& {\color{blue}(\rho)} \\ + & l_2 \leq y \leq u_2 && (7) \quad& {\color{blue}(\sigma)} +\end{aligned}\]

where $y$ are second-stage variables, $z'$ and $z''$ are artificial variables (in grey because they are deactivated by default), constraints (5) are the reformulation of linking constraints using the first-stage solution $\bar{x}$, constraints (6) are the second-stage constraints, and constraints (7) are the bounds on the second-stage variables. In blue, we define the dual variables associated to these constraints.

References:

Main loop

This is a description of how the Coluna.Benders.run_benders_loop! generic function behaves with the default implementation.

The loop stops if one of the following conditions is met:

  • the master is infeasible
  • a separation subproblem is infeasible
  • the time limit is reached
  • the maximum number of iterations is reached
  • no new cut is generated

The default implementation returns:

Coluna.Algorithm.BendersOutputType

Output of the default implementation of the Benders algorithm.

It contains:

  • infeasible: the original problem is infeasible
  • time_limit_reached: the time limit was reached
  • mlp: the final bound obtained with the Benders cut algorithm
  • ip_primal_sol: the best primal solution to the original problem found by the Benders cut algorithm
source

References:

Coluna.Benders.benders_output_typeFunction
benders_output_type(context) -> Type{<:AbstractBendersOutput}

Returns the type of the custom object that will store the output of the Benders cut generation algorithm.

source

Benders cut generation iteration

This is a description of how the Coluna.Benders.run_benders_iteration! generic function behaves with the default implementation.

These are the main steps of a Benders cut generation iteration without stabilization. Click on the step to go to the corresponding section.

flowchart TB; + id1(Optimize master) + id2(Treat unbounded master) + id3(Setup separation subproblems) + id4(Separation subproblem iterator) + id5(Optimize separation subproblem) + id6(Push cut into set) + id9(Master is unbounded?) + id10(Error) + id7(Insert cuts) + id11(Build primal solution) + id8(Iteration output) + id1 --unbounded--> id2 + id2 --certificate--> id3 + id1 -- optimal --> id3 + id3 --> id4 + id4 -- subproblem --> id5 + id5 --> id6 + id6 --> id4 + id4 -- end --> id9 + id9 -- yes --> id10 + id9 -- no --> id7 + id7 --> id11 + id11 --> id8 + click id1 href "#Master-optimization" "Link to doc" + click id2 href "#Unbounded-master-case" "Link to doc" + click id3 href "#Setup-separation-subproblems" "Link to doc" + click id4 href "#Subproblem-iterator" "Link to doc" + click id5 href "#Separation-subproblem-optimization" "Link to doc" + click id6 href "#Set-of-generated-cuts" "Link to doc" + click id9 href "#Unboundedness-check" "Link to doc" + click id11 href "#Current-primal-solution" "Link to doc" + click id7 href "#Cuts-insertion" "Link to doc" + click id8 href "#Iteration-output" "Link to doc"

In the default implementation, some sections may have different behaviors depending on the result of previous steps.

Master optimization

This operation consists in optimizing the master problem in order to find a first-level solution $\bar{x}$.

In the default implementation, master optimization can be performed using SolveLpForm (LP solver) or SolveIpForm (MILP solver). When getting the solution, we store the current value of second stage variables $\bar{\eta}_k$ as incumbent value (see Coluna.MathProg.getcurincval).

It returns an object of the following type:

Coluna.Algorithm.BendersMasterResultType

Output of the default implementation of the Benders.optimize_master_problem! method.

It contains:

  • ip_solver: true if the master problem is solved with a MIP solver and involves integral variables, false otherwise.
  • result: the result of the master problem optimization stored in an OptimizationState object.
  • infeasible: true if the master at the current iteration is infeasible; false otherwise.
  • unbounded: true if the master at the current iteration is unbounded; false otherwise.
  • certificate: true if the master at the current iteration is unbounded and if the current result is a dual infeasibility certificate, false otherwise.
source

References:

Coluna.Benders.optimize_master_problem!Function
optimize_master_problem!(master, context, env) -> MasterResult

Returns an instance of a custom object MasterResult that implements the following methods:

  • is_unbounded(res::MasterResult) -> Bool
  • is_infeasible(res::MasterResult) -> Bool
  • is_certificate(res::MasterResult) -> Bool
  • get_primal_sol(res::MasterResult) -> Union{Nothing, PrimalSolution}
source

Go back to the cut generation iteration diagram.

Unbounded master case

Second stage cost $\eta_k$ variables are free. As a consequence, the master problem is unbounded when there is no optimality Benders cuts.

In this case, Coluna.Benders.treat_unbounded_master_problem_case! is called. The main goal of the default implementation of this method is to get the dual infeasibility certificate of the master problem.

If the master has been solved with a MIP solver at the previous step, we need to relax the integrality constraints to get a dual infeasibility certificate.

If the solver does not provide a dual infeasibility certificate, the implementation has an "emergency" routine to provide a first-stage feasible solution by solving the master LP with cost of second stage variables set to zero. We recommend using a solver that provides a dual infeasibility certificate and avoiding the "emergency" routine.

References:

Coluna.Benders.treat_unbounded_master_problem_case!Function
treat_unbounded_master_problem_case!(master, context, env) -> MasterResult

When after a call to optimize_master_problem!, the master is unbounded, this method is called. Returns an instance of a custom object MasterResult.

source

Go back to the cut generation iteration diagram.

Setup separation subproblems

Info

The separation subproblems differs depending on whether the restricted master is unbounded or not:

  • if the restricted master is optimal, the generic function calls Coluna.Benders.update_sp_rhs!
  • if the restricted master is unbounded, the generic function calls Coluna.Benders.setup_separation_for_unbounded_master_case!

Default implementation of Coluna.Benders.update_sp_rhs! updates the right-hand side of the linking constraints (5).

Reference:

Coluna.Benders.update_sp_rhs!Function
update_sp_rhs!(context, sp, mast_primal_sol)

Updates the right-hand side of the separation problem sp by fixing the first-level solution obtained by solving the master problem mast_primal_sol.

source

Default implementation of Coluna.Benders.setup_separation_for_unbounded_master_case! gives rise to the formulation proposed in Lemma 2 of Bonami et al:

\[\begin{aligned} +(SepB) \equiv \min \quad& fy + {\color{gray} \mathbf{1}z' + \mathbf{1}z''} &&& \\ +\text{s.t.} \quad& Dy {\color{gray} + z'} \geq -B\bar{x} && (5a) \quad& {\color{blue}(\pi)} \\ + & Ey {\color{gray} + z''} \geq 0 && (6a) \quad& {\color{blue}(\rho)} \\ + & y \geq 0 && (7a) \quad& {\color{blue}(\sigma)} +\end{aligned}\]

where $y$ are second-stage variables, $z'$ and $z''$ are artificial variables (in grey because they are deactivated by default), and $\bar{x}$ is an unbounded ray of the restricted master.

Reference:

Subproblem iterator

Not implemented yet.

Separation subproblem optimization

The default implementation first optimize the subproblem without the artificial variables $z'$ and $z''$. In the case where it finds $(\bar{\pi}, \bar{\rho}, \bar{\sigma})$ an optimal dual solution to the subproblem, the following cut is generated:

\[\eta_k + \bar{\pi}Bx \geq d\bar{\pi} + \bar{\rho}e + \bar{\sigma_{\leq}} l_2 + \bar{\sigma_{\geq}} u_2\]

with $\bar{\sigma_{\leq}} l_2$ (respectively $\bar{\sigma_{\geq}} u_2$) the dual of the left part (respectively the right part) of constraint $l_2 \leq y \leq u_2$ of the subproblem.

In the case where it finds the subproblem infeasible, it calls Coluna.Benders.treat_infeasible_separation_problem_case!. The default implementation of this method activates the artificial variables $z'$ and $z''$, sets the cost of second stage variables to 0, and optimizes the subproblem again.

If a solution with no artificial variables is found, the following cut is generated:

\[\bar{\pi}Bx \geq d\bar{\pi} + \bar{\rho}e + \bar{\sigma_{\leq}} l_2 + \bar{\sigma_{\geq}} u_2\]

Both methods return an object of the following type:

Coluna.Algorithm.BendersSeparationResultType

Output of the default implementation of the Benders.optimize_separation_problem! and Benders.treat_infeasible_separation_problem_case! methods.

It contains:

  • second_stage_estimation_in_master: the value of the second stage cost variable in the solution to the master problem.
  • second_stage_cost: the value of the second stage cost variable in the solution to the separation problem.
  • lp_primal_sol: the primal solution to the separation problem.
  • infeasible: true if the current separation problem is infeasible; false otherwise.
  • unbounded: true if the current separation problem is unbounded; false otherwise.
  • cut: the cut generated by the separation problem.
  • infeasible_treatment: true if this object is an output of the Benders.treat_infeasible_separation_problem_case! method; false otherwise.
  • unbounded_master: true if the separation subproblem has the form of Lemma 2 to separate a cut to truncate an unbounded ray of the restricted master problem; false otherwise.
source

References:

Coluna.Benders.optimize_separation_problem!Function
optimize_separation_problem!(context, sp_to_solve, env, unbounded_master) -> SeparationResult

Returns an instance of a custom object SeparationResult that implements the following methods:

  • is_unbounded(res::SeparationResult) -> Bool
  • is_infeasible(res::SeparationResult) -> Bool
  • get_obj_val(res::SeparationResult) -> Float64
  • get_primal_sol(res::SeparationResult) -> Union{Nothing, PrimalSolution}
  • get_dual_sp_sol(res::SeparationResult) -> Union{Nothing, DualSolution}
source
Coluna.Benders.treat_infeasible_separation_problem_case!Function
treat_infeasible_separation_problem_case!(context, sp_to_solve, env, unbounded_master) -> SeparationResult

When after a call to optimize_separation_problem!, the separation problem is infeasible, this method is called. Returns an instance of a custom object SeparationResult.

source

Go back to the cut generation iteration diagram.

Set of generated cuts

You can define your data structure to manage the cuts generated at a given iteration. Columns are inserted after the optimization of all the separation subproblems to allow the parallelization of the latter.

In the default implementation, cuts are represented by the following data structure:

Coluna.Algorithm.GeneratedCutType

Solution to the separation problem together with its corresponding benders cut.

It contains:

  • min_sense: true if it's a minimization problem; false otherwise.
  • lhs: the left-hand side of the cut.
  • rhs: the right-hand side of the cut.
  • dual_sol: an optimal dual solution to the separation problem.
source

We use the following data structures to store the cuts and the primal solutions to the subproblems:

Coluna.Algorithm.SepSolSetType

Primal solutions to the separation problems optimized at the current iteration. This is used to build a primal solution.

It contains sols a vector of primal solutions.

source

The default implementation of push_in_set! has the responsibility to check if the cut is violated. Given $\bar{\eta}_k$ solution to the restricted master and $\bar{y}$ solution to the separation problem, the cut is considered as violated when:

  • the separation subproblem was infeasible
  • or $\bar{\eta}_k \geq f\bar{y}$

References:

Coluna.Benders.set_of_cutsFunction

Returns an empty container that will store all the cuts generated by the separation problems during an iteration of the Benders cut generation algorithm. One must be able to iterate on this container to insert the cuts in the master problem.

source
Coluna.Benders.set_of_sep_solsFunction

Returns an empty container that will store the primal solutions to the separation problems at a given iteration of the Benders cut generation algorithm.

source
Coluna.Benders.push_in_set!Function
push_in_set!(context, cut_pool, sep_result) -> Bool

Inserts a cut in the set of cuts generated at a given iteration of the Benders cut generation algorithm. The cut_pool structure is generated by set_of_cuts(context).

push_in_set!(context, sep_sp_sols, sep_result) -> Bool

Inserts a primal solution to a separation problem in the set of primal solutions generated at a given iteration of the Benders cut generation algorithm. The sep_sp_sols structure is generated by set_of_sep_sols(context).

Returns true if the cut or the primal solution was inserted in the set, false otherwise.

source

Go back to the cut generation iteration diagram.

Unboundedness check

Info

This check is performed only when the restricted master is unbounded.

To perform this check, we need a solution to each separation problem.

Let $(\bar{\eta}_k)_{k \in K}$ be the value of second stage variables in the dual infeasibility certificate of the restricted master. Let $\bar{y}$ be an optimal solution to the separation problem (SepB).

As indicated by Bonami et al., if $f\bar{y} \leq \sum\limits_{k \in K} \bar{\eta}_k$, then the original problem is unbounded (by definition of an unbounded ray of the original problem).

References:

Cuts insertion

The default implementation inserts into the master all the cuts stored in the CutsSet object.

Reference:

Go back to the cut generation iteration diagram.

Current primal solution

Lorem ipsum.

References:

Iteration output

Coluna.Algorithm.BendersIterationOutputType

Output of the default implementation of an iteration of the Benders algorithm.

It contains:

  • min_sense: the original problem is a minimization problem
  • nb_new_cuts: the number of new cuts added to the master problem
  • ip_primal_sol: the primal solution to the original problem found during this iteration
  • infeasible: the original problem is infeasible
  • time_limit_reached: the time limit was reached
  • master: the solution value to the master problem
source

References:

Go back to the cut generation iteration diagram.

Getters for Result data structures

Method nameMasterSeparation
is_unboundedXX
is_infeasibleXX
is_certificateX
get_primal_solXX
get_dual_solX
get_obj_valXX

Go back to the cut generation iteration diagram.

Stabilization

Not implemented yet.

diff --git a/previews/PR1107/api/branching/index.html b/previews/PR1107/api/branching/index.html new file mode 100644 index 000000000..3ea4900f9 --- /dev/null +++ b/previews/PR1107/api/branching/index.html @@ -0,0 +1,7 @@ + +Branching · Coluna.jl

Branching API

Coluna provides default implementations for the branching algorithm and the strong branching algorithms. Both implementations are built on top of an API that we describe here.

Candidates selection

Candidates selection is the first step (and sometimes the only step) of any branching algorithm. It chooses what are the possible branching constraints that will generate the children of the current node of the branch-and-bound tree.

Coluna provides the following function for this step:

It works as follows.

The user chooses one or several branching rules that indicate the type of branching he wants to perform. This may be on a single variable or on a linear expression of variables for instance.

The branching rule must implement apply_branching_rule that generates the candidates. The latter are the variables or expressions on which the branch-and-bound may branch with additional information that is requested by Coluna's branching implementation through the API.

Then, candidates are sorted according to a selection criterion (e.g. most fractional). The algorithm keeps a certain number of candidates (one for classic branching, and several for strong branching). It generates the children of each candidate kept. At last, it returns the candidates kept.

Branching rule

Candidate

Coluna.Branching.generate_children!Function
generate_children!(branching_context, branching_candidate, lhs, env, reform, node)

This method generates the children of a node described by branching_candidate.

source

Selection criterion

Coluna.Branching.AbstractSelectionCriterionType

Supertype of selection criteria of branching candidates.

A selection criterion provides a way to keep only the most promising branching candidates. To create a new selection criterion, one needs to create a subtype of AbstractSelectionCriterion and implements the method select_candidates!.

source

Branching API

Method advanced_select! is part of the API but presented just below.

Advanced candidates selection

If the candidates' selection returns several candidates will all their children, advanced candidates selection must keep only one of them.

The advanced candidates' selection is the place to evaluate the children to get relevant additional key performance indicators about each branching candidate.

Coluna provides the following function for this step.

Coluna has two default implementations for this method:

  • for the classic branching that does nothing because the candidates selection returns 1 candidate
  • for the strong branching that performs several evaluations of the candidates.

Let us focus on the strong branching. Strong branching is a procedure that heuristically selects a branching constraint that potentially gives the best progress of the dual bound. The procedure selects a collection of branching candidates based on their branching rule (done in classic candidate selection) and their score (done in advanced candidate selection). Then, the procedure evaluates the progress of the dual bound in both branches of each branching candidate by solving both potential children using a conquer algorithm. The candidate that has the largest score is chosen to be the branching constraint.

However, the score can be difficult to compute. For instance, when the score is based on dual bound improvement produced by the branching constraint which is time-consuming to evaluate in the context of column generation Therefore, one can let the branching algorithm quickly estimate the score of each candidate and retain the most promising branching candidates. This is called a phase. The goal is to first evaluate a large number of candidates with a very fast conquer algorithm and retain a certain number of promising ones. Then, over the phases, it evaluates the improvement with a more precise conquer algorithm and restricts the number of retained candidates until only one is left.

Strong Branching API

The following methods are part of the API but have a default implementation. We advise you to not change them.

Missing docstring.

Missing docstring for Branching.perform_branching_phase!. Check Documenter's build log for details.

Missing docstring.

Missing docstring for Branching.eval_candidate!. Check Documenter's build log for details.

Score

diff --git a/previews/PR1107/api/colgen/index.html b/previews/PR1107/api/colgen/index.html new file mode 100644 index 000000000..f4ac0f066 --- /dev/null +++ b/previews/PR1107/api/colgen/index.html @@ -0,0 +1,70 @@ + +ColGen · Coluna.jl

Column generation

Coluna provides an interface and generic functions to implement a multi-stage column generation algorithm together with a default implementation of this algorithm.

In this section, we are first going to present the generic functions, the implementation with some theory backgrounds and then give the references of the interface.

You can find the generic functions and the interface in the ColGen submodule and the default implementation in the Algorithm submodule at src/Algorithm/colgen.

Context

The ColGen submodule provides an interface and generic functions to implement a column generation algorithm. The implementation depends on an object called context.

Coluna.ColGen.AbstractColGenContextType

Supertype for the objects to which belongs the implementation of the column generation and that stores any kind of information during the execution of the column generation algorithm.

IMPORTANT: implementation of the column generation mainly depends on the context type.

source

Coluna provides two types of context:

Coluna.Algorithm.ColGenContextType
ColGenContext(reformulation, algo_params) -> ColGenContext

Creates a context to run the default implementation of the column generation algorithm.

source
Coluna.Algorithm.ColGenPrinterContextType
ColGenPrinterContext(reformulation, algo_params) -> ColGenPrinterContext

Creates a context to run the default implementation of the column generation algorithm together with a printer that prints information about the algorithm execution.

source

Generic functions

Generic functions are the core of the column generation algorithm. There are three generic functions:

Coluna.ColGen.run!Function
run!(ctx, env, ip_primal_sol; iter = 1) -> AbstractColGenOutput

Runs the column generation algorithm.

Arguments are:

  • ctx: column generation context
  • env: Coluna environment
  • ip_primal_sol: current best primal solution to the master problem
  • iter: iteration number (default: 1)

This function is responsible for initializing the column generation context, the reformulation, and the stabilization. We iterate on the loop each time the phase or the stage changes.

source

See the main loop section for more details.

Coluna.ColGen.run_colgen_phase!Function
run_colgen_phase!(ctx, phase, stage, env, ip_primal_sol, stab; iter = 1) -> AbstractColGenPhaseOutput

Runs a phase of the column generation algorithm.

Arguments are:

  • ctx: column generation context
  • phase: current column generation phase
  • stage: current column generation stage
  • env: Coluna environment
  • ip_primal_sol: current best primal solution to the master problem
  • stab: stabilization
  • iter: iteration number (default: 1)

This function is responsible for running the column generation iterations until the phase is finished.

source

See the phase loop section for more details.

Coluna.ColGen.run_colgen_iteration!Function
run_colgen_iteration!(context, phase, stage, env, ip_primal_sol, stab) -> AbstractColGenIterationOutput

Runs an iteration of column generation.

Arguments are:

  • context: column generation context
  • phase: current column generation phase
  • stage: current column generation stage
  • env: Coluna environment
  • ip_primal_sol: current best primal solution to the master problem
  • stab: stabilization
source

See the column generation iteration section for more details.

They are independent of any other submodule of Coluna. You can use them to implement your own column generation algorithm.

Reformulation

The default implementation works with a reformulated problem contained in MathProg.Reformulation where master and subproblems are MathProg.Formulation objects.

The master has the following form:

\[\begin{aligned} +\min \quad& \sum_{k \in K} c^k \lambda^k+\bar{c} y & \\ +\text{s.t.} \quad& \sum_{k \in K} A^k \lambda^k+\bar{A} y \geq a & (1)\\ +& l_k \leq \mathbf{1} \lambda^k \leq u_k & (2) \\ +& \bar{l} \leq y \leq \bar{u} & (3) +\end{aligned}\]

where $\lambda$ are the master columns, $y$ are the pure master variables, constraints (1) are the linking constraints, constraints (2) are the convexity constraints that depend on $l_k$ and $u_k$ (e.g. the lower and upper multiplicity of the subproblem $k$ respectively), and constraints (3) are the bounds on the pure master variables.

The subproblems have the following form:

\[\begin{aligned} +\min \quad& cx + 0z \\ +\text{s.t.} \quad& Bx \geq b \\ +& 1 \leq z \leq 1 +\end{aligned}\]

where $x$ are the subproblem variables, $z$ is a setup variable that always takes the value one in a solution to the subproblem.

The coefficients of the columns in constraints (1) and (2) of the master are computed using representative variables of the subproblems. You can read this section (TODO Natacha) to understand how we map the subproblem solutions into master columns.

References:

Main loop

This is a description of how the Coluna.ColGen.run! generic function behaves in the default implementation.

The main loop stops when the Coluna.ColGen.stop_colgen method returns true. This is the case when one of the following conditions holds:

  • the master or a pricing subproblem is infeasible
  • the time limit is reached
  • the maximum number of iterations is reached

Otherwise, the main loop runs until there is no more phase or stage to execute.

The method returns:

References:

Coluna.ColGen.new_outputFunction
new_output(OutputType, colgen_phase_output) -> OutputType

Returns the column generation output where colgen_phase_output is the output of the last column generation phase executed.

source

Phase loop

This is a description of how the Coluna.ColGen.run_colgen_phase! generic function behaves in the default implementation.

This function is responsible for maintaining the incumbent dual bound and the incumbent master IP primal solution.

The phase loop stops when the Coluna.ColGen.stop_colgen_phase method returns true. This is the case when one of the following conditions holds:

  • the maximum number of iterations is reached
  • the time limit is reached
  • the master is infeasible
  • the master is unbounded
  • a pricing subproblem is infeasible
  • a pricing subproblem is unbounded
  • there is no new column generated at the last iteration
  • there is a new constraint or valid inequality in the master
  • the incumbent dual bound and the primal master LP solution value converged

The method returns:

References:

Coluna.ColGen.before_colgen_iterationFunction

Placeholder method called before the column generation iteration. Does nothing by default but can be redefined to print some informations for instance. We strongly advise users against the use of this method to modify the context or the reformulation.

source
Coluna.ColGen.after_colgen_iterationFunction

Placeholder method called after the column generation iteration. Does nothing by default but can be redefined to print some informations for instance. We strongly advise users against the use of this method to modify the context or the reformulation.

source

Phase iterator

In the first iterations, the restricted master LP contains a few columns and may be infeasible. To prevent this, we introduced artificial variables $v$ and we activate/deactivate these variables depending on whether we want to prove the infeasibility of the master LP or find the optimal LP solution. The default implementation provides three phases:

Coluna.Algorithm.ColGenPhase0Type

Phase 0 is a mix of phase 1 and phase 2. It sets a very large cost to artifical variables to force them to be removed from the master LP solution. If the final master LP solution contains artifical variables either the master is infeasible or the cost of artificial variables is not large enough. Phase 1 must be run.

source
Coluna.Algorithm.ColGenPhase1Type

Phase 1 sets the cost of variables to 0 except for artifical variables. The goal is to find a solution to the master LP problem that has no artificial variables.

source
Coluna.Algorithm.ColGenPhase2Type

Phase 2 solves the master LP without artificial variables. To start, it requires a set of columns that forms a feasible solution to the LP master. This set is found with phase 1.

source

Column generation always starts with Phase 0.

The default implementation of the phase iterator belongs to the following type:

Transitions between the phases depend on four conditions:

  • (A) the presence of artificial variables in the master LP solution
  • (B) the generation of new essential constraints (may happen when a new master IP solution is found)
  • (C) the current stage is exact
  • (D) column generation converged

Transitions are the following:

flowchart TB; + id1(Phase 0) + id2(Phase 1) + id3(Phase 2) + id4(end) + id5(error) + id1 --A & !B & C--> id2 + id1 --!A & !B & C & D--> id4 + id1 -- otherwise --> id1 + id2 --!A & !B--> id3 + id2 --A & C & D--> id4 + id2 -- otherwise --> id2 + id3 -- !B & C & D --> id4 + id3 -- otherwise --> id3 + id3 -- B --> id2 + id3 -- A --> id5 + style id5 stroke:#f66

References:

Coluna.ColGen.decrease_stageFunction

Returns the next stage involving a "more exact solver" than the current one. Returns nothing if the algorithm has already reached the exact phase (last phase).

source

Phase output

Coluna.ColGen.new_phase_outputFunction
new_phase_output(OutputType, min_sense, phase, stage, colgen_iter_output, iter, inc_dual_bound) -> OutputType

Returns the column generation phase output.

Arguments of this function are:

  • OutputType: the type of the column generation phase output
  • min_sense: true if it is a minimization problem; false otherwise
  • phase: the current column generation phase
  • stage: the current column generation stage
  • col_gen_iter_output: the last column generation iteration output
  • iter: the last iteration number
  • inc_dual_bound: the current incumbent dual bound
source

Stages

A stage is a set of consecutive iterations in which we use a given pricing solver. The aim is to speed up the resolution of the pricing problem by first using an approximate but fast pricing algorithm and then switching to increasingly less heuristic algorithms until the last stage where an exact solver is used. and an exact solver at the last stage. Given a pricing solver, when the column generation does not progress anymore or the pricing solver does not return any new column, the default implementation switch to a more exact pricing solver. Stages are created using the stages_pricing_solver_ids of the ColumnGenerationAlgorithm parameter object. The default implementation implements the interface around the following object:

Coluna.Algorithm.ColGenStageIteratorType

Default implementation of the column generation stages works as follows.

Consider a set {A,B,C} of subproblems each of them associated to the following sets of pricing solvers: {a1, a2, a3}, {b1, b2}, {c1, c2, c3, c4}. Pricing solvers a1, b1, c1 are exact solvers; others are heuristic.

The column generation algorithm will run the following stages:

  • stage 4 with pricing solvers {a3, b2, c4}
  • stage 3 with pricing solvers {a2, b1, c3}
  • stage 2 with pricing solvers {a1, b1, c2}
  • stage 1 with pricing solvers {a1, b1, c1} (exact stage)

Column generation moves from one stage to another when all solvers find no column.

source

References:

Column generation iteration

This is a description of how the Coluna.ColGen.run_colgen_iteration! generic function behaves in the default implementation.

These are the main steps of a column generation iteration without stabilization. Click on the step to go to the relevant section.

flowchart TB; + id1(Optimize master LP) + id2{{Solution to master LP is integer?}} + id3(Update incumbent primal solution if better than current one) + id4(Compute reduced cost of subproblem variables) + id5{{Subproblem iterator}} + id6(Optimize pricing subproblem) + id7(Push subproblem solution into set) + id8(Compute dual bound) + id9(Insert columns) + id10(Iteration output) + id1 --> id2 + id2 --yes--> id3 + id2 --no--> id4 + id3 --> id4 + id4 --> id5 + id5 --subproblem--> id6 + id6 --> id7 + id7 --> id5 + id5 --end--> id8 + id8 --> id9 + id9 --> id10 + click id1 href "#Optimize-master-LP" "Link to doc" + click id2 href "#Check-integrality-of-the-master-LP-solution" "Link to doc" + click id3 href "#Update-incumbent-primal-solution" "Link to doc" + click id4 href "#Reduced-costs-calculation" "Link to doc" + click id5 href "#Pricing-subproblem-iterator" "Link to doc" + click id6 href "#Pricing-subproblem-optimization" "Link to doc" + click id7 href "#Set-of-generated-columns" "Link to doc" + click id8 href "#Dual-bound-calculation" "Link to doc" + click id9 href "#Columns-insertion" "Link to doc" + click id10 href "#Iteration-output" "Link to doc"

Optimize master LP

At each iteration, the algorithm requires a dual solution to the master LP to compute the reduced cost of subproblem variables.

The default implementation optimizes the master with an LP solver through MathOptInterface. It returns a primal and a dual solution.

In the default implementation, the master LP output is in the following data structure:

Coluna.Algorithm.ColGenMasterResultType

Output of the ColGen.optimize_master_lp_problem! method.

Contains result, an OptimizationState object that is the output of the SolveLpForm algorithm called to optimize the master LP problem.

source

References:

Coluna.ColGen.optimize_master_lp_problem!Function
optimize_master_lp_problem!(master, context, env) -> MasterResult

Returns an instance of a custom object MasterResult that implements the following methods:

  • get_obj_val: objective value of the master (mandatory)
  • get_primal_sol: primal solution to the master (optional)
  • get_dual_sol: dual solution to the master (mandatory otherwise column generation stops)

It should at least return a dual solution (obtained with LP optimization or subgradient) otherwise column generation cannot continue.

source

You can see the additional methods to implement in the result data structures section.

Go back to the column generation iteration overview.

Check the integrality of the master LP solution

The algorithm checks the integrality of the primal solution to the master LP to improve the global primal bound of the branch-cut-price algorithm.

In the default implementation, the integrality check is done using the MathProg.proj_cols_is_integer method. It implements the procedure described in the paper (TODO). Basically, it sorts the column used in the master LP primal solution in lexicographic order. It assigns a weight to each column equal to the value of the column in the master LP solution. It then forms columns of weight one by accumulating the columns of the fractional solution. If columns are integral, the solution is integral. This is a heuristic procedure so it can miss some integer solutions.

If the solution is integral, the essential cut callback is called to make sure it is feasible.

References:

Go back to the column generation iteration overview.

Update incumbent primal solution

If the solution to master LP is integral and better than the current best one, we need to update the incumbent. This solution is then used by the tree-search algorithm in the bounding mechanism that prunes the nodes.

References:

Go back to the column generation iteration overview.

Reduced costs calculation

Reduced costs calculation is written as a math operation in the run_colgen_iteration! generic function. As a consequence, the dual solution to the master LP and the implementation of the two following methods must return data structures that support math operations.

To speed up this operation, we cache data in the following data structure:

Coluna.Algorithm.ReducedCostsCalculationHelperType

Extracted information to speed-up calculation of reduced costs of subproblem representatives and pure master variables. We extract from the master the information we need to compute the reduced cost of DW subproblem variables:

  • dw_subprob_c contains the perenial cost of DW subproblem representative variables
  • dw_subprob_A is a submatrix of the master coefficient matrix that involves only DW subproblem representative variables.

We also extract from the master the information we need to compute the reduced cost of pure master variables:

  • pure_master_c contains the perenial cost of pure master variables
  • pure_master_A is a submatrix of the master coefficient matrix that involves only pure master variables.

Calculation is c - transpose(A) * master_lp_dual_solution.

This information is given to the generic implementation of the column generation algorithm through methods:

  • ColGen.getsubprobvarorigcosts
  • ColGen.getorigcoefmatrix
source

Reduced costs calculation also requires the implementation of the two following methods:

Go back to the column generation iteration overview.

Pricing subproblem iterator

The pricing strategy is basically an iterator used to iterate over the pricing subproblems to optimize at each iteration of the column generation. The context can serve as a memory of the pricing strategy to change the way we iterate over subproblems between each column generation iteration.

The default implementation iterates over all subproblems.

Here are the references for the interface:

Coluna.ColGen.AbstractPricingStrategyType

A pricing strategy defines how we iterate on pricing subproblems. A default pricing strategy consists in iterating on all pricing subproblems.

Basically, this object is used like this:

    pricing_strategy = ColGen.get_pricing_strategy(ctx, phase)
+    next = ColGen.pricing_strategy_iterate(pricing_strategy)
+    while !isnothing(next)
+        (sp_id, sp_to_solve), state = next
+        # Solve the subproblem `sp_to_solve`.
+        next = ColGen.pricing_strategy_iterate(pricing_strategy, state)
+    end
source
Coluna.ColGen.pricing_strategy_iterateFunction
pricing_strategy_iterate(pricing_strategy) -> ((sp_id, sp_to_solve), state)
+pricing_strategy_iterate(pricing_strategy, state) -> ((sp_id, sp_to_solve), state)

Returns an iterator with the first pricing subproblem that must be optimized. The next subproblem is returned by a call to Base.iterate using the information provided by this method.

source

Go back to the column generation iteration overview.

Pricing subproblem optimization

At each iteration, the algorithm requires primal solutions to the pricing subproblems. The generic function supports multi-column generation so you can return any number of solutions.

The default implementation supports optimization of the pricing subproblems using a MILP solver or a pricing callback. Non-robust valid inequalities are not supported by MILP solvers as they change the structure of the subproblems. When using a pricing callback, you must be aware of how Coluna calculates the reduced cost of a column:

The reduced cost of a column is split into three contributions:

  • the contribution of the subproblem variables that is the primal solution cost given the reduced cost of subproblem variables
  • the contribution of the non-robust constraints (i.e. master constraints that cannot be expressed using subproblem variables except the convexity constraint) that is not supported by MILP solver but that you must take into account in the pricing callback
  • the contribution of the master convexity constraint that is automatically taken into account by Coluna once the primal solution is returned.

Therefore, when you use a pricing callback, you must not discard some columns based only on the primal solution cost because you don't know the contribution of the convexity constraint.

Coluna.Algorithm.GeneratedColumnType

Solution to a pricing subproblem after a given optimization.

It contains:

  • column: the solution stored as a PrimalSolution object
  • red_cost: the reduced cost of the column
  • min_obj: a boolean indicating if the objective is to minimize or maximize
source
Coluna.Algorithm.ColGenPricingResultType

Output of the default implementation of ColGen.optimize_pricing_problem!.

It contains:

  • result: the output of the SolveIpForm algorithm called to optimize the pricing subproblem
  • columns: a vector of GeneratedColumn objects obtained by processing of the output of pricing subproblem optimization, it stores the reduced cost of each column
  • best_red_cost: the best reduced cost of the columns
source

References:

Coluna.ColGen.optimize_pricing_problem!Function
optimize_pricing_problem!(ctx, sp, env, optimizer, mast_dual_sol) -> PricingResult

Returns a custom object PricingResult that must implement the following functions:

  • get_primal_sols: array of primal solution to the pricing subproblem
  • get_primal_bound: best reduced cost (optional ?)
  • get_dual_bound: dual bound of the pricing subproblem (used to compute the master dual bound)
  • master_dual_sol: dual solution $\pi^{\text{out}}$ to the master problem used to compute the real reduced cost of the column when stabilization is active
source

You can see the additional methods to implement in the result data structures section.

Go back to the column generation iteration overview.

Set of generated columns

You can define your data structure to manage the columns generated at a given iteration. Columns are inserted after the optimization of all pricing subproblems to allow the parallelization of the latter.

In the default implementation, we use the following data structure:

Coluna.Algorithm.ColumnsSetType

Stores a collection of columns.

It contains:

  • columns: a vector of GeneratedColumn objects by all pricing subproblems that will be inserted into the master
  • subprob_primal_solutions: an object that stores the best columns generated by each pricing subproblem at this iteration.
source
Coluna.Algorithm.SubprobPrimalSolsSetType

Columns generated at the current iteration that forms the "current primal solution". This is used to compute the Lagragian dual bound.

It contains:

  • primal_sols a dictionary that maps a formulation id to the best primal solution found by the pricing subproblem associated to this formulation
  • improve_master a dictionary that maps a formulation id to a boolean indicating if the best primal solution found by the pricing subproblem associated to this formulation has negative reduced cost

This structure also helps to compute the subgradient of the Lagrangian function.

source

In the default implementation, push_in_set! is responsible for checking if the column has improving reduced cost. Only columns with improving reduced cost are inserted in the set. The push_in_set! is also responsible to insert he best primal solution to each pricing problem into the SubprobPrimalSolsSet object.

References:

Coluna.ColGen.set_of_columnsFunction

Returns an empty container that will store all the columns generated by the pricing problems during an iteration of the column generation algorithm. One must be able to iterate on this container to insert the columns in the master problem.

source
Coluna.ColGen.push_in_set!Function

Pushes the column in the set of columns generated at a given iteration of the column generation algorithm. Columns stored in the set will then be considered for insertion in the master problem. Returns true if column was inserted in the set, false otherwise.

source

Go back to the column generation iteration overview.

Dual bound calculation

In the default implementation, given a vector $\pi \geq 0$ of dual values to the master constraints (1), the Lagrangian dual function is given by:

\[L(\pi) = \pi a + \sum_{k \in K} \max_{l_k \leq \mathbf{1} \lambda^k \leq u^k} (c^k - \pi A^k)\lambda^k + \max_{ \bar{l} \leq y \leq \bar{u}} (\bar{c} - \pi \bar{A})y\]

Let:

  • element $z_k(\pi) \leq \min_i (c^k_i - \pi A^k_i)$ be a lower bound on the solution value of the pricing problem
  • element $\bar{z}_j(\pi) = \bar{c} - \pi \bar{A}$ be the reduced cost of pure master variable $y_j$

Then, the Lagrangian dual function can be lower bounded by:

\[L(\pi) \geq \pi a + \sum_{k \in K} \max\{ z_k(\pi) \cdot l_k, z_k(\pi) \cdot u_k \} + \sum_{j \in J} \max\{ \bar{z}_j(\pi) \cdot \bar{l}_j, \bar{z}_j(\pi) \cdot \bar{u}_j\}\]

More precisely:

  • the first term is the contribution of the master obtained by removing the contribution of the convexity constraints (computed by ColGen.Algorithm._convexity_contrib), and the pure master variables (but you should see the third term) from the master LP solution value
  • the second term is the contribution of the subproblem variables which is the sum of the best solution value of each pricing subproblem multiplied by the lower and upper multiplicity of the subproblem depending on whether the reduced cost is negative or positive (this is computed by ColGen.Algorithm._subprob_contrib)
  • the third term is the contribution of the pure master variables which is taken into account by master LP value.

Therefore, we can compute the Lagrangian dual bound as follows:

master_lp_obj_val - convexity_contrib + sp_contrib

However, if the smoothing stabilization is active, we compute the dual bound at the sep-point. As a consequence, we can't use the master LP value because it corresponds to the dual solution at the out-point. We therefore need to compute the lagrangian dual bound by strictly applying the above formula.

References:

Coluna.ColGen.compute_dual_boundFunction
compute_dual_bound(ctx, phase, master_lp_obj_val, master_dbs, generated_columns, mast_dual_sol) -> Float64

Caculates the dual bound at a given iteration of column generation. The dual bound is composed of:

  • master_lp_obj_val: objective value of the master LP problem
  • master_dbs: dual values of the pricing subproblems
  • the contribution of the master convexity constraints that you should compute from mast_dual_sol.
source

Go back to the column generation iteration overview.

Columns insertion

The default implementation inserts into the master all the columns stored in the ColumnsSet object.

Reference:

Coluna.ColGen.insert_columns!Function

Inserts columns into the master. Returns the number of columns inserted. Implementation is responsible for checking if the column must be inserted and warn the user if something unexpected happens.

source

Go back to the column generation iteration overview.

Iteration output

References:

Coluna.ColGen.new_iteration_outputFunction
new_iteration_output(::Type{<:AbstractColGenIterationOutput}, args...) -> AbstractColGenIterationOutput

Arguments (i.e. arg...) of this function are the following:

  • min_sense: true if the objective is a minimization function; false otherwise
  • mlp: the optimal solution value of the master LP
  • db: the Lagrangian dual bound
  • nb_new_cols: the number of columns inserted into the master
  • new_cut_in_master: true if valid inequalities or new constraints added into the master; false otherwise
  • infeasible_master: true if the master is proven infeasible; false otherwise
  • unbounded_master: true if the master is unbounded; false otherwise
  • infeasible_subproblem: true if a pricing subproblem is proven infeasible; false otherwise
  • unbounded_subproblem: true if a pricing subproblem is unbounded; false otherwise
  • time_limit_reached: true if time limit is reached; false otherwise
  • master_primal_sol: the primal master LP solution
  • ip_primal_sol: the incumbent primal master solution
  • dual_sol: the dual master LP solution
source

Go back to the column generation iteration overview.

Getters for Result data structures

Method nameMasterPricing
is_unboundedXX
is_infeasibleXX
get_primal_solX
get_primal_solsX
get_dual_solX
get_obj_valX
get_primal_boundX
get_dual_boundX

References:

Coluna.ColGen.get_primal_boundFunction

Returns primal bound of the pricing subproblem; nothing if no primal bound is available and the initial dual bound returned by compute_sp_init_pb will be used to compute the pseudo dual bound.

source

Go back to the column generation iteration overview.

Getters for Output data structures

Method nameColGenPhaseIteration
get_nb_new_colsX
get_master_ip_primal_solXXX
get_master_lp_primal_solX
get_master_dual_solX
get_dual_boundXX
get_master_lp_primal_boundX
is_infeasibleX

References:

Go back to the column generation iteration overview.

Stabilization

Coluna provides a default implementation of the smoothing stabilization with a self-adjusted $\alpha$ parameter, $0 \leq \alpha < 1$.

At each iteration of the column generation algorithm, instead of generating columns for the dual solution to the master LP, we generate columns for a perturbed dual solution defined as follows:

\[\pi^{\text{sep}} = \alpha \pi^{\text{in}} + (1-\alpha) \pi^{\text{out}}\]

where $\pi^{\text{in}}$ is the dual solution that gives the best Lagrangian dual bound so far (also called stabilization center) and $\pi^{\text{out}}$ is the dual solution to the master LP at the current iteration. This solution is returned by the default implementation of Coluna.ColGen.get_stab_dual_sol.

Some elements of the column generation change when using stabilization.

  • Columns are generated using the smoothed dual solution $\pi^{\text{sep}}$ but we still need to compute the reduced cost of the columns using the original dual solution $\pi^{\text{out}}$.
  • The dual bound is computed using the smoothed dual solution $\pi^{\text{sep}}$.
  • The pseudo bound is computed using the smoothed dual solution $\pi^{\text{sep}}$.
  • The smoothed dual bound can result in the generation of no improving columns. This is called a misprice. In that case, we need to move away from the stabilization center $\pi^{\text{in}}$ by decreasing $\alpha$.

When using self-adjusted stabilization, the smoothing coefficient $\alpha$ is adjusted to make the smoothed dual solution $\pi^{\text{sep}}$ closer to the best possible dual solution on the line between $\pi^{\text{in}}$ and $\pi^{\text{out}}$ (i.e. where the subgradient of the current primal solution is perpendicular to the latter line). To compute the subgradient, we use the following data structure:

Coluna.Algorithm.SubgradientCalculationHelperType

Precompute information to speed-up calculation of subgradient of master variables. We extract from the master follwowing information:

  • a contains the perenial rhs of all master constraints except convexity constraints;
  • A is a submatrix of the master coefficient matrix that involves only representative of original variables (pure master vars + DW subproblem represtative vars)

Calculation is a - A * (m .* z) where :

  • m contains a multiplicity factor for each variable involved in the calculation (lower or upper sp multiplicity depending on variable reduced cost);
  • z is the concatenation of the solution to the master (for pure master vars) and pricing subproblems (for DW subproblem represtative vars).

Operation m .* z "mimics" a solution in the original space.

source

References:

Coluna.ColGen.update_stabilization_after_master_optim!Function
update_stabilization_after_master_optim!(stab, phase, mast_dual_sol) -> Bool

Update stabilization after master optimization where mast_dual_sol is the dual solution to the master problem. Returns true if the stabilization will change the dual solution used for the pricing in the current column generation iteration, false otherwise.

source
Coluna.ColGen.update_stabilization_after_pricing_optim!Function

Updates stabilization after pricing optimization where:

  • mast_dual_sol is the dual solution to the master problem
  • pseudo_db is the pseudo dual bound of the problem after optimization of the pricing problems
  • smooth_dual_sol is the current smoothed dual solution
source
Coluna.ColGen.update_stabilization_after_iter!Function

Updates stabilization after an iteration of the column generation algorithm. Arguments:

  • stab is the stabilization data structure
  • ctx is the column generation context
  • master is the master problem
  • generated_columns is the set of generated columns
  • mast_dual_sol is the dual solution to the master problem
source
diff --git a/previews/PR1107/api/presolve/index.html b/previews/PR1107/api/presolve/index.html new file mode 100644 index 000000000..044ebf831 --- /dev/null +++ b/previews/PR1107/api/presolve/index.html @@ -0,0 +1,7 @@ + +Presolve · Coluna.jl

Presolve

Currently, the presolve algorithm supports only the Dantzig-Wolfe decomposition.

The presolve algorithm operates on matrix representations of the formulation. It requires two representations of the master formulation:

  • the restricted master that contains master column variables, pure master variables and artificial variables;

  • the representative master that contains subproblem representative variables and pure master variables;

and the representation of the pricing subproblems.

The current presolve operations available are the following (taxonomy of Achterberg et al. 2016):

  • model cleanup & removal of redundant constraints
  • bound strengthening
  • removal of fixed variables

Partial solution

The presolve algorithm has the responsibility to define and fix a partial solution when it exists. When a variable $x$ has a value $\bar{x} > 0$ (resp. $\bar{x} < 0$) in the partial solution, it means that $x$ has a lower (resp. upper) bounds $\bar{x}$ that will definitely be part of the solution at the current branch-and-bound node and its successors.

In other words, the partial solution describes a minimal distance of the variables from zero in the all the solutions to a problem at a given branch-and-bound node. It always restricts the domain of the variables (i.e. increase distance from zero). The only way to relax the domains is to backtrack to an ancestor of the current branch-and-bound node (i.e. go back to a previous partial solution).

Augmenting the partial solution

Consider a local partial solution $(\bar{x}^{\rm pure}, \bar{\lambda})$ (where $\bar{x}^{\rm pure}$ is the vector of values for pure master variables, and $\bar{\lambda}$ is the vector of values for master columns), which should be added to the global partial solution $(\bar{y}^{\rm pure}, \bar{\theta})$:

  1. augment the global partial solution: $(\bar{y}^{\rm pure}, \bar{\theta})\leftarrow(\bar{x}^{\rm pure}+\bar{y}^{\rm pure}, \bar{\lambda}+\bar{\theta})$.

  2. update the right-hand side values of the master constraints: ${\rm rhs}_i\leftarrow {\rm rhs}_i - {A}^{\rm pure}\cdot\bar{x}^{\rm pure} - {A}^{\rm col}\cdot\bar{ \lambda}$, where ${A}^{\rm pure}$ is the matrix of coefficients of pure master constraints and ${A}^{\rm col}$ is the matrix of coefficients of master columns.

  3. update subproblem multiplicities $U_k\leftarrow U_k - \sum_{q\in Q_k}\bar\lambda_q$, and $L_k\leftarrow \max\left\{0,\; L_k - \sum_{q\in Q_k}\bar\lambda_q\right\}$, where $Q_k$ is the set of indices of columns associated with solutions from subproblem $k$.

  4. update the bounds of pure master variables and representative master variables using the representative local partial solution: $\bar{x}^{\rm repr} = \sum_{q\in Q}{s_q}\cdot \bar\lambda_q$, where $Q$ is the total number of columns, and ${s_q}$ is the subproblem solution associated with column $\lambda_q$.

Updating bounds of pure & representative master variables

Consider a pure master variable $x^{\rm pure}_j$ with $\bar{x}^{\rm pure}_j\neq 0$ and bounds $[lb_j,ub_j]$ before augmenting the partial solution.

If $\bar{x}^{\rm pure}_j > 0$, then we have $lb_j\leftarrow 0$, $ub_j\leftarrow ub_j - \bar{x}^{\rm pure}_j$.

If $\bar{x}^{\rm pure}_j < 0$, then we have $lb_j\leftarrow lb_j - \bar{x}^{\rm pure}_j$, $ub_j\leftarrow 0$.

Consider a representative master variable $x^{\rm repr}_j$ with bounds $[lb^g_j, ub^g_j]$ before augmenting the partial solution. Assume that $x^{\rm repr}_j$ represents exactly one variable $x^k_j$ in subproblem $k$ with bounds $[lb^l_j, ub^l_j]$ before augmenting the partial solution. This assumption should be verified before augmenting the partial solution! For the clarity of presentation, we omit index $j$ for the remainder of this section.

After augmenting the partial solution, the following inequalities should be satisfied: $ lb^g - \bar{x}^{\rm repr}\leq x^{\rm repr} \leq ub^g - \bar{x}^{\rm repr}.$

At the same time, we should have $\min\{lb^l\cdot L_k,\; lb^l\cdot U_k\}\leq x^{\rm repr}\leq \max\{ub^l\cdot U_k,\; ub^l\cdot L_k\}$

Thus, the following update should be done $ lb^g\leftarrow \max\left{lb^g - \bar{x}^{\rm repr},\; \min{lb^l\cdot Lk,\; lb^l\cdot Uk}\right}$ $ ub^g\leftarrow \min\left{ub^g - \bar{x}^{\rm repr},\; \max{ub^l\cdot Uk,\; ub^l\cdot Lk}\right}$

Example 1: $0\leq x^k\leq 3$, $0\leq x^{\rm repr}\leq 6$, $L_k=0$, $U_k=2$. Let $\bar{x}^{\rm repr}=2$. Then after augmenting the partial solution, we have $ \max\left{-2,\; 0\right}\leq x^{\rm repr} \leq \min\left{4,\; 3\right} \Rightarrow 0 \leq x^{\rm repr} \leq 3$

Example 2: $0\leq x^k\leq 5$, $3\leq x^{\rm repr}\leq 6$, $L_k=0$, $U_k=2$. Let $\bar{x}^{\rm repr}=2$. Then after augmenting the partial solution, we have $ \max\left{1,\; 0\right}\leq x'{\rm repr} \leq \min\left{4,\; 5\right} \Rightarrow 1 \leq x'{\rm repr} \leq 4$

Example 3: $-1\leq x^k\leq 4$, $-2\leq x^{\rm repr}\leq 2$, $L_k=0$, $U_k=2$. Let $\bar{x}^{\rm repr}=-1$. Then after augmenting the partial solution, we have $ \max\left{-1,\; -1\right}\leq x^{\rm repr} \leq \min\left{3,\; 4\right} \Rightarrow -1 \leq x^{\rm repr} \leq 3$

diff --git a/previews/PR1107/api/storage.jl b/previews/PR1107/api/storage.jl new file mode 100644 index 000000000..dea945268 --- /dev/null +++ b/previews/PR1107/api/storage.jl @@ -0,0 +1,31 @@ +# # Storage API + +# ```@meta +# CurrentModule = Coluna +# ``` + + +# ## API + +# To summarize from a developer's point of view, there is a one-to-one correspondence between +# storage unit types and record types. +# This correspondence is implemented by methods +# `record_type(StorageUnitType)` and `storage_unit_type(RecordType)`. + +# The developer must also implement methods `storage_unit(StorageUnitType)` and +# `record(RecordType, id, model, storage_unit)` that must call constructors of the custom +# storage unit and one of its associated records. +# Arguments of `record` allow the developer to record the state of entities from +# both the storage unit and the model. + +# At last, he must implement `restore_from_record!(storage_unit, model, record)` to restore the +# state of the entities represented by the storage unit. +# Entities can be in the storage unit, the model, or both of them. + +# ```@docs +# ColunaBase.record_type +# ColunaBase.storage_unit_type +# ColunaBase.storage_unit +# ColunaBase.record +# ColunaBase.restore_from_record! +# ``` \ No newline at end of file diff --git a/previews/PR1107/api/storage/index.html b/previews/PR1107/api/storage/index.html new file mode 100644 index 000000000..510da8782 --- /dev/null +++ b/previews/PR1107/api/storage/index.html @@ -0,0 +1,7 @@ + +Storage · Coluna.jl

Storage API

API

To summarize from a developer's point of view, there is a one-to-one correspondence between storage unit types and record types. This correspondence is implemented by methods record_type(StorageUnitType) and storage_unit_type(RecordType).

The developer must also implement methods storage_unit(StorageUnitType) and record(RecordType, id, model, storage_unit) that must call constructors of the custom storage unit and one of its associated records. Arguments of record allow the developer to record the state of entities from both the storage unit and the model.

At last, he must implement restore_from_record!(storage_unit, model, record) to restore the state of the entities represented by the storage unit. Entities can be in the storage unit, the model, or both of them.


This page was generated using Literate.jl.

diff --git a/previews/PR1107/api/treesearch/index.html b/previews/PR1107/api/treesearch/index.html new file mode 100644 index 000000000..7bf005118 --- /dev/null +++ b/previews/PR1107/api/treesearch/index.html @@ -0,0 +1,7 @@ + +TreeSearch · Coluna.jl

Tree search API

Danger

Update needed.

Now, we define the two concepts we'll use in the tree search algorithms: the node and the search space. The third concept is the explore strategy and implemented in Coluna.

Every tree search algorithm must be associated to a search space.

Implementing tree search interface

First, we indicate the type of search space used by our algorithms. Note that the type of the search space can depends on the configuration of the algorithm. So there is a 1-to-n relation between tree search algorithm configurations and search space. because one search space can be used by several tree search algorithms configuration.

Now, we implement the method that calls the constructor of a search space. The type of the search space is known from above method. A search space may receive information from the tree-search algorithm. The model, and input arguments are the same than those received by the tree search algorithm.

We implement the method that returns the root node. The definition of the root node depends on the search space.

Then, we implement the method that converts the branching rules into nodes for the tree search algorithm.

We implement the node_change method to update the search space called by the tree search algorithm just after it finishes to evaluate a node and chooses the next one. Be careful, this method is not called after the evaluation of a node when there is no more unevaluated nodes (i.e. tree exploration is finished).

There are two ways to store the state of a formulation at a given node. We can distribute information across the nodes or store the whole state at each node. We follow the second way (so we don't need previous).

Method after_conquer is a callback to do some operations after the conquer of a node and before the divide. Here, we update the best solution found after the conquer algorithm. We implement one method for each search space.

We implement getters to retrieve the input from the search space and the node. The input is passed to the conquer and the divide algorithms.

At last, we implement methods that will return the output of the tree search algorithms. We return the cost of the best solution found. We write one method for each search space.

API

Search space

Node

Additional methods needed for Coluna's algorithms:

Missing docstring.

Missing docstring for Coluna.TreeSearch.get_opt_state. Check Documenter's build log for details.

Missing docstring.

Missing docstring for Coluna.TreeSearch.get_records. Check Documenter's build log for details.

Tree search algorithm

Tree search algorithm for Coluna

The children method has a specific implementation for AbstractColunaSearchSpace` that involves following methods:

Coluna.Algorithm.node_change!Function

Methods to perform operations before the tree search algorithm evaluates a node (current). This is useful to restore the state of the formulation for instance.

source
Coluna.Algorithm.get_inputFunction

Returns the input that will be passed to an algorithm. The input can be built from information contained in a search space and a node.

source

This page was generated using Literate.jl.

diff --git a/previews/PR1107/assets/documenter.js b/previews/PR1107/assets/documenter.js new file mode 100644 index 000000000..7002e2519 --- /dev/null +++ b/previews/PR1107/assets/documenter.js @@ -0,0 +1,874 @@ +// Generated by Documenter.jl +requirejs.config({ + paths: { + 'highlight-julia': 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/languages/julia.min', + 'headroom': 'https://cdnjs.cloudflare.com/ajax/libs/headroom/0.12.0/headroom.min', + 'jqueryui': 'https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.13.2/jquery-ui.min', + 'minisearch': 'https://cdn.jsdelivr.net/npm/minisearch@6.1.0/dist/umd/index.min', + 'katex-auto-render': 'https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.8/contrib/auto-render.min', + 'jquery': 'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.0/jquery.min', + 'headroom-jquery': 'https://cdnjs.cloudflare.com/ajax/libs/headroom/0.12.0/jQuery.headroom.min', + 'katex': 'https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.8/katex.min', + 'highlight': 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/highlight.min', + 'highlight-julia-repl': 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/languages/julia-repl.min', + }, + shim: { + "highlight-julia": { + "deps": [ + "highlight" + ] + }, + "katex-auto-render": { + "deps": [ + "katex" + ] + }, + "headroom-jquery": { + "deps": [ + "jquery", + "headroom" + ] + }, + "highlight-julia-repl": { + "deps": [ + "highlight" + ] + } +} +}); +//////////////////////////////////////////////////////////////////////////////// +require(['jquery', 'katex', 'katex-auto-render'], function($, katex, renderMathInElement) { +$(document).ready(function() { + renderMathInElement( + document.body, + { + "delimiters": [ + { + "left": "$", + "right": "$", + "display": false + }, + { + "left": "$$", + "right": "$$", + "display": true + }, + { + "left": "\\[", + "right": "\\]", + "display": true + } + ] +} + + ); +}) + +}) +//////////////////////////////////////////////////////////////////////////////// +require(['jquery', 'highlight', 'highlight-julia', 'highlight-julia-repl'], function($) { +$(document).ready(function() { + hljs.highlightAll(); +}) + +}) +//////////////////////////////////////////////////////////////////////////////// +require(['jquery'], function($) { + +var isExpanded = true; + +$(document).on("click", ".docstring header", function () { + let articleToggleTitle = "Expand docstring"; + + if ($(this).siblings("section").is(":visible")) { + $(this) + .find(".docstring-article-toggle-button") + .removeClass("fa-chevron-down") + .addClass("fa-chevron-right"); + } else { + $(this) + .find(".docstring-article-toggle-button") + .removeClass("fa-chevron-right") + .addClass("fa-chevron-down"); + + articleToggleTitle = "Collapse docstring"; + } + + $(this) + .find(".docstring-article-toggle-button") + .prop("title", articleToggleTitle); + $(this).siblings("section").slideToggle(); +}); + +$(document).on("click", ".docs-article-toggle-button", function () { + let articleToggleTitle = "Expand docstring"; + let navArticleToggleTitle = "Expand all docstrings"; + + if (isExpanded) { + $(this).removeClass("fa-chevron-up").addClass("fa-chevron-down"); + $(".docstring-article-toggle-button") + .removeClass("fa-chevron-down") + .addClass("fa-chevron-right"); + + isExpanded = false; + + $(".docstring section").slideUp(); + } else { + $(this).removeClass("fa-chevron-down").addClass("fa-chevron-up"); + $(".docstring-article-toggle-button") + .removeClass("fa-chevron-right") + .addClass("fa-chevron-down"); + + isExpanded = true; + articleToggleTitle = "Collapse docstring"; + navArticleToggleTitle = "Collapse all docstrings"; + + $(".docstring section").slideDown(); + } + + $(this).prop("title", navArticleToggleTitle); + $(".docstring-article-toggle-button").prop("title", articleToggleTitle); +}); + +}) +//////////////////////////////////////////////////////////////////////////////// +require([], function() { +function addCopyButtonCallbacks() { + for (const el of document.getElementsByTagName("pre")) { + const button = document.createElement("button"); + button.classList.add("copy-button", "fa-solid", "fa-copy"); + button.setAttribute("aria-label", "Copy this code block"); + button.setAttribute("title", "Copy"); + + el.appendChild(button); + + const success = function () { + button.classList.add("success", "fa-check"); + button.classList.remove("fa-copy"); + }; + + const failure = function () { + button.classList.add("error", "fa-xmark"); + button.classList.remove("fa-copy"); + }; + + button.addEventListener("click", function () { + copyToClipboard(el.innerText).then(success, failure); + + setTimeout(function () { + button.classList.add("fa-copy"); + button.classList.remove("success", "fa-check", "fa-xmark"); + }, 5000); + }); + } +} + +function copyToClipboard(text) { + // clipboard API is only available in secure contexts + if (window.navigator && window.navigator.clipboard) { + return window.navigator.clipboard.writeText(text); + } else { + return new Promise(function (resolve, reject) { + try { + const el = document.createElement("textarea"); + el.textContent = text; + el.style.position = "fixed"; + el.style.opacity = 0; + document.body.appendChild(el); + el.select(); + document.execCommand("copy"); + + resolve(); + } catch (err) { + reject(err); + } finally { + document.body.removeChild(el); + } + }); + } +} + +if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", addCopyButtonCallbacks); +} else { + addCopyButtonCallbacks(); +} + +}) +//////////////////////////////////////////////////////////////////////////////// +require(['jquery', 'headroom', 'headroom-jquery'], function($, Headroom) { + +// Manages the top navigation bar (hides it when the user starts scrolling down on the +// mobile). +window.Headroom = Headroom; // work around buggy module loading? +$(document).ready(function () { + $("#documenter .docs-navbar").headroom({ + tolerance: { up: 10, down: 10 }, + }); +}); + +}) +//////////////////////////////////////////////////////////////////////////////// +require(['jquery', 'minisearch'], function($, minisearch) { + +// In general, most search related things will have "search" as a prefix. +// To get an in-depth about the thought process you can refer: https://hetarth02.hashnode.dev/series/gsoc + +let results = []; +let timer = undefined; + +let data = documenterSearchIndex["docs"].map((x, key) => { + x["id"] = key; // minisearch requires a unique for each object + return x; +}); + +// list below is the lunr 2.1.3 list minus the intersect with names(Base) +// (all, any, get, in, is, only, which) and (do, else, for, let, where, while, with) +// ideally we'd just filter the original list but it's not available as a variable +const stopWords = new Set([ + "a", + "able", + "about", + "across", + "after", + "almost", + "also", + "am", + "among", + "an", + "and", + "are", + "as", + "at", + "be", + "because", + "been", + "but", + "by", + "can", + "cannot", + "could", + "dear", + "did", + "does", + "either", + "ever", + "every", + "from", + "got", + "had", + "has", + "have", + "he", + "her", + "hers", + "him", + "his", + "how", + "however", + "i", + "if", + "into", + "it", + "its", + "just", + "least", + "like", + "likely", + "may", + "me", + "might", + "most", + "must", + "my", + "neither", + "no", + "nor", + "not", + "of", + "off", + "often", + "on", + "or", + "other", + "our", + "own", + "rather", + "said", + "say", + "says", + "she", + "should", + "since", + "so", + "some", + "than", + "that", + "the", + "their", + "them", + "then", + "there", + "these", + "they", + "this", + "tis", + "to", + "too", + "twas", + "us", + "wants", + "was", + "we", + "were", + "what", + "when", + "who", + "whom", + "why", + "will", + "would", + "yet", + "you", + "your", +]); + +let index = new minisearch({ + fields: ["title", "text"], // fields to index for full-text search + storeFields: ["location", "title", "text", "category", "page"], // fields to return with search results + processTerm: (term) => { + let word = stopWords.has(term) ? null : term; + if (word) { + // custom trimmer that doesn't strip @ and !, which are used in julia macro and function names + word = word + .replace(/^[^a-zA-Z0-9@!]+/, "") + .replace(/[^a-zA-Z0-9@!]+$/, ""); + } + + return word ?? null; + }, + // add . as a separator, because otherwise "title": "Documenter.Anchors.add!", would not find anything if searching for "add!", only for the entire qualification + tokenize: (string) => string.split(/[\s\-\.]+/), + // options which will be applied during the search + searchOptions: { + boost: { title: 100 }, + fuzzy: 2, + processTerm: (term) => { + let word = stopWords.has(term) ? null : term; + if (word) { + word = word + .replace(/^[^a-zA-Z0-9@!]+/, "") + .replace(/[^a-zA-Z0-9@!]+$/, ""); + } + + return word ?? null; + }, + tokenize: (string) => string.split(/[\s\-\.]+/), + }, +}); + +index.addAll(data); + +let filters = [...new Set(data.map((x) => x.category))]; +var modal_filters = make_modal_body_filters(filters); +var filter_results = []; + +$(document).on("keyup", ".documenter-search-input", function (event) { + // Adding a debounce to prevent disruptions from super-speed typing! + debounce(() => update_search(filter_results), 300); +}); + +$(document).on("click", ".search-filter", function () { + if ($(this).hasClass("search-filter-selected")) { + $(this).removeClass("search-filter-selected"); + } else { + $(this).addClass("search-filter-selected"); + } + + // Adding a debounce to prevent disruptions from crazy clicking! + debounce(() => get_filters(), 300); +}); + +/** + * A debounce function, takes a function and an optional timeout in milliseconds + * + * @function callback + * @param {number} timeout + */ +function debounce(callback, timeout = 300) { + clearTimeout(timer); + timer = setTimeout(callback, timeout); +} + +/** + * Make/Update the search component + * + * @param {string[]} selected_filters + */ +function update_search(selected_filters = []) { + let initial_search_body = ` +
Type something to get started!
+ `; + + let querystring = $(".documenter-search-input").val(); + + if (querystring.trim()) { + results = index.search(querystring, { + filter: (result) => { + // Filtering results + if (selected_filters.length === 0) { + return result.score >= 1; + } else { + return ( + result.score >= 1 && selected_filters.includes(result.category) + ); + } + }, + }); + + let search_result_container = ``; + let search_divider = `
`; + + if (results.length) { + let links = []; + let count = 0; + let search_results = ""; + + results.forEach(function (result) { + if (result.location) { + // Checking for duplication of results for the same page + if (!links.includes(result.location)) { + search_results += make_search_result(result, querystring); + count++; + } + + links.push(result.location); + } + }); + + let result_count = `
${count} result(s)
`; + + search_result_container = ` +
+ ${modal_filters} + ${search_divider} + ${result_count} +
+ ${search_results} +
+
+ `; + } else { + search_result_container = ` +
+ ${modal_filters} + ${search_divider} +
0 result(s)
+
+
No result found!
+ `; + } + + if ($(".search-modal-card-body").hasClass("is-justify-content-center")) { + $(".search-modal-card-body").removeClass("is-justify-content-center"); + } + + $(".search-modal-card-body").html(search_result_container); + } else { + filter_results = []; + modal_filters = make_modal_body_filters(filters, filter_results); + + if (!$(".search-modal-card-body").hasClass("is-justify-content-center")) { + $(".search-modal-card-body").addClass("is-justify-content-center"); + } + + $(".search-modal-card-body").html(initial_search_body); + } +} + +/** + * Make the modal filter html + * + * @param {string[]} filters + * @param {string[]} selected_filters + * @returns string + */ +function make_modal_body_filters(filters, selected_filters = []) { + let str = ``; + + filters.forEach((val) => { + if (selected_filters.includes(val)) { + str += `${val}`; + } else { + str += `${val}`; + } + }); + + let filter_html = ` +
+ Filters: + ${str} +
+ `; + + return filter_html; +} + +/** + * Make the result component given a minisearch result data object and the value of the search input as queryString. + * To view the result object structure, refer: https://lucaong.github.io/minisearch/modules/_minisearch_.html#searchresult + * + * @param {object} result + * @param {string} querystring + * @returns string + */ +function make_search_result(result, querystring) { + let search_divider = `
`; + let display_link = + result.location.slice(Math.max(0), Math.min(50, result.location.length)) + + (result.location.length > 30 ? "..." : ""); // To cut-off the link because it messes with the overflow of the whole div + + if (result.page !== "") { + display_link += ` (${result.page})`; + } + + let textindex = new RegExp(`\\b${querystring}\\b`, "i").exec(result.text); + let text = + textindex !== null + ? result.text.slice( + Math.max(textindex.index - 100, 0), + Math.min( + textindex.index + querystring.length + 100, + result.text.length + ) + ) + : ""; // cut-off text before and after from the match + + let display_result = text.length + ? "..." + + text.replace( + new RegExp(`\\b${querystring}\\b`, "i"), // For first occurrence + '$&' + ) + + "..." + : ""; // highlights the match + + let in_code = false; + if (!["page", "section"].includes(result.category.toLowerCase())) { + in_code = true; + } + + // We encode the full url to escape some special characters which can lead to broken links + let result_div = ` + +
+
${result.title}
+
${result.category}
+
+

+ ${display_result} +

+
+ ${display_link} +
+
+ ${search_divider} + `; + + return result_div; +} + +/** + * Get selected filters, remake the filter html and lastly update the search modal + */ +function get_filters() { + let ele = $(".search-filters .search-filter-selected").get(); + filter_results = ele.map((x) => $(x).text().toLowerCase()); + modal_filters = make_modal_body_filters(filters, filter_results); + update_search(filter_results); +} + +}) +//////////////////////////////////////////////////////////////////////////////// +require(['jquery'], function($) { + +// Modal settings dialog +$(document).ready(function () { + var settings = $("#documenter-settings"); + $("#documenter-settings-button").click(function () { + settings.toggleClass("is-active"); + }); + // Close the dialog if X is clicked + $("#documenter-settings button.delete").click(function () { + settings.removeClass("is-active"); + }); + // Close dialog if ESC is pressed + $(document).keyup(function (e) { + if (e.keyCode == 27) settings.removeClass("is-active"); + }); +}); + +}) +//////////////////////////////////////////////////////////////////////////////// +require(['jquery'], function($) { + +let search_modal_header = ` + +`; + +let initial_search_body = ` +
Type something to get started!
+`; + +let search_modal_footer = ` + +`; + +$(document.body).append( + ` + + ` +); + +document.querySelector(".docs-search-query").addEventListener("click", () => { + openModal(); +}); + +document.querySelector(".close-search-modal").addEventListener("click", () => { + closeModal(); +}); + +$(document).on("click", ".search-result-link", function () { + closeModal(); +}); + +document.addEventListener("keydown", (event) => { + if ((event.ctrlKey || event.metaKey) && event.key === "/") { + openModal(); + } else if (event.key === "Escape") { + closeModal(); + } + + return false; +}); + +// Functions to open and close a modal +function openModal() { + let searchModal = document.querySelector("#search-modal"); + + searchModal.classList.add("is-active"); + document.querySelector(".documenter-search-input").focus(); +} + +function closeModal() { + let searchModal = document.querySelector("#search-modal"); + let initial_search_body = ` +
Type something to get started!
+ `; + + searchModal.classList.remove("is-active"); + document.querySelector(".documenter-search-input").blur(); + + if (!$(".search-modal-card-body").hasClass("is-justify-content-center")) { + $(".search-modal-card-body").addClass("is-justify-content-center"); + } + + $(".documenter-search-input").val(""); + $(".search-modal-card-body").html(initial_search_body); +} + +document + .querySelector("#search-modal .modal-background") + .addEventListener("click", () => { + closeModal(); + }); + +}) +//////////////////////////////////////////////////////////////////////////////// +require(['jquery'], function($) { + +// Manages the showing and hiding of the sidebar. +$(document).ready(function () { + var sidebar = $("#documenter > .docs-sidebar"); + var sidebar_button = $("#documenter-sidebar-button"); + sidebar_button.click(function (ev) { + ev.preventDefault(); + sidebar.toggleClass("visible"); + if (sidebar.hasClass("visible")) { + // Makes sure that the current menu item is visible in the sidebar. + $("#documenter .docs-menu a.is-active").focus(); + } + }); + $("#documenter > .docs-main").bind("click", function (ev) { + if ($(ev.target).is(sidebar_button)) { + return; + } + if (sidebar.hasClass("visible")) { + sidebar.removeClass("visible"); + } + }); +}); + +// Resizes the package name / sitename in the sidebar if it is too wide. +// Inspired by: https://github.com/davatron5000/FitText.js +$(document).ready(function () { + e = $("#documenter .docs-autofit"); + function resize() { + var L = parseInt(e.css("max-width"), 10); + var L0 = e.width(); + if (L0 > L) { + var h0 = parseInt(e.css("font-size"), 10); + e.css("font-size", (L * h0) / L0); + // TODO: make sure it survives resizes? + } + } + // call once and then register events + resize(); + $(window).resize(resize); + $(window).on("orientationchange", resize); +}); + +// Scroll the navigation bar to the currently selected menu item +$(document).ready(function () { + var sidebar = $("#documenter .docs-menu").get(0); + var active = $("#documenter .docs-menu .is-active").get(0); + if (typeof active !== "undefined") { + sidebar.scrollTop = active.offsetTop - sidebar.offsetTop - 15; + } +}); + +}) +//////////////////////////////////////////////////////////////////////////////// +require(['jquery'], function($) { + +// Theme picker setup +$(document).ready(function () { + // onchange callback + $("#documenter-themepicker").change(function themepick_callback(ev) { + var themename = $("#documenter-themepicker option:selected").attr("value"); + if (themename === "auto") { + // set_theme(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'); + window.localStorage.removeItem("documenter-theme"); + } else { + // set_theme(themename); + window.localStorage.setItem("documenter-theme", themename); + } + // We re-use the global function from themeswap.js to actually do the swapping. + set_theme_from_local_storage(); + }); + + // Make sure that the themepicker displays the correct theme when the theme is retrieved + // from localStorage + if (typeof window.localStorage !== "undefined") { + var theme = window.localStorage.getItem("documenter-theme"); + if (theme !== null) { + $("#documenter-themepicker option").each(function (i, e) { + e.selected = e.value === theme; + }); + } + } +}); + +}) +//////////////////////////////////////////////////////////////////////////////// +require(['jquery'], function($) { + +// update the version selector with info from the siteinfo.js and ../versions.js files +$(document).ready(function () { + // If the version selector is disabled with DOCUMENTER_VERSION_SELECTOR_DISABLED in the + // siteinfo.js file, we just return immediately and not display the version selector. + if ( + typeof DOCUMENTER_VERSION_SELECTOR_DISABLED === "boolean" && + DOCUMENTER_VERSION_SELECTOR_DISABLED + ) { + return; + } + + var version_selector = $("#documenter .docs-version-selector"); + var version_selector_select = $("#documenter .docs-version-selector select"); + + version_selector_select.change(function (x) { + target_href = version_selector_select + .children("option:selected") + .get(0).value; + window.location.href = target_href; + }); + + // add the current version to the selector based on siteinfo.js, but only if the selector is empty + if ( + typeof DOCUMENTER_CURRENT_VERSION !== "undefined" && + $("#version-selector > option").length == 0 + ) { + var option = $( + "" + ); + version_selector_select.append(option); + } + + if (typeof DOC_VERSIONS !== "undefined") { + var existing_versions = version_selector_select.children("option"); + var existing_versions_texts = existing_versions.map(function (i, x) { + return x.text; + }); + DOC_VERSIONS.forEach(function (each) { + var version_url = documenterBaseURL + "/../" + each + "/"; + var existing_id = $.inArray(each, existing_versions_texts); + // if not already in the version selector, add it as a new option, + // otherwise update the old option with the URL and enable it + if (existing_id == -1) { + var option = $( + "" + ); + version_selector_select.append(option); + } else { + var option = existing_versions[existing_id]; + option.value = version_url; + option.disabled = false; + } + }); + } + + // only show the version selector if the selector has been populated + if (version_selector_select.children("option").length > 0) { + version_selector.toggleClass("visible"); + } +}); + +}) diff --git a/previews/PR1107/assets/img/bdec.png b/previews/PR1107/assets/img/bdec.png new file mode 100644 index 0000000000000000000000000000000000000000..3fc20810f14a58a06f13f98ff16f0c0e6c4ef364 GIT binary patch literal 2127 zcmeAS@N?(olHy`uVBq!ia0y~yU^D}=D>&Fd_gqWrYXoKyw| zjfu$#2|}Ja8YdDH6B16G)z;)HDi7-D=m|=wKXu@o!l46a4;()*cScOi0||i#%pr#u z76%^Op3T#v@N1Lai8YhMmgYMt$v7?X&0qboBlA=8DtE>h&6P(_I{e}nNLLXTnQCBQ zu#Dm6si&%}3T5mQbc0w*L?+m>AMtD7VBT=UQ{bE6hFKF7(iMKQP0*b%XCjA5^GDVR zYbUp;%khKW_-JW(uGL_?cL(^__;KvO0UJTZX(p|WiPh4~%|~=w zW}LENWBVt@)|T7OCDbkSkT2oSv7k9=IV_2vZx|bFxcB$}|IKX7%)edO+V%_o|NnpQ zkN^MocQ-z}pO#<|z{zm7HEy2ZQWaic*oLJ>c&7RKGH3zW91M)@nG7sI7BILOq=1-# zfq7CJ13Qow0%Dg1OfXqSvjxmBHbVtSp<}SKrzemOPA*DK%`48xFZp&xK^|z-wak!+ zk_cZPtK|G#y~LFKq*T3%+yamm2Ac{iATu|$BvGLvHz%*ys=`(YtilS&1_|pcDS(xf zWZNo5_y#CA=NF|anCThl87SFtDJUq|6s4qD1-ZCEwF7y!N*N_31y=g{<>lpi<;HsX zMd|v6mX?EaW<_dFq)TQlFnEA=*clob z*nrhz$RKP)(iwr!X>5q16G;YLXJ~P1k#l}7(8>}!M9e^~MV5qWw9yCo94RCq!3-7! zhO-@)jXpfi?6^Y8e+Muyu+)0GIEGX(zP*0XS0+)QCGeV3gwqTGHI_}W%J=s;W%0=@ zdtQC#hJ0k~`T$P*dGYc2`#1y?3X~h|%}*(DF*33IkH5DuD9rW+_utX73Yucwbyp+VPRrw(+$|v!IH()Hp<5u9{ta+U(Y^w z{P}D3>p%bfESwwtuHt0nyz-vXPM_n2F^~EV3I0pc-lC)2IHMu4TU1Z4v8f?(l#d)9 z2dWj9^suZ`D46$L^xTf8yvGawOh3Kh@9~GTug420?N?8@QFp%R*YrQ(B|@M48V*=7 z9^SUjh+SC0fKy_d=Lu^@rq?Vyw|AYA@o6}ql5j&!@jVO28exN+*ILB}j7*ao5@)yU z5mwmXcVNTX)n^W}aQHB_<&J2BK2+4z?)`V+@#n9TU)TKqv*Vnuo}$BpSxoo&Zkzg- ziUJFXI<*J;7ugqo3TENZ5i-c>K2@Z}$Yd}4gM0lBrN`T&jtYP(6i-(_mvv4FO#r?` B8@2!d literal 0 HcmV?d00001 diff --git a/previews/PR1107/assets/img/dw_master.svg b/previews/PR1107/assets/img/dw_master.svg new file mode 100644 index 000000000..38cd2be00 --- /dev/null +++ b/previews/PR1107/assets/img/dw_master.svg @@ -0,0 +1,3 @@ +Dw master + + \ No newline at end of file diff --git a/previews/PR1107/assets/img/dw_origform.svg b/previews/PR1107/assets/img/dw_origform.svg new file mode 100644 index 000000000..58d261853 --- /dev/null +++ b/previews/PR1107/assets/img/dw_origform.svg @@ -0,0 +1,3 @@ +Dw original formulation + + \ No newline at end of file diff --git a/previews/PR1107/assets/img/dw_sp.svg b/previews/PR1107/assets/img/dw_sp.svg new file mode 100644 index 000000000..4b2586538 --- /dev/null +++ b/previews/PR1107/assets/img/dw_sp.svg @@ -0,0 +1,3 @@ +Dw subproblem + + \ No newline at end of file diff --git a/previews/PR1107/assets/img/dwdec.png b/previews/PR1107/assets/img/dwdec.png new file mode 100644 index 0000000000000000000000000000000000000000..301c81add3e5f383237750d313247c3b4eb38bd2 GIT binary patch literal 2363 zcmdT_k5iI&7=PcFSA3;Z(9*4O^@f_33P_sDp`l=kg&~#0J8O`Fp&|^}q{}80Y0VLmK1d0AUEExRjNTJyFxt82^3*q5^Mwtrx~ci;DU-sgEf&yUaZ z-urG5d-|g1lC%}#&6~H1rB%I-OSsd~=je9qZYn5ny+?KtmOG(+xu=hpPTh(> zJx1?oYshO=r_-qEEkgC-2b+}-3%|*P3+5g=cY*sfkX#%dI<1k(WbTJ<^bSY(;GgHTrxh>8pAoIGR$Cnwzwv>O^+z zwkgU`*`w?8nH{&rJsvxeKzPO{R(Az^Jf2feJf5yv)7{$&X_Ckf>f5xcnbI2W54g>f zO9Tp`5Sj;c41#Nv5DGLHoEhLi5TU*r@&z&o9R42UwHFT9LwK!2P2Rw?WjuiZ$Yq6E zxx8GrMy+>oN&-Que&uqBULst=E>x@NvLf|rIo+Vrcp(tSzy_*Hu9x8kRav=?ZAhWL zj9>%ZYi7{!mm&Jn6q-aR!g*?~9A7|Z(wVeW5{}~>ZIOa4<}aFngQpbQ8oge_W-ux$ zD(DsS>1u5;BQ7~PnZaZ+SgcqO5vyBUu9q2N%XML|lDy8tm+K0(N{wErF2}ujWvkVn z=u>DkZ=pBWt3Krh<=aZ-x{0yCfDEsP5l3e--sA>Qj@QZ-DGlF7iB~kgv;v+} zNgT$Tou!gqTtKK0KO%xxsPQr@H;!{-^B`QN4l6mXP}7VfLy& zx37!)`Bd-VhA)r*eK~Lzf?}0vuA_{~qv{B*(b0uC-3t*I`6z^JVKj{Sq8L`=A%K{C z*JFFtgNKjo2bwAPXe8<6`1)IeYU+V4ktkMxn(rV(2{3ZNsIkpyCgU0k*)qjCfMPbp z`SDIey3vurO`>yG1>+hL)~)=-4@N>^_xI3Rq0uoHe;1RuhVPH-#%3P%BjclKuE%FS z^sIkSnYwV@?>87^17~KJ>%bEVs_=P!i1nMAZE8fE-)dC>LTMg1Nii;r>=1L4^4k0M z`dtKcv~`W3c|i7dCdZrdVRzHx#NCc;*e&^CLCqXyG)}d@*dHtbG(`{G{So9e7xv%Q zBhf$(4^nqJ+KifuPj}@=TaB8wf?d4ers-r$e$`dKwrONbW2tTpVn>{D?@|*dPoApE zYpxvG`}O5}R~kGox<3PadS)-q`8Cl}pK*d{YB6fUJNsxN8cvO<)%zm?*qv3fD`O9z zo0J_gG6StgoORb0)N;2NH9KOiBw*n_=CDJm5K|EBj>>N-C1;VaO!3VaBm!~f9?KfQ zt$?YP@AA=X)V!~#u00J0TnkioYzKyAUZAbq$OYS~A5>7Hz+3%4Z@|?#G_yU;)%|F! zWmJf4@4juz=~T%qlsLpcb>*}38bX0jPILv2EuC=912 z9M)5iWZ2!4zl*L;UAu=Y9Igr literal 0 HcmV?d00001 diff --git a/previews/PR1107/assets/img/dynamic_sparse_arrays.svg b/previews/PR1107/assets/img/dynamic_sparse_arrays.svg new file mode 100644 index 000000000..d14940b43 --- /dev/null +++ b/previews/PR1107/assets/img/dynamic_sparse_arrays.svg @@ -0,0 +1,2716 @@ + + + Dynamic sparse arrays + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/previews/PR1107/assets/js/init.js b/previews/PR1107/assets/js/init.js new file mode 100644 index 000000000..4274e6669 --- /dev/null +++ b/previews/PR1107/assets/js/init.js @@ -0,0 +1,6 @@ +require.config({ + paths: { + mermaid: "https://cdnjs.cloudflare.com/ajax/libs/mermaid/9.1.2/mermaid" + } +}); +require(['mermaid'], function(mermaid) { mermaid.init() }); \ No newline at end of file diff --git a/previews/PR1107/assets/themes/documenter-dark.css b/previews/PR1107/assets/themes/documenter-dark.css new file mode 100644 index 000000000..691b83abd --- /dev/null +++ b/previews/PR1107/assets/themes/documenter-dark.css @@ -0,0 +1,7 @@ +html.theme--documenter-dark .pagination-previous,html.theme--documenter-dark .pagination-next,html.theme--documenter-dark .pagination-link,html.theme--documenter-dark .pagination-ellipsis,html.theme--documenter-dark .file-cta,html.theme--documenter-dark .file-name,html.theme--documenter-dark .select select,html.theme--documenter-dark .textarea,html.theme--documenter-dark .input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input,html.theme--documenter-dark .button{-moz-appearance:none;-webkit-appearance:none;align-items:center;border:1px solid transparent;border-radius:.4em;box-shadow:none;display:inline-flex;font-size:1rem;height:2.5em;justify-content:flex-start;line-height:1.5;padding-bottom:calc(0.5em - 1px);padding-left:calc(0.75em - 1px);padding-right:calc(0.75em - 1px);padding-top:calc(0.5em - 1px);position:relative;vertical-align:top}html.theme--documenter-dark .pagination-previous:focus,html.theme--documenter-dark .pagination-next:focus,html.theme--documenter-dark .pagination-link:focus,html.theme--documenter-dark .pagination-ellipsis:focus,html.theme--documenter-dark .file-cta:focus,html.theme--documenter-dark .file-name:focus,html.theme--documenter-dark .select select:focus,html.theme--documenter-dark .textarea:focus,html.theme--documenter-dark .input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input:focus,html.theme--documenter-dark .button:focus,html.theme--documenter-dark .is-focused.pagination-previous,html.theme--documenter-dark .is-focused.pagination-next,html.theme--documenter-dark .is-focused.pagination-link,html.theme--documenter-dark .is-focused.pagination-ellipsis,html.theme--documenter-dark .is-focused.file-cta,html.theme--documenter-dark .is-focused.file-name,html.theme--documenter-dark .select select.is-focused,html.theme--documenter-dark .is-focused.textarea,html.theme--documenter-dark .is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .is-focused.button,html.theme--documenter-dark .pagination-previous:active,html.theme--documenter-dark .pagination-next:active,html.theme--documenter-dark .pagination-link:active,html.theme--documenter-dark .pagination-ellipsis:active,html.theme--documenter-dark .file-cta:active,html.theme--documenter-dark .file-name:active,html.theme--documenter-dark .select select:active,html.theme--documenter-dark .textarea:active,html.theme--documenter-dark .input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input:active,html.theme--documenter-dark .button:active,html.theme--documenter-dark .is-active.pagination-previous,html.theme--documenter-dark .is-active.pagination-next,html.theme--documenter-dark .is-active.pagination-link,html.theme--documenter-dark .is-active.pagination-ellipsis,html.theme--documenter-dark .is-active.file-cta,html.theme--documenter-dark .is-active.file-name,html.theme--documenter-dark .select select.is-active,html.theme--documenter-dark .is-active.textarea,html.theme--documenter-dark .is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active,html.theme--documenter-dark .is-active.button{outline:none}html.theme--documenter-dark .pagination-previous[disabled],html.theme--documenter-dark .pagination-next[disabled],html.theme--documenter-dark .pagination-link[disabled],html.theme--documenter-dark .pagination-ellipsis[disabled],html.theme--documenter-dark .file-cta[disabled],html.theme--documenter-dark .file-name[disabled],html.theme--documenter-dark .select select[disabled],html.theme--documenter-dark .textarea[disabled],html.theme--documenter-dark .input[disabled],html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input[disabled],html.theme--documenter-dark .button[disabled],fieldset[disabled] html.theme--documenter-dark .pagination-previous,html.theme--documenter-dark fieldset[disabled] .pagination-previous,fieldset[disabled] html.theme--documenter-dark .pagination-next,html.theme--documenter-dark fieldset[disabled] .pagination-next,fieldset[disabled] html.theme--documenter-dark .pagination-link,html.theme--documenter-dark fieldset[disabled] .pagination-link,fieldset[disabled] html.theme--documenter-dark .pagination-ellipsis,html.theme--documenter-dark fieldset[disabled] .pagination-ellipsis,fieldset[disabled] html.theme--documenter-dark .file-cta,html.theme--documenter-dark fieldset[disabled] .file-cta,fieldset[disabled] html.theme--documenter-dark .file-name,html.theme--documenter-dark fieldset[disabled] .file-name,fieldset[disabled] html.theme--documenter-dark .select select,fieldset[disabled] html.theme--documenter-dark .textarea,fieldset[disabled] html.theme--documenter-dark .input,fieldset[disabled] html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input,html.theme--documenter-dark fieldset[disabled] .select select,html.theme--documenter-dark .select fieldset[disabled] select,html.theme--documenter-dark fieldset[disabled] .textarea,html.theme--documenter-dark fieldset[disabled] .input,html.theme--documenter-dark fieldset[disabled] #documenter .docs-sidebar form.docs-search>input,html.theme--documenter-dark #documenter .docs-sidebar fieldset[disabled] form.docs-search>input,fieldset[disabled] html.theme--documenter-dark .button,html.theme--documenter-dark fieldset[disabled] .button{cursor:not-allowed}html.theme--documenter-dark .tabs,html.theme--documenter-dark .pagination-previous,html.theme--documenter-dark .pagination-next,html.theme--documenter-dark .pagination-link,html.theme--documenter-dark .pagination-ellipsis,html.theme--documenter-dark .breadcrumb,html.theme--documenter-dark .file,html.theme--documenter-dark .button,.is-unselectable{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}html.theme--documenter-dark .navbar-link:not(.is-arrowless)::after,html.theme--documenter-dark .select:not(.is-multiple):not(.is-loading)::after{border:3px solid rgba(0,0,0,0);border-radius:2px;border-right:0;border-top:0;content:" ";display:block;height:0.625em;margin-top:-0.4375em;pointer-events:none;position:absolute;top:50%;transform:rotate(-45deg);transform-origin:center;width:0.625em}html.theme--documenter-dark .admonition:not(:last-child),html.theme--documenter-dark .tabs:not(:last-child),html.theme--documenter-dark .pagination:not(:last-child),html.theme--documenter-dark .message:not(:last-child),html.theme--documenter-dark .level:not(:last-child),html.theme--documenter-dark .breadcrumb:not(:last-child),html.theme--documenter-dark .block:not(:last-child),html.theme--documenter-dark .title:not(:last-child),html.theme--documenter-dark .subtitle:not(:last-child),html.theme--documenter-dark .table-container:not(:last-child),html.theme--documenter-dark .table:not(:last-child),html.theme--documenter-dark .progress:not(:last-child),html.theme--documenter-dark .notification:not(:last-child),html.theme--documenter-dark .content:not(:last-child),html.theme--documenter-dark .box:not(:last-child){margin-bottom:1.5rem}html.theme--documenter-dark .modal-close,html.theme--documenter-dark .delete{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-moz-appearance:none;-webkit-appearance:none;background-color:rgba(10,10,10,0.2);border:none;border-radius:9999px;cursor:pointer;pointer-events:auto;display:inline-block;flex-grow:0;flex-shrink:0;font-size:0;height:20px;max-height:20px;max-width:20px;min-height:20px;min-width:20px;outline:none;position:relative;vertical-align:top;width:20px}html.theme--documenter-dark .modal-close::before,html.theme--documenter-dark .delete::before,html.theme--documenter-dark .modal-close::after,html.theme--documenter-dark .delete::after{background-color:#fff;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}html.theme--documenter-dark .modal-close::before,html.theme--documenter-dark .delete::before{height:2px;width:50%}html.theme--documenter-dark .modal-close::after,html.theme--documenter-dark .delete::after{height:50%;width:2px}html.theme--documenter-dark .modal-close:hover,html.theme--documenter-dark .delete:hover,html.theme--documenter-dark .modal-close:focus,html.theme--documenter-dark .delete:focus{background-color:rgba(10,10,10,0.3)}html.theme--documenter-dark .modal-close:active,html.theme--documenter-dark .delete:active{background-color:rgba(10,10,10,0.4)}html.theme--documenter-dark .is-small.modal-close,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.modal-close,html.theme--documenter-dark .is-small.delete,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.delete{height:16px;max-height:16px;max-width:16px;min-height:16px;min-width:16px;width:16px}html.theme--documenter-dark .is-medium.modal-close,html.theme--documenter-dark .is-medium.delete{height:24px;max-height:24px;max-width:24px;min-height:24px;min-width:24px;width:24px}html.theme--documenter-dark .is-large.modal-close,html.theme--documenter-dark .is-large.delete{height:32px;max-height:32px;max-width:32px;min-height:32px;min-width:32px;width:32px}html.theme--documenter-dark .control.is-loading::after,html.theme--documenter-dark .select.is-loading::after,html.theme--documenter-dark .loader,html.theme--documenter-dark .button.is-loading::after{animation:spinAround 500ms infinite linear;border:2px solid #dbdee0;border-radius:9999px;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:1em;position:relative;width:1em}html.theme--documenter-dark .hero-video,html.theme--documenter-dark .modal-background,html.theme--documenter-dark .modal,html.theme--documenter-dark .image.is-square img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-square img,html.theme--documenter-dark .image.is-square .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-square .has-ratio,html.theme--documenter-dark .image.is-1by1 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by1 img,html.theme--documenter-dark .image.is-1by1 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by1 .has-ratio,html.theme--documenter-dark .image.is-5by4 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-5by4 img,html.theme--documenter-dark .image.is-5by4 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-5by4 .has-ratio,html.theme--documenter-dark .image.is-4by3 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-4by3 img,html.theme--documenter-dark .image.is-4by3 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-4by3 .has-ratio,html.theme--documenter-dark .image.is-3by2 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by2 img,html.theme--documenter-dark .image.is-3by2 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by2 .has-ratio,html.theme--documenter-dark .image.is-5by3 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-5by3 img,html.theme--documenter-dark .image.is-5by3 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-5by3 .has-ratio,html.theme--documenter-dark .image.is-16by9 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-16by9 img,html.theme--documenter-dark .image.is-16by9 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-16by9 .has-ratio,html.theme--documenter-dark .image.is-2by1 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-2by1 img,html.theme--documenter-dark .image.is-2by1 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-2by1 .has-ratio,html.theme--documenter-dark .image.is-3by1 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by1 img,html.theme--documenter-dark .image.is-3by1 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by1 .has-ratio,html.theme--documenter-dark .image.is-4by5 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-4by5 img,html.theme--documenter-dark .image.is-4by5 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-4by5 .has-ratio,html.theme--documenter-dark .image.is-3by4 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by4 img,html.theme--documenter-dark .image.is-3by4 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by4 .has-ratio,html.theme--documenter-dark .image.is-2by3 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-2by3 img,html.theme--documenter-dark .image.is-2by3 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-2by3 .has-ratio,html.theme--documenter-dark .image.is-3by5 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by5 img,html.theme--documenter-dark .image.is-3by5 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by5 .has-ratio,html.theme--documenter-dark .image.is-9by16 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-9by16 img,html.theme--documenter-dark .image.is-9by16 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-9by16 .has-ratio,html.theme--documenter-dark .image.is-1by2 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by2 img,html.theme--documenter-dark .image.is-1by2 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by2 .has-ratio,html.theme--documenter-dark .image.is-1by3 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by3 img,html.theme--documenter-dark .image.is-1by3 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by3 .has-ratio,.is-overlay{bottom:0;left:0;position:absolute;right:0;top:0}html.theme--documenter-dark .navbar-burger{-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;color:currentColor;font-family:inherit;font-size:1em;margin:0;padding:0}/*! minireset.css v0.0.6 | MIT License | github.com/jgthms/minireset.css */html,body,p,ol,ul,li,dl,dt,dd,blockquote,figure,fieldset,legend,textarea,pre,iframe,hr,h1,h2,h3,h4,h5,h6{margin:0;padding:0}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal}ul{list-style:none}button,input,select,textarea{margin:0}html{box-sizing:border-box}*,*::before,*::after{box-sizing:inherit}img,video{height:auto;max-width:100%}iframe{border:0}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}td:not([align]),th:not([align]){text-align:inherit}.has-text-white{color:#fff !important}a.has-text-white:hover,a.has-text-white:focus{color:#e6e6e6 !important}.has-background-white{background-color:#fff !important}.has-text-black{color:#0a0a0a !important}a.has-text-black:hover,a.has-text-black:focus{color:#000 !important}.has-background-black{background-color:#0a0a0a !important}.has-text-light{color:#ecf0f1 !important}a.has-text-light:hover,a.has-text-light:focus{color:#cfd9db !important}.has-background-light{background-color:#ecf0f1 !important}.has-text-dark{color:#282f2f !important}a.has-text-dark:hover,a.has-text-dark:focus{color:#111414 !important}.has-background-dark{background-color:#282f2f !important}.has-text-primary{color:#375a7f !important}a.has-text-primary:hover,a.has-text-primary:focus{color:#28415b !important}.has-background-primary{background-color:#375a7f !important}.has-text-primary-light{color:#f1f5f9 !important}a.has-text-primary-light:hover,a.has-text-primary-light:focus{color:#cddbe9 !important}.has-background-primary-light{background-color:#f1f5f9 !important}.has-text-primary-dark{color:#4d7eb2 !important}a.has-text-primary-dark:hover,a.has-text-primary-dark:focus{color:#7198c1 !important}.has-background-primary-dark{background-color:#4d7eb2 !important}.has-text-link{color:#1abc9c !important}a.has-text-link:hover,a.has-text-link:focus{color:#148f77 !important}.has-background-link{background-color:#1abc9c !important}.has-text-link-light{color:#edfdf9 !important}a.has-text-link-light:hover,a.has-text-link-light:focus{color:#c0f6ec !important}.has-background-link-light{background-color:#edfdf9 !important}.has-text-link-dark{color:#15987e !important}a.has-text-link-dark:hover,a.has-text-link-dark:focus{color:#1bc5a4 !important}.has-background-link-dark{background-color:#15987e !important}.has-text-info{color:#024c7d !important}a.has-text-info:hover,a.has-text-info:focus{color:#012d4b !important}.has-background-info{background-color:#024c7d !important}.has-text-info-light{color:#ebf7ff !important}a.has-text-info-light:hover,a.has-text-info-light:focus{color:#b9e2fe !important}.has-background-info-light{background-color:#ebf7ff !important}.has-text-info-dark{color:#0e9dfb !important}a.has-text-info-dark:hover,a.has-text-info-dark:focus{color:#40b1fc !important}.has-background-info-dark{background-color:#0e9dfb !important}.has-text-success{color:#008438 !important}a.has-text-success:hover,a.has-text-success:focus{color:#005122 !important}.has-background-success{background-color:#008438 !important}.has-text-success-light{color:#ebfff3 !important}a.has-text-success-light:hover,a.has-text-success-light:focus{color:#b8ffd6 !important}.has-background-success-light{background-color:#ebfff3 !important}.has-text-success-dark{color:#00eb64 !important}a.has-text-success-dark:hover,a.has-text-success-dark:focus{color:#1fff7e !important}.has-background-success-dark{background-color:#00eb64 !important}.has-text-warning{color:#ad8100 !important}a.has-text-warning:hover,a.has-text-warning:focus{color:#7a5b00 !important}.has-background-warning{background-color:#ad8100 !important}.has-text-warning-light{color:#fffaeb !important}a.has-text-warning-light:hover,a.has-text-warning-light:focus{color:#ffedb8 !important}.has-background-warning-light{background-color:#fffaeb !important}.has-text-warning-dark{color:#d19c00 !important}a.has-text-warning-dark:hover,a.has-text-warning-dark:focus{color:#ffbf05 !important}.has-background-warning-dark{background-color:#d19c00 !important}.has-text-danger{color:#9e1b0d !important}a.has-text-danger:hover,a.has-text-danger:focus{color:#6f1309 !important}.has-background-danger{background-color:#9e1b0d !important}.has-text-danger-light{color:#fdeeec !important}a.has-text-danger-light:hover,a.has-text-danger-light:focus{color:#fac3bd !important}.has-background-danger-light{background-color:#fdeeec !important}.has-text-danger-dark{color:#ec311d !important}a.has-text-danger-dark:hover,a.has-text-danger-dark:focus{color:#f05c4c !important}.has-background-danger-dark{background-color:#ec311d !important}.has-text-black-bis{color:#121212 !important}.has-background-black-bis{background-color:#121212 !important}.has-text-black-ter{color:#242424 !important}.has-background-black-ter{background-color:#242424 !important}.has-text-grey-darker{color:#282f2f !important}.has-background-grey-darker{background-color:#282f2f !important}.has-text-grey-dark{color:#343c3d !important}.has-background-grey-dark{background-color:#343c3d !important}.has-text-grey{color:#5e6d6f !important}.has-background-grey{background-color:#5e6d6f !important}.has-text-grey-light{color:#8c9b9d !important}.has-background-grey-light{background-color:#8c9b9d !important}.has-text-grey-lighter{color:#dbdee0 !important}.has-background-grey-lighter{background-color:#dbdee0 !important}.has-text-white-ter{color:#ecf0f1 !important}.has-background-white-ter{background-color:#ecf0f1 !important}.has-text-white-bis{color:#fafafa !important}.has-background-white-bis{background-color:#fafafa !important}.is-flex-direction-row{flex-direction:row !important}.is-flex-direction-row-reverse{flex-direction:row-reverse !important}.is-flex-direction-column{flex-direction:column !important}.is-flex-direction-column-reverse{flex-direction:column-reverse !important}.is-flex-wrap-nowrap{flex-wrap:nowrap !important}.is-flex-wrap-wrap{flex-wrap:wrap !important}.is-flex-wrap-wrap-reverse{flex-wrap:wrap-reverse !important}.is-justify-content-flex-start{justify-content:flex-start !important}.is-justify-content-flex-end{justify-content:flex-end !important}.is-justify-content-center{justify-content:center !important}.is-justify-content-space-between{justify-content:space-between !important}.is-justify-content-space-around{justify-content:space-around !important}.is-justify-content-space-evenly{justify-content:space-evenly !important}.is-justify-content-start{justify-content:start !important}.is-justify-content-end{justify-content:end !important}.is-justify-content-left{justify-content:left !important}.is-justify-content-right{justify-content:right !important}.is-align-content-flex-start{align-content:flex-start !important}.is-align-content-flex-end{align-content:flex-end !important}.is-align-content-center{align-content:center !important}.is-align-content-space-between{align-content:space-between !important}.is-align-content-space-around{align-content:space-around !important}.is-align-content-space-evenly{align-content:space-evenly !important}.is-align-content-stretch{align-content:stretch !important}.is-align-content-start{align-content:start !important}.is-align-content-end{align-content:end !important}.is-align-content-baseline{align-content:baseline !important}.is-align-items-stretch{align-items:stretch !important}.is-align-items-flex-start{align-items:flex-start !important}.is-align-items-flex-end{align-items:flex-end !important}.is-align-items-center{align-items:center !important}.is-align-items-baseline{align-items:baseline !important}.is-align-items-start{align-items:start !important}.is-align-items-end{align-items:end !important}.is-align-items-self-start{align-items:self-start !important}.is-align-items-self-end{align-items:self-end !important}.is-align-self-auto{align-self:auto !important}.is-align-self-flex-start{align-self:flex-start !important}.is-align-self-flex-end{align-self:flex-end !important}.is-align-self-center{align-self:center !important}.is-align-self-baseline{align-self:baseline !important}.is-align-self-stretch{align-self:stretch !important}.is-flex-grow-0{flex-grow:0 !important}.is-flex-grow-1{flex-grow:1 !important}.is-flex-grow-2{flex-grow:2 !important}.is-flex-grow-3{flex-grow:3 !important}.is-flex-grow-4{flex-grow:4 !important}.is-flex-grow-5{flex-grow:5 !important}.is-flex-shrink-0{flex-shrink:0 !important}.is-flex-shrink-1{flex-shrink:1 !important}.is-flex-shrink-2{flex-shrink:2 !important}.is-flex-shrink-3{flex-shrink:3 !important}.is-flex-shrink-4{flex-shrink:4 !important}.is-flex-shrink-5{flex-shrink:5 !important}.is-clearfix::after{clear:both;content:" ";display:table}.is-pulled-left{float:left !important}.is-pulled-right{float:right !important}.is-radiusless{border-radius:0 !important}.is-shadowless{box-shadow:none !important}.is-clickable{cursor:pointer !important;pointer-events:all !important}.is-clipped{overflow:hidden !important}.is-relative{position:relative !important}.is-marginless{margin:0 !important}.is-paddingless{padding:0 !important}.m-0{margin:0 !important}.mt-0{margin-top:0 !important}.mr-0{margin-right:0 !important}.mb-0{margin-bottom:0 !important}.ml-0{margin-left:0 !important}.mx-0{margin-left:0 !important;margin-right:0 !important}.my-0{margin-top:0 !important;margin-bottom:0 !important}.m-1{margin:.25rem !important}.mt-1{margin-top:.25rem !important}.mr-1{margin-right:.25rem !important}.mb-1{margin-bottom:.25rem !important}.ml-1{margin-left:.25rem !important}.mx-1{margin-left:.25rem !important;margin-right:.25rem !important}.my-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.m-2{margin:.5rem !important}.mt-2{margin-top:.5rem !important}.mr-2{margin-right:.5rem !important}.mb-2{margin-bottom:.5rem !important}.ml-2{margin-left:.5rem !important}.mx-2{margin-left:.5rem !important;margin-right:.5rem !important}.my-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.m-3{margin:.75rem !important}.mt-3{margin-top:.75rem !important}.mr-3{margin-right:.75rem !important}.mb-3{margin-bottom:.75rem !important}.ml-3{margin-left:.75rem !important}.mx-3{margin-left:.75rem !important;margin-right:.75rem !important}.my-3{margin-top:.75rem !important;margin-bottom:.75rem !important}.m-4{margin:1rem !important}.mt-4{margin-top:1rem !important}.mr-4{margin-right:1rem !important}.mb-4{margin-bottom:1rem !important}.ml-4{margin-left:1rem !important}.mx-4{margin-left:1rem !important;margin-right:1rem !important}.my-4{margin-top:1rem !important;margin-bottom:1rem !important}.m-5{margin:1.5rem !important}.mt-5{margin-top:1.5rem !important}.mr-5{margin-right:1.5rem !important}.mb-5{margin-bottom:1.5rem !important}.ml-5{margin-left:1.5rem !important}.mx-5{margin-left:1.5rem !important;margin-right:1.5rem !important}.my-5{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.m-6{margin:3rem !important}.mt-6{margin-top:3rem !important}.mr-6{margin-right:3rem !important}.mb-6{margin-bottom:3rem !important}.ml-6{margin-left:3rem !important}.mx-6{margin-left:3rem !important;margin-right:3rem !important}.my-6{margin-top:3rem !important;margin-bottom:3rem !important}.m-auto{margin:auto !important}.mt-auto{margin-top:auto !important}.mr-auto{margin-right:auto !important}.mb-auto{margin-bottom:auto !important}.ml-auto{margin-left:auto !important}.mx-auto{margin-left:auto !important;margin-right:auto !important}.my-auto{margin-top:auto !important;margin-bottom:auto !important}.p-0{padding:0 !important}.pt-0{padding-top:0 !important}.pr-0{padding-right:0 !important}.pb-0{padding-bottom:0 !important}.pl-0{padding-left:0 !important}.px-0{padding-left:0 !important;padding-right:0 !important}.py-0{padding-top:0 !important;padding-bottom:0 !important}.p-1{padding:.25rem !important}.pt-1{padding-top:.25rem !important}.pr-1{padding-right:.25rem !important}.pb-1{padding-bottom:.25rem !important}.pl-1{padding-left:.25rem !important}.px-1{padding-left:.25rem !important;padding-right:.25rem !important}.py-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.p-2{padding:.5rem !important}.pt-2{padding-top:.5rem !important}.pr-2{padding-right:.5rem !important}.pb-2{padding-bottom:.5rem !important}.pl-2{padding-left:.5rem !important}.px-2{padding-left:.5rem !important;padding-right:.5rem !important}.py-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.p-3{padding:.75rem !important}.pt-3{padding-top:.75rem !important}.pr-3{padding-right:.75rem !important}.pb-3{padding-bottom:.75rem !important}.pl-3{padding-left:.75rem !important}.px-3{padding-left:.75rem !important;padding-right:.75rem !important}.py-3{padding-top:.75rem !important;padding-bottom:.75rem !important}.p-4{padding:1rem !important}.pt-4{padding-top:1rem !important}.pr-4{padding-right:1rem !important}.pb-4{padding-bottom:1rem !important}.pl-4{padding-left:1rem !important}.px-4{padding-left:1rem !important;padding-right:1rem !important}.py-4{padding-top:1rem !important;padding-bottom:1rem !important}.p-5{padding:1.5rem !important}.pt-5{padding-top:1.5rem !important}.pr-5{padding-right:1.5rem !important}.pb-5{padding-bottom:1.5rem !important}.pl-5{padding-left:1.5rem !important}.px-5{padding-left:1.5rem !important;padding-right:1.5rem !important}.py-5{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.p-6{padding:3rem !important}.pt-6{padding-top:3rem !important}.pr-6{padding-right:3rem !important}.pb-6{padding-bottom:3rem !important}.pl-6{padding-left:3rem !important}.px-6{padding-left:3rem !important;padding-right:3rem !important}.py-6{padding-top:3rem !important;padding-bottom:3rem !important}.p-auto{padding:auto !important}.pt-auto{padding-top:auto !important}.pr-auto{padding-right:auto !important}.pb-auto{padding-bottom:auto !important}.pl-auto{padding-left:auto !important}.px-auto{padding-left:auto !important;padding-right:auto !important}.py-auto{padding-top:auto !important;padding-bottom:auto !important}.is-size-1{font-size:3rem !important}.is-size-2{font-size:2.5rem !important}.is-size-3{font-size:2rem !important}.is-size-4{font-size:1.5rem !important}.is-size-5{font-size:1.25rem !important}.is-size-6{font-size:1rem !important}.is-size-7,html.theme--documenter-dark .docstring>section>a.docs-sourcelink{font-size:.75rem !important}@media screen and (max-width: 768px){.is-size-1-mobile{font-size:3rem !important}.is-size-2-mobile{font-size:2.5rem !important}.is-size-3-mobile{font-size:2rem !important}.is-size-4-mobile{font-size:1.5rem !important}.is-size-5-mobile{font-size:1.25rem !important}.is-size-6-mobile{font-size:1rem !important}.is-size-7-mobile{font-size:.75rem !important}}@media screen and (min-width: 769px),print{.is-size-1-tablet{font-size:3rem !important}.is-size-2-tablet{font-size:2.5rem !important}.is-size-3-tablet{font-size:2rem !important}.is-size-4-tablet{font-size:1.5rem !important}.is-size-5-tablet{font-size:1.25rem !important}.is-size-6-tablet{font-size:1rem !important}.is-size-7-tablet{font-size:.75rem !important}}@media screen and (max-width: 1055px){.is-size-1-touch{font-size:3rem !important}.is-size-2-touch{font-size:2.5rem !important}.is-size-3-touch{font-size:2rem !important}.is-size-4-touch{font-size:1.5rem !important}.is-size-5-touch{font-size:1.25rem !important}.is-size-6-touch{font-size:1rem !important}.is-size-7-touch{font-size:.75rem !important}}@media screen and (min-width: 1056px){.is-size-1-desktop{font-size:3rem !important}.is-size-2-desktop{font-size:2.5rem !important}.is-size-3-desktop{font-size:2rem !important}.is-size-4-desktop{font-size:1.5rem !important}.is-size-5-desktop{font-size:1.25rem !important}.is-size-6-desktop{font-size:1rem !important}.is-size-7-desktop{font-size:.75rem !important}}@media screen and (min-width: 1216px){.is-size-1-widescreen{font-size:3rem !important}.is-size-2-widescreen{font-size:2.5rem !important}.is-size-3-widescreen{font-size:2rem !important}.is-size-4-widescreen{font-size:1.5rem !important}.is-size-5-widescreen{font-size:1.25rem !important}.is-size-6-widescreen{font-size:1rem !important}.is-size-7-widescreen{font-size:.75rem !important}}@media screen and (min-width: 1408px){.is-size-1-fullhd{font-size:3rem !important}.is-size-2-fullhd{font-size:2.5rem !important}.is-size-3-fullhd{font-size:2rem !important}.is-size-4-fullhd{font-size:1.5rem !important}.is-size-5-fullhd{font-size:1.25rem !important}.is-size-6-fullhd{font-size:1rem !important}.is-size-7-fullhd{font-size:.75rem !important}}.has-text-centered{text-align:center !important}.has-text-justified{text-align:justify !important}.has-text-left{text-align:left !important}.has-text-right{text-align:right !important}@media screen and (max-width: 768px){.has-text-centered-mobile{text-align:center !important}}@media screen and (min-width: 769px),print{.has-text-centered-tablet{text-align:center !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-centered-tablet-only{text-align:center !important}}@media screen and (max-width: 1055px){.has-text-centered-touch{text-align:center !important}}@media screen and (min-width: 1056px){.has-text-centered-desktop{text-align:center !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-centered-desktop-only{text-align:center !important}}@media screen and (min-width: 1216px){.has-text-centered-widescreen{text-align:center !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-centered-widescreen-only{text-align:center !important}}@media screen and (min-width: 1408px){.has-text-centered-fullhd{text-align:center !important}}@media screen and (max-width: 768px){.has-text-justified-mobile{text-align:justify !important}}@media screen and (min-width: 769px),print{.has-text-justified-tablet{text-align:justify !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-justified-tablet-only{text-align:justify !important}}@media screen and (max-width: 1055px){.has-text-justified-touch{text-align:justify !important}}@media screen and (min-width: 1056px){.has-text-justified-desktop{text-align:justify !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-justified-desktop-only{text-align:justify !important}}@media screen and (min-width: 1216px){.has-text-justified-widescreen{text-align:justify !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-justified-widescreen-only{text-align:justify !important}}@media screen and (min-width: 1408px){.has-text-justified-fullhd{text-align:justify !important}}@media screen and (max-width: 768px){.has-text-left-mobile{text-align:left !important}}@media screen and (min-width: 769px),print{.has-text-left-tablet{text-align:left !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-left-tablet-only{text-align:left !important}}@media screen and (max-width: 1055px){.has-text-left-touch{text-align:left !important}}@media screen and (min-width: 1056px){.has-text-left-desktop{text-align:left !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-left-desktop-only{text-align:left !important}}@media screen and (min-width: 1216px){.has-text-left-widescreen{text-align:left !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-left-widescreen-only{text-align:left !important}}@media screen and (min-width: 1408px){.has-text-left-fullhd{text-align:left !important}}@media screen and (max-width: 768px){.has-text-right-mobile{text-align:right !important}}@media screen and (min-width: 769px),print{.has-text-right-tablet{text-align:right !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-right-tablet-only{text-align:right !important}}@media screen and (max-width: 1055px){.has-text-right-touch{text-align:right !important}}@media screen and (min-width: 1056px){.has-text-right-desktop{text-align:right !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-right-desktop-only{text-align:right !important}}@media screen and (min-width: 1216px){.has-text-right-widescreen{text-align:right !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-right-widescreen-only{text-align:right !important}}@media screen and (min-width: 1408px){.has-text-right-fullhd{text-align:right !important}}.is-capitalized{text-transform:capitalize !important}.is-lowercase{text-transform:lowercase !important}.is-uppercase{text-transform:uppercase !important}.is-italic{font-style:italic !important}.is-underlined{text-decoration:underline !important}.has-text-weight-light{font-weight:300 !important}.has-text-weight-normal{font-weight:400 !important}.has-text-weight-medium{font-weight:500 !important}.has-text-weight-semibold{font-weight:600 !important}.has-text-weight-bold{font-weight:700 !important}.is-family-primary{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-secondary{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-sans-serif{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-monospace{font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace !important}.is-family-code{font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace !important}.is-block{display:block !important}@media screen and (max-width: 768px){.is-block-mobile{display:block !important}}@media screen and (min-width: 769px),print{.is-block-tablet{display:block !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-block-tablet-only{display:block !important}}@media screen and (max-width: 1055px){.is-block-touch{display:block !important}}@media screen and (min-width: 1056px){.is-block-desktop{display:block !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-block-desktop-only{display:block !important}}@media screen and (min-width: 1216px){.is-block-widescreen{display:block !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-block-widescreen-only{display:block !important}}@media screen and (min-width: 1408px){.is-block-fullhd{display:block !important}}.is-flex{display:flex !important}@media screen and (max-width: 768px){.is-flex-mobile{display:flex !important}}@media screen and (min-width: 769px),print{.is-flex-tablet{display:flex !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-flex-tablet-only{display:flex !important}}@media screen and (max-width: 1055px){.is-flex-touch{display:flex !important}}@media screen and (min-width: 1056px){.is-flex-desktop{display:flex !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-flex-desktop-only{display:flex !important}}@media screen and (min-width: 1216px){.is-flex-widescreen{display:flex !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-flex-widescreen-only{display:flex !important}}@media screen and (min-width: 1408px){.is-flex-fullhd{display:flex !important}}.is-inline{display:inline !important}@media screen and (max-width: 768px){.is-inline-mobile{display:inline !important}}@media screen and (min-width: 769px),print{.is-inline-tablet{display:inline !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-tablet-only{display:inline !important}}@media screen and (max-width: 1055px){.is-inline-touch{display:inline !important}}@media screen and (min-width: 1056px){.is-inline-desktop{display:inline !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-desktop-only{display:inline !important}}@media screen and (min-width: 1216px){.is-inline-widescreen{display:inline !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-widescreen-only{display:inline !important}}@media screen and (min-width: 1408px){.is-inline-fullhd{display:inline !important}}.is-inline-block{display:inline-block !important}@media screen and (max-width: 768px){.is-inline-block-mobile{display:inline-block !important}}@media screen and (min-width: 769px),print{.is-inline-block-tablet{display:inline-block !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-block-tablet-only{display:inline-block !important}}@media screen and (max-width: 1055px){.is-inline-block-touch{display:inline-block !important}}@media screen and (min-width: 1056px){.is-inline-block-desktop{display:inline-block !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-block-desktop-only{display:inline-block !important}}@media screen and (min-width: 1216px){.is-inline-block-widescreen{display:inline-block !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-block-widescreen-only{display:inline-block !important}}@media screen and (min-width: 1408px){.is-inline-block-fullhd{display:inline-block !important}}.is-inline-flex{display:inline-flex !important}@media screen and (max-width: 768px){.is-inline-flex-mobile{display:inline-flex !important}}@media screen and (min-width: 769px),print{.is-inline-flex-tablet{display:inline-flex !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-flex-tablet-only{display:inline-flex !important}}@media screen and (max-width: 1055px){.is-inline-flex-touch{display:inline-flex !important}}@media screen and (min-width: 1056px){.is-inline-flex-desktop{display:inline-flex !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-flex-desktop-only{display:inline-flex !important}}@media screen and (min-width: 1216px){.is-inline-flex-widescreen{display:inline-flex !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-flex-widescreen-only{display:inline-flex !important}}@media screen and (min-width: 1408px){.is-inline-flex-fullhd{display:inline-flex !important}}.is-hidden{display:none !important}.is-sr-only{border:none !important;clip:rect(0, 0, 0, 0) !important;height:0.01em !important;overflow:hidden !important;padding:0 !important;position:absolute !important;white-space:nowrap !important;width:0.01em !important}@media screen and (max-width: 768px){.is-hidden-mobile{display:none !important}}@media screen and (min-width: 769px),print{.is-hidden-tablet{display:none !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-hidden-tablet-only{display:none !important}}@media screen and (max-width: 1055px){.is-hidden-touch{display:none !important}}@media screen and (min-width: 1056px){.is-hidden-desktop{display:none !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-hidden-desktop-only{display:none !important}}@media screen and (min-width: 1216px){.is-hidden-widescreen{display:none !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-hidden-widescreen-only{display:none !important}}@media screen and (min-width: 1408px){.is-hidden-fullhd{display:none !important}}.is-invisible{visibility:hidden !important}@media screen and (max-width: 768px){.is-invisible-mobile{visibility:hidden !important}}@media screen and (min-width: 769px),print{.is-invisible-tablet{visibility:hidden !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-invisible-tablet-only{visibility:hidden !important}}@media screen and (max-width: 1055px){.is-invisible-touch{visibility:hidden !important}}@media screen and (min-width: 1056px){.is-invisible-desktop{visibility:hidden !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-invisible-desktop-only{visibility:hidden !important}}@media screen and (min-width: 1216px){.is-invisible-widescreen{visibility:hidden !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-invisible-widescreen-only{visibility:hidden !important}}@media screen and (min-width: 1408px){.is-invisible-fullhd{visibility:hidden !important}}html.theme--documenter-dark{/*! + Theme: a11y-dark + Author: @ericwbailey + Maintainer: @ericwbailey + + Based on the Tomorrow Night Eighties theme: https://github.com/isagalaev/highlight.js/blob/master/src/styles/tomorrow-night-eighties.css +*/}html.theme--documenter-dark html{background-color:#1f2424;font-size:16px;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;min-width:300px;overflow-x:auto;overflow-y:scroll;text-rendering:optimizeLegibility;text-size-adjust:100%}html.theme--documenter-dark article,html.theme--documenter-dark aside,html.theme--documenter-dark figure,html.theme--documenter-dark footer,html.theme--documenter-dark header,html.theme--documenter-dark hgroup,html.theme--documenter-dark section{display:block}html.theme--documenter-dark body,html.theme--documenter-dark button,html.theme--documenter-dark input,html.theme--documenter-dark optgroup,html.theme--documenter-dark select,html.theme--documenter-dark textarea{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif}html.theme--documenter-dark code,html.theme--documenter-dark pre{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto;font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace}html.theme--documenter-dark body{color:#fff;font-size:1em;font-weight:400;line-height:1.5}html.theme--documenter-dark a{color:#1abc9c;cursor:pointer;text-decoration:none}html.theme--documenter-dark a strong{color:currentColor}html.theme--documenter-dark a:hover{color:#1dd2af}html.theme--documenter-dark code{background-color:rgba(255,255,255,0.05);color:#ececec;font-size:.875em;font-weight:normal;padding:.1em}html.theme--documenter-dark hr{background-color:#282f2f;border:none;display:block;height:2px;margin:1.5rem 0}html.theme--documenter-dark img{height:auto;max-width:100%}html.theme--documenter-dark input[type="checkbox"],html.theme--documenter-dark input[type="radio"]{vertical-align:baseline}html.theme--documenter-dark small{font-size:.875em}html.theme--documenter-dark span{font-style:inherit;font-weight:inherit}html.theme--documenter-dark strong{color:#f2f2f2;font-weight:700}html.theme--documenter-dark fieldset{border:none}html.theme--documenter-dark pre{-webkit-overflow-scrolling:touch;background-color:#282f2f;color:#fff;font-size:.875em;overflow-x:auto;padding:1.25rem 1.5rem;white-space:pre;word-wrap:normal}html.theme--documenter-dark pre code{background-color:transparent;color:currentColor;font-size:1em;padding:0}html.theme--documenter-dark table td,html.theme--documenter-dark table th{vertical-align:top}html.theme--documenter-dark table td:not([align]),html.theme--documenter-dark table th:not([align]){text-align:inherit}html.theme--documenter-dark table th{color:#f2f2f2}html.theme--documenter-dark .box{background-color:#343c3d;border-radius:8px;box-shadow:none;color:#fff;display:block;padding:1.25rem}html.theme--documenter-dark a.box:hover,html.theme--documenter-dark a.box:focus{box-shadow:0 0.5em 1em -0.125em rgba(10,10,10,0.1),0 0 0 1px #1abc9c}html.theme--documenter-dark a.box:active{box-shadow:inset 0 1px 2px rgba(10,10,10,0.2),0 0 0 1px #1abc9c}html.theme--documenter-dark .button{background-color:#282f2f;border-color:#4c5759;border-width:1px;color:#375a7f;cursor:pointer;justify-content:center;padding-bottom:calc(0.5em - 1px);padding-left:1em;padding-right:1em;padding-top:calc(0.5em - 1px);text-align:center;white-space:nowrap}html.theme--documenter-dark .button strong{color:inherit}html.theme--documenter-dark .button .icon,html.theme--documenter-dark .button .icon.is-small,html.theme--documenter-dark .button #documenter .docs-sidebar form.docs-search>input.icon,html.theme--documenter-dark #documenter .docs-sidebar .button form.docs-search>input.icon,html.theme--documenter-dark .button .icon.is-medium,html.theme--documenter-dark .button .icon.is-large{height:1.5em;width:1.5em}html.theme--documenter-dark .button .icon:first-child:not(:last-child){margin-left:calc(-0.5em - 1px);margin-right:.25em}html.theme--documenter-dark .button .icon:last-child:not(:first-child){margin-left:.25em;margin-right:calc(-0.5em - 1px)}html.theme--documenter-dark .button .icon:first-child:last-child{margin-left:calc(-0.5em - 1px);margin-right:calc(-0.5em - 1px)}html.theme--documenter-dark .button:hover,html.theme--documenter-dark .button.is-hovered{border-color:#8c9b9d;color:#f2f2f2}html.theme--documenter-dark .button:focus,html.theme--documenter-dark .button.is-focused{border-color:#8c9b9d;color:#17a689}html.theme--documenter-dark .button:focus:not(:active),html.theme--documenter-dark .button.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(26,188,156,0.25)}html.theme--documenter-dark .button:active,html.theme--documenter-dark .button.is-active{border-color:#343c3d;color:#f2f2f2}html.theme--documenter-dark .button.is-text{background-color:transparent;border-color:transparent;color:#fff;text-decoration:underline}html.theme--documenter-dark .button.is-text:hover,html.theme--documenter-dark .button.is-text.is-hovered,html.theme--documenter-dark .button.is-text:focus,html.theme--documenter-dark .button.is-text.is-focused{background-color:#282f2f;color:#f2f2f2}html.theme--documenter-dark .button.is-text:active,html.theme--documenter-dark .button.is-text.is-active{background-color:#1d2122;color:#f2f2f2}html.theme--documenter-dark .button.is-text[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-text{background-color:transparent;border-color:transparent;box-shadow:none}html.theme--documenter-dark .button.is-ghost{background:none;border-color:rgba(0,0,0,0);color:#1abc9c;text-decoration:none}html.theme--documenter-dark .button.is-ghost:hover,html.theme--documenter-dark .button.is-ghost.is-hovered{color:#1abc9c;text-decoration:underline}html.theme--documenter-dark .button.is-white{background-color:#fff;border-color:transparent;color:#0a0a0a}html.theme--documenter-dark .button.is-white:hover,html.theme--documenter-dark .button.is-white.is-hovered{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}html.theme--documenter-dark .button.is-white:focus,html.theme--documenter-dark .button.is-white.is-focused{border-color:transparent;color:#0a0a0a}html.theme--documenter-dark .button.is-white:focus:not(:active),html.theme--documenter-dark .button.is-white.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}html.theme--documenter-dark .button.is-white:active,html.theme--documenter-dark .button.is-white.is-active{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}html.theme--documenter-dark .button.is-white[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-white{background-color:#fff;border-color:#fff;box-shadow:none}html.theme--documenter-dark .button.is-white.is-inverted{background-color:#0a0a0a;color:#fff}html.theme--documenter-dark .button.is-white.is-inverted:hover,html.theme--documenter-dark .button.is-white.is-inverted.is-hovered{background-color:#000}html.theme--documenter-dark .button.is-white.is-inverted[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-white.is-inverted{background-color:#0a0a0a;border-color:transparent;box-shadow:none;color:#fff}html.theme--documenter-dark .button.is-white.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--documenter-dark .button.is-white.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--documenter-dark .button.is-white.is-outlined:hover,html.theme--documenter-dark .button.is-white.is-outlined.is-hovered,html.theme--documenter-dark .button.is-white.is-outlined:focus,html.theme--documenter-dark .button.is-white.is-outlined.is-focused{background-color:#fff;border-color:#fff;color:#0a0a0a}html.theme--documenter-dark .button.is-white.is-outlined.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-white.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-white.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-white.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-white.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--documenter-dark .button.is-white.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-white.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--documenter-dark .button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}html.theme--documenter-dark .button.is-white.is-inverted.is-outlined:hover,html.theme--documenter-dark .button.is-white.is-inverted.is-outlined.is-hovered,html.theme--documenter-dark .button.is-white.is-inverted.is-outlined:focus,html.theme--documenter-dark .button.is-white.is-inverted.is-outlined.is-focused{background-color:#0a0a0a;color:#fff}html.theme--documenter-dark .button.is-white.is-inverted.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-white.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-white.is-inverted.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-white.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-white.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}html.theme--documenter-dark .button.is-black{background-color:#0a0a0a;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-black:hover,html.theme--documenter-dark .button.is-black.is-hovered{background-color:#040404;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-black:focus,html.theme--documenter-dark .button.is-black.is-focused{border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-black:focus:not(:active),html.theme--documenter-dark .button.is-black.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}html.theme--documenter-dark .button.is-black:active,html.theme--documenter-dark .button.is-black.is-active{background-color:#000;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-black[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-black{background-color:#0a0a0a;border-color:#0a0a0a;box-shadow:none}html.theme--documenter-dark .button.is-black.is-inverted{background-color:#fff;color:#0a0a0a}html.theme--documenter-dark .button.is-black.is-inverted:hover,html.theme--documenter-dark .button.is-black.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--documenter-dark .button.is-black.is-inverted[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-black.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#0a0a0a}html.theme--documenter-dark .button.is-black.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}html.theme--documenter-dark .button.is-black.is-outlined:hover,html.theme--documenter-dark .button.is-black.is-outlined.is-hovered,html.theme--documenter-dark .button.is-black.is-outlined:focus,html.theme--documenter-dark .button.is-black.is-outlined.is-focused{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}html.theme--documenter-dark .button.is-black.is-outlined.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--documenter-dark .button.is-black.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-black.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-black.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-black.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-black.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}html.theme--documenter-dark .button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--documenter-dark .button.is-black.is-inverted.is-outlined:hover,html.theme--documenter-dark .button.is-black.is-inverted.is-outlined.is-hovered,html.theme--documenter-dark .button.is-black.is-inverted.is-outlined:focus,html.theme--documenter-dark .button.is-black.is-inverted.is-outlined.is-focused{background-color:#fff;color:#0a0a0a}html.theme--documenter-dark .button.is-black.is-inverted.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-black.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-black.is-inverted.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-black.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--documenter-dark .button.is-black.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--documenter-dark .button.is-light{background-color:#ecf0f1;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-light:hover,html.theme--documenter-dark .button.is-light.is-hovered{background-color:#e5eaec;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-light:focus,html.theme--documenter-dark .button.is-light.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-light:focus:not(:active),html.theme--documenter-dark .button.is-light.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(236,240,241,0.25)}html.theme--documenter-dark .button.is-light:active,html.theme--documenter-dark .button.is-light.is-active{background-color:#dde4e6;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-light[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-light{background-color:#ecf0f1;border-color:#ecf0f1;box-shadow:none}html.theme--documenter-dark .button.is-light.is-inverted{background-color:rgba(0,0,0,0.7);color:#ecf0f1}html.theme--documenter-dark .button.is-light.is-inverted:hover,html.theme--documenter-dark .button.is-light.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-light.is-inverted[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-light.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#ecf0f1}html.theme--documenter-dark .button.is-light.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--documenter-dark .button.is-light.is-outlined{background-color:transparent;border-color:#ecf0f1;color:#ecf0f1}html.theme--documenter-dark .button.is-light.is-outlined:hover,html.theme--documenter-dark .button.is-light.is-outlined.is-hovered,html.theme--documenter-dark .button.is-light.is-outlined:focus,html.theme--documenter-dark .button.is-light.is-outlined.is-focused{background-color:#ecf0f1;border-color:#ecf0f1;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-light.is-outlined.is-loading::after{border-color:transparent transparent #ecf0f1 #ecf0f1 !important}html.theme--documenter-dark .button.is-light.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-light.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-light.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-light.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--documenter-dark .button.is-light.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-light.is-outlined{background-color:transparent;border-color:#ecf0f1;box-shadow:none;color:#ecf0f1}html.theme--documenter-dark .button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-light.is-inverted.is-outlined:hover,html.theme--documenter-dark .button.is-light.is-inverted.is-outlined.is-hovered,html.theme--documenter-dark .button.is-light.is-inverted.is-outlined:focus,html.theme--documenter-dark .button.is-light.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#ecf0f1}html.theme--documenter-dark .button.is-light.is-inverted.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-light.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-light.is-inverted.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-light.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #ecf0f1 #ecf0f1 !important}html.theme--documenter-dark .button.is-light.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-dark,html.theme--documenter-dark .content kbd.button{background-color:#282f2f;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-dark:hover,html.theme--documenter-dark .content kbd.button:hover,html.theme--documenter-dark .button.is-dark.is-hovered,html.theme--documenter-dark .content kbd.button.is-hovered{background-color:#232829;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-dark:focus,html.theme--documenter-dark .content kbd.button:focus,html.theme--documenter-dark .button.is-dark.is-focused,html.theme--documenter-dark .content kbd.button.is-focused{border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-dark:focus:not(:active),html.theme--documenter-dark .content kbd.button:focus:not(:active),html.theme--documenter-dark .button.is-dark.is-focused:not(:active),html.theme--documenter-dark .content kbd.button.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(40,47,47,0.25)}html.theme--documenter-dark .button.is-dark:active,html.theme--documenter-dark .content kbd.button:active,html.theme--documenter-dark .button.is-dark.is-active,html.theme--documenter-dark .content kbd.button.is-active{background-color:#1d2122;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-dark[disabled],html.theme--documenter-dark .content kbd.button[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-dark,fieldset[disabled] html.theme--documenter-dark .content kbd.button{background-color:#282f2f;border-color:#282f2f;box-shadow:none}html.theme--documenter-dark .button.is-dark.is-inverted,html.theme--documenter-dark .content kbd.button.is-inverted{background-color:#fff;color:#282f2f}html.theme--documenter-dark .button.is-dark.is-inverted:hover,html.theme--documenter-dark .content kbd.button.is-inverted:hover,html.theme--documenter-dark .button.is-dark.is-inverted.is-hovered,html.theme--documenter-dark .content kbd.button.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--documenter-dark .button.is-dark.is-inverted[disabled],html.theme--documenter-dark .content kbd.button.is-inverted[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-dark.is-inverted,fieldset[disabled] html.theme--documenter-dark .content kbd.button.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#282f2f}html.theme--documenter-dark .button.is-dark.is-loading::after,html.theme--documenter-dark .content kbd.button.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-dark.is-outlined,html.theme--documenter-dark .content kbd.button.is-outlined{background-color:transparent;border-color:#282f2f;color:#282f2f}html.theme--documenter-dark .button.is-dark.is-outlined:hover,html.theme--documenter-dark .content kbd.button.is-outlined:hover,html.theme--documenter-dark .button.is-dark.is-outlined.is-hovered,html.theme--documenter-dark .content kbd.button.is-outlined.is-hovered,html.theme--documenter-dark .button.is-dark.is-outlined:focus,html.theme--documenter-dark .content kbd.button.is-outlined:focus,html.theme--documenter-dark .button.is-dark.is-outlined.is-focused,html.theme--documenter-dark .content kbd.button.is-outlined.is-focused{background-color:#282f2f;border-color:#282f2f;color:#fff}html.theme--documenter-dark .button.is-dark.is-outlined.is-loading::after,html.theme--documenter-dark .content kbd.button.is-outlined.is-loading::after{border-color:transparent transparent #282f2f #282f2f !important}html.theme--documenter-dark .button.is-dark.is-outlined.is-loading:hover::after,html.theme--documenter-dark .content kbd.button.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-dark.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .content kbd.button.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-dark.is-outlined.is-loading:focus::after,html.theme--documenter-dark .content kbd.button.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-dark.is-outlined.is-loading.is-focused::after,html.theme--documenter-dark .content kbd.button.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-dark.is-outlined[disabled],html.theme--documenter-dark .content kbd.button.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-dark.is-outlined,fieldset[disabled] html.theme--documenter-dark .content kbd.button.is-outlined{background-color:transparent;border-color:#282f2f;box-shadow:none;color:#282f2f}html.theme--documenter-dark .button.is-dark.is-inverted.is-outlined,html.theme--documenter-dark .content kbd.button.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--documenter-dark .button.is-dark.is-inverted.is-outlined:hover,html.theme--documenter-dark .content kbd.button.is-inverted.is-outlined:hover,html.theme--documenter-dark .button.is-dark.is-inverted.is-outlined.is-hovered,html.theme--documenter-dark .content kbd.button.is-inverted.is-outlined.is-hovered,html.theme--documenter-dark .button.is-dark.is-inverted.is-outlined:focus,html.theme--documenter-dark .content kbd.button.is-inverted.is-outlined:focus,html.theme--documenter-dark .button.is-dark.is-inverted.is-outlined.is-focused,html.theme--documenter-dark .content kbd.button.is-inverted.is-outlined.is-focused{background-color:#fff;color:#282f2f}html.theme--documenter-dark .button.is-dark.is-inverted.is-outlined.is-loading:hover::after,html.theme--documenter-dark .content kbd.button.is-inverted.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-dark.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .content kbd.button.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-dark.is-inverted.is-outlined.is-loading:focus::after,html.theme--documenter-dark .content kbd.button.is-inverted.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-dark.is-inverted.is-outlined.is-loading.is-focused::after,html.theme--documenter-dark .content kbd.button.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #282f2f #282f2f !important}html.theme--documenter-dark .button.is-dark.is-inverted.is-outlined[disabled],html.theme--documenter-dark .content kbd.button.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-dark.is-inverted.is-outlined,fieldset[disabled] html.theme--documenter-dark .content kbd.button.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--documenter-dark .button.is-primary,html.theme--documenter-dark .docstring>section>a.button.docs-sourcelink{background-color:#375a7f;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-primary:hover,html.theme--documenter-dark .docstring>section>a.button.docs-sourcelink:hover,html.theme--documenter-dark .button.is-primary.is-hovered,html.theme--documenter-dark .docstring>section>a.button.is-hovered.docs-sourcelink{background-color:#335476;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-primary:focus,html.theme--documenter-dark .docstring>section>a.button.docs-sourcelink:focus,html.theme--documenter-dark .button.is-primary.is-focused,html.theme--documenter-dark .docstring>section>a.button.is-focused.docs-sourcelink{border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-primary:focus:not(:active),html.theme--documenter-dark .docstring>section>a.button.docs-sourcelink:focus:not(:active),html.theme--documenter-dark .button.is-primary.is-focused:not(:active),html.theme--documenter-dark .docstring>section>a.button.is-focused.docs-sourcelink:not(:active){box-shadow:0 0 0 0.125em rgba(55,90,127,0.25)}html.theme--documenter-dark .button.is-primary:active,html.theme--documenter-dark .docstring>section>a.button.docs-sourcelink:active,html.theme--documenter-dark .button.is-primary.is-active,html.theme--documenter-dark .docstring>section>a.button.is-active.docs-sourcelink{background-color:#2f4d6d;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-primary[disabled],html.theme--documenter-dark .docstring>section>a.button.docs-sourcelink[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-primary,fieldset[disabled] html.theme--documenter-dark .docstring>section>a.button.docs-sourcelink{background-color:#375a7f;border-color:#375a7f;box-shadow:none}html.theme--documenter-dark .button.is-primary.is-inverted,html.theme--documenter-dark .docstring>section>a.button.is-inverted.docs-sourcelink{background-color:#fff;color:#375a7f}html.theme--documenter-dark .button.is-primary.is-inverted:hover,html.theme--documenter-dark .docstring>section>a.button.is-inverted.docs-sourcelink:hover,html.theme--documenter-dark .button.is-primary.is-inverted.is-hovered,html.theme--documenter-dark .docstring>section>a.button.is-inverted.is-hovered.docs-sourcelink{background-color:#f2f2f2}html.theme--documenter-dark .button.is-primary.is-inverted[disabled],html.theme--documenter-dark .docstring>section>a.button.is-inverted.docs-sourcelink[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-primary.is-inverted,fieldset[disabled] html.theme--documenter-dark .docstring>section>a.button.is-inverted.docs-sourcelink{background-color:#fff;border-color:transparent;box-shadow:none;color:#375a7f}html.theme--documenter-dark .button.is-primary.is-loading::after,html.theme--documenter-dark .docstring>section>a.button.is-loading.docs-sourcelink::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-primary.is-outlined,html.theme--documenter-dark .docstring>section>a.button.is-outlined.docs-sourcelink{background-color:transparent;border-color:#375a7f;color:#375a7f}html.theme--documenter-dark .button.is-primary.is-outlined:hover,html.theme--documenter-dark .docstring>section>a.button.is-outlined.docs-sourcelink:hover,html.theme--documenter-dark .button.is-primary.is-outlined.is-hovered,html.theme--documenter-dark .docstring>section>a.button.is-outlined.is-hovered.docs-sourcelink,html.theme--documenter-dark .button.is-primary.is-outlined:focus,html.theme--documenter-dark .docstring>section>a.button.is-outlined.docs-sourcelink:focus,html.theme--documenter-dark .button.is-primary.is-outlined.is-focused,html.theme--documenter-dark .docstring>section>a.button.is-outlined.is-focused.docs-sourcelink{background-color:#375a7f;border-color:#375a7f;color:#fff}html.theme--documenter-dark .button.is-primary.is-outlined.is-loading::after,html.theme--documenter-dark .docstring>section>a.button.is-outlined.is-loading.docs-sourcelink::after{border-color:transparent transparent #375a7f #375a7f !important}html.theme--documenter-dark .button.is-primary.is-outlined.is-loading:hover::after,html.theme--documenter-dark .docstring>section>a.button.is-outlined.is-loading.docs-sourcelink:hover::after,html.theme--documenter-dark .button.is-primary.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .docstring>section>a.button.is-outlined.is-loading.is-hovered.docs-sourcelink::after,html.theme--documenter-dark .button.is-primary.is-outlined.is-loading:focus::after,html.theme--documenter-dark .docstring>section>a.button.is-outlined.is-loading.docs-sourcelink:focus::after,html.theme--documenter-dark .button.is-primary.is-outlined.is-loading.is-focused::after,html.theme--documenter-dark .docstring>section>a.button.is-outlined.is-loading.is-focused.docs-sourcelink::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-primary.is-outlined[disabled],html.theme--documenter-dark .docstring>section>a.button.is-outlined.docs-sourcelink[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-primary.is-outlined,fieldset[disabled] html.theme--documenter-dark .docstring>section>a.button.is-outlined.docs-sourcelink{background-color:transparent;border-color:#375a7f;box-shadow:none;color:#375a7f}html.theme--documenter-dark .button.is-primary.is-inverted.is-outlined,html.theme--documenter-dark .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink{background-color:transparent;border-color:#fff;color:#fff}html.theme--documenter-dark .button.is-primary.is-inverted.is-outlined:hover,html.theme--documenter-dark .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink:hover,html.theme--documenter-dark .button.is-primary.is-inverted.is-outlined.is-hovered,html.theme--documenter-dark .docstring>section>a.button.is-inverted.is-outlined.is-hovered.docs-sourcelink,html.theme--documenter-dark .button.is-primary.is-inverted.is-outlined:focus,html.theme--documenter-dark .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink:focus,html.theme--documenter-dark .button.is-primary.is-inverted.is-outlined.is-focused,html.theme--documenter-dark .docstring>section>a.button.is-inverted.is-outlined.is-focused.docs-sourcelink{background-color:#fff;color:#375a7f}html.theme--documenter-dark .button.is-primary.is-inverted.is-outlined.is-loading:hover::after,html.theme--documenter-dark .docstring>section>a.button.is-inverted.is-outlined.is-loading.docs-sourcelink:hover::after,html.theme--documenter-dark .button.is-primary.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .docstring>section>a.button.is-inverted.is-outlined.is-loading.is-hovered.docs-sourcelink::after,html.theme--documenter-dark .button.is-primary.is-inverted.is-outlined.is-loading:focus::after,html.theme--documenter-dark .docstring>section>a.button.is-inverted.is-outlined.is-loading.docs-sourcelink:focus::after,html.theme--documenter-dark .button.is-primary.is-inverted.is-outlined.is-loading.is-focused::after,html.theme--documenter-dark .docstring>section>a.button.is-inverted.is-outlined.is-loading.is-focused.docs-sourcelink::after{border-color:transparent transparent #375a7f #375a7f !important}html.theme--documenter-dark .button.is-primary.is-inverted.is-outlined[disabled],html.theme--documenter-dark .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-primary.is-inverted.is-outlined,fieldset[disabled] html.theme--documenter-dark .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--documenter-dark .button.is-primary.is-light,html.theme--documenter-dark .docstring>section>a.button.is-light.docs-sourcelink{background-color:#f1f5f9;color:#4d7eb2}html.theme--documenter-dark .button.is-primary.is-light:hover,html.theme--documenter-dark .docstring>section>a.button.is-light.docs-sourcelink:hover,html.theme--documenter-dark .button.is-primary.is-light.is-hovered,html.theme--documenter-dark .docstring>section>a.button.is-light.is-hovered.docs-sourcelink{background-color:#e8eef5;border-color:transparent;color:#4d7eb2}html.theme--documenter-dark .button.is-primary.is-light:active,html.theme--documenter-dark .docstring>section>a.button.is-light.docs-sourcelink:active,html.theme--documenter-dark .button.is-primary.is-light.is-active,html.theme--documenter-dark .docstring>section>a.button.is-light.is-active.docs-sourcelink{background-color:#dfe8f1;border-color:transparent;color:#4d7eb2}html.theme--documenter-dark .button.is-link{background-color:#1abc9c;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-link:hover,html.theme--documenter-dark .button.is-link.is-hovered{background-color:#18b193;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-link:focus,html.theme--documenter-dark .button.is-link.is-focused{border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-link:focus:not(:active),html.theme--documenter-dark .button.is-link.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(26,188,156,0.25)}html.theme--documenter-dark .button.is-link:active,html.theme--documenter-dark .button.is-link.is-active{background-color:#17a689;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-link[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-link{background-color:#1abc9c;border-color:#1abc9c;box-shadow:none}html.theme--documenter-dark .button.is-link.is-inverted{background-color:#fff;color:#1abc9c}html.theme--documenter-dark .button.is-link.is-inverted:hover,html.theme--documenter-dark .button.is-link.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--documenter-dark .button.is-link.is-inverted[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-link.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#1abc9c}html.theme--documenter-dark .button.is-link.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-link.is-outlined{background-color:transparent;border-color:#1abc9c;color:#1abc9c}html.theme--documenter-dark .button.is-link.is-outlined:hover,html.theme--documenter-dark .button.is-link.is-outlined.is-hovered,html.theme--documenter-dark .button.is-link.is-outlined:focus,html.theme--documenter-dark .button.is-link.is-outlined.is-focused{background-color:#1abc9c;border-color:#1abc9c;color:#fff}html.theme--documenter-dark .button.is-link.is-outlined.is-loading::after{border-color:transparent transparent #1abc9c #1abc9c !important}html.theme--documenter-dark .button.is-link.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-link.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-link.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-link.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-link.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-link.is-outlined{background-color:transparent;border-color:#1abc9c;box-shadow:none;color:#1abc9c}html.theme--documenter-dark .button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--documenter-dark .button.is-link.is-inverted.is-outlined:hover,html.theme--documenter-dark .button.is-link.is-inverted.is-outlined.is-hovered,html.theme--documenter-dark .button.is-link.is-inverted.is-outlined:focus,html.theme--documenter-dark .button.is-link.is-inverted.is-outlined.is-focused{background-color:#fff;color:#1abc9c}html.theme--documenter-dark .button.is-link.is-inverted.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-link.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-link.is-inverted.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-link.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #1abc9c #1abc9c !important}html.theme--documenter-dark .button.is-link.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--documenter-dark .button.is-link.is-light{background-color:#edfdf9;color:#15987e}html.theme--documenter-dark .button.is-link.is-light:hover,html.theme--documenter-dark .button.is-link.is-light.is-hovered{background-color:#e2fbf6;border-color:transparent;color:#15987e}html.theme--documenter-dark .button.is-link.is-light:active,html.theme--documenter-dark .button.is-link.is-light.is-active{background-color:#d7f9f3;border-color:transparent;color:#15987e}html.theme--documenter-dark .button.is-info{background-color:#024c7d;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-info:hover,html.theme--documenter-dark .button.is-info.is-hovered{background-color:#024470;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-info:focus,html.theme--documenter-dark .button.is-info.is-focused{border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-info:focus:not(:active),html.theme--documenter-dark .button.is-info.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(2,76,125,0.25)}html.theme--documenter-dark .button.is-info:active,html.theme--documenter-dark .button.is-info.is-active{background-color:#023d64;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-info[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-info{background-color:#024c7d;border-color:#024c7d;box-shadow:none}html.theme--documenter-dark .button.is-info.is-inverted{background-color:#fff;color:#024c7d}html.theme--documenter-dark .button.is-info.is-inverted:hover,html.theme--documenter-dark .button.is-info.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--documenter-dark .button.is-info.is-inverted[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-info.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#024c7d}html.theme--documenter-dark .button.is-info.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-info.is-outlined{background-color:transparent;border-color:#024c7d;color:#024c7d}html.theme--documenter-dark .button.is-info.is-outlined:hover,html.theme--documenter-dark .button.is-info.is-outlined.is-hovered,html.theme--documenter-dark .button.is-info.is-outlined:focus,html.theme--documenter-dark .button.is-info.is-outlined.is-focused{background-color:#024c7d;border-color:#024c7d;color:#fff}html.theme--documenter-dark .button.is-info.is-outlined.is-loading::after{border-color:transparent transparent #024c7d #024c7d !important}html.theme--documenter-dark .button.is-info.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-info.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-info.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-info.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-info.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-info.is-outlined{background-color:transparent;border-color:#024c7d;box-shadow:none;color:#024c7d}html.theme--documenter-dark .button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--documenter-dark .button.is-info.is-inverted.is-outlined:hover,html.theme--documenter-dark .button.is-info.is-inverted.is-outlined.is-hovered,html.theme--documenter-dark .button.is-info.is-inverted.is-outlined:focus,html.theme--documenter-dark .button.is-info.is-inverted.is-outlined.is-focused{background-color:#fff;color:#024c7d}html.theme--documenter-dark .button.is-info.is-inverted.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-info.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-info.is-inverted.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-info.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #024c7d #024c7d !important}html.theme--documenter-dark .button.is-info.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--documenter-dark .button.is-info.is-light{background-color:#ebf7ff;color:#0e9dfb}html.theme--documenter-dark .button.is-info.is-light:hover,html.theme--documenter-dark .button.is-info.is-light.is-hovered{background-color:#def2fe;border-color:transparent;color:#0e9dfb}html.theme--documenter-dark .button.is-info.is-light:active,html.theme--documenter-dark .button.is-info.is-light.is-active{background-color:#d2edfe;border-color:transparent;color:#0e9dfb}html.theme--documenter-dark .button.is-success{background-color:#008438;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-success:hover,html.theme--documenter-dark .button.is-success.is-hovered{background-color:#073;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-success:focus,html.theme--documenter-dark .button.is-success.is-focused{border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-success:focus:not(:active),html.theme--documenter-dark .button.is-success.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(0,132,56,0.25)}html.theme--documenter-dark .button.is-success:active,html.theme--documenter-dark .button.is-success.is-active{background-color:#006b2d;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-success[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-success{background-color:#008438;border-color:#008438;box-shadow:none}html.theme--documenter-dark .button.is-success.is-inverted{background-color:#fff;color:#008438}html.theme--documenter-dark .button.is-success.is-inverted:hover,html.theme--documenter-dark .button.is-success.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--documenter-dark .button.is-success.is-inverted[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-success.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#008438}html.theme--documenter-dark .button.is-success.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-success.is-outlined{background-color:transparent;border-color:#008438;color:#008438}html.theme--documenter-dark .button.is-success.is-outlined:hover,html.theme--documenter-dark .button.is-success.is-outlined.is-hovered,html.theme--documenter-dark .button.is-success.is-outlined:focus,html.theme--documenter-dark .button.is-success.is-outlined.is-focused{background-color:#008438;border-color:#008438;color:#fff}html.theme--documenter-dark .button.is-success.is-outlined.is-loading::after{border-color:transparent transparent #008438 #008438 !important}html.theme--documenter-dark .button.is-success.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-success.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-success.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-success.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-success.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-success.is-outlined{background-color:transparent;border-color:#008438;box-shadow:none;color:#008438}html.theme--documenter-dark .button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--documenter-dark .button.is-success.is-inverted.is-outlined:hover,html.theme--documenter-dark .button.is-success.is-inverted.is-outlined.is-hovered,html.theme--documenter-dark .button.is-success.is-inverted.is-outlined:focus,html.theme--documenter-dark .button.is-success.is-inverted.is-outlined.is-focused{background-color:#fff;color:#008438}html.theme--documenter-dark .button.is-success.is-inverted.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-success.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-success.is-inverted.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-success.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #008438 #008438 !important}html.theme--documenter-dark .button.is-success.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--documenter-dark .button.is-success.is-light{background-color:#ebfff3;color:#00eb64}html.theme--documenter-dark .button.is-success.is-light:hover,html.theme--documenter-dark .button.is-success.is-light.is-hovered{background-color:#deffec;border-color:transparent;color:#00eb64}html.theme--documenter-dark .button.is-success.is-light:active,html.theme--documenter-dark .button.is-success.is-light.is-active{background-color:#d1ffe5;border-color:transparent;color:#00eb64}html.theme--documenter-dark .button.is-warning{background-color:#ad8100;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-warning:hover,html.theme--documenter-dark .button.is-warning.is-hovered{background-color:#a07700;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-warning:focus,html.theme--documenter-dark .button.is-warning.is-focused{border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-warning:focus:not(:active),html.theme--documenter-dark .button.is-warning.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(173,129,0,0.25)}html.theme--documenter-dark .button.is-warning:active,html.theme--documenter-dark .button.is-warning.is-active{background-color:#946e00;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-warning[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-warning{background-color:#ad8100;border-color:#ad8100;box-shadow:none}html.theme--documenter-dark .button.is-warning.is-inverted{background-color:#fff;color:#ad8100}html.theme--documenter-dark .button.is-warning.is-inverted:hover,html.theme--documenter-dark .button.is-warning.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--documenter-dark .button.is-warning.is-inverted[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-warning.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#ad8100}html.theme--documenter-dark .button.is-warning.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-warning.is-outlined{background-color:transparent;border-color:#ad8100;color:#ad8100}html.theme--documenter-dark .button.is-warning.is-outlined:hover,html.theme--documenter-dark .button.is-warning.is-outlined.is-hovered,html.theme--documenter-dark .button.is-warning.is-outlined:focus,html.theme--documenter-dark .button.is-warning.is-outlined.is-focused{background-color:#ad8100;border-color:#ad8100;color:#fff}html.theme--documenter-dark .button.is-warning.is-outlined.is-loading::after{border-color:transparent transparent #ad8100 #ad8100 !important}html.theme--documenter-dark .button.is-warning.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-warning.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-warning.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-warning.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-warning.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-warning.is-outlined{background-color:transparent;border-color:#ad8100;box-shadow:none;color:#ad8100}html.theme--documenter-dark .button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--documenter-dark .button.is-warning.is-inverted.is-outlined:hover,html.theme--documenter-dark .button.is-warning.is-inverted.is-outlined.is-hovered,html.theme--documenter-dark .button.is-warning.is-inverted.is-outlined:focus,html.theme--documenter-dark .button.is-warning.is-inverted.is-outlined.is-focused{background-color:#fff;color:#ad8100}html.theme--documenter-dark .button.is-warning.is-inverted.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-warning.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-warning.is-inverted.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-warning.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #ad8100 #ad8100 !important}html.theme--documenter-dark .button.is-warning.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--documenter-dark .button.is-warning.is-light{background-color:#fffaeb;color:#d19c00}html.theme--documenter-dark .button.is-warning.is-light:hover,html.theme--documenter-dark .button.is-warning.is-light.is-hovered{background-color:#fff7de;border-color:transparent;color:#d19c00}html.theme--documenter-dark .button.is-warning.is-light:active,html.theme--documenter-dark .button.is-warning.is-light.is-active{background-color:#fff3d1;border-color:transparent;color:#d19c00}html.theme--documenter-dark .button.is-danger{background-color:#9e1b0d;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-danger:hover,html.theme--documenter-dark .button.is-danger.is-hovered{background-color:#92190c;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-danger:focus,html.theme--documenter-dark .button.is-danger.is-focused{border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-danger:focus:not(:active),html.theme--documenter-dark .button.is-danger.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(158,27,13,0.25)}html.theme--documenter-dark .button.is-danger:active,html.theme--documenter-dark .button.is-danger.is-active{background-color:#86170b;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-danger[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-danger{background-color:#9e1b0d;border-color:#9e1b0d;box-shadow:none}html.theme--documenter-dark .button.is-danger.is-inverted{background-color:#fff;color:#9e1b0d}html.theme--documenter-dark .button.is-danger.is-inverted:hover,html.theme--documenter-dark .button.is-danger.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--documenter-dark .button.is-danger.is-inverted[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-danger.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#9e1b0d}html.theme--documenter-dark .button.is-danger.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-danger.is-outlined{background-color:transparent;border-color:#9e1b0d;color:#9e1b0d}html.theme--documenter-dark .button.is-danger.is-outlined:hover,html.theme--documenter-dark .button.is-danger.is-outlined.is-hovered,html.theme--documenter-dark .button.is-danger.is-outlined:focus,html.theme--documenter-dark .button.is-danger.is-outlined.is-focused{background-color:#9e1b0d;border-color:#9e1b0d;color:#fff}html.theme--documenter-dark .button.is-danger.is-outlined.is-loading::after{border-color:transparent transparent #9e1b0d #9e1b0d !important}html.theme--documenter-dark .button.is-danger.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-danger.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-danger.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-danger.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-danger.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-danger.is-outlined{background-color:transparent;border-color:#9e1b0d;box-shadow:none;color:#9e1b0d}html.theme--documenter-dark .button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--documenter-dark .button.is-danger.is-inverted.is-outlined:hover,html.theme--documenter-dark .button.is-danger.is-inverted.is-outlined.is-hovered,html.theme--documenter-dark .button.is-danger.is-inverted.is-outlined:focus,html.theme--documenter-dark .button.is-danger.is-inverted.is-outlined.is-focused{background-color:#fff;color:#9e1b0d}html.theme--documenter-dark .button.is-danger.is-inverted.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-danger.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-danger.is-inverted.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-danger.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #9e1b0d #9e1b0d !important}html.theme--documenter-dark .button.is-danger.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--documenter-dark .button.is-danger.is-light{background-color:#fdeeec;color:#ec311d}html.theme--documenter-dark .button.is-danger.is-light:hover,html.theme--documenter-dark .button.is-danger.is-light.is-hovered{background-color:#fce3e0;border-color:transparent;color:#ec311d}html.theme--documenter-dark .button.is-danger.is-light:active,html.theme--documenter-dark .button.is-danger.is-light.is-active{background-color:#fcd8d5;border-color:transparent;color:#ec311d}html.theme--documenter-dark .button.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.button{font-size:.75rem}html.theme--documenter-dark .button.is-small:not(.is-rounded),html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.button:not(.is-rounded){border-radius:3px}html.theme--documenter-dark .button.is-normal{font-size:1rem}html.theme--documenter-dark .button.is-medium{font-size:1.25rem}html.theme--documenter-dark .button.is-large{font-size:1.5rem}html.theme--documenter-dark .button[disabled],fieldset[disabled] html.theme--documenter-dark .button{background-color:#8c9b9d;border-color:#5e6d6f;box-shadow:none;opacity:.5}html.theme--documenter-dark .button.is-fullwidth{display:flex;width:100%}html.theme--documenter-dark .button.is-loading{color:transparent !important;pointer-events:none}html.theme--documenter-dark .button.is-loading::after{position:absolute;left:calc(50% - (1em * 0.5));top:calc(50% - (1em * 0.5));position:absolute !important}html.theme--documenter-dark .button.is-static{background-color:#282f2f;border-color:#5e6d6f;color:#dbdee0;box-shadow:none;pointer-events:none}html.theme--documenter-dark .button.is-rounded,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.button{border-radius:9999px;padding-left:calc(1em + 0.25em);padding-right:calc(1em + 0.25em)}html.theme--documenter-dark .buttons{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}html.theme--documenter-dark .buttons .button{margin-bottom:0.5rem}html.theme--documenter-dark .buttons .button:not(:last-child):not(.is-fullwidth){margin-right:.5rem}html.theme--documenter-dark .buttons:last-child{margin-bottom:-0.5rem}html.theme--documenter-dark .buttons:not(:last-child){margin-bottom:1rem}html.theme--documenter-dark .buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large){font-size:.75rem}html.theme--documenter-dark .buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large):not(.is-rounded){border-radius:3px}html.theme--documenter-dark .buttons.are-medium .button:not(.is-small):not(.is-normal):not(.is-large){font-size:1.25rem}html.theme--documenter-dark .buttons.are-large .button:not(.is-small):not(.is-normal):not(.is-medium){font-size:1.5rem}html.theme--documenter-dark .buttons.has-addons .button:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}html.theme--documenter-dark .buttons.has-addons .button:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0;margin-right:-1px}html.theme--documenter-dark .buttons.has-addons .button:last-child{margin-right:0}html.theme--documenter-dark .buttons.has-addons .button:hover,html.theme--documenter-dark .buttons.has-addons .button.is-hovered{z-index:2}html.theme--documenter-dark .buttons.has-addons .button:focus,html.theme--documenter-dark .buttons.has-addons .button.is-focused,html.theme--documenter-dark .buttons.has-addons .button:active,html.theme--documenter-dark .buttons.has-addons .button.is-active,html.theme--documenter-dark .buttons.has-addons .button.is-selected{z-index:3}html.theme--documenter-dark .buttons.has-addons .button:focus:hover,html.theme--documenter-dark .buttons.has-addons .button.is-focused:hover,html.theme--documenter-dark .buttons.has-addons .button:active:hover,html.theme--documenter-dark .buttons.has-addons .button.is-active:hover,html.theme--documenter-dark .buttons.has-addons .button.is-selected:hover{z-index:4}html.theme--documenter-dark .buttons.has-addons .button.is-expanded{flex-grow:1;flex-shrink:1}html.theme--documenter-dark .buttons.is-centered{justify-content:center}html.theme--documenter-dark .buttons.is-centered:not(.has-addons) .button:not(.is-fullwidth){margin-left:0.25rem;margin-right:0.25rem}html.theme--documenter-dark .buttons.is-right{justify-content:flex-end}html.theme--documenter-dark .buttons.is-right:not(.has-addons) .button:not(.is-fullwidth){margin-left:0.25rem;margin-right:0.25rem}@media screen and (max-width: 768px){html.theme--documenter-dark .button.is-responsive.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-responsive{font-size:.5625rem}html.theme--documenter-dark .button.is-responsive,html.theme--documenter-dark .button.is-responsive.is-normal{font-size:.65625rem}html.theme--documenter-dark .button.is-responsive.is-medium{font-size:.75rem}html.theme--documenter-dark .button.is-responsive.is-large{font-size:1rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--documenter-dark .button.is-responsive.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-responsive{font-size:.65625rem}html.theme--documenter-dark .button.is-responsive,html.theme--documenter-dark .button.is-responsive.is-normal{font-size:.75rem}html.theme--documenter-dark .button.is-responsive.is-medium{font-size:1rem}html.theme--documenter-dark .button.is-responsive.is-large{font-size:1.25rem}}html.theme--documenter-dark .container{flex-grow:1;margin:0 auto;position:relative;width:auto}html.theme--documenter-dark .container.is-fluid{max-width:none !important;padding-left:32px;padding-right:32px;width:100%}@media screen and (min-width: 1056px){html.theme--documenter-dark .container{max-width:992px}}@media screen and (max-width: 1215px){html.theme--documenter-dark .container.is-widescreen:not(.is-max-desktop){max-width:1152px}}@media screen and (max-width: 1407px){html.theme--documenter-dark .container.is-fullhd:not(.is-max-desktop):not(.is-max-widescreen){max-width:1344px}}@media screen and (min-width: 1216px){html.theme--documenter-dark .container:not(.is-max-desktop){max-width:1152px}}@media screen and (min-width: 1408px){html.theme--documenter-dark .container:not(.is-max-desktop):not(.is-max-widescreen){max-width:1344px}}html.theme--documenter-dark .content li+li{margin-top:0.25em}html.theme--documenter-dark .content p:not(:last-child),html.theme--documenter-dark .content dl:not(:last-child),html.theme--documenter-dark .content ol:not(:last-child),html.theme--documenter-dark .content ul:not(:last-child),html.theme--documenter-dark .content blockquote:not(:last-child),html.theme--documenter-dark .content pre:not(:last-child),html.theme--documenter-dark .content table:not(:last-child){margin-bottom:1em}html.theme--documenter-dark .content h1,html.theme--documenter-dark .content h2,html.theme--documenter-dark .content h3,html.theme--documenter-dark .content h4,html.theme--documenter-dark .content h5,html.theme--documenter-dark .content h6{color:#f2f2f2;font-weight:600;line-height:1.125}html.theme--documenter-dark .content h1{font-size:2em;margin-bottom:0.5em}html.theme--documenter-dark .content h1:not(:first-child){margin-top:1em}html.theme--documenter-dark .content h2{font-size:1.75em;margin-bottom:0.5714em}html.theme--documenter-dark .content h2:not(:first-child){margin-top:1.1428em}html.theme--documenter-dark .content h3{font-size:1.5em;margin-bottom:0.6666em}html.theme--documenter-dark .content h3:not(:first-child){margin-top:1.3333em}html.theme--documenter-dark .content h4{font-size:1.25em;margin-bottom:0.8em}html.theme--documenter-dark .content h5{font-size:1.125em;margin-bottom:0.8888em}html.theme--documenter-dark .content h6{font-size:1em;margin-bottom:1em}html.theme--documenter-dark .content blockquote{background-color:#282f2f;border-left:5px solid #5e6d6f;padding:1.25em 1.5em}html.theme--documenter-dark .content ol{list-style-position:outside;margin-left:2em;margin-top:1em}html.theme--documenter-dark .content ol:not([type]){list-style-type:decimal}html.theme--documenter-dark .content ol.is-lower-alpha:not([type]){list-style-type:lower-alpha}html.theme--documenter-dark .content ol.is-lower-roman:not([type]){list-style-type:lower-roman}html.theme--documenter-dark .content ol.is-upper-alpha:not([type]){list-style-type:upper-alpha}html.theme--documenter-dark .content ol.is-upper-roman:not([type]){list-style-type:upper-roman}html.theme--documenter-dark .content ul{list-style:disc outside;margin-left:2em;margin-top:1em}html.theme--documenter-dark .content ul ul{list-style-type:circle;margin-top:0.5em}html.theme--documenter-dark .content ul ul ul{list-style-type:square}html.theme--documenter-dark .content dd{margin-left:2em}html.theme--documenter-dark .content figure{margin-left:2em;margin-right:2em;text-align:center}html.theme--documenter-dark .content figure:not(:first-child){margin-top:2em}html.theme--documenter-dark .content figure:not(:last-child){margin-bottom:2em}html.theme--documenter-dark .content figure img{display:inline-block}html.theme--documenter-dark .content figure figcaption{font-style:italic}html.theme--documenter-dark .content pre{-webkit-overflow-scrolling:touch;overflow-x:auto;padding:0;white-space:pre;word-wrap:normal}html.theme--documenter-dark .content sup,html.theme--documenter-dark .content sub{font-size:75%}html.theme--documenter-dark .content table{width:100%}html.theme--documenter-dark .content table td,html.theme--documenter-dark .content table th{border:1px solid #5e6d6f;border-width:0 0 1px;padding:0.5em 0.75em;vertical-align:top}html.theme--documenter-dark .content table th{color:#f2f2f2}html.theme--documenter-dark .content table th:not([align]){text-align:inherit}html.theme--documenter-dark .content table thead td,html.theme--documenter-dark .content table thead th{border-width:0 0 2px;color:#f2f2f2}html.theme--documenter-dark .content table tfoot td,html.theme--documenter-dark .content table tfoot th{border-width:2px 0 0;color:#f2f2f2}html.theme--documenter-dark .content table tbody tr:last-child td,html.theme--documenter-dark .content table tbody tr:last-child th{border-bottom-width:0}html.theme--documenter-dark .content .tabs li+li{margin-top:0}html.theme--documenter-dark .content.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.content{font-size:.75rem}html.theme--documenter-dark .content.is-normal{font-size:1rem}html.theme--documenter-dark .content.is-medium{font-size:1.25rem}html.theme--documenter-dark .content.is-large{font-size:1.5rem}html.theme--documenter-dark .icon{align-items:center;display:inline-flex;justify-content:center;height:1.5rem;width:1.5rem}html.theme--documenter-dark .icon.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.icon{height:1rem;width:1rem}html.theme--documenter-dark .icon.is-medium{height:2rem;width:2rem}html.theme--documenter-dark .icon.is-large{height:3rem;width:3rem}html.theme--documenter-dark .icon-text{align-items:flex-start;color:inherit;display:inline-flex;flex-wrap:wrap;line-height:1.5rem;vertical-align:top}html.theme--documenter-dark .icon-text .icon{flex-grow:0;flex-shrink:0}html.theme--documenter-dark .icon-text .icon:not(:last-child){margin-right:.25em}html.theme--documenter-dark .icon-text .icon:not(:first-child){margin-left:.25em}html.theme--documenter-dark div.icon-text{display:flex}html.theme--documenter-dark .image,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img{display:block;position:relative}html.theme--documenter-dark .image img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img img{display:block;height:auto;width:100%}html.theme--documenter-dark .image img.is-rounded,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img img.is-rounded{border-radius:9999px}html.theme--documenter-dark .image.is-fullwidth,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-fullwidth{width:100%}html.theme--documenter-dark .image.is-square img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-square img,html.theme--documenter-dark .image.is-square .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-square .has-ratio,html.theme--documenter-dark .image.is-1by1 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by1 img,html.theme--documenter-dark .image.is-1by1 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by1 .has-ratio,html.theme--documenter-dark .image.is-5by4 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-5by4 img,html.theme--documenter-dark .image.is-5by4 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-5by4 .has-ratio,html.theme--documenter-dark .image.is-4by3 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-4by3 img,html.theme--documenter-dark .image.is-4by3 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-4by3 .has-ratio,html.theme--documenter-dark .image.is-3by2 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by2 img,html.theme--documenter-dark .image.is-3by2 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by2 .has-ratio,html.theme--documenter-dark .image.is-5by3 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-5by3 img,html.theme--documenter-dark .image.is-5by3 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-5by3 .has-ratio,html.theme--documenter-dark .image.is-16by9 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-16by9 img,html.theme--documenter-dark .image.is-16by9 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-16by9 .has-ratio,html.theme--documenter-dark .image.is-2by1 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-2by1 img,html.theme--documenter-dark .image.is-2by1 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-2by1 .has-ratio,html.theme--documenter-dark .image.is-3by1 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by1 img,html.theme--documenter-dark .image.is-3by1 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by1 .has-ratio,html.theme--documenter-dark .image.is-4by5 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-4by5 img,html.theme--documenter-dark .image.is-4by5 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-4by5 .has-ratio,html.theme--documenter-dark .image.is-3by4 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by4 img,html.theme--documenter-dark .image.is-3by4 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by4 .has-ratio,html.theme--documenter-dark .image.is-2by3 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-2by3 img,html.theme--documenter-dark .image.is-2by3 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-2by3 .has-ratio,html.theme--documenter-dark .image.is-3by5 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by5 img,html.theme--documenter-dark .image.is-3by5 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by5 .has-ratio,html.theme--documenter-dark .image.is-9by16 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-9by16 img,html.theme--documenter-dark .image.is-9by16 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-9by16 .has-ratio,html.theme--documenter-dark .image.is-1by2 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by2 img,html.theme--documenter-dark .image.is-1by2 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by2 .has-ratio,html.theme--documenter-dark .image.is-1by3 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by3 img,html.theme--documenter-dark .image.is-1by3 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by3 .has-ratio{height:100%;width:100%}html.theme--documenter-dark .image.is-square,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-square,html.theme--documenter-dark .image.is-1by1,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by1{padding-top:100%}html.theme--documenter-dark .image.is-5by4,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-5by4{padding-top:80%}html.theme--documenter-dark .image.is-4by3,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-4by3{padding-top:75%}html.theme--documenter-dark .image.is-3by2,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by2{padding-top:66.6666%}html.theme--documenter-dark .image.is-5by3,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-5by3{padding-top:60%}html.theme--documenter-dark .image.is-16by9,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-16by9{padding-top:56.25%}html.theme--documenter-dark .image.is-2by1,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-2by1{padding-top:50%}html.theme--documenter-dark .image.is-3by1,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by1{padding-top:33.3333%}html.theme--documenter-dark .image.is-4by5,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-4by5{padding-top:125%}html.theme--documenter-dark .image.is-3by4,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by4{padding-top:133.3333%}html.theme--documenter-dark .image.is-2by3,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-2by3{padding-top:150%}html.theme--documenter-dark .image.is-3by5,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by5{padding-top:166.6666%}html.theme--documenter-dark .image.is-9by16,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-9by16{padding-top:177.7777%}html.theme--documenter-dark .image.is-1by2,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by2{padding-top:200%}html.theme--documenter-dark .image.is-1by3,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by3{padding-top:300%}html.theme--documenter-dark .image.is-16x16,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-16x16{height:16px;width:16px}html.theme--documenter-dark .image.is-24x24,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-24x24{height:24px;width:24px}html.theme--documenter-dark .image.is-32x32,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-32x32{height:32px;width:32px}html.theme--documenter-dark .image.is-48x48,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-48x48{height:48px;width:48px}html.theme--documenter-dark .image.is-64x64,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-64x64{height:64px;width:64px}html.theme--documenter-dark .image.is-96x96,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-96x96{height:96px;width:96px}html.theme--documenter-dark .image.is-128x128,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-128x128{height:128px;width:128px}html.theme--documenter-dark .notification{background-color:#282f2f;border-radius:.4em;position:relative;padding:1.25rem 2.5rem 1.25rem 1.5rem}html.theme--documenter-dark .notification a:not(.button):not(.dropdown-item){color:currentColor;text-decoration:underline}html.theme--documenter-dark .notification strong{color:currentColor}html.theme--documenter-dark .notification code,html.theme--documenter-dark .notification pre{background:#fff}html.theme--documenter-dark .notification pre code{background:transparent}html.theme--documenter-dark .notification>.delete{right:.5rem;position:absolute;top:0.5rem}html.theme--documenter-dark .notification .title,html.theme--documenter-dark .notification .subtitle,html.theme--documenter-dark .notification .content{color:currentColor}html.theme--documenter-dark .notification.is-white{background-color:#fff;color:#0a0a0a}html.theme--documenter-dark .notification.is-black{background-color:#0a0a0a;color:#fff}html.theme--documenter-dark .notification.is-light{background-color:#ecf0f1;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .notification.is-dark,html.theme--documenter-dark .content kbd.notification{background-color:#282f2f;color:#fff}html.theme--documenter-dark .notification.is-primary,html.theme--documenter-dark .docstring>section>a.notification.docs-sourcelink{background-color:#375a7f;color:#fff}html.theme--documenter-dark .notification.is-primary.is-light,html.theme--documenter-dark .docstring>section>a.notification.is-light.docs-sourcelink{background-color:#f1f5f9;color:#4d7eb2}html.theme--documenter-dark .notification.is-link{background-color:#1abc9c;color:#fff}html.theme--documenter-dark .notification.is-link.is-light{background-color:#edfdf9;color:#15987e}html.theme--documenter-dark .notification.is-info{background-color:#024c7d;color:#fff}html.theme--documenter-dark .notification.is-info.is-light{background-color:#ebf7ff;color:#0e9dfb}html.theme--documenter-dark .notification.is-success{background-color:#008438;color:#fff}html.theme--documenter-dark .notification.is-success.is-light{background-color:#ebfff3;color:#00eb64}html.theme--documenter-dark .notification.is-warning{background-color:#ad8100;color:#fff}html.theme--documenter-dark .notification.is-warning.is-light{background-color:#fffaeb;color:#d19c00}html.theme--documenter-dark .notification.is-danger{background-color:#9e1b0d;color:#fff}html.theme--documenter-dark .notification.is-danger.is-light{background-color:#fdeeec;color:#ec311d}html.theme--documenter-dark .progress{-moz-appearance:none;-webkit-appearance:none;border:none;border-radius:9999px;display:block;height:1rem;overflow:hidden;padding:0;width:100%}html.theme--documenter-dark .progress::-webkit-progress-bar{background-color:#343c3d}html.theme--documenter-dark .progress::-webkit-progress-value{background-color:#dbdee0}html.theme--documenter-dark .progress::-moz-progress-bar{background-color:#dbdee0}html.theme--documenter-dark .progress::-ms-fill{background-color:#dbdee0;border:none}html.theme--documenter-dark .progress.is-white::-webkit-progress-value{background-color:#fff}html.theme--documenter-dark .progress.is-white::-moz-progress-bar{background-color:#fff}html.theme--documenter-dark .progress.is-white::-ms-fill{background-color:#fff}html.theme--documenter-dark .progress.is-white:indeterminate{background-image:linear-gradient(to right, #fff 30%, #343c3d 30%)}html.theme--documenter-dark .progress.is-black::-webkit-progress-value{background-color:#0a0a0a}html.theme--documenter-dark .progress.is-black::-moz-progress-bar{background-color:#0a0a0a}html.theme--documenter-dark .progress.is-black::-ms-fill{background-color:#0a0a0a}html.theme--documenter-dark .progress.is-black:indeterminate{background-image:linear-gradient(to right, #0a0a0a 30%, #343c3d 30%)}html.theme--documenter-dark .progress.is-light::-webkit-progress-value{background-color:#ecf0f1}html.theme--documenter-dark .progress.is-light::-moz-progress-bar{background-color:#ecf0f1}html.theme--documenter-dark .progress.is-light::-ms-fill{background-color:#ecf0f1}html.theme--documenter-dark .progress.is-light:indeterminate{background-image:linear-gradient(to right, #ecf0f1 30%, #343c3d 30%)}html.theme--documenter-dark .progress.is-dark::-webkit-progress-value,html.theme--documenter-dark .content kbd.progress::-webkit-progress-value{background-color:#282f2f}html.theme--documenter-dark .progress.is-dark::-moz-progress-bar,html.theme--documenter-dark .content kbd.progress::-moz-progress-bar{background-color:#282f2f}html.theme--documenter-dark .progress.is-dark::-ms-fill,html.theme--documenter-dark .content kbd.progress::-ms-fill{background-color:#282f2f}html.theme--documenter-dark .progress.is-dark:indeterminate,html.theme--documenter-dark .content kbd.progress:indeterminate{background-image:linear-gradient(to right, #282f2f 30%, #343c3d 30%)}html.theme--documenter-dark .progress.is-primary::-webkit-progress-value,html.theme--documenter-dark .docstring>section>a.progress.docs-sourcelink::-webkit-progress-value{background-color:#375a7f}html.theme--documenter-dark .progress.is-primary::-moz-progress-bar,html.theme--documenter-dark .docstring>section>a.progress.docs-sourcelink::-moz-progress-bar{background-color:#375a7f}html.theme--documenter-dark .progress.is-primary::-ms-fill,html.theme--documenter-dark .docstring>section>a.progress.docs-sourcelink::-ms-fill{background-color:#375a7f}html.theme--documenter-dark .progress.is-primary:indeterminate,html.theme--documenter-dark .docstring>section>a.progress.docs-sourcelink:indeterminate{background-image:linear-gradient(to right, #375a7f 30%, #343c3d 30%)}html.theme--documenter-dark .progress.is-link::-webkit-progress-value{background-color:#1abc9c}html.theme--documenter-dark .progress.is-link::-moz-progress-bar{background-color:#1abc9c}html.theme--documenter-dark .progress.is-link::-ms-fill{background-color:#1abc9c}html.theme--documenter-dark .progress.is-link:indeterminate{background-image:linear-gradient(to right, #1abc9c 30%, #343c3d 30%)}html.theme--documenter-dark .progress.is-info::-webkit-progress-value{background-color:#024c7d}html.theme--documenter-dark .progress.is-info::-moz-progress-bar{background-color:#024c7d}html.theme--documenter-dark .progress.is-info::-ms-fill{background-color:#024c7d}html.theme--documenter-dark .progress.is-info:indeterminate{background-image:linear-gradient(to right, #024c7d 30%, #343c3d 30%)}html.theme--documenter-dark .progress.is-success::-webkit-progress-value{background-color:#008438}html.theme--documenter-dark .progress.is-success::-moz-progress-bar{background-color:#008438}html.theme--documenter-dark .progress.is-success::-ms-fill{background-color:#008438}html.theme--documenter-dark .progress.is-success:indeterminate{background-image:linear-gradient(to right, #008438 30%, #343c3d 30%)}html.theme--documenter-dark .progress.is-warning::-webkit-progress-value{background-color:#ad8100}html.theme--documenter-dark .progress.is-warning::-moz-progress-bar{background-color:#ad8100}html.theme--documenter-dark .progress.is-warning::-ms-fill{background-color:#ad8100}html.theme--documenter-dark .progress.is-warning:indeterminate{background-image:linear-gradient(to right, #ad8100 30%, #343c3d 30%)}html.theme--documenter-dark .progress.is-danger::-webkit-progress-value{background-color:#9e1b0d}html.theme--documenter-dark .progress.is-danger::-moz-progress-bar{background-color:#9e1b0d}html.theme--documenter-dark .progress.is-danger::-ms-fill{background-color:#9e1b0d}html.theme--documenter-dark .progress.is-danger:indeterminate{background-image:linear-gradient(to right, #9e1b0d 30%, #343c3d 30%)}html.theme--documenter-dark .progress:indeterminate{animation-duration:1.5s;animation-iteration-count:infinite;animation-name:moveIndeterminate;animation-timing-function:linear;background-color:#343c3d;background-image:linear-gradient(to right, #fff 30%, #343c3d 30%);background-position:top left;background-repeat:no-repeat;background-size:150% 150%}html.theme--documenter-dark .progress:indeterminate::-webkit-progress-bar{background-color:transparent}html.theme--documenter-dark .progress:indeterminate::-moz-progress-bar{background-color:transparent}html.theme--documenter-dark .progress:indeterminate::-ms-fill{animation-name:none}html.theme--documenter-dark .progress.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.progress{height:.75rem}html.theme--documenter-dark .progress.is-medium{height:1.25rem}html.theme--documenter-dark .progress.is-large{height:1.5rem}@keyframes moveIndeterminate{from{background-position:200% 0}to{background-position:-200% 0}}html.theme--documenter-dark .table{background-color:#343c3d;color:#fff}html.theme--documenter-dark .table td,html.theme--documenter-dark .table th{border:1px solid #5e6d6f;border-width:0 0 1px;padding:0.5em 0.75em;vertical-align:top}html.theme--documenter-dark .table td.is-white,html.theme--documenter-dark .table th.is-white{background-color:#fff;border-color:#fff;color:#0a0a0a}html.theme--documenter-dark .table td.is-black,html.theme--documenter-dark .table th.is-black{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}html.theme--documenter-dark .table td.is-light,html.theme--documenter-dark .table th.is-light{background-color:#ecf0f1;border-color:#ecf0f1;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .table td.is-dark,html.theme--documenter-dark .table th.is-dark{background-color:#282f2f;border-color:#282f2f;color:#fff}html.theme--documenter-dark .table td.is-primary,html.theme--documenter-dark .table th.is-primary{background-color:#375a7f;border-color:#375a7f;color:#fff}html.theme--documenter-dark .table td.is-link,html.theme--documenter-dark .table th.is-link{background-color:#1abc9c;border-color:#1abc9c;color:#fff}html.theme--documenter-dark .table td.is-info,html.theme--documenter-dark .table th.is-info{background-color:#024c7d;border-color:#024c7d;color:#fff}html.theme--documenter-dark .table td.is-success,html.theme--documenter-dark .table th.is-success{background-color:#008438;border-color:#008438;color:#fff}html.theme--documenter-dark .table td.is-warning,html.theme--documenter-dark .table th.is-warning{background-color:#ad8100;border-color:#ad8100;color:#fff}html.theme--documenter-dark .table td.is-danger,html.theme--documenter-dark .table th.is-danger{background-color:#9e1b0d;border-color:#9e1b0d;color:#fff}html.theme--documenter-dark .table td.is-narrow,html.theme--documenter-dark .table th.is-narrow{white-space:nowrap;width:1%}html.theme--documenter-dark .table td.is-selected,html.theme--documenter-dark .table th.is-selected{background-color:#375a7f;color:#fff}html.theme--documenter-dark .table td.is-selected a,html.theme--documenter-dark .table td.is-selected strong,html.theme--documenter-dark .table th.is-selected a,html.theme--documenter-dark .table th.is-selected strong{color:currentColor}html.theme--documenter-dark .table td.is-vcentered,html.theme--documenter-dark .table th.is-vcentered{vertical-align:middle}html.theme--documenter-dark .table th{color:#f2f2f2}html.theme--documenter-dark .table th:not([align]){text-align:left}html.theme--documenter-dark .table tr.is-selected{background-color:#375a7f;color:#fff}html.theme--documenter-dark .table tr.is-selected a,html.theme--documenter-dark .table tr.is-selected strong{color:currentColor}html.theme--documenter-dark .table tr.is-selected td,html.theme--documenter-dark .table tr.is-selected th{border-color:#fff;color:currentColor}html.theme--documenter-dark .table thead{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .table thead td,html.theme--documenter-dark .table thead th{border-width:0 0 2px;color:#f2f2f2}html.theme--documenter-dark .table tfoot{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .table tfoot td,html.theme--documenter-dark .table tfoot th{border-width:2px 0 0;color:#f2f2f2}html.theme--documenter-dark .table tbody{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .table tbody tr:last-child td,html.theme--documenter-dark .table tbody tr:last-child th{border-bottom-width:0}html.theme--documenter-dark .table.is-bordered td,html.theme--documenter-dark .table.is-bordered th{border-width:1px}html.theme--documenter-dark .table.is-bordered tr:last-child td,html.theme--documenter-dark .table.is-bordered tr:last-child th{border-bottom-width:1px}html.theme--documenter-dark .table.is-fullwidth{width:100%}html.theme--documenter-dark .table.is-hoverable tbody tr:not(.is-selected):hover{background-color:#282f2f}html.theme--documenter-dark .table.is-hoverable.is-striped tbody tr:not(.is-selected):hover{background-color:#282f2f}html.theme--documenter-dark .table.is-hoverable.is-striped tbody tr:not(.is-selected):hover:nth-child(even){background-color:#2d3435}html.theme--documenter-dark .table.is-narrow td,html.theme--documenter-dark .table.is-narrow th{padding:0.25em 0.5em}html.theme--documenter-dark .table.is-striped tbody tr:not(.is-selected):nth-child(even){background-color:#282f2f}html.theme--documenter-dark .table-container{-webkit-overflow-scrolling:touch;overflow:auto;overflow-y:hidden;max-width:100%}html.theme--documenter-dark .tags{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}html.theme--documenter-dark .tags .tag,html.theme--documenter-dark .tags .content kbd,html.theme--documenter-dark .content .tags kbd,html.theme--documenter-dark .tags .docstring>section>a.docs-sourcelink{margin-bottom:0.5rem}html.theme--documenter-dark .tags .tag:not(:last-child),html.theme--documenter-dark .tags .content kbd:not(:last-child),html.theme--documenter-dark .content .tags kbd:not(:last-child),html.theme--documenter-dark .tags .docstring>section>a.docs-sourcelink:not(:last-child){margin-right:.5rem}html.theme--documenter-dark .tags:last-child{margin-bottom:-0.5rem}html.theme--documenter-dark .tags:not(:last-child){margin-bottom:1rem}html.theme--documenter-dark .tags.are-medium .tag:not(.is-normal):not(.is-large),html.theme--documenter-dark .tags.are-medium .content kbd:not(.is-normal):not(.is-large),html.theme--documenter-dark .content .tags.are-medium kbd:not(.is-normal):not(.is-large),html.theme--documenter-dark .tags.are-medium .docstring>section>a.docs-sourcelink:not(.is-normal):not(.is-large){font-size:1rem}html.theme--documenter-dark .tags.are-large .tag:not(.is-normal):not(.is-medium),html.theme--documenter-dark .tags.are-large .content kbd:not(.is-normal):not(.is-medium),html.theme--documenter-dark .content .tags.are-large kbd:not(.is-normal):not(.is-medium),html.theme--documenter-dark .tags.are-large .docstring>section>a.docs-sourcelink:not(.is-normal):not(.is-medium){font-size:1.25rem}html.theme--documenter-dark .tags.is-centered{justify-content:center}html.theme--documenter-dark .tags.is-centered .tag,html.theme--documenter-dark .tags.is-centered .content kbd,html.theme--documenter-dark .content .tags.is-centered kbd,html.theme--documenter-dark .tags.is-centered .docstring>section>a.docs-sourcelink{margin-right:0.25rem;margin-left:0.25rem}html.theme--documenter-dark .tags.is-right{justify-content:flex-end}html.theme--documenter-dark .tags.is-right .tag:not(:first-child),html.theme--documenter-dark .tags.is-right .content kbd:not(:first-child),html.theme--documenter-dark .content .tags.is-right kbd:not(:first-child),html.theme--documenter-dark .tags.is-right .docstring>section>a.docs-sourcelink:not(:first-child){margin-left:0.5rem}html.theme--documenter-dark .tags.is-right .tag:not(:last-child),html.theme--documenter-dark .tags.is-right .content kbd:not(:last-child),html.theme--documenter-dark .content .tags.is-right kbd:not(:last-child),html.theme--documenter-dark .tags.is-right .docstring>section>a.docs-sourcelink:not(:last-child){margin-right:0}html.theme--documenter-dark .tags.has-addons .tag,html.theme--documenter-dark .tags.has-addons .content kbd,html.theme--documenter-dark .content .tags.has-addons kbd,html.theme--documenter-dark .tags.has-addons .docstring>section>a.docs-sourcelink{margin-right:0}html.theme--documenter-dark .tags.has-addons .tag:not(:first-child),html.theme--documenter-dark .tags.has-addons .content kbd:not(:first-child),html.theme--documenter-dark .content .tags.has-addons kbd:not(:first-child),html.theme--documenter-dark .tags.has-addons .docstring>section>a.docs-sourcelink:not(:first-child){margin-left:0;border-top-left-radius:0;border-bottom-left-radius:0}html.theme--documenter-dark .tags.has-addons .tag:not(:last-child),html.theme--documenter-dark .tags.has-addons .content kbd:not(:last-child),html.theme--documenter-dark .content .tags.has-addons kbd:not(:last-child),html.theme--documenter-dark .tags.has-addons .docstring>section>a.docs-sourcelink:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}html.theme--documenter-dark .tag:not(body),html.theme--documenter-dark .content kbd:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink:not(body){align-items:center;background-color:#282f2f;border-radius:.4em;color:#fff;display:inline-flex;font-size:.75rem;height:2em;justify-content:center;line-height:1.5;padding-left:0.75em;padding-right:0.75em;white-space:nowrap}html.theme--documenter-dark .tag:not(body) .delete,html.theme--documenter-dark .content kbd:not(body) .delete,html.theme--documenter-dark .docstring>section>a.docs-sourcelink:not(body) .delete{margin-left:.25rem;margin-right:-.375rem}html.theme--documenter-dark .tag.is-white:not(body),html.theme--documenter-dark .content kbd.is-white:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-white:not(body){background-color:#fff;color:#0a0a0a}html.theme--documenter-dark .tag.is-black:not(body),html.theme--documenter-dark .content kbd.is-black:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-black:not(body){background-color:#0a0a0a;color:#fff}html.theme--documenter-dark .tag.is-light:not(body),html.theme--documenter-dark .content kbd.is-light:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-light:not(body){background-color:#ecf0f1;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .tag.is-dark:not(body),html.theme--documenter-dark .content kbd:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-dark:not(body),html.theme--documenter-dark .content .docstring>section>kbd:not(body){background-color:#282f2f;color:#fff}html.theme--documenter-dark .tag.is-primary:not(body),html.theme--documenter-dark .content kbd.is-primary:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink:not(body){background-color:#375a7f;color:#fff}html.theme--documenter-dark .tag.is-primary.is-light:not(body),html.theme--documenter-dark .content kbd.is-primary.is-light:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-light:not(body){background-color:#f1f5f9;color:#4d7eb2}html.theme--documenter-dark .tag.is-link:not(body),html.theme--documenter-dark .content kbd.is-link:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-link:not(body){background-color:#1abc9c;color:#fff}html.theme--documenter-dark .tag.is-link.is-light:not(body),html.theme--documenter-dark .content kbd.is-link.is-light:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-link.is-light:not(body){background-color:#edfdf9;color:#15987e}html.theme--documenter-dark .tag.is-info:not(body),html.theme--documenter-dark .content kbd.is-info:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-info:not(body){background-color:#024c7d;color:#fff}html.theme--documenter-dark .tag.is-info.is-light:not(body),html.theme--documenter-dark .content kbd.is-info.is-light:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-info.is-light:not(body){background-color:#ebf7ff;color:#0e9dfb}html.theme--documenter-dark .tag.is-success:not(body),html.theme--documenter-dark .content kbd.is-success:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-success:not(body){background-color:#008438;color:#fff}html.theme--documenter-dark .tag.is-success.is-light:not(body),html.theme--documenter-dark .content kbd.is-success.is-light:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-success.is-light:not(body){background-color:#ebfff3;color:#00eb64}html.theme--documenter-dark .tag.is-warning:not(body),html.theme--documenter-dark .content kbd.is-warning:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-warning:not(body){background-color:#ad8100;color:#fff}html.theme--documenter-dark .tag.is-warning.is-light:not(body),html.theme--documenter-dark .content kbd.is-warning.is-light:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-warning.is-light:not(body){background-color:#fffaeb;color:#d19c00}html.theme--documenter-dark .tag.is-danger:not(body),html.theme--documenter-dark .content kbd.is-danger:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-danger:not(body){background-color:#9e1b0d;color:#fff}html.theme--documenter-dark .tag.is-danger.is-light:not(body),html.theme--documenter-dark .content kbd.is-danger.is-light:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-danger.is-light:not(body){background-color:#fdeeec;color:#ec311d}html.theme--documenter-dark .tag.is-normal:not(body),html.theme--documenter-dark .content kbd.is-normal:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-normal:not(body){font-size:.75rem}html.theme--documenter-dark .tag.is-medium:not(body),html.theme--documenter-dark .content kbd.is-medium:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-medium:not(body){font-size:1rem}html.theme--documenter-dark .tag.is-large:not(body),html.theme--documenter-dark .content kbd.is-large:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-large:not(body){font-size:1.25rem}html.theme--documenter-dark .tag:not(body) .icon:first-child:not(:last-child),html.theme--documenter-dark .content kbd:not(body) .icon:first-child:not(:last-child),html.theme--documenter-dark .docstring>section>a.docs-sourcelink:not(body) .icon:first-child:not(:last-child){margin-left:-.375em;margin-right:.1875em}html.theme--documenter-dark .tag:not(body) .icon:last-child:not(:first-child),html.theme--documenter-dark .content kbd:not(body) .icon:last-child:not(:first-child),html.theme--documenter-dark .docstring>section>a.docs-sourcelink:not(body) .icon:last-child:not(:first-child){margin-left:.1875em;margin-right:-.375em}html.theme--documenter-dark .tag:not(body) .icon:first-child:last-child,html.theme--documenter-dark .content kbd:not(body) .icon:first-child:last-child,html.theme--documenter-dark .docstring>section>a.docs-sourcelink:not(body) .icon:first-child:last-child{margin-left:-.375em;margin-right:-.375em}html.theme--documenter-dark .tag.is-delete:not(body),html.theme--documenter-dark .content kbd.is-delete:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-delete:not(body){margin-left:1px;padding:0;position:relative;width:2em}html.theme--documenter-dark .tag.is-delete:not(body)::before,html.theme--documenter-dark .content kbd.is-delete:not(body)::before,html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-delete:not(body)::before,html.theme--documenter-dark .tag.is-delete:not(body)::after,html.theme--documenter-dark .content kbd.is-delete:not(body)::after,html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-delete:not(body)::after{background-color:currentColor;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}html.theme--documenter-dark .tag.is-delete:not(body)::before,html.theme--documenter-dark .content kbd.is-delete:not(body)::before,html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-delete:not(body)::before{height:1px;width:50%}html.theme--documenter-dark .tag.is-delete:not(body)::after,html.theme--documenter-dark .content kbd.is-delete:not(body)::after,html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-delete:not(body)::after{height:50%;width:1px}html.theme--documenter-dark .tag.is-delete:not(body):hover,html.theme--documenter-dark .content kbd.is-delete:not(body):hover,html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-delete:not(body):hover,html.theme--documenter-dark .tag.is-delete:not(body):focus,html.theme--documenter-dark .content kbd.is-delete:not(body):focus,html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-delete:not(body):focus{background-color:#1d2122}html.theme--documenter-dark .tag.is-delete:not(body):active,html.theme--documenter-dark .content kbd.is-delete:not(body):active,html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-delete:not(body):active{background-color:#111414}html.theme--documenter-dark .tag.is-rounded:not(body),html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input:not(body),html.theme--documenter-dark .content kbd.is-rounded:not(body),html.theme--documenter-dark #documenter .docs-sidebar .content form.docs-search>input:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-rounded:not(body){border-radius:9999px}html.theme--documenter-dark a.tag:hover,html.theme--documenter-dark .docstring>section>a.docs-sourcelink:hover{text-decoration:underline}html.theme--documenter-dark .title,html.theme--documenter-dark .subtitle{word-break:break-word}html.theme--documenter-dark .title em,html.theme--documenter-dark .title span,html.theme--documenter-dark .subtitle em,html.theme--documenter-dark .subtitle span{font-weight:inherit}html.theme--documenter-dark .title sub,html.theme--documenter-dark .subtitle sub{font-size:.75em}html.theme--documenter-dark .title sup,html.theme--documenter-dark .subtitle sup{font-size:.75em}html.theme--documenter-dark .title .tag,html.theme--documenter-dark .title .content kbd,html.theme--documenter-dark .content .title kbd,html.theme--documenter-dark .title .docstring>section>a.docs-sourcelink,html.theme--documenter-dark .subtitle .tag,html.theme--documenter-dark .subtitle .content kbd,html.theme--documenter-dark .content .subtitle kbd,html.theme--documenter-dark .subtitle .docstring>section>a.docs-sourcelink{vertical-align:middle}html.theme--documenter-dark .title{color:#fff;font-size:2rem;font-weight:500;line-height:1.125}html.theme--documenter-dark .title strong{color:inherit;font-weight:inherit}html.theme--documenter-dark .title:not(.is-spaced)+.subtitle{margin-top:-1.25rem}html.theme--documenter-dark .title.is-1{font-size:3rem}html.theme--documenter-dark .title.is-2{font-size:2.5rem}html.theme--documenter-dark .title.is-3{font-size:2rem}html.theme--documenter-dark .title.is-4{font-size:1.5rem}html.theme--documenter-dark .title.is-5{font-size:1.25rem}html.theme--documenter-dark .title.is-6{font-size:1rem}html.theme--documenter-dark .title.is-7{font-size:.75rem}html.theme--documenter-dark .subtitle{color:#8c9b9d;font-size:1.25rem;font-weight:400;line-height:1.25}html.theme--documenter-dark .subtitle strong{color:#8c9b9d;font-weight:600}html.theme--documenter-dark .subtitle:not(.is-spaced)+.title{margin-top:-1.25rem}html.theme--documenter-dark .subtitle.is-1{font-size:3rem}html.theme--documenter-dark .subtitle.is-2{font-size:2.5rem}html.theme--documenter-dark .subtitle.is-3{font-size:2rem}html.theme--documenter-dark .subtitle.is-4{font-size:1.5rem}html.theme--documenter-dark .subtitle.is-5{font-size:1.25rem}html.theme--documenter-dark .subtitle.is-6{font-size:1rem}html.theme--documenter-dark .subtitle.is-7{font-size:.75rem}html.theme--documenter-dark .heading{display:block;font-size:11px;letter-spacing:1px;margin-bottom:5px;text-transform:uppercase}html.theme--documenter-dark .number{align-items:center;background-color:#282f2f;border-radius:9999px;display:inline-flex;font-size:1.25rem;height:2em;justify-content:center;margin-right:1.5rem;min-width:2.5em;padding:0.25rem 0.5rem;text-align:center;vertical-align:top}html.theme--documenter-dark .select select,html.theme--documenter-dark .textarea,html.theme--documenter-dark .input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input{background-color:#1f2424;border-color:#5e6d6f;border-radius:.4em;color:#dbdee0}html.theme--documenter-dark .select select::-moz-placeholder,html.theme--documenter-dark .textarea::-moz-placeholder,html.theme--documenter-dark .input::-moz-placeholder,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input::-moz-placeholder{color:#868c98}html.theme--documenter-dark .select select::-webkit-input-placeholder,html.theme--documenter-dark .textarea::-webkit-input-placeholder,html.theme--documenter-dark .input::-webkit-input-placeholder,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input::-webkit-input-placeholder{color:#868c98}html.theme--documenter-dark .select select:-moz-placeholder,html.theme--documenter-dark .textarea:-moz-placeholder,html.theme--documenter-dark .input:-moz-placeholder,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input:-moz-placeholder{color:#868c98}html.theme--documenter-dark .select select:-ms-input-placeholder,html.theme--documenter-dark .textarea:-ms-input-placeholder,html.theme--documenter-dark .input:-ms-input-placeholder,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input:-ms-input-placeholder{color:#868c98}html.theme--documenter-dark .select select:hover,html.theme--documenter-dark .textarea:hover,html.theme--documenter-dark .input:hover,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input:hover,html.theme--documenter-dark .select select.is-hovered,html.theme--documenter-dark .is-hovered.textarea,html.theme--documenter-dark .is-hovered.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-hovered{border-color:#8c9b9d}html.theme--documenter-dark .select select:focus,html.theme--documenter-dark .textarea:focus,html.theme--documenter-dark .input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input:focus,html.theme--documenter-dark .select select.is-focused,html.theme--documenter-dark .is-focused.textarea,html.theme--documenter-dark .is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .select select:active,html.theme--documenter-dark .textarea:active,html.theme--documenter-dark .input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input:active,html.theme--documenter-dark .select select.is-active,html.theme--documenter-dark .is-active.textarea,html.theme--documenter-dark .is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active{border-color:#1abc9c;box-shadow:0 0 0 0.125em rgba(26,188,156,0.25)}html.theme--documenter-dark .select select[disabled],html.theme--documenter-dark .textarea[disabled],html.theme--documenter-dark .input[disabled],html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input[disabled],fieldset[disabled] html.theme--documenter-dark .select select,fieldset[disabled] html.theme--documenter-dark .textarea,fieldset[disabled] html.theme--documenter-dark .input,fieldset[disabled] html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input{background-color:#8c9b9d;border-color:#282f2f;box-shadow:none;color:#fff}html.theme--documenter-dark .select select[disabled]::-moz-placeholder,html.theme--documenter-dark .textarea[disabled]::-moz-placeholder,html.theme--documenter-dark .input[disabled]::-moz-placeholder,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input[disabled]::-moz-placeholder,fieldset[disabled] html.theme--documenter-dark .select select::-moz-placeholder,fieldset[disabled] html.theme--documenter-dark .textarea::-moz-placeholder,fieldset[disabled] html.theme--documenter-dark .input::-moz-placeholder,fieldset[disabled] html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input::-moz-placeholder{color:rgba(255,255,255,0.3)}html.theme--documenter-dark .select select[disabled]::-webkit-input-placeholder,html.theme--documenter-dark .textarea[disabled]::-webkit-input-placeholder,html.theme--documenter-dark .input[disabled]::-webkit-input-placeholder,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input[disabled]::-webkit-input-placeholder,fieldset[disabled] html.theme--documenter-dark .select select::-webkit-input-placeholder,fieldset[disabled] html.theme--documenter-dark .textarea::-webkit-input-placeholder,fieldset[disabled] html.theme--documenter-dark .input::-webkit-input-placeholder,fieldset[disabled] html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input::-webkit-input-placeholder{color:rgba(255,255,255,0.3)}html.theme--documenter-dark .select select[disabled]:-moz-placeholder,html.theme--documenter-dark .textarea[disabled]:-moz-placeholder,html.theme--documenter-dark .input[disabled]:-moz-placeholder,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input[disabled]:-moz-placeholder,fieldset[disabled] html.theme--documenter-dark .select select:-moz-placeholder,fieldset[disabled] html.theme--documenter-dark .textarea:-moz-placeholder,fieldset[disabled] html.theme--documenter-dark .input:-moz-placeholder,fieldset[disabled] html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input:-moz-placeholder{color:rgba(255,255,255,0.3)}html.theme--documenter-dark .select select[disabled]:-ms-input-placeholder,html.theme--documenter-dark .textarea[disabled]:-ms-input-placeholder,html.theme--documenter-dark .input[disabled]:-ms-input-placeholder,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input[disabled]:-ms-input-placeholder,fieldset[disabled] html.theme--documenter-dark .select select:-ms-input-placeholder,fieldset[disabled] html.theme--documenter-dark .textarea:-ms-input-placeholder,fieldset[disabled] html.theme--documenter-dark .input:-ms-input-placeholder,fieldset[disabled] html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input:-ms-input-placeholder{color:rgba(255,255,255,0.3)}html.theme--documenter-dark .textarea,html.theme--documenter-dark .input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input{box-shadow:inset 0 0.0625em 0.125em rgba(10,10,10,0.05);max-width:100%;width:100%}html.theme--documenter-dark .textarea[readonly],html.theme--documenter-dark .input[readonly],html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input[readonly]{box-shadow:none}html.theme--documenter-dark .is-white.textarea,html.theme--documenter-dark .is-white.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-white{border-color:#fff}html.theme--documenter-dark .is-white.textarea:focus,html.theme--documenter-dark .is-white.input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-white:focus,html.theme--documenter-dark .is-white.is-focused.textarea,html.theme--documenter-dark .is-white.is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .is-white.textarea:active,html.theme--documenter-dark .is-white.input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-white:active,html.theme--documenter-dark .is-white.is-active.textarea,html.theme--documenter-dark .is-white.is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}html.theme--documenter-dark .is-black.textarea,html.theme--documenter-dark .is-black.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-black{border-color:#0a0a0a}html.theme--documenter-dark .is-black.textarea:focus,html.theme--documenter-dark .is-black.input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-black:focus,html.theme--documenter-dark .is-black.is-focused.textarea,html.theme--documenter-dark .is-black.is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .is-black.textarea:active,html.theme--documenter-dark .is-black.input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-black:active,html.theme--documenter-dark .is-black.is-active.textarea,html.theme--documenter-dark .is-black.is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}html.theme--documenter-dark .is-light.textarea,html.theme--documenter-dark .is-light.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-light{border-color:#ecf0f1}html.theme--documenter-dark .is-light.textarea:focus,html.theme--documenter-dark .is-light.input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-light:focus,html.theme--documenter-dark .is-light.is-focused.textarea,html.theme--documenter-dark .is-light.is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .is-light.textarea:active,html.theme--documenter-dark .is-light.input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-light:active,html.theme--documenter-dark .is-light.is-active.textarea,html.theme--documenter-dark .is-light.is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(236,240,241,0.25)}html.theme--documenter-dark .is-dark.textarea,html.theme--documenter-dark .content kbd.textarea,html.theme--documenter-dark .is-dark.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-dark,html.theme--documenter-dark .content kbd.input{border-color:#282f2f}html.theme--documenter-dark .is-dark.textarea:focus,html.theme--documenter-dark .content kbd.textarea:focus,html.theme--documenter-dark .is-dark.input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-dark:focus,html.theme--documenter-dark .content kbd.input:focus,html.theme--documenter-dark .is-dark.is-focused.textarea,html.theme--documenter-dark .content kbd.is-focused.textarea,html.theme--documenter-dark .is-dark.is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .content kbd.is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar .content form.docs-search>input.is-focused,html.theme--documenter-dark .is-dark.textarea:active,html.theme--documenter-dark .content kbd.textarea:active,html.theme--documenter-dark .is-dark.input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-dark:active,html.theme--documenter-dark .content kbd.input:active,html.theme--documenter-dark .is-dark.is-active.textarea,html.theme--documenter-dark .content kbd.is-active.textarea,html.theme--documenter-dark .is-dark.is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active,html.theme--documenter-dark .content kbd.is-active.input,html.theme--documenter-dark #documenter .docs-sidebar .content form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(40,47,47,0.25)}html.theme--documenter-dark .is-primary.textarea,html.theme--documenter-dark .docstring>section>a.textarea.docs-sourcelink,html.theme--documenter-dark .is-primary.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-primary,html.theme--documenter-dark .docstring>section>a.input.docs-sourcelink{border-color:#375a7f}html.theme--documenter-dark .is-primary.textarea:focus,html.theme--documenter-dark .docstring>section>a.textarea.docs-sourcelink:focus,html.theme--documenter-dark .is-primary.input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-primary:focus,html.theme--documenter-dark .docstring>section>a.input.docs-sourcelink:focus,html.theme--documenter-dark .is-primary.is-focused.textarea,html.theme--documenter-dark .docstring>section>a.is-focused.textarea.docs-sourcelink,html.theme--documenter-dark .is-primary.is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .docstring>section>a.is-focused.input.docs-sourcelink,html.theme--documenter-dark .is-primary.textarea:active,html.theme--documenter-dark .docstring>section>a.textarea.docs-sourcelink:active,html.theme--documenter-dark .is-primary.input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-primary:active,html.theme--documenter-dark .docstring>section>a.input.docs-sourcelink:active,html.theme--documenter-dark .is-primary.is-active.textarea,html.theme--documenter-dark .docstring>section>a.is-active.textarea.docs-sourcelink,html.theme--documenter-dark .is-primary.is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active,html.theme--documenter-dark .docstring>section>a.is-active.input.docs-sourcelink{box-shadow:0 0 0 0.125em rgba(55,90,127,0.25)}html.theme--documenter-dark .is-link.textarea,html.theme--documenter-dark .is-link.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-link{border-color:#1abc9c}html.theme--documenter-dark .is-link.textarea:focus,html.theme--documenter-dark .is-link.input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-link:focus,html.theme--documenter-dark .is-link.is-focused.textarea,html.theme--documenter-dark .is-link.is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .is-link.textarea:active,html.theme--documenter-dark .is-link.input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-link:active,html.theme--documenter-dark .is-link.is-active.textarea,html.theme--documenter-dark .is-link.is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(26,188,156,0.25)}html.theme--documenter-dark .is-info.textarea,html.theme--documenter-dark .is-info.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-info{border-color:#024c7d}html.theme--documenter-dark .is-info.textarea:focus,html.theme--documenter-dark .is-info.input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-info:focus,html.theme--documenter-dark .is-info.is-focused.textarea,html.theme--documenter-dark .is-info.is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .is-info.textarea:active,html.theme--documenter-dark .is-info.input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-info:active,html.theme--documenter-dark .is-info.is-active.textarea,html.theme--documenter-dark .is-info.is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(2,76,125,0.25)}html.theme--documenter-dark .is-success.textarea,html.theme--documenter-dark .is-success.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-success{border-color:#008438}html.theme--documenter-dark .is-success.textarea:focus,html.theme--documenter-dark .is-success.input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-success:focus,html.theme--documenter-dark .is-success.is-focused.textarea,html.theme--documenter-dark .is-success.is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .is-success.textarea:active,html.theme--documenter-dark .is-success.input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-success:active,html.theme--documenter-dark .is-success.is-active.textarea,html.theme--documenter-dark .is-success.is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(0,132,56,0.25)}html.theme--documenter-dark .is-warning.textarea,html.theme--documenter-dark .is-warning.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-warning{border-color:#ad8100}html.theme--documenter-dark .is-warning.textarea:focus,html.theme--documenter-dark .is-warning.input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-warning:focus,html.theme--documenter-dark .is-warning.is-focused.textarea,html.theme--documenter-dark .is-warning.is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .is-warning.textarea:active,html.theme--documenter-dark .is-warning.input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-warning:active,html.theme--documenter-dark .is-warning.is-active.textarea,html.theme--documenter-dark .is-warning.is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(173,129,0,0.25)}html.theme--documenter-dark .is-danger.textarea,html.theme--documenter-dark .is-danger.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-danger{border-color:#9e1b0d}html.theme--documenter-dark .is-danger.textarea:focus,html.theme--documenter-dark .is-danger.input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-danger:focus,html.theme--documenter-dark .is-danger.is-focused.textarea,html.theme--documenter-dark .is-danger.is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .is-danger.textarea:active,html.theme--documenter-dark .is-danger.input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-danger:active,html.theme--documenter-dark .is-danger.is-active.textarea,html.theme--documenter-dark .is-danger.is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(158,27,13,0.25)}html.theme--documenter-dark .is-small.textarea,html.theme--documenter-dark .is-small.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input{border-radius:3px;font-size:.75rem}html.theme--documenter-dark .is-medium.textarea,html.theme--documenter-dark .is-medium.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-medium{font-size:1.25rem}html.theme--documenter-dark .is-large.textarea,html.theme--documenter-dark .is-large.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-large{font-size:1.5rem}html.theme--documenter-dark .is-fullwidth.textarea,html.theme--documenter-dark .is-fullwidth.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-fullwidth{display:block;width:100%}html.theme--documenter-dark .is-inline.textarea,html.theme--documenter-dark .is-inline.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-inline{display:inline;width:auto}html.theme--documenter-dark .input.is-rounded,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input{border-radius:9999px;padding-left:calc(calc(0.75em - 1px) + 0.375em);padding-right:calc(calc(0.75em - 1px) + 0.375em)}html.theme--documenter-dark .input.is-static,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-static{background-color:transparent;border-color:transparent;box-shadow:none;padding-left:0;padding-right:0}html.theme--documenter-dark .textarea{display:block;max-width:100%;min-width:100%;padding:calc(0.75em - 1px);resize:vertical}html.theme--documenter-dark .textarea:not([rows]){max-height:40em;min-height:8em}html.theme--documenter-dark .textarea[rows]{height:initial}html.theme--documenter-dark .textarea.has-fixed-size{resize:none}html.theme--documenter-dark .radio,html.theme--documenter-dark .checkbox{cursor:pointer;display:inline-block;line-height:1.25;position:relative}html.theme--documenter-dark .radio input,html.theme--documenter-dark .checkbox input{cursor:pointer}html.theme--documenter-dark .radio:hover,html.theme--documenter-dark .checkbox:hover{color:#8c9b9d}html.theme--documenter-dark .radio[disabled],html.theme--documenter-dark .checkbox[disabled],fieldset[disabled] html.theme--documenter-dark .radio,fieldset[disabled] html.theme--documenter-dark .checkbox,html.theme--documenter-dark .radio input[disabled],html.theme--documenter-dark .checkbox input[disabled]{color:#fff;cursor:not-allowed}html.theme--documenter-dark .radio+.radio{margin-left:.5em}html.theme--documenter-dark .select{display:inline-block;max-width:100%;position:relative;vertical-align:top}html.theme--documenter-dark .select:not(.is-multiple){height:2.5em}html.theme--documenter-dark .select:not(.is-multiple):not(.is-loading)::after{border-color:#1abc9c;right:1.125em;z-index:4}html.theme--documenter-dark .select.is-rounded select,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.select select{border-radius:9999px;padding-left:1em}html.theme--documenter-dark .select select{cursor:pointer;display:block;font-size:1em;max-width:100%;outline:none}html.theme--documenter-dark .select select::-ms-expand{display:none}html.theme--documenter-dark .select select[disabled]:hover,fieldset[disabled] html.theme--documenter-dark .select select:hover{border-color:#282f2f}html.theme--documenter-dark .select select:not([multiple]){padding-right:2.5em}html.theme--documenter-dark .select select[multiple]{height:auto;padding:0}html.theme--documenter-dark .select select[multiple] option{padding:0.5em 1em}html.theme--documenter-dark .select:not(.is-multiple):not(.is-loading):hover::after{border-color:#8c9b9d}html.theme--documenter-dark .select.is-white:not(:hover)::after{border-color:#fff}html.theme--documenter-dark .select.is-white select{border-color:#fff}html.theme--documenter-dark .select.is-white select:hover,html.theme--documenter-dark .select.is-white select.is-hovered{border-color:#f2f2f2}html.theme--documenter-dark .select.is-white select:focus,html.theme--documenter-dark .select.is-white select.is-focused,html.theme--documenter-dark .select.is-white select:active,html.theme--documenter-dark .select.is-white select.is-active{box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}html.theme--documenter-dark .select.is-black:not(:hover)::after{border-color:#0a0a0a}html.theme--documenter-dark .select.is-black select{border-color:#0a0a0a}html.theme--documenter-dark .select.is-black select:hover,html.theme--documenter-dark .select.is-black select.is-hovered{border-color:#000}html.theme--documenter-dark .select.is-black select:focus,html.theme--documenter-dark .select.is-black select.is-focused,html.theme--documenter-dark .select.is-black select:active,html.theme--documenter-dark .select.is-black select.is-active{box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}html.theme--documenter-dark .select.is-light:not(:hover)::after{border-color:#ecf0f1}html.theme--documenter-dark .select.is-light select{border-color:#ecf0f1}html.theme--documenter-dark .select.is-light select:hover,html.theme--documenter-dark .select.is-light select.is-hovered{border-color:#dde4e6}html.theme--documenter-dark .select.is-light select:focus,html.theme--documenter-dark .select.is-light select.is-focused,html.theme--documenter-dark .select.is-light select:active,html.theme--documenter-dark .select.is-light select.is-active{box-shadow:0 0 0 0.125em rgba(236,240,241,0.25)}html.theme--documenter-dark .select.is-dark:not(:hover)::after,html.theme--documenter-dark .content kbd.select:not(:hover)::after{border-color:#282f2f}html.theme--documenter-dark .select.is-dark select,html.theme--documenter-dark .content kbd.select select{border-color:#282f2f}html.theme--documenter-dark .select.is-dark select:hover,html.theme--documenter-dark .content kbd.select select:hover,html.theme--documenter-dark .select.is-dark select.is-hovered,html.theme--documenter-dark .content kbd.select select.is-hovered{border-color:#1d2122}html.theme--documenter-dark .select.is-dark select:focus,html.theme--documenter-dark .content kbd.select select:focus,html.theme--documenter-dark .select.is-dark select.is-focused,html.theme--documenter-dark .content kbd.select select.is-focused,html.theme--documenter-dark .select.is-dark select:active,html.theme--documenter-dark .content kbd.select select:active,html.theme--documenter-dark .select.is-dark select.is-active,html.theme--documenter-dark .content kbd.select select.is-active{box-shadow:0 0 0 0.125em rgba(40,47,47,0.25)}html.theme--documenter-dark .select.is-primary:not(:hover)::after,html.theme--documenter-dark .docstring>section>a.select.docs-sourcelink:not(:hover)::after{border-color:#375a7f}html.theme--documenter-dark .select.is-primary select,html.theme--documenter-dark .docstring>section>a.select.docs-sourcelink select{border-color:#375a7f}html.theme--documenter-dark .select.is-primary select:hover,html.theme--documenter-dark .docstring>section>a.select.docs-sourcelink select:hover,html.theme--documenter-dark .select.is-primary select.is-hovered,html.theme--documenter-dark .docstring>section>a.select.docs-sourcelink select.is-hovered{border-color:#2f4d6d}html.theme--documenter-dark .select.is-primary select:focus,html.theme--documenter-dark .docstring>section>a.select.docs-sourcelink select:focus,html.theme--documenter-dark .select.is-primary select.is-focused,html.theme--documenter-dark .docstring>section>a.select.docs-sourcelink select.is-focused,html.theme--documenter-dark .select.is-primary select:active,html.theme--documenter-dark .docstring>section>a.select.docs-sourcelink select:active,html.theme--documenter-dark .select.is-primary select.is-active,html.theme--documenter-dark .docstring>section>a.select.docs-sourcelink select.is-active{box-shadow:0 0 0 0.125em rgba(55,90,127,0.25)}html.theme--documenter-dark .select.is-link:not(:hover)::after{border-color:#1abc9c}html.theme--documenter-dark .select.is-link select{border-color:#1abc9c}html.theme--documenter-dark .select.is-link select:hover,html.theme--documenter-dark .select.is-link select.is-hovered{border-color:#17a689}html.theme--documenter-dark .select.is-link select:focus,html.theme--documenter-dark .select.is-link select.is-focused,html.theme--documenter-dark .select.is-link select:active,html.theme--documenter-dark .select.is-link select.is-active{box-shadow:0 0 0 0.125em rgba(26,188,156,0.25)}html.theme--documenter-dark .select.is-info:not(:hover)::after{border-color:#024c7d}html.theme--documenter-dark .select.is-info select{border-color:#024c7d}html.theme--documenter-dark .select.is-info select:hover,html.theme--documenter-dark .select.is-info select.is-hovered{border-color:#023d64}html.theme--documenter-dark .select.is-info select:focus,html.theme--documenter-dark .select.is-info select.is-focused,html.theme--documenter-dark .select.is-info select:active,html.theme--documenter-dark .select.is-info select.is-active{box-shadow:0 0 0 0.125em rgba(2,76,125,0.25)}html.theme--documenter-dark .select.is-success:not(:hover)::after{border-color:#008438}html.theme--documenter-dark .select.is-success select{border-color:#008438}html.theme--documenter-dark .select.is-success select:hover,html.theme--documenter-dark .select.is-success select.is-hovered{border-color:#006b2d}html.theme--documenter-dark .select.is-success select:focus,html.theme--documenter-dark .select.is-success select.is-focused,html.theme--documenter-dark .select.is-success select:active,html.theme--documenter-dark .select.is-success select.is-active{box-shadow:0 0 0 0.125em rgba(0,132,56,0.25)}html.theme--documenter-dark .select.is-warning:not(:hover)::after{border-color:#ad8100}html.theme--documenter-dark .select.is-warning select{border-color:#ad8100}html.theme--documenter-dark .select.is-warning select:hover,html.theme--documenter-dark .select.is-warning select.is-hovered{border-color:#946e00}html.theme--documenter-dark .select.is-warning select:focus,html.theme--documenter-dark .select.is-warning select.is-focused,html.theme--documenter-dark .select.is-warning select:active,html.theme--documenter-dark .select.is-warning select.is-active{box-shadow:0 0 0 0.125em rgba(173,129,0,0.25)}html.theme--documenter-dark .select.is-danger:not(:hover)::after{border-color:#9e1b0d}html.theme--documenter-dark .select.is-danger select{border-color:#9e1b0d}html.theme--documenter-dark .select.is-danger select:hover,html.theme--documenter-dark .select.is-danger select.is-hovered{border-color:#86170b}html.theme--documenter-dark .select.is-danger select:focus,html.theme--documenter-dark .select.is-danger select.is-focused,html.theme--documenter-dark .select.is-danger select:active,html.theme--documenter-dark .select.is-danger select.is-active{box-shadow:0 0 0 0.125em rgba(158,27,13,0.25)}html.theme--documenter-dark .select.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.select{border-radius:3px;font-size:.75rem}html.theme--documenter-dark .select.is-medium{font-size:1.25rem}html.theme--documenter-dark .select.is-large{font-size:1.5rem}html.theme--documenter-dark .select.is-disabled::after{border-color:#fff !important;opacity:0.5}html.theme--documenter-dark .select.is-fullwidth{width:100%}html.theme--documenter-dark .select.is-fullwidth select{width:100%}html.theme--documenter-dark .select.is-loading::after{margin-top:0;position:absolute;right:.625em;top:0.625em;transform:none}html.theme--documenter-dark .select.is-loading.is-small:after,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-loading:after{font-size:.75rem}html.theme--documenter-dark .select.is-loading.is-medium:after{font-size:1.25rem}html.theme--documenter-dark .select.is-loading.is-large:after{font-size:1.5rem}html.theme--documenter-dark .file{align-items:stretch;display:flex;justify-content:flex-start;position:relative}html.theme--documenter-dark .file.is-white .file-cta{background-color:#fff;border-color:transparent;color:#0a0a0a}html.theme--documenter-dark .file.is-white:hover .file-cta,html.theme--documenter-dark .file.is-white.is-hovered .file-cta{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}html.theme--documenter-dark .file.is-white:focus .file-cta,html.theme--documenter-dark .file.is-white.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(255,255,255,0.25);color:#0a0a0a}html.theme--documenter-dark .file.is-white:active .file-cta,html.theme--documenter-dark .file.is-white.is-active .file-cta{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}html.theme--documenter-dark .file.is-black .file-cta{background-color:#0a0a0a;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-black:hover .file-cta,html.theme--documenter-dark .file.is-black.is-hovered .file-cta{background-color:#040404;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-black:focus .file-cta,html.theme--documenter-dark .file.is-black.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(10,10,10,0.25);color:#fff}html.theme--documenter-dark .file.is-black:active .file-cta,html.theme--documenter-dark .file.is-black.is-active .file-cta{background-color:#000;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-light .file-cta{background-color:#ecf0f1;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .file.is-light:hover .file-cta,html.theme--documenter-dark .file.is-light.is-hovered .file-cta{background-color:#e5eaec;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .file.is-light:focus .file-cta,html.theme--documenter-dark .file.is-light.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(236,240,241,0.25);color:rgba(0,0,0,0.7)}html.theme--documenter-dark .file.is-light:active .file-cta,html.theme--documenter-dark .file.is-light.is-active .file-cta{background-color:#dde4e6;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .file.is-dark .file-cta,html.theme--documenter-dark .content kbd.file .file-cta{background-color:#282f2f;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-dark:hover .file-cta,html.theme--documenter-dark .content kbd.file:hover .file-cta,html.theme--documenter-dark .file.is-dark.is-hovered .file-cta,html.theme--documenter-dark .content kbd.file.is-hovered .file-cta{background-color:#232829;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-dark:focus .file-cta,html.theme--documenter-dark .content kbd.file:focus .file-cta,html.theme--documenter-dark .file.is-dark.is-focused .file-cta,html.theme--documenter-dark .content kbd.file.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(40,47,47,0.25);color:#fff}html.theme--documenter-dark .file.is-dark:active .file-cta,html.theme--documenter-dark .content kbd.file:active .file-cta,html.theme--documenter-dark .file.is-dark.is-active .file-cta,html.theme--documenter-dark .content kbd.file.is-active .file-cta{background-color:#1d2122;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-primary .file-cta,html.theme--documenter-dark .docstring>section>a.file.docs-sourcelink .file-cta{background-color:#375a7f;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-primary:hover .file-cta,html.theme--documenter-dark .docstring>section>a.file.docs-sourcelink:hover .file-cta,html.theme--documenter-dark .file.is-primary.is-hovered .file-cta,html.theme--documenter-dark .docstring>section>a.file.is-hovered.docs-sourcelink .file-cta{background-color:#335476;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-primary:focus .file-cta,html.theme--documenter-dark .docstring>section>a.file.docs-sourcelink:focus .file-cta,html.theme--documenter-dark .file.is-primary.is-focused .file-cta,html.theme--documenter-dark .docstring>section>a.file.is-focused.docs-sourcelink .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(55,90,127,0.25);color:#fff}html.theme--documenter-dark .file.is-primary:active .file-cta,html.theme--documenter-dark .docstring>section>a.file.docs-sourcelink:active .file-cta,html.theme--documenter-dark .file.is-primary.is-active .file-cta,html.theme--documenter-dark .docstring>section>a.file.is-active.docs-sourcelink .file-cta{background-color:#2f4d6d;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-link .file-cta{background-color:#1abc9c;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-link:hover .file-cta,html.theme--documenter-dark .file.is-link.is-hovered .file-cta{background-color:#18b193;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-link:focus .file-cta,html.theme--documenter-dark .file.is-link.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(26,188,156,0.25);color:#fff}html.theme--documenter-dark .file.is-link:active .file-cta,html.theme--documenter-dark .file.is-link.is-active .file-cta{background-color:#17a689;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-info .file-cta{background-color:#024c7d;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-info:hover .file-cta,html.theme--documenter-dark .file.is-info.is-hovered .file-cta{background-color:#024470;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-info:focus .file-cta,html.theme--documenter-dark .file.is-info.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(2,76,125,0.25);color:#fff}html.theme--documenter-dark .file.is-info:active .file-cta,html.theme--documenter-dark .file.is-info.is-active .file-cta{background-color:#023d64;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-success .file-cta{background-color:#008438;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-success:hover .file-cta,html.theme--documenter-dark .file.is-success.is-hovered .file-cta{background-color:#073;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-success:focus .file-cta,html.theme--documenter-dark .file.is-success.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(0,132,56,0.25);color:#fff}html.theme--documenter-dark .file.is-success:active .file-cta,html.theme--documenter-dark .file.is-success.is-active .file-cta{background-color:#006b2d;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-warning .file-cta{background-color:#ad8100;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-warning:hover .file-cta,html.theme--documenter-dark .file.is-warning.is-hovered .file-cta{background-color:#a07700;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-warning:focus .file-cta,html.theme--documenter-dark .file.is-warning.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(173,129,0,0.25);color:#fff}html.theme--documenter-dark .file.is-warning:active .file-cta,html.theme--documenter-dark .file.is-warning.is-active .file-cta{background-color:#946e00;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-danger .file-cta{background-color:#9e1b0d;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-danger:hover .file-cta,html.theme--documenter-dark .file.is-danger.is-hovered .file-cta{background-color:#92190c;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-danger:focus .file-cta,html.theme--documenter-dark .file.is-danger.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(158,27,13,0.25);color:#fff}html.theme--documenter-dark .file.is-danger:active .file-cta,html.theme--documenter-dark .file.is-danger.is-active .file-cta{background-color:#86170b;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.file{font-size:.75rem}html.theme--documenter-dark .file.is-normal{font-size:1rem}html.theme--documenter-dark .file.is-medium{font-size:1.25rem}html.theme--documenter-dark .file.is-medium .file-icon .fa{font-size:21px}html.theme--documenter-dark .file.is-large{font-size:1.5rem}html.theme--documenter-dark .file.is-large .file-icon .fa{font-size:28px}html.theme--documenter-dark .file.has-name .file-cta{border-bottom-right-radius:0;border-top-right-radius:0}html.theme--documenter-dark .file.has-name .file-name{border-bottom-left-radius:0;border-top-left-radius:0}html.theme--documenter-dark .file.has-name.is-empty .file-cta{border-radius:.4em}html.theme--documenter-dark .file.has-name.is-empty .file-name{display:none}html.theme--documenter-dark .file.is-boxed .file-label{flex-direction:column}html.theme--documenter-dark .file.is-boxed .file-cta{flex-direction:column;height:auto;padding:1em 3em}html.theme--documenter-dark .file.is-boxed .file-name{border-width:0 1px 1px}html.theme--documenter-dark .file.is-boxed .file-icon{height:1.5em;width:1.5em}html.theme--documenter-dark .file.is-boxed .file-icon .fa{font-size:21px}html.theme--documenter-dark .file.is-boxed.is-small .file-icon .fa,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-boxed .file-icon .fa{font-size:14px}html.theme--documenter-dark .file.is-boxed.is-medium .file-icon .fa{font-size:28px}html.theme--documenter-dark .file.is-boxed.is-large .file-icon .fa{font-size:35px}html.theme--documenter-dark .file.is-boxed.has-name .file-cta{border-radius:.4em .4em 0 0}html.theme--documenter-dark .file.is-boxed.has-name .file-name{border-radius:0 0 .4em .4em;border-width:0 1px 1px}html.theme--documenter-dark .file.is-centered{justify-content:center}html.theme--documenter-dark .file.is-fullwidth .file-label{width:100%}html.theme--documenter-dark .file.is-fullwidth .file-name{flex-grow:1;max-width:none}html.theme--documenter-dark .file.is-right{justify-content:flex-end}html.theme--documenter-dark .file.is-right .file-cta{border-radius:0 .4em .4em 0}html.theme--documenter-dark .file.is-right .file-name{border-radius:.4em 0 0 .4em;border-width:1px 0 1px 1px;order:-1}html.theme--documenter-dark .file-label{align-items:stretch;display:flex;cursor:pointer;justify-content:flex-start;overflow:hidden;position:relative}html.theme--documenter-dark .file-label:hover .file-cta{background-color:#232829;color:#f2f2f2}html.theme--documenter-dark .file-label:hover .file-name{border-color:#596668}html.theme--documenter-dark .file-label:active .file-cta{background-color:#1d2122;color:#f2f2f2}html.theme--documenter-dark .file-label:active .file-name{border-color:#535f61}html.theme--documenter-dark .file-input{height:100%;left:0;opacity:0;outline:none;position:absolute;top:0;width:100%}html.theme--documenter-dark .file-cta,html.theme--documenter-dark .file-name{border-color:#5e6d6f;border-radius:.4em;font-size:1em;padding-left:1em;padding-right:1em;white-space:nowrap}html.theme--documenter-dark .file-cta{background-color:#282f2f;color:#fff}html.theme--documenter-dark .file-name{border-color:#5e6d6f;border-style:solid;border-width:1px 1px 1px 0;display:block;max-width:16em;overflow:hidden;text-align:inherit;text-overflow:ellipsis}html.theme--documenter-dark .file-icon{align-items:center;display:flex;height:1em;justify-content:center;margin-right:.5em;width:1em}html.theme--documenter-dark .file-icon .fa{font-size:14px}html.theme--documenter-dark .label{color:#f2f2f2;display:block;font-size:1rem;font-weight:700}html.theme--documenter-dark .label:not(:last-child){margin-bottom:0.5em}html.theme--documenter-dark .label.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.label{font-size:.75rem}html.theme--documenter-dark .label.is-medium{font-size:1.25rem}html.theme--documenter-dark .label.is-large{font-size:1.5rem}html.theme--documenter-dark .help{display:block;font-size:.75rem;margin-top:0.25rem}html.theme--documenter-dark .help.is-white{color:#fff}html.theme--documenter-dark .help.is-black{color:#0a0a0a}html.theme--documenter-dark .help.is-light{color:#ecf0f1}html.theme--documenter-dark .help.is-dark,html.theme--documenter-dark .content kbd.help{color:#282f2f}html.theme--documenter-dark .help.is-primary,html.theme--documenter-dark .docstring>section>a.help.docs-sourcelink{color:#375a7f}html.theme--documenter-dark .help.is-link{color:#1abc9c}html.theme--documenter-dark .help.is-info{color:#024c7d}html.theme--documenter-dark .help.is-success{color:#008438}html.theme--documenter-dark .help.is-warning{color:#ad8100}html.theme--documenter-dark .help.is-danger{color:#9e1b0d}html.theme--documenter-dark .field:not(:last-child){margin-bottom:0.75rem}html.theme--documenter-dark .field.has-addons{display:flex;justify-content:flex-start}html.theme--documenter-dark .field.has-addons .control:not(:last-child){margin-right:-1px}html.theme--documenter-dark .field.has-addons .control:not(:first-child):not(:last-child) .button,html.theme--documenter-dark .field.has-addons .control:not(:first-child):not(:last-child) .input,html.theme--documenter-dark .field.has-addons .control:not(:first-child):not(:last-child) #documenter .docs-sidebar form.docs-search>input,html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control:not(:first-child):not(:last-child) form.docs-search>input,html.theme--documenter-dark .field.has-addons .control:not(:first-child):not(:last-child) .select select{border-radius:0}html.theme--documenter-dark .field.has-addons .control:first-child:not(:only-child) .button,html.theme--documenter-dark .field.has-addons .control:first-child:not(:only-child) .input,html.theme--documenter-dark .field.has-addons .control:first-child:not(:only-child) #documenter .docs-sidebar form.docs-search>input,html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control:first-child:not(:only-child) form.docs-search>input,html.theme--documenter-dark .field.has-addons .control:first-child:not(:only-child) .select select{border-bottom-right-radius:0;border-top-right-radius:0}html.theme--documenter-dark .field.has-addons .control:last-child:not(:only-child) .button,html.theme--documenter-dark .field.has-addons .control:last-child:not(:only-child) .input,html.theme--documenter-dark .field.has-addons .control:last-child:not(:only-child) #documenter .docs-sidebar form.docs-search>input,html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control:last-child:not(:only-child) form.docs-search>input,html.theme--documenter-dark .field.has-addons .control:last-child:not(:only-child) .select select{border-bottom-left-radius:0;border-top-left-radius:0}html.theme--documenter-dark .field.has-addons .control .button:not([disabled]):hover,html.theme--documenter-dark .field.has-addons .control .button.is-hovered:not([disabled]),html.theme--documenter-dark .field.has-addons .control .input:not([disabled]):hover,html.theme--documenter-dark .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):hover,html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):hover,html.theme--documenter-dark .field.has-addons .control .input.is-hovered:not([disabled]),html.theme--documenter-dark .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-hovered:not([disabled]),html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-hovered:not([disabled]),html.theme--documenter-dark .field.has-addons .control .select select:not([disabled]):hover,html.theme--documenter-dark .field.has-addons .control .select select.is-hovered:not([disabled]){z-index:2}html.theme--documenter-dark .field.has-addons .control .button:not([disabled]):focus,html.theme--documenter-dark .field.has-addons .control .button.is-focused:not([disabled]),html.theme--documenter-dark .field.has-addons .control .button:not([disabled]):active,html.theme--documenter-dark .field.has-addons .control .button.is-active:not([disabled]),html.theme--documenter-dark .field.has-addons .control .input:not([disabled]):focus,html.theme--documenter-dark .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):focus,html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):focus,html.theme--documenter-dark .field.has-addons .control .input.is-focused:not([disabled]),html.theme--documenter-dark .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-focused:not([disabled]),html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-focused:not([disabled]),html.theme--documenter-dark .field.has-addons .control .input:not([disabled]):active,html.theme--documenter-dark .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):active,html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):active,html.theme--documenter-dark .field.has-addons .control .input.is-active:not([disabled]),html.theme--documenter-dark .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-active:not([disabled]),html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-active:not([disabled]),html.theme--documenter-dark .field.has-addons .control .select select:not([disabled]):focus,html.theme--documenter-dark .field.has-addons .control .select select.is-focused:not([disabled]),html.theme--documenter-dark .field.has-addons .control .select select:not([disabled]):active,html.theme--documenter-dark .field.has-addons .control .select select.is-active:not([disabled]){z-index:3}html.theme--documenter-dark .field.has-addons .control .button:not([disabled]):focus:hover,html.theme--documenter-dark .field.has-addons .control .button.is-focused:not([disabled]):hover,html.theme--documenter-dark .field.has-addons .control .button:not([disabled]):active:hover,html.theme--documenter-dark .field.has-addons .control .button.is-active:not([disabled]):hover,html.theme--documenter-dark .field.has-addons .control .input:not([disabled]):focus:hover,html.theme--documenter-dark .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):focus:hover,html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):focus:hover,html.theme--documenter-dark .field.has-addons .control .input.is-focused:not([disabled]):hover,html.theme--documenter-dark .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-focused:not([disabled]):hover,html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-focused:not([disabled]):hover,html.theme--documenter-dark .field.has-addons .control .input:not([disabled]):active:hover,html.theme--documenter-dark .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):active:hover,html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):active:hover,html.theme--documenter-dark .field.has-addons .control .input.is-active:not([disabled]):hover,html.theme--documenter-dark .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-active:not([disabled]):hover,html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-active:not([disabled]):hover,html.theme--documenter-dark .field.has-addons .control .select select:not([disabled]):focus:hover,html.theme--documenter-dark .field.has-addons .control .select select.is-focused:not([disabled]):hover,html.theme--documenter-dark .field.has-addons .control .select select:not([disabled]):active:hover,html.theme--documenter-dark .field.has-addons .control .select select.is-active:not([disabled]):hover{z-index:4}html.theme--documenter-dark .field.has-addons .control.is-expanded{flex-grow:1;flex-shrink:1}html.theme--documenter-dark .field.has-addons.has-addons-centered{justify-content:center}html.theme--documenter-dark .field.has-addons.has-addons-right{justify-content:flex-end}html.theme--documenter-dark .field.has-addons.has-addons-fullwidth .control{flex-grow:1;flex-shrink:0}html.theme--documenter-dark .field.is-grouped{display:flex;justify-content:flex-start}html.theme--documenter-dark .field.is-grouped>.control{flex-shrink:0}html.theme--documenter-dark .field.is-grouped>.control:not(:last-child){margin-bottom:0;margin-right:.75rem}html.theme--documenter-dark .field.is-grouped>.control.is-expanded{flex-grow:1;flex-shrink:1}html.theme--documenter-dark .field.is-grouped.is-grouped-centered{justify-content:center}html.theme--documenter-dark .field.is-grouped.is-grouped-right{justify-content:flex-end}html.theme--documenter-dark .field.is-grouped.is-grouped-multiline{flex-wrap:wrap}html.theme--documenter-dark .field.is-grouped.is-grouped-multiline>.control:last-child,html.theme--documenter-dark .field.is-grouped.is-grouped-multiline>.control:not(:last-child){margin-bottom:0.75rem}html.theme--documenter-dark .field.is-grouped.is-grouped-multiline:last-child{margin-bottom:-0.75rem}html.theme--documenter-dark .field.is-grouped.is-grouped-multiline:not(:last-child){margin-bottom:0}@media screen and (min-width: 769px),print{html.theme--documenter-dark .field.is-horizontal{display:flex}}html.theme--documenter-dark .field-label .label{font-size:inherit}@media screen and (max-width: 768px){html.theme--documenter-dark .field-label{margin-bottom:0.5rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .field-label{flex-basis:0;flex-grow:1;flex-shrink:0;margin-right:1.5rem;text-align:right}html.theme--documenter-dark .field-label.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.field-label{font-size:.75rem;padding-top:0.375em}html.theme--documenter-dark .field-label.is-normal{padding-top:0.375em}html.theme--documenter-dark .field-label.is-medium{font-size:1.25rem;padding-top:0.375em}html.theme--documenter-dark .field-label.is-large{font-size:1.5rem;padding-top:0.375em}}html.theme--documenter-dark .field-body .field .field{margin-bottom:0}@media screen and (min-width: 769px),print{html.theme--documenter-dark .field-body{display:flex;flex-basis:0;flex-grow:5;flex-shrink:1}html.theme--documenter-dark .field-body .field{margin-bottom:0}html.theme--documenter-dark .field-body>.field{flex-shrink:1}html.theme--documenter-dark .field-body>.field:not(.is-narrow){flex-grow:1}html.theme--documenter-dark .field-body>.field:not(:last-child){margin-right:.75rem}}html.theme--documenter-dark .control{box-sizing:border-box;clear:both;font-size:1rem;position:relative;text-align:inherit}html.theme--documenter-dark .control.has-icons-left .input:focus~.icon,html.theme--documenter-dark .control.has-icons-left #documenter .docs-sidebar form.docs-search>input:focus~.icon,html.theme--documenter-dark #documenter .docs-sidebar .control.has-icons-left form.docs-search>input:focus~.icon,html.theme--documenter-dark .control.has-icons-left .select:focus~.icon,html.theme--documenter-dark .control.has-icons-right .input:focus~.icon,html.theme--documenter-dark .control.has-icons-right #documenter .docs-sidebar form.docs-search>input:focus~.icon,html.theme--documenter-dark #documenter .docs-sidebar .control.has-icons-right form.docs-search>input:focus~.icon,html.theme--documenter-dark .control.has-icons-right .select:focus~.icon{color:#282f2f}html.theme--documenter-dark .control.has-icons-left .input.is-small~.icon,html.theme--documenter-dark .control.has-icons-left #documenter .docs-sidebar form.docs-search>input~.icon,html.theme--documenter-dark #documenter .docs-sidebar .control.has-icons-left form.docs-search>input~.icon,html.theme--documenter-dark .control.has-icons-left .select.is-small~.icon,html.theme--documenter-dark .control.has-icons-right .input.is-small~.icon,html.theme--documenter-dark .control.has-icons-right #documenter .docs-sidebar form.docs-search>input~.icon,html.theme--documenter-dark #documenter .docs-sidebar .control.has-icons-right form.docs-search>input~.icon,html.theme--documenter-dark .control.has-icons-right .select.is-small~.icon{font-size:.75rem}html.theme--documenter-dark .control.has-icons-left .input.is-medium~.icon,html.theme--documenter-dark .control.has-icons-left #documenter .docs-sidebar form.docs-search>input.is-medium~.icon,html.theme--documenter-dark #documenter .docs-sidebar .control.has-icons-left form.docs-search>input.is-medium~.icon,html.theme--documenter-dark .control.has-icons-left .select.is-medium~.icon,html.theme--documenter-dark .control.has-icons-right .input.is-medium~.icon,html.theme--documenter-dark .control.has-icons-right #documenter .docs-sidebar form.docs-search>input.is-medium~.icon,html.theme--documenter-dark #documenter .docs-sidebar .control.has-icons-right form.docs-search>input.is-medium~.icon,html.theme--documenter-dark .control.has-icons-right .select.is-medium~.icon{font-size:1.25rem}html.theme--documenter-dark .control.has-icons-left .input.is-large~.icon,html.theme--documenter-dark .control.has-icons-left #documenter .docs-sidebar form.docs-search>input.is-large~.icon,html.theme--documenter-dark #documenter .docs-sidebar .control.has-icons-left form.docs-search>input.is-large~.icon,html.theme--documenter-dark .control.has-icons-left .select.is-large~.icon,html.theme--documenter-dark .control.has-icons-right .input.is-large~.icon,html.theme--documenter-dark .control.has-icons-right #documenter .docs-sidebar form.docs-search>input.is-large~.icon,html.theme--documenter-dark #documenter .docs-sidebar .control.has-icons-right form.docs-search>input.is-large~.icon,html.theme--documenter-dark .control.has-icons-right .select.is-large~.icon{font-size:1.5rem}html.theme--documenter-dark .control.has-icons-left .icon,html.theme--documenter-dark .control.has-icons-right .icon{color:#5e6d6f;height:2.5em;pointer-events:none;position:absolute;top:0;width:2.5em;z-index:4}html.theme--documenter-dark .control.has-icons-left .input,html.theme--documenter-dark .control.has-icons-left #documenter .docs-sidebar form.docs-search>input,html.theme--documenter-dark #documenter .docs-sidebar .control.has-icons-left form.docs-search>input,html.theme--documenter-dark .control.has-icons-left .select select{padding-left:2.5em}html.theme--documenter-dark .control.has-icons-left .icon.is-left{left:0}html.theme--documenter-dark .control.has-icons-right .input,html.theme--documenter-dark .control.has-icons-right #documenter .docs-sidebar form.docs-search>input,html.theme--documenter-dark #documenter .docs-sidebar .control.has-icons-right form.docs-search>input,html.theme--documenter-dark .control.has-icons-right .select select{padding-right:2.5em}html.theme--documenter-dark .control.has-icons-right .icon.is-right{right:0}html.theme--documenter-dark .control.is-loading::after{position:absolute !important;right:.625em;top:0.625em;z-index:4}html.theme--documenter-dark .control.is-loading.is-small:after,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-loading:after{font-size:.75rem}html.theme--documenter-dark .control.is-loading.is-medium:after{font-size:1.25rem}html.theme--documenter-dark .control.is-loading.is-large:after{font-size:1.5rem}html.theme--documenter-dark .breadcrumb{font-size:1rem;white-space:nowrap}html.theme--documenter-dark .breadcrumb a{align-items:center;color:#1abc9c;display:flex;justify-content:center;padding:0 .75em}html.theme--documenter-dark .breadcrumb a:hover{color:#1dd2af}html.theme--documenter-dark .breadcrumb li{align-items:center;display:flex}html.theme--documenter-dark .breadcrumb li:first-child a{padding-left:0}html.theme--documenter-dark .breadcrumb li.is-active a{color:#f2f2f2;cursor:default;pointer-events:none}html.theme--documenter-dark .breadcrumb li+li::before{color:#8c9b9d;content:"\0002f"}html.theme--documenter-dark .breadcrumb ul,html.theme--documenter-dark .breadcrumb ol{align-items:flex-start;display:flex;flex-wrap:wrap;justify-content:flex-start}html.theme--documenter-dark .breadcrumb .icon:first-child{margin-right:.5em}html.theme--documenter-dark .breadcrumb .icon:last-child{margin-left:.5em}html.theme--documenter-dark .breadcrumb.is-centered ol,html.theme--documenter-dark .breadcrumb.is-centered ul{justify-content:center}html.theme--documenter-dark .breadcrumb.is-right ol,html.theme--documenter-dark .breadcrumb.is-right ul{justify-content:flex-end}html.theme--documenter-dark .breadcrumb.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.breadcrumb{font-size:.75rem}html.theme--documenter-dark .breadcrumb.is-medium{font-size:1.25rem}html.theme--documenter-dark .breadcrumb.is-large{font-size:1.5rem}html.theme--documenter-dark .breadcrumb.has-arrow-separator li+li::before{content:"\02192"}html.theme--documenter-dark .breadcrumb.has-bullet-separator li+li::before{content:"\02022"}html.theme--documenter-dark .breadcrumb.has-dot-separator li+li::before{content:"\000b7"}html.theme--documenter-dark .breadcrumb.has-succeeds-separator li+li::before{content:"\0227B"}html.theme--documenter-dark .card{background-color:#fff;border-radius:.25rem;box-shadow:#171717;color:#fff;max-width:100%;position:relative}html.theme--documenter-dark .card-footer:first-child,html.theme--documenter-dark .card-content:first-child,html.theme--documenter-dark .card-header:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}html.theme--documenter-dark .card-footer:last-child,html.theme--documenter-dark .card-content:last-child,html.theme--documenter-dark .card-header:last-child{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}html.theme--documenter-dark .card-header{background-color:rgba(0,0,0,0);align-items:stretch;box-shadow:0 0.125em 0.25em rgba(10,10,10,0.1);display:flex}html.theme--documenter-dark .card-header-title{align-items:center;color:#f2f2f2;display:flex;flex-grow:1;font-weight:700;padding:0.75rem 1rem}html.theme--documenter-dark .card-header-title.is-centered{justify-content:center}html.theme--documenter-dark .card-header-icon{-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;color:currentColor;font-family:inherit;font-size:1em;margin:0;padding:0;align-items:center;cursor:pointer;display:flex;justify-content:center;padding:0.75rem 1rem}html.theme--documenter-dark .card-image{display:block;position:relative}html.theme--documenter-dark .card-image:first-child img{border-top-left-radius:.25rem;border-top-right-radius:.25rem}html.theme--documenter-dark .card-image:last-child img{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}html.theme--documenter-dark .card-content{background-color:rgba(0,0,0,0);padding:1.5rem}html.theme--documenter-dark .card-footer{background-color:rgba(0,0,0,0);border-top:1px solid #ededed;align-items:stretch;display:flex}html.theme--documenter-dark .card-footer-item{align-items:center;display:flex;flex-basis:0;flex-grow:1;flex-shrink:0;justify-content:center;padding:.75rem}html.theme--documenter-dark .card-footer-item:not(:last-child){border-right:1px solid #ededed}html.theme--documenter-dark .card .media:not(:last-child){margin-bottom:1.5rem}html.theme--documenter-dark .dropdown{display:inline-flex;position:relative;vertical-align:top}html.theme--documenter-dark .dropdown.is-active .dropdown-menu,html.theme--documenter-dark .dropdown.is-hoverable:hover .dropdown-menu{display:block}html.theme--documenter-dark .dropdown.is-right .dropdown-menu{left:auto;right:0}html.theme--documenter-dark .dropdown.is-up .dropdown-menu{bottom:100%;padding-bottom:4px;padding-top:initial;top:auto}html.theme--documenter-dark .dropdown-menu{display:none;left:0;min-width:12rem;padding-top:4px;position:absolute;top:100%;z-index:20}html.theme--documenter-dark .dropdown-content{background-color:#282f2f;border-radius:.4em;box-shadow:#171717;padding-bottom:.5rem;padding-top:.5rem}html.theme--documenter-dark .dropdown-item{color:#fff;display:block;font-size:0.875rem;line-height:1.5;padding:0.375rem 1rem;position:relative}html.theme--documenter-dark a.dropdown-item,html.theme--documenter-dark button.dropdown-item{padding-right:3rem;text-align:inherit;white-space:nowrap;width:100%}html.theme--documenter-dark a.dropdown-item:hover,html.theme--documenter-dark button.dropdown-item:hover{background-color:#282f2f;color:#0a0a0a}html.theme--documenter-dark a.dropdown-item.is-active,html.theme--documenter-dark button.dropdown-item.is-active{background-color:#1abc9c;color:#fff}html.theme--documenter-dark .dropdown-divider{background-color:#ededed;border:none;display:block;height:1px;margin:0.5rem 0}html.theme--documenter-dark .level{align-items:center;justify-content:space-between}html.theme--documenter-dark .level code{border-radius:.4em}html.theme--documenter-dark .level img{display:inline-block;vertical-align:top}html.theme--documenter-dark .level.is-mobile{display:flex}html.theme--documenter-dark .level.is-mobile .level-left,html.theme--documenter-dark .level.is-mobile .level-right{display:flex}html.theme--documenter-dark .level.is-mobile .level-left+.level-right{margin-top:0}html.theme--documenter-dark .level.is-mobile .level-item:not(:last-child){margin-bottom:0;margin-right:.75rem}html.theme--documenter-dark .level.is-mobile .level-item:not(.is-narrow){flex-grow:1}@media screen and (min-width: 769px),print{html.theme--documenter-dark .level{display:flex}html.theme--documenter-dark .level>.level-item:not(.is-narrow){flex-grow:1}}html.theme--documenter-dark .level-item{align-items:center;display:flex;flex-basis:auto;flex-grow:0;flex-shrink:0;justify-content:center}html.theme--documenter-dark .level-item .title,html.theme--documenter-dark .level-item .subtitle{margin-bottom:0}@media screen and (max-width: 768px){html.theme--documenter-dark .level-item:not(:last-child){margin-bottom:.75rem}}html.theme--documenter-dark .level-left,html.theme--documenter-dark .level-right{flex-basis:auto;flex-grow:0;flex-shrink:0}html.theme--documenter-dark .level-left .level-item.is-flexible,html.theme--documenter-dark .level-right .level-item.is-flexible{flex-grow:1}@media screen and (min-width: 769px),print{html.theme--documenter-dark .level-left .level-item:not(:last-child),html.theme--documenter-dark .level-right .level-item:not(:last-child){margin-right:.75rem}}html.theme--documenter-dark .level-left{align-items:center;justify-content:flex-start}@media screen and (max-width: 768px){html.theme--documenter-dark .level-left+.level-right{margin-top:1.5rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .level-left{display:flex}}html.theme--documenter-dark .level-right{align-items:center;justify-content:flex-end}@media screen and (min-width: 769px),print{html.theme--documenter-dark .level-right{display:flex}}html.theme--documenter-dark .media{align-items:flex-start;display:flex;text-align:inherit}html.theme--documenter-dark .media .content:not(:last-child){margin-bottom:.75rem}html.theme--documenter-dark .media .media{border-top:1px solid rgba(94,109,111,0.5);display:flex;padding-top:.75rem}html.theme--documenter-dark .media .media .content:not(:last-child),html.theme--documenter-dark .media .media .control:not(:last-child){margin-bottom:.5rem}html.theme--documenter-dark .media .media .media{padding-top:.5rem}html.theme--documenter-dark .media .media .media+.media{margin-top:.5rem}html.theme--documenter-dark .media+.media{border-top:1px solid rgba(94,109,111,0.5);margin-top:1rem;padding-top:1rem}html.theme--documenter-dark .media.is-large+.media{margin-top:1.5rem;padding-top:1.5rem}html.theme--documenter-dark .media-left,html.theme--documenter-dark .media-right{flex-basis:auto;flex-grow:0;flex-shrink:0}html.theme--documenter-dark .media-left{margin-right:1rem}html.theme--documenter-dark .media-right{margin-left:1rem}html.theme--documenter-dark .media-content{flex-basis:auto;flex-grow:1;flex-shrink:1;text-align:inherit}@media screen and (max-width: 768px){html.theme--documenter-dark .media-content{overflow-x:auto}}html.theme--documenter-dark .menu{font-size:1rem}html.theme--documenter-dark .menu.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.menu{font-size:.75rem}html.theme--documenter-dark .menu.is-medium{font-size:1.25rem}html.theme--documenter-dark .menu.is-large{font-size:1.5rem}html.theme--documenter-dark .menu-list{line-height:1.25}html.theme--documenter-dark .menu-list a{border-radius:3px;color:#fff;display:block;padding:0.5em 0.75em}html.theme--documenter-dark .menu-list a:hover{background-color:#282f2f;color:#f2f2f2}html.theme--documenter-dark .menu-list a.is-active{background-color:#1abc9c;color:#fff}html.theme--documenter-dark .menu-list li ul{border-left:1px solid #5e6d6f;margin:.75em;padding-left:.75em}html.theme--documenter-dark .menu-label{color:#fff;font-size:.75em;letter-spacing:.1em;text-transform:uppercase}html.theme--documenter-dark .menu-label:not(:first-child){margin-top:1em}html.theme--documenter-dark .menu-label:not(:last-child){margin-bottom:1em}html.theme--documenter-dark .message{background-color:#282f2f;border-radius:.4em;font-size:1rem}html.theme--documenter-dark .message strong{color:currentColor}html.theme--documenter-dark .message a:not(.button):not(.tag):not(.dropdown-item){color:currentColor;text-decoration:underline}html.theme--documenter-dark .message.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.message{font-size:.75rem}html.theme--documenter-dark .message.is-medium{font-size:1.25rem}html.theme--documenter-dark .message.is-large{font-size:1.5rem}html.theme--documenter-dark .message.is-white{background-color:#fff}html.theme--documenter-dark .message.is-white .message-header{background-color:#fff;color:#0a0a0a}html.theme--documenter-dark .message.is-white .message-body{border-color:#fff}html.theme--documenter-dark .message.is-black{background-color:#fafafa}html.theme--documenter-dark .message.is-black .message-header{background-color:#0a0a0a;color:#fff}html.theme--documenter-dark .message.is-black .message-body{border-color:#0a0a0a}html.theme--documenter-dark .message.is-light{background-color:#f9fafb}html.theme--documenter-dark .message.is-light .message-header{background-color:#ecf0f1;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .message.is-light .message-body{border-color:#ecf0f1}html.theme--documenter-dark .message.is-dark,html.theme--documenter-dark .content kbd.message{background-color:#f9fafa}html.theme--documenter-dark .message.is-dark .message-header,html.theme--documenter-dark .content kbd.message .message-header{background-color:#282f2f;color:#fff}html.theme--documenter-dark .message.is-dark .message-body,html.theme--documenter-dark .content kbd.message .message-body{border-color:#282f2f}html.theme--documenter-dark .message.is-primary,html.theme--documenter-dark .docstring>section>a.message.docs-sourcelink{background-color:#f1f5f9}html.theme--documenter-dark .message.is-primary .message-header,html.theme--documenter-dark .docstring>section>a.message.docs-sourcelink .message-header{background-color:#375a7f;color:#fff}html.theme--documenter-dark .message.is-primary .message-body,html.theme--documenter-dark .docstring>section>a.message.docs-sourcelink .message-body{border-color:#375a7f;color:#4d7eb2}html.theme--documenter-dark .message.is-link{background-color:#edfdf9}html.theme--documenter-dark .message.is-link .message-header{background-color:#1abc9c;color:#fff}html.theme--documenter-dark .message.is-link .message-body{border-color:#1abc9c;color:#15987e}html.theme--documenter-dark .message.is-info{background-color:#ebf7ff}html.theme--documenter-dark .message.is-info .message-header{background-color:#024c7d;color:#fff}html.theme--documenter-dark .message.is-info .message-body{border-color:#024c7d;color:#0e9dfb}html.theme--documenter-dark .message.is-success{background-color:#ebfff3}html.theme--documenter-dark .message.is-success .message-header{background-color:#008438;color:#fff}html.theme--documenter-dark .message.is-success .message-body{border-color:#008438;color:#00eb64}html.theme--documenter-dark .message.is-warning{background-color:#fffaeb}html.theme--documenter-dark .message.is-warning .message-header{background-color:#ad8100;color:#fff}html.theme--documenter-dark .message.is-warning .message-body{border-color:#ad8100;color:#d19c00}html.theme--documenter-dark .message.is-danger{background-color:#fdeeec}html.theme--documenter-dark .message.is-danger .message-header{background-color:#9e1b0d;color:#fff}html.theme--documenter-dark .message.is-danger .message-body{border-color:#9e1b0d;color:#ec311d}html.theme--documenter-dark .message-header{align-items:center;background-color:#fff;border-radius:.4em .4em 0 0;color:rgba(0,0,0,0.7);display:flex;font-weight:700;justify-content:space-between;line-height:1.25;padding:0.75em 1em;position:relative}html.theme--documenter-dark .message-header .delete{flex-grow:0;flex-shrink:0;margin-left:.75em}html.theme--documenter-dark .message-header+.message-body{border-width:0;border-top-left-radius:0;border-top-right-radius:0}html.theme--documenter-dark .message-body{border-color:#5e6d6f;border-radius:.4em;border-style:solid;border-width:0 0 0 4px;color:#fff;padding:1.25em 1.5em}html.theme--documenter-dark .message-body code,html.theme--documenter-dark .message-body pre{background-color:#fff}html.theme--documenter-dark .message-body pre code{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .modal{align-items:center;display:none;flex-direction:column;justify-content:center;overflow:hidden;position:fixed;z-index:40}html.theme--documenter-dark .modal.is-active{display:flex}html.theme--documenter-dark .modal-background{background-color:rgba(10,10,10,0.86)}html.theme--documenter-dark .modal-content,html.theme--documenter-dark .modal-card{margin:0 20px;max-height:calc(100vh - 160px);overflow:auto;position:relative;width:100%}@media screen and (min-width: 769px){html.theme--documenter-dark .modal-content,html.theme--documenter-dark .modal-card{margin:0 auto;max-height:calc(100vh - 40px);width:640px}}html.theme--documenter-dark .modal-close{background:none;height:40px;position:fixed;right:20px;top:20px;width:40px}html.theme--documenter-dark .modal-card{display:flex;flex-direction:column;max-height:calc(100vh - 40px);overflow:hidden;-ms-overflow-y:visible}html.theme--documenter-dark .modal-card-head,html.theme--documenter-dark .modal-card-foot{align-items:center;background-color:#282f2f;display:flex;flex-shrink:0;justify-content:flex-start;padding:20px;position:relative}html.theme--documenter-dark .modal-card-head{border-bottom:1px solid #5e6d6f;border-top-left-radius:8px;border-top-right-radius:8px}html.theme--documenter-dark .modal-card-title{color:#f2f2f2;flex-grow:1;flex-shrink:0;font-size:1.5rem;line-height:1}html.theme--documenter-dark .modal-card-foot{border-bottom-left-radius:8px;border-bottom-right-radius:8px;border-top:1px solid #5e6d6f}html.theme--documenter-dark .modal-card-foot .button:not(:last-child){margin-right:.5em}html.theme--documenter-dark .modal-card-body{-webkit-overflow-scrolling:touch;background-color:#fff;flex-grow:1;flex-shrink:1;overflow:auto;padding:20px}html.theme--documenter-dark .navbar{background-color:#375a7f;min-height:4rem;position:relative;z-index:30}html.theme--documenter-dark .navbar.is-white{background-color:#fff;color:#0a0a0a}html.theme--documenter-dark .navbar.is-white .navbar-brand>.navbar-item,html.theme--documenter-dark .navbar.is-white .navbar-brand .navbar-link{color:#0a0a0a}html.theme--documenter-dark .navbar.is-white .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-white .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-white .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-white .navbar-brand .navbar-link:focus,html.theme--documenter-dark .navbar.is-white .navbar-brand .navbar-link:hover,html.theme--documenter-dark .navbar.is-white .navbar-brand .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}html.theme--documenter-dark .navbar.is-white .navbar-brand .navbar-link::after{border-color:#0a0a0a}html.theme--documenter-dark .navbar.is-white .navbar-burger{color:#0a0a0a}@media screen and (min-width: 1056px){html.theme--documenter-dark .navbar.is-white .navbar-start>.navbar-item,html.theme--documenter-dark .navbar.is-white .navbar-start .navbar-link,html.theme--documenter-dark .navbar.is-white .navbar-end>.navbar-item,html.theme--documenter-dark .navbar.is-white .navbar-end .navbar-link{color:#0a0a0a}html.theme--documenter-dark .navbar.is-white .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-white .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-white .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-white .navbar-start .navbar-link:focus,html.theme--documenter-dark .navbar.is-white .navbar-start .navbar-link:hover,html.theme--documenter-dark .navbar.is-white .navbar-start .navbar-link.is-active,html.theme--documenter-dark .navbar.is-white .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-white .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-white .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-white .navbar-end .navbar-link:focus,html.theme--documenter-dark .navbar.is-white .navbar-end .navbar-link:hover,html.theme--documenter-dark .navbar.is-white .navbar-end .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}html.theme--documenter-dark .navbar.is-white .navbar-start .navbar-link::after,html.theme--documenter-dark .navbar.is-white .navbar-end .navbar-link::after{border-color:#0a0a0a}html.theme--documenter-dark .navbar.is-white .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .navbar.is-white .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .navbar.is-white .navbar-item.has-dropdown.is-active .navbar-link{background-color:#f2f2f2;color:#0a0a0a}html.theme--documenter-dark .navbar.is-white .navbar-dropdown a.navbar-item.is-active{background-color:#fff;color:#0a0a0a}}html.theme--documenter-dark .navbar.is-black{background-color:#0a0a0a;color:#fff}html.theme--documenter-dark .navbar.is-black .navbar-brand>.navbar-item,html.theme--documenter-dark .navbar.is-black .navbar-brand .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-black .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-black .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-black .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-black .navbar-brand .navbar-link:focus,html.theme--documenter-dark .navbar.is-black .navbar-brand .navbar-link:hover,html.theme--documenter-dark .navbar.is-black .navbar-brand .navbar-link.is-active{background-color:#000;color:#fff}html.theme--documenter-dark .navbar.is-black .navbar-brand .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-black .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--documenter-dark .navbar.is-black .navbar-start>.navbar-item,html.theme--documenter-dark .navbar.is-black .navbar-start .navbar-link,html.theme--documenter-dark .navbar.is-black .navbar-end>.navbar-item,html.theme--documenter-dark .navbar.is-black .navbar-end .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-black .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-black .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-black .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-black .navbar-start .navbar-link:focus,html.theme--documenter-dark .navbar.is-black .navbar-start .navbar-link:hover,html.theme--documenter-dark .navbar.is-black .navbar-start .navbar-link.is-active,html.theme--documenter-dark .navbar.is-black .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-black .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-black .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-black .navbar-end .navbar-link:focus,html.theme--documenter-dark .navbar.is-black .navbar-end .navbar-link:hover,html.theme--documenter-dark .navbar.is-black .navbar-end .navbar-link.is-active{background-color:#000;color:#fff}html.theme--documenter-dark .navbar.is-black .navbar-start .navbar-link::after,html.theme--documenter-dark .navbar.is-black .navbar-end .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-black .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .navbar.is-black .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .navbar.is-black .navbar-item.has-dropdown.is-active .navbar-link{background-color:#000;color:#fff}html.theme--documenter-dark .navbar.is-black .navbar-dropdown a.navbar-item.is-active{background-color:#0a0a0a;color:#fff}}html.theme--documenter-dark .navbar.is-light{background-color:#ecf0f1;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-light .navbar-brand>.navbar-item,html.theme--documenter-dark .navbar.is-light .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-light .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-light .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-light .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-light .navbar-brand .navbar-link:focus,html.theme--documenter-dark .navbar.is-light .navbar-brand .navbar-link:hover,html.theme--documenter-dark .navbar.is-light .navbar-brand .navbar-link.is-active{background-color:#dde4e6;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-light .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-light .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1056px){html.theme--documenter-dark .navbar.is-light .navbar-start>.navbar-item,html.theme--documenter-dark .navbar.is-light .navbar-start .navbar-link,html.theme--documenter-dark .navbar.is-light .navbar-end>.navbar-item,html.theme--documenter-dark .navbar.is-light .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-light .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-light .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-light .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-light .navbar-start .navbar-link:focus,html.theme--documenter-dark .navbar.is-light .navbar-start .navbar-link:hover,html.theme--documenter-dark .navbar.is-light .navbar-start .navbar-link.is-active,html.theme--documenter-dark .navbar.is-light .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-light .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-light .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-light .navbar-end .navbar-link:focus,html.theme--documenter-dark .navbar.is-light .navbar-end .navbar-link:hover,html.theme--documenter-dark .navbar.is-light .navbar-end .navbar-link.is-active{background-color:#dde4e6;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-light .navbar-start .navbar-link::after,html.theme--documenter-dark .navbar.is-light .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-light .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .navbar.is-light .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .navbar.is-light .navbar-item.has-dropdown.is-active .navbar-link{background-color:#dde4e6;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-light .navbar-dropdown a.navbar-item.is-active{background-color:#ecf0f1;color:rgba(0,0,0,0.7)}}html.theme--documenter-dark .navbar.is-dark,html.theme--documenter-dark .content kbd.navbar{background-color:#282f2f;color:#fff}html.theme--documenter-dark .navbar.is-dark .navbar-brand>.navbar-item,html.theme--documenter-dark .content kbd.navbar .navbar-brand>.navbar-item,html.theme--documenter-dark .navbar.is-dark .navbar-brand .navbar-link,html.theme--documenter-dark .content kbd.navbar .navbar-brand .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-dark .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .content kbd.navbar .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-dark .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .content kbd.navbar .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-dark .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .content kbd.navbar .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-dark .navbar-brand .navbar-link:focus,html.theme--documenter-dark .content kbd.navbar .navbar-brand .navbar-link:focus,html.theme--documenter-dark .navbar.is-dark .navbar-brand .navbar-link:hover,html.theme--documenter-dark .content kbd.navbar .navbar-brand .navbar-link:hover,html.theme--documenter-dark .navbar.is-dark .navbar-brand .navbar-link.is-active,html.theme--documenter-dark .content kbd.navbar .navbar-brand .navbar-link.is-active{background-color:#1d2122;color:#fff}html.theme--documenter-dark .navbar.is-dark .navbar-brand .navbar-link::after,html.theme--documenter-dark .content kbd.navbar .navbar-brand .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-dark .navbar-burger,html.theme--documenter-dark .content kbd.navbar .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--documenter-dark .navbar.is-dark .navbar-start>.navbar-item,html.theme--documenter-dark .content kbd.navbar .navbar-start>.navbar-item,html.theme--documenter-dark .navbar.is-dark .navbar-start .navbar-link,html.theme--documenter-dark .content kbd.navbar .navbar-start .navbar-link,html.theme--documenter-dark .navbar.is-dark .navbar-end>.navbar-item,html.theme--documenter-dark .content kbd.navbar .navbar-end>.navbar-item,html.theme--documenter-dark .navbar.is-dark .navbar-end .navbar-link,html.theme--documenter-dark .content kbd.navbar .navbar-end .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-dark .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .content kbd.navbar .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-dark .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .content kbd.navbar .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-dark .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .content kbd.navbar .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-dark .navbar-start .navbar-link:focus,html.theme--documenter-dark .content kbd.navbar .navbar-start .navbar-link:focus,html.theme--documenter-dark .navbar.is-dark .navbar-start .navbar-link:hover,html.theme--documenter-dark .content kbd.navbar .navbar-start .navbar-link:hover,html.theme--documenter-dark .navbar.is-dark .navbar-start .navbar-link.is-active,html.theme--documenter-dark .content kbd.navbar .navbar-start .navbar-link.is-active,html.theme--documenter-dark .navbar.is-dark .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .content kbd.navbar .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-dark .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .content kbd.navbar .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-dark .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .content kbd.navbar .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-dark .navbar-end .navbar-link:focus,html.theme--documenter-dark .content kbd.navbar .navbar-end .navbar-link:focus,html.theme--documenter-dark .navbar.is-dark .navbar-end .navbar-link:hover,html.theme--documenter-dark .content kbd.navbar .navbar-end .navbar-link:hover,html.theme--documenter-dark .navbar.is-dark .navbar-end .navbar-link.is-active,html.theme--documenter-dark .content kbd.navbar .navbar-end .navbar-link.is-active{background-color:#1d2122;color:#fff}html.theme--documenter-dark .navbar.is-dark .navbar-start .navbar-link::after,html.theme--documenter-dark .content kbd.navbar .navbar-start .navbar-link::after,html.theme--documenter-dark .navbar.is-dark .navbar-end .navbar-link::after,html.theme--documenter-dark .content kbd.navbar .navbar-end .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-dark .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .content kbd.navbar .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .navbar.is-dark .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .content kbd.navbar .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .navbar.is-dark .navbar-item.has-dropdown.is-active .navbar-link,html.theme--documenter-dark .content kbd.navbar .navbar-item.has-dropdown.is-active .navbar-link{background-color:#1d2122;color:#fff}html.theme--documenter-dark .navbar.is-dark .navbar-dropdown a.navbar-item.is-active,html.theme--documenter-dark .content kbd.navbar .navbar-dropdown a.navbar-item.is-active{background-color:#282f2f;color:#fff}}html.theme--documenter-dark .navbar.is-primary,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink{background-color:#375a7f;color:#fff}html.theme--documenter-dark .navbar.is-primary .navbar-brand>.navbar-item,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-brand>.navbar-item,html.theme--documenter-dark .navbar.is-primary .navbar-brand .navbar-link,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-primary .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-primary .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-primary .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-primary .navbar-brand .navbar-link:focus,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link:focus,html.theme--documenter-dark .navbar.is-primary .navbar-brand .navbar-link:hover,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link:hover,html.theme--documenter-dark .navbar.is-primary .navbar-brand .navbar-link.is-active,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link.is-active{background-color:#2f4d6d;color:#fff}html.theme--documenter-dark .navbar.is-primary .navbar-brand .navbar-link::after,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-primary .navbar-burger,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--documenter-dark .navbar.is-primary .navbar-start>.navbar-item,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-start>.navbar-item,html.theme--documenter-dark .navbar.is-primary .navbar-start .navbar-link,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link,html.theme--documenter-dark .navbar.is-primary .navbar-end>.navbar-item,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-end>.navbar-item,html.theme--documenter-dark .navbar.is-primary .navbar-end .navbar-link,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-primary .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-primary .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-primary .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-primary .navbar-start .navbar-link:focus,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link:focus,html.theme--documenter-dark .navbar.is-primary .navbar-start .navbar-link:hover,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link:hover,html.theme--documenter-dark .navbar.is-primary .navbar-start .navbar-link.is-active,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link.is-active,html.theme--documenter-dark .navbar.is-primary .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-primary .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-primary .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-primary .navbar-end .navbar-link:focus,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link:focus,html.theme--documenter-dark .navbar.is-primary .navbar-end .navbar-link:hover,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link:hover,html.theme--documenter-dark .navbar.is-primary .navbar-end .navbar-link.is-active,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link.is-active{background-color:#2f4d6d;color:#fff}html.theme--documenter-dark .navbar.is-primary .navbar-start .navbar-link::after,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link::after,html.theme--documenter-dark .navbar.is-primary .navbar-end .navbar-link::after,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-primary .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .navbar.is-primary .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .navbar.is-primary .navbar-item.has-dropdown.is-active .navbar-link,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown.is-active .navbar-link{background-color:#2f4d6d;color:#fff}html.theme--documenter-dark .navbar.is-primary .navbar-dropdown a.navbar-item.is-active,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-dropdown a.navbar-item.is-active{background-color:#375a7f;color:#fff}}html.theme--documenter-dark .navbar.is-link{background-color:#1abc9c;color:#fff}html.theme--documenter-dark .navbar.is-link .navbar-brand>.navbar-item,html.theme--documenter-dark .navbar.is-link .navbar-brand .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-link .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-link .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-link .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-link .navbar-brand .navbar-link:focus,html.theme--documenter-dark .navbar.is-link .navbar-brand .navbar-link:hover,html.theme--documenter-dark .navbar.is-link .navbar-brand .navbar-link.is-active{background-color:#17a689;color:#fff}html.theme--documenter-dark .navbar.is-link .navbar-brand .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-link .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--documenter-dark .navbar.is-link .navbar-start>.navbar-item,html.theme--documenter-dark .navbar.is-link .navbar-start .navbar-link,html.theme--documenter-dark .navbar.is-link .navbar-end>.navbar-item,html.theme--documenter-dark .navbar.is-link .navbar-end .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-link .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-link .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-link .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-link .navbar-start .navbar-link:focus,html.theme--documenter-dark .navbar.is-link .navbar-start .navbar-link:hover,html.theme--documenter-dark .navbar.is-link .navbar-start .navbar-link.is-active,html.theme--documenter-dark .navbar.is-link .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-link .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-link .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-link .navbar-end .navbar-link:focus,html.theme--documenter-dark .navbar.is-link .navbar-end .navbar-link:hover,html.theme--documenter-dark .navbar.is-link .navbar-end .navbar-link.is-active{background-color:#17a689;color:#fff}html.theme--documenter-dark .navbar.is-link .navbar-start .navbar-link::after,html.theme--documenter-dark .navbar.is-link .navbar-end .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-link .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .navbar.is-link .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .navbar.is-link .navbar-item.has-dropdown.is-active .navbar-link{background-color:#17a689;color:#fff}html.theme--documenter-dark .navbar.is-link .navbar-dropdown a.navbar-item.is-active{background-color:#1abc9c;color:#fff}}html.theme--documenter-dark .navbar.is-info{background-color:#024c7d;color:#fff}html.theme--documenter-dark .navbar.is-info .navbar-brand>.navbar-item,html.theme--documenter-dark .navbar.is-info .navbar-brand .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-info .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-info .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-info .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-info .navbar-brand .navbar-link:focus,html.theme--documenter-dark .navbar.is-info .navbar-brand .navbar-link:hover,html.theme--documenter-dark .navbar.is-info .navbar-brand .navbar-link.is-active{background-color:#023d64;color:#fff}html.theme--documenter-dark .navbar.is-info .navbar-brand .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-info .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--documenter-dark .navbar.is-info .navbar-start>.navbar-item,html.theme--documenter-dark .navbar.is-info .navbar-start .navbar-link,html.theme--documenter-dark .navbar.is-info .navbar-end>.navbar-item,html.theme--documenter-dark .navbar.is-info .navbar-end .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-info .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-info .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-info .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-info .navbar-start .navbar-link:focus,html.theme--documenter-dark .navbar.is-info .navbar-start .navbar-link:hover,html.theme--documenter-dark .navbar.is-info .navbar-start .navbar-link.is-active,html.theme--documenter-dark .navbar.is-info .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-info .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-info .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-info .navbar-end .navbar-link:focus,html.theme--documenter-dark .navbar.is-info .navbar-end .navbar-link:hover,html.theme--documenter-dark .navbar.is-info .navbar-end .navbar-link.is-active{background-color:#023d64;color:#fff}html.theme--documenter-dark .navbar.is-info .navbar-start .navbar-link::after,html.theme--documenter-dark .navbar.is-info .navbar-end .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-info .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .navbar.is-info .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .navbar.is-info .navbar-item.has-dropdown.is-active .navbar-link{background-color:#023d64;color:#fff}html.theme--documenter-dark .navbar.is-info .navbar-dropdown a.navbar-item.is-active{background-color:#024c7d;color:#fff}}html.theme--documenter-dark .navbar.is-success{background-color:#008438;color:#fff}html.theme--documenter-dark .navbar.is-success .navbar-brand>.navbar-item,html.theme--documenter-dark .navbar.is-success .navbar-brand .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-success .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-success .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-success .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-success .navbar-brand .navbar-link:focus,html.theme--documenter-dark .navbar.is-success .navbar-brand .navbar-link:hover,html.theme--documenter-dark .navbar.is-success .navbar-brand .navbar-link.is-active{background-color:#006b2d;color:#fff}html.theme--documenter-dark .navbar.is-success .navbar-brand .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-success .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--documenter-dark .navbar.is-success .navbar-start>.navbar-item,html.theme--documenter-dark .navbar.is-success .navbar-start .navbar-link,html.theme--documenter-dark .navbar.is-success .navbar-end>.navbar-item,html.theme--documenter-dark .navbar.is-success .navbar-end .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-success .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-success .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-success .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-success .navbar-start .navbar-link:focus,html.theme--documenter-dark .navbar.is-success .navbar-start .navbar-link:hover,html.theme--documenter-dark .navbar.is-success .navbar-start .navbar-link.is-active,html.theme--documenter-dark .navbar.is-success .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-success .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-success .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-success .navbar-end .navbar-link:focus,html.theme--documenter-dark .navbar.is-success .navbar-end .navbar-link:hover,html.theme--documenter-dark .navbar.is-success .navbar-end .navbar-link.is-active{background-color:#006b2d;color:#fff}html.theme--documenter-dark .navbar.is-success .navbar-start .navbar-link::after,html.theme--documenter-dark .navbar.is-success .navbar-end .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-success .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .navbar.is-success .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .navbar.is-success .navbar-item.has-dropdown.is-active .navbar-link{background-color:#006b2d;color:#fff}html.theme--documenter-dark .navbar.is-success .navbar-dropdown a.navbar-item.is-active{background-color:#008438;color:#fff}}html.theme--documenter-dark .navbar.is-warning{background-color:#ad8100;color:#fff}html.theme--documenter-dark .navbar.is-warning .navbar-brand>.navbar-item,html.theme--documenter-dark .navbar.is-warning .navbar-brand .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-warning .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-warning .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-warning .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-warning .navbar-brand .navbar-link:focus,html.theme--documenter-dark .navbar.is-warning .navbar-brand .navbar-link:hover,html.theme--documenter-dark .navbar.is-warning .navbar-brand .navbar-link.is-active{background-color:#946e00;color:#fff}html.theme--documenter-dark .navbar.is-warning .navbar-brand .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-warning .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--documenter-dark .navbar.is-warning .navbar-start>.navbar-item,html.theme--documenter-dark .navbar.is-warning .navbar-start .navbar-link,html.theme--documenter-dark .navbar.is-warning .navbar-end>.navbar-item,html.theme--documenter-dark .navbar.is-warning .navbar-end .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-warning .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-warning .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-warning .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-warning .navbar-start .navbar-link:focus,html.theme--documenter-dark .navbar.is-warning .navbar-start .navbar-link:hover,html.theme--documenter-dark .navbar.is-warning .navbar-start .navbar-link.is-active,html.theme--documenter-dark .navbar.is-warning .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-warning .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-warning .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-warning .navbar-end .navbar-link:focus,html.theme--documenter-dark .navbar.is-warning .navbar-end .navbar-link:hover,html.theme--documenter-dark .navbar.is-warning .navbar-end .navbar-link.is-active{background-color:#946e00;color:#fff}html.theme--documenter-dark .navbar.is-warning .navbar-start .navbar-link::after,html.theme--documenter-dark .navbar.is-warning .navbar-end .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-warning .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .navbar.is-warning .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .navbar.is-warning .navbar-item.has-dropdown.is-active .navbar-link{background-color:#946e00;color:#fff}html.theme--documenter-dark .navbar.is-warning .navbar-dropdown a.navbar-item.is-active{background-color:#ad8100;color:#fff}}html.theme--documenter-dark .navbar.is-danger{background-color:#9e1b0d;color:#fff}html.theme--documenter-dark .navbar.is-danger .navbar-brand>.navbar-item,html.theme--documenter-dark .navbar.is-danger .navbar-brand .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-danger .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-danger .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-danger .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-danger .navbar-brand .navbar-link:focus,html.theme--documenter-dark .navbar.is-danger .navbar-brand .navbar-link:hover,html.theme--documenter-dark .navbar.is-danger .navbar-brand .navbar-link.is-active{background-color:#86170b;color:#fff}html.theme--documenter-dark .navbar.is-danger .navbar-brand .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-danger .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--documenter-dark .navbar.is-danger .navbar-start>.navbar-item,html.theme--documenter-dark .navbar.is-danger .navbar-start .navbar-link,html.theme--documenter-dark .navbar.is-danger .navbar-end>.navbar-item,html.theme--documenter-dark .navbar.is-danger .navbar-end .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-danger .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-danger .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-danger .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-danger .navbar-start .navbar-link:focus,html.theme--documenter-dark .navbar.is-danger .navbar-start .navbar-link:hover,html.theme--documenter-dark .navbar.is-danger .navbar-start .navbar-link.is-active,html.theme--documenter-dark .navbar.is-danger .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-danger .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-danger .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-danger .navbar-end .navbar-link:focus,html.theme--documenter-dark .navbar.is-danger .navbar-end .navbar-link:hover,html.theme--documenter-dark .navbar.is-danger .navbar-end .navbar-link.is-active{background-color:#86170b;color:#fff}html.theme--documenter-dark .navbar.is-danger .navbar-start .navbar-link::after,html.theme--documenter-dark .navbar.is-danger .navbar-end .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-danger .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .navbar.is-danger .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .navbar.is-danger .navbar-item.has-dropdown.is-active .navbar-link{background-color:#86170b;color:#fff}html.theme--documenter-dark .navbar.is-danger .navbar-dropdown a.navbar-item.is-active{background-color:#9e1b0d;color:#fff}}html.theme--documenter-dark .navbar>.container{align-items:stretch;display:flex;min-height:4rem;width:100%}html.theme--documenter-dark .navbar.has-shadow{box-shadow:0 2px 0 0 #282f2f}html.theme--documenter-dark .navbar.is-fixed-bottom,html.theme--documenter-dark .navbar.is-fixed-top{left:0;position:fixed;right:0;z-index:30}html.theme--documenter-dark .navbar.is-fixed-bottom{bottom:0}html.theme--documenter-dark .navbar.is-fixed-bottom.has-shadow{box-shadow:0 -2px 0 0 #282f2f}html.theme--documenter-dark .navbar.is-fixed-top{top:0}html.theme--documenter-dark html.has-navbar-fixed-top,html.theme--documenter-dark body.has-navbar-fixed-top{padding-top:4rem}html.theme--documenter-dark html.has-navbar-fixed-bottom,html.theme--documenter-dark body.has-navbar-fixed-bottom{padding-bottom:4rem}html.theme--documenter-dark .navbar-brand,html.theme--documenter-dark .navbar-tabs{align-items:stretch;display:flex;flex-shrink:0;min-height:4rem}html.theme--documenter-dark .navbar-brand a.navbar-item:focus,html.theme--documenter-dark .navbar-brand a.navbar-item:hover{background-color:transparent}html.theme--documenter-dark .navbar-tabs{-webkit-overflow-scrolling:touch;max-width:100vw;overflow-x:auto;overflow-y:hidden}html.theme--documenter-dark .navbar-burger{color:#fff;-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;cursor:pointer;display:block;height:4rem;position:relative;width:4rem;margin-left:auto}html.theme--documenter-dark .navbar-burger span{background-color:currentColor;display:block;height:1px;left:calc(50% - 8px);position:absolute;transform-origin:center;transition-duration:86ms;transition-property:background-color, opacity, transform;transition-timing-function:ease-out;width:16px}html.theme--documenter-dark .navbar-burger span:nth-child(1){top:calc(50% - 6px)}html.theme--documenter-dark .navbar-burger span:nth-child(2){top:calc(50% - 1px)}html.theme--documenter-dark .navbar-burger span:nth-child(3){top:calc(50% + 4px)}html.theme--documenter-dark .navbar-burger:hover{background-color:rgba(0,0,0,0.05)}html.theme--documenter-dark .navbar-burger.is-active span:nth-child(1){transform:translateY(5px) rotate(45deg)}html.theme--documenter-dark .navbar-burger.is-active span:nth-child(2){opacity:0}html.theme--documenter-dark .navbar-burger.is-active span:nth-child(3){transform:translateY(-5px) rotate(-45deg)}html.theme--documenter-dark .navbar-menu{display:none}html.theme--documenter-dark .navbar-item,html.theme--documenter-dark .navbar-link{color:#fff;display:block;line-height:1.5;padding:0.5rem 0.75rem;position:relative}html.theme--documenter-dark .navbar-item .icon:only-child,html.theme--documenter-dark .navbar-link .icon:only-child{margin-left:-0.25rem;margin-right:-0.25rem}html.theme--documenter-dark a.navbar-item,html.theme--documenter-dark .navbar-link{cursor:pointer}html.theme--documenter-dark a.navbar-item:focus,html.theme--documenter-dark a.navbar-item:focus-within,html.theme--documenter-dark a.navbar-item:hover,html.theme--documenter-dark a.navbar-item.is-active,html.theme--documenter-dark .navbar-link:focus,html.theme--documenter-dark .navbar-link:focus-within,html.theme--documenter-dark .navbar-link:hover,html.theme--documenter-dark .navbar-link.is-active{background-color:rgba(0,0,0,0);color:#1abc9c}html.theme--documenter-dark .navbar-item{flex-grow:0;flex-shrink:0}html.theme--documenter-dark .navbar-item img{max-height:1.75rem}html.theme--documenter-dark .navbar-item.has-dropdown{padding:0}html.theme--documenter-dark .navbar-item.is-expanded{flex-grow:1;flex-shrink:1}html.theme--documenter-dark .navbar-item.is-tab{border-bottom:1px solid transparent;min-height:4rem;padding-bottom:calc(0.5rem - 1px)}html.theme--documenter-dark .navbar-item.is-tab:focus,html.theme--documenter-dark .navbar-item.is-tab:hover{background-color:rgba(0,0,0,0);border-bottom-color:#1abc9c}html.theme--documenter-dark .navbar-item.is-tab.is-active{background-color:rgba(0,0,0,0);border-bottom-color:#1abc9c;border-bottom-style:solid;border-bottom-width:3px;color:#1abc9c;padding-bottom:calc(0.5rem - 3px)}html.theme--documenter-dark .navbar-content{flex-grow:1;flex-shrink:1}html.theme--documenter-dark .navbar-link:not(.is-arrowless){padding-right:2.5em}html.theme--documenter-dark .navbar-link:not(.is-arrowless)::after{border-color:#fff;margin-top:-0.375em;right:1.125em}html.theme--documenter-dark .navbar-dropdown{font-size:0.875rem;padding-bottom:0.5rem;padding-top:0.5rem}html.theme--documenter-dark .navbar-dropdown .navbar-item{padding-left:1.5rem;padding-right:1.5rem}html.theme--documenter-dark .navbar-divider{background-color:rgba(0,0,0,0.2);border:none;display:none;height:2px;margin:0.5rem 0}@media screen and (max-width: 1055px){html.theme--documenter-dark .navbar>.container{display:block}html.theme--documenter-dark .navbar-brand .navbar-item,html.theme--documenter-dark .navbar-tabs .navbar-item{align-items:center;display:flex}html.theme--documenter-dark .navbar-link::after{display:none}html.theme--documenter-dark .navbar-menu{background-color:#375a7f;box-shadow:0 8px 16px rgba(10,10,10,0.1);padding:0.5rem 0}html.theme--documenter-dark .navbar-menu.is-active{display:block}html.theme--documenter-dark .navbar.is-fixed-bottom-touch,html.theme--documenter-dark .navbar.is-fixed-top-touch{left:0;position:fixed;right:0;z-index:30}html.theme--documenter-dark .navbar.is-fixed-bottom-touch{bottom:0}html.theme--documenter-dark .navbar.is-fixed-bottom-touch.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,0.1)}html.theme--documenter-dark .navbar.is-fixed-top-touch{top:0}html.theme--documenter-dark .navbar.is-fixed-top .navbar-menu,html.theme--documenter-dark .navbar.is-fixed-top-touch .navbar-menu{-webkit-overflow-scrolling:touch;max-height:calc(100vh - 4rem);overflow:auto}html.theme--documenter-dark html.has-navbar-fixed-top-touch,html.theme--documenter-dark body.has-navbar-fixed-top-touch{padding-top:4rem}html.theme--documenter-dark html.has-navbar-fixed-bottom-touch,html.theme--documenter-dark body.has-navbar-fixed-bottom-touch{padding-bottom:4rem}}@media screen and (min-width: 1056px){html.theme--documenter-dark .navbar,html.theme--documenter-dark .navbar-menu,html.theme--documenter-dark .navbar-start,html.theme--documenter-dark .navbar-end{align-items:stretch;display:flex}html.theme--documenter-dark .navbar{min-height:4rem}html.theme--documenter-dark .navbar.is-spaced{padding:1rem 2rem}html.theme--documenter-dark .navbar.is-spaced .navbar-start,html.theme--documenter-dark .navbar.is-spaced .navbar-end{align-items:center}html.theme--documenter-dark .navbar.is-spaced a.navbar-item,html.theme--documenter-dark .navbar.is-spaced .navbar-link{border-radius:.4em}html.theme--documenter-dark .navbar.is-transparent a.navbar-item:focus,html.theme--documenter-dark .navbar.is-transparent a.navbar-item:hover,html.theme--documenter-dark .navbar.is-transparent a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-transparent .navbar-link:focus,html.theme--documenter-dark .navbar.is-transparent .navbar-link:hover,html.theme--documenter-dark .navbar.is-transparent .navbar-link.is-active{background-color:transparent !important}html.theme--documenter-dark .navbar.is-transparent .navbar-item.has-dropdown.is-active .navbar-link,html.theme--documenter-dark .navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus .navbar-link,html.theme--documenter-dark .navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus-within .navbar-link,html.theme--documenter-dark .navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:hover .navbar-link{background-color:transparent !important}html.theme--documenter-dark .navbar.is-transparent .navbar-dropdown a.navbar-item:focus,html.theme--documenter-dark .navbar.is-transparent .navbar-dropdown a.navbar-item:hover{background-color:rgba(0,0,0,0);color:#dbdee0}html.theme--documenter-dark .navbar.is-transparent .navbar-dropdown a.navbar-item.is-active{background-color:rgba(0,0,0,0);color:#1abc9c}html.theme--documenter-dark .navbar-burger{display:none}html.theme--documenter-dark .navbar-item,html.theme--documenter-dark .navbar-link{align-items:center;display:flex}html.theme--documenter-dark .navbar-item.has-dropdown{align-items:stretch}html.theme--documenter-dark .navbar-item.has-dropdown-up .navbar-link::after{transform:rotate(135deg) translate(0.25em, -0.25em)}html.theme--documenter-dark .navbar-item.has-dropdown-up .navbar-dropdown{border-bottom:1px solid rgba(0,0,0,0.2);border-radius:8px 8px 0 0;border-top:none;bottom:100%;box-shadow:0 -8px 8px rgba(10,10,10,0.1);top:auto}html.theme--documenter-dark .navbar-item.is-active .navbar-dropdown,html.theme--documenter-dark .navbar-item.is-hoverable:focus .navbar-dropdown,html.theme--documenter-dark .navbar-item.is-hoverable:focus-within .navbar-dropdown,html.theme--documenter-dark .navbar-item.is-hoverable:hover .navbar-dropdown{display:block}.navbar.is-spaced html.theme--documenter-dark .navbar-item.is-active .navbar-dropdown,html.theme--documenter-dark .navbar-item.is-active .navbar-dropdown.is-boxed,.navbar.is-spaced html.theme--documenter-dark .navbar-item.is-hoverable:focus .navbar-dropdown,html.theme--documenter-dark .navbar-item.is-hoverable:focus .navbar-dropdown.is-boxed,.navbar.is-spaced html.theme--documenter-dark .navbar-item.is-hoverable:focus-within .navbar-dropdown,html.theme--documenter-dark .navbar-item.is-hoverable:focus-within .navbar-dropdown.is-boxed,.navbar.is-spaced html.theme--documenter-dark .navbar-item.is-hoverable:hover .navbar-dropdown,html.theme--documenter-dark .navbar-item.is-hoverable:hover .navbar-dropdown.is-boxed{opacity:1;pointer-events:auto;transform:translateY(0)}html.theme--documenter-dark .navbar-menu{flex-grow:1;flex-shrink:0}html.theme--documenter-dark .navbar-start{justify-content:flex-start;margin-right:auto}html.theme--documenter-dark .navbar-end{justify-content:flex-end;margin-left:auto}html.theme--documenter-dark .navbar-dropdown{background-color:#375a7f;border-bottom-left-radius:8px;border-bottom-right-radius:8px;border-top:1px solid rgba(0,0,0,0.2);box-shadow:0 8px 8px rgba(10,10,10,0.1);display:none;font-size:0.875rem;left:0;min-width:100%;position:absolute;top:100%;z-index:20}html.theme--documenter-dark .navbar-dropdown .navbar-item{padding:0.375rem 1rem;white-space:nowrap}html.theme--documenter-dark .navbar-dropdown a.navbar-item{padding-right:3rem}html.theme--documenter-dark .navbar-dropdown a.navbar-item:focus,html.theme--documenter-dark .navbar-dropdown a.navbar-item:hover{background-color:rgba(0,0,0,0);color:#dbdee0}html.theme--documenter-dark .navbar-dropdown a.navbar-item.is-active{background-color:rgba(0,0,0,0);color:#1abc9c}.navbar.is-spaced html.theme--documenter-dark .navbar-dropdown,html.theme--documenter-dark .navbar-dropdown.is-boxed{border-radius:8px;border-top:none;box-shadow:0 8px 8px rgba(10,10,10,0.1), 0 0 0 1px rgba(10,10,10,0.1);display:block;opacity:0;pointer-events:none;top:calc(100% + (-4px));transform:translateY(-5px);transition-duration:86ms;transition-property:opacity, transform}html.theme--documenter-dark .navbar-dropdown.is-right{left:auto;right:0}html.theme--documenter-dark .navbar-divider{display:block}html.theme--documenter-dark .navbar>.container .navbar-brand,html.theme--documenter-dark .container>.navbar .navbar-brand{margin-left:-.75rem}html.theme--documenter-dark .navbar>.container .navbar-menu,html.theme--documenter-dark .container>.navbar .navbar-menu{margin-right:-.75rem}html.theme--documenter-dark .navbar.is-fixed-bottom-desktop,html.theme--documenter-dark .navbar.is-fixed-top-desktop{left:0;position:fixed;right:0;z-index:30}html.theme--documenter-dark .navbar.is-fixed-bottom-desktop{bottom:0}html.theme--documenter-dark .navbar.is-fixed-bottom-desktop.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,0.1)}html.theme--documenter-dark .navbar.is-fixed-top-desktop{top:0}html.theme--documenter-dark html.has-navbar-fixed-top-desktop,html.theme--documenter-dark body.has-navbar-fixed-top-desktop{padding-top:4rem}html.theme--documenter-dark html.has-navbar-fixed-bottom-desktop,html.theme--documenter-dark body.has-navbar-fixed-bottom-desktop{padding-bottom:4rem}html.theme--documenter-dark html.has-spaced-navbar-fixed-top,html.theme--documenter-dark body.has-spaced-navbar-fixed-top{padding-top:6rem}html.theme--documenter-dark html.has-spaced-navbar-fixed-bottom,html.theme--documenter-dark body.has-spaced-navbar-fixed-bottom{padding-bottom:6rem}html.theme--documenter-dark a.navbar-item.is-active,html.theme--documenter-dark .navbar-link.is-active{color:#1abc9c}html.theme--documenter-dark a.navbar-item.is-active:not(:focus):not(:hover),html.theme--documenter-dark .navbar-link.is-active:not(:focus):not(:hover){background-color:rgba(0,0,0,0)}html.theme--documenter-dark .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .navbar-item.has-dropdown.is-active .navbar-link{background-color:rgba(0,0,0,0)}}html.theme--documenter-dark .hero.is-fullheight-with-navbar{min-height:calc(100vh - 4rem)}html.theme--documenter-dark .pagination{font-size:1rem;margin:-.25rem}html.theme--documenter-dark .pagination.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.pagination{font-size:.75rem}html.theme--documenter-dark .pagination.is-medium{font-size:1.25rem}html.theme--documenter-dark .pagination.is-large{font-size:1.5rem}html.theme--documenter-dark .pagination.is-rounded .pagination-previous,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.pagination .pagination-previous,html.theme--documenter-dark .pagination.is-rounded .pagination-next,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.pagination .pagination-next{padding-left:1em;padding-right:1em;border-radius:9999px}html.theme--documenter-dark .pagination.is-rounded .pagination-link,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.pagination .pagination-link{border-radius:9999px}html.theme--documenter-dark .pagination,html.theme--documenter-dark .pagination-list{align-items:center;display:flex;justify-content:center;text-align:center}html.theme--documenter-dark .pagination-previous,html.theme--documenter-dark .pagination-next,html.theme--documenter-dark .pagination-link,html.theme--documenter-dark .pagination-ellipsis{font-size:1em;justify-content:center;margin:.25rem;padding-left:.5em;padding-right:.5em;text-align:center}html.theme--documenter-dark .pagination-previous,html.theme--documenter-dark .pagination-next,html.theme--documenter-dark .pagination-link{border-color:#5e6d6f;color:#1abc9c;min-width:2.5em}html.theme--documenter-dark .pagination-previous:hover,html.theme--documenter-dark .pagination-next:hover,html.theme--documenter-dark .pagination-link:hover{border-color:#8c9b9d;color:#1dd2af}html.theme--documenter-dark .pagination-previous:focus,html.theme--documenter-dark .pagination-next:focus,html.theme--documenter-dark .pagination-link:focus{border-color:#8c9b9d}html.theme--documenter-dark .pagination-previous:active,html.theme--documenter-dark .pagination-next:active,html.theme--documenter-dark .pagination-link:active{box-shadow:inset 0 1px 2px rgba(10,10,10,0.2)}html.theme--documenter-dark .pagination-previous[disabled],html.theme--documenter-dark .pagination-previous.is-disabled,html.theme--documenter-dark .pagination-next[disabled],html.theme--documenter-dark .pagination-next.is-disabled,html.theme--documenter-dark .pagination-link[disabled],html.theme--documenter-dark .pagination-link.is-disabled{background-color:#5e6d6f;border-color:#5e6d6f;box-shadow:none;color:#fff;opacity:0.5}html.theme--documenter-dark .pagination-previous,html.theme--documenter-dark .pagination-next{padding-left:.75em;padding-right:.75em;white-space:nowrap}html.theme--documenter-dark .pagination-link.is-current{background-color:#1abc9c;border-color:#1abc9c;color:#fff}html.theme--documenter-dark .pagination-ellipsis{color:#8c9b9d;pointer-events:none}html.theme--documenter-dark .pagination-list{flex-wrap:wrap}html.theme--documenter-dark .pagination-list li{list-style:none}@media screen and (max-width: 768px){html.theme--documenter-dark .pagination{flex-wrap:wrap}html.theme--documenter-dark .pagination-previous,html.theme--documenter-dark .pagination-next{flex-grow:1;flex-shrink:1}html.theme--documenter-dark .pagination-list li{flex-grow:1;flex-shrink:1}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .pagination-list{flex-grow:1;flex-shrink:1;justify-content:flex-start;order:1}html.theme--documenter-dark .pagination-previous,html.theme--documenter-dark .pagination-next,html.theme--documenter-dark .pagination-link,html.theme--documenter-dark .pagination-ellipsis{margin-bottom:0;margin-top:0}html.theme--documenter-dark .pagination-previous{order:2}html.theme--documenter-dark .pagination-next{order:3}html.theme--documenter-dark .pagination{justify-content:space-between;margin-bottom:0;margin-top:0}html.theme--documenter-dark .pagination.is-centered .pagination-previous{order:1}html.theme--documenter-dark .pagination.is-centered .pagination-list{justify-content:center;order:2}html.theme--documenter-dark .pagination.is-centered .pagination-next{order:3}html.theme--documenter-dark .pagination.is-right .pagination-previous{order:1}html.theme--documenter-dark .pagination.is-right .pagination-next{order:2}html.theme--documenter-dark .pagination.is-right .pagination-list{justify-content:flex-end;order:3}}html.theme--documenter-dark .panel{border-radius:8px;box-shadow:#171717;font-size:1rem}html.theme--documenter-dark .panel:not(:last-child){margin-bottom:1.5rem}html.theme--documenter-dark .panel.is-white .panel-heading{background-color:#fff;color:#0a0a0a}html.theme--documenter-dark .panel.is-white .panel-tabs a.is-active{border-bottom-color:#fff}html.theme--documenter-dark .panel.is-white .panel-block.is-active .panel-icon{color:#fff}html.theme--documenter-dark .panel.is-black .panel-heading{background-color:#0a0a0a;color:#fff}html.theme--documenter-dark .panel.is-black .panel-tabs a.is-active{border-bottom-color:#0a0a0a}html.theme--documenter-dark .panel.is-black .panel-block.is-active .panel-icon{color:#0a0a0a}html.theme--documenter-dark .panel.is-light .panel-heading{background-color:#ecf0f1;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .panel.is-light .panel-tabs a.is-active{border-bottom-color:#ecf0f1}html.theme--documenter-dark .panel.is-light .panel-block.is-active .panel-icon{color:#ecf0f1}html.theme--documenter-dark .panel.is-dark .panel-heading,html.theme--documenter-dark .content kbd.panel .panel-heading{background-color:#282f2f;color:#fff}html.theme--documenter-dark .panel.is-dark .panel-tabs a.is-active,html.theme--documenter-dark .content kbd.panel .panel-tabs a.is-active{border-bottom-color:#282f2f}html.theme--documenter-dark .panel.is-dark .panel-block.is-active .panel-icon,html.theme--documenter-dark .content kbd.panel .panel-block.is-active .panel-icon{color:#282f2f}html.theme--documenter-dark .panel.is-primary .panel-heading,html.theme--documenter-dark .docstring>section>a.panel.docs-sourcelink .panel-heading{background-color:#375a7f;color:#fff}html.theme--documenter-dark .panel.is-primary .panel-tabs a.is-active,html.theme--documenter-dark .docstring>section>a.panel.docs-sourcelink .panel-tabs a.is-active{border-bottom-color:#375a7f}html.theme--documenter-dark .panel.is-primary .panel-block.is-active .panel-icon,html.theme--documenter-dark .docstring>section>a.panel.docs-sourcelink .panel-block.is-active .panel-icon{color:#375a7f}html.theme--documenter-dark .panel.is-link .panel-heading{background-color:#1abc9c;color:#fff}html.theme--documenter-dark .panel.is-link .panel-tabs a.is-active{border-bottom-color:#1abc9c}html.theme--documenter-dark .panel.is-link .panel-block.is-active .panel-icon{color:#1abc9c}html.theme--documenter-dark .panel.is-info .panel-heading{background-color:#024c7d;color:#fff}html.theme--documenter-dark .panel.is-info .panel-tabs a.is-active{border-bottom-color:#024c7d}html.theme--documenter-dark .panel.is-info .panel-block.is-active .panel-icon{color:#024c7d}html.theme--documenter-dark .panel.is-success .panel-heading{background-color:#008438;color:#fff}html.theme--documenter-dark .panel.is-success .panel-tabs a.is-active{border-bottom-color:#008438}html.theme--documenter-dark .panel.is-success .panel-block.is-active .panel-icon{color:#008438}html.theme--documenter-dark .panel.is-warning .panel-heading{background-color:#ad8100;color:#fff}html.theme--documenter-dark .panel.is-warning .panel-tabs a.is-active{border-bottom-color:#ad8100}html.theme--documenter-dark .panel.is-warning .panel-block.is-active .panel-icon{color:#ad8100}html.theme--documenter-dark .panel.is-danger .panel-heading{background-color:#9e1b0d;color:#fff}html.theme--documenter-dark .panel.is-danger .panel-tabs a.is-active{border-bottom-color:#9e1b0d}html.theme--documenter-dark .panel.is-danger .panel-block.is-active .panel-icon{color:#9e1b0d}html.theme--documenter-dark .panel-tabs:not(:last-child),html.theme--documenter-dark .panel-block:not(:last-child){border-bottom:1px solid #ededed}html.theme--documenter-dark .panel-heading{background-color:#343c3d;border-radius:8px 8px 0 0;color:#f2f2f2;font-size:1.25em;font-weight:700;line-height:1.25;padding:0.75em 1em}html.theme--documenter-dark .panel-tabs{align-items:flex-end;display:flex;font-size:.875em;justify-content:center}html.theme--documenter-dark .panel-tabs a{border-bottom:1px solid #5e6d6f;margin-bottom:-1px;padding:0.5em}html.theme--documenter-dark .panel-tabs a.is-active{border-bottom-color:#343c3d;color:#17a689}html.theme--documenter-dark .panel-list a{color:#fff}html.theme--documenter-dark .panel-list a:hover{color:#1abc9c}html.theme--documenter-dark .panel-block{align-items:center;color:#f2f2f2;display:flex;justify-content:flex-start;padding:0.5em 0.75em}html.theme--documenter-dark .panel-block input[type="checkbox"]{margin-right:.75em}html.theme--documenter-dark .panel-block>.control{flex-grow:1;flex-shrink:1;width:100%}html.theme--documenter-dark .panel-block.is-wrapped{flex-wrap:wrap}html.theme--documenter-dark .panel-block.is-active{border-left-color:#1abc9c;color:#17a689}html.theme--documenter-dark .panel-block.is-active .panel-icon{color:#1abc9c}html.theme--documenter-dark .panel-block:last-child{border-bottom-left-radius:8px;border-bottom-right-radius:8px}html.theme--documenter-dark a.panel-block,html.theme--documenter-dark label.panel-block{cursor:pointer}html.theme--documenter-dark a.panel-block:hover,html.theme--documenter-dark label.panel-block:hover{background-color:#282f2f}html.theme--documenter-dark .panel-icon{display:inline-block;font-size:14px;height:1em;line-height:1em;text-align:center;vertical-align:top;width:1em;color:#fff;margin-right:.75em}html.theme--documenter-dark .panel-icon .fa{font-size:inherit;line-height:inherit}html.theme--documenter-dark .tabs{-webkit-overflow-scrolling:touch;align-items:stretch;display:flex;font-size:1rem;justify-content:space-between;overflow:hidden;overflow-x:auto;white-space:nowrap}html.theme--documenter-dark .tabs a{align-items:center;border-bottom-color:#5e6d6f;border-bottom-style:solid;border-bottom-width:1px;color:#fff;display:flex;justify-content:center;margin-bottom:-1px;padding:0.5em 1em;vertical-align:top}html.theme--documenter-dark .tabs a:hover{border-bottom-color:#f2f2f2;color:#f2f2f2}html.theme--documenter-dark .tabs li{display:block}html.theme--documenter-dark .tabs li.is-active a{border-bottom-color:#1abc9c;color:#1abc9c}html.theme--documenter-dark .tabs ul{align-items:center;border-bottom-color:#5e6d6f;border-bottom-style:solid;border-bottom-width:1px;display:flex;flex-grow:1;flex-shrink:0;justify-content:flex-start}html.theme--documenter-dark .tabs ul.is-left{padding-right:0.75em}html.theme--documenter-dark .tabs ul.is-center{flex:none;justify-content:center;padding-left:0.75em;padding-right:0.75em}html.theme--documenter-dark .tabs ul.is-right{justify-content:flex-end;padding-left:0.75em}html.theme--documenter-dark .tabs .icon:first-child{margin-right:.5em}html.theme--documenter-dark .tabs .icon:last-child{margin-left:.5em}html.theme--documenter-dark .tabs.is-centered ul{justify-content:center}html.theme--documenter-dark .tabs.is-right ul{justify-content:flex-end}html.theme--documenter-dark .tabs.is-boxed a{border:1px solid transparent;border-radius:.4em .4em 0 0}html.theme--documenter-dark .tabs.is-boxed a:hover{background-color:#282f2f;border-bottom-color:#5e6d6f}html.theme--documenter-dark .tabs.is-boxed li.is-active a{background-color:#fff;border-color:#5e6d6f;border-bottom-color:rgba(0,0,0,0) !important}html.theme--documenter-dark .tabs.is-fullwidth li{flex-grow:1;flex-shrink:0}html.theme--documenter-dark .tabs.is-toggle a{border-color:#5e6d6f;border-style:solid;border-width:1px;margin-bottom:0;position:relative}html.theme--documenter-dark .tabs.is-toggle a:hover{background-color:#282f2f;border-color:#8c9b9d;z-index:2}html.theme--documenter-dark .tabs.is-toggle li+li{margin-left:-1px}html.theme--documenter-dark .tabs.is-toggle li:first-child a{border-top-left-radius:.4em;border-bottom-left-radius:.4em}html.theme--documenter-dark .tabs.is-toggle li:last-child a{border-top-right-radius:.4em;border-bottom-right-radius:.4em}html.theme--documenter-dark .tabs.is-toggle li.is-active a{background-color:#1abc9c;border-color:#1abc9c;color:#fff;z-index:1}html.theme--documenter-dark .tabs.is-toggle ul{border-bottom:none}html.theme--documenter-dark .tabs.is-toggle.is-toggle-rounded li:first-child a{border-bottom-left-radius:9999px;border-top-left-radius:9999px;padding-left:1.25em}html.theme--documenter-dark .tabs.is-toggle.is-toggle-rounded li:last-child a{border-bottom-right-radius:9999px;border-top-right-radius:9999px;padding-right:1.25em}html.theme--documenter-dark .tabs.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.tabs{font-size:.75rem}html.theme--documenter-dark .tabs.is-medium{font-size:1.25rem}html.theme--documenter-dark .tabs.is-large{font-size:1.5rem}html.theme--documenter-dark .column{display:block;flex-basis:0;flex-grow:1;flex-shrink:1;padding:.75rem}.columns.is-mobile>html.theme--documenter-dark .column.is-narrow{flex:none;width:unset}.columns.is-mobile>html.theme--documenter-dark .column.is-full{flex:none;width:100%}.columns.is-mobile>html.theme--documenter-dark .column.is-three-quarters{flex:none;width:75%}.columns.is-mobile>html.theme--documenter-dark .column.is-two-thirds{flex:none;width:66.6666%}.columns.is-mobile>html.theme--documenter-dark .column.is-half{flex:none;width:50%}.columns.is-mobile>html.theme--documenter-dark .column.is-one-third{flex:none;width:33.3333%}.columns.is-mobile>html.theme--documenter-dark .column.is-one-quarter{flex:none;width:25%}.columns.is-mobile>html.theme--documenter-dark .column.is-one-fifth{flex:none;width:20%}.columns.is-mobile>html.theme--documenter-dark .column.is-two-fifths{flex:none;width:40%}.columns.is-mobile>html.theme--documenter-dark .column.is-three-fifths{flex:none;width:60%}.columns.is-mobile>html.theme--documenter-dark .column.is-four-fifths{flex:none;width:80%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-three-quarters{margin-left:75%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-two-thirds{margin-left:66.6666%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-half{margin-left:50%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-one-third{margin-left:33.3333%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-one-quarter{margin-left:25%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-one-fifth{margin-left:20%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-two-fifths{margin-left:40%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-three-fifths{margin-left:60%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-four-fifths{margin-left:80%}.columns.is-mobile>html.theme--documenter-dark .column.is-0{flex:none;width:0%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-0{margin-left:0%}.columns.is-mobile>html.theme--documenter-dark .column.is-1{flex:none;width:8.33333337%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-1{margin-left:8.33333337%}.columns.is-mobile>html.theme--documenter-dark .column.is-2{flex:none;width:16.66666674%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-2{margin-left:16.66666674%}.columns.is-mobile>html.theme--documenter-dark .column.is-3{flex:none;width:25%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-3{margin-left:25%}.columns.is-mobile>html.theme--documenter-dark .column.is-4{flex:none;width:33.33333337%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-4{margin-left:33.33333337%}.columns.is-mobile>html.theme--documenter-dark .column.is-5{flex:none;width:41.66666674%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-5{margin-left:41.66666674%}.columns.is-mobile>html.theme--documenter-dark .column.is-6{flex:none;width:50%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-6{margin-left:50%}.columns.is-mobile>html.theme--documenter-dark .column.is-7{flex:none;width:58.33333337%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-7{margin-left:58.33333337%}.columns.is-mobile>html.theme--documenter-dark .column.is-8{flex:none;width:66.66666674%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-8{margin-left:66.66666674%}.columns.is-mobile>html.theme--documenter-dark .column.is-9{flex:none;width:75%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-9{margin-left:75%}.columns.is-mobile>html.theme--documenter-dark .column.is-10{flex:none;width:83.33333337%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-10{margin-left:83.33333337%}.columns.is-mobile>html.theme--documenter-dark .column.is-11{flex:none;width:91.66666674%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-11{margin-left:91.66666674%}.columns.is-mobile>html.theme--documenter-dark .column.is-12{flex:none;width:100%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-12{margin-left:100%}@media screen and (max-width: 768px){html.theme--documenter-dark .column.is-narrow-mobile{flex:none;width:unset}html.theme--documenter-dark .column.is-full-mobile{flex:none;width:100%}html.theme--documenter-dark .column.is-three-quarters-mobile{flex:none;width:75%}html.theme--documenter-dark .column.is-two-thirds-mobile{flex:none;width:66.6666%}html.theme--documenter-dark .column.is-half-mobile{flex:none;width:50%}html.theme--documenter-dark .column.is-one-third-mobile{flex:none;width:33.3333%}html.theme--documenter-dark .column.is-one-quarter-mobile{flex:none;width:25%}html.theme--documenter-dark .column.is-one-fifth-mobile{flex:none;width:20%}html.theme--documenter-dark .column.is-two-fifths-mobile{flex:none;width:40%}html.theme--documenter-dark .column.is-three-fifths-mobile{flex:none;width:60%}html.theme--documenter-dark .column.is-four-fifths-mobile{flex:none;width:80%}html.theme--documenter-dark .column.is-offset-three-quarters-mobile{margin-left:75%}html.theme--documenter-dark .column.is-offset-two-thirds-mobile{margin-left:66.6666%}html.theme--documenter-dark .column.is-offset-half-mobile{margin-left:50%}html.theme--documenter-dark .column.is-offset-one-third-mobile{margin-left:33.3333%}html.theme--documenter-dark .column.is-offset-one-quarter-mobile{margin-left:25%}html.theme--documenter-dark .column.is-offset-one-fifth-mobile{margin-left:20%}html.theme--documenter-dark .column.is-offset-two-fifths-mobile{margin-left:40%}html.theme--documenter-dark .column.is-offset-three-fifths-mobile{margin-left:60%}html.theme--documenter-dark .column.is-offset-four-fifths-mobile{margin-left:80%}html.theme--documenter-dark .column.is-0-mobile{flex:none;width:0%}html.theme--documenter-dark .column.is-offset-0-mobile{margin-left:0%}html.theme--documenter-dark .column.is-1-mobile{flex:none;width:8.33333337%}html.theme--documenter-dark .column.is-offset-1-mobile{margin-left:8.33333337%}html.theme--documenter-dark .column.is-2-mobile{flex:none;width:16.66666674%}html.theme--documenter-dark .column.is-offset-2-mobile{margin-left:16.66666674%}html.theme--documenter-dark .column.is-3-mobile{flex:none;width:25%}html.theme--documenter-dark .column.is-offset-3-mobile{margin-left:25%}html.theme--documenter-dark .column.is-4-mobile{flex:none;width:33.33333337%}html.theme--documenter-dark .column.is-offset-4-mobile{margin-left:33.33333337%}html.theme--documenter-dark .column.is-5-mobile{flex:none;width:41.66666674%}html.theme--documenter-dark .column.is-offset-5-mobile{margin-left:41.66666674%}html.theme--documenter-dark .column.is-6-mobile{flex:none;width:50%}html.theme--documenter-dark .column.is-offset-6-mobile{margin-left:50%}html.theme--documenter-dark .column.is-7-mobile{flex:none;width:58.33333337%}html.theme--documenter-dark .column.is-offset-7-mobile{margin-left:58.33333337%}html.theme--documenter-dark .column.is-8-mobile{flex:none;width:66.66666674%}html.theme--documenter-dark .column.is-offset-8-mobile{margin-left:66.66666674%}html.theme--documenter-dark .column.is-9-mobile{flex:none;width:75%}html.theme--documenter-dark .column.is-offset-9-mobile{margin-left:75%}html.theme--documenter-dark .column.is-10-mobile{flex:none;width:83.33333337%}html.theme--documenter-dark .column.is-offset-10-mobile{margin-left:83.33333337%}html.theme--documenter-dark .column.is-11-mobile{flex:none;width:91.66666674%}html.theme--documenter-dark .column.is-offset-11-mobile{margin-left:91.66666674%}html.theme--documenter-dark .column.is-12-mobile{flex:none;width:100%}html.theme--documenter-dark .column.is-offset-12-mobile{margin-left:100%}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .column.is-narrow,html.theme--documenter-dark .column.is-narrow-tablet{flex:none;width:unset}html.theme--documenter-dark .column.is-full,html.theme--documenter-dark .column.is-full-tablet{flex:none;width:100%}html.theme--documenter-dark .column.is-three-quarters,html.theme--documenter-dark .column.is-three-quarters-tablet{flex:none;width:75%}html.theme--documenter-dark .column.is-two-thirds,html.theme--documenter-dark .column.is-two-thirds-tablet{flex:none;width:66.6666%}html.theme--documenter-dark .column.is-half,html.theme--documenter-dark .column.is-half-tablet{flex:none;width:50%}html.theme--documenter-dark .column.is-one-third,html.theme--documenter-dark .column.is-one-third-tablet{flex:none;width:33.3333%}html.theme--documenter-dark .column.is-one-quarter,html.theme--documenter-dark .column.is-one-quarter-tablet{flex:none;width:25%}html.theme--documenter-dark .column.is-one-fifth,html.theme--documenter-dark .column.is-one-fifth-tablet{flex:none;width:20%}html.theme--documenter-dark .column.is-two-fifths,html.theme--documenter-dark .column.is-two-fifths-tablet{flex:none;width:40%}html.theme--documenter-dark .column.is-three-fifths,html.theme--documenter-dark .column.is-three-fifths-tablet{flex:none;width:60%}html.theme--documenter-dark .column.is-four-fifths,html.theme--documenter-dark .column.is-four-fifths-tablet{flex:none;width:80%}html.theme--documenter-dark .column.is-offset-three-quarters,html.theme--documenter-dark .column.is-offset-three-quarters-tablet{margin-left:75%}html.theme--documenter-dark .column.is-offset-two-thirds,html.theme--documenter-dark .column.is-offset-two-thirds-tablet{margin-left:66.6666%}html.theme--documenter-dark .column.is-offset-half,html.theme--documenter-dark .column.is-offset-half-tablet{margin-left:50%}html.theme--documenter-dark .column.is-offset-one-third,html.theme--documenter-dark .column.is-offset-one-third-tablet{margin-left:33.3333%}html.theme--documenter-dark .column.is-offset-one-quarter,html.theme--documenter-dark .column.is-offset-one-quarter-tablet{margin-left:25%}html.theme--documenter-dark .column.is-offset-one-fifth,html.theme--documenter-dark .column.is-offset-one-fifth-tablet{margin-left:20%}html.theme--documenter-dark .column.is-offset-two-fifths,html.theme--documenter-dark .column.is-offset-two-fifths-tablet{margin-left:40%}html.theme--documenter-dark .column.is-offset-three-fifths,html.theme--documenter-dark .column.is-offset-three-fifths-tablet{margin-left:60%}html.theme--documenter-dark .column.is-offset-four-fifths,html.theme--documenter-dark .column.is-offset-four-fifths-tablet{margin-left:80%}html.theme--documenter-dark .column.is-0,html.theme--documenter-dark .column.is-0-tablet{flex:none;width:0%}html.theme--documenter-dark .column.is-offset-0,html.theme--documenter-dark .column.is-offset-0-tablet{margin-left:0%}html.theme--documenter-dark .column.is-1,html.theme--documenter-dark .column.is-1-tablet{flex:none;width:8.33333337%}html.theme--documenter-dark .column.is-offset-1,html.theme--documenter-dark .column.is-offset-1-tablet{margin-left:8.33333337%}html.theme--documenter-dark .column.is-2,html.theme--documenter-dark .column.is-2-tablet{flex:none;width:16.66666674%}html.theme--documenter-dark .column.is-offset-2,html.theme--documenter-dark .column.is-offset-2-tablet{margin-left:16.66666674%}html.theme--documenter-dark .column.is-3,html.theme--documenter-dark .column.is-3-tablet{flex:none;width:25%}html.theme--documenter-dark .column.is-offset-3,html.theme--documenter-dark .column.is-offset-3-tablet{margin-left:25%}html.theme--documenter-dark .column.is-4,html.theme--documenter-dark .column.is-4-tablet{flex:none;width:33.33333337%}html.theme--documenter-dark .column.is-offset-4,html.theme--documenter-dark .column.is-offset-4-tablet{margin-left:33.33333337%}html.theme--documenter-dark .column.is-5,html.theme--documenter-dark .column.is-5-tablet{flex:none;width:41.66666674%}html.theme--documenter-dark .column.is-offset-5,html.theme--documenter-dark .column.is-offset-5-tablet{margin-left:41.66666674%}html.theme--documenter-dark .column.is-6,html.theme--documenter-dark .column.is-6-tablet{flex:none;width:50%}html.theme--documenter-dark .column.is-offset-6,html.theme--documenter-dark .column.is-offset-6-tablet{margin-left:50%}html.theme--documenter-dark .column.is-7,html.theme--documenter-dark .column.is-7-tablet{flex:none;width:58.33333337%}html.theme--documenter-dark .column.is-offset-7,html.theme--documenter-dark .column.is-offset-7-tablet{margin-left:58.33333337%}html.theme--documenter-dark .column.is-8,html.theme--documenter-dark .column.is-8-tablet{flex:none;width:66.66666674%}html.theme--documenter-dark .column.is-offset-8,html.theme--documenter-dark .column.is-offset-8-tablet{margin-left:66.66666674%}html.theme--documenter-dark .column.is-9,html.theme--documenter-dark .column.is-9-tablet{flex:none;width:75%}html.theme--documenter-dark .column.is-offset-9,html.theme--documenter-dark .column.is-offset-9-tablet{margin-left:75%}html.theme--documenter-dark .column.is-10,html.theme--documenter-dark .column.is-10-tablet{flex:none;width:83.33333337%}html.theme--documenter-dark .column.is-offset-10,html.theme--documenter-dark .column.is-offset-10-tablet{margin-left:83.33333337%}html.theme--documenter-dark .column.is-11,html.theme--documenter-dark .column.is-11-tablet{flex:none;width:91.66666674%}html.theme--documenter-dark .column.is-offset-11,html.theme--documenter-dark .column.is-offset-11-tablet{margin-left:91.66666674%}html.theme--documenter-dark .column.is-12,html.theme--documenter-dark .column.is-12-tablet{flex:none;width:100%}html.theme--documenter-dark .column.is-offset-12,html.theme--documenter-dark .column.is-offset-12-tablet{margin-left:100%}}@media screen and (max-width: 1055px){html.theme--documenter-dark .column.is-narrow-touch{flex:none;width:unset}html.theme--documenter-dark .column.is-full-touch{flex:none;width:100%}html.theme--documenter-dark .column.is-three-quarters-touch{flex:none;width:75%}html.theme--documenter-dark .column.is-two-thirds-touch{flex:none;width:66.6666%}html.theme--documenter-dark .column.is-half-touch{flex:none;width:50%}html.theme--documenter-dark .column.is-one-third-touch{flex:none;width:33.3333%}html.theme--documenter-dark .column.is-one-quarter-touch{flex:none;width:25%}html.theme--documenter-dark .column.is-one-fifth-touch{flex:none;width:20%}html.theme--documenter-dark .column.is-two-fifths-touch{flex:none;width:40%}html.theme--documenter-dark .column.is-three-fifths-touch{flex:none;width:60%}html.theme--documenter-dark .column.is-four-fifths-touch{flex:none;width:80%}html.theme--documenter-dark .column.is-offset-three-quarters-touch{margin-left:75%}html.theme--documenter-dark .column.is-offset-two-thirds-touch{margin-left:66.6666%}html.theme--documenter-dark .column.is-offset-half-touch{margin-left:50%}html.theme--documenter-dark .column.is-offset-one-third-touch{margin-left:33.3333%}html.theme--documenter-dark .column.is-offset-one-quarter-touch{margin-left:25%}html.theme--documenter-dark .column.is-offset-one-fifth-touch{margin-left:20%}html.theme--documenter-dark .column.is-offset-two-fifths-touch{margin-left:40%}html.theme--documenter-dark .column.is-offset-three-fifths-touch{margin-left:60%}html.theme--documenter-dark .column.is-offset-four-fifths-touch{margin-left:80%}html.theme--documenter-dark .column.is-0-touch{flex:none;width:0%}html.theme--documenter-dark .column.is-offset-0-touch{margin-left:0%}html.theme--documenter-dark .column.is-1-touch{flex:none;width:8.33333337%}html.theme--documenter-dark .column.is-offset-1-touch{margin-left:8.33333337%}html.theme--documenter-dark .column.is-2-touch{flex:none;width:16.66666674%}html.theme--documenter-dark .column.is-offset-2-touch{margin-left:16.66666674%}html.theme--documenter-dark .column.is-3-touch{flex:none;width:25%}html.theme--documenter-dark .column.is-offset-3-touch{margin-left:25%}html.theme--documenter-dark .column.is-4-touch{flex:none;width:33.33333337%}html.theme--documenter-dark .column.is-offset-4-touch{margin-left:33.33333337%}html.theme--documenter-dark .column.is-5-touch{flex:none;width:41.66666674%}html.theme--documenter-dark .column.is-offset-5-touch{margin-left:41.66666674%}html.theme--documenter-dark .column.is-6-touch{flex:none;width:50%}html.theme--documenter-dark .column.is-offset-6-touch{margin-left:50%}html.theme--documenter-dark .column.is-7-touch{flex:none;width:58.33333337%}html.theme--documenter-dark .column.is-offset-7-touch{margin-left:58.33333337%}html.theme--documenter-dark .column.is-8-touch{flex:none;width:66.66666674%}html.theme--documenter-dark .column.is-offset-8-touch{margin-left:66.66666674%}html.theme--documenter-dark .column.is-9-touch{flex:none;width:75%}html.theme--documenter-dark .column.is-offset-9-touch{margin-left:75%}html.theme--documenter-dark .column.is-10-touch{flex:none;width:83.33333337%}html.theme--documenter-dark .column.is-offset-10-touch{margin-left:83.33333337%}html.theme--documenter-dark .column.is-11-touch{flex:none;width:91.66666674%}html.theme--documenter-dark .column.is-offset-11-touch{margin-left:91.66666674%}html.theme--documenter-dark .column.is-12-touch{flex:none;width:100%}html.theme--documenter-dark .column.is-offset-12-touch{margin-left:100%}}@media screen and (min-width: 1056px){html.theme--documenter-dark .column.is-narrow-desktop{flex:none;width:unset}html.theme--documenter-dark .column.is-full-desktop{flex:none;width:100%}html.theme--documenter-dark .column.is-three-quarters-desktop{flex:none;width:75%}html.theme--documenter-dark .column.is-two-thirds-desktop{flex:none;width:66.6666%}html.theme--documenter-dark .column.is-half-desktop{flex:none;width:50%}html.theme--documenter-dark .column.is-one-third-desktop{flex:none;width:33.3333%}html.theme--documenter-dark .column.is-one-quarter-desktop{flex:none;width:25%}html.theme--documenter-dark .column.is-one-fifth-desktop{flex:none;width:20%}html.theme--documenter-dark .column.is-two-fifths-desktop{flex:none;width:40%}html.theme--documenter-dark .column.is-three-fifths-desktop{flex:none;width:60%}html.theme--documenter-dark .column.is-four-fifths-desktop{flex:none;width:80%}html.theme--documenter-dark .column.is-offset-three-quarters-desktop{margin-left:75%}html.theme--documenter-dark .column.is-offset-two-thirds-desktop{margin-left:66.6666%}html.theme--documenter-dark .column.is-offset-half-desktop{margin-left:50%}html.theme--documenter-dark .column.is-offset-one-third-desktop{margin-left:33.3333%}html.theme--documenter-dark .column.is-offset-one-quarter-desktop{margin-left:25%}html.theme--documenter-dark .column.is-offset-one-fifth-desktop{margin-left:20%}html.theme--documenter-dark .column.is-offset-two-fifths-desktop{margin-left:40%}html.theme--documenter-dark .column.is-offset-three-fifths-desktop{margin-left:60%}html.theme--documenter-dark .column.is-offset-four-fifths-desktop{margin-left:80%}html.theme--documenter-dark .column.is-0-desktop{flex:none;width:0%}html.theme--documenter-dark .column.is-offset-0-desktop{margin-left:0%}html.theme--documenter-dark .column.is-1-desktop{flex:none;width:8.33333337%}html.theme--documenter-dark .column.is-offset-1-desktop{margin-left:8.33333337%}html.theme--documenter-dark .column.is-2-desktop{flex:none;width:16.66666674%}html.theme--documenter-dark .column.is-offset-2-desktop{margin-left:16.66666674%}html.theme--documenter-dark .column.is-3-desktop{flex:none;width:25%}html.theme--documenter-dark .column.is-offset-3-desktop{margin-left:25%}html.theme--documenter-dark .column.is-4-desktop{flex:none;width:33.33333337%}html.theme--documenter-dark .column.is-offset-4-desktop{margin-left:33.33333337%}html.theme--documenter-dark .column.is-5-desktop{flex:none;width:41.66666674%}html.theme--documenter-dark .column.is-offset-5-desktop{margin-left:41.66666674%}html.theme--documenter-dark .column.is-6-desktop{flex:none;width:50%}html.theme--documenter-dark .column.is-offset-6-desktop{margin-left:50%}html.theme--documenter-dark .column.is-7-desktop{flex:none;width:58.33333337%}html.theme--documenter-dark .column.is-offset-7-desktop{margin-left:58.33333337%}html.theme--documenter-dark .column.is-8-desktop{flex:none;width:66.66666674%}html.theme--documenter-dark .column.is-offset-8-desktop{margin-left:66.66666674%}html.theme--documenter-dark .column.is-9-desktop{flex:none;width:75%}html.theme--documenter-dark .column.is-offset-9-desktop{margin-left:75%}html.theme--documenter-dark .column.is-10-desktop{flex:none;width:83.33333337%}html.theme--documenter-dark .column.is-offset-10-desktop{margin-left:83.33333337%}html.theme--documenter-dark .column.is-11-desktop{flex:none;width:91.66666674%}html.theme--documenter-dark .column.is-offset-11-desktop{margin-left:91.66666674%}html.theme--documenter-dark .column.is-12-desktop{flex:none;width:100%}html.theme--documenter-dark .column.is-offset-12-desktop{margin-left:100%}}@media screen and (min-width: 1216px){html.theme--documenter-dark .column.is-narrow-widescreen{flex:none;width:unset}html.theme--documenter-dark .column.is-full-widescreen{flex:none;width:100%}html.theme--documenter-dark .column.is-three-quarters-widescreen{flex:none;width:75%}html.theme--documenter-dark .column.is-two-thirds-widescreen{flex:none;width:66.6666%}html.theme--documenter-dark .column.is-half-widescreen{flex:none;width:50%}html.theme--documenter-dark .column.is-one-third-widescreen{flex:none;width:33.3333%}html.theme--documenter-dark .column.is-one-quarter-widescreen{flex:none;width:25%}html.theme--documenter-dark .column.is-one-fifth-widescreen{flex:none;width:20%}html.theme--documenter-dark .column.is-two-fifths-widescreen{flex:none;width:40%}html.theme--documenter-dark .column.is-three-fifths-widescreen{flex:none;width:60%}html.theme--documenter-dark .column.is-four-fifths-widescreen{flex:none;width:80%}html.theme--documenter-dark .column.is-offset-three-quarters-widescreen{margin-left:75%}html.theme--documenter-dark .column.is-offset-two-thirds-widescreen{margin-left:66.6666%}html.theme--documenter-dark .column.is-offset-half-widescreen{margin-left:50%}html.theme--documenter-dark .column.is-offset-one-third-widescreen{margin-left:33.3333%}html.theme--documenter-dark .column.is-offset-one-quarter-widescreen{margin-left:25%}html.theme--documenter-dark .column.is-offset-one-fifth-widescreen{margin-left:20%}html.theme--documenter-dark .column.is-offset-two-fifths-widescreen{margin-left:40%}html.theme--documenter-dark .column.is-offset-three-fifths-widescreen{margin-left:60%}html.theme--documenter-dark .column.is-offset-four-fifths-widescreen{margin-left:80%}html.theme--documenter-dark .column.is-0-widescreen{flex:none;width:0%}html.theme--documenter-dark .column.is-offset-0-widescreen{margin-left:0%}html.theme--documenter-dark .column.is-1-widescreen{flex:none;width:8.33333337%}html.theme--documenter-dark .column.is-offset-1-widescreen{margin-left:8.33333337%}html.theme--documenter-dark .column.is-2-widescreen{flex:none;width:16.66666674%}html.theme--documenter-dark .column.is-offset-2-widescreen{margin-left:16.66666674%}html.theme--documenter-dark .column.is-3-widescreen{flex:none;width:25%}html.theme--documenter-dark .column.is-offset-3-widescreen{margin-left:25%}html.theme--documenter-dark .column.is-4-widescreen{flex:none;width:33.33333337%}html.theme--documenter-dark .column.is-offset-4-widescreen{margin-left:33.33333337%}html.theme--documenter-dark .column.is-5-widescreen{flex:none;width:41.66666674%}html.theme--documenter-dark .column.is-offset-5-widescreen{margin-left:41.66666674%}html.theme--documenter-dark .column.is-6-widescreen{flex:none;width:50%}html.theme--documenter-dark .column.is-offset-6-widescreen{margin-left:50%}html.theme--documenter-dark .column.is-7-widescreen{flex:none;width:58.33333337%}html.theme--documenter-dark .column.is-offset-7-widescreen{margin-left:58.33333337%}html.theme--documenter-dark .column.is-8-widescreen{flex:none;width:66.66666674%}html.theme--documenter-dark .column.is-offset-8-widescreen{margin-left:66.66666674%}html.theme--documenter-dark .column.is-9-widescreen{flex:none;width:75%}html.theme--documenter-dark .column.is-offset-9-widescreen{margin-left:75%}html.theme--documenter-dark .column.is-10-widescreen{flex:none;width:83.33333337%}html.theme--documenter-dark .column.is-offset-10-widescreen{margin-left:83.33333337%}html.theme--documenter-dark .column.is-11-widescreen{flex:none;width:91.66666674%}html.theme--documenter-dark .column.is-offset-11-widescreen{margin-left:91.66666674%}html.theme--documenter-dark .column.is-12-widescreen{flex:none;width:100%}html.theme--documenter-dark .column.is-offset-12-widescreen{margin-left:100%}}@media screen and (min-width: 1408px){html.theme--documenter-dark .column.is-narrow-fullhd{flex:none;width:unset}html.theme--documenter-dark .column.is-full-fullhd{flex:none;width:100%}html.theme--documenter-dark .column.is-three-quarters-fullhd{flex:none;width:75%}html.theme--documenter-dark .column.is-two-thirds-fullhd{flex:none;width:66.6666%}html.theme--documenter-dark .column.is-half-fullhd{flex:none;width:50%}html.theme--documenter-dark .column.is-one-third-fullhd{flex:none;width:33.3333%}html.theme--documenter-dark .column.is-one-quarter-fullhd{flex:none;width:25%}html.theme--documenter-dark .column.is-one-fifth-fullhd{flex:none;width:20%}html.theme--documenter-dark .column.is-two-fifths-fullhd{flex:none;width:40%}html.theme--documenter-dark .column.is-three-fifths-fullhd{flex:none;width:60%}html.theme--documenter-dark .column.is-four-fifths-fullhd{flex:none;width:80%}html.theme--documenter-dark .column.is-offset-three-quarters-fullhd{margin-left:75%}html.theme--documenter-dark .column.is-offset-two-thirds-fullhd{margin-left:66.6666%}html.theme--documenter-dark .column.is-offset-half-fullhd{margin-left:50%}html.theme--documenter-dark .column.is-offset-one-third-fullhd{margin-left:33.3333%}html.theme--documenter-dark .column.is-offset-one-quarter-fullhd{margin-left:25%}html.theme--documenter-dark .column.is-offset-one-fifth-fullhd{margin-left:20%}html.theme--documenter-dark .column.is-offset-two-fifths-fullhd{margin-left:40%}html.theme--documenter-dark .column.is-offset-three-fifths-fullhd{margin-left:60%}html.theme--documenter-dark .column.is-offset-four-fifths-fullhd{margin-left:80%}html.theme--documenter-dark .column.is-0-fullhd{flex:none;width:0%}html.theme--documenter-dark .column.is-offset-0-fullhd{margin-left:0%}html.theme--documenter-dark .column.is-1-fullhd{flex:none;width:8.33333337%}html.theme--documenter-dark .column.is-offset-1-fullhd{margin-left:8.33333337%}html.theme--documenter-dark .column.is-2-fullhd{flex:none;width:16.66666674%}html.theme--documenter-dark .column.is-offset-2-fullhd{margin-left:16.66666674%}html.theme--documenter-dark .column.is-3-fullhd{flex:none;width:25%}html.theme--documenter-dark .column.is-offset-3-fullhd{margin-left:25%}html.theme--documenter-dark .column.is-4-fullhd{flex:none;width:33.33333337%}html.theme--documenter-dark .column.is-offset-4-fullhd{margin-left:33.33333337%}html.theme--documenter-dark .column.is-5-fullhd{flex:none;width:41.66666674%}html.theme--documenter-dark .column.is-offset-5-fullhd{margin-left:41.66666674%}html.theme--documenter-dark .column.is-6-fullhd{flex:none;width:50%}html.theme--documenter-dark .column.is-offset-6-fullhd{margin-left:50%}html.theme--documenter-dark .column.is-7-fullhd{flex:none;width:58.33333337%}html.theme--documenter-dark .column.is-offset-7-fullhd{margin-left:58.33333337%}html.theme--documenter-dark .column.is-8-fullhd{flex:none;width:66.66666674%}html.theme--documenter-dark .column.is-offset-8-fullhd{margin-left:66.66666674%}html.theme--documenter-dark .column.is-9-fullhd{flex:none;width:75%}html.theme--documenter-dark .column.is-offset-9-fullhd{margin-left:75%}html.theme--documenter-dark .column.is-10-fullhd{flex:none;width:83.33333337%}html.theme--documenter-dark .column.is-offset-10-fullhd{margin-left:83.33333337%}html.theme--documenter-dark .column.is-11-fullhd{flex:none;width:91.66666674%}html.theme--documenter-dark .column.is-offset-11-fullhd{margin-left:91.66666674%}html.theme--documenter-dark .column.is-12-fullhd{flex:none;width:100%}html.theme--documenter-dark .column.is-offset-12-fullhd{margin-left:100%}}html.theme--documenter-dark .columns{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}html.theme--documenter-dark .columns:last-child{margin-bottom:-.75rem}html.theme--documenter-dark .columns:not(:last-child){margin-bottom:calc(1.5rem - .75rem)}html.theme--documenter-dark .columns.is-centered{justify-content:center}html.theme--documenter-dark .columns.is-gapless{margin-left:0;margin-right:0;margin-top:0}html.theme--documenter-dark .columns.is-gapless>.column{margin:0;padding:0 !important}html.theme--documenter-dark .columns.is-gapless:not(:last-child){margin-bottom:1.5rem}html.theme--documenter-dark .columns.is-gapless:last-child{margin-bottom:0}html.theme--documenter-dark .columns.is-mobile{display:flex}html.theme--documenter-dark .columns.is-multiline{flex-wrap:wrap}html.theme--documenter-dark .columns.is-vcentered{align-items:center}@media screen and (min-width: 769px),print{html.theme--documenter-dark .columns:not(.is-desktop){display:flex}}@media screen and (min-width: 1056px){html.theme--documenter-dark .columns.is-desktop{display:flex}}html.theme--documenter-dark .columns.is-variable{--columnGap: 0.75rem;margin-left:calc(-1 * var(--columnGap));margin-right:calc(-1 * var(--columnGap))}html.theme--documenter-dark .columns.is-variable>.column{padding-left:var(--columnGap);padding-right:var(--columnGap)}html.theme--documenter-dark .columns.is-variable.is-0{--columnGap: 0rem}@media screen and (max-width: 768px){html.theme--documenter-dark .columns.is-variable.is-0-mobile{--columnGap: 0rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .columns.is-variable.is-0-tablet{--columnGap: 0rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-0-tablet-only{--columnGap: 0rem}}@media screen and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-0-touch{--columnGap: 0rem}}@media screen and (min-width: 1056px){html.theme--documenter-dark .columns.is-variable.is-0-desktop{--columnGap: 0rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--documenter-dark .columns.is-variable.is-0-desktop-only{--columnGap: 0rem}}@media screen and (min-width: 1216px){html.theme--documenter-dark .columns.is-variable.is-0-widescreen{--columnGap: 0rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--documenter-dark .columns.is-variable.is-0-widescreen-only{--columnGap: 0rem}}@media screen and (min-width: 1408px){html.theme--documenter-dark .columns.is-variable.is-0-fullhd{--columnGap: 0rem}}html.theme--documenter-dark .columns.is-variable.is-1{--columnGap: .25rem}@media screen and (max-width: 768px){html.theme--documenter-dark .columns.is-variable.is-1-mobile{--columnGap: .25rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .columns.is-variable.is-1-tablet{--columnGap: .25rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-1-tablet-only{--columnGap: .25rem}}@media screen and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-1-touch{--columnGap: .25rem}}@media screen and (min-width: 1056px){html.theme--documenter-dark .columns.is-variable.is-1-desktop{--columnGap: .25rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--documenter-dark .columns.is-variable.is-1-desktop-only{--columnGap: .25rem}}@media screen and (min-width: 1216px){html.theme--documenter-dark .columns.is-variable.is-1-widescreen{--columnGap: .25rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--documenter-dark .columns.is-variable.is-1-widescreen-only{--columnGap: .25rem}}@media screen and (min-width: 1408px){html.theme--documenter-dark .columns.is-variable.is-1-fullhd{--columnGap: .25rem}}html.theme--documenter-dark .columns.is-variable.is-2{--columnGap: .5rem}@media screen and (max-width: 768px){html.theme--documenter-dark .columns.is-variable.is-2-mobile{--columnGap: .5rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .columns.is-variable.is-2-tablet{--columnGap: .5rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-2-tablet-only{--columnGap: .5rem}}@media screen and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-2-touch{--columnGap: .5rem}}@media screen and (min-width: 1056px){html.theme--documenter-dark .columns.is-variable.is-2-desktop{--columnGap: .5rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--documenter-dark .columns.is-variable.is-2-desktop-only{--columnGap: .5rem}}@media screen and (min-width: 1216px){html.theme--documenter-dark .columns.is-variable.is-2-widescreen{--columnGap: .5rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--documenter-dark .columns.is-variable.is-2-widescreen-only{--columnGap: .5rem}}@media screen and (min-width: 1408px){html.theme--documenter-dark .columns.is-variable.is-2-fullhd{--columnGap: .5rem}}html.theme--documenter-dark .columns.is-variable.is-3{--columnGap: .75rem}@media screen and (max-width: 768px){html.theme--documenter-dark .columns.is-variable.is-3-mobile{--columnGap: .75rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .columns.is-variable.is-3-tablet{--columnGap: .75rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-3-tablet-only{--columnGap: .75rem}}@media screen and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-3-touch{--columnGap: .75rem}}@media screen and (min-width: 1056px){html.theme--documenter-dark .columns.is-variable.is-3-desktop{--columnGap: .75rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--documenter-dark .columns.is-variable.is-3-desktop-only{--columnGap: .75rem}}@media screen and (min-width: 1216px){html.theme--documenter-dark .columns.is-variable.is-3-widescreen{--columnGap: .75rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--documenter-dark .columns.is-variable.is-3-widescreen-only{--columnGap: .75rem}}@media screen and (min-width: 1408px){html.theme--documenter-dark .columns.is-variable.is-3-fullhd{--columnGap: .75rem}}html.theme--documenter-dark .columns.is-variable.is-4{--columnGap: 1rem}@media screen and (max-width: 768px){html.theme--documenter-dark .columns.is-variable.is-4-mobile{--columnGap: 1rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .columns.is-variable.is-4-tablet{--columnGap: 1rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-4-tablet-only{--columnGap: 1rem}}@media screen and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-4-touch{--columnGap: 1rem}}@media screen and (min-width: 1056px){html.theme--documenter-dark .columns.is-variable.is-4-desktop{--columnGap: 1rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--documenter-dark .columns.is-variable.is-4-desktop-only{--columnGap: 1rem}}@media screen and (min-width: 1216px){html.theme--documenter-dark .columns.is-variable.is-4-widescreen{--columnGap: 1rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--documenter-dark .columns.is-variable.is-4-widescreen-only{--columnGap: 1rem}}@media screen and (min-width: 1408px){html.theme--documenter-dark .columns.is-variable.is-4-fullhd{--columnGap: 1rem}}html.theme--documenter-dark .columns.is-variable.is-5{--columnGap: 1.25rem}@media screen and (max-width: 768px){html.theme--documenter-dark .columns.is-variable.is-5-mobile{--columnGap: 1.25rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .columns.is-variable.is-5-tablet{--columnGap: 1.25rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-5-tablet-only{--columnGap: 1.25rem}}@media screen and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-5-touch{--columnGap: 1.25rem}}@media screen and (min-width: 1056px){html.theme--documenter-dark .columns.is-variable.is-5-desktop{--columnGap: 1.25rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--documenter-dark .columns.is-variable.is-5-desktop-only{--columnGap: 1.25rem}}@media screen and (min-width: 1216px){html.theme--documenter-dark .columns.is-variable.is-5-widescreen{--columnGap: 1.25rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--documenter-dark .columns.is-variable.is-5-widescreen-only{--columnGap: 1.25rem}}@media screen and (min-width: 1408px){html.theme--documenter-dark .columns.is-variable.is-5-fullhd{--columnGap: 1.25rem}}html.theme--documenter-dark .columns.is-variable.is-6{--columnGap: 1.5rem}@media screen and (max-width: 768px){html.theme--documenter-dark .columns.is-variable.is-6-mobile{--columnGap: 1.5rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .columns.is-variable.is-6-tablet{--columnGap: 1.5rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-6-tablet-only{--columnGap: 1.5rem}}@media screen and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-6-touch{--columnGap: 1.5rem}}@media screen and (min-width: 1056px){html.theme--documenter-dark .columns.is-variable.is-6-desktop{--columnGap: 1.5rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--documenter-dark .columns.is-variable.is-6-desktop-only{--columnGap: 1.5rem}}@media screen and (min-width: 1216px){html.theme--documenter-dark .columns.is-variable.is-6-widescreen{--columnGap: 1.5rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--documenter-dark .columns.is-variable.is-6-widescreen-only{--columnGap: 1.5rem}}@media screen and (min-width: 1408px){html.theme--documenter-dark .columns.is-variable.is-6-fullhd{--columnGap: 1.5rem}}html.theme--documenter-dark .columns.is-variable.is-7{--columnGap: 1.75rem}@media screen and (max-width: 768px){html.theme--documenter-dark .columns.is-variable.is-7-mobile{--columnGap: 1.75rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .columns.is-variable.is-7-tablet{--columnGap: 1.75rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-7-tablet-only{--columnGap: 1.75rem}}@media screen and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-7-touch{--columnGap: 1.75rem}}@media screen and (min-width: 1056px){html.theme--documenter-dark .columns.is-variable.is-7-desktop{--columnGap: 1.75rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--documenter-dark .columns.is-variable.is-7-desktop-only{--columnGap: 1.75rem}}@media screen and (min-width: 1216px){html.theme--documenter-dark .columns.is-variable.is-7-widescreen{--columnGap: 1.75rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--documenter-dark .columns.is-variable.is-7-widescreen-only{--columnGap: 1.75rem}}@media screen and (min-width: 1408px){html.theme--documenter-dark .columns.is-variable.is-7-fullhd{--columnGap: 1.75rem}}html.theme--documenter-dark .columns.is-variable.is-8{--columnGap: 2rem}@media screen and (max-width: 768px){html.theme--documenter-dark .columns.is-variable.is-8-mobile{--columnGap: 2rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .columns.is-variable.is-8-tablet{--columnGap: 2rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-8-tablet-only{--columnGap: 2rem}}@media screen and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-8-touch{--columnGap: 2rem}}@media screen and (min-width: 1056px){html.theme--documenter-dark .columns.is-variable.is-8-desktop{--columnGap: 2rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--documenter-dark .columns.is-variable.is-8-desktop-only{--columnGap: 2rem}}@media screen and (min-width: 1216px){html.theme--documenter-dark .columns.is-variable.is-8-widescreen{--columnGap: 2rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--documenter-dark .columns.is-variable.is-8-widescreen-only{--columnGap: 2rem}}@media screen and (min-width: 1408px){html.theme--documenter-dark .columns.is-variable.is-8-fullhd{--columnGap: 2rem}}html.theme--documenter-dark .tile{align-items:stretch;display:block;flex-basis:0;flex-grow:1;flex-shrink:1;min-height:min-content}html.theme--documenter-dark .tile.is-ancestor{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}html.theme--documenter-dark .tile.is-ancestor:last-child{margin-bottom:-.75rem}html.theme--documenter-dark .tile.is-ancestor:not(:last-child){margin-bottom:.75rem}html.theme--documenter-dark .tile.is-child{margin:0 !important}html.theme--documenter-dark .tile.is-parent{padding:.75rem}html.theme--documenter-dark .tile.is-vertical{flex-direction:column}html.theme--documenter-dark .tile.is-vertical>.tile.is-child:not(:last-child){margin-bottom:1.5rem !important}@media screen and (min-width: 769px),print{html.theme--documenter-dark .tile:not(.is-child){display:flex}html.theme--documenter-dark .tile.is-1{flex:none;width:8.33333337%}html.theme--documenter-dark .tile.is-2{flex:none;width:16.66666674%}html.theme--documenter-dark .tile.is-3{flex:none;width:25%}html.theme--documenter-dark .tile.is-4{flex:none;width:33.33333337%}html.theme--documenter-dark .tile.is-5{flex:none;width:41.66666674%}html.theme--documenter-dark .tile.is-6{flex:none;width:50%}html.theme--documenter-dark .tile.is-7{flex:none;width:58.33333337%}html.theme--documenter-dark .tile.is-8{flex:none;width:66.66666674%}html.theme--documenter-dark .tile.is-9{flex:none;width:75%}html.theme--documenter-dark .tile.is-10{flex:none;width:83.33333337%}html.theme--documenter-dark .tile.is-11{flex:none;width:91.66666674%}html.theme--documenter-dark .tile.is-12{flex:none;width:100%}}html.theme--documenter-dark .hero{align-items:stretch;display:flex;flex-direction:column;justify-content:space-between}html.theme--documenter-dark .hero .navbar{background:none}html.theme--documenter-dark .hero .tabs ul{border-bottom:none}html.theme--documenter-dark .hero.is-white{background-color:#fff;color:#0a0a0a}html.theme--documenter-dark .hero.is-white a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .hero.is-white strong{color:inherit}html.theme--documenter-dark .hero.is-white .title{color:#0a0a0a}html.theme--documenter-dark .hero.is-white .subtitle{color:rgba(10,10,10,0.9)}html.theme--documenter-dark .hero.is-white .subtitle a:not(.button),html.theme--documenter-dark .hero.is-white .subtitle strong{color:#0a0a0a}@media screen and (max-width: 1055px){html.theme--documenter-dark .hero.is-white .navbar-menu{background-color:#fff}}html.theme--documenter-dark .hero.is-white .navbar-item,html.theme--documenter-dark .hero.is-white .navbar-link{color:rgba(10,10,10,0.7)}html.theme--documenter-dark .hero.is-white a.navbar-item:hover,html.theme--documenter-dark .hero.is-white a.navbar-item.is-active,html.theme--documenter-dark .hero.is-white .navbar-link:hover,html.theme--documenter-dark .hero.is-white .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}html.theme--documenter-dark .hero.is-white .tabs a{color:#0a0a0a;opacity:0.9}html.theme--documenter-dark .hero.is-white .tabs a:hover{opacity:1}html.theme--documenter-dark .hero.is-white .tabs li.is-active a{color:#fff !important;opacity:1}html.theme--documenter-dark .hero.is-white .tabs.is-boxed a,html.theme--documenter-dark .hero.is-white .tabs.is-toggle a{color:#0a0a0a}html.theme--documenter-dark .hero.is-white .tabs.is-boxed a:hover,html.theme--documenter-dark .hero.is-white .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--documenter-dark .hero.is-white .tabs.is-boxed li.is-active a,html.theme--documenter-dark .hero.is-white .tabs.is-boxed li.is-active a:hover,html.theme--documenter-dark .hero.is-white .tabs.is-toggle li.is-active a,html.theme--documenter-dark .hero.is-white .tabs.is-toggle li.is-active a:hover{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}html.theme--documenter-dark .hero.is-white.is-bold{background-image:linear-gradient(141deg, #e8e3e4 0%, #fff 71%, #fff 100%)}@media screen and (max-width: 768px){html.theme--documenter-dark .hero.is-white.is-bold .navbar-menu{background-image:linear-gradient(141deg, #e8e3e4 0%, #fff 71%, #fff 100%)}}html.theme--documenter-dark .hero.is-black{background-color:#0a0a0a;color:#fff}html.theme--documenter-dark .hero.is-black a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .hero.is-black strong{color:inherit}html.theme--documenter-dark .hero.is-black .title{color:#fff}html.theme--documenter-dark .hero.is-black .subtitle{color:rgba(255,255,255,0.9)}html.theme--documenter-dark .hero.is-black .subtitle a:not(.button),html.theme--documenter-dark .hero.is-black .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--documenter-dark .hero.is-black .navbar-menu{background-color:#0a0a0a}}html.theme--documenter-dark .hero.is-black .navbar-item,html.theme--documenter-dark .hero.is-black .navbar-link{color:rgba(255,255,255,0.7)}html.theme--documenter-dark .hero.is-black a.navbar-item:hover,html.theme--documenter-dark .hero.is-black a.navbar-item.is-active,html.theme--documenter-dark .hero.is-black .navbar-link:hover,html.theme--documenter-dark .hero.is-black .navbar-link.is-active{background-color:#000;color:#fff}html.theme--documenter-dark .hero.is-black .tabs a{color:#fff;opacity:0.9}html.theme--documenter-dark .hero.is-black .tabs a:hover{opacity:1}html.theme--documenter-dark .hero.is-black .tabs li.is-active a{color:#0a0a0a !important;opacity:1}html.theme--documenter-dark .hero.is-black .tabs.is-boxed a,html.theme--documenter-dark .hero.is-black .tabs.is-toggle a{color:#fff}html.theme--documenter-dark .hero.is-black .tabs.is-boxed a:hover,html.theme--documenter-dark .hero.is-black .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--documenter-dark .hero.is-black .tabs.is-boxed li.is-active a,html.theme--documenter-dark .hero.is-black .tabs.is-boxed li.is-active a:hover,html.theme--documenter-dark .hero.is-black .tabs.is-toggle li.is-active a,html.theme--documenter-dark .hero.is-black .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#0a0a0a}html.theme--documenter-dark .hero.is-black.is-bold{background-image:linear-gradient(141deg, #000 0%, #0a0a0a 71%, #181616 100%)}@media screen and (max-width: 768px){html.theme--documenter-dark .hero.is-black.is-bold .navbar-menu{background-image:linear-gradient(141deg, #000 0%, #0a0a0a 71%, #181616 100%)}}html.theme--documenter-dark .hero.is-light{background-color:#ecf0f1;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .hero.is-light a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .hero.is-light strong{color:inherit}html.theme--documenter-dark .hero.is-light .title{color:rgba(0,0,0,0.7)}html.theme--documenter-dark .hero.is-light .subtitle{color:rgba(0,0,0,0.9)}html.theme--documenter-dark .hero.is-light .subtitle a:not(.button),html.theme--documenter-dark .hero.is-light .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1055px){html.theme--documenter-dark .hero.is-light .navbar-menu{background-color:#ecf0f1}}html.theme--documenter-dark .hero.is-light .navbar-item,html.theme--documenter-dark .hero.is-light .navbar-link{color:rgba(0,0,0,0.7)}html.theme--documenter-dark .hero.is-light a.navbar-item:hover,html.theme--documenter-dark .hero.is-light a.navbar-item.is-active,html.theme--documenter-dark .hero.is-light .navbar-link:hover,html.theme--documenter-dark .hero.is-light .navbar-link.is-active{background-color:#dde4e6;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .hero.is-light .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}html.theme--documenter-dark .hero.is-light .tabs a:hover{opacity:1}html.theme--documenter-dark .hero.is-light .tabs li.is-active a{color:#ecf0f1 !important;opacity:1}html.theme--documenter-dark .hero.is-light .tabs.is-boxed a,html.theme--documenter-dark .hero.is-light .tabs.is-toggle a{color:rgba(0,0,0,0.7)}html.theme--documenter-dark .hero.is-light .tabs.is-boxed a:hover,html.theme--documenter-dark .hero.is-light .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--documenter-dark .hero.is-light .tabs.is-boxed li.is-active a,html.theme--documenter-dark .hero.is-light .tabs.is-boxed li.is-active a:hover,html.theme--documenter-dark .hero.is-light .tabs.is-toggle li.is-active a,html.theme--documenter-dark .hero.is-light .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#ecf0f1}html.theme--documenter-dark .hero.is-light.is-bold{background-image:linear-gradient(141deg, #cadfe0 0%, #ecf0f1 71%, #fafbfc 100%)}@media screen and (max-width: 768px){html.theme--documenter-dark .hero.is-light.is-bold .navbar-menu{background-image:linear-gradient(141deg, #cadfe0 0%, #ecf0f1 71%, #fafbfc 100%)}}html.theme--documenter-dark .hero.is-dark,html.theme--documenter-dark .content kbd.hero{background-color:#282f2f;color:#fff}html.theme--documenter-dark .hero.is-dark a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .content kbd.hero a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .hero.is-dark strong,html.theme--documenter-dark .content kbd.hero strong{color:inherit}html.theme--documenter-dark .hero.is-dark .title,html.theme--documenter-dark .content kbd.hero .title{color:#fff}html.theme--documenter-dark .hero.is-dark .subtitle,html.theme--documenter-dark .content kbd.hero .subtitle{color:rgba(255,255,255,0.9)}html.theme--documenter-dark .hero.is-dark .subtitle a:not(.button),html.theme--documenter-dark .content kbd.hero .subtitle a:not(.button),html.theme--documenter-dark .hero.is-dark .subtitle strong,html.theme--documenter-dark .content kbd.hero .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--documenter-dark .hero.is-dark .navbar-menu,html.theme--documenter-dark .content kbd.hero .navbar-menu{background-color:#282f2f}}html.theme--documenter-dark .hero.is-dark .navbar-item,html.theme--documenter-dark .content kbd.hero .navbar-item,html.theme--documenter-dark .hero.is-dark .navbar-link,html.theme--documenter-dark .content kbd.hero .navbar-link{color:rgba(255,255,255,0.7)}html.theme--documenter-dark .hero.is-dark a.navbar-item:hover,html.theme--documenter-dark .content kbd.hero a.navbar-item:hover,html.theme--documenter-dark .hero.is-dark a.navbar-item.is-active,html.theme--documenter-dark .content kbd.hero a.navbar-item.is-active,html.theme--documenter-dark .hero.is-dark .navbar-link:hover,html.theme--documenter-dark .content kbd.hero .navbar-link:hover,html.theme--documenter-dark .hero.is-dark .navbar-link.is-active,html.theme--documenter-dark .content kbd.hero .navbar-link.is-active{background-color:#1d2122;color:#fff}html.theme--documenter-dark .hero.is-dark .tabs a,html.theme--documenter-dark .content kbd.hero .tabs a{color:#fff;opacity:0.9}html.theme--documenter-dark .hero.is-dark .tabs a:hover,html.theme--documenter-dark .content kbd.hero .tabs a:hover{opacity:1}html.theme--documenter-dark .hero.is-dark .tabs li.is-active a,html.theme--documenter-dark .content kbd.hero .tabs li.is-active a{color:#282f2f !important;opacity:1}html.theme--documenter-dark .hero.is-dark .tabs.is-boxed a,html.theme--documenter-dark .content kbd.hero .tabs.is-boxed a,html.theme--documenter-dark .hero.is-dark .tabs.is-toggle a,html.theme--documenter-dark .content kbd.hero .tabs.is-toggle a{color:#fff}html.theme--documenter-dark .hero.is-dark .tabs.is-boxed a:hover,html.theme--documenter-dark .content kbd.hero .tabs.is-boxed a:hover,html.theme--documenter-dark .hero.is-dark .tabs.is-toggle a:hover,html.theme--documenter-dark .content kbd.hero .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--documenter-dark .hero.is-dark .tabs.is-boxed li.is-active a,html.theme--documenter-dark .content kbd.hero .tabs.is-boxed li.is-active a,html.theme--documenter-dark .hero.is-dark .tabs.is-boxed li.is-active a:hover,html.theme--documenter-dark .hero.is-dark .tabs.is-toggle li.is-active a,html.theme--documenter-dark .content kbd.hero .tabs.is-toggle li.is-active a,html.theme--documenter-dark .hero.is-dark .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#282f2f}html.theme--documenter-dark .hero.is-dark.is-bold,html.theme--documenter-dark .content kbd.hero.is-bold{background-image:linear-gradient(141deg, #0f1615 0%, #282f2f 71%, #313c40 100%)}@media screen and (max-width: 768px){html.theme--documenter-dark .hero.is-dark.is-bold .navbar-menu,html.theme--documenter-dark .content kbd.hero.is-bold .navbar-menu{background-image:linear-gradient(141deg, #0f1615 0%, #282f2f 71%, #313c40 100%)}}html.theme--documenter-dark .hero.is-primary,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink{background-color:#375a7f;color:#fff}html.theme--documenter-dark .hero.is-primary a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .hero.is-primary strong,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink strong{color:inherit}html.theme--documenter-dark .hero.is-primary .title,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .title{color:#fff}html.theme--documenter-dark .hero.is-primary .subtitle,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .subtitle{color:rgba(255,255,255,0.9)}html.theme--documenter-dark .hero.is-primary .subtitle a:not(.button),html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .subtitle a:not(.button),html.theme--documenter-dark .hero.is-primary .subtitle strong,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--documenter-dark .hero.is-primary .navbar-menu,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .navbar-menu{background-color:#375a7f}}html.theme--documenter-dark .hero.is-primary .navbar-item,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .navbar-item,html.theme--documenter-dark .hero.is-primary .navbar-link,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .navbar-link{color:rgba(255,255,255,0.7)}html.theme--documenter-dark .hero.is-primary a.navbar-item:hover,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink a.navbar-item:hover,html.theme--documenter-dark .hero.is-primary a.navbar-item.is-active,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink a.navbar-item.is-active,html.theme--documenter-dark .hero.is-primary .navbar-link:hover,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .navbar-link:hover,html.theme--documenter-dark .hero.is-primary .navbar-link.is-active,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .navbar-link.is-active{background-color:#2f4d6d;color:#fff}html.theme--documenter-dark .hero.is-primary .tabs a,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .tabs a{color:#fff;opacity:0.9}html.theme--documenter-dark .hero.is-primary .tabs a:hover,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .tabs a:hover{opacity:1}html.theme--documenter-dark .hero.is-primary .tabs li.is-active a,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .tabs li.is-active a{color:#375a7f !important;opacity:1}html.theme--documenter-dark .hero.is-primary .tabs.is-boxed a,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .tabs.is-boxed a,html.theme--documenter-dark .hero.is-primary .tabs.is-toggle a,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .tabs.is-toggle a{color:#fff}html.theme--documenter-dark .hero.is-primary .tabs.is-boxed a:hover,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .tabs.is-boxed a:hover,html.theme--documenter-dark .hero.is-primary .tabs.is-toggle a:hover,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--documenter-dark .hero.is-primary .tabs.is-boxed li.is-active a,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .tabs.is-boxed li.is-active a,html.theme--documenter-dark .hero.is-primary .tabs.is-boxed li.is-active a:hover,html.theme--documenter-dark .hero.is-primary .tabs.is-toggle li.is-active a,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .tabs.is-toggle li.is-active a,html.theme--documenter-dark .hero.is-primary .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#375a7f}html.theme--documenter-dark .hero.is-primary.is-bold,html.theme--documenter-dark .docstring>section>a.hero.is-bold.docs-sourcelink{background-image:linear-gradient(141deg, #214b62 0%, #375a7f 71%, #3a5796 100%)}@media screen and (max-width: 768px){html.theme--documenter-dark .hero.is-primary.is-bold .navbar-menu,html.theme--documenter-dark .docstring>section>a.hero.is-bold.docs-sourcelink .navbar-menu{background-image:linear-gradient(141deg, #214b62 0%, #375a7f 71%, #3a5796 100%)}}html.theme--documenter-dark .hero.is-link{background-color:#1abc9c;color:#fff}html.theme--documenter-dark .hero.is-link a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .hero.is-link strong{color:inherit}html.theme--documenter-dark .hero.is-link .title{color:#fff}html.theme--documenter-dark .hero.is-link .subtitle{color:rgba(255,255,255,0.9)}html.theme--documenter-dark .hero.is-link .subtitle a:not(.button),html.theme--documenter-dark .hero.is-link .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--documenter-dark .hero.is-link .navbar-menu{background-color:#1abc9c}}html.theme--documenter-dark .hero.is-link .navbar-item,html.theme--documenter-dark .hero.is-link .navbar-link{color:rgba(255,255,255,0.7)}html.theme--documenter-dark .hero.is-link a.navbar-item:hover,html.theme--documenter-dark .hero.is-link a.navbar-item.is-active,html.theme--documenter-dark .hero.is-link .navbar-link:hover,html.theme--documenter-dark .hero.is-link .navbar-link.is-active{background-color:#17a689;color:#fff}html.theme--documenter-dark .hero.is-link .tabs a{color:#fff;opacity:0.9}html.theme--documenter-dark .hero.is-link .tabs a:hover{opacity:1}html.theme--documenter-dark .hero.is-link .tabs li.is-active a{color:#1abc9c !important;opacity:1}html.theme--documenter-dark .hero.is-link .tabs.is-boxed a,html.theme--documenter-dark .hero.is-link .tabs.is-toggle a{color:#fff}html.theme--documenter-dark .hero.is-link .tabs.is-boxed a:hover,html.theme--documenter-dark .hero.is-link .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--documenter-dark .hero.is-link .tabs.is-boxed li.is-active a,html.theme--documenter-dark .hero.is-link .tabs.is-boxed li.is-active a:hover,html.theme--documenter-dark .hero.is-link .tabs.is-toggle li.is-active a,html.theme--documenter-dark .hero.is-link .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#1abc9c}html.theme--documenter-dark .hero.is-link.is-bold{background-image:linear-gradient(141deg, #0c9764 0%, #1abc9c 71%, #17d8d2 100%)}@media screen and (max-width: 768px){html.theme--documenter-dark .hero.is-link.is-bold .navbar-menu{background-image:linear-gradient(141deg, #0c9764 0%, #1abc9c 71%, #17d8d2 100%)}}html.theme--documenter-dark .hero.is-info{background-color:#024c7d;color:#fff}html.theme--documenter-dark .hero.is-info a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .hero.is-info strong{color:inherit}html.theme--documenter-dark .hero.is-info .title{color:#fff}html.theme--documenter-dark .hero.is-info .subtitle{color:rgba(255,255,255,0.9)}html.theme--documenter-dark .hero.is-info .subtitle a:not(.button),html.theme--documenter-dark .hero.is-info .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--documenter-dark .hero.is-info .navbar-menu{background-color:#024c7d}}html.theme--documenter-dark .hero.is-info .navbar-item,html.theme--documenter-dark .hero.is-info .navbar-link{color:rgba(255,255,255,0.7)}html.theme--documenter-dark .hero.is-info a.navbar-item:hover,html.theme--documenter-dark .hero.is-info a.navbar-item.is-active,html.theme--documenter-dark .hero.is-info .navbar-link:hover,html.theme--documenter-dark .hero.is-info .navbar-link.is-active{background-color:#023d64;color:#fff}html.theme--documenter-dark .hero.is-info .tabs a{color:#fff;opacity:0.9}html.theme--documenter-dark .hero.is-info .tabs a:hover{opacity:1}html.theme--documenter-dark .hero.is-info .tabs li.is-active a{color:#024c7d !important;opacity:1}html.theme--documenter-dark .hero.is-info .tabs.is-boxed a,html.theme--documenter-dark .hero.is-info .tabs.is-toggle a{color:#fff}html.theme--documenter-dark .hero.is-info .tabs.is-boxed a:hover,html.theme--documenter-dark .hero.is-info .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--documenter-dark .hero.is-info .tabs.is-boxed li.is-active a,html.theme--documenter-dark .hero.is-info .tabs.is-boxed li.is-active a:hover,html.theme--documenter-dark .hero.is-info .tabs.is-toggle li.is-active a,html.theme--documenter-dark .hero.is-info .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#024c7d}html.theme--documenter-dark .hero.is-info.is-bold{background-image:linear-gradient(141deg, #003a4c 0%, #024c7d 71%, #004299 100%)}@media screen and (max-width: 768px){html.theme--documenter-dark .hero.is-info.is-bold .navbar-menu{background-image:linear-gradient(141deg, #003a4c 0%, #024c7d 71%, #004299 100%)}}html.theme--documenter-dark .hero.is-success{background-color:#008438;color:#fff}html.theme--documenter-dark .hero.is-success a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .hero.is-success strong{color:inherit}html.theme--documenter-dark .hero.is-success .title{color:#fff}html.theme--documenter-dark .hero.is-success .subtitle{color:rgba(255,255,255,0.9)}html.theme--documenter-dark .hero.is-success .subtitle a:not(.button),html.theme--documenter-dark .hero.is-success .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--documenter-dark .hero.is-success .navbar-menu{background-color:#008438}}html.theme--documenter-dark .hero.is-success .navbar-item,html.theme--documenter-dark .hero.is-success .navbar-link{color:rgba(255,255,255,0.7)}html.theme--documenter-dark .hero.is-success a.navbar-item:hover,html.theme--documenter-dark .hero.is-success a.navbar-item.is-active,html.theme--documenter-dark .hero.is-success .navbar-link:hover,html.theme--documenter-dark .hero.is-success .navbar-link.is-active{background-color:#006b2d;color:#fff}html.theme--documenter-dark .hero.is-success .tabs a{color:#fff;opacity:0.9}html.theme--documenter-dark .hero.is-success .tabs a:hover{opacity:1}html.theme--documenter-dark .hero.is-success .tabs li.is-active a{color:#008438 !important;opacity:1}html.theme--documenter-dark .hero.is-success .tabs.is-boxed a,html.theme--documenter-dark .hero.is-success .tabs.is-toggle a{color:#fff}html.theme--documenter-dark .hero.is-success .tabs.is-boxed a:hover,html.theme--documenter-dark .hero.is-success .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--documenter-dark .hero.is-success .tabs.is-boxed li.is-active a,html.theme--documenter-dark .hero.is-success .tabs.is-boxed li.is-active a:hover,html.theme--documenter-dark .hero.is-success .tabs.is-toggle li.is-active a,html.theme--documenter-dark .hero.is-success .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#008438}html.theme--documenter-dark .hero.is-success.is-bold{background-image:linear-gradient(141deg, #005115 0%, #008438 71%, #009e5d 100%)}@media screen and (max-width: 768px){html.theme--documenter-dark .hero.is-success.is-bold .navbar-menu{background-image:linear-gradient(141deg, #005115 0%, #008438 71%, #009e5d 100%)}}html.theme--documenter-dark .hero.is-warning{background-color:#ad8100;color:#fff}html.theme--documenter-dark .hero.is-warning a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .hero.is-warning strong{color:inherit}html.theme--documenter-dark .hero.is-warning .title{color:#fff}html.theme--documenter-dark .hero.is-warning .subtitle{color:rgba(255,255,255,0.9)}html.theme--documenter-dark .hero.is-warning .subtitle a:not(.button),html.theme--documenter-dark .hero.is-warning .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--documenter-dark .hero.is-warning .navbar-menu{background-color:#ad8100}}html.theme--documenter-dark .hero.is-warning .navbar-item,html.theme--documenter-dark .hero.is-warning .navbar-link{color:rgba(255,255,255,0.7)}html.theme--documenter-dark .hero.is-warning a.navbar-item:hover,html.theme--documenter-dark .hero.is-warning a.navbar-item.is-active,html.theme--documenter-dark .hero.is-warning .navbar-link:hover,html.theme--documenter-dark .hero.is-warning .navbar-link.is-active{background-color:#946e00;color:#fff}html.theme--documenter-dark .hero.is-warning .tabs a{color:#fff;opacity:0.9}html.theme--documenter-dark .hero.is-warning .tabs a:hover{opacity:1}html.theme--documenter-dark .hero.is-warning .tabs li.is-active a{color:#ad8100 !important;opacity:1}html.theme--documenter-dark .hero.is-warning .tabs.is-boxed a,html.theme--documenter-dark .hero.is-warning .tabs.is-toggle a{color:#fff}html.theme--documenter-dark .hero.is-warning .tabs.is-boxed a:hover,html.theme--documenter-dark .hero.is-warning .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--documenter-dark .hero.is-warning .tabs.is-boxed li.is-active a,html.theme--documenter-dark .hero.is-warning .tabs.is-boxed li.is-active a:hover,html.theme--documenter-dark .hero.is-warning .tabs.is-toggle li.is-active a,html.theme--documenter-dark .hero.is-warning .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#ad8100}html.theme--documenter-dark .hero.is-warning.is-bold{background-image:linear-gradient(141deg, #7a4700 0%, #ad8100 71%, #c7b500 100%)}@media screen and (max-width: 768px){html.theme--documenter-dark .hero.is-warning.is-bold .navbar-menu{background-image:linear-gradient(141deg, #7a4700 0%, #ad8100 71%, #c7b500 100%)}}html.theme--documenter-dark .hero.is-danger{background-color:#9e1b0d;color:#fff}html.theme--documenter-dark .hero.is-danger a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .hero.is-danger strong{color:inherit}html.theme--documenter-dark .hero.is-danger .title{color:#fff}html.theme--documenter-dark .hero.is-danger .subtitle{color:rgba(255,255,255,0.9)}html.theme--documenter-dark .hero.is-danger .subtitle a:not(.button),html.theme--documenter-dark .hero.is-danger .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--documenter-dark .hero.is-danger .navbar-menu{background-color:#9e1b0d}}html.theme--documenter-dark .hero.is-danger .navbar-item,html.theme--documenter-dark .hero.is-danger .navbar-link{color:rgba(255,255,255,0.7)}html.theme--documenter-dark .hero.is-danger a.navbar-item:hover,html.theme--documenter-dark .hero.is-danger a.navbar-item.is-active,html.theme--documenter-dark .hero.is-danger .navbar-link:hover,html.theme--documenter-dark .hero.is-danger .navbar-link.is-active{background-color:#86170b;color:#fff}html.theme--documenter-dark .hero.is-danger .tabs a{color:#fff;opacity:0.9}html.theme--documenter-dark .hero.is-danger .tabs a:hover{opacity:1}html.theme--documenter-dark .hero.is-danger .tabs li.is-active a{color:#9e1b0d !important;opacity:1}html.theme--documenter-dark .hero.is-danger .tabs.is-boxed a,html.theme--documenter-dark .hero.is-danger .tabs.is-toggle a{color:#fff}html.theme--documenter-dark .hero.is-danger .tabs.is-boxed a:hover,html.theme--documenter-dark .hero.is-danger .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--documenter-dark .hero.is-danger .tabs.is-boxed li.is-active a,html.theme--documenter-dark .hero.is-danger .tabs.is-boxed li.is-active a:hover,html.theme--documenter-dark .hero.is-danger .tabs.is-toggle li.is-active a,html.theme--documenter-dark .hero.is-danger .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#9e1b0d}html.theme--documenter-dark .hero.is-danger.is-bold{background-image:linear-gradient(141deg, #75030b 0%, #9e1b0d 71%, #ba380a 100%)}@media screen and (max-width: 768px){html.theme--documenter-dark .hero.is-danger.is-bold .navbar-menu{background-image:linear-gradient(141deg, #75030b 0%, #9e1b0d 71%, #ba380a 100%)}}html.theme--documenter-dark .hero.is-small .hero-body,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.hero .hero-body{padding:1.5rem}@media screen and (min-width: 769px),print{html.theme--documenter-dark .hero.is-medium .hero-body{padding:9rem 4.5rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .hero.is-large .hero-body{padding:18rem 6rem}}html.theme--documenter-dark .hero.is-halfheight .hero-body,html.theme--documenter-dark .hero.is-fullheight .hero-body,html.theme--documenter-dark .hero.is-fullheight-with-navbar .hero-body{align-items:center;display:flex}html.theme--documenter-dark .hero.is-halfheight .hero-body>.container,html.theme--documenter-dark .hero.is-fullheight .hero-body>.container,html.theme--documenter-dark .hero.is-fullheight-with-navbar .hero-body>.container{flex-grow:1;flex-shrink:1}html.theme--documenter-dark .hero.is-halfheight{min-height:50vh}html.theme--documenter-dark .hero.is-fullheight{min-height:100vh}html.theme--documenter-dark .hero-video{overflow:hidden}html.theme--documenter-dark .hero-video video{left:50%;min-height:100%;min-width:100%;position:absolute;top:50%;transform:translate3d(-50%, -50%, 0)}html.theme--documenter-dark .hero-video.is-transparent{opacity:0.3}@media screen and (max-width: 768px){html.theme--documenter-dark .hero-video{display:none}}html.theme--documenter-dark .hero-buttons{margin-top:1.5rem}@media screen and (max-width: 768px){html.theme--documenter-dark .hero-buttons .button{display:flex}html.theme--documenter-dark .hero-buttons .button:not(:last-child){margin-bottom:0.75rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .hero-buttons{display:flex;justify-content:center}html.theme--documenter-dark .hero-buttons .button:not(:last-child){margin-right:1.5rem}}html.theme--documenter-dark .hero-head,html.theme--documenter-dark .hero-foot{flex-grow:0;flex-shrink:0}html.theme--documenter-dark .hero-body{flex-grow:1;flex-shrink:0;padding:3rem 1.5rem}@media screen and (min-width: 769px),print{html.theme--documenter-dark .hero-body{padding:3rem 3rem}}html.theme--documenter-dark .section{padding:3rem 1.5rem}@media screen and (min-width: 1056px){html.theme--documenter-dark .section{padding:3rem 3rem}html.theme--documenter-dark .section.is-medium{padding:9rem 4.5rem}html.theme--documenter-dark .section.is-large{padding:18rem 6rem}}html.theme--documenter-dark .footer{background-color:#282f2f;padding:3rem 1.5rem 6rem}html.theme--documenter-dark hr{height:1px}html.theme--documenter-dark h6{text-transform:uppercase;letter-spacing:0.5px}html.theme--documenter-dark .hero{background-color:#343c3d}html.theme--documenter-dark a{transition:all 200ms ease}html.theme--documenter-dark .button{transition:all 200ms ease;border-width:1px;color:#fff}html.theme--documenter-dark .button.is-active,html.theme--documenter-dark .button.is-focused,html.theme--documenter-dark .button:active,html.theme--documenter-dark .button:focus{box-shadow:0 0 0 2px rgba(140,155,157,0.5)}html.theme--documenter-dark .button.is-white.is-hovered,html.theme--documenter-dark .button.is-white:hover{background-color:#fff}html.theme--documenter-dark .button.is-white.is-active,html.theme--documenter-dark .button.is-white.is-focused,html.theme--documenter-dark .button.is-white:active,html.theme--documenter-dark .button.is-white:focus{border-color:#fff;box-shadow:0 0 0 2px rgba(255,255,255,0.5)}html.theme--documenter-dark .button.is-black.is-hovered,html.theme--documenter-dark .button.is-black:hover{background-color:#1d1d1d}html.theme--documenter-dark .button.is-black.is-active,html.theme--documenter-dark .button.is-black.is-focused,html.theme--documenter-dark .button.is-black:active,html.theme--documenter-dark .button.is-black:focus{border-color:#0a0a0a;box-shadow:0 0 0 2px rgba(10,10,10,0.5)}html.theme--documenter-dark .button.is-light.is-hovered,html.theme--documenter-dark .button.is-light:hover{background-color:#fff}html.theme--documenter-dark .button.is-light.is-active,html.theme--documenter-dark .button.is-light.is-focused,html.theme--documenter-dark .button.is-light:active,html.theme--documenter-dark .button.is-light:focus{border-color:#ecf0f1;box-shadow:0 0 0 2px rgba(236,240,241,0.5)}html.theme--documenter-dark .button.is-dark.is-hovered,html.theme--documenter-dark .content kbd.button.is-hovered,html.theme--documenter-dark .button.is-dark:hover,html.theme--documenter-dark .content kbd.button:hover{background-color:#3a4344}html.theme--documenter-dark .button.is-dark.is-active,html.theme--documenter-dark .content kbd.button.is-active,html.theme--documenter-dark .button.is-dark.is-focused,html.theme--documenter-dark .content kbd.button.is-focused,html.theme--documenter-dark .button.is-dark:active,html.theme--documenter-dark .content kbd.button:active,html.theme--documenter-dark .button.is-dark:focus,html.theme--documenter-dark .content kbd.button:focus{border-color:#282f2f;box-shadow:0 0 0 2px rgba(40,47,47,0.5)}html.theme--documenter-dark .button.is-primary.is-hovered,html.theme--documenter-dark .docstring>section>a.button.is-hovered.docs-sourcelink,html.theme--documenter-dark .button.is-primary:hover,html.theme--documenter-dark .docstring>section>a.button.docs-sourcelink:hover{background-color:#436d9a}html.theme--documenter-dark .button.is-primary.is-active,html.theme--documenter-dark .docstring>section>a.button.is-active.docs-sourcelink,html.theme--documenter-dark .button.is-primary.is-focused,html.theme--documenter-dark .docstring>section>a.button.is-focused.docs-sourcelink,html.theme--documenter-dark .button.is-primary:active,html.theme--documenter-dark .docstring>section>a.button.docs-sourcelink:active,html.theme--documenter-dark .button.is-primary:focus,html.theme--documenter-dark .docstring>section>a.button.docs-sourcelink:focus{border-color:#375a7f;box-shadow:0 0 0 2px rgba(55,90,127,0.5)}html.theme--documenter-dark .button.is-link.is-hovered,html.theme--documenter-dark .button.is-link:hover{background-color:#1fdeb8}html.theme--documenter-dark .button.is-link.is-active,html.theme--documenter-dark .button.is-link.is-focused,html.theme--documenter-dark .button.is-link:active,html.theme--documenter-dark .button.is-link:focus{border-color:#1abc9c;box-shadow:0 0 0 2px rgba(26,188,156,0.5)}html.theme--documenter-dark .button.is-info.is-hovered,html.theme--documenter-dark .button.is-info:hover{background-color:#0363a3}html.theme--documenter-dark .button.is-info.is-active,html.theme--documenter-dark .button.is-info.is-focused,html.theme--documenter-dark .button.is-info:active,html.theme--documenter-dark .button.is-info:focus{border-color:#024c7d;box-shadow:0 0 0 2px rgba(2,76,125,0.5)}html.theme--documenter-dark .button.is-success.is-hovered,html.theme--documenter-dark .button.is-success:hover{background-color:#00aa48}html.theme--documenter-dark .button.is-success.is-active,html.theme--documenter-dark .button.is-success.is-focused,html.theme--documenter-dark .button.is-success:active,html.theme--documenter-dark .button.is-success:focus{border-color:#008438;box-shadow:0 0 0 2px rgba(0,132,56,0.5)}html.theme--documenter-dark .button.is-warning.is-hovered,html.theme--documenter-dark .button.is-warning:hover{background-color:#d39e00}html.theme--documenter-dark .button.is-warning.is-active,html.theme--documenter-dark .button.is-warning.is-focused,html.theme--documenter-dark .button.is-warning:active,html.theme--documenter-dark .button.is-warning:focus{border-color:#ad8100;box-shadow:0 0 0 2px rgba(173,129,0,0.5)}html.theme--documenter-dark .button.is-danger.is-hovered,html.theme--documenter-dark .button.is-danger:hover{background-color:#c12110}html.theme--documenter-dark .button.is-danger.is-active,html.theme--documenter-dark .button.is-danger.is-focused,html.theme--documenter-dark .button.is-danger:active,html.theme--documenter-dark .button.is-danger:focus{border-color:#9e1b0d;box-shadow:0 0 0 2px rgba(158,27,13,0.5)}html.theme--documenter-dark .label{color:#dbdee0}html.theme--documenter-dark .button,html.theme--documenter-dark .control.has-icons-left .icon,html.theme--documenter-dark .control.has-icons-right .icon,html.theme--documenter-dark .input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input,html.theme--documenter-dark .pagination-ellipsis,html.theme--documenter-dark .pagination-link,html.theme--documenter-dark .pagination-next,html.theme--documenter-dark .pagination-previous,html.theme--documenter-dark .select,html.theme--documenter-dark .select select,html.theme--documenter-dark .textarea{height:2.5em}html.theme--documenter-dark .input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input,html.theme--documenter-dark .textarea{transition:all 200ms ease;box-shadow:none;border-width:1px;padding-left:1em;padding-right:1em}html.theme--documenter-dark .select:after,html.theme--documenter-dark .select select{border-width:1px}html.theme--documenter-dark .control.has-addons .button,html.theme--documenter-dark .control.has-addons .input,html.theme--documenter-dark .control.has-addons #documenter .docs-sidebar form.docs-search>input,html.theme--documenter-dark #documenter .docs-sidebar .control.has-addons form.docs-search>input,html.theme--documenter-dark .control.has-addons .select{margin-right:-1px}html.theme--documenter-dark .notification{background-color:#343c3d}html.theme--documenter-dark .card{box-shadow:none;border:1px solid #343c3d;background-color:#282f2f;border-radius:.4em}html.theme--documenter-dark .card .card-image img{border-radius:.4em .4em 0 0}html.theme--documenter-dark .card .card-header{box-shadow:none;background-color:rgba(18,18,18,0.2);border-radius:.4em .4em 0 0}html.theme--documenter-dark .card .card-footer{background-color:rgba(18,18,18,0.2)}html.theme--documenter-dark .card .card-footer,html.theme--documenter-dark .card .card-footer-item{border-width:1px;border-color:#343c3d}html.theme--documenter-dark .notification.is-white a:not(.button){color:#0a0a0a;text-decoration:underline}html.theme--documenter-dark .notification.is-black a:not(.button){color:#fff;text-decoration:underline}html.theme--documenter-dark .notification.is-light a:not(.button){color:rgba(0,0,0,0.7);text-decoration:underline}html.theme--documenter-dark .notification.is-dark a:not(.button),html.theme--documenter-dark .content kbd.notification a:not(.button){color:#fff;text-decoration:underline}html.theme--documenter-dark .notification.is-primary a:not(.button),html.theme--documenter-dark .docstring>section>a.notification.docs-sourcelink a:not(.button){color:#fff;text-decoration:underline}html.theme--documenter-dark .notification.is-link a:not(.button){color:#fff;text-decoration:underline}html.theme--documenter-dark .notification.is-info a:not(.button){color:#fff;text-decoration:underline}html.theme--documenter-dark .notification.is-success a:not(.button){color:#fff;text-decoration:underline}html.theme--documenter-dark .notification.is-warning a:not(.button){color:#fff;text-decoration:underline}html.theme--documenter-dark .notification.is-danger a:not(.button){color:#fff;text-decoration:underline}html.theme--documenter-dark .tag,html.theme--documenter-dark .content kbd,html.theme--documenter-dark .docstring>section>a.docs-sourcelink{border-radius:.4em}html.theme--documenter-dark .menu-list a{transition:all 300ms ease}html.theme--documenter-dark .modal-card-body{background-color:#282f2f}html.theme--documenter-dark .modal-card-foot,html.theme--documenter-dark .modal-card-head{border-color:#343c3d}html.theme--documenter-dark .message-header{font-weight:700;background-color:#343c3d;color:#fff}html.theme--documenter-dark .message-body{border-width:1px;border-color:#343c3d}html.theme--documenter-dark .navbar{border-radius:.4em}html.theme--documenter-dark .navbar.is-transparent{background:none}html.theme--documenter-dark .navbar.is-primary .navbar-dropdown a.navbar-item.is-active,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-dropdown a.navbar-item.is-active{background-color:#1abc9c}@media screen and (max-width: 1055px){html.theme--documenter-dark .navbar .navbar-menu{background-color:#375a7f;border-radius:0 0 .4em .4em}}html.theme--documenter-dark .hero .navbar,html.theme--documenter-dark body>.navbar{border-radius:0}html.theme--documenter-dark .pagination-link,html.theme--documenter-dark .pagination-next,html.theme--documenter-dark .pagination-previous{border-width:1px}html.theme--documenter-dark .panel-block,html.theme--documenter-dark .panel-heading,html.theme--documenter-dark .panel-tabs{border-width:1px}html.theme--documenter-dark .panel-block:first-child,html.theme--documenter-dark .panel-heading:first-child,html.theme--documenter-dark .panel-tabs:first-child{border-top-width:1px}html.theme--documenter-dark .panel-heading{font-weight:700}html.theme--documenter-dark .panel-tabs a{border-width:1px;margin-bottom:-1px}html.theme--documenter-dark .panel-tabs a.is-active{border-bottom-color:#17a689}html.theme--documenter-dark .panel-block:hover{color:#1dd2af}html.theme--documenter-dark .panel-block:hover .panel-icon{color:#1dd2af}html.theme--documenter-dark .panel-block.is-active .panel-icon{color:#17a689}html.theme--documenter-dark .tabs a{border-bottom-width:1px;margin-bottom:-1px}html.theme--documenter-dark .tabs ul{border-bottom-width:1px}html.theme--documenter-dark .tabs.is-boxed a{border-width:1px}html.theme--documenter-dark .tabs.is-boxed li.is-active a{background-color:#1f2424}html.theme--documenter-dark .tabs.is-toggle li a{border-width:1px;margin-bottom:0}html.theme--documenter-dark .tabs.is-toggle li+li{margin-left:-1px}html.theme--documenter-dark .hero.is-white .navbar .navbar-dropdown .navbar-item:hover{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .hero.is-black .navbar .navbar-dropdown .navbar-item:hover{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .hero.is-light .navbar .navbar-dropdown .navbar-item:hover{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .hero.is-dark .navbar .navbar-dropdown .navbar-item:hover,html.theme--documenter-dark .content kbd.hero .navbar .navbar-dropdown .navbar-item:hover{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .hero.is-primary .navbar .navbar-dropdown .navbar-item:hover,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .navbar .navbar-dropdown .navbar-item:hover{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .hero.is-link .navbar .navbar-dropdown .navbar-item:hover{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .hero.is-info .navbar .navbar-dropdown .navbar-item:hover{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .hero.is-success .navbar .navbar-dropdown .navbar-item:hover{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .hero.is-warning .navbar .navbar-dropdown .navbar-item:hover{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .hero.is-danger .navbar .navbar-dropdown .navbar-item:hover{background-color:rgba(0,0,0,0)}html.theme--documenter-dark h1 .docs-heading-anchor,html.theme--documenter-dark h1 .docs-heading-anchor:hover,html.theme--documenter-dark h1 .docs-heading-anchor:visited,html.theme--documenter-dark h2 .docs-heading-anchor,html.theme--documenter-dark h2 .docs-heading-anchor:hover,html.theme--documenter-dark h2 .docs-heading-anchor:visited,html.theme--documenter-dark h3 .docs-heading-anchor,html.theme--documenter-dark h3 .docs-heading-anchor:hover,html.theme--documenter-dark h3 .docs-heading-anchor:visited,html.theme--documenter-dark h4 .docs-heading-anchor,html.theme--documenter-dark h4 .docs-heading-anchor:hover,html.theme--documenter-dark h4 .docs-heading-anchor:visited,html.theme--documenter-dark h5 .docs-heading-anchor,html.theme--documenter-dark h5 .docs-heading-anchor:hover,html.theme--documenter-dark h5 .docs-heading-anchor:visited,html.theme--documenter-dark h6 .docs-heading-anchor,html.theme--documenter-dark h6 .docs-heading-anchor:hover,html.theme--documenter-dark h6 .docs-heading-anchor:visited{color:#f2f2f2}html.theme--documenter-dark h1 .docs-heading-anchor-permalink,html.theme--documenter-dark h2 .docs-heading-anchor-permalink,html.theme--documenter-dark h3 .docs-heading-anchor-permalink,html.theme--documenter-dark h4 .docs-heading-anchor-permalink,html.theme--documenter-dark h5 .docs-heading-anchor-permalink,html.theme--documenter-dark h6 .docs-heading-anchor-permalink{visibility:hidden;vertical-align:middle;margin-left:0.5em;font-size:0.7rem}html.theme--documenter-dark h1 .docs-heading-anchor-permalink::before,html.theme--documenter-dark h2 .docs-heading-anchor-permalink::before,html.theme--documenter-dark h3 .docs-heading-anchor-permalink::before,html.theme--documenter-dark h4 .docs-heading-anchor-permalink::before,html.theme--documenter-dark h5 .docs-heading-anchor-permalink::before,html.theme--documenter-dark h6 .docs-heading-anchor-permalink::before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f0c1"}html.theme--documenter-dark h1:hover .docs-heading-anchor-permalink,html.theme--documenter-dark h2:hover .docs-heading-anchor-permalink,html.theme--documenter-dark h3:hover .docs-heading-anchor-permalink,html.theme--documenter-dark h4:hover .docs-heading-anchor-permalink,html.theme--documenter-dark h5:hover .docs-heading-anchor-permalink,html.theme--documenter-dark h6:hover .docs-heading-anchor-permalink{visibility:visible}html.theme--documenter-dark .docs-light-only{display:none !important}html.theme--documenter-dark pre{position:relative;overflow:hidden}html.theme--documenter-dark pre code,html.theme--documenter-dark pre code.hljs{padding:0 .75rem !important;overflow:auto;display:block}html.theme--documenter-dark pre code:first-of-type,html.theme--documenter-dark pre code.hljs:first-of-type{padding-top:0.5rem !important}html.theme--documenter-dark pre code:last-of-type,html.theme--documenter-dark pre code.hljs:last-of-type{padding-bottom:0.5rem !important}html.theme--documenter-dark pre .copy-button{opacity:0.2;transition:opacity 0.2s;position:absolute;right:0em;top:0em;padding:0.5em;width:2.5em;height:2.5em;background:transparent;border:none;font-family:"Font Awesome 6 Free";color:#fff;cursor:pointer;text-align:center}html.theme--documenter-dark pre .copy-button:focus,html.theme--documenter-dark pre .copy-button:hover{opacity:1;background:rgba(255,255,255,0.1);color:#1abc9c}html.theme--documenter-dark pre .copy-button.success{color:#259a12;opacity:1}html.theme--documenter-dark pre .copy-button.error{color:#cb3c33;opacity:1}html.theme--documenter-dark pre:hover .copy-button{opacity:1}html.theme--documenter-dark .admonition{background-color:#282f2f;border-style:solid;border-width:1px;border-color:#5e6d6f;border-radius:.4em;font-size:1rem}html.theme--documenter-dark .admonition strong{color:currentColor}html.theme--documenter-dark .admonition.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.admonition{font-size:.75rem}html.theme--documenter-dark .admonition.is-medium{font-size:1.25rem}html.theme--documenter-dark .admonition.is-large{font-size:1.5rem}html.theme--documenter-dark .admonition.is-default{background-color:#282f2f;border-color:#5e6d6f}html.theme--documenter-dark .admonition.is-default>.admonition-header{background-color:#5e6d6f;color:#fff}html.theme--documenter-dark .admonition.is-default>.admonition-body{color:#fff}html.theme--documenter-dark .admonition.is-info{background-color:#282f2f;border-color:#024c7d}html.theme--documenter-dark .admonition.is-info>.admonition-header{background-color:#024c7d;color:#fff}html.theme--documenter-dark .admonition.is-info>.admonition-body{color:#fff}html.theme--documenter-dark .admonition.is-success{background-color:#282f2f;border-color:#008438}html.theme--documenter-dark .admonition.is-success>.admonition-header{background-color:#008438;color:#fff}html.theme--documenter-dark .admonition.is-success>.admonition-body{color:#fff}html.theme--documenter-dark .admonition.is-warning{background-color:#282f2f;border-color:#ad8100}html.theme--documenter-dark .admonition.is-warning>.admonition-header{background-color:#ad8100;color:#fff}html.theme--documenter-dark .admonition.is-warning>.admonition-body{color:#fff}html.theme--documenter-dark .admonition.is-danger{background-color:#282f2f;border-color:#9e1b0d}html.theme--documenter-dark .admonition.is-danger>.admonition-header{background-color:#9e1b0d;color:#fff}html.theme--documenter-dark .admonition.is-danger>.admonition-body{color:#fff}html.theme--documenter-dark .admonition.is-compat{background-color:#282f2f;border-color:#137886}html.theme--documenter-dark .admonition.is-compat>.admonition-header{background-color:#137886;color:#fff}html.theme--documenter-dark .admonition.is-compat>.admonition-body{color:#fff}html.theme--documenter-dark .admonition-header{color:#fff;background-color:#5e6d6f;align-items:center;font-weight:700;justify-content:space-between;line-height:1.25;padding:0.5rem .75rem;position:relative}html.theme--documenter-dark .admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;margin-right:.75rem;content:"\f06a"}html.theme--documenter-dark details.admonition.is-details>.admonition-header{list-style:none}html.theme--documenter-dark details.admonition.is-details>.admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f055"}html.theme--documenter-dark details.admonition.is-details[open]>.admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f056"}html.theme--documenter-dark .admonition-body{color:#fff;padding:0.5rem .75rem}html.theme--documenter-dark .admonition-body pre{background-color:#282f2f}html.theme--documenter-dark .admonition-body code{background-color:rgba(255,255,255,0.05)}html.theme--documenter-dark .docstring{margin-bottom:1em;background-color:rgba(0,0,0,0);border:1px solid #5e6d6f;box-shadow:none;max-width:100%}html.theme--documenter-dark .docstring>header{cursor:pointer;display:flex;flex-grow:1;align-items:stretch;padding:0.5rem .75rem;background-color:#282f2f;box-shadow:0 0.125em 0.25em rgba(10,10,10,0.1);box-shadow:none;border-bottom:1px solid #5e6d6f}html.theme--documenter-dark .docstring>header code{background-color:transparent}html.theme--documenter-dark .docstring>header .docstring-article-toggle-button{min-width:1.1rem;padding:0.2rem 0.2rem 0.2rem 0}html.theme--documenter-dark .docstring>header .docstring-binding{margin-right:0.3em}html.theme--documenter-dark .docstring>header .docstring-category{margin-left:0.3em}html.theme--documenter-dark .docstring>section{position:relative;padding:.75rem .75rem;border-bottom:1px solid #5e6d6f}html.theme--documenter-dark .docstring>section:last-child{border-bottom:none}html.theme--documenter-dark .docstring>section>a.docs-sourcelink{transition:opacity 0.3s;opacity:0;position:absolute;right:.375rem;bottom:.375rem}html.theme--documenter-dark .docstring>section>a.docs-sourcelink:focus{opacity:1 !important}html.theme--documenter-dark .docstring:hover>section>a.docs-sourcelink{opacity:0.2}html.theme--documenter-dark .docstring:focus-within>section>a.docs-sourcelink{opacity:0.2}html.theme--documenter-dark .docstring>section:hover a.docs-sourcelink{opacity:1}html.theme--documenter-dark .documenter-example-output{background-color:#1f2424}html.theme--documenter-dark .outdated-warning-overlay{position:fixed;top:0;left:0;right:0;box-shadow:0 0 10px rgba(0,0,0,0.3);z-index:999;background-color:#282f2f;color:#fff;border-bottom:3px solid #9e1b0d;padding:10px 35px;text-align:center;font-size:15px}html.theme--documenter-dark .outdated-warning-overlay .outdated-warning-closer{position:absolute;top:calc(50% - 10px);right:18px;cursor:pointer;width:12px}html.theme--documenter-dark .outdated-warning-overlay a{color:#1abc9c}html.theme--documenter-dark .outdated-warning-overlay a:hover{color:#1dd2af}html.theme--documenter-dark .content pre{border:1px solid #5e6d6f}html.theme--documenter-dark .content code{font-weight:inherit}html.theme--documenter-dark .content a code{color:#1abc9c}html.theme--documenter-dark .content h1 code,html.theme--documenter-dark .content h2 code,html.theme--documenter-dark .content h3 code,html.theme--documenter-dark .content h4 code,html.theme--documenter-dark .content h5 code,html.theme--documenter-dark .content h6 code{color:#f2f2f2}html.theme--documenter-dark .content table{display:block;width:initial;max-width:100%;overflow-x:auto}html.theme--documenter-dark .content blockquote>ul:first-child,html.theme--documenter-dark .content blockquote>ol:first-child,html.theme--documenter-dark .content .admonition-body>ul:first-child,html.theme--documenter-dark .content .admonition-body>ol:first-child{margin-top:0}html.theme--documenter-dark pre,html.theme--documenter-dark code{font-variant-ligatures:no-contextual}html.theme--documenter-dark .breadcrumb a.is-disabled{cursor:default;pointer-events:none}html.theme--documenter-dark .breadcrumb a.is-disabled,html.theme--documenter-dark .breadcrumb a.is-disabled:hover{color:#f2f2f2}html.theme--documenter-dark .hljs{background:initial !important}html.theme--documenter-dark .katex .katex-mathml{top:0;right:0}html.theme--documenter-dark .katex-display,html.theme--documenter-dark mjx-container,html.theme--documenter-dark .MathJax_Display{margin:0.5em 0 !important}html.theme--documenter-dark html{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto}html.theme--documenter-dark li.no-marker{list-style:none}html.theme--documenter-dark #documenter .docs-main>article{overflow-wrap:break-word}html.theme--documenter-dark #documenter .docs-main>article .math-container{overflow-x:auto;overflow-y:hidden}@media screen and (min-width: 1056px){html.theme--documenter-dark #documenter .docs-main{max-width:52rem;margin-left:20rem;padding-right:1rem}}@media screen and (max-width: 1055px){html.theme--documenter-dark #documenter .docs-main{width:100%}html.theme--documenter-dark #documenter .docs-main>article{max-width:52rem;margin-left:auto;margin-right:auto;margin-bottom:1rem;padding:0 1rem}html.theme--documenter-dark #documenter .docs-main>header,html.theme--documenter-dark #documenter .docs-main>nav{max-width:100%;width:100%;margin:0}}html.theme--documenter-dark #documenter .docs-main header.docs-navbar{background-color:#1f2424;border-bottom:1px solid #5e6d6f;z-index:2;min-height:4rem;margin-bottom:1rem;display:flex}html.theme--documenter-dark #documenter .docs-main header.docs-navbar .breadcrumb{flex-grow:1}html.theme--documenter-dark #documenter .docs-main header.docs-navbar .docs-sidebar-button{display:block;font-size:1.5rem;padding-bottom:0.1rem;margin-right:1rem}html.theme--documenter-dark #documenter .docs-main header.docs-navbar .docs-right{display:flex;white-space:nowrap;gap:1rem;align-items:center}html.theme--documenter-dark #documenter .docs-main header.docs-navbar .docs-right .docs-icon,html.theme--documenter-dark #documenter .docs-main header.docs-navbar .docs-right .docs-label{display:inline-block}html.theme--documenter-dark #documenter .docs-main header.docs-navbar .docs-right .docs-label{padding:0;margin-left:0.3em}@media screen and (max-width: 1055px){html.theme--documenter-dark #documenter .docs-main header.docs-navbar .docs-right .docs-navbar-link{margin-left:0.4rem;margin-right:0.4rem}}html.theme--documenter-dark #documenter .docs-main header.docs-navbar>*{margin:auto 0}@media screen and (max-width: 1055px){html.theme--documenter-dark #documenter .docs-main header.docs-navbar{position:sticky;top:0;padding:0 1rem;transition-property:top, box-shadow;-webkit-transition-property:top, box-shadow;transition-duration:0.3s;-webkit-transition-duration:0.3s}html.theme--documenter-dark #documenter .docs-main header.docs-navbar.headroom--not-top{box-shadow:.2rem 0rem .4rem #171717;transition-duration:0.7s;-webkit-transition-duration:0.7s}html.theme--documenter-dark #documenter .docs-main header.docs-navbar.headroom--unpinned.headroom--not-top.headroom--not-bottom{top:-4.5rem;transition-duration:0.7s;-webkit-transition-duration:0.7s}}html.theme--documenter-dark #documenter .docs-main section.footnotes{border-top:1px solid #5e6d6f}html.theme--documenter-dark #documenter .docs-main section.footnotes li .tag:first-child,html.theme--documenter-dark #documenter .docs-main section.footnotes li .docstring>section>a.docs-sourcelink:first-child,html.theme--documenter-dark #documenter .docs-main section.footnotes li .content kbd:first-child,html.theme--documenter-dark .content #documenter .docs-main section.footnotes li kbd:first-child{margin-right:1em;margin-bottom:0.4em}html.theme--documenter-dark #documenter .docs-main .docs-footer{display:flex;flex-wrap:wrap;margin-left:0;margin-right:0;border-top:1px solid #5e6d6f;padding-top:1rem;padding-bottom:1rem}@media screen and (max-width: 1055px){html.theme--documenter-dark #documenter .docs-main .docs-footer{padding-left:1rem;padding-right:1rem}}html.theme--documenter-dark #documenter .docs-main .docs-footer .docs-footer-nextpage,html.theme--documenter-dark #documenter .docs-main .docs-footer .docs-footer-prevpage{flex-grow:1}html.theme--documenter-dark #documenter .docs-main .docs-footer .docs-footer-nextpage{text-align:right}html.theme--documenter-dark #documenter .docs-main .docs-footer .flexbox-break{flex-basis:100%;height:0}html.theme--documenter-dark #documenter .docs-main .docs-footer .footer-message{font-size:0.8em;margin:0.5em auto 0 auto;text-align:center}html.theme--documenter-dark #documenter .docs-sidebar{display:flex;flex-direction:column;color:#fff;background-color:#282f2f;border-right:1px solid #5e6d6f;padding:0;flex:0 0 18rem;z-index:5;font-size:1rem;position:fixed;left:-18rem;width:18rem;height:100%;transition:left 0.3s}html.theme--documenter-dark #documenter .docs-sidebar.visible{left:0;box-shadow:.4rem 0rem .8rem #171717}@media screen and (min-width: 1056px){html.theme--documenter-dark #documenter .docs-sidebar.visible{box-shadow:none}}@media screen and (min-width: 1056px){html.theme--documenter-dark #documenter .docs-sidebar{left:0;top:0}}html.theme--documenter-dark #documenter .docs-sidebar .docs-logo{margin-top:1rem;padding:0 1rem}html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img{max-height:6rem;margin:auto}html.theme--documenter-dark #documenter .docs-sidebar .docs-package-name{flex-shrink:0;font-size:1.5rem;font-weight:700;text-align:center;white-space:nowrap;overflow:hidden;padding:0.5rem 0}html.theme--documenter-dark #documenter .docs-sidebar .docs-package-name .docs-autofit{max-width:16.2rem}html.theme--documenter-dark #documenter .docs-sidebar .docs-package-name a,html.theme--documenter-dark #documenter .docs-sidebar .docs-package-name a:hover{color:#fff}html.theme--documenter-dark #documenter .docs-sidebar .docs-version-selector{border-top:1px solid #5e6d6f;display:none;padding:0.5rem}html.theme--documenter-dark #documenter .docs-sidebar .docs-version-selector.visible{display:flex}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu{flex-grow:1;user-select:none;border-top:1px solid #5e6d6f;padding-bottom:1.5rem}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu>li>.tocitem{font-weight:bold}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu>li li{font-size:.95rem;margin-left:1em;border-left:1px solid #5e6d6f}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu input.collapse-toggle{display:none}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu ul.collapsed{display:none}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu input:checked~ul.collapsed{display:block}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu label.tocitem{display:flex}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu label.tocitem .docs-label{flex-grow:2}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu label.tocitem .docs-chevron{display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1;font-size:.75rem;margin-left:1rem;margin-top:auto;margin-bottom:auto}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu label.tocitem .docs-chevron::before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f054"}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu input:checked~label.tocitem .docs-chevron::before{content:"\f078"}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu .tocitem{display:block;padding:0.5rem 0.5rem}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu .tocitem,html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu .tocitem:hover{color:#fff;background:#282f2f}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu a.tocitem:hover,html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu label.tocitem:hover{color:#fff;background-color:#32393a}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu li.is-active{border-top:1px solid #5e6d6f;border-bottom:1px solid #5e6d6f;background-color:#1f2424}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu li.is-active .tocitem,html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu li.is-active .tocitem:hover{background-color:#1f2424;color:#fff}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu li.is-active ul.internal .tocitem:hover{background-color:#32393a;color:#fff}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu>li.is-active:first-child{border-top:none}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu ul.internal{margin:0 0.5rem 0.5rem;border-top:1px solid #5e6d6f}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu ul.internal li{font-size:.85rem;border-left:none;margin-left:0;margin-top:0.5rem}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu ul.internal .tocitem{width:100%;padding:0}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu ul.internal .tocitem::before{content:"⚬";margin-right:0.4em}html.theme--documenter-dark #documenter .docs-sidebar form.docs-search{margin:auto;margin-top:0.5rem;margin-bottom:0.5rem}html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input{width:14.4rem}html.theme--documenter-dark #documenter .docs-sidebar #documenter-search-query{color:#868c98;width:14.4rem;box-shadow:inset 0 1px 2px rgba(10,10,10,0.1)}@media screen and (min-width: 1056px){html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu{overflow-y:auto;-webkit-overflow-scroll:touch}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar{width:.3rem;background:none}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar-thumb{border-radius:5px 0px 0px 5px;background:#3b4445}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar-thumb:hover{background:#4e5a5c}}@media screen and (max-width: 1055px){html.theme--documenter-dark #documenter .docs-sidebar{overflow-y:auto;-webkit-overflow-scroll:touch}html.theme--documenter-dark #documenter .docs-sidebar::-webkit-scrollbar{width:.3rem;background:none}html.theme--documenter-dark #documenter .docs-sidebar::-webkit-scrollbar-thumb{border-radius:5px 0px 0px 5px;background:#3b4445}html.theme--documenter-dark #documenter .docs-sidebar::-webkit-scrollbar-thumb:hover{background:#4e5a5c}}html.theme--documenter-dark kbd.search-modal-key-hints{border-radius:0.25rem;border:1px solid rgba(245,245,245,0.6);box-shadow:0 2px 0 1px rgba(245,245,245,0.6);cursor:default;font-size:0.9rem;line-height:1.5;min-width:0.75rem;text-align:center;padding:0.1rem 0.3rem;position:relative;top:-1px}html.theme--documenter-dark .search-min-width-50{min-width:50%}html.theme--documenter-dark .search-min-height-100{min-height:100%}html.theme--documenter-dark .search-modal-card-body{max-height:calc(100vh - 15rem)}html.theme--documenter-dark .search-result-link{border-radius:0.7em;transition:all 300ms}html.theme--documenter-dark .search-result-link:hover,html.theme--documenter-dark .search-result-link:focus{background-color:rgba(0,128,128,0.1)}html.theme--documenter-dark .search-result-link .property-search-result-badge,html.theme--documenter-dark .search-result-link .search-filter{transition:all 300ms}html.theme--documenter-dark .property-search-result-badge,html.theme--documenter-dark .search-filter{padding:0.15em 0.5em;font-size:0.8em;font-style:italic;text-transform:none !important;line-height:1.5;color:#f5f5f5;background-color:rgba(51,65,85,0.501961);border-radius:0.6rem}html.theme--documenter-dark .search-result-link:hover .property-search-result-badge,html.theme--documenter-dark .search-result-link:hover .search-filter,html.theme--documenter-dark .search-result-link:focus .property-search-result-badge,html.theme--documenter-dark .search-result-link:focus .search-filter{color:#333;background-color:#f1f5f9}html.theme--documenter-dark .search-filter{color:#333;background-color:#f5f5f5;transition:all 300ms}html.theme--documenter-dark .search-filter:hover,html.theme--documenter-dark .search-filter:focus{color:#333}html.theme--documenter-dark .search-filter-selected{color:#f5f5f5;background-color:rgba(139,0,139,0.5)}html.theme--documenter-dark .search-filter-selected:hover,html.theme--documenter-dark .search-filter-selected:focus{color:#f5f5f5}html.theme--documenter-dark .search-result-highlight{background-color:#ffdd57;color:black}html.theme--documenter-dark .search-divider{border-bottom:1px solid #5e6d6f}html.theme--documenter-dark .search-result-title{width:85%;color:#f5f5f5}html.theme--documenter-dark .search-result-code-title{font-size:0.875rem;font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace}html.theme--documenter-dark #search-modal .modal-card-body::-webkit-scrollbar,html.theme--documenter-dark #search-modal .filter-tabs::-webkit-scrollbar{height:10px;width:10px;background-color:transparent}html.theme--documenter-dark #search-modal .modal-card-body::-webkit-scrollbar-thumb,html.theme--documenter-dark #search-modal .filter-tabs::-webkit-scrollbar-thumb{background-color:gray;border-radius:1rem}html.theme--documenter-dark #search-modal .modal-card-body::-webkit-scrollbar-track,html.theme--documenter-dark #search-modal .filter-tabs::-webkit-scrollbar-track{-webkit-box-shadow:inset 0 0 6px rgba(0,0,0,0.6);background-color:transparent}html.theme--documenter-dark .w-100{width:100%}html.theme--documenter-dark .gap-2{gap:0.5rem}html.theme--documenter-dark .gap-4{gap:1rem}html.theme--documenter-dark .gap-8{gap:2rem}html.theme--documenter-dark{background-color:#1f2424;font-size:16px;min-width:300px;overflow-x:auto;overflow-y:scroll;text-rendering:optimizeLegibility;text-size-adjust:100%}html.theme--documenter-dark .ansi span.sgr1{font-weight:bolder}html.theme--documenter-dark .ansi span.sgr2{font-weight:lighter}html.theme--documenter-dark .ansi span.sgr3{font-style:italic}html.theme--documenter-dark .ansi span.sgr4{text-decoration:underline}html.theme--documenter-dark .ansi span.sgr7{color:#1f2424;background-color:#fff}html.theme--documenter-dark .ansi span.sgr8{color:transparent}html.theme--documenter-dark .ansi span.sgr8 span{color:transparent}html.theme--documenter-dark .ansi span.sgr9{text-decoration:line-through}html.theme--documenter-dark .ansi span.sgr30{color:#242424}html.theme--documenter-dark .ansi span.sgr31{color:#f6705f}html.theme--documenter-dark .ansi span.sgr32{color:#4fb43a}html.theme--documenter-dark .ansi span.sgr33{color:#f4c72f}html.theme--documenter-dark .ansi span.sgr34{color:#7587f0}html.theme--documenter-dark .ansi span.sgr35{color:#bc89d3}html.theme--documenter-dark .ansi span.sgr36{color:#49b6ca}html.theme--documenter-dark .ansi span.sgr37{color:#b3bdbe}html.theme--documenter-dark .ansi span.sgr40{background-color:#242424}html.theme--documenter-dark .ansi span.sgr41{background-color:#f6705f}html.theme--documenter-dark .ansi span.sgr42{background-color:#4fb43a}html.theme--documenter-dark .ansi span.sgr43{background-color:#f4c72f}html.theme--documenter-dark .ansi span.sgr44{background-color:#7587f0}html.theme--documenter-dark .ansi span.sgr45{background-color:#bc89d3}html.theme--documenter-dark .ansi span.sgr46{background-color:#49b6ca}html.theme--documenter-dark .ansi span.sgr47{background-color:#b3bdbe}html.theme--documenter-dark .ansi span.sgr90{color:#92a0a2}html.theme--documenter-dark .ansi span.sgr91{color:#ff8674}html.theme--documenter-dark .ansi span.sgr92{color:#79d462}html.theme--documenter-dark .ansi span.sgr93{color:#ffe76b}html.theme--documenter-dark .ansi span.sgr94{color:#8a98ff}html.theme--documenter-dark .ansi span.sgr95{color:#d2a4e6}html.theme--documenter-dark .ansi span.sgr96{color:#6bc8db}html.theme--documenter-dark .ansi span.sgr97{color:#ecf0f1}html.theme--documenter-dark .ansi span.sgr100{background-color:#92a0a2}html.theme--documenter-dark .ansi span.sgr101{background-color:#ff8674}html.theme--documenter-dark .ansi span.sgr102{background-color:#79d462}html.theme--documenter-dark .ansi span.sgr103{background-color:#ffe76b}html.theme--documenter-dark .ansi span.sgr104{background-color:#8a98ff}html.theme--documenter-dark .ansi span.sgr105{background-color:#d2a4e6}html.theme--documenter-dark .ansi span.sgr106{background-color:#6bc8db}html.theme--documenter-dark .ansi span.sgr107{background-color:#ecf0f1}html.theme--documenter-dark code.language-julia-repl>span.hljs-meta{color:#4fb43a;font-weight:bolder}html.theme--documenter-dark .hljs{background:#2b2b2b;color:#f8f8f2}html.theme--documenter-dark .hljs-comment,html.theme--documenter-dark .hljs-quote{color:#d4d0ab}html.theme--documenter-dark .hljs-variable,html.theme--documenter-dark .hljs-template-variable,html.theme--documenter-dark .hljs-tag,html.theme--documenter-dark .hljs-name,html.theme--documenter-dark .hljs-selector-id,html.theme--documenter-dark .hljs-selector-class,html.theme--documenter-dark .hljs-regexp,html.theme--documenter-dark .hljs-deletion{color:#ffa07a}html.theme--documenter-dark .hljs-number,html.theme--documenter-dark .hljs-built_in,html.theme--documenter-dark .hljs-literal,html.theme--documenter-dark .hljs-type,html.theme--documenter-dark .hljs-params,html.theme--documenter-dark .hljs-meta,html.theme--documenter-dark .hljs-link{color:#f5ab35}html.theme--documenter-dark .hljs-attribute{color:#ffd700}html.theme--documenter-dark .hljs-string,html.theme--documenter-dark .hljs-symbol,html.theme--documenter-dark .hljs-bullet,html.theme--documenter-dark .hljs-addition{color:#abe338}html.theme--documenter-dark .hljs-title,html.theme--documenter-dark .hljs-section{color:#00e0e0}html.theme--documenter-dark .hljs-keyword,html.theme--documenter-dark .hljs-selector-tag{color:#dcc6e0}html.theme--documenter-dark .hljs-emphasis{font-style:italic}html.theme--documenter-dark .hljs-strong{font-weight:bold}@media screen and (-ms-high-contrast: active){html.theme--documenter-dark .hljs-addition,html.theme--documenter-dark .hljs-attribute,html.theme--documenter-dark .hljs-built_in,html.theme--documenter-dark .hljs-bullet,html.theme--documenter-dark .hljs-comment,html.theme--documenter-dark .hljs-link,html.theme--documenter-dark .hljs-literal,html.theme--documenter-dark .hljs-meta,html.theme--documenter-dark .hljs-number,html.theme--documenter-dark .hljs-params,html.theme--documenter-dark .hljs-string,html.theme--documenter-dark .hljs-symbol,html.theme--documenter-dark .hljs-type,html.theme--documenter-dark .hljs-quote{color:highlight}html.theme--documenter-dark .hljs-keyword,html.theme--documenter-dark .hljs-selector-tag{font-weight:bold}}html.theme--documenter-dark .hljs-subst{color:#f8f8f2} diff --git a/previews/PR1107/assets/themes/documenter-light.css b/previews/PR1107/assets/themes/documenter-light.css new file mode 100644 index 000000000..60a317a4c --- /dev/null +++ b/previews/PR1107/assets/themes/documenter-light.css @@ -0,0 +1,9 @@ +.pagination-previous,.pagination-next,.pagination-link,.pagination-ellipsis,.file-cta,.file-name,.select select,.textarea,.input,#documenter .docs-sidebar form.docs-search>input,.button{-moz-appearance:none;-webkit-appearance:none;align-items:center;border:1px solid transparent;border-radius:4px;box-shadow:none;display:inline-flex;font-size:1rem;height:2.5em;justify-content:flex-start;line-height:1.5;padding-bottom:calc(0.5em - 1px);padding-left:calc(0.75em - 1px);padding-right:calc(0.75em - 1px);padding-top:calc(0.5em - 1px);position:relative;vertical-align:top}.pagination-previous:focus,.pagination-next:focus,.pagination-link:focus,.pagination-ellipsis:focus,.file-cta:focus,.file-name:focus,.select select:focus,.textarea:focus,.input:focus,#documenter .docs-sidebar form.docs-search>input:focus,.button:focus,.is-focused.pagination-previous,.is-focused.pagination-next,.is-focused.pagination-link,.is-focused.pagination-ellipsis,.is-focused.file-cta,.is-focused.file-name,.select select.is-focused,.is-focused.textarea,.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.is-focused.button,.pagination-previous:active,.pagination-next:active,.pagination-link:active,.pagination-ellipsis:active,.file-cta:active,.file-name:active,.select select:active,.textarea:active,.input:active,#documenter .docs-sidebar form.docs-search>input:active,.button:active,.is-active.pagination-previous,.is-active.pagination-next,.is-active.pagination-link,.is-active.pagination-ellipsis,.is-active.file-cta,.is-active.file-name,.select select.is-active,.is-active.textarea,.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active,.is-active.button{outline:none}.pagination-previous[disabled],.pagination-next[disabled],.pagination-link[disabled],.pagination-ellipsis[disabled],.file-cta[disabled],.file-name[disabled],.select select[disabled],.textarea[disabled],.input[disabled],#documenter .docs-sidebar form.docs-search>input[disabled],.button[disabled],fieldset[disabled] .pagination-previous,fieldset[disabled] .pagination-next,fieldset[disabled] .pagination-link,fieldset[disabled] .pagination-ellipsis,fieldset[disabled] .file-cta,fieldset[disabled] .file-name,fieldset[disabled] .select select,.select fieldset[disabled] select,fieldset[disabled] .textarea,fieldset[disabled] .input,fieldset[disabled] #documenter .docs-sidebar form.docs-search>input,#documenter .docs-sidebar fieldset[disabled] form.docs-search>input,fieldset[disabled] .button{cursor:not-allowed}.tabs,.pagination-previous,.pagination-next,.pagination-link,.pagination-ellipsis,.breadcrumb,.file,.button,.is-unselectable{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.navbar-link:not(.is-arrowless)::after,.select:not(.is-multiple):not(.is-loading)::after{border:3px solid rgba(0,0,0,0);border-radius:2px;border-right:0;border-top:0;content:" ";display:block;height:0.625em;margin-top:-0.4375em;pointer-events:none;position:absolute;top:50%;transform:rotate(-45deg);transform-origin:center;width:0.625em}.admonition:not(:last-child),.tabs:not(:last-child),.pagination:not(:last-child),.message:not(:last-child),.level:not(:last-child),.breadcrumb:not(:last-child),.block:not(:last-child),.title:not(:last-child),.subtitle:not(:last-child),.table-container:not(:last-child),.table:not(:last-child),.progress:not(:last-child),.notification:not(:last-child),.content:not(:last-child),.box:not(:last-child){margin-bottom:1.5rem}.modal-close,.delete{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-moz-appearance:none;-webkit-appearance:none;background-color:rgba(10,10,10,0.2);border:none;border-radius:9999px;cursor:pointer;pointer-events:auto;display:inline-block;flex-grow:0;flex-shrink:0;font-size:0;height:20px;max-height:20px;max-width:20px;min-height:20px;min-width:20px;outline:none;position:relative;vertical-align:top;width:20px}.modal-close::before,.delete::before,.modal-close::after,.delete::after{background-color:#fff;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}.modal-close::before,.delete::before{height:2px;width:50%}.modal-close::after,.delete::after{height:50%;width:2px}.modal-close:hover,.delete:hover,.modal-close:focus,.delete:focus{background-color:rgba(10,10,10,0.3)}.modal-close:active,.delete:active{background-color:rgba(10,10,10,0.4)}.is-small.modal-close,#documenter .docs-sidebar form.docs-search>input.modal-close,.is-small.delete,#documenter .docs-sidebar form.docs-search>input.delete{height:16px;max-height:16px;max-width:16px;min-height:16px;min-width:16px;width:16px}.is-medium.modal-close,.is-medium.delete{height:24px;max-height:24px;max-width:24px;min-height:24px;min-width:24px;width:24px}.is-large.modal-close,.is-large.delete{height:32px;max-height:32px;max-width:32px;min-height:32px;min-width:32px;width:32px}.control.is-loading::after,.select.is-loading::after,.loader,.button.is-loading::after{animation:spinAround 500ms infinite linear;border:2px solid #dbdbdb;border-radius:9999px;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:1em;position:relative;width:1em}.hero-video,.modal-background,.modal,.image.is-square img,#documenter .docs-sidebar .docs-logo>img.is-square img,.image.is-square .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-square .has-ratio,.image.is-1by1 img,#documenter .docs-sidebar .docs-logo>img.is-1by1 img,.image.is-1by1 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-1by1 .has-ratio,.image.is-5by4 img,#documenter .docs-sidebar .docs-logo>img.is-5by4 img,.image.is-5by4 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-5by4 .has-ratio,.image.is-4by3 img,#documenter .docs-sidebar .docs-logo>img.is-4by3 img,.image.is-4by3 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-4by3 .has-ratio,.image.is-3by2 img,#documenter .docs-sidebar .docs-logo>img.is-3by2 img,.image.is-3by2 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-3by2 .has-ratio,.image.is-5by3 img,#documenter .docs-sidebar .docs-logo>img.is-5by3 img,.image.is-5by3 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-5by3 .has-ratio,.image.is-16by9 img,#documenter .docs-sidebar .docs-logo>img.is-16by9 img,.image.is-16by9 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-16by9 .has-ratio,.image.is-2by1 img,#documenter .docs-sidebar .docs-logo>img.is-2by1 img,.image.is-2by1 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-2by1 .has-ratio,.image.is-3by1 img,#documenter .docs-sidebar .docs-logo>img.is-3by1 img,.image.is-3by1 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-3by1 .has-ratio,.image.is-4by5 img,#documenter .docs-sidebar .docs-logo>img.is-4by5 img,.image.is-4by5 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-4by5 .has-ratio,.image.is-3by4 img,#documenter .docs-sidebar .docs-logo>img.is-3by4 img,.image.is-3by4 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-3by4 .has-ratio,.image.is-2by3 img,#documenter .docs-sidebar .docs-logo>img.is-2by3 img,.image.is-2by3 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-2by3 .has-ratio,.image.is-3by5 img,#documenter .docs-sidebar .docs-logo>img.is-3by5 img,.image.is-3by5 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-3by5 .has-ratio,.image.is-9by16 img,#documenter .docs-sidebar .docs-logo>img.is-9by16 img,.image.is-9by16 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-9by16 .has-ratio,.image.is-1by2 img,#documenter .docs-sidebar .docs-logo>img.is-1by2 img,.image.is-1by2 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-1by2 .has-ratio,.image.is-1by3 img,#documenter .docs-sidebar .docs-logo>img.is-1by3 img,.image.is-1by3 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-1by3 .has-ratio,.is-overlay{bottom:0;left:0;position:absolute;right:0;top:0}.navbar-burger{-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;color:currentColor;font-family:inherit;font-size:1em;margin:0;padding:0}.has-text-white{color:#fff !important}a.has-text-white:hover,a.has-text-white:focus{color:#e6e6e6 !important}.has-background-white{background-color:#fff !important}.has-text-black{color:#0a0a0a !important}a.has-text-black:hover,a.has-text-black:focus{color:#000 !important}.has-background-black{background-color:#0a0a0a !important}.has-text-light{color:#f5f5f5 !important}a.has-text-light:hover,a.has-text-light:focus{color:#dbdbdb !important}.has-background-light{background-color:#f5f5f5 !important}.has-text-dark{color:#363636 !important}a.has-text-dark:hover,a.has-text-dark:focus{color:#1c1c1c !important}.has-background-dark{background-color:#363636 !important}.has-text-primary{color:#4eb5de !important}a.has-text-primary:hover,a.has-text-primary:focus{color:#27a1d2 !important}.has-background-primary{background-color:#4eb5de !important}.has-text-primary-light{color:#eef8fc !important}a.has-text-primary-light:hover,a.has-text-primary-light:focus{color:#c3e6f4 !important}.has-background-primary-light{background-color:#eef8fc !important}.has-text-primary-dark{color:#1a6d8e !important}a.has-text-primary-dark:hover,a.has-text-primary-dark:focus{color:#228eb9 !important}.has-background-primary-dark{background-color:#1a6d8e !important}.has-text-link{color:#2e63b8 !important}a.has-text-link:hover,a.has-text-link:focus{color:#244d8f !important}.has-background-link{background-color:#2e63b8 !important}.has-text-link-light{color:#eff3fb !important}a.has-text-link-light:hover,a.has-text-link-light:focus{color:#c6d6f1 !important}.has-background-link-light{background-color:#eff3fb !important}.has-text-link-dark{color:#3169c4 !important}a.has-text-link-dark:hover,a.has-text-link-dark:focus{color:#5485d4 !important}.has-background-link-dark{background-color:#3169c4 !important}.has-text-info{color:#209cee !important}a.has-text-info:hover,a.has-text-info:focus{color:#1081cb !important}.has-background-info{background-color:#209cee !important}.has-text-info-light{color:#ecf7fe !important}a.has-text-info-light:hover,a.has-text-info-light:focus{color:#bde2fa !important}.has-background-info-light{background-color:#ecf7fe !important}.has-text-info-dark{color:#0e72b4 !important}a.has-text-info-dark:hover,a.has-text-info-dark:focus{color:#1190e3 !important}.has-background-info-dark{background-color:#0e72b4 !important}.has-text-success{color:#22c35b !important}a.has-text-success:hover,a.has-text-success:focus{color:#1a9847 !important}.has-background-success{background-color:#22c35b !important}.has-text-success-light{color:#eefcf3 !important}a.has-text-success-light:hover,a.has-text-success-light:focus{color:#c2f4d4 !important}.has-background-success-light{background-color:#eefcf3 !important}.has-text-success-dark{color:#198f43 !important}a.has-text-success-dark:hover,a.has-text-success-dark:focus{color:#21bb57 !important}.has-background-success-dark{background-color:#198f43 !important}.has-text-warning{color:#ffdd57 !important}a.has-text-warning:hover,a.has-text-warning:focus{color:#ffd324 !important}.has-background-warning{background-color:#ffdd57 !important}.has-text-warning-light{color:#fffbeb !important}a.has-text-warning-light:hover,a.has-text-warning-light:focus{color:#fff1b8 !important}.has-background-warning-light{background-color:#fffbeb !important}.has-text-warning-dark{color:#947600 !important}a.has-text-warning-dark:hover,a.has-text-warning-dark:focus{color:#c79f00 !important}.has-background-warning-dark{background-color:#947600 !important}.has-text-danger{color:#da0b00 !important}a.has-text-danger:hover,a.has-text-danger:focus{color:#a70800 !important}.has-background-danger{background-color:#da0b00 !important}.has-text-danger-light{color:#ffeceb !important}a.has-text-danger-light:hover,a.has-text-danger-light:focus{color:#ffbbb8 !important}.has-background-danger-light{background-color:#ffeceb !important}.has-text-danger-dark{color:#f50c00 !important}a.has-text-danger-dark:hover,a.has-text-danger-dark:focus{color:#ff3429 !important}.has-background-danger-dark{background-color:#f50c00 !important}.has-text-black-bis{color:#121212 !important}.has-background-black-bis{background-color:#121212 !important}.has-text-black-ter{color:#242424 !important}.has-background-black-ter{background-color:#242424 !important}.has-text-grey-darker{color:#363636 !important}.has-background-grey-darker{background-color:#363636 !important}.has-text-grey-dark{color:#4a4a4a !important}.has-background-grey-dark{background-color:#4a4a4a !important}.has-text-grey{color:#6b6b6b !important}.has-background-grey{background-color:#6b6b6b !important}.has-text-grey-light{color:#b5b5b5 !important}.has-background-grey-light{background-color:#b5b5b5 !important}.has-text-grey-lighter{color:#dbdbdb !important}.has-background-grey-lighter{background-color:#dbdbdb !important}.has-text-white-ter{color:#f5f5f5 !important}.has-background-white-ter{background-color:#f5f5f5 !important}.has-text-white-bis{color:#fafafa !important}.has-background-white-bis{background-color:#fafafa !important}.is-flex-direction-row{flex-direction:row !important}.is-flex-direction-row-reverse{flex-direction:row-reverse !important}.is-flex-direction-column{flex-direction:column !important}.is-flex-direction-column-reverse{flex-direction:column-reverse !important}.is-flex-wrap-nowrap{flex-wrap:nowrap !important}.is-flex-wrap-wrap{flex-wrap:wrap !important}.is-flex-wrap-wrap-reverse{flex-wrap:wrap-reverse !important}.is-justify-content-flex-start{justify-content:flex-start !important}.is-justify-content-flex-end{justify-content:flex-end !important}.is-justify-content-center{justify-content:center !important}.is-justify-content-space-between{justify-content:space-between !important}.is-justify-content-space-around{justify-content:space-around !important}.is-justify-content-space-evenly{justify-content:space-evenly !important}.is-justify-content-start{justify-content:start !important}.is-justify-content-end{justify-content:end !important}.is-justify-content-left{justify-content:left !important}.is-justify-content-right{justify-content:right !important}.is-align-content-flex-start{align-content:flex-start !important}.is-align-content-flex-end{align-content:flex-end !important}.is-align-content-center{align-content:center !important}.is-align-content-space-between{align-content:space-between !important}.is-align-content-space-around{align-content:space-around !important}.is-align-content-space-evenly{align-content:space-evenly !important}.is-align-content-stretch{align-content:stretch !important}.is-align-content-start{align-content:start !important}.is-align-content-end{align-content:end !important}.is-align-content-baseline{align-content:baseline !important}.is-align-items-stretch{align-items:stretch !important}.is-align-items-flex-start{align-items:flex-start !important}.is-align-items-flex-end{align-items:flex-end !important}.is-align-items-center{align-items:center !important}.is-align-items-baseline{align-items:baseline !important}.is-align-items-start{align-items:start !important}.is-align-items-end{align-items:end !important}.is-align-items-self-start{align-items:self-start !important}.is-align-items-self-end{align-items:self-end !important}.is-align-self-auto{align-self:auto !important}.is-align-self-flex-start{align-self:flex-start !important}.is-align-self-flex-end{align-self:flex-end !important}.is-align-self-center{align-self:center !important}.is-align-self-baseline{align-self:baseline !important}.is-align-self-stretch{align-self:stretch !important}.is-flex-grow-0{flex-grow:0 !important}.is-flex-grow-1{flex-grow:1 !important}.is-flex-grow-2{flex-grow:2 !important}.is-flex-grow-3{flex-grow:3 !important}.is-flex-grow-4{flex-grow:4 !important}.is-flex-grow-5{flex-grow:5 !important}.is-flex-shrink-0{flex-shrink:0 !important}.is-flex-shrink-1{flex-shrink:1 !important}.is-flex-shrink-2{flex-shrink:2 !important}.is-flex-shrink-3{flex-shrink:3 !important}.is-flex-shrink-4{flex-shrink:4 !important}.is-flex-shrink-5{flex-shrink:5 !important}.is-clearfix::after{clear:both;content:" ";display:table}.is-pulled-left{float:left !important}.is-pulled-right{float:right !important}.is-radiusless{border-radius:0 !important}.is-shadowless{box-shadow:none !important}.is-clickable{cursor:pointer !important;pointer-events:all !important}.is-clipped{overflow:hidden !important}.is-relative{position:relative !important}.is-marginless{margin:0 !important}.is-paddingless{padding:0 !important}.m-0{margin:0 !important}.mt-0{margin-top:0 !important}.mr-0{margin-right:0 !important}.mb-0{margin-bottom:0 !important}.ml-0{margin-left:0 !important}.mx-0{margin-left:0 !important;margin-right:0 !important}.my-0{margin-top:0 !important;margin-bottom:0 !important}.m-1{margin:.25rem !important}.mt-1{margin-top:.25rem !important}.mr-1{margin-right:.25rem !important}.mb-1{margin-bottom:.25rem !important}.ml-1{margin-left:.25rem !important}.mx-1{margin-left:.25rem !important;margin-right:.25rem !important}.my-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.m-2{margin:.5rem !important}.mt-2{margin-top:.5rem !important}.mr-2{margin-right:.5rem !important}.mb-2{margin-bottom:.5rem !important}.ml-2{margin-left:.5rem !important}.mx-2{margin-left:.5rem !important;margin-right:.5rem !important}.my-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.m-3{margin:.75rem !important}.mt-3{margin-top:.75rem !important}.mr-3{margin-right:.75rem !important}.mb-3{margin-bottom:.75rem !important}.ml-3{margin-left:.75rem !important}.mx-3{margin-left:.75rem !important;margin-right:.75rem !important}.my-3{margin-top:.75rem !important;margin-bottom:.75rem !important}.m-4{margin:1rem !important}.mt-4{margin-top:1rem !important}.mr-4{margin-right:1rem !important}.mb-4{margin-bottom:1rem !important}.ml-4{margin-left:1rem !important}.mx-4{margin-left:1rem !important;margin-right:1rem !important}.my-4{margin-top:1rem !important;margin-bottom:1rem !important}.m-5{margin:1.5rem !important}.mt-5{margin-top:1.5rem !important}.mr-5{margin-right:1.5rem !important}.mb-5{margin-bottom:1.5rem !important}.ml-5{margin-left:1.5rem !important}.mx-5{margin-left:1.5rem !important;margin-right:1.5rem !important}.my-5{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.m-6{margin:3rem !important}.mt-6{margin-top:3rem !important}.mr-6{margin-right:3rem !important}.mb-6{margin-bottom:3rem !important}.ml-6{margin-left:3rem !important}.mx-6{margin-left:3rem !important;margin-right:3rem !important}.my-6{margin-top:3rem !important;margin-bottom:3rem !important}.m-auto{margin:auto !important}.mt-auto{margin-top:auto !important}.mr-auto{margin-right:auto !important}.mb-auto{margin-bottom:auto !important}.ml-auto{margin-left:auto !important}.mx-auto{margin-left:auto !important;margin-right:auto !important}.my-auto{margin-top:auto !important;margin-bottom:auto !important}.p-0{padding:0 !important}.pt-0{padding-top:0 !important}.pr-0{padding-right:0 !important}.pb-0{padding-bottom:0 !important}.pl-0{padding-left:0 !important}.px-0{padding-left:0 !important;padding-right:0 !important}.py-0{padding-top:0 !important;padding-bottom:0 !important}.p-1{padding:.25rem !important}.pt-1{padding-top:.25rem !important}.pr-1{padding-right:.25rem !important}.pb-1{padding-bottom:.25rem !important}.pl-1{padding-left:.25rem !important}.px-1{padding-left:.25rem !important;padding-right:.25rem !important}.py-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.p-2{padding:.5rem !important}.pt-2{padding-top:.5rem !important}.pr-2{padding-right:.5rem !important}.pb-2{padding-bottom:.5rem !important}.pl-2{padding-left:.5rem !important}.px-2{padding-left:.5rem !important;padding-right:.5rem !important}.py-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.p-3{padding:.75rem !important}.pt-3{padding-top:.75rem !important}.pr-3{padding-right:.75rem !important}.pb-3{padding-bottom:.75rem !important}.pl-3{padding-left:.75rem !important}.px-3{padding-left:.75rem !important;padding-right:.75rem !important}.py-3{padding-top:.75rem !important;padding-bottom:.75rem !important}.p-4{padding:1rem !important}.pt-4{padding-top:1rem !important}.pr-4{padding-right:1rem !important}.pb-4{padding-bottom:1rem !important}.pl-4{padding-left:1rem !important}.px-4{padding-left:1rem !important;padding-right:1rem !important}.py-4{padding-top:1rem !important;padding-bottom:1rem !important}.p-5{padding:1.5rem !important}.pt-5{padding-top:1.5rem !important}.pr-5{padding-right:1.5rem !important}.pb-5{padding-bottom:1.5rem !important}.pl-5{padding-left:1.5rem !important}.px-5{padding-left:1.5rem !important;padding-right:1.5rem !important}.py-5{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.p-6{padding:3rem !important}.pt-6{padding-top:3rem !important}.pr-6{padding-right:3rem !important}.pb-6{padding-bottom:3rem !important}.pl-6{padding-left:3rem !important}.px-6{padding-left:3rem !important;padding-right:3rem !important}.py-6{padding-top:3rem !important;padding-bottom:3rem !important}.p-auto{padding:auto !important}.pt-auto{padding-top:auto !important}.pr-auto{padding-right:auto !important}.pb-auto{padding-bottom:auto !important}.pl-auto{padding-left:auto !important}.px-auto{padding-left:auto !important;padding-right:auto !important}.py-auto{padding-top:auto !important;padding-bottom:auto !important}.is-size-1{font-size:3rem !important}.is-size-2{font-size:2.5rem !important}.is-size-3{font-size:2rem !important}.is-size-4{font-size:1.5rem !important}.is-size-5{font-size:1.25rem !important}.is-size-6{font-size:1rem !important}.is-size-7,.docstring>section>a.docs-sourcelink{font-size:.75rem !important}@media screen and (max-width: 768px){.is-size-1-mobile{font-size:3rem !important}.is-size-2-mobile{font-size:2.5rem !important}.is-size-3-mobile{font-size:2rem !important}.is-size-4-mobile{font-size:1.5rem !important}.is-size-5-mobile{font-size:1.25rem !important}.is-size-6-mobile{font-size:1rem !important}.is-size-7-mobile{font-size:.75rem !important}}@media screen and (min-width: 769px),print{.is-size-1-tablet{font-size:3rem !important}.is-size-2-tablet{font-size:2.5rem !important}.is-size-3-tablet{font-size:2rem !important}.is-size-4-tablet{font-size:1.5rem !important}.is-size-5-tablet{font-size:1.25rem !important}.is-size-6-tablet{font-size:1rem !important}.is-size-7-tablet{font-size:.75rem !important}}@media screen and (max-width: 1055px){.is-size-1-touch{font-size:3rem !important}.is-size-2-touch{font-size:2.5rem !important}.is-size-3-touch{font-size:2rem !important}.is-size-4-touch{font-size:1.5rem !important}.is-size-5-touch{font-size:1.25rem !important}.is-size-6-touch{font-size:1rem !important}.is-size-7-touch{font-size:.75rem !important}}@media screen and (min-width: 1056px){.is-size-1-desktop{font-size:3rem !important}.is-size-2-desktop{font-size:2.5rem !important}.is-size-3-desktop{font-size:2rem !important}.is-size-4-desktop{font-size:1.5rem !important}.is-size-5-desktop{font-size:1.25rem !important}.is-size-6-desktop{font-size:1rem !important}.is-size-7-desktop{font-size:.75rem !important}}@media screen and (min-width: 1216px){.is-size-1-widescreen{font-size:3rem !important}.is-size-2-widescreen{font-size:2.5rem !important}.is-size-3-widescreen{font-size:2rem !important}.is-size-4-widescreen{font-size:1.5rem !important}.is-size-5-widescreen{font-size:1.25rem !important}.is-size-6-widescreen{font-size:1rem !important}.is-size-7-widescreen{font-size:.75rem !important}}@media screen and (min-width: 1408px){.is-size-1-fullhd{font-size:3rem !important}.is-size-2-fullhd{font-size:2.5rem !important}.is-size-3-fullhd{font-size:2rem !important}.is-size-4-fullhd{font-size:1.5rem !important}.is-size-5-fullhd{font-size:1.25rem !important}.is-size-6-fullhd{font-size:1rem !important}.is-size-7-fullhd{font-size:.75rem !important}}.has-text-centered{text-align:center !important}.has-text-justified{text-align:justify !important}.has-text-left{text-align:left !important}.has-text-right{text-align:right !important}@media screen and (max-width: 768px){.has-text-centered-mobile{text-align:center !important}}@media screen and (min-width: 769px),print{.has-text-centered-tablet{text-align:center !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-centered-tablet-only{text-align:center !important}}@media screen and (max-width: 1055px){.has-text-centered-touch{text-align:center !important}}@media screen and (min-width: 1056px){.has-text-centered-desktop{text-align:center !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-centered-desktop-only{text-align:center !important}}@media screen and (min-width: 1216px){.has-text-centered-widescreen{text-align:center !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-centered-widescreen-only{text-align:center !important}}@media screen and (min-width: 1408px){.has-text-centered-fullhd{text-align:center !important}}@media screen and (max-width: 768px){.has-text-justified-mobile{text-align:justify !important}}@media screen and (min-width: 769px),print{.has-text-justified-tablet{text-align:justify !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-justified-tablet-only{text-align:justify !important}}@media screen and (max-width: 1055px){.has-text-justified-touch{text-align:justify !important}}@media screen and (min-width: 1056px){.has-text-justified-desktop{text-align:justify !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-justified-desktop-only{text-align:justify !important}}@media screen and (min-width: 1216px){.has-text-justified-widescreen{text-align:justify !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-justified-widescreen-only{text-align:justify !important}}@media screen and (min-width: 1408px){.has-text-justified-fullhd{text-align:justify !important}}@media screen and (max-width: 768px){.has-text-left-mobile{text-align:left !important}}@media screen and (min-width: 769px),print{.has-text-left-tablet{text-align:left !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-left-tablet-only{text-align:left !important}}@media screen and (max-width: 1055px){.has-text-left-touch{text-align:left !important}}@media screen and (min-width: 1056px){.has-text-left-desktop{text-align:left !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-left-desktop-only{text-align:left !important}}@media screen and (min-width: 1216px){.has-text-left-widescreen{text-align:left !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-left-widescreen-only{text-align:left !important}}@media screen and (min-width: 1408px){.has-text-left-fullhd{text-align:left !important}}@media screen and (max-width: 768px){.has-text-right-mobile{text-align:right !important}}@media screen and (min-width: 769px),print{.has-text-right-tablet{text-align:right !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-right-tablet-only{text-align:right !important}}@media screen and (max-width: 1055px){.has-text-right-touch{text-align:right !important}}@media screen and (min-width: 1056px){.has-text-right-desktop{text-align:right !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-right-desktop-only{text-align:right !important}}@media screen and (min-width: 1216px){.has-text-right-widescreen{text-align:right !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-right-widescreen-only{text-align:right !important}}@media screen and (min-width: 1408px){.has-text-right-fullhd{text-align:right !important}}.is-capitalized{text-transform:capitalize !important}.is-lowercase{text-transform:lowercase !important}.is-uppercase{text-transform:uppercase !important}.is-italic{font-style:italic !important}.is-underlined{text-decoration:underline !important}.has-text-weight-light{font-weight:300 !important}.has-text-weight-normal{font-weight:400 !important}.has-text-weight-medium{font-weight:500 !important}.has-text-weight-semibold{font-weight:600 !important}.has-text-weight-bold{font-weight:700 !important}.is-family-primary{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-secondary{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-sans-serif{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-monospace{font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace !important}.is-family-code{font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace !important}.is-block{display:block !important}@media screen and (max-width: 768px){.is-block-mobile{display:block !important}}@media screen and (min-width: 769px),print{.is-block-tablet{display:block !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-block-tablet-only{display:block !important}}@media screen and (max-width: 1055px){.is-block-touch{display:block !important}}@media screen and (min-width: 1056px){.is-block-desktop{display:block !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-block-desktop-only{display:block !important}}@media screen and (min-width: 1216px){.is-block-widescreen{display:block !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-block-widescreen-only{display:block !important}}@media screen and (min-width: 1408px){.is-block-fullhd{display:block !important}}.is-flex{display:flex !important}@media screen and (max-width: 768px){.is-flex-mobile{display:flex !important}}@media screen and (min-width: 769px),print{.is-flex-tablet{display:flex !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-flex-tablet-only{display:flex !important}}@media screen and (max-width: 1055px){.is-flex-touch{display:flex !important}}@media screen and (min-width: 1056px){.is-flex-desktop{display:flex !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-flex-desktop-only{display:flex !important}}@media screen and (min-width: 1216px){.is-flex-widescreen{display:flex !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-flex-widescreen-only{display:flex !important}}@media screen and (min-width: 1408px){.is-flex-fullhd{display:flex !important}}.is-inline{display:inline !important}@media screen and (max-width: 768px){.is-inline-mobile{display:inline !important}}@media screen and (min-width: 769px),print{.is-inline-tablet{display:inline !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-tablet-only{display:inline !important}}@media screen and (max-width: 1055px){.is-inline-touch{display:inline !important}}@media screen and (min-width: 1056px){.is-inline-desktop{display:inline !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-desktop-only{display:inline !important}}@media screen and (min-width: 1216px){.is-inline-widescreen{display:inline !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-widescreen-only{display:inline !important}}@media screen and (min-width: 1408px){.is-inline-fullhd{display:inline !important}}.is-inline-block{display:inline-block !important}@media screen and (max-width: 768px){.is-inline-block-mobile{display:inline-block !important}}@media screen and (min-width: 769px),print{.is-inline-block-tablet{display:inline-block !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-block-tablet-only{display:inline-block !important}}@media screen and (max-width: 1055px){.is-inline-block-touch{display:inline-block !important}}@media screen and (min-width: 1056px){.is-inline-block-desktop{display:inline-block !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-block-desktop-only{display:inline-block !important}}@media screen and (min-width: 1216px){.is-inline-block-widescreen{display:inline-block !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-block-widescreen-only{display:inline-block !important}}@media screen and (min-width: 1408px){.is-inline-block-fullhd{display:inline-block !important}}.is-inline-flex{display:inline-flex !important}@media screen and (max-width: 768px){.is-inline-flex-mobile{display:inline-flex !important}}@media screen and (min-width: 769px),print{.is-inline-flex-tablet{display:inline-flex !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-flex-tablet-only{display:inline-flex !important}}@media screen and (max-width: 1055px){.is-inline-flex-touch{display:inline-flex !important}}@media screen and (min-width: 1056px){.is-inline-flex-desktop{display:inline-flex !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-flex-desktop-only{display:inline-flex !important}}@media screen and (min-width: 1216px){.is-inline-flex-widescreen{display:inline-flex !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-flex-widescreen-only{display:inline-flex !important}}@media screen and (min-width: 1408px){.is-inline-flex-fullhd{display:inline-flex !important}}.is-hidden{display:none !important}.is-sr-only{border:none !important;clip:rect(0, 0, 0, 0) !important;height:0.01em !important;overflow:hidden !important;padding:0 !important;position:absolute !important;white-space:nowrap !important;width:0.01em !important}@media screen and (max-width: 768px){.is-hidden-mobile{display:none !important}}@media screen and (min-width: 769px),print{.is-hidden-tablet{display:none !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-hidden-tablet-only{display:none !important}}@media screen and (max-width: 1055px){.is-hidden-touch{display:none !important}}@media screen and (min-width: 1056px){.is-hidden-desktop{display:none !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-hidden-desktop-only{display:none !important}}@media screen and (min-width: 1216px){.is-hidden-widescreen{display:none !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-hidden-widescreen-only{display:none !important}}@media screen and (min-width: 1408px){.is-hidden-fullhd{display:none !important}}.is-invisible{visibility:hidden !important}@media screen and (max-width: 768px){.is-invisible-mobile{visibility:hidden !important}}@media screen and (min-width: 769px),print{.is-invisible-tablet{visibility:hidden !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-invisible-tablet-only{visibility:hidden !important}}@media screen and (max-width: 1055px){.is-invisible-touch{visibility:hidden !important}}@media screen and (min-width: 1056px){.is-invisible-desktop{visibility:hidden !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-invisible-desktop-only{visibility:hidden !important}}@media screen and (min-width: 1216px){.is-invisible-widescreen{visibility:hidden !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-invisible-widescreen-only{visibility:hidden !important}}@media screen and (min-width: 1408px){.is-invisible-fullhd{visibility:hidden !important}}/*! minireset.css v0.0.6 | MIT License | github.com/jgthms/minireset.css */html,body,p,ol,ul,li,dl,dt,dd,blockquote,figure,fieldset,legend,textarea,pre,iframe,hr,h1,h2,h3,h4,h5,h6{margin:0;padding:0}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal}ul{list-style:none}button,input,select,textarea{margin:0}html{box-sizing:border-box}*,*::before,*::after{box-sizing:inherit}img,video{height:auto;max-width:100%}iframe{border:0}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}td:not([align]),th:not([align]){text-align:inherit}html{background-color:#fff;font-size:16px;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;min-width:300px;overflow-x:auto;overflow-y:scroll;text-rendering:optimizeLegibility;text-size-adjust:100%}article,aside,figure,footer,header,hgroup,section{display:block}body,button,input,optgroup,select,textarea{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif}code,pre{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto;font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace}body{color:#222;font-size:1em;font-weight:400;line-height:1.5}a{color:#2e63b8;cursor:pointer;text-decoration:none}a strong{color:currentColor}a:hover{color:#363636}code{background-color:rgba(0,0,0,0.05);color:#000;font-size:.875em;font-weight:normal;padding:.1em}hr{background-color:#f5f5f5;border:none;display:block;height:2px;margin:1.5rem 0}img{height:auto;max-width:100%}input[type="checkbox"],input[type="radio"]{vertical-align:baseline}small{font-size:.875em}span{font-style:inherit;font-weight:inherit}strong{color:#222;font-weight:700}fieldset{border:none}pre{-webkit-overflow-scrolling:touch;background-color:#f5f5f5;color:#222;font-size:.875em;overflow-x:auto;padding:1.25rem 1.5rem;white-space:pre;word-wrap:normal}pre code{background-color:transparent;color:currentColor;font-size:1em;padding:0}table td,table th{vertical-align:top}table td:not([align]),table th:not([align]){text-align:inherit}table th{color:#222}@keyframes spinAround{from{transform:rotate(0deg)}to{transform:rotate(359deg)}}.box{background-color:#fff;border-radius:6px;box-shadow:#bbb;color:#222;display:block;padding:1.25rem}a.box:hover,a.box:focus{box-shadow:0 0.5em 1em -0.125em rgba(10,10,10,0.1),0 0 0 1px #2e63b8}a.box:active{box-shadow:inset 0 1px 2px rgba(10,10,10,0.2),0 0 0 1px #2e63b8}.button{background-color:#fff;border-color:#dbdbdb;border-width:1px;color:#222;cursor:pointer;justify-content:center;padding-bottom:calc(0.5em - 1px);padding-left:1em;padding-right:1em;padding-top:calc(0.5em - 1px);text-align:center;white-space:nowrap}.button strong{color:inherit}.button .icon,.button .icon.is-small,.button #documenter .docs-sidebar form.docs-search>input.icon,#documenter .docs-sidebar .button form.docs-search>input.icon,.button .icon.is-medium,.button .icon.is-large{height:1.5em;width:1.5em}.button .icon:first-child:not(:last-child){margin-left:calc(-0.5em - 1px);margin-right:.25em}.button .icon:last-child:not(:first-child){margin-left:.25em;margin-right:calc(-0.5em - 1px)}.button .icon:first-child:last-child{margin-left:calc(-0.5em - 1px);margin-right:calc(-0.5em - 1px)}.button:hover,.button.is-hovered{border-color:#b5b5b5;color:#363636}.button:focus,.button.is-focused{border-color:#3c5dcd;color:#363636}.button:focus:not(:active),.button.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(46,99,184,0.25)}.button:active,.button.is-active{border-color:#4a4a4a;color:#363636}.button.is-text{background-color:transparent;border-color:transparent;color:#222;text-decoration:underline}.button.is-text:hover,.button.is-text.is-hovered,.button.is-text:focus,.button.is-text.is-focused{background-color:#f5f5f5;color:#222}.button.is-text:active,.button.is-text.is-active{background-color:#e8e8e8;color:#222}.button.is-text[disabled],fieldset[disabled] .button.is-text{background-color:transparent;border-color:transparent;box-shadow:none}.button.is-ghost{background:none;border-color:rgba(0,0,0,0);color:#2e63b8;text-decoration:none}.button.is-ghost:hover,.button.is-ghost.is-hovered{color:#2e63b8;text-decoration:underline}.button.is-white{background-color:#fff;border-color:transparent;color:#0a0a0a}.button.is-white:hover,.button.is-white.is-hovered{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}.button.is-white:focus,.button.is-white.is-focused{border-color:transparent;color:#0a0a0a}.button.is-white:focus:not(:active),.button.is-white.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}.button.is-white:active,.button.is-white.is-active{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}.button.is-white[disabled],fieldset[disabled] .button.is-white{background-color:#fff;border-color:#fff;box-shadow:none}.button.is-white.is-inverted{background-color:#0a0a0a;color:#fff}.button.is-white.is-inverted:hover,.button.is-white.is-inverted.is-hovered{background-color:#000}.button.is-white.is-inverted[disabled],fieldset[disabled] .button.is-white.is-inverted{background-color:#0a0a0a;border-color:transparent;box-shadow:none;color:#fff}.button.is-white.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}.button.is-white.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-white.is-outlined:hover,.button.is-white.is-outlined.is-hovered,.button.is-white.is-outlined:focus,.button.is-white.is-outlined.is-focused{background-color:#fff;border-color:#fff;color:#0a0a0a}.button.is-white.is-outlined.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-white.is-outlined.is-loading:hover::after,.button.is-white.is-outlined.is-loading.is-hovered::after,.button.is-white.is-outlined.is-loading:focus::after,.button.is-white.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}.button.is-white.is-outlined[disabled],fieldset[disabled] .button.is-white.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}.button.is-white.is-inverted.is-outlined:hover,.button.is-white.is-inverted.is-outlined.is-hovered,.button.is-white.is-inverted.is-outlined:focus,.button.is-white.is-inverted.is-outlined.is-focused{background-color:#0a0a0a;color:#fff}.button.is-white.is-inverted.is-outlined.is-loading:hover::after,.button.is-white.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-white.is-inverted.is-outlined.is-loading:focus::after,.button.is-white.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-white.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}.button.is-black{background-color:#0a0a0a;border-color:transparent;color:#fff}.button.is-black:hover,.button.is-black.is-hovered{background-color:#040404;border-color:transparent;color:#fff}.button.is-black:focus,.button.is-black.is-focused{border-color:transparent;color:#fff}.button.is-black:focus:not(:active),.button.is-black.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}.button.is-black:active,.button.is-black.is-active{background-color:#000;border-color:transparent;color:#fff}.button.is-black[disabled],fieldset[disabled] .button.is-black{background-color:#0a0a0a;border-color:#0a0a0a;box-shadow:none}.button.is-black.is-inverted{background-color:#fff;color:#0a0a0a}.button.is-black.is-inverted:hover,.button.is-black.is-inverted.is-hovered{background-color:#f2f2f2}.button.is-black.is-inverted[disabled],fieldset[disabled] .button.is-black.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#0a0a0a}.button.is-black.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}.button.is-black.is-outlined:hover,.button.is-black.is-outlined.is-hovered,.button.is-black.is-outlined:focus,.button.is-black.is-outlined.is-focused{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}.button.is-black.is-outlined.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}.button.is-black.is-outlined.is-loading:hover::after,.button.is-black.is-outlined.is-loading.is-hovered::after,.button.is-black.is-outlined.is-loading:focus::after,.button.is-black.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-black.is-outlined[disabled],fieldset[disabled] .button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}.button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-black.is-inverted.is-outlined:hover,.button.is-black.is-inverted.is-outlined.is-hovered,.button.is-black.is-inverted.is-outlined:focus,.button.is-black.is-inverted.is-outlined.is-focused{background-color:#fff;color:#0a0a0a}.button.is-black.is-inverted.is-outlined.is-loading:hover::after,.button.is-black.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-black.is-inverted.is-outlined.is-loading:focus::after,.button.is-black.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}.button.is-black.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-light{background-color:#f5f5f5;border-color:transparent;color:rgba(0,0,0,0.7)}.button.is-light:hover,.button.is-light.is-hovered{background-color:#eee;border-color:transparent;color:rgba(0,0,0,0.7)}.button.is-light:focus,.button.is-light.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}.button.is-light:focus:not(:active),.button.is-light.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(245,245,245,0.25)}.button.is-light:active,.button.is-light.is-active{background-color:#e8e8e8;border-color:transparent;color:rgba(0,0,0,0.7)}.button.is-light[disabled],fieldset[disabled] .button.is-light{background-color:#f5f5f5;border-color:#f5f5f5;box-shadow:none}.button.is-light.is-inverted{background-color:rgba(0,0,0,0.7);color:#f5f5f5}.button.is-light.is-inverted:hover,.button.is-light.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}.button.is-light.is-inverted[disabled],fieldset[disabled] .button.is-light.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#f5f5f5}.button.is-light.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}.button.is-light.is-outlined{background-color:transparent;border-color:#f5f5f5;color:#f5f5f5}.button.is-light.is-outlined:hover,.button.is-light.is-outlined.is-hovered,.button.is-light.is-outlined:focus,.button.is-light.is-outlined.is-focused{background-color:#f5f5f5;border-color:#f5f5f5;color:rgba(0,0,0,0.7)}.button.is-light.is-outlined.is-loading::after{border-color:transparent transparent #f5f5f5 #f5f5f5 !important}.button.is-light.is-outlined.is-loading:hover::after,.button.is-light.is-outlined.is-loading.is-hovered::after,.button.is-light.is-outlined.is-loading:focus::after,.button.is-light.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}.button.is-light.is-outlined[disabled],fieldset[disabled] .button.is-light.is-outlined{background-color:transparent;border-color:#f5f5f5;box-shadow:none;color:#f5f5f5}.button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}.button.is-light.is-inverted.is-outlined:hover,.button.is-light.is-inverted.is-outlined.is-hovered,.button.is-light.is-inverted.is-outlined:focus,.button.is-light.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#f5f5f5}.button.is-light.is-inverted.is-outlined.is-loading:hover::after,.button.is-light.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-light.is-inverted.is-outlined.is-loading:focus::after,.button.is-light.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #f5f5f5 #f5f5f5 !important}.button.is-light.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}.button.is-dark,.content kbd.button{background-color:#363636;border-color:transparent;color:#fff}.button.is-dark:hover,.content kbd.button:hover,.button.is-dark.is-hovered,.content kbd.button.is-hovered{background-color:#2f2f2f;border-color:transparent;color:#fff}.button.is-dark:focus,.content kbd.button:focus,.button.is-dark.is-focused,.content kbd.button.is-focused{border-color:transparent;color:#fff}.button.is-dark:focus:not(:active),.content kbd.button:focus:not(:active),.button.is-dark.is-focused:not(:active),.content kbd.button.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(54,54,54,0.25)}.button.is-dark:active,.content kbd.button:active,.button.is-dark.is-active,.content kbd.button.is-active{background-color:#292929;border-color:transparent;color:#fff}.button.is-dark[disabled],.content kbd.button[disabled],fieldset[disabled] .button.is-dark,fieldset[disabled] .content kbd.button,.content fieldset[disabled] kbd.button{background-color:#363636;border-color:#363636;box-shadow:none}.button.is-dark.is-inverted,.content kbd.button.is-inverted{background-color:#fff;color:#363636}.button.is-dark.is-inverted:hover,.content kbd.button.is-inverted:hover,.button.is-dark.is-inverted.is-hovered,.content kbd.button.is-inverted.is-hovered{background-color:#f2f2f2}.button.is-dark.is-inverted[disabled],.content kbd.button.is-inverted[disabled],fieldset[disabled] .button.is-dark.is-inverted,fieldset[disabled] .content kbd.button.is-inverted,.content fieldset[disabled] kbd.button.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#363636}.button.is-dark.is-loading::after,.content kbd.button.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-dark.is-outlined,.content kbd.button.is-outlined{background-color:transparent;border-color:#363636;color:#363636}.button.is-dark.is-outlined:hover,.content kbd.button.is-outlined:hover,.button.is-dark.is-outlined.is-hovered,.content kbd.button.is-outlined.is-hovered,.button.is-dark.is-outlined:focus,.content kbd.button.is-outlined:focus,.button.is-dark.is-outlined.is-focused,.content kbd.button.is-outlined.is-focused{background-color:#363636;border-color:#363636;color:#fff}.button.is-dark.is-outlined.is-loading::after,.content kbd.button.is-outlined.is-loading::after{border-color:transparent transparent #363636 #363636 !important}.button.is-dark.is-outlined.is-loading:hover::after,.content kbd.button.is-outlined.is-loading:hover::after,.button.is-dark.is-outlined.is-loading.is-hovered::after,.content kbd.button.is-outlined.is-loading.is-hovered::after,.button.is-dark.is-outlined.is-loading:focus::after,.content kbd.button.is-outlined.is-loading:focus::after,.button.is-dark.is-outlined.is-loading.is-focused::after,.content kbd.button.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-dark.is-outlined[disabled],.content kbd.button.is-outlined[disabled],fieldset[disabled] .button.is-dark.is-outlined,fieldset[disabled] .content kbd.button.is-outlined,.content fieldset[disabled] kbd.button.is-outlined{background-color:transparent;border-color:#363636;box-shadow:none;color:#363636}.button.is-dark.is-inverted.is-outlined,.content kbd.button.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-dark.is-inverted.is-outlined:hover,.content kbd.button.is-inverted.is-outlined:hover,.button.is-dark.is-inverted.is-outlined.is-hovered,.content kbd.button.is-inverted.is-outlined.is-hovered,.button.is-dark.is-inverted.is-outlined:focus,.content kbd.button.is-inverted.is-outlined:focus,.button.is-dark.is-inverted.is-outlined.is-focused,.content kbd.button.is-inverted.is-outlined.is-focused{background-color:#fff;color:#363636}.button.is-dark.is-inverted.is-outlined.is-loading:hover::after,.content kbd.button.is-inverted.is-outlined.is-loading:hover::after,.button.is-dark.is-inverted.is-outlined.is-loading.is-hovered::after,.content kbd.button.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-dark.is-inverted.is-outlined.is-loading:focus::after,.content kbd.button.is-inverted.is-outlined.is-loading:focus::after,.button.is-dark.is-inverted.is-outlined.is-loading.is-focused::after,.content kbd.button.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #363636 #363636 !important}.button.is-dark.is-inverted.is-outlined[disabled],.content kbd.button.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-dark.is-inverted.is-outlined,fieldset[disabled] .content kbd.button.is-inverted.is-outlined,.content fieldset[disabled] kbd.button.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-primary,.docstring>section>a.button.docs-sourcelink{background-color:#4eb5de;border-color:transparent;color:#fff}.button.is-primary:hover,.docstring>section>a.button.docs-sourcelink:hover,.button.is-primary.is-hovered,.docstring>section>a.button.is-hovered.docs-sourcelink{background-color:#43b1dc;border-color:transparent;color:#fff}.button.is-primary:focus,.docstring>section>a.button.docs-sourcelink:focus,.button.is-primary.is-focused,.docstring>section>a.button.is-focused.docs-sourcelink{border-color:transparent;color:#fff}.button.is-primary:focus:not(:active),.docstring>section>a.button.docs-sourcelink:focus:not(:active),.button.is-primary.is-focused:not(:active),.docstring>section>a.button.is-focused.docs-sourcelink:not(:active){box-shadow:0 0 0 0.125em rgba(78,181,222,0.25)}.button.is-primary:active,.docstring>section>a.button.docs-sourcelink:active,.button.is-primary.is-active,.docstring>section>a.button.is-active.docs-sourcelink{background-color:#39acda;border-color:transparent;color:#fff}.button.is-primary[disabled],.docstring>section>a.button.docs-sourcelink[disabled],fieldset[disabled] .button.is-primary,fieldset[disabled] .docstring>section>a.button.docs-sourcelink{background-color:#4eb5de;border-color:#4eb5de;box-shadow:none}.button.is-primary.is-inverted,.docstring>section>a.button.is-inverted.docs-sourcelink{background-color:#fff;color:#4eb5de}.button.is-primary.is-inverted:hover,.docstring>section>a.button.is-inverted.docs-sourcelink:hover,.button.is-primary.is-inverted.is-hovered,.docstring>section>a.button.is-inverted.is-hovered.docs-sourcelink{background-color:#f2f2f2}.button.is-primary.is-inverted[disabled],.docstring>section>a.button.is-inverted.docs-sourcelink[disabled],fieldset[disabled] .button.is-primary.is-inverted,fieldset[disabled] .docstring>section>a.button.is-inverted.docs-sourcelink{background-color:#fff;border-color:transparent;box-shadow:none;color:#4eb5de}.button.is-primary.is-loading::after,.docstring>section>a.button.is-loading.docs-sourcelink::after{border-color:transparent transparent #fff #fff !important}.button.is-primary.is-outlined,.docstring>section>a.button.is-outlined.docs-sourcelink{background-color:transparent;border-color:#4eb5de;color:#4eb5de}.button.is-primary.is-outlined:hover,.docstring>section>a.button.is-outlined.docs-sourcelink:hover,.button.is-primary.is-outlined.is-hovered,.docstring>section>a.button.is-outlined.is-hovered.docs-sourcelink,.button.is-primary.is-outlined:focus,.docstring>section>a.button.is-outlined.docs-sourcelink:focus,.button.is-primary.is-outlined.is-focused,.docstring>section>a.button.is-outlined.is-focused.docs-sourcelink{background-color:#4eb5de;border-color:#4eb5de;color:#fff}.button.is-primary.is-outlined.is-loading::after,.docstring>section>a.button.is-outlined.is-loading.docs-sourcelink::after{border-color:transparent transparent #4eb5de #4eb5de !important}.button.is-primary.is-outlined.is-loading:hover::after,.docstring>section>a.button.is-outlined.is-loading.docs-sourcelink:hover::after,.button.is-primary.is-outlined.is-loading.is-hovered::after,.docstring>section>a.button.is-outlined.is-loading.is-hovered.docs-sourcelink::after,.button.is-primary.is-outlined.is-loading:focus::after,.docstring>section>a.button.is-outlined.is-loading.docs-sourcelink:focus::after,.button.is-primary.is-outlined.is-loading.is-focused::after,.docstring>section>a.button.is-outlined.is-loading.is-focused.docs-sourcelink::after{border-color:transparent transparent #fff #fff !important}.button.is-primary.is-outlined[disabled],.docstring>section>a.button.is-outlined.docs-sourcelink[disabled],fieldset[disabled] .button.is-primary.is-outlined,fieldset[disabled] .docstring>section>a.button.is-outlined.docs-sourcelink{background-color:transparent;border-color:#4eb5de;box-shadow:none;color:#4eb5de}.button.is-primary.is-inverted.is-outlined,.docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink{background-color:transparent;border-color:#fff;color:#fff}.button.is-primary.is-inverted.is-outlined:hover,.docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink:hover,.button.is-primary.is-inverted.is-outlined.is-hovered,.docstring>section>a.button.is-inverted.is-outlined.is-hovered.docs-sourcelink,.button.is-primary.is-inverted.is-outlined:focus,.docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink:focus,.button.is-primary.is-inverted.is-outlined.is-focused,.docstring>section>a.button.is-inverted.is-outlined.is-focused.docs-sourcelink{background-color:#fff;color:#4eb5de}.button.is-primary.is-inverted.is-outlined.is-loading:hover::after,.docstring>section>a.button.is-inverted.is-outlined.is-loading.docs-sourcelink:hover::after,.button.is-primary.is-inverted.is-outlined.is-loading.is-hovered::after,.docstring>section>a.button.is-inverted.is-outlined.is-loading.is-hovered.docs-sourcelink::after,.button.is-primary.is-inverted.is-outlined.is-loading:focus::after,.docstring>section>a.button.is-inverted.is-outlined.is-loading.docs-sourcelink:focus::after,.button.is-primary.is-inverted.is-outlined.is-loading.is-focused::after,.docstring>section>a.button.is-inverted.is-outlined.is-loading.is-focused.docs-sourcelink::after{border-color:transparent transparent #4eb5de #4eb5de !important}.button.is-primary.is-inverted.is-outlined[disabled],.docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink[disabled],fieldset[disabled] .button.is-primary.is-inverted.is-outlined,fieldset[disabled] .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-primary.is-light,.docstring>section>a.button.is-light.docs-sourcelink{background-color:#eef8fc;color:#1a6d8e}.button.is-primary.is-light:hover,.docstring>section>a.button.is-light.docs-sourcelink:hover,.button.is-primary.is-light.is-hovered,.docstring>section>a.button.is-light.is-hovered.docs-sourcelink{background-color:#e3f3fa;border-color:transparent;color:#1a6d8e}.button.is-primary.is-light:active,.docstring>section>a.button.is-light.docs-sourcelink:active,.button.is-primary.is-light.is-active,.docstring>section>a.button.is-light.is-active.docs-sourcelink{background-color:#d8eff8;border-color:transparent;color:#1a6d8e}.button.is-link{background-color:#2e63b8;border-color:transparent;color:#fff}.button.is-link:hover,.button.is-link.is-hovered{background-color:#2b5eae;border-color:transparent;color:#fff}.button.is-link:focus,.button.is-link.is-focused{border-color:transparent;color:#fff}.button.is-link:focus:not(:active),.button.is-link.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(46,99,184,0.25)}.button.is-link:active,.button.is-link.is-active{background-color:#2958a4;border-color:transparent;color:#fff}.button.is-link[disabled],fieldset[disabled] .button.is-link{background-color:#2e63b8;border-color:#2e63b8;box-shadow:none}.button.is-link.is-inverted{background-color:#fff;color:#2e63b8}.button.is-link.is-inverted:hover,.button.is-link.is-inverted.is-hovered{background-color:#f2f2f2}.button.is-link.is-inverted[disabled],fieldset[disabled] .button.is-link.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#2e63b8}.button.is-link.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-link.is-outlined{background-color:transparent;border-color:#2e63b8;color:#2e63b8}.button.is-link.is-outlined:hover,.button.is-link.is-outlined.is-hovered,.button.is-link.is-outlined:focus,.button.is-link.is-outlined.is-focused{background-color:#2e63b8;border-color:#2e63b8;color:#fff}.button.is-link.is-outlined.is-loading::after{border-color:transparent transparent #2e63b8 #2e63b8 !important}.button.is-link.is-outlined.is-loading:hover::after,.button.is-link.is-outlined.is-loading.is-hovered::after,.button.is-link.is-outlined.is-loading:focus::after,.button.is-link.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-link.is-outlined[disabled],fieldset[disabled] .button.is-link.is-outlined{background-color:transparent;border-color:#2e63b8;box-shadow:none;color:#2e63b8}.button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-link.is-inverted.is-outlined:hover,.button.is-link.is-inverted.is-outlined.is-hovered,.button.is-link.is-inverted.is-outlined:focus,.button.is-link.is-inverted.is-outlined.is-focused{background-color:#fff;color:#2e63b8}.button.is-link.is-inverted.is-outlined.is-loading:hover::after,.button.is-link.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-link.is-inverted.is-outlined.is-loading:focus::after,.button.is-link.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #2e63b8 #2e63b8 !important}.button.is-link.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-link.is-light{background-color:#eff3fb;color:#3169c4}.button.is-link.is-light:hover,.button.is-link.is-light.is-hovered{background-color:#e4ecf8;border-color:transparent;color:#3169c4}.button.is-link.is-light:active,.button.is-link.is-light.is-active{background-color:#dae5f6;border-color:transparent;color:#3169c4}.button.is-info{background-color:#209cee;border-color:transparent;color:#fff}.button.is-info:hover,.button.is-info.is-hovered{background-color:#1497ed;border-color:transparent;color:#fff}.button.is-info:focus,.button.is-info.is-focused{border-color:transparent;color:#fff}.button.is-info:focus:not(:active),.button.is-info.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(32,156,238,0.25)}.button.is-info:active,.button.is-info.is-active{background-color:#1190e3;border-color:transparent;color:#fff}.button.is-info[disabled],fieldset[disabled] .button.is-info{background-color:#209cee;border-color:#209cee;box-shadow:none}.button.is-info.is-inverted{background-color:#fff;color:#209cee}.button.is-info.is-inverted:hover,.button.is-info.is-inverted.is-hovered{background-color:#f2f2f2}.button.is-info.is-inverted[disabled],fieldset[disabled] .button.is-info.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#209cee}.button.is-info.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-info.is-outlined{background-color:transparent;border-color:#209cee;color:#209cee}.button.is-info.is-outlined:hover,.button.is-info.is-outlined.is-hovered,.button.is-info.is-outlined:focus,.button.is-info.is-outlined.is-focused{background-color:#209cee;border-color:#209cee;color:#fff}.button.is-info.is-outlined.is-loading::after{border-color:transparent transparent #209cee #209cee !important}.button.is-info.is-outlined.is-loading:hover::after,.button.is-info.is-outlined.is-loading.is-hovered::after,.button.is-info.is-outlined.is-loading:focus::after,.button.is-info.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-info.is-outlined[disabled],fieldset[disabled] .button.is-info.is-outlined{background-color:transparent;border-color:#209cee;box-shadow:none;color:#209cee}.button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-info.is-inverted.is-outlined:hover,.button.is-info.is-inverted.is-outlined.is-hovered,.button.is-info.is-inverted.is-outlined:focus,.button.is-info.is-inverted.is-outlined.is-focused{background-color:#fff;color:#209cee}.button.is-info.is-inverted.is-outlined.is-loading:hover::after,.button.is-info.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-info.is-inverted.is-outlined.is-loading:focus::after,.button.is-info.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #209cee #209cee !important}.button.is-info.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-info.is-light{background-color:#ecf7fe;color:#0e72b4}.button.is-info.is-light:hover,.button.is-info.is-light.is-hovered{background-color:#e0f1fd;border-color:transparent;color:#0e72b4}.button.is-info.is-light:active,.button.is-info.is-light.is-active{background-color:#d4ecfc;border-color:transparent;color:#0e72b4}.button.is-success{background-color:#22c35b;border-color:transparent;color:#fff}.button.is-success:hover,.button.is-success.is-hovered{background-color:#20b856;border-color:transparent;color:#fff}.button.is-success:focus,.button.is-success.is-focused{border-color:transparent;color:#fff}.button.is-success:focus:not(:active),.button.is-success.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(34,195,91,0.25)}.button.is-success:active,.button.is-success.is-active{background-color:#1ead51;border-color:transparent;color:#fff}.button.is-success[disabled],fieldset[disabled] .button.is-success{background-color:#22c35b;border-color:#22c35b;box-shadow:none}.button.is-success.is-inverted{background-color:#fff;color:#22c35b}.button.is-success.is-inverted:hover,.button.is-success.is-inverted.is-hovered{background-color:#f2f2f2}.button.is-success.is-inverted[disabled],fieldset[disabled] .button.is-success.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#22c35b}.button.is-success.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-success.is-outlined{background-color:transparent;border-color:#22c35b;color:#22c35b}.button.is-success.is-outlined:hover,.button.is-success.is-outlined.is-hovered,.button.is-success.is-outlined:focus,.button.is-success.is-outlined.is-focused{background-color:#22c35b;border-color:#22c35b;color:#fff}.button.is-success.is-outlined.is-loading::after{border-color:transparent transparent #22c35b #22c35b !important}.button.is-success.is-outlined.is-loading:hover::after,.button.is-success.is-outlined.is-loading.is-hovered::after,.button.is-success.is-outlined.is-loading:focus::after,.button.is-success.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-success.is-outlined[disabled],fieldset[disabled] .button.is-success.is-outlined{background-color:transparent;border-color:#22c35b;box-shadow:none;color:#22c35b}.button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-success.is-inverted.is-outlined:hover,.button.is-success.is-inverted.is-outlined.is-hovered,.button.is-success.is-inverted.is-outlined:focus,.button.is-success.is-inverted.is-outlined.is-focused{background-color:#fff;color:#22c35b}.button.is-success.is-inverted.is-outlined.is-loading:hover::after,.button.is-success.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-success.is-inverted.is-outlined.is-loading:focus::after,.button.is-success.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #22c35b #22c35b !important}.button.is-success.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-success.is-light{background-color:#eefcf3;color:#198f43}.button.is-success.is-light:hover,.button.is-success.is-light.is-hovered{background-color:#e3faeb;border-color:transparent;color:#198f43}.button.is-success.is-light:active,.button.is-success.is-light.is-active{background-color:#d8f8e3;border-color:transparent;color:#198f43}.button.is-warning{background-color:#ffdd57;border-color:transparent;color:rgba(0,0,0,0.7)}.button.is-warning:hover,.button.is-warning.is-hovered{background-color:#ffda4a;border-color:transparent;color:rgba(0,0,0,0.7)}.button.is-warning:focus,.button.is-warning.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}.button.is-warning:focus:not(:active),.button.is-warning.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(255,221,87,0.25)}.button.is-warning:active,.button.is-warning.is-active{background-color:#ffd83e;border-color:transparent;color:rgba(0,0,0,0.7)}.button.is-warning[disabled],fieldset[disabled] .button.is-warning{background-color:#ffdd57;border-color:#ffdd57;box-shadow:none}.button.is-warning.is-inverted{background-color:rgba(0,0,0,0.7);color:#ffdd57}.button.is-warning.is-inverted:hover,.button.is-warning.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}.button.is-warning.is-inverted[disabled],fieldset[disabled] .button.is-warning.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#ffdd57}.button.is-warning.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}.button.is-warning.is-outlined{background-color:transparent;border-color:#ffdd57;color:#ffdd57}.button.is-warning.is-outlined:hover,.button.is-warning.is-outlined.is-hovered,.button.is-warning.is-outlined:focus,.button.is-warning.is-outlined.is-focused{background-color:#ffdd57;border-color:#ffdd57;color:rgba(0,0,0,0.7)}.button.is-warning.is-outlined.is-loading::after{border-color:transparent transparent #ffdd57 #ffdd57 !important}.button.is-warning.is-outlined.is-loading:hover::after,.button.is-warning.is-outlined.is-loading.is-hovered::after,.button.is-warning.is-outlined.is-loading:focus::after,.button.is-warning.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}.button.is-warning.is-outlined[disabled],fieldset[disabled] .button.is-warning.is-outlined{background-color:transparent;border-color:#ffdd57;box-shadow:none;color:#ffdd57}.button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}.button.is-warning.is-inverted.is-outlined:hover,.button.is-warning.is-inverted.is-outlined.is-hovered,.button.is-warning.is-inverted.is-outlined:focus,.button.is-warning.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#ffdd57}.button.is-warning.is-inverted.is-outlined.is-loading:hover::after,.button.is-warning.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-warning.is-inverted.is-outlined.is-loading:focus::after,.button.is-warning.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #ffdd57 #ffdd57 !important}.button.is-warning.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}.button.is-warning.is-light{background-color:#fffbeb;color:#947600}.button.is-warning.is-light:hover,.button.is-warning.is-light.is-hovered{background-color:#fff8de;border-color:transparent;color:#947600}.button.is-warning.is-light:active,.button.is-warning.is-light.is-active{background-color:#fff6d1;border-color:transparent;color:#947600}.button.is-danger{background-color:#da0b00;border-color:transparent;color:#fff}.button.is-danger:hover,.button.is-danger.is-hovered{background-color:#cd0a00;border-color:transparent;color:#fff}.button.is-danger:focus,.button.is-danger.is-focused{border-color:transparent;color:#fff}.button.is-danger:focus:not(:active),.button.is-danger.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(218,11,0,0.25)}.button.is-danger:active,.button.is-danger.is-active{background-color:#c10a00;border-color:transparent;color:#fff}.button.is-danger[disabled],fieldset[disabled] .button.is-danger{background-color:#da0b00;border-color:#da0b00;box-shadow:none}.button.is-danger.is-inverted{background-color:#fff;color:#da0b00}.button.is-danger.is-inverted:hover,.button.is-danger.is-inverted.is-hovered{background-color:#f2f2f2}.button.is-danger.is-inverted[disabled],fieldset[disabled] .button.is-danger.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#da0b00}.button.is-danger.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-danger.is-outlined{background-color:transparent;border-color:#da0b00;color:#da0b00}.button.is-danger.is-outlined:hover,.button.is-danger.is-outlined.is-hovered,.button.is-danger.is-outlined:focus,.button.is-danger.is-outlined.is-focused{background-color:#da0b00;border-color:#da0b00;color:#fff}.button.is-danger.is-outlined.is-loading::after{border-color:transparent transparent #da0b00 #da0b00 !important}.button.is-danger.is-outlined.is-loading:hover::after,.button.is-danger.is-outlined.is-loading.is-hovered::after,.button.is-danger.is-outlined.is-loading:focus::after,.button.is-danger.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-danger.is-outlined[disabled],fieldset[disabled] .button.is-danger.is-outlined{background-color:transparent;border-color:#da0b00;box-shadow:none;color:#da0b00}.button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-danger.is-inverted.is-outlined:hover,.button.is-danger.is-inverted.is-outlined.is-hovered,.button.is-danger.is-inverted.is-outlined:focus,.button.is-danger.is-inverted.is-outlined.is-focused{background-color:#fff;color:#da0b00}.button.is-danger.is-inverted.is-outlined.is-loading:hover::after,.button.is-danger.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-danger.is-inverted.is-outlined.is-loading:focus::after,.button.is-danger.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #da0b00 #da0b00 !important}.button.is-danger.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-danger.is-light{background-color:#ffeceb;color:#f50c00}.button.is-danger.is-light:hover,.button.is-danger.is-light.is-hovered{background-color:#ffe0de;border-color:transparent;color:#f50c00}.button.is-danger.is-light:active,.button.is-danger.is-light.is-active{background-color:#ffd3d1;border-color:transparent;color:#f50c00}.button.is-small,#documenter .docs-sidebar form.docs-search>input.button{font-size:.75rem}.button.is-small:not(.is-rounded),#documenter .docs-sidebar form.docs-search>input.button:not(.is-rounded){border-radius:2px}.button.is-normal{font-size:1rem}.button.is-medium{font-size:1.25rem}.button.is-large{font-size:1.5rem}.button[disabled],fieldset[disabled] .button{background-color:#fff;border-color:#dbdbdb;box-shadow:none;opacity:.5}.button.is-fullwidth{display:flex;width:100%}.button.is-loading{color:transparent !important;pointer-events:none}.button.is-loading::after{position:absolute;left:calc(50% - (1em * 0.5));top:calc(50% - (1em * 0.5));position:absolute !important}.button.is-static{background-color:#f5f5f5;border-color:#dbdbdb;color:#6b6b6b;box-shadow:none;pointer-events:none}.button.is-rounded,#documenter .docs-sidebar form.docs-search>input.button{border-radius:9999px;padding-left:calc(1em + 0.25em);padding-right:calc(1em + 0.25em)}.buttons{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}.buttons .button{margin-bottom:0.5rem}.buttons .button:not(:last-child):not(.is-fullwidth){margin-right:.5rem}.buttons:last-child{margin-bottom:-0.5rem}.buttons:not(:last-child){margin-bottom:1rem}.buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large){font-size:.75rem}.buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large):not(.is-rounded){border-radius:2px}.buttons.are-medium .button:not(.is-small):not(.is-normal):not(.is-large){font-size:1.25rem}.buttons.are-large .button:not(.is-small):not(.is-normal):not(.is-medium){font-size:1.5rem}.buttons.has-addons .button:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.buttons.has-addons .button:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0;margin-right:-1px}.buttons.has-addons .button:last-child{margin-right:0}.buttons.has-addons .button:hover,.buttons.has-addons .button.is-hovered{z-index:2}.buttons.has-addons .button:focus,.buttons.has-addons .button.is-focused,.buttons.has-addons .button:active,.buttons.has-addons .button.is-active,.buttons.has-addons .button.is-selected{z-index:3}.buttons.has-addons .button:focus:hover,.buttons.has-addons .button.is-focused:hover,.buttons.has-addons .button:active:hover,.buttons.has-addons .button.is-active:hover,.buttons.has-addons .button.is-selected:hover{z-index:4}.buttons.has-addons .button.is-expanded{flex-grow:1;flex-shrink:1}.buttons.is-centered{justify-content:center}.buttons.is-centered:not(.has-addons) .button:not(.is-fullwidth){margin-left:0.25rem;margin-right:0.25rem}.buttons.is-right{justify-content:flex-end}.buttons.is-right:not(.has-addons) .button:not(.is-fullwidth){margin-left:0.25rem;margin-right:0.25rem}@media screen and (max-width: 768px){.button.is-responsive.is-small,#documenter .docs-sidebar form.docs-search>input.is-responsive{font-size:.5625rem}.button.is-responsive,.button.is-responsive.is-normal{font-size:.65625rem}.button.is-responsive.is-medium{font-size:.75rem}.button.is-responsive.is-large{font-size:1rem}}@media screen and (min-width: 769px) and (max-width: 1055px){.button.is-responsive.is-small,#documenter .docs-sidebar form.docs-search>input.is-responsive{font-size:.65625rem}.button.is-responsive,.button.is-responsive.is-normal{font-size:.75rem}.button.is-responsive.is-medium{font-size:1rem}.button.is-responsive.is-large{font-size:1.25rem}}.container{flex-grow:1;margin:0 auto;position:relative;width:auto}.container.is-fluid{max-width:none !important;padding-left:32px;padding-right:32px;width:100%}@media screen and (min-width: 1056px){.container{max-width:992px}}@media screen and (max-width: 1215px){.container.is-widescreen:not(.is-max-desktop){max-width:1152px}}@media screen and (max-width: 1407px){.container.is-fullhd:not(.is-max-desktop):not(.is-max-widescreen){max-width:1344px}}@media screen and (min-width: 1216px){.container:not(.is-max-desktop){max-width:1152px}}@media screen and (min-width: 1408px){.container:not(.is-max-desktop):not(.is-max-widescreen){max-width:1344px}}.content li+li{margin-top:0.25em}.content p:not(:last-child),.content dl:not(:last-child),.content ol:not(:last-child),.content ul:not(:last-child),.content blockquote:not(:last-child),.content pre:not(:last-child),.content table:not(:last-child){margin-bottom:1em}.content h1,.content h2,.content h3,.content h4,.content h5,.content h6{color:#222;font-weight:600;line-height:1.125}.content h1{font-size:2em;margin-bottom:0.5em}.content h1:not(:first-child){margin-top:1em}.content h2{font-size:1.75em;margin-bottom:0.5714em}.content h2:not(:first-child){margin-top:1.1428em}.content h3{font-size:1.5em;margin-bottom:0.6666em}.content h3:not(:first-child){margin-top:1.3333em}.content h4{font-size:1.25em;margin-bottom:0.8em}.content h5{font-size:1.125em;margin-bottom:0.8888em}.content h6{font-size:1em;margin-bottom:1em}.content blockquote{background-color:#f5f5f5;border-left:5px solid #dbdbdb;padding:1.25em 1.5em}.content ol{list-style-position:outside;margin-left:2em;margin-top:1em}.content ol:not([type]){list-style-type:decimal}.content ol.is-lower-alpha:not([type]){list-style-type:lower-alpha}.content ol.is-lower-roman:not([type]){list-style-type:lower-roman}.content ol.is-upper-alpha:not([type]){list-style-type:upper-alpha}.content ol.is-upper-roman:not([type]){list-style-type:upper-roman}.content ul{list-style:disc outside;margin-left:2em;margin-top:1em}.content ul ul{list-style-type:circle;margin-top:0.5em}.content ul ul ul{list-style-type:square}.content dd{margin-left:2em}.content figure{margin-left:2em;margin-right:2em;text-align:center}.content figure:not(:first-child){margin-top:2em}.content figure:not(:last-child){margin-bottom:2em}.content figure img{display:inline-block}.content figure figcaption{font-style:italic}.content pre{-webkit-overflow-scrolling:touch;overflow-x:auto;padding:0;white-space:pre;word-wrap:normal}.content sup,.content sub{font-size:75%}.content table{width:100%}.content table td,.content table th{border:1px solid #dbdbdb;border-width:0 0 1px;padding:0.5em 0.75em;vertical-align:top}.content table th{color:#222}.content table th:not([align]){text-align:inherit}.content table thead td,.content table thead th{border-width:0 0 2px;color:#222}.content table tfoot td,.content table tfoot th{border-width:2px 0 0;color:#222}.content table tbody tr:last-child td,.content table tbody tr:last-child th{border-bottom-width:0}.content .tabs li+li{margin-top:0}.content.is-small,#documenter .docs-sidebar form.docs-search>input.content{font-size:.75rem}.content.is-normal{font-size:1rem}.content.is-medium{font-size:1.25rem}.content.is-large{font-size:1.5rem}.icon{align-items:center;display:inline-flex;justify-content:center;height:1.5rem;width:1.5rem}.icon.is-small,#documenter .docs-sidebar form.docs-search>input.icon{height:1rem;width:1rem}.icon.is-medium{height:2rem;width:2rem}.icon.is-large{height:3rem;width:3rem}.icon-text{align-items:flex-start;color:inherit;display:inline-flex;flex-wrap:wrap;line-height:1.5rem;vertical-align:top}.icon-text .icon{flex-grow:0;flex-shrink:0}.icon-text .icon:not(:last-child){margin-right:.25em}.icon-text .icon:not(:first-child){margin-left:.25em}div.icon-text{display:flex}.image,#documenter .docs-sidebar .docs-logo>img{display:block;position:relative}.image img,#documenter .docs-sidebar .docs-logo>img img{display:block;height:auto;width:100%}.image img.is-rounded,#documenter .docs-sidebar .docs-logo>img img.is-rounded{border-radius:9999px}.image.is-fullwidth,#documenter .docs-sidebar .docs-logo>img.is-fullwidth{width:100%}.image.is-square img,#documenter .docs-sidebar .docs-logo>img.is-square img,.image.is-square .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-square .has-ratio,.image.is-1by1 img,#documenter .docs-sidebar .docs-logo>img.is-1by1 img,.image.is-1by1 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-1by1 .has-ratio,.image.is-5by4 img,#documenter .docs-sidebar .docs-logo>img.is-5by4 img,.image.is-5by4 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-5by4 .has-ratio,.image.is-4by3 img,#documenter .docs-sidebar .docs-logo>img.is-4by3 img,.image.is-4by3 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-4by3 .has-ratio,.image.is-3by2 img,#documenter .docs-sidebar .docs-logo>img.is-3by2 img,.image.is-3by2 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-3by2 .has-ratio,.image.is-5by3 img,#documenter .docs-sidebar .docs-logo>img.is-5by3 img,.image.is-5by3 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-5by3 .has-ratio,.image.is-16by9 img,#documenter .docs-sidebar .docs-logo>img.is-16by9 img,.image.is-16by9 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-16by9 .has-ratio,.image.is-2by1 img,#documenter .docs-sidebar .docs-logo>img.is-2by1 img,.image.is-2by1 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-2by1 .has-ratio,.image.is-3by1 img,#documenter .docs-sidebar .docs-logo>img.is-3by1 img,.image.is-3by1 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-3by1 .has-ratio,.image.is-4by5 img,#documenter .docs-sidebar .docs-logo>img.is-4by5 img,.image.is-4by5 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-4by5 .has-ratio,.image.is-3by4 img,#documenter .docs-sidebar .docs-logo>img.is-3by4 img,.image.is-3by4 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-3by4 .has-ratio,.image.is-2by3 img,#documenter .docs-sidebar .docs-logo>img.is-2by3 img,.image.is-2by3 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-2by3 .has-ratio,.image.is-3by5 img,#documenter .docs-sidebar .docs-logo>img.is-3by5 img,.image.is-3by5 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-3by5 .has-ratio,.image.is-9by16 img,#documenter .docs-sidebar .docs-logo>img.is-9by16 img,.image.is-9by16 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-9by16 .has-ratio,.image.is-1by2 img,#documenter .docs-sidebar .docs-logo>img.is-1by2 img,.image.is-1by2 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-1by2 .has-ratio,.image.is-1by3 img,#documenter .docs-sidebar .docs-logo>img.is-1by3 img,.image.is-1by3 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-1by3 .has-ratio{height:100%;width:100%}.image.is-square,#documenter .docs-sidebar .docs-logo>img.is-square,.image.is-1by1,#documenter .docs-sidebar .docs-logo>img.is-1by1{padding-top:100%}.image.is-5by4,#documenter .docs-sidebar .docs-logo>img.is-5by4{padding-top:80%}.image.is-4by3,#documenter .docs-sidebar .docs-logo>img.is-4by3{padding-top:75%}.image.is-3by2,#documenter .docs-sidebar .docs-logo>img.is-3by2{padding-top:66.6666%}.image.is-5by3,#documenter .docs-sidebar .docs-logo>img.is-5by3{padding-top:60%}.image.is-16by9,#documenter .docs-sidebar .docs-logo>img.is-16by9{padding-top:56.25%}.image.is-2by1,#documenter .docs-sidebar .docs-logo>img.is-2by1{padding-top:50%}.image.is-3by1,#documenter .docs-sidebar .docs-logo>img.is-3by1{padding-top:33.3333%}.image.is-4by5,#documenter .docs-sidebar .docs-logo>img.is-4by5{padding-top:125%}.image.is-3by4,#documenter .docs-sidebar .docs-logo>img.is-3by4{padding-top:133.3333%}.image.is-2by3,#documenter .docs-sidebar .docs-logo>img.is-2by3{padding-top:150%}.image.is-3by5,#documenter .docs-sidebar .docs-logo>img.is-3by5{padding-top:166.6666%}.image.is-9by16,#documenter .docs-sidebar .docs-logo>img.is-9by16{padding-top:177.7777%}.image.is-1by2,#documenter .docs-sidebar .docs-logo>img.is-1by2{padding-top:200%}.image.is-1by3,#documenter .docs-sidebar .docs-logo>img.is-1by3{padding-top:300%}.image.is-16x16,#documenter .docs-sidebar .docs-logo>img.is-16x16{height:16px;width:16px}.image.is-24x24,#documenter .docs-sidebar .docs-logo>img.is-24x24{height:24px;width:24px}.image.is-32x32,#documenter .docs-sidebar .docs-logo>img.is-32x32{height:32px;width:32px}.image.is-48x48,#documenter .docs-sidebar .docs-logo>img.is-48x48{height:48px;width:48px}.image.is-64x64,#documenter .docs-sidebar .docs-logo>img.is-64x64{height:64px;width:64px}.image.is-96x96,#documenter .docs-sidebar .docs-logo>img.is-96x96{height:96px;width:96px}.image.is-128x128,#documenter .docs-sidebar .docs-logo>img.is-128x128{height:128px;width:128px}.notification{background-color:#f5f5f5;border-radius:4px;position:relative;padding:1.25rem 2.5rem 1.25rem 1.5rem}.notification a:not(.button):not(.dropdown-item){color:currentColor;text-decoration:underline}.notification strong{color:currentColor}.notification code,.notification pre{background:#fff}.notification pre code{background:transparent}.notification>.delete{right:.5rem;position:absolute;top:0.5rem}.notification .title,.notification .subtitle,.notification .content{color:currentColor}.notification.is-white{background-color:#fff;color:#0a0a0a}.notification.is-black{background-color:#0a0a0a;color:#fff}.notification.is-light{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}.notification.is-dark,.content kbd.notification{background-color:#363636;color:#fff}.notification.is-primary,.docstring>section>a.notification.docs-sourcelink{background-color:#4eb5de;color:#fff}.notification.is-primary.is-light,.docstring>section>a.notification.is-light.docs-sourcelink{background-color:#eef8fc;color:#1a6d8e}.notification.is-link{background-color:#2e63b8;color:#fff}.notification.is-link.is-light{background-color:#eff3fb;color:#3169c4}.notification.is-info{background-color:#209cee;color:#fff}.notification.is-info.is-light{background-color:#ecf7fe;color:#0e72b4}.notification.is-success{background-color:#22c35b;color:#fff}.notification.is-success.is-light{background-color:#eefcf3;color:#198f43}.notification.is-warning{background-color:#ffdd57;color:rgba(0,0,0,0.7)}.notification.is-warning.is-light{background-color:#fffbeb;color:#947600}.notification.is-danger{background-color:#da0b00;color:#fff}.notification.is-danger.is-light{background-color:#ffeceb;color:#f50c00}.progress{-moz-appearance:none;-webkit-appearance:none;border:none;border-radius:9999px;display:block;height:1rem;overflow:hidden;padding:0;width:100%}.progress::-webkit-progress-bar{background-color:#ededed}.progress::-webkit-progress-value{background-color:#222}.progress::-moz-progress-bar{background-color:#222}.progress::-ms-fill{background-color:#222;border:none}.progress.is-white::-webkit-progress-value{background-color:#fff}.progress.is-white::-moz-progress-bar{background-color:#fff}.progress.is-white::-ms-fill{background-color:#fff}.progress.is-white:indeterminate{background-image:linear-gradient(to right, #fff 30%, #ededed 30%)}.progress.is-black::-webkit-progress-value{background-color:#0a0a0a}.progress.is-black::-moz-progress-bar{background-color:#0a0a0a}.progress.is-black::-ms-fill{background-color:#0a0a0a}.progress.is-black:indeterminate{background-image:linear-gradient(to right, #0a0a0a 30%, #ededed 30%)}.progress.is-light::-webkit-progress-value{background-color:#f5f5f5}.progress.is-light::-moz-progress-bar{background-color:#f5f5f5}.progress.is-light::-ms-fill{background-color:#f5f5f5}.progress.is-light:indeterminate{background-image:linear-gradient(to right, #f5f5f5 30%, #ededed 30%)}.progress.is-dark::-webkit-progress-value,.content kbd.progress::-webkit-progress-value{background-color:#363636}.progress.is-dark::-moz-progress-bar,.content kbd.progress::-moz-progress-bar{background-color:#363636}.progress.is-dark::-ms-fill,.content kbd.progress::-ms-fill{background-color:#363636}.progress.is-dark:indeterminate,.content kbd.progress:indeterminate{background-image:linear-gradient(to right, #363636 30%, #ededed 30%)}.progress.is-primary::-webkit-progress-value,.docstring>section>a.progress.docs-sourcelink::-webkit-progress-value{background-color:#4eb5de}.progress.is-primary::-moz-progress-bar,.docstring>section>a.progress.docs-sourcelink::-moz-progress-bar{background-color:#4eb5de}.progress.is-primary::-ms-fill,.docstring>section>a.progress.docs-sourcelink::-ms-fill{background-color:#4eb5de}.progress.is-primary:indeterminate,.docstring>section>a.progress.docs-sourcelink:indeterminate{background-image:linear-gradient(to right, #4eb5de 30%, #ededed 30%)}.progress.is-link::-webkit-progress-value{background-color:#2e63b8}.progress.is-link::-moz-progress-bar{background-color:#2e63b8}.progress.is-link::-ms-fill{background-color:#2e63b8}.progress.is-link:indeterminate{background-image:linear-gradient(to right, #2e63b8 30%, #ededed 30%)}.progress.is-info::-webkit-progress-value{background-color:#209cee}.progress.is-info::-moz-progress-bar{background-color:#209cee}.progress.is-info::-ms-fill{background-color:#209cee}.progress.is-info:indeterminate{background-image:linear-gradient(to right, #209cee 30%, #ededed 30%)}.progress.is-success::-webkit-progress-value{background-color:#22c35b}.progress.is-success::-moz-progress-bar{background-color:#22c35b}.progress.is-success::-ms-fill{background-color:#22c35b}.progress.is-success:indeterminate{background-image:linear-gradient(to right, #22c35b 30%, #ededed 30%)}.progress.is-warning::-webkit-progress-value{background-color:#ffdd57}.progress.is-warning::-moz-progress-bar{background-color:#ffdd57}.progress.is-warning::-ms-fill{background-color:#ffdd57}.progress.is-warning:indeterminate{background-image:linear-gradient(to right, #ffdd57 30%, #ededed 30%)}.progress.is-danger::-webkit-progress-value{background-color:#da0b00}.progress.is-danger::-moz-progress-bar{background-color:#da0b00}.progress.is-danger::-ms-fill{background-color:#da0b00}.progress.is-danger:indeterminate{background-image:linear-gradient(to right, #da0b00 30%, #ededed 30%)}.progress:indeterminate{animation-duration:1.5s;animation-iteration-count:infinite;animation-name:moveIndeterminate;animation-timing-function:linear;background-color:#ededed;background-image:linear-gradient(to right, #222 30%, #ededed 30%);background-position:top left;background-repeat:no-repeat;background-size:150% 150%}.progress:indeterminate::-webkit-progress-bar{background-color:transparent}.progress:indeterminate::-moz-progress-bar{background-color:transparent}.progress:indeterminate::-ms-fill{animation-name:none}.progress.is-small,#documenter .docs-sidebar form.docs-search>input.progress{height:.75rem}.progress.is-medium{height:1.25rem}.progress.is-large{height:1.5rem}@keyframes moveIndeterminate{from{background-position:200% 0}to{background-position:-200% 0}}.table{background-color:#fff;color:#222}.table td,.table th{border:1px solid #dbdbdb;border-width:0 0 1px;padding:0.5em 0.75em;vertical-align:top}.table td.is-white,.table th.is-white{background-color:#fff;border-color:#fff;color:#0a0a0a}.table td.is-black,.table th.is-black{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}.table td.is-light,.table th.is-light{background-color:#f5f5f5;border-color:#f5f5f5;color:rgba(0,0,0,0.7)}.table td.is-dark,.table th.is-dark{background-color:#363636;border-color:#363636;color:#fff}.table td.is-primary,.table th.is-primary{background-color:#4eb5de;border-color:#4eb5de;color:#fff}.table td.is-link,.table th.is-link{background-color:#2e63b8;border-color:#2e63b8;color:#fff}.table td.is-info,.table th.is-info{background-color:#209cee;border-color:#209cee;color:#fff}.table td.is-success,.table th.is-success{background-color:#22c35b;border-color:#22c35b;color:#fff}.table td.is-warning,.table th.is-warning{background-color:#ffdd57;border-color:#ffdd57;color:rgba(0,0,0,0.7)}.table td.is-danger,.table th.is-danger{background-color:#da0b00;border-color:#da0b00;color:#fff}.table td.is-narrow,.table th.is-narrow{white-space:nowrap;width:1%}.table td.is-selected,.table th.is-selected{background-color:#4eb5de;color:#fff}.table td.is-selected a,.table td.is-selected strong,.table th.is-selected a,.table th.is-selected strong{color:currentColor}.table td.is-vcentered,.table th.is-vcentered{vertical-align:middle}.table th{color:#222}.table th:not([align]){text-align:left}.table tr.is-selected{background-color:#4eb5de;color:#fff}.table tr.is-selected a,.table tr.is-selected strong{color:currentColor}.table tr.is-selected td,.table tr.is-selected th{border-color:#fff;color:currentColor}.table thead{background-color:rgba(0,0,0,0)}.table thead td,.table thead th{border-width:0 0 2px;color:#222}.table tfoot{background-color:rgba(0,0,0,0)}.table tfoot td,.table tfoot th{border-width:2px 0 0;color:#222}.table tbody{background-color:rgba(0,0,0,0)}.table tbody tr:last-child td,.table tbody tr:last-child th{border-bottom-width:0}.table.is-bordered td,.table.is-bordered th{border-width:1px}.table.is-bordered tr:last-child td,.table.is-bordered tr:last-child th{border-bottom-width:1px}.table.is-fullwidth{width:100%}.table.is-hoverable tbody tr:not(.is-selected):hover{background-color:#fafafa}.table.is-hoverable.is-striped tbody tr:not(.is-selected):hover{background-color:#fafafa}.table.is-hoverable.is-striped tbody tr:not(.is-selected):hover:nth-child(even){background-color:#f5f5f5}.table.is-narrow td,.table.is-narrow th{padding:0.25em 0.5em}.table.is-striped tbody tr:not(.is-selected):nth-child(even){background-color:#fafafa}.table-container{-webkit-overflow-scrolling:touch;overflow:auto;overflow-y:hidden;max-width:100%}.tags{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}.tags .tag,.tags .content kbd,.content .tags kbd,.tags .docstring>section>a.docs-sourcelink{margin-bottom:0.5rem}.tags .tag:not(:last-child),.tags .content kbd:not(:last-child),.content .tags kbd:not(:last-child),.tags .docstring>section>a.docs-sourcelink:not(:last-child){margin-right:.5rem}.tags:last-child{margin-bottom:-0.5rem}.tags:not(:last-child){margin-bottom:1rem}.tags.are-medium .tag:not(.is-normal):not(.is-large),.tags.are-medium .content kbd:not(.is-normal):not(.is-large),.content .tags.are-medium kbd:not(.is-normal):not(.is-large),.tags.are-medium .docstring>section>a.docs-sourcelink:not(.is-normal):not(.is-large){font-size:1rem}.tags.are-large .tag:not(.is-normal):not(.is-medium),.tags.are-large .content kbd:not(.is-normal):not(.is-medium),.content .tags.are-large kbd:not(.is-normal):not(.is-medium),.tags.are-large .docstring>section>a.docs-sourcelink:not(.is-normal):not(.is-medium){font-size:1.25rem}.tags.is-centered{justify-content:center}.tags.is-centered .tag,.tags.is-centered .content kbd,.content .tags.is-centered kbd,.tags.is-centered .docstring>section>a.docs-sourcelink{margin-right:0.25rem;margin-left:0.25rem}.tags.is-right{justify-content:flex-end}.tags.is-right .tag:not(:first-child),.tags.is-right .content kbd:not(:first-child),.content .tags.is-right kbd:not(:first-child),.tags.is-right .docstring>section>a.docs-sourcelink:not(:first-child){margin-left:0.5rem}.tags.is-right .tag:not(:last-child),.tags.is-right .content kbd:not(:last-child),.content .tags.is-right kbd:not(:last-child),.tags.is-right .docstring>section>a.docs-sourcelink:not(:last-child){margin-right:0}.tags.has-addons .tag,.tags.has-addons .content kbd,.content .tags.has-addons kbd,.tags.has-addons .docstring>section>a.docs-sourcelink{margin-right:0}.tags.has-addons .tag:not(:first-child),.tags.has-addons .content kbd:not(:first-child),.content .tags.has-addons kbd:not(:first-child),.tags.has-addons .docstring>section>a.docs-sourcelink:not(:first-child){margin-left:0;border-top-left-radius:0;border-bottom-left-radius:0}.tags.has-addons .tag:not(:last-child),.tags.has-addons .content kbd:not(:last-child),.content .tags.has-addons kbd:not(:last-child),.tags.has-addons .docstring>section>a.docs-sourcelink:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.tag:not(body),.content kbd:not(body),.docstring>section>a.docs-sourcelink:not(body){align-items:center;background-color:#f5f5f5;border-radius:4px;color:#222;display:inline-flex;font-size:.75rem;height:2em;justify-content:center;line-height:1.5;padding-left:0.75em;padding-right:0.75em;white-space:nowrap}.tag:not(body) .delete,.content kbd:not(body) .delete,.docstring>section>a.docs-sourcelink:not(body) .delete{margin-left:.25rem;margin-right:-.375rem}.tag.is-white:not(body),.content kbd.is-white:not(body),.docstring>section>a.docs-sourcelink.is-white:not(body){background-color:#fff;color:#0a0a0a}.tag.is-black:not(body),.content kbd.is-black:not(body),.docstring>section>a.docs-sourcelink.is-black:not(body){background-color:#0a0a0a;color:#fff}.tag.is-light:not(body),.content kbd.is-light:not(body),.docstring>section>a.docs-sourcelink.is-light:not(body){background-color:#f5f5f5;color:rgba(0,0,0,0.7)}.tag.is-dark:not(body),.content kbd:not(body),.docstring>section>a.docs-sourcelink.is-dark:not(body),.content .docstring>section>kbd:not(body){background-color:#363636;color:#fff}.tag.is-primary:not(body),.content kbd.is-primary:not(body),.docstring>section>a.docs-sourcelink:not(body){background-color:#4eb5de;color:#fff}.tag.is-primary.is-light:not(body),.content kbd.is-primary.is-light:not(body),.docstring>section>a.docs-sourcelink.is-light:not(body){background-color:#eef8fc;color:#1a6d8e}.tag.is-link:not(body),.content kbd.is-link:not(body),.docstring>section>a.docs-sourcelink.is-link:not(body){background-color:#2e63b8;color:#fff}.tag.is-link.is-light:not(body),.content kbd.is-link.is-light:not(body),.docstring>section>a.docs-sourcelink.is-link.is-light:not(body){background-color:#eff3fb;color:#3169c4}.tag.is-info:not(body),.content kbd.is-info:not(body),.docstring>section>a.docs-sourcelink.is-info:not(body){background-color:#209cee;color:#fff}.tag.is-info.is-light:not(body),.content kbd.is-info.is-light:not(body),.docstring>section>a.docs-sourcelink.is-info.is-light:not(body){background-color:#ecf7fe;color:#0e72b4}.tag.is-success:not(body),.content kbd.is-success:not(body),.docstring>section>a.docs-sourcelink.is-success:not(body){background-color:#22c35b;color:#fff}.tag.is-success.is-light:not(body),.content kbd.is-success.is-light:not(body),.docstring>section>a.docs-sourcelink.is-success.is-light:not(body){background-color:#eefcf3;color:#198f43}.tag.is-warning:not(body),.content kbd.is-warning:not(body),.docstring>section>a.docs-sourcelink.is-warning:not(body){background-color:#ffdd57;color:rgba(0,0,0,0.7)}.tag.is-warning.is-light:not(body),.content kbd.is-warning.is-light:not(body),.docstring>section>a.docs-sourcelink.is-warning.is-light:not(body){background-color:#fffbeb;color:#947600}.tag.is-danger:not(body),.content kbd.is-danger:not(body),.docstring>section>a.docs-sourcelink.is-danger:not(body){background-color:#da0b00;color:#fff}.tag.is-danger.is-light:not(body),.content kbd.is-danger.is-light:not(body),.docstring>section>a.docs-sourcelink.is-danger.is-light:not(body){background-color:#ffeceb;color:#f50c00}.tag.is-normal:not(body),.content kbd.is-normal:not(body),.docstring>section>a.docs-sourcelink.is-normal:not(body){font-size:.75rem}.tag.is-medium:not(body),.content kbd.is-medium:not(body),.docstring>section>a.docs-sourcelink.is-medium:not(body){font-size:1rem}.tag.is-large:not(body),.content kbd.is-large:not(body),.docstring>section>a.docs-sourcelink.is-large:not(body){font-size:1.25rem}.tag:not(body) .icon:first-child:not(:last-child),.content kbd:not(body) .icon:first-child:not(:last-child),.docstring>section>a.docs-sourcelink:not(body) .icon:first-child:not(:last-child){margin-left:-.375em;margin-right:.1875em}.tag:not(body) .icon:last-child:not(:first-child),.content kbd:not(body) .icon:last-child:not(:first-child),.docstring>section>a.docs-sourcelink:not(body) .icon:last-child:not(:first-child){margin-left:.1875em;margin-right:-.375em}.tag:not(body) .icon:first-child:last-child,.content kbd:not(body) .icon:first-child:last-child,.docstring>section>a.docs-sourcelink:not(body) .icon:first-child:last-child{margin-left:-.375em;margin-right:-.375em}.tag.is-delete:not(body),.content kbd.is-delete:not(body),.docstring>section>a.docs-sourcelink.is-delete:not(body){margin-left:1px;padding:0;position:relative;width:2em}.tag.is-delete:not(body)::before,.content kbd.is-delete:not(body)::before,.docstring>section>a.docs-sourcelink.is-delete:not(body)::before,.tag.is-delete:not(body)::after,.content kbd.is-delete:not(body)::after,.docstring>section>a.docs-sourcelink.is-delete:not(body)::after{background-color:currentColor;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}.tag.is-delete:not(body)::before,.content kbd.is-delete:not(body)::before,.docstring>section>a.docs-sourcelink.is-delete:not(body)::before{height:1px;width:50%}.tag.is-delete:not(body)::after,.content kbd.is-delete:not(body)::after,.docstring>section>a.docs-sourcelink.is-delete:not(body)::after{height:50%;width:1px}.tag.is-delete:not(body):hover,.content kbd.is-delete:not(body):hover,.docstring>section>a.docs-sourcelink.is-delete:not(body):hover,.tag.is-delete:not(body):focus,.content kbd.is-delete:not(body):focus,.docstring>section>a.docs-sourcelink.is-delete:not(body):focus{background-color:#e8e8e8}.tag.is-delete:not(body):active,.content kbd.is-delete:not(body):active,.docstring>section>a.docs-sourcelink.is-delete:not(body):active{background-color:#dbdbdb}.tag.is-rounded:not(body),#documenter .docs-sidebar form.docs-search>input:not(body),.content kbd.is-rounded:not(body),#documenter .docs-sidebar .content form.docs-search>input:not(body),.docstring>section>a.docs-sourcelink.is-rounded:not(body){border-radius:9999px}a.tag:hover,.docstring>section>a.docs-sourcelink:hover{text-decoration:underline}.title,.subtitle{word-break:break-word}.title em,.title span,.subtitle em,.subtitle span{font-weight:inherit}.title sub,.subtitle sub{font-size:.75em}.title sup,.subtitle sup{font-size:.75em}.title .tag,.title .content kbd,.content .title kbd,.title .docstring>section>a.docs-sourcelink,.subtitle .tag,.subtitle .content kbd,.content .subtitle kbd,.subtitle .docstring>section>a.docs-sourcelink{vertical-align:middle}.title{color:#222;font-size:2rem;font-weight:600;line-height:1.125}.title strong{color:inherit;font-weight:inherit}.title:not(.is-spaced)+.subtitle{margin-top:-1.25rem}.title.is-1{font-size:3rem}.title.is-2{font-size:2.5rem}.title.is-3{font-size:2rem}.title.is-4{font-size:1.5rem}.title.is-5{font-size:1.25rem}.title.is-6{font-size:1rem}.title.is-7{font-size:.75rem}.subtitle{color:#222;font-size:1.25rem;font-weight:400;line-height:1.25}.subtitle strong{color:#222;font-weight:600}.subtitle:not(.is-spaced)+.title{margin-top:-1.25rem}.subtitle.is-1{font-size:3rem}.subtitle.is-2{font-size:2.5rem}.subtitle.is-3{font-size:2rem}.subtitle.is-4{font-size:1.5rem}.subtitle.is-5{font-size:1.25rem}.subtitle.is-6{font-size:1rem}.subtitle.is-7{font-size:.75rem}.heading{display:block;font-size:11px;letter-spacing:1px;margin-bottom:5px;text-transform:uppercase}.number{align-items:center;background-color:#f5f5f5;border-radius:9999px;display:inline-flex;font-size:1.25rem;height:2em;justify-content:center;margin-right:1.5rem;min-width:2.5em;padding:0.25rem 0.5rem;text-align:center;vertical-align:top}.select select,.textarea,.input,#documenter .docs-sidebar form.docs-search>input{background-color:#fff;border-color:#dbdbdb;border-radius:4px;color:#222}.select select::-moz-placeholder,.textarea::-moz-placeholder,.input::-moz-placeholder,#documenter .docs-sidebar form.docs-search>input::-moz-placeholder{color:#707070}.select select::-webkit-input-placeholder,.textarea::-webkit-input-placeholder,.input::-webkit-input-placeholder,#documenter .docs-sidebar form.docs-search>input::-webkit-input-placeholder{color:#707070}.select select:-moz-placeholder,.textarea:-moz-placeholder,.input:-moz-placeholder,#documenter .docs-sidebar form.docs-search>input:-moz-placeholder{color:#707070}.select select:-ms-input-placeholder,.textarea:-ms-input-placeholder,.input:-ms-input-placeholder,#documenter .docs-sidebar form.docs-search>input:-ms-input-placeholder{color:#707070}.select select:hover,.textarea:hover,.input:hover,#documenter .docs-sidebar form.docs-search>input:hover,.select select.is-hovered,.is-hovered.textarea,.is-hovered.input,#documenter .docs-sidebar form.docs-search>input.is-hovered{border-color:#b5b5b5}.select select:focus,.textarea:focus,.input:focus,#documenter .docs-sidebar form.docs-search>input:focus,.select select.is-focused,.is-focused.textarea,.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.select select:active,.textarea:active,.input:active,#documenter .docs-sidebar form.docs-search>input:active,.select select.is-active,.is-active.textarea,.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active{border-color:#2e63b8;box-shadow:0 0 0 0.125em rgba(46,99,184,0.25)}.select select[disabled],.textarea[disabled],.input[disabled],#documenter .docs-sidebar form.docs-search>input[disabled],fieldset[disabled] .select select,.select fieldset[disabled] select,fieldset[disabled] .textarea,fieldset[disabled] .input,fieldset[disabled] #documenter .docs-sidebar form.docs-search>input,#documenter .docs-sidebar fieldset[disabled] form.docs-search>input{background-color:#f5f5f5;border-color:#f5f5f5;box-shadow:none;color:#6b6b6b}.select select[disabled]::-moz-placeholder,.textarea[disabled]::-moz-placeholder,.input[disabled]::-moz-placeholder,#documenter .docs-sidebar form.docs-search>input[disabled]::-moz-placeholder,fieldset[disabled] .select select::-moz-placeholder,.select fieldset[disabled] select::-moz-placeholder,fieldset[disabled] .textarea::-moz-placeholder,fieldset[disabled] .input::-moz-placeholder,fieldset[disabled] #documenter .docs-sidebar form.docs-search>input::-moz-placeholder,#documenter .docs-sidebar fieldset[disabled] form.docs-search>input::-moz-placeholder{color:rgba(107,107,107,0.3)}.select select[disabled]::-webkit-input-placeholder,.textarea[disabled]::-webkit-input-placeholder,.input[disabled]::-webkit-input-placeholder,#documenter .docs-sidebar form.docs-search>input[disabled]::-webkit-input-placeholder,fieldset[disabled] .select select::-webkit-input-placeholder,.select fieldset[disabled] select::-webkit-input-placeholder,fieldset[disabled] .textarea::-webkit-input-placeholder,fieldset[disabled] .input::-webkit-input-placeholder,fieldset[disabled] #documenter .docs-sidebar form.docs-search>input::-webkit-input-placeholder,#documenter .docs-sidebar fieldset[disabled] form.docs-search>input::-webkit-input-placeholder{color:rgba(107,107,107,0.3)}.select select[disabled]:-moz-placeholder,.textarea[disabled]:-moz-placeholder,.input[disabled]:-moz-placeholder,#documenter .docs-sidebar form.docs-search>input[disabled]:-moz-placeholder,fieldset[disabled] .select select:-moz-placeholder,.select fieldset[disabled] select:-moz-placeholder,fieldset[disabled] .textarea:-moz-placeholder,fieldset[disabled] .input:-moz-placeholder,fieldset[disabled] #documenter .docs-sidebar form.docs-search>input:-moz-placeholder,#documenter .docs-sidebar fieldset[disabled] form.docs-search>input:-moz-placeholder{color:rgba(107,107,107,0.3)}.select select[disabled]:-ms-input-placeholder,.textarea[disabled]:-ms-input-placeholder,.input[disabled]:-ms-input-placeholder,#documenter .docs-sidebar form.docs-search>input[disabled]:-ms-input-placeholder,fieldset[disabled] .select select:-ms-input-placeholder,.select fieldset[disabled] select:-ms-input-placeholder,fieldset[disabled] .textarea:-ms-input-placeholder,fieldset[disabled] .input:-ms-input-placeholder,fieldset[disabled] #documenter .docs-sidebar form.docs-search>input:-ms-input-placeholder,#documenter .docs-sidebar fieldset[disabled] form.docs-search>input:-ms-input-placeholder{color:rgba(107,107,107,0.3)}.textarea,.input,#documenter .docs-sidebar form.docs-search>input{box-shadow:inset 0 0.0625em 0.125em rgba(10,10,10,0.05);max-width:100%;width:100%}.textarea[readonly],.input[readonly],#documenter .docs-sidebar form.docs-search>input[readonly]{box-shadow:none}.is-white.textarea,.is-white.input,#documenter .docs-sidebar form.docs-search>input.is-white{border-color:#fff}.is-white.textarea:focus,.is-white.input:focus,#documenter .docs-sidebar form.docs-search>input.is-white:focus,.is-white.is-focused.textarea,.is-white.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.is-white.textarea:active,.is-white.input:active,#documenter .docs-sidebar form.docs-search>input.is-white:active,.is-white.is-active.textarea,.is-white.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}.is-black.textarea,.is-black.input,#documenter .docs-sidebar form.docs-search>input.is-black{border-color:#0a0a0a}.is-black.textarea:focus,.is-black.input:focus,#documenter .docs-sidebar form.docs-search>input.is-black:focus,.is-black.is-focused.textarea,.is-black.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.is-black.textarea:active,.is-black.input:active,#documenter .docs-sidebar form.docs-search>input.is-black:active,.is-black.is-active.textarea,.is-black.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}.is-light.textarea,.is-light.input,#documenter .docs-sidebar form.docs-search>input.is-light{border-color:#f5f5f5}.is-light.textarea:focus,.is-light.input:focus,#documenter .docs-sidebar form.docs-search>input.is-light:focus,.is-light.is-focused.textarea,.is-light.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.is-light.textarea:active,.is-light.input:active,#documenter .docs-sidebar form.docs-search>input.is-light:active,.is-light.is-active.textarea,.is-light.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(245,245,245,0.25)}.is-dark.textarea,.content kbd.textarea,.is-dark.input,#documenter .docs-sidebar form.docs-search>input.is-dark,.content kbd.input{border-color:#363636}.is-dark.textarea:focus,.content kbd.textarea:focus,.is-dark.input:focus,#documenter .docs-sidebar form.docs-search>input.is-dark:focus,.content kbd.input:focus,.is-dark.is-focused.textarea,.content kbd.is-focused.textarea,.is-dark.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.content kbd.is-focused.input,#documenter .docs-sidebar .content form.docs-search>input.is-focused,.is-dark.textarea:active,.content kbd.textarea:active,.is-dark.input:active,#documenter .docs-sidebar form.docs-search>input.is-dark:active,.content kbd.input:active,.is-dark.is-active.textarea,.content kbd.is-active.textarea,.is-dark.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active,.content kbd.is-active.input,#documenter .docs-sidebar .content form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(54,54,54,0.25)}.is-primary.textarea,.docstring>section>a.textarea.docs-sourcelink,.is-primary.input,#documenter .docs-sidebar form.docs-search>input.is-primary,.docstring>section>a.input.docs-sourcelink{border-color:#4eb5de}.is-primary.textarea:focus,.docstring>section>a.textarea.docs-sourcelink:focus,.is-primary.input:focus,#documenter .docs-sidebar form.docs-search>input.is-primary:focus,.docstring>section>a.input.docs-sourcelink:focus,.is-primary.is-focused.textarea,.docstring>section>a.is-focused.textarea.docs-sourcelink,.is-primary.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.docstring>section>a.is-focused.input.docs-sourcelink,.is-primary.textarea:active,.docstring>section>a.textarea.docs-sourcelink:active,.is-primary.input:active,#documenter .docs-sidebar form.docs-search>input.is-primary:active,.docstring>section>a.input.docs-sourcelink:active,.is-primary.is-active.textarea,.docstring>section>a.is-active.textarea.docs-sourcelink,.is-primary.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active,.docstring>section>a.is-active.input.docs-sourcelink{box-shadow:0 0 0 0.125em rgba(78,181,222,0.25)}.is-link.textarea,.is-link.input,#documenter .docs-sidebar form.docs-search>input.is-link{border-color:#2e63b8}.is-link.textarea:focus,.is-link.input:focus,#documenter .docs-sidebar form.docs-search>input.is-link:focus,.is-link.is-focused.textarea,.is-link.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.is-link.textarea:active,.is-link.input:active,#documenter .docs-sidebar form.docs-search>input.is-link:active,.is-link.is-active.textarea,.is-link.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(46,99,184,0.25)}.is-info.textarea,.is-info.input,#documenter .docs-sidebar form.docs-search>input.is-info{border-color:#209cee}.is-info.textarea:focus,.is-info.input:focus,#documenter .docs-sidebar form.docs-search>input.is-info:focus,.is-info.is-focused.textarea,.is-info.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.is-info.textarea:active,.is-info.input:active,#documenter .docs-sidebar form.docs-search>input.is-info:active,.is-info.is-active.textarea,.is-info.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(32,156,238,0.25)}.is-success.textarea,.is-success.input,#documenter .docs-sidebar form.docs-search>input.is-success{border-color:#22c35b}.is-success.textarea:focus,.is-success.input:focus,#documenter .docs-sidebar form.docs-search>input.is-success:focus,.is-success.is-focused.textarea,.is-success.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.is-success.textarea:active,.is-success.input:active,#documenter .docs-sidebar form.docs-search>input.is-success:active,.is-success.is-active.textarea,.is-success.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(34,195,91,0.25)}.is-warning.textarea,.is-warning.input,#documenter .docs-sidebar form.docs-search>input.is-warning{border-color:#ffdd57}.is-warning.textarea:focus,.is-warning.input:focus,#documenter .docs-sidebar form.docs-search>input.is-warning:focus,.is-warning.is-focused.textarea,.is-warning.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.is-warning.textarea:active,.is-warning.input:active,#documenter .docs-sidebar form.docs-search>input.is-warning:active,.is-warning.is-active.textarea,.is-warning.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(255,221,87,0.25)}.is-danger.textarea,.is-danger.input,#documenter .docs-sidebar form.docs-search>input.is-danger{border-color:#da0b00}.is-danger.textarea:focus,.is-danger.input:focus,#documenter .docs-sidebar form.docs-search>input.is-danger:focus,.is-danger.is-focused.textarea,.is-danger.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.is-danger.textarea:active,.is-danger.input:active,#documenter .docs-sidebar form.docs-search>input.is-danger:active,.is-danger.is-active.textarea,.is-danger.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(218,11,0,0.25)}.is-small.textarea,.is-small.input,#documenter .docs-sidebar form.docs-search>input{border-radius:2px;font-size:.75rem}.is-medium.textarea,.is-medium.input,#documenter .docs-sidebar form.docs-search>input.is-medium{font-size:1.25rem}.is-large.textarea,.is-large.input,#documenter .docs-sidebar form.docs-search>input.is-large{font-size:1.5rem}.is-fullwidth.textarea,.is-fullwidth.input,#documenter .docs-sidebar form.docs-search>input.is-fullwidth{display:block;width:100%}.is-inline.textarea,.is-inline.input,#documenter .docs-sidebar form.docs-search>input.is-inline{display:inline;width:auto}.input.is-rounded,#documenter .docs-sidebar form.docs-search>input{border-radius:9999px;padding-left:calc(calc(0.75em - 1px) + 0.375em);padding-right:calc(calc(0.75em - 1px) + 0.375em)}.input.is-static,#documenter .docs-sidebar form.docs-search>input.is-static{background-color:transparent;border-color:transparent;box-shadow:none;padding-left:0;padding-right:0}.textarea{display:block;max-width:100%;min-width:100%;padding:calc(0.75em - 1px);resize:vertical}.textarea:not([rows]){max-height:40em;min-height:8em}.textarea[rows]{height:initial}.textarea.has-fixed-size{resize:none}.radio,.checkbox{cursor:pointer;display:inline-block;line-height:1.25;position:relative}.radio input,.checkbox input{cursor:pointer}.radio:hover,.checkbox:hover{color:#222}.radio[disabled],.checkbox[disabled],fieldset[disabled] .radio,fieldset[disabled] .checkbox,.radio input[disabled],.checkbox input[disabled]{color:#6b6b6b;cursor:not-allowed}.radio+.radio{margin-left:.5em}.select{display:inline-block;max-width:100%;position:relative;vertical-align:top}.select:not(.is-multiple){height:2.5em}.select:not(.is-multiple):not(.is-loading)::after{border-color:#2e63b8;right:1.125em;z-index:4}.select.is-rounded select,#documenter .docs-sidebar form.docs-search>input.select select{border-radius:9999px;padding-left:1em}.select select{cursor:pointer;display:block;font-size:1em;max-width:100%;outline:none}.select select::-ms-expand{display:none}.select select[disabled]:hover,fieldset[disabled] .select select:hover{border-color:#f5f5f5}.select select:not([multiple]){padding-right:2.5em}.select select[multiple]{height:auto;padding:0}.select select[multiple] option{padding:0.5em 1em}.select:not(.is-multiple):not(.is-loading):hover::after{border-color:#222}.select.is-white:not(:hover)::after{border-color:#fff}.select.is-white select{border-color:#fff}.select.is-white select:hover,.select.is-white select.is-hovered{border-color:#f2f2f2}.select.is-white select:focus,.select.is-white select.is-focused,.select.is-white select:active,.select.is-white select.is-active{box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}.select.is-black:not(:hover)::after{border-color:#0a0a0a}.select.is-black select{border-color:#0a0a0a}.select.is-black select:hover,.select.is-black select.is-hovered{border-color:#000}.select.is-black select:focus,.select.is-black select.is-focused,.select.is-black select:active,.select.is-black select.is-active{box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}.select.is-light:not(:hover)::after{border-color:#f5f5f5}.select.is-light select{border-color:#f5f5f5}.select.is-light select:hover,.select.is-light select.is-hovered{border-color:#e8e8e8}.select.is-light select:focus,.select.is-light select.is-focused,.select.is-light select:active,.select.is-light select.is-active{box-shadow:0 0 0 0.125em rgba(245,245,245,0.25)}.select.is-dark:not(:hover)::after,.content kbd.select:not(:hover)::after{border-color:#363636}.select.is-dark select,.content kbd.select select{border-color:#363636}.select.is-dark select:hover,.content kbd.select select:hover,.select.is-dark select.is-hovered,.content kbd.select select.is-hovered{border-color:#292929}.select.is-dark select:focus,.content kbd.select select:focus,.select.is-dark select.is-focused,.content kbd.select select.is-focused,.select.is-dark select:active,.content kbd.select select:active,.select.is-dark select.is-active,.content kbd.select select.is-active{box-shadow:0 0 0 0.125em rgba(54,54,54,0.25)}.select.is-primary:not(:hover)::after,.docstring>section>a.select.docs-sourcelink:not(:hover)::after{border-color:#4eb5de}.select.is-primary select,.docstring>section>a.select.docs-sourcelink select{border-color:#4eb5de}.select.is-primary select:hover,.docstring>section>a.select.docs-sourcelink select:hover,.select.is-primary select.is-hovered,.docstring>section>a.select.docs-sourcelink select.is-hovered{border-color:#39acda}.select.is-primary select:focus,.docstring>section>a.select.docs-sourcelink select:focus,.select.is-primary select.is-focused,.docstring>section>a.select.docs-sourcelink select.is-focused,.select.is-primary select:active,.docstring>section>a.select.docs-sourcelink select:active,.select.is-primary select.is-active,.docstring>section>a.select.docs-sourcelink select.is-active{box-shadow:0 0 0 0.125em rgba(78,181,222,0.25)}.select.is-link:not(:hover)::after{border-color:#2e63b8}.select.is-link select{border-color:#2e63b8}.select.is-link select:hover,.select.is-link select.is-hovered{border-color:#2958a4}.select.is-link select:focus,.select.is-link select.is-focused,.select.is-link select:active,.select.is-link select.is-active{box-shadow:0 0 0 0.125em rgba(46,99,184,0.25)}.select.is-info:not(:hover)::after{border-color:#209cee}.select.is-info select{border-color:#209cee}.select.is-info select:hover,.select.is-info select.is-hovered{border-color:#1190e3}.select.is-info select:focus,.select.is-info select.is-focused,.select.is-info select:active,.select.is-info select.is-active{box-shadow:0 0 0 0.125em rgba(32,156,238,0.25)}.select.is-success:not(:hover)::after{border-color:#22c35b}.select.is-success select{border-color:#22c35b}.select.is-success select:hover,.select.is-success select.is-hovered{border-color:#1ead51}.select.is-success select:focus,.select.is-success select.is-focused,.select.is-success select:active,.select.is-success select.is-active{box-shadow:0 0 0 0.125em rgba(34,195,91,0.25)}.select.is-warning:not(:hover)::after{border-color:#ffdd57}.select.is-warning select{border-color:#ffdd57}.select.is-warning select:hover,.select.is-warning select.is-hovered{border-color:#ffd83e}.select.is-warning select:focus,.select.is-warning select.is-focused,.select.is-warning select:active,.select.is-warning select.is-active{box-shadow:0 0 0 0.125em rgba(255,221,87,0.25)}.select.is-danger:not(:hover)::after{border-color:#da0b00}.select.is-danger select{border-color:#da0b00}.select.is-danger select:hover,.select.is-danger select.is-hovered{border-color:#c10a00}.select.is-danger select:focus,.select.is-danger select.is-focused,.select.is-danger select:active,.select.is-danger select.is-active{box-shadow:0 0 0 0.125em rgba(218,11,0,0.25)}.select.is-small,#documenter .docs-sidebar form.docs-search>input.select{border-radius:2px;font-size:.75rem}.select.is-medium{font-size:1.25rem}.select.is-large{font-size:1.5rem}.select.is-disabled::after{border-color:#6b6b6b !important;opacity:0.5}.select.is-fullwidth{width:100%}.select.is-fullwidth select{width:100%}.select.is-loading::after{margin-top:0;position:absolute;right:.625em;top:0.625em;transform:none}.select.is-loading.is-small:after,#documenter .docs-sidebar form.docs-search>input.is-loading:after{font-size:.75rem}.select.is-loading.is-medium:after{font-size:1.25rem}.select.is-loading.is-large:after{font-size:1.5rem}.file{align-items:stretch;display:flex;justify-content:flex-start;position:relative}.file.is-white .file-cta{background-color:#fff;border-color:transparent;color:#0a0a0a}.file.is-white:hover .file-cta,.file.is-white.is-hovered .file-cta{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}.file.is-white:focus .file-cta,.file.is-white.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(255,255,255,0.25);color:#0a0a0a}.file.is-white:active .file-cta,.file.is-white.is-active .file-cta{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}.file.is-black .file-cta{background-color:#0a0a0a;border-color:transparent;color:#fff}.file.is-black:hover .file-cta,.file.is-black.is-hovered .file-cta{background-color:#040404;border-color:transparent;color:#fff}.file.is-black:focus .file-cta,.file.is-black.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(10,10,10,0.25);color:#fff}.file.is-black:active .file-cta,.file.is-black.is-active .file-cta{background-color:#000;border-color:transparent;color:#fff}.file.is-light .file-cta{background-color:#f5f5f5;border-color:transparent;color:rgba(0,0,0,0.7)}.file.is-light:hover .file-cta,.file.is-light.is-hovered .file-cta{background-color:#eee;border-color:transparent;color:rgba(0,0,0,0.7)}.file.is-light:focus .file-cta,.file.is-light.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(245,245,245,0.25);color:rgba(0,0,0,0.7)}.file.is-light:active .file-cta,.file.is-light.is-active .file-cta{background-color:#e8e8e8;border-color:transparent;color:rgba(0,0,0,0.7)}.file.is-dark .file-cta,.content kbd.file .file-cta{background-color:#363636;border-color:transparent;color:#fff}.file.is-dark:hover .file-cta,.content kbd.file:hover .file-cta,.file.is-dark.is-hovered .file-cta,.content kbd.file.is-hovered .file-cta{background-color:#2f2f2f;border-color:transparent;color:#fff}.file.is-dark:focus .file-cta,.content kbd.file:focus .file-cta,.file.is-dark.is-focused .file-cta,.content kbd.file.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(54,54,54,0.25);color:#fff}.file.is-dark:active .file-cta,.content kbd.file:active .file-cta,.file.is-dark.is-active .file-cta,.content kbd.file.is-active .file-cta{background-color:#292929;border-color:transparent;color:#fff}.file.is-primary .file-cta,.docstring>section>a.file.docs-sourcelink .file-cta{background-color:#4eb5de;border-color:transparent;color:#fff}.file.is-primary:hover .file-cta,.docstring>section>a.file.docs-sourcelink:hover .file-cta,.file.is-primary.is-hovered .file-cta,.docstring>section>a.file.is-hovered.docs-sourcelink .file-cta{background-color:#43b1dc;border-color:transparent;color:#fff}.file.is-primary:focus .file-cta,.docstring>section>a.file.docs-sourcelink:focus .file-cta,.file.is-primary.is-focused .file-cta,.docstring>section>a.file.is-focused.docs-sourcelink .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(78,181,222,0.25);color:#fff}.file.is-primary:active .file-cta,.docstring>section>a.file.docs-sourcelink:active .file-cta,.file.is-primary.is-active .file-cta,.docstring>section>a.file.is-active.docs-sourcelink .file-cta{background-color:#39acda;border-color:transparent;color:#fff}.file.is-link .file-cta{background-color:#2e63b8;border-color:transparent;color:#fff}.file.is-link:hover .file-cta,.file.is-link.is-hovered .file-cta{background-color:#2b5eae;border-color:transparent;color:#fff}.file.is-link:focus .file-cta,.file.is-link.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(46,99,184,0.25);color:#fff}.file.is-link:active .file-cta,.file.is-link.is-active .file-cta{background-color:#2958a4;border-color:transparent;color:#fff}.file.is-info .file-cta{background-color:#209cee;border-color:transparent;color:#fff}.file.is-info:hover .file-cta,.file.is-info.is-hovered .file-cta{background-color:#1497ed;border-color:transparent;color:#fff}.file.is-info:focus .file-cta,.file.is-info.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(32,156,238,0.25);color:#fff}.file.is-info:active .file-cta,.file.is-info.is-active .file-cta{background-color:#1190e3;border-color:transparent;color:#fff}.file.is-success .file-cta{background-color:#22c35b;border-color:transparent;color:#fff}.file.is-success:hover .file-cta,.file.is-success.is-hovered .file-cta{background-color:#20b856;border-color:transparent;color:#fff}.file.is-success:focus .file-cta,.file.is-success.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(34,195,91,0.25);color:#fff}.file.is-success:active .file-cta,.file.is-success.is-active .file-cta{background-color:#1ead51;border-color:transparent;color:#fff}.file.is-warning .file-cta{background-color:#ffdd57;border-color:transparent;color:rgba(0,0,0,0.7)}.file.is-warning:hover .file-cta,.file.is-warning.is-hovered .file-cta{background-color:#ffda4a;border-color:transparent;color:rgba(0,0,0,0.7)}.file.is-warning:focus .file-cta,.file.is-warning.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(255,221,87,0.25);color:rgba(0,0,0,0.7)}.file.is-warning:active .file-cta,.file.is-warning.is-active .file-cta{background-color:#ffd83e;border-color:transparent;color:rgba(0,0,0,0.7)}.file.is-danger .file-cta{background-color:#da0b00;border-color:transparent;color:#fff}.file.is-danger:hover .file-cta,.file.is-danger.is-hovered .file-cta{background-color:#cd0a00;border-color:transparent;color:#fff}.file.is-danger:focus .file-cta,.file.is-danger.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(218,11,0,0.25);color:#fff}.file.is-danger:active .file-cta,.file.is-danger.is-active .file-cta{background-color:#c10a00;border-color:transparent;color:#fff}.file.is-small,#documenter .docs-sidebar form.docs-search>input.file{font-size:.75rem}.file.is-normal{font-size:1rem}.file.is-medium{font-size:1.25rem}.file.is-medium .file-icon .fa{font-size:21px}.file.is-large{font-size:1.5rem}.file.is-large .file-icon .fa{font-size:28px}.file.has-name .file-cta{border-bottom-right-radius:0;border-top-right-radius:0}.file.has-name .file-name{border-bottom-left-radius:0;border-top-left-radius:0}.file.has-name.is-empty .file-cta{border-radius:4px}.file.has-name.is-empty .file-name{display:none}.file.is-boxed .file-label{flex-direction:column}.file.is-boxed .file-cta{flex-direction:column;height:auto;padding:1em 3em}.file.is-boxed .file-name{border-width:0 1px 1px}.file.is-boxed .file-icon{height:1.5em;width:1.5em}.file.is-boxed .file-icon .fa{font-size:21px}.file.is-boxed.is-small .file-icon .fa,#documenter .docs-sidebar form.docs-search>input.is-boxed .file-icon .fa{font-size:14px}.file.is-boxed.is-medium .file-icon .fa{font-size:28px}.file.is-boxed.is-large .file-icon .fa{font-size:35px}.file.is-boxed.has-name .file-cta{border-radius:4px 4px 0 0}.file.is-boxed.has-name .file-name{border-radius:0 0 4px 4px;border-width:0 1px 1px}.file.is-centered{justify-content:center}.file.is-fullwidth .file-label{width:100%}.file.is-fullwidth .file-name{flex-grow:1;max-width:none}.file.is-right{justify-content:flex-end}.file.is-right .file-cta{border-radius:0 4px 4px 0}.file.is-right .file-name{border-radius:4px 0 0 4px;border-width:1px 0 1px 1px;order:-1}.file-label{align-items:stretch;display:flex;cursor:pointer;justify-content:flex-start;overflow:hidden;position:relative}.file-label:hover .file-cta{background-color:#eee;color:#222}.file-label:hover .file-name{border-color:#d5d5d5}.file-label:active .file-cta{background-color:#e8e8e8;color:#222}.file-label:active .file-name{border-color:#cfcfcf}.file-input{height:100%;left:0;opacity:0;outline:none;position:absolute;top:0;width:100%}.file-cta,.file-name{border-color:#dbdbdb;border-radius:4px;font-size:1em;padding-left:1em;padding-right:1em;white-space:nowrap}.file-cta{background-color:#f5f5f5;color:#222}.file-name{border-color:#dbdbdb;border-style:solid;border-width:1px 1px 1px 0;display:block;max-width:16em;overflow:hidden;text-align:inherit;text-overflow:ellipsis}.file-icon{align-items:center;display:flex;height:1em;justify-content:center;margin-right:.5em;width:1em}.file-icon .fa{font-size:14px}.label{color:#222;display:block;font-size:1rem;font-weight:700}.label:not(:last-child){margin-bottom:0.5em}.label.is-small,#documenter .docs-sidebar form.docs-search>input.label{font-size:.75rem}.label.is-medium{font-size:1.25rem}.label.is-large{font-size:1.5rem}.help{display:block;font-size:.75rem;margin-top:0.25rem}.help.is-white{color:#fff}.help.is-black{color:#0a0a0a}.help.is-light{color:#f5f5f5}.help.is-dark,.content kbd.help{color:#363636}.help.is-primary,.docstring>section>a.help.docs-sourcelink{color:#4eb5de}.help.is-link{color:#2e63b8}.help.is-info{color:#209cee}.help.is-success{color:#22c35b}.help.is-warning{color:#ffdd57}.help.is-danger{color:#da0b00}.field:not(:last-child){margin-bottom:0.75rem}.field.has-addons{display:flex;justify-content:flex-start}.field.has-addons .control:not(:last-child){margin-right:-1px}.field.has-addons .control:not(:first-child):not(:last-child) .button,.field.has-addons .control:not(:first-child):not(:last-child) .input,.field.has-addons .control:not(:first-child):not(:last-child) #documenter .docs-sidebar form.docs-search>input,#documenter .docs-sidebar .field.has-addons .control:not(:first-child):not(:last-child) form.docs-search>input,.field.has-addons .control:not(:first-child):not(:last-child) .select select{border-radius:0}.field.has-addons .control:first-child:not(:only-child) .button,.field.has-addons .control:first-child:not(:only-child) .input,.field.has-addons .control:first-child:not(:only-child) #documenter .docs-sidebar form.docs-search>input,#documenter .docs-sidebar .field.has-addons .control:first-child:not(:only-child) form.docs-search>input,.field.has-addons .control:first-child:not(:only-child) .select select{border-bottom-right-radius:0;border-top-right-radius:0}.field.has-addons .control:last-child:not(:only-child) .button,.field.has-addons .control:last-child:not(:only-child) .input,.field.has-addons .control:last-child:not(:only-child) #documenter .docs-sidebar form.docs-search>input,#documenter .docs-sidebar .field.has-addons .control:last-child:not(:only-child) form.docs-search>input,.field.has-addons .control:last-child:not(:only-child) .select select{border-bottom-left-radius:0;border-top-left-radius:0}.field.has-addons .control .button:not([disabled]):hover,.field.has-addons .control .button.is-hovered:not([disabled]),.field.has-addons .control .input:not([disabled]):hover,.field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):hover,#documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):hover,.field.has-addons .control .input.is-hovered:not([disabled]),.field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-hovered:not([disabled]),#documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-hovered:not([disabled]),.field.has-addons .control .select select:not([disabled]):hover,.field.has-addons .control .select select.is-hovered:not([disabled]){z-index:2}.field.has-addons .control .button:not([disabled]):focus,.field.has-addons .control .button.is-focused:not([disabled]),.field.has-addons .control .button:not([disabled]):active,.field.has-addons .control .button.is-active:not([disabled]),.field.has-addons .control .input:not([disabled]):focus,.field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):focus,#documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):focus,.field.has-addons .control .input.is-focused:not([disabled]),.field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-focused:not([disabled]),#documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-focused:not([disabled]),.field.has-addons .control .input:not([disabled]):active,.field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):active,#documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):active,.field.has-addons .control .input.is-active:not([disabled]),.field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-active:not([disabled]),#documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-active:not([disabled]),.field.has-addons .control .select select:not([disabled]):focus,.field.has-addons .control .select select.is-focused:not([disabled]),.field.has-addons .control .select select:not([disabled]):active,.field.has-addons .control .select select.is-active:not([disabled]){z-index:3}.field.has-addons .control .button:not([disabled]):focus:hover,.field.has-addons .control .button.is-focused:not([disabled]):hover,.field.has-addons .control .button:not([disabled]):active:hover,.field.has-addons .control .button.is-active:not([disabled]):hover,.field.has-addons .control .input:not([disabled]):focus:hover,.field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):focus:hover,#documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):focus:hover,.field.has-addons .control .input.is-focused:not([disabled]):hover,.field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-focused:not([disabled]):hover,#documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-focused:not([disabled]):hover,.field.has-addons .control .input:not([disabled]):active:hover,.field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):active:hover,#documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):active:hover,.field.has-addons .control .input.is-active:not([disabled]):hover,.field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-active:not([disabled]):hover,#documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-active:not([disabled]):hover,.field.has-addons .control .select select:not([disabled]):focus:hover,.field.has-addons .control .select select.is-focused:not([disabled]):hover,.field.has-addons .control .select select:not([disabled]):active:hover,.field.has-addons .control .select select.is-active:not([disabled]):hover{z-index:4}.field.has-addons .control.is-expanded{flex-grow:1;flex-shrink:1}.field.has-addons.has-addons-centered{justify-content:center}.field.has-addons.has-addons-right{justify-content:flex-end}.field.has-addons.has-addons-fullwidth .control{flex-grow:1;flex-shrink:0}.field.is-grouped{display:flex;justify-content:flex-start}.field.is-grouped>.control{flex-shrink:0}.field.is-grouped>.control:not(:last-child){margin-bottom:0;margin-right:.75rem}.field.is-grouped>.control.is-expanded{flex-grow:1;flex-shrink:1}.field.is-grouped.is-grouped-centered{justify-content:center}.field.is-grouped.is-grouped-right{justify-content:flex-end}.field.is-grouped.is-grouped-multiline{flex-wrap:wrap}.field.is-grouped.is-grouped-multiline>.control:last-child,.field.is-grouped.is-grouped-multiline>.control:not(:last-child){margin-bottom:0.75rem}.field.is-grouped.is-grouped-multiline:last-child{margin-bottom:-0.75rem}.field.is-grouped.is-grouped-multiline:not(:last-child){margin-bottom:0}@media screen and (min-width: 769px),print{.field.is-horizontal{display:flex}}.field-label .label{font-size:inherit}@media screen and (max-width: 768px){.field-label{margin-bottom:0.5rem}}@media screen and (min-width: 769px),print{.field-label{flex-basis:0;flex-grow:1;flex-shrink:0;margin-right:1.5rem;text-align:right}.field-label.is-small,#documenter .docs-sidebar form.docs-search>input.field-label{font-size:.75rem;padding-top:0.375em}.field-label.is-normal{padding-top:0.375em}.field-label.is-medium{font-size:1.25rem;padding-top:0.375em}.field-label.is-large{font-size:1.5rem;padding-top:0.375em}}.field-body .field .field{margin-bottom:0}@media screen and (min-width: 769px),print{.field-body{display:flex;flex-basis:0;flex-grow:5;flex-shrink:1}.field-body .field{margin-bottom:0}.field-body>.field{flex-shrink:1}.field-body>.field:not(.is-narrow){flex-grow:1}.field-body>.field:not(:last-child){margin-right:.75rem}}.control{box-sizing:border-box;clear:both;font-size:1rem;position:relative;text-align:inherit}.control.has-icons-left .input:focus~.icon,.control.has-icons-left #documenter .docs-sidebar form.docs-search>input:focus~.icon,#documenter .docs-sidebar .control.has-icons-left form.docs-search>input:focus~.icon,.control.has-icons-left .select:focus~.icon,.control.has-icons-right .input:focus~.icon,.control.has-icons-right #documenter .docs-sidebar form.docs-search>input:focus~.icon,#documenter .docs-sidebar .control.has-icons-right form.docs-search>input:focus~.icon,.control.has-icons-right .select:focus~.icon{color:#222}.control.has-icons-left .input.is-small~.icon,.control.has-icons-left #documenter .docs-sidebar form.docs-search>input~.icon,#documenter .docs-sidebar .control.has-icons-left form.docs-search>input~.icon,.control.has-icons-left .select.is-small~.icon,.control.has-icons-right .input.is-small~.icon,.control.has-icons-right #documenter .docs-sidebar form.docs-search>input~.icon,#documenter .docs-sidebar .control.has-icons-right form.docs-search>input~.icon,.control.has-icons-right .select.is-small~.icon{font-size:.75rem}.control.has-icons-left .input.is-medium~.icon,.control.has-icons-left #documenter .docs-sidebar form.docs-search>input.is-medium~.icon,#documenter .docs-sidebar .control.has-icons-left form.docs-search>input.is-medium~.icon,.control.has-icons-left .select.is-medium~.icon,.control.has-icons-right .input.is-medium~.icon,.control.has-icons-right #documenter .docs-sidebar form.docs-search>input.is-medium~.icon,#documenter .docs-sidebar .control.has-icons-right form.docs-search>input.is-medium~.icon,.control.has-icons-right .select.is-medium~.icon{font-size:1.25rem}.control.has-icons-left .input.is-large~.icon,.control.has-icons-left #documenter .docs-sidebar form.docs-search>input.is-large~.icon,#documenter .docs-sidebar .control.has-icons-left form.docs-search>input.is-large~.icon,.control.has-icons-left .select.is-large~.icon,.control.has-icons-right .input.is-large~.icon,.control.has-icons-right #documenter .docs-sidebar form.docs-search>input.is-large~.icon,#documenter .docs-sidebar .control.has-icons-right form.docs-search>input.is-large~.icon,.control.has-icons-right .select.is-large~.icon{font-size:1.5rem}.control.has-icons-left .icon,.control.has-icons-right .icon{color:#dbdbdb;height:2.5em;pointer-events:none;position:absolute;top:0;width:2.5em;z-index:4}.control.has-icons-left .input,.control.has-icons-left #documenter .docs-sidebar form.docs-search>input,#documenter .docs-sidebar .control.has-icons-left form.docs-search>input,.control.has-icons-left .select select{padding-left:2.5em}.control.has-icons-left .icon.is-left{left:0}.control.has-icons-right .input,.control.has-icons-right #documenter .docs-sidebar form.docs-search>input,#documenter .docs-sidebar .control.has-icons-right form.docs-search>input,.control.has-icons-right .select select{padding-right:2.5em}.control.has-icons-right .icon.is-right{right:0}.control.is-loading::after{position:absolute !important;right:.625em;top:0.625em;z-index:4}.control.is-loading.is-small:after,#documenter .docs-sidebar form.docs-search>input.is-loading:after{font-size:.75rem}.control.is-loading.is-medium:after{font-size:1.25rem}.control.is-loading.is-large:after{font-size:1.5rem}.breadcrumb{font-size:1rem;white-space:nowrap}.breadcrumb a{align-items:center;color:#2e63b8;display:flex;justify-content:center;padding:0 .75em}.breadcrumb a:hover{color:#363636}.breadcrumb li{align-items:center;display:flex}.breadcrumb li:first-child a{padding-left:0}.breadcrumb li.is-active a{color:#222;cursor:default;pointer-events:none}.breadcrumb li+li::before{color:#b5b5b5;content:"\0002f"}.breadcrumb ul,.breadcrumb ol{align-items:flex-start;display:flex;flex-wrap:wrap;justify-content:flex-start}.breadcrumb .icon:first-child{margin-right:.5em}.breadcrumb .icon:last-child{margin-left:.5em}.breadcrumb.is-centered ol,.breadcrumb.is-centered ul{justify-content:center}.breadcrumb.is-right ol,.breadcrumb.is-right ul{justify-content:flex-end}.breadcrumb.is-small,#documenter .docs-sidebar form.docs-search>input.breadcrumb{font-size:.75rem}.breadcrumb.is-medium{font-size:1.25rem}.breadcrumb.is-large{font-size:1.5rem}.breadcrumb.has-arrow-separator li+li::before{content:"\02192"}.breadcrumb.has-bullet-separator li+li::before{content:"\02022"}.breadcrumb.has-dot-separator li+li::before{content:"\000b7"}.breadcrumb.has-succeeds-separator li+li::before{content:"\0227B"}.card{background-color:#fff;border-radius:.25rem;box-shadow:#bbb;color:#222;max-width:100%;position:relative}.card-footer:first-child,.card-content:first-child,.card-header:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.card-footer:last-child,.card-content:last-child,.card-header:last-child{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}.card-header{background-color:rgba(0,0,0,0);align-items:stretch;box-shadow:0 0.125em 0.25em rgba(10,10,10,0.1);display:flex}.card-header-title{align-items:center;color:#222;display:flex;flex-grow:1;font-weight:700;padding:0.75rem 1rem}.card-header-title.is-centered{justify-content:center}.card-header-icon{-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;color:currentColor;font-family:inherit;font-size:1em;margin:0;padding:0;align-items:center;cursor:pointer;display:flex;justify-content:center;padding:0.75rem 1rem}.card-image{display:block;position:relative}.card-image:first-child img{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.card-image:last-child img{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}.card-content{background-color:rgba(0,0,0,0);padding:1.5rem}.card-footer{background-color:rgba(0,0,0,0);border-top:1px solid #ededed;align-items:stretch;display:flex}.card-footer-item{align-items:center;display:flex;flex-basis:0;flex-grow:1;flex-shrink:0;justify-content:center;padding:.75rem}.card-footer-item:not(:last-child){border-right:1px solid #ededed}.card .media:not(:last-child){margin-bottom:1.5rem}.dropdown{display:inline-flex;position:relative;vertical-align:top}.dropdown.is-active .dropdown-menu,.dropdown.is-hoverable:hover .dropdown-menu{display:block}.dropdown.is-right .dropdown-menu{left:auto;right:0}.dropdown.is-up .dropdown-menu{bottom:100%;padding-bottom:4px;padding-top:initial;top:auto}.dropdown-menu{display:none;left:0;min-width:12rem;padding-top:4px;position:absolute;top:100%;z-index:20}.dropdown-content{background-color:#fff;border-radius:4px;box-shadow:#bbb;padding-bottom:.5rem;padding-top:.5rem}.dropdown-item{color:#222;display:block;font-size:0.875rem;line-height:1.5;padding:0.375rem 1rem;position:relative}a.dropdown-item,button.dropdown-item{padding-right:3rem;text-align:inherit;white-space:nowrap;width:100%}a.dropdown-item:hover,button.dropdown-item:hover{background-color:#f5f5f5;color:#0a0a0a}a.dropdown-item.is-active,button.dropdown-item.is-active{background-color:#2e63b8;color:#fff}.dropdown-divider{background-color:#ededed;border:none;display:block;height:1px;margin:0.5rem 0}.level{align-items:center;justify-content:space-between}.level code{border-radius:4px}.level img{display:inline-block;vertical-align:top}.level.is-mobile{display:flex}.level.is-mobile .level-left,.level.is-mobile .level-right{display:flex}.level.is-mobile .level-left+.level-right{margin-top:0}.level.is-mobile .level-item:not(:last-child){margin-bottom:0;margin-right:.75rem}.level.is-mobile .level-item:not(.is-narrow){flex-grow:1}@media screen and (min-width: 769px),print{.level{display:flex}.level>.level-item:not(.is-narrow){flex-grow:1}}.level-item{align-items:center;display:flex;flex-basis:auto;flex-grow:0;flex-shrink:0;justify-content:center}.level-item .title,.level-item .subtitle{margin-bottom:0}@media screen and (max-width: 768px){.level-item:not(:last-child){margin-bottom:.75rem}}.level-left,.level-right{flex-basis:auto;flex-grow:0;flex-shrink:0}.level-left .level-item.is-flexible,.level-right .level-item.is-flexible{flex-grow:1}@media screen and (min-width: 769px),print{.level-left .level-item:not(:last-child),.level-right .level-item:not(:last-child){margin-right:.75rem}}.level-left{align-items:center;justify-content:flex-start}@media screen and (max-width: 768px){.level-left+.level-right{margin-top:1.5rem}}@media screen and (min-width: 769px),print{.level-left{display:flex}}.level-right{align-items:center;justify-content:flex-end}@media screen and (min-width: 769px),print{.level-right{display:flex}}.media{align-items:flex-start;display:flex;text-align:inherit}.media .content:not(:last-child){margin-bottom:.75rem}.media .media{border-top:1px solid rgba(219,219,219,0.5);display:flex;padding-top:.75rem}.media .media .content:not(:last-child),.media .media .control:not(:last-child){margin-bottom:.5rem}.media .media .media{padding-top:.5rem}.media .media .media+.media{margin-top:.5rem}.media+.media{border-top:1px solid rgba(219,219,219,0.5);margin-top:1rem;padding-top:1rem}.media.is-large+.media{margin-top:1.5rem;padding-top:1.5rem}.media-left,.media-right{flex-basis:auto;flex-grow:0;flex-shrink:0}.media-left{margin-right:1rem}.media-right{margin-left:1rem}.media-content{flex-basis:auto;flex-grow:1;flex-shrink:1;text-align:inherit}@media screen and (max-width: 768px){.media-content{overflow-x:auto}}.menu{font-size:1rem}.menu.is-small,#documenter .docs-sidebar form.docs-search>input.menu{font-size:.75rem}.menu.is-medium{font-size:1.25rem}.menu.is-large{font-size:1.5rem}.menu-list{line-height:1.25}.menu-list a{border-radius:2px;color:#222;display:block;padding:0.5em 0.75em}.menu-list a:hover{background-color:#f5f5f5;color:#222}.menu-list a.is-active{background-color:#2e63b8;color:#fff}.menu-list li ul{border-left:1px solid #dbdbdb;margin:.75em;padding-left:.75em}.menu-label{color:#6b6b6b;font-size:.75em;letter-spacing:.1em;text-transform:uppercase}.menu-label:not(:first-child){margin-top:1em}.menu-label:not(:last-child){margin-bottom:1em}.message{background-color:#f5f5f5;border-radius:4px;font-size:1rem}.message strong{color:currentColor}.message a:not(.button):not(.tag):not(.dropdown-item){color:currentColor;text-decoration:underline}.message.is-small,#documenter .docs-sidebar form.docs-search>input.message{font-size:.75rem}.message.is-medium{font-size:1.25rem}.message.is-large{font-size:1.5rem}.message.is-white{background-color:#fff}.message.is-white .message-header{background-color:#fff;color:#0a0a0a}.message.is-white .message-body{border-color:#fff}.message.is-black{background-color:#fafafa}.message.is-black .message-header{background-color:#0a0a0a;color:#fff}.message.is-black .message-body{border-color:#0a0a0a}.message.is-light{background-color:#fafafa}.message.is-light .message-header{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}.message.is-light .message-body{border-color:#f5f5f5}.message.is-dark,.content kbd.message{background-color:#fafafa}.message.is-dark .message-header,.content kbd.message .message-header{background-color:#363636;color:#fff}.message.is-dark .message-body,.content kbd.message .message-body{border-color:#363636}.message.is-primary,.docstring>section>a.message.docs-sourcelink{background-color:#eef8fc}.message.is-primary .message-header,.docstring>section>a.message.docs-sourcelink .message-header{background-color:#4eb5de;color:#fff}.message.is-primary .message-body,.docstring>section>a.message.docs-sourcelink .message-body{border-color:#4eb5de;color:#1a6d8e}.message.is-link{background-color:#eff3fb}.message.is-link .message-header{background-color:#2e63b8;color:#fff}.message.is-link .message-body{border-color:#2e63b8;color:#3169c4}.message.is-info{background-color:#ecf7fe}.message.is-info .message-header{background-color:#209cee;color:#fff}.message.is-info .message-body{border-color:#209cee;color:#0e72b4}.message.is-success{background-color:#eefcf3}.message.is-success .message-header{background-color:#22c35b;color:#fff}.message.is-success .message-body{border-color:#22c35b;color:#198f43}.message.is-warning{background-color:#fffbeb}.message.is-warning .message-header{background-color:#ffdd57;color:rgba(0,0,0,0.7)}.message.is-warning .message-body{border-color:#ffdd57;color:#947600}.message.is-danger{background-color:#ffeceb}.message.is-danger .message-header{background-color:#da0b00;color:#fff}.message.is-danger .message-body{border-color:#da0b00;color:#f50c00}.message-header{align-items:center;background-color:#222;border-radius:4px 4px 0 0;color:#fff;display:flex;font-weight:700;justify-content:space-between;line-height:1.25;padding:0.75em 1em;position:relative}.message-header .delete{flex-grow:0;flex-shrink:0;margin-left:.75em}.message-header+.message-body{border-width:0;border-top-left-radius:0;border-top-right-radius:0}.message-body{border-color:#dbdbdb;border-radius:4px;border-style:solid;border-width:0 0 0 4px;color:#222;padding:1.25em 1.5em}.message-body code,.message-body pre{background-color:#fff}.message-body pre code{background-color:rgba(0,0,0,0)}.modal{align-items:center;display:none;flex-direction:column;justify-content:center;overflow:hidden;position:fixed;z-index:40}.modal.is-active{display:flex}.modal-background{background-color:rgba(10,10,10,0.86)}.modal-content,.modal-card{margin:0 20px;max-height:calc(100vh - 160px);overflow:auto;position:relative;width:100%}@media screen and (min-width: 769px){.modal-content,.modal-card{margin:0 auto;max-height:calc(100vh - 40px);width:640px}}.modal-close{background:none;height:40px;position:fixed;right:20px;top:20px;width:40px}.modal-card{display:flex;flex-direction:column;max-height:calc(100vh - 40px);overflow:hidden;-ms-overflow-y:visible}.modal-card-head,.modal-card-foot{align-items:center;background-color:#f5f5f5;display:flex;flex-shrink:0;justify-content:flex-start;padding:20px;position:relative}.modal-card-head{border-bottom:1px solid #dbdbdb;border-top-left-radius:6px;border-top-right-radius:6px}.modal-card-title{color:#222;flex-grow:1;flex-shrink:0;font-size:1.5rem;line-height:1}.modal-card-foot{border-bottom-left-radius:6px;border-bottom-right-radius:6px;border-top:1px solid #dbdbdb}.modal-card-foot .button:not(:last-child){margin-right:.5em}.modal-card-body{-webkit-overflow-scrolling:touch;background-color:#fff;flex-grow:1;flex-shrink:1;overflow:auto;padding:20px}.navbar{background-color:#fff;min-height:3.25rem;position:relative;z-index:30}.navbar.is-white{background-color:#fff;color:#0a0a0a}.navbar.is-white .navbar-brand>.navbar-item,.navbar.is-white .navbar-brand .navbar-link{color:#0a0a0a}.navbar.is-white .navbar-brand>a.navbar-item:focus,.navbar.is-white .navbar-brand>a.navbar-item:hover,.navbar.is-white .navbar-brand>a.navbar-item.is-active,.navbar.is-white .navbar-brand .navbar-link:focus,.navbar.is-white .navbar-brand .navbar-link:hover,.navbar.is-white .navbar-brand .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}.navbar.is-white .navbar-brand .navbar-link::after{border-color:#0a0a0a}.navbar.is-white .navbar-burger{color:#0a0a0a}@media screen and (min-width: 1056px){.navbar.is-white .navbar-start>.navbar-item,.navbar.is-white .navbar-start .navbar-link,.navbar.is-white .navbar-end>.navbar-item,.navbar.is-white .navbar-end .navbar-link{color:#0a0a0a}.navbar.is-white .navbar-start>a.navbar-item:focus,.navbar.is-white .navbar-start>a.navbar-item:hover,.navbar.is-white .navbar-start>a.navbar-item.is-active,.navbar.is-white .navbar-start .navbar-link:focus,.navbar.is-white .navbar-start .navbar-link:hover,.navbar.is-white .navbar-start .navbar-link.is-active,.navbar.is-white .navbar-end>a.navbar-item:focus,.navbar.is-white .navbar-end>a.navbar-item:hover,.navbar.is-white .navbar-end>a.navbar-item.is-active,.navbar.is-white .navbar-end .navbar-link:focus,.navbar.is-white .navbar-end .navbar-link:hover,.navbar.is-white .navbar-end .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}.navbar.is-white .navbar-start .navbar-link::after,.navbar.is-white .navbar-end .navbar-link::after{border-color:#0a0a0a}.navbar.is-white .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-white .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-white .navbar-item.has-dropdown.is-active .navbar-link{background-color:#f2f2f2;color:#0a0a0a}.navbar.is-white .navbar-dropdown a.navbar-item.is-active{background-color:#fff;color:#0a0a0a}}.navbar.is-black{background-color:#0a0a0a;color:#fff}.navbar.is-black .navbar-brand>.navbar-item,.navbar.is-black .navbar-brand .navbar-link{color:#fff}.navbar.is-black .navbar-brand>a.navbar-item:focus,.navbar.is-black .navbar-brand>a.navbar-item:hover,.navbar.is-black .navbar-brand>a.navbar-item.is-active,.navbar.is-black .navbar-brand .navbar-link:focus,.navbar.is-black .navbar-brand .navbar-link:hover,.navbar.is-black .navbar-brand .navbar-link.is-active{background-color:#000;color:#fff}.navbar.is-black .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-black .navbar-burger{color:#fff}@media screen and (min-width: 1056px){.navbar.is-black .navbar-start>.navbar-item,.navbar.is-black .navbar-start .navbar-link,.navbar.is-black .navbar-end>.navbar-item,.navbar.is-black .navbar-end .navbar-link{color:#fff}.navbar.is-black .navbar-start>a.navbar-item:focus,.navbar.is-black .navbar-start>a.navbar-item:hover,.navbar.is-black .navbar-start>a.navbar-item.is-active,.navbar.is-black .navbar-start .navbar-link:focus,.navbar.is-black .navbar-start .navbar-link:hover,.navbar.is-black .navbar-start .navbar-link.is-active,.navbar.is-black .navbar-end>a.navbar-item:focus,.navbar.is-black .navbar-end>a.navbar-item:hover,.navbar.is-black .navbar-end>a.navbar-item.is-active,.navbar.is-black .navbar-end .navbar-link:focus,.navbar.is-black .navbar-end .navbar-link:hover,.navbar.is-black .navbar-end .navbar-link.is-active{background-color:#000;color:#fff}.navbar.is-black .navbar-start .navbar-link::after,.navbar.is-black .navbar-end .navbar-link::after{border-color:#fff}.navbar.is-black .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-black .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-black .navbar-item.has-dropdown.is-active .navbar-link{background-color:#000;color:#fff}.navbar.is-black .navbar-dropdown a.navbar-item.is-active{background-color:#0a0a0a;color:#fff}}.navbar.is-light{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}.navbar.is-light .navbar-brand>.navbar-item,.navbar.is-light .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}.navbar.is-light .navbar-brand>a.navbar-item:focus,.navbar.is-light .navbar-brand>a.navbar-item:hover,.navbar.is-light .navbar-brand>a.navbar-item.is-active,.navbar.is-light .navbar-brand .navbar-link:focus,.navbar.is-light .navbar-brand .navbar-link:hover,.navbar.is-light .navbar-brand .navbar-link.is-active{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}.navbar.is-light .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}.navbar.is-light .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1056px){.navbar.is-light .navbar-start>.navbar-item,.navbar.is-light .navbar-start .navbar-link,.navbar.is-light .navbar-end>.navbar-item,.navbar.is-light .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}.navbar.is-light .navbar-start>a.navbar-item:focus,.navbar.is-light .navbar-start>a.navbar-item:hover,.navbar.is-light .navbar-start>a.navbar-item.is-active,.navbar.is-light .navbar-start .navbar-link:focus,.navbar.is-light .navbar-start .navbar-link:hover,.navbar.is-light .navbar-start .navbar-link.is-active,.navbar.is-light .navbar-end>a.navbar-item:focus,.navbar.is-light .navbar-end>a.navbar-item:hover,.navbar.is-light .navbar-end>a.navbar-item.is-active,.navbar.is-light .navbar-end .navbar-link:focus,.navbar.is-light .navbar-end .navbar-link:hover,.navbar.is-light .navbar-end .navbar-link.is-active{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}.navbar.is-light .navbar-start .navbar-link::after,.navbar.is-light .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}.navbar.is-light .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-light .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-light .navbar-item.has-dropdown.is-active .navbar-link{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}.navbar.is-light .navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}}.navbar.is-dark,.content kbd.navbar{background-color:#363636;color:#fff}.navbar.is-dark .navbar-brand>.navbar-item,.content kbd.navbar .navbar-brand>.navbar-item,.navbar.is-dark .navbar-brand .navbar-link,.content kbd.navbar .navbar-brand .navbar-link{color:#fff}.navbar.is-dark .navbar-brand>a.navbar-item:focus,.content kbd.navbar .navbar-brand>a.navbar-item:focus,.navbar.is-dark .navbar-brand>a.navbar-item:hover,.content kbd.navbar .navbar-brand>a.navbar-item:hover,.navbar.is-dark .navbar-brand>a.navbar-item.is-active,.content kbd.navbar .navbar-brand>a.navbar-item.is-active,.navbar.is-dark .navbar-brand .navbar-link:focus,.content kbd.navbar .navbar-brand .navbar-link:focus,.navbar.is-dark .navbar-brand .navbar-link:hover,.content kbd.navbar .navbar-brand .navbar-link:hover,.navbar.is-dark .navbar-brand .navbar-link.is-active,.content kbd.navbar .navbar-brand .navbar-link.is-active{background-color:#292929;color:#fff}.navbar.is-dark .navbar-brand .navbar-link::after,.content kbd.navbar .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-dark .navbar-burger,.content kbd.navbar .navbar-burger{color:#fff}@media screen and (min-width: 1056px){.navbar.is-dark .navbar-start>.navbar-item,.content kbd.navbar .navbar-start>.navbar-item,.navbar.is-dark .navbar-start .navbar-link,.content kbd.navbar .navbar-start .navbar-link,.navbar.is-dark .navbar-end>.navbar-item,.content kbd.navbar .navbar-end>.navbar-item,.navbar.is-dark .navbar-end .navbar-link,.content kbd.navbar .navbar-end .navbar-link{color:#fff}.navbar.is-dark .navbar-start>a.navbar-item:focus,.content kbd.navbar .navbar-start>a.navbar-item:focus,.navbar.is-dark .navbar-start>a.navbar-item:hover,.content kbd.navbar .navbar-start>a.navbar-item:hover,.navbar.is-dark .navbar-start>a.navbar-item.is-active,.content kbd.navbar .navbar-start>a.navbar-item.is-active,.navbar.is-dark .navbar-start .navbar-link:focus,.content kbd.navbar .navbar-start .navbar-link:focus,.navbar.is-dark .navbar-start .navbar-link:hover,.content kbd.navbar .navbar-start .navbar-link:hover,.navbar.is-dark .navbar-start .navbar-link.is-active,.content kbd.navbar .navbar-start .navbar-link.is-active,.navbar.is-dark .navbar-end>a.navbar-item:focus,.content kbd.navbar .navbar-end>a.navbar-item:focus,.navbar.is-dark .navbar-end>a.navbar-item:hover,.content kbd.navbar .navbar-end>a.navbar-item:hover,.navbar.is-dark .navbar-end>a.navbar-item.is-active,.content kbd.navbar .navbar-end>a.navbar-item.is-active,.navbar.is-dark .navbar-end .navbar-link:focus,.content kbd.navbar .navbar-end .navbar-link:focus,.navbar.is-dark .navbar-end .navbar-link:hover,.content kbd.navbar .navbar-end .navbar-link:hover,.navbar.is-dark .navbar-end .navbar-link.is-active,.content kbd.navbar .navbar-end .navbar-link.is-active{background-color:#292929;color:#fff}.navbar.is-dark .navbar-start .navbar-link::after,.content kbd.navbar .navbar-start .navbar-link::after,.navbar.is-dark .navbar-end .navbar-link::after,.content kbd.navbar .navbar-end .navbar-link::after{border-color:#fff}.navbar.is-dark .navbar-item.has-dropdown:focus .navbar-link,.content kbd.navbar .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-dark .navbar-item.has-dropdown:hover .navbar-link,.content kbd.navbar .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-dark .navbar-item.has-dropdown.is-active .navbar-link,.content kbd.navbar .navbar-item.has-dropdown.is-active .navbar-link{background-color:#292929;color:#fff}.navbar.is-dark .navbar-dropdown a.navbar-item.is-active,.content kbd.navbar .navbar-dropdown a.navbar-item.is-active{background-color:#363636;color:#fff}}.navbar.is-primary,.docstring>section>a.navbar.docs-sourcelink{background-color:#4eb5de;color:#fff}.navbar.is-primary .navbar-brand>.navbar-item,.docstring>section>a.navbar.docs-sourcelink .navbar-brand>.navbar-item,.navbar.is-primary .navbar-brand .navbar-link,.docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link{color:#fff}.navbar.is-primary .navbar-brand>a.navbar-item:focus,.docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item:focus,.navbar.is-primary .navbar-brand>a.navbar-item:hover,.docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item:hover,.navbar.is-primary .navbar-brand>a.navbar-item.is-active,.docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item.is-active,.navbar.is-primary .navbar-brand .navbar-link:focus,.docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link:focus,.navbar.is-primary .navbar-brand .navbar-link:hover,.docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link:hover,.navbar.is-primary .navbar-brand .navbar-link.is-active,.docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link.is-active{background-color:#39acda;color:#fff}.navbar.is-primary .navbar-brand .navbar-link::after,.docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-primary .navbar-burger,.docstring>section>a.navbar.docs-sourcelink .navbar-burger{color:#fff}@media screen and (min-width: 1056px){.navbar.is-primary .navbar-start>.navbar-item,.docstring>section>a.navbar.docs-sourcelink .navbar-start>.navbar-item,.navbar.is-primary .navbar-start .navbar-link,.docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link,.navbar.is-primary .navbar-end>.navbar-item,.docstring>section>a.navbar.docs-sourcelink .navbar-end>.navbar-item,.navbar.is-primary .navbar-end .navbar-link,.docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link{color:#fff}.navbar.is-primary .navbar-start>a.navbar-item:focus,.docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item:focus,.navbar.is-primary .navbar-start>a.navbar-item:hover,.docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item:hover,.navbar.is-primary .navbar-start>a.navbar-item.is-active,.docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item.is-active,.navbar.is-primary .navbar-start .navbar-link:focus,.docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link:focus,.navbar.is-primary .navbar-start .navbar-link:hover,.docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link:hover,.navbar.is-primary .navbar-start .navbar-link.is-active,.docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link.is-active,.navbar.is-primary .navbar-end>a.navbar-item:focus,.docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item:focus,.navbar.is-primary .navbar-end>a.navbar-item:hover,.docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item:hover,.navbar.is-primary .navbar-end>a.navbar-item.is-active,.docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item.is-active,.navbar.is-primary .navbar-end .navbar-link:focus,.docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link:focus,.navbar.is-primary .navbar-end .navbar-link:hover,.docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link:hover,.navbar.is-primary .navbar-end .navbar-link.is-active,.docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link.is-active{background-color:#39acda;color:#fff}.navbar.is-primary .navbar-start .navbar-link::after,.docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link::after,.navbar.is-primary .navbar-end .navbar-link::after,.docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link::after{border-color:#fff}.navbar.is-primary .navbar-item.has-dropdown:focus .navbar-link,.docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-primary .navbar-item.has-dropdown:hover .navbar-link,.docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-primary .navbar-item.has-dropdown.is-active .navbar-link,.docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown.is-active .navbar-link{background-color:#39acda;color:#fff}.navbar.is-primary .navbar-dropdown a.navbar-item.is-active,.docstring>section>a.navbar.docs-sourcelink .navbar-dropdown a.navbar-item.is-active{background-color:#4eb5de;color:#fff}}.navbar.is-link{background-color:#2e63b8;color:#fff}.navbar.is-link .navbar-brand>.navbar-item,.navbar.is-link .navbar-brand .navbar-link{color:#fff}.navbar.is-link .navbar-brand>a.navbar-item:focus,.navbar.is-link .navbar-brand>a.navbar-item:hover,.navbar.is-link .navbar-brand>a.navbar-item.is-active,.navbar.is-link .navbar-brand .navbar-link:focus,.navbar.is-link .navbar-brand .navbar-link:hover,.navbar.is-link .navbar-brand .navbar-link.is-active{background-color:#2958a4;color:#fff}.navbar.is-link .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-link .navbar-burger{color:#fff}@media screen and (min-width: 1056px){.navbar.is-link .navbar-start>.navbar-item,.navbar.is-link .navbar-start .navbar-link,.navbar.is-link .navbar-end>.navbar-item,.navbar.is-link .navbar-end .navbar-link{color:#fff}.navbar.is-link .navbar-start>a.navbar-item:focus,.navbar.is-link .navbar-start>a.navbar-item:hover,.navbar.is-link .navbar-start>a.navbar-item.is-active,.navbar.is-link .navbar-start .navbar-link:focus,.navbar.is-link .navbar-start .navbar-link:hover,.navbar.is-link .navbar-start .navbar-link.is-active,.navbar.is-link .navbar-end>a.navbar-item:focus,.navbar.is-link .navbar-end>a.navbar-item:hover,.navbar.is-link .navbar-end>a.navbar-item.is-active,.navbar.is-link .navbar-end .navbar-link:focus,.navbar.is-link .navbar-end .navbar-link:hover,.navbar.is-link .navbar-end .navbar-link.is-active{background-color:#2958a4;color:#fff}.navbar.is-link .navbar-start .navbar-link::after,.navbar.is-link .navbar-end .navbar-link::after{border-color:#fff}.navbar.is-link .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-link .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-link .navbar-item.has-dropdown.is-active .navbar-link{background-color:#2958a4;color:#fff}.navbar.is-link .navbar-dropdown a.navbar-item.is-active{background-color:#2e63b8;color:#fff}}.navbar.is-info{background-color:#209cee;color:#fff}.navbar.is-info .navbar-brand>.navbar-item,.navbar.is-info .navbar-brand .navbar-link{color:#fff}.navbar.is-info .navbar-brand>a.navbar-item:focus,.navbar.is-info .navbar-brand>a.navbar-item:hover,.navbar.is-info .navbar-brand>a.navbar-item.is-active,.navbar.is-info .navbar-brand .navbar-link:focus,.navbar.is-info .navbar-brand .navbar-link:hover,.navbar.is-info .navbar-brand .navbar-link.is-active{background-color:#1190e3;color:#fff}.navbar.is-info .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-info .navbar-burger{color:#fff}@media screen and (min-width: 1056px){.navbar.is-info .navbar-start>.navbar-item,.navbar.is-info .navbar-start .navbar-link,.navbar.is-info .navbar-end>.navbar-item,.navbar.is-info .navbar-end .navbar-link{color:#fff}.navbar.is-info .navbar-start>a.navbar-item:focus,.navbar.is-info .navbar-start>a.navbar-item:hover,.navbar.is-info .navbar-start>a.navbar-item.is-active,.navbar.is-info .navbar-start .navbar-link:focus,.navbar.is-info .navbar-start .navbar-link:hover,.navbar.is-info .navbar-start .navbar-link.is-active,.navbar.is-info .navbar-end>a.navbar-item:focus,.navbar.is-info .navbar-end>a.navbar-item:hover,.navbar.is-info .navbar-end>a.navbar-item.is-active,.navbar.is-info .navbar-end .navbar-link:focus,.navbar.is-info .navbar-end .navbar-link:hover,.navbar.is-info .navbar-end .navbar-link.is-active{background-color:#1190e3;color:#fff}.navbar.is-info .navbar-start .navbar-link::after,.navbar.is-info .navbar-end .navbar-link::after{border-color:#fff}.navbar.is-info .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-info .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-info .navbar-item.has-dropdown.is-active .navbar-link{background-color:#1190e3;color:#fff}.navbar.is-info .navbar-dropdown a.navbar-item.is-active{background-color:#209cee;color:#fff}}.navbar.is-success{background-color:#22c35b;color:#fff}.navbar.is-success .navbar-brand>.navbar-item,.navbar.is-success .navbar-brand .navbar-link{color:#fff}.navbar.is-success .navbar-brand>a.navbar-item:focus,.navbar.is-success .navbar-brand>a.navbar-item:hover,.navbar.is-success .navbar-brand>a.navbar-item.is-active,.navbar.is-success .navbar-brand .navbar-link:focus,.navbar.is-success .navbar-brand .navbar-link:hover,.navbar.is-success .navbar-brand .navbar-link.is-active{background-color:#1ead51;color:#fff}.navbar.is-success .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-success .navbar-burger{color:#fff}@media screen and (min-width: 1056px){.navbar.is-success .navbar-start>.navbar-item,.navbar.is-success .navbar-start .navbar-link,.navbar.is-success .navbar-end>.navbar-item,.navbar.is-success .navbar-end .navbar-link{color:#fff}.navbar.is-success .navbar-start>a.navbar-item:focus,.navbar.is-success .navbar-start>a.navbar-item:hover,.navbar.is-success .navbar-start>a.navbar-item.is-active,.navbar.is-success .navbar-start .navbar-link:focus,.navbar.is-success .navbar-start .navbar-link:hover,.navbar.is-success .navbar-start .navbar-link.is-active,.navbar.is-success .navbar-end>a.navbar-item:focus,.navbar.is-success .navbar-end>a.navbar-item:hover,.navbar.is-success .navbar-end>a.navbar-item.is-active,.navbar.is-success .navbar-end .navbar-link:focus,.navbar.is-success .navbar-end .navbar-link:hover,.navbar.is-success .navbar-end .navbar-link.is-active{background-color:#1ead51;color:#fff}.navbar.is-success .navbar-start .navbar-link::after,.navbar.is-success .navbar-end .navbar-link::after{border-color:#fff}.navbar.is-success .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-success .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-success .navbar-item.has-dropdown.is-active .navbar-link{background-color:#1ead51;color:#fff}.navbar.is-success .navbar-dropdown a.navbar-item.is-active{background-color:#22c35b;color:#fff}}.navbar.is-warning{background-color:#ffdd57;color:rgba(0,0,0,0.7)}.navbar.is-warning .navbar-brand>.navbar-item,.navbar.is-warning .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}.navbar.is-warning .navbar-brand>a.navbar-item:focus,.navbar.is-warning .navbar-brand>a.navbar-item:hover,.navbar.is-warning .navbar-brand>a.navbar-item.is-active,.navbar.is-warning .navbar-brand .navbar-link:focus,.navbar.is-warning .navbar-brand .navbar-link:hover,.navbar.is-warning .navbar-brand .navbar-link.is-active{background-color:#ffd83e;color:rgba(0,0,0,0.7)}.navbar.is-warning .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}.navbar.is-warning .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1056px){.navbar.is-warning .navbar-start>.navbar-item,.navbar.is-warning .navbar-start .navbar-link,.navbar.is-warning .navbar-end>.navbar-item,.navbar.is-warning .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}.navbar.is-warning .navbar-start>a.navbar-item:focus,.navbar.is-warning .navbar-start>a.navbar-item:hover,.navbar.is-warning .navbar-start>a.navbar-item.is-active,.navbar.is-warning .navbar-start .navbar-link:focus,.navbar.is-warning .navbar-start .navbar-link:hover,.navbar.is-warning .navbar-start .navbar-link.is-active,.navbar.is-warning .navbar-end>a.navbar-item:focus,.navbar.is-warning .navbar-end>a.navbar-item:hover,.navbar.is-warning .navbar-end>a.navbar-item.is-active,.navbar.is-warning .navbar-end .navbar-link:focus,.navbar.is-warning .navbar-end .navbar-link:hover,.navbar.is-warning .navbar-end .navbar-link.is-active{background-color:#ffd83e;color:rgba(0,0,0,0.7)}.navbar.is-warning .navbar-start .navbar-link::after,.navbar.is-warning .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}.navbar.is-warning .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-warning .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-warning .navbar-item.has-dropdown.is-active .navbar-link{background-color:#ffd83e;color:rgba(0,0,0,0.7)}.navbar.is-warning .navbar-dropdown a.navbar-item.is-active{background-color:#ffdd57;color:rgba(0,0,0,0.7)}}.navbar.is-danger{background-color:#da0b00;color:#fff}.navbar.is-danger .navbar-brand>.navbar-item,.navbar.is-danger .navbar-brand .navbar-link{color:#fff}.navbar.is-danger .navbar-brand>a.navbar-item:focus,.navbar.is-danger .navbar-brand>a.navbar-item:hover,.navbar.is-danger .navbar-brand>a.navbar-item.is-active,.navbar.is-danger .navbar-brand .navbar-link:focus,.navbar.is-danger .navbar-brand .navbar-link:hover,.navbar.is-danger .navbar-brand .navbar-link.is-active{background-color:#c10a00;color:#fff}.navbar.is-danger .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-danger .navbar-burger{color:#fff}@media screen and (min-width: 1056px){.navbar.is-danger .navbar-start>.navbar-item,.navbar.is-danger .navbar-start .navbar-link,.navbar.is-danger .navbar-end>.navbar-item,.navbar.is-danger .navbar-end .navbar-link{color:#fff}.navbar.is-danger .navbar-start>a.navbar-item:focus,.navbar.is-danger .navbar-start>a.navbar-item:hover,.navbar.is-danger .navbar-start>a.navbar-item.is-active,.navbar.is-danger .navbar-start .navbar-link:focus,.navbar.is-danger .navbar-start .navbar-link:hover,.navbar.is-danger .navbar-start .navbar-link.is-active,.navbar.is-danger .navbar-end>a.navbar-item:focus,.navbar.is-danger .navbar-end>a.navbar-item:hover,.navbar.is-danger .navbar-end>a.navbar-item.is-active,.navbar.is-danger .navbar-end .navbar-link:focus,.navbar.is-danger .navbar-end .navbar-link:hover,.navbar.is-danger .navbar-end .navbar-link.is-active{background-color:#c10a00;color:#fff}.navbar.is-danger .navbar-start .navbar-link::after,.navbar.is-danger .navbar-end .navbar-link::after{border-color:#fff}.navbar.is-danger .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-danger .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-danger .navbar-item.has-dropdown.is-active .navbar-link{background-color:#c10a00;color:#fff}.navbar.is-danger .navbar-dropdown a.navbar-item.is-active{background-color:#da0b00;color:#fff}}.navbar>.container{align-items:stretch;display:flex;min-height:3.25rem;width:100%}.navbar.has-shadow{box-shadow:0 2px 0 0 #f5f5f5}.navbar.is-fixed-bottom,.navbar.is-fixed-top{left:0;position:fixed;right:0;z-index:30}.navbar.is-fixed-bottom{bottom:0}.navbar.is-fixed-bottom.has-shadow{box-shadow:0 -2px 0 0 #f5f5f5}.navbar.is-fixed-top{top:0}html.has-navbar-fixed-top,body.has-navbar-fixed-top{padding-top:3.25rem}html.has-navbar-fixed-bottom,body.has-navbar-fixed-bottom{padding-bottom:3.25rem}.navbar-brand,.navbar-tabs{align-items:stretch;display:flex;flex-shrink:0;min-height:3.25rem}.navbar-brand a.navbar-item:focus,.navbar-brand a.navbar-item:hover{background-color:transparent}.navbar-tabs{-webkit-overflow-scrolling:touch;max-width:100vw;overflow-x:auto;overflow-y:hidden}.navbar-burger{color:#222;-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;cursor:pointer;display:block;height:3.25rem;position:relative;width:3.25rem;margin-left:auto}.navbar-burger span{background-color:currentColor;display:block;height:1px;left:calc(50% - 8px);position:absolute;transform-origin:center;transition-duration:86ms;transition-property:background-color, opacity, transform;transition-timing-function:ease-out;width:16px}.navbar-burger span:nth-child(1){top:calc(50% - 6px)}.navbar-burger span:nth-child(2){top:calc(50% - 1px)}.navbar-burger span:nth-child(3){top:calc(50% + 4px)}.navbar-burger:hover{background-color:rgba(0,0,0,0.05)}.navbar-burger.is-active span:nth-child(1){transform:translateY(5px) rotate(45deg)}.navbar-burger.is-active span:nth-child(2){opacity:0}.navbar-burger.is-active span:nth-child(3){transform:translateY(-5px) rotate(-45deg)}.navbar-menu{display:none}.navbar-item,.navbar-link{color:#222;display:block;line-height:1.5;padding:0.5rem 0.75rem;position:relative}.navbar-item .icon:only-child,.navbar-link .icon:only-child{margin-left:-0.25rem;margin-right:-0.25rem}a.navbar-item,.navbar-link{cursor:pointer}a.navbar-item:focus,a.navbar-item:focus-within,a.navbar-item:hover,a.navbar-item.is-active,.navbar-link:focus,.navbar-link:focus-within,.navbar-link:hover,.navbar-link.is-active{background-color:#fafafa;color:#2e63b8}.navbar-item{flex-grow:0;flex-shrink:0}.navbar-item img{max-height:1.75rem}.navbar-item.has-dropdown{padding:0}.navbar-item.is-expanded{flex-grow:1;flex-shrink:1}.navbar-item.is-tab{border-bottom:1px solid transparent;min-height:3.25rem;padding-bottom:calc(0.5rem - 1px)}.navbar-item.is-tab:focus,.navbar-item.is-tab:hover{background-color:rgba(0,0,0,0);border-bottom-color:#2e63b8}.navbar-item.is-tab.is-active{background-color:rgba(0,0,0,0);border-bottom-color:#2e63b8;border-bottom-style:solid;border-bottom-width:3px;color:#2e63b8;padding-bottom:calc(0.5rem - 3px)}.navbar-content{flex-grow:1;flex-shrink:1}.navbar-link:not(.is-arrowless){padding-right:2.5em}.navbar-link:not(.is-arrowless)::after{border-color:#2e63b8;margin-top:-0.375em;right:1.125em}.navbar-dropdown{font-size:0.875rem;padding-bottom:0.5rem;padding-top:0.5rem}.navbar-dropdown .navbar-item{padding-left:1.5rem;padding-right:1.5rem}.navbar-divider{background-color:#f5f5f5;border:none;display:none;height:2px;margin:0.5rem 0}@media screen and (max-width: 1055px){.navbar>.container{display:block}.navbar-brand .navbar-item,.navbar-tabs .navbar-item{align-items:center;display:flex}.navbar-link::after{display:none}.navbar-menu{background-color:#fff;box-shadow:0 8px 16px rgba(10,10,10,0.1);padding:0.5rem 0}.navbar-menu.is-active{display:block}.navbar.is-fixed-bottom-touch,.navbar.is-fixed-top-touch{left:0;position:fixed;right:0;z-index:30}.navbar.is-fixed-bottom-touch{bottom:0}.navbar.is-fixed-bottom-touch.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,0.1)}.navbar.is-fixed-top-touch{top:0}.navbar.is-fixed-top .navbar-menu,.navbar.is-fixed-top-touch .navbar-menu{-webkit-overflow-scrolling:touch;max-height:calc(100vh - 3.25rem);overflow:auto}html.has-navbar-fixed-top-touch,body.has-navbar-fixed-top-touch{padding-top:3.25rem}html.has-navbar-fixed-bottom-touch,body.has-navbar-fixed-bottom-touch{padding-bottom:3.25rem}}@media screen and (min-width: 1056px){.navbar,.navbar-menu,.navbar-start,.navbar-end{align-items:stretch;display:flex}.navbar{min-height:3.25rem}.navbar.is-spaced{padding:1rem 2rem}.navbar.is-spaced .navbar-start,.navbar.is-spaced .navbar-end{align-items:center}.navbar.is-spaced a.navbar-item,.navbar.is-spaced .navbar-link{border-radius:4px}.navbar.is-transparent a.navbar-item:focus,.navbar.is-transparent a.navbar-item:hover,.navbar.is-transparent a.navbar-item.is-active,.navbar.is-transparent .navbar-link:focus,.navbar.is-transparent .navbar-link:hover,.navbar.is-transparent .navbar-link.is-active{background-color:transparent !important}.navbar.is-transparent .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus .navbar-link,.navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus-within .navbar-link,.navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:hover .navbar-link{background-color:transparent !important}.navbar.is-transparent .navbar-dropdown a.navbar-item:focus,.navbar.is-transparent .navbar-dropdown a.navbar-item:hover{background-color:#f5f5f5;color:#0a0a0a}.navbar.is-transparent .navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:#2e63b8}.navbar-burger{display:none}.navbar-item,.navbar-link{align-items:center;display:flex}.navbar-item.has-dropdown{align-items:stretch}.navbar-item.has-dropdown-up .navbar-link::after{transform:rotate(135deg) translate(0.25em, -0.25em)}.navbar-item.has-dropdown-up .navbar-dropdown{border-bottom:2px solid #dbdbdb;border-radius:6px 6px 0 0;border-top:none;bottom:100%;box-shadow:0 -8px 8px rgba(10,10,10,0.1);top:auto}.navbar-item.is-active .navbar-dropdown,.navbar-item.is-hoverable:focus .navbar-dropdown,.navbar-item.is-hoverable:focus-within .navbar-dropdown,.navbar-item.is-hoverable:hover .navbar-dropdown{display:block}.navbar.is-spaced .navbar-item.is-active .navbar-dropdown,.navbar-item.is-active .navbar-dropdown.is-boxed,.navbar.is-spaced .navbar-item.is-hoverable:focus .navbar-dropdown,.navbar-item.is-hoverable:focus .navbar-dropdown.is-boxed,.navbar.is-spaced .navbar-item.is-hoverable:focus-within .navbar-dropdown,.navbar-item.is-hoverable:focus-within .navbar-dropdown.is-boxed,.navbar.is-spaced .navbar-item.is-hoverable:hover .navbar-dropdown,.navbar-item.is-hoverable:hover .navbar-dropdown.is-boxed{opacity:1;pointer-events:auto;transform:translateY(0)}.navbar-menu{flex-grow:1;flex-shrink:0}.navbar-start{justify-content:flex-start;margin-right:auto}.navbar-end{justify-content:flex-end;margin-left:auto}.navbar-dropdown{background-color:#fff;border-bottom-left-radius:6px;border-bottom-right-radius:6px;border-top:2px solid #dbdbdb;box-shadow:0 8px 8px rgba(10,10,10,0.1);display:none;font-size:0.875rem;left:0;min-width:100%;position:absolute;top:100%;z-index:20}.navbar-dropdown .navbar-item{padding:0.375rem 1rem;white-space:nowrap}.navbar-dropdown a.navbar-item{padding-right:3rem}.navbar-dropdown a.navbar-item:focus,.navbar-dropdown a.navbar-item:hover{background-color:#f5f5f5;color:#0a0a0a}.navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:#2e63b8}.navbar.is-spaced .navbar-dropdown,.navbar-dropdown.is-boxed{border-radius:6px;border-top:none;box-shadow:0 8px 8px rgba(10,10,10,0.1), 0 0 0 1px rgba(10,10,10,0.1);display:block;opacity:0;pointer-events:none;top:calc(100% + (-4px));transform:translateY(-5px);transition-duration:86ms;transition-property:opacity, transform}.navbar-dropdown.is-right{left:auto;right:0}.navbar-divider{display:block}.navbar>.container .navbar-brand,.container>.navbar .navbar-brand{margin-left:-.75rem}.navbar>.container .navbar-menu,.container>.navbar .navbar-menu{margin-right:-.75rem}.navbar.is-fixed-bottom-desktop,.navbar.is-fixed-top-desktop{left:0;position:fixed;right:0;z-index:30}.navbar.is-fixed-bottom-desktop{bottom:0}.navbar.is-fixed-bottom-desktop.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,0.1)}.navbar.is-fixed-top-desktop{top:0}html.has-navbar-fixed-top-desktop,body.has-navbar-fixed-top-desktop{padding-top:3.25rem}html.has-navbar-fixed-bottom-desktop,body.has-navbar-fixed-bottom-desktop{padding-bottom:3.25rem}html.has-spaced-navbar-fixed-top,body.has-spaced-navbar-fixed-top{padding-top:5.25rem}html.has-spaced-navbar-fixed-bottom,body.has-spaced-navbar-fixed-bottom{padding-bottom:5.25rem}a.navbar-item.is-active,.navbar-link.is-active{color:#0a0a0a}a.navbar-item.is-active:not(:focus):not(:hover),.navbar-link.is-active:not(:focus):not(:hover){background-color:rgba(0,0,0,0)}.navbar-item.has-dropdown:focus .navbar-link,.navbar-item.has-dropdown:hover .navbar-link,.navbar-item.has-dropdown.is-active .navbar-link{background-color:#fafafa}}.hero.is-fullheight-with-navbar{min-height:calc(100vh - 3.25rem)}.pagination{font-size:1rem;margin:-.25rem}.pagination.is-small,#documenter .docs-sidebar form.docs-search>input.pagination{font-size:.75rem}.pagination.is-medium{font-size:1.25rem}.pagination.is-large{font-size:1.5rem}.pagination.is-rounded .pagination-previous,#documenter .docs-sidebar form.docs-search>input.pagination .pagination-previous,.pagination.is-rounded .pagination-next,#documenter .docs-sidebar form.docs-search>input.pagination .pagination-next{padding-left:1em;padding-right:1em;border-radius:9999px}.pagination.is-rounded .pagination-link,#documenter .docs-sidebar form.docs-search>input.pagination .pagination-link{border-radius:9999px}.pagination,.pagination-list{align-items:center;display:flex;justify-content:center;text-align:center}.pagination-previous,.pagination-next,.pagination-link,.pagination-ellipsis{font-size:1em;justify-content:center;margin:.25rem;padding-left:.5em;padding-right:.5em;text-align:center}.pagination-previous,.pagination-next,.pagination-link{border-color:#dbdbdb;color:#222;min-width:2.5em}.pagination-previous:hover,.pagination-next:hover,.pagination-link:hover{border-color:#b5b5b5;color:#363636}.pagination-previous:focus,.pagination-next:focus,.pagination-link:focus{border-color:#3c5dcd}.pagination-previous:active,.pagination-next:active,.pagination-link:active{box-shadow:inset 0 1px 2px rgba(10,10,10,0.2)}.pagination-previous[disabled],.pagination-previous.is-disabled,.pagination-next[disabled],.pagination-next.is-disabled,.pagination-link[disabled],.pagination-link.is-disabled{background-color:#dbdbdb;border-color:#dbdbdb;box-shadow:none;color:#6b6b6b;opacity:0.5}.pagination-previous,.pagination-next{padding-left:.75em;padding-right:.75em;white-space:nowrap}.pagination-link.is-current{background-color:#2e63b8;border-color:#2e63b8;color:#fff}.pagination-ellipsis{color:#b5b5b5;pointer-events:none}.pagination-list{flex-wrap:wrap}.pagination-list li{list-style:none}@media screen and (max-width: 768px){.pagination{flex-wrap:wrap}.pagination-previous,.pagination-next{flex-grow:1;flex-shrink:1}.pagination-list li{flex-grow:1;flex-shrink:1}}@media screen and (min-width: 769px),print{.pagination-list{flex-grow:1;flex-shrink:1;justify-content:flex-start;order:1}.pagination-previous,.pagination-next,.pagination-link,.pagination-ellipsis{margin-bottom:0;margin-top:0}.pagination-previous{order:2}.pagination-next{order:3}.pagination{justify-content:space-between;margin-bottom:0;margin-top:0}.pagination.is-centered .pagination-previous{order:1}.pagination.is-centered .pagination-list{justify-content:center;order:2}.pagination.is-centered .pagination-next{order:3}.pagination.is-right .pagination-previous{order:1}.pagination.is-right .pagination-next{order:2}.pagination.is-right .pagination-list{justify-content:flex-end;order:3}}.panel{border-radius:6px;box-shadow:#bbb;font-size:1rem}.panel:not(:last-child){margin-bottom:1.5rem}.panel.is-white .panel-heading{background-color:#fff;color:#0a0a0a}.panel.is-white .panel-tabs a.is-active{border-bottom-color:#fff}.panel.is-white .panel-block.is-active .panel-icon{color:#fff}.panel.is-black .panel-heading{background-color:#0a0a0a;color:#fff}.panel.is-black .panel-tabs a.is-active{border-bottom-color:#0a0a0a}.panel.is-black .panel-block.is-active .panel-icon{color:#0a0a0a}.panel.is-light .panel-heading{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}.panel.is-light .panel-tabs a.is-active{border-bottom-color:#f5f5f5}.panel.is-light .panel-block.is-active .panel-icon{color:#f5f5f5}.panel.is-dark .panel-heading,.content kbd.panel .panel-heading{background-color:#363636;color:#fff}.panel.is-dark .panel-tabs a.is-active,.content kbd.panel .panel-tabs a.is-active{border-bottom-color:#363636}.panel.is-dark .panel-block.is-active .panel-icon,.content kbd.panel .panel-block.is-active .panel-icon{color:#363636}.panel.is-primary .panel-heading,.docstring>section>a.panel.docs-sourcelink .panel-heading{background-color:#4eb5de;color:#fff}.panel.is-primary .panel-tabs a.is-active,.docstring>section>a.panel.docs-sourcelink .panel-tabs a.is-active{border-bottom-color:#4eb5de}.panel.is-primary .panel-block.is-active .panel-icon,.docstring>section>a.panel.docs-sourcelink .panel-block.is-active .panel-icon{color:#4eb5de}.panel.is-link .panel-heading{background-color:#2e63b8;color:#fff}.panel.is-link .panel-tabs a.is-active{border-bottom-color:#2e63b8}.panel.is-link .panel-block.is-active .panel-icon{color:#2e63b8}.panel.is-info .panel-heading{background-color:#209cee;color:#fff}.panel.is-info .panel-tabs a.is-active{border-bottom-color:#209cee}.panel.is-info .panel-block.is-active .panel-icon{color:#209cee}.panel.is-success .panel-heading{background-color:#22c35b;color:#fff}.panel.is-success .panel-tabs a.is-active{border-bottom-color:#22c35b}.panel.is-success .panel-block.is-active .panel-icon{color:#22c35b}.panel.is-warning .panel-heading{background-color:#ffdd57;color:rgba(0,0,0,0.7)}.panel.is-warning .panel-tabs a.is-active{border-bottom-color:#ffdd57}.panel.is-warning .panel-block.is-active .panel-icon{color:#ffdd57}.panel.is-danger .panel-heading{background-color:#da0b00;color:#fff}.panel.is-danger .panel-tabs a.is-active{border-bottom-color:#da0b00}.panel.is-danger .panel-block.is-active .panel-icon{color:#da0b00}.panel-tabs:not(:last-child),.panel-block:not(:last-child){border-bottom:1px solid #ededed}.panel-heading{background-color:#ededed;border-radius:6px 6px 0 0;color:#222;font-size:1.25em;font-weight:700;line-height:1.25;padding:0.75em 1em}.panel-tabs{align-items:flex-end;display:flex;font-size:.875em;justify-content:center}.panel-tabs a{border-bottom:1px solid #dbdbdb;margin-bottom:-1px;padding:0.5em}.panel-tabs a.is-active{border-bottom-color:#4a4a4a;color:#363636}.panel-list a{color:#222}.panel-list a:hover{color:#2e63b8}.panel-block{align-items:center;color:#222;display:flex;justify-content:flex-start;padding:0.5em 0.75em}.panel-block input[type="checkbox"]{margin-right:.75em}.panel-block>.control{flex-grow:1;flex-shrink:1;width:100%}.panel-block.is-wrapped{flex-wrap:wrap}.panel-block.is-active{border-left-color:#2e63b8;color:#363636}.panel-block.is-active .panel-icon{color:#2e63b8}.panel-block:last-child{border-bottom-left-radius:6px;border-bottom-right-radius:6px}a.panel-block,label.panel-block{cursor:pointer}a.panel-block:hover,label.panel-block:hover{background-color:#f5f5f5}.panel-icon{display:inline-block;font-size:14px;height:1em;line-height:1em;text-align:center;vertical-align:top;width:1em;color:#6b6b6b;margin-right:.75em}.panel-icon .fa{font-size:inherit;line-height:inherit}.tabs{-webkit-overflow-scrolling:touch;align-items:stretch;display:flex;font-size:1rem;justify-content:space-between;overflow:hidden;overflow-x:auto;white-space:nowrap}.tabs a{align-items:center;border-bottom-color:#dbdbdb;border-bottom-style:solid;border-bottom-width:1px;color:#222;display:flex;justify-content:center;margin-bottom:-1px;padding:0.5em 1em;vertical-align:top}.tabs a:hover{border-bottom-color:#222;color:#222}.tabs li{display:block}.tabs li.is-active a{border-bottom-color:#2e63b8;color:#2e63b8}.tabs ul{align-items:center;border-bottom-color:#dbdbdb;border-bottom-style:solid;border-bottom-width:1px;display:flex;flex-grow:1;flex-shrink:0;justify-content:flex-start}.tabs ul.is-left{padding-right:0.75em}.tabs ul.is-center{flex:none;justify-content:center;padding-left:0.75em;padding-right:0.75em}.tabs ul.is-right{justify-content:flex-end;padding-left:0.75em}.tabs .icon:first-child{margin-right:.5em}.tabs .icon:last-child{margin-left:.5em}.tabs.is-centered ul{justify-content:center}.tabs.is-right ul{justify-content:flex-end}.tabs.is-boxed a{border:1px solid transparent;border-radius:4px 4px 0 0}.tabs.is-boxed a:hover{background-color:#f5f5f5;border-bottom-color:#dbdbdb}.tabs.is-boxed li.is-active a{background-color:#fff;border-color:#dbdbdb;border-bottom-color:rgba(0,0,0,0) !important}.tabs.is-fullwidth li{flex-grow:1;flex-shrink:0}.tabs.is-toggle a{border-color:#dbdbdb;border-style:solid;border-width:1px;margin-bottom:0;position:relative}.tabs.is-toggle a:hover{background-color:#f5f5f5;border-color:#b5b5b5;z-index:2}.tabs.is-toggle li+li{margin-left:-1px}.tabs.is-toggle li:first-child a{border-top-left-radius:4px;border-bottom-left-radius:4px}.tabs.is-toggle li:last-child a{border-top-right-radius:4px;border-bottom-right-radius:4px}.tabs.is-toggle li.is-active a{background-color:#2e63b8;border-color:#2e63b8;color:#fff;z-index:1}.tabs.is-toggle ul{border-bottom:none}.tabs.is-toggle.is-toggle-rounded li:first-child a{border-bottom-left-radius:9999px;border-top-left-radius:9999px;padding-left:1.25em}.tabs.is-toggle.is-toggle-rounded li:last-child a{border-bottom-right-radius:9999px;border-top-right-radius:9999px;padding-right:1.25em}.tabs.is-small,#documenter .docs-sidebar form.docs-search>input.tabs{font-size:.75rem}.tabs.is-medium{font-size:1.25rem}.tabs.is-large{font-size:1.5rem}.column{display:block;flex-basis:0;flex-grow:1;flex-shrink:1;padding:.75rem}.columns.is-mobile>.column.is-narrow{flex:none;width:unset}.columns.is-mobile>.column.is-full{flex:none;width:100%}.columns.is-mobile>.column.is-three-quarters{flex:none;width:75%}.columns.is-mobile>.column.is-two-thirds{flex:none;width:66.6666%}.columns.is-mobile>.column.is-half{flex:none;width:50%}.columns.is-mobile>.column.is-one-third{flex:none;width:33.3333%}.columns.is-mobile>.column.is-one-quarter{flex:none;width:25%}.columns.is-mobile>.column.is-one-fifth{flex:none;width:20%}.columns.is-mobile>.column.is-two-fifths{flex:none;width:40%}.columns.is-mobile>.column.is-three-fifths{flex:none;width:60%}.columns.is-mobile>.column.is-four-fifths{flex:none;width:80%}.columns.is-mobile>.column.is-offset-three-quarters{margin-left:75%}.columns.is-mobile>.column.is-offset-two-thirds{margin-left:66.6666%}.columns.is-mobile>.column.is-offset-half{margin-left:50%}.columns.is-mobile>.column.is-offset-one-third{margin-left:33.3333%}.columns.is-mobile>.column.is-offset-one-quarter{margin-left:25%}.columns.is-mobile>.column.is-offset-one-fifth{margin-left:20%}.columns.is-mobile>.column.is-offset-two-fifths{margin-left:40%}.columns.is-mobile>.column.is-offset-three-fifths{margin-left:60%}.columns.is-mobile>.column.is-offset-four-fifths{margin-left:80%}.columns.is-mobile>.column.is-0{flex:none;width:0%}.columns.is-mobile>.column.is-offset-0{margin-left:0%}.columns.is-mobile>.column.is-1{flex:none;width:8.33333337%}.columns.is-mobile>.column.is-offset-1{margin-left:8.33333337%}.columns.is-mobile>.column.is-2{flex:none;width:16.66666674%}.columns.is-mobile>.column.is-offset-2{margin-left:16.66666674%}.columns.is-mobile>.column.is-3{flex:none;width:25%}.columns.is-mobile>.column.is-offset-3{margin-left:25%}.columns.is-mobile>.column.is-4{flex:none;width:33.33333337%}.columns.is-mobile>.column.is-offset-4{margin-left:33.33333337%}.columns.is-mobile>.column.is-5{flex:none;width:41.66666674%}.columns.is-mobile>.column.is-offset-5{margin-left:41.66666674%}.columns.is-mobile>.column.is-6{flex:none;width:50%}.columns.is-mobile>.column.is-offset-6{margin-left:50%}.columns.is-mobile>.column.is-7{flex:none;width:58.33333337%}.columns.is-mobile>.column.is-offset-7{margin-left:58.33333337%}.columns.is-mobile>.column.is-8{flex:none;width:66.66666674%}.columns.is-mobile>.column.is-offset-8{margin-left:66.66666674%}.columns.is-mobile>.column.is-9{flex:none;width:75%}.columns.is-mobile>.column.is-offset-9{margin-left:75%}.columns.is-mobile>.column.is-10{flex:none;width:83.33333337%}.columns.is-mobile>.column.is-offset-10{margin-left:83.33333337%}.columns.is-mobile>.column.is-11{flex:none;width:91.66666674%}.columns.is-mobile>.column.is-offset-11{margin-left:91.66666674%}.columns.is-mobile>.column.is-12{flex:none;width:100%}.columns.is-mobile>.column.is-offset-12{margin-left:100%}@media screen and (max-width: 768px){.column.is-narrow-mobile{flex:none;width:unset}.column.is-full-mobile{flex:none;width:100%}.column.is-three-quarters-mobile{flex:none;width:75%}.column.is-two-thirds-mobile{flex:none;width:66.6666%}.column.is-half-mobile{flex:none;width:50%}.column.is-one-third-mobile{flex:none;width:33.3333%}.column.is-one-quarter-mobile{flex:none;width:25%}.column.is-one-fifth-mobile{flex:none;width:20%}.column.is-two-fifths-mobile{flex:none;width:40%}.column.is-three-fifths-mobile{flex:none;width:60%}.column.is-four-fifths-mobile{flex:none;width:80%}.column.is-offset-three-quarters-mobile{margin-left:75%}.column.is-offset-two-thirds-mobile{margin-left:66.6666%}.column.is-offset-half-mobile{margin-left:50%}.column.is-offset-one-third-mobile{margin-left:33.3333%}.column.is-offset-one-quarter-mobile{margin-left:25%}.column.is-offset-one-fifth-mobile{margin-left:20%}.column.is-offset-two-fifths-mobile{margin-left:40%}.column.is-offset-three-fifths-mobile{margin-left:60%}.column.is-offset-four-fifths-mobile{margin-left:80%}.column.is-0-mobile{flex:none;width:0%}.column.is-offset-0-mobile{margin-left:0%}.column.is-1-mobile{flex:none;width:8.33333337%}.column.is-offset-1-mobile{margin-left:8.33333337%}.column.is-2-mobile{flex:none;width:16.66666674%}.column.is-offset-2-mobile{margin-left:16.66666674%}.column.is-3-mobile{flex:none;width:25%}.column.is-offset-3-mobile{margin-left:25%}.column.is-4-mobile{flex:none;width:33.33333337%}.column.is-offset-4-mobile{margin-left:33.33333337%}.column.is-5-mobile{flex:none;width:41.66666674%}.column.is-offset-5-mobile{margin-left:41.66666674%}.column.is-6-mobile{flex:none;width:50%}.column.is-offset-6-mobile{margin-left:50%}.column.is-7-mobile{flex:none;width:58.33333337%}.column.is-offset-7-mobile{margin-left:58.33333337%}.column.is-8-mobile{flex:none;width:66.66666674%}.column.is-offset-8-mobile{margin-left:66.66666674%}.column.is-9-mobile{flex:none;width:75%}.column.is-offset-9-mobile{margin-left:75%}.column.is-10-mobile{flex:none;width:83.33333337%}.column.is-offset-10-mobile{margin-left:83.33333337%}.column.is-11-mobile{flex:none;width:91.66666674%}.column.is-offset-11-mobile{margin-left:91.66666674%}.column.is-12-mobile{flex:none;width:100%}.column.is-offset-12-mobile{margin-left:100%}}@media screen and (min-width: 769px),print{.column.is-narrow,.column.is-narrow-tablet{flex:none;width:unset}.column.is-full,.column.is-full-tablet{flex:none;width:100%}.column.is-three-quarters,.column.is-three-quarters-tablet{flex:none;width:75%}.column.is-two-thirds,.column.is-two-thirds-tablet{flex:none;width:66.6666%}.column.is-half,.column.is-half-tablet{flex:none;width:50%}.column.is-one-third,.column.is-one-third-tablet{flex:none;width:33.3333%}.column.is-one-quarter,.column.is-one-quarter-tablet{flex:none;width:25%}.column.is-one-fifth,.column.is-one-fifth-tablet{flex:none;width:20%}.column.is-two-fifths,.column.is-two-fifths-tablet{flex:none;width:40%}.column.is-three-fifths,.column.is-three-fifths-tablet{flex:none;width:60%}.column.is-four-fifths,.column.is-four-fifths-tablet{flex:none;width:80%}.column.is-offset-three-quarters,.column.is-offset-three-quarters-tablet{margin-left:75%}.column.is-offset-two-thirds,.column.is-offset-two-thirds-tablet{margin-left:66.6666%}.column.is-offset-half,.column.is-offset-half-tablet{margin-left:50%}.column.is-offset-one-third,.column.is-offset-one-third-tablet{margin-left:33.3333%}.column.is-offset-one-quarter,.column.is-offset-one-quarter-tablet{margin-left:25%}.column.is-offset-one-fifth,.column.is-offset-one-fifth-tablet{margin-left:20%}.column.is-offset-two-fifths,.column.is-offset-two-fifths-tablet{margin-left:40%}.column.is-offset-three-fifths,.column.is-offset-three-fifths-tablet{margin-left:60%}.column.is-offset-four-fifths,.column.is-offset-four-fifths-tablet{margin-left:80%}.column.is-0,.column.is-0-tablet{flex:none;width:0%}.column.is-offset-0,.column.is-offset-0-tablet{margin-left:0%}.column.is-1,.column.is-1-tablet{flex:none;width:8.33333337%}.column.is-offset-1,.column.is-offset-1-tablet{margin-left:8.33333337%}.column.is-2,.column.is-2-tablet{flex:none;width:16.66666674%}.column.is-offset-2,.column.is-offset-2-tablet{margin-left:16.66666674%}.column.is-3,.column.is-3-tablet{flex:none;width:25%}.column.is-offset-3,.column.is-offset-3-tablet{margin-left:25%}.column.is-4,.column.is-4-tablet{flex:none;width:33.33333337%}.column.is-offset-4,.column.is-offset-4-tablet{margin-left:33.33333337%}.column.is-5,.column.is-5-tablet{flex:none;width:41.66666674%}.column.is-offset-5,.column.is-offset-5-tablet{margin-left:41.66666674%}.column.is-6,.column.is-6-tablet{flex:none;width:50%}.column.is-offset-6,.column.is-offset-6-tablet{margin-left:50%}.column.is-7,.column.is-7-tablet{flex:none;width:58.33333337%}.column.is-offset-7,.column.is-offset-7-tablet{margin-left:58.33333337%}.column.is-8,.column.is-8-tablet{flex:none;width:66.66666674%}.column.is-offset-8,.column.is-offset-8-tablet{margin-left:66.66666674%}.column.is-9,.column.is-9-tablet{flex:none;width:75%}.column.is-offset-9,.column.is-offset-9-tablet{margin-left:75%}.column.is-10,.column.is-10-tablet{flex:none;width:83.33333337%}.column.is-offset-10,.column.is-offset-10-tablet{margin-left:83.33333337%}.column.is-11,.column.is-11-tablet{flex:none;width:91.66666674%}.column.is-offset-11,.column.is-offset-11-tablet{margin-left:91.66666674%}.column.is-12,.column.is-12-tablet{flex:none;width:100%}.column.is-offset-12,.column.is-offset-12-tablet{margin-left:100%}}@media screen and (max-width: 1055px){.column.is-narrow-touch{flex:none;width:unset}.column.is-full-touch{flex:none;width:100%}.column.is-three-quarters-touch{flex:none;width:75%}.column.is-two-thirds-touch{flex:none;width:66.6666%}.column.is-half-touch{flex:none;width:50%}.column.is-one-third-touch{flex:none;width:33.3333%}.column.is-one-quarter-touch{flex:none;width:25%}.column.is-one-fifth-touch{flex:none;width:20%}.column.is-two-fifths-touch{flex:none;width:40%}.column.is-three-fifths-touch{flex:none;width:60%}.column.is-four-fifths-touch{flex:none;width:80%}.column.is-offset-three-quarters-touch{margin-left:75%}.column.is-offset-two-thirds-touch{margin-left:66.6666%}.column.is-offset-half-touch{margin-left:50%}.column.is-offset-one-third-touch{margin-left:33.3333%}.column.is-offset-one-quarter-touch{margin-left:25%}.column.is-offset-one-fifth-touch{margin-left:20%}.column.is-offset-two-fifths-touch{margin-left:40%}.column.is-offset-three-fifths-touch{margin-left:60%}.column.is-offset-four-fifths-touch{margin-left:80%}.column.is-0-touch{flex:none;width:0%}.column.is-offset-0-touch{margin-left:0%}.column.is-1-touch{flex:none;width:8.33333337%}.column.is-offset-1-touch{margin-left:8.33333337%}.column.is-2-touch{flex:none;width:16.66666674%}.column.is-offset-2-touch{margin-left:16.66666674%}.column.is-3-touch{flex:none;width:25%}.column.is-offset-3-touch{margin-left:25%}.column.is-4-touch{flex:none;width:33.33333337%}.column.is-offset-4-touch{margin-left:33.33333337%}.column.is-5-touch{flex:none;width:41.66666674%}.column.is-offset-5-touch{margin-left:41.66666674%}.column.is-6-touch{flex:none;width:50%}.column.is-offset-6-touch{margin-left:50%}.column.is-7-touch{flex:none;width:58.33333337%}.column.is-offset-7-touch{margin-left:58.33333337%}.column.is-8-touch{flex:none;width:66.66666674%}.column.is-offset-8-touch{margin-left:66.66666674%}.column.is-9-touch{flex:none;width:75%}.column.is-offset-9-touch{margin-left:75%}.column.is-10-touch{flex:none;width:83.33333337%}.column.is-offset-10-touch{margin-left:83.33333337%}.column.is-11-touch{flex:none;width:91.66666674%}.column.is-offset-11-touch{margin-left:91.66666674%}.column.is-12-touch{flex:none;width:100%}.column.is-offset-12-touch{margin-left:100%}}@media screen and (min-width: 1056px){.column.is-narrow-desktop{flex:none;width:unset}.column.is-full-desktop{flex:none;width:100%}.column.is-three-quarters-desktop{flex:none;width:75%}.column.is-two-thirds-desktop{flex:none;width:66.6666%}.column.is-half-desktop{flex:none;width:50%}.column.is-one-third-desktop{flex:none;width:33.3333%}.column.is-one-quarter-desktop{flex:none;width:25%}.column.is-one-fifth-desktop{flex:none;width:20%}.column.is-two-fifths-desktop{flex:none;width:40%}.column.is-three-fifths-desktop{flex:none;width:60%}.column.is-four-fifths-desktop{flex:none;width:80%}.column.is-offset-three-quarters-desktop{margin-left:75%}.column.is-offset-two-thirds-desktop{margin-left:66.6666%}.column.is-offset-half-desktop{margin-left:50%}.column.is-offset-one-third-desktop{margin-left:33.3333%}.column.is-offset-one-quarter-desktop{margin-left:25%}.column.is-offset-one-fifth-desktop{margin-left:20%}.column.is-offset-two-fifths-desktop{margin-left:40%}.column.is-offset-three-fifths-desktop{margin-left:60%}.column.is-offset-four-fifths-desktop{margin-left:80%}.column.is-0-desktop{flex:none;width:0%}.column.is-offset-0-desktop{margin-left:0%}.column.is-1-desktop{flex:none;width:8.33333337%}.column.is-offset-1-desktop{margin-left:8.33333337%}.column.is-2-desktop{flex:none;width:16.66666674%}.column.is-offset-2-desktop{margin-left:16.66666674%}.column.is-3-desktop{flex:none;width:25%}.column.is-offset-3-desktop{margin-left:25%}.column.is-4-desktop{flex:none;width:33.33333337%}.column.is-offset-4-desktop{margin-left:33.33333337%}.column.is-5-desktop{flex:none;width:41.66666674%}.column.is-offset-5-desktop{margin-left:41.66666674%}.column.is-6-desktop{flex:none;width:50%}.column.is-offset-6-desktop{margin-left:50%}.column.is-7-desktop{flex:none;width:58.33333337%}.column.is-offset-7-desktop{margin-left:58.33333337%}.column.is-8-desktop{flex:none;width:66.66666674%}.column.is-offset-8-desktop{margin-left:66.66666674%}.column.is-9-desktop{flex:none;width:75%}.column.is-offset-9-desktop{margin-left:75%}.column.is-10-desktop{flex:none;width:83.33333337%}.column.is-offset-10-desktop{margin-left:83.33333337%}.column.is-11-desktop{flex:none;width:91.66666674%}.column.is-offset-11-desktop{margin-left:91.66666674%}.column.is-12-desktop{flex:none;width:100%}.column.is-offset-12-desktop{margin-left:100%}}@media screen and (min-width: 1216px){.column.is-narrow-widescreen{flex:none;width:unset}.column.is-full-widescreen{flex:none;width:100%}.column.is-three-quarters-widescreen{flex:none;width:75%}.column.is-two-thirds-widescreen{flex:none;width:66.6666%}.column.is-half-widescreen{flex:none;width:50%}.column.is-one-third-widescreen{flex:none;width:33.3333%}.column.is-one-quarter-widescreen{flex:none;width:25%}.column.is-one-fifth-widescreen{flex:none;width:20%}.column.is-two-fifths-widescreen{flex:none;width:40%}.column.is-three-fifths-widescreen{flex:none;width:60%}.column.is-four-fifths-widescreen{flex:none;width:80%}.column.is-offset-three-quarters-widescreen{margin-left:75%}.column.is-offset-two-thirds-widescreen{margin-left:66.6666%}.column.is-offset-half-widescreen{margin-left:50%}.column.is-offset-one-third-widescreen{margin-left:33.3333%}.column.is-offset-one-quarter-widescreen{margin-left:25%}.column.is-offset-one-fifth-widescreen{margin-left:20%}.column.is-offset-two-fifths-widescreen{margin-left:40%}.column.is-offset-three-fifths-widescreen{margin-left:60%}.column.is-offset-four-fifths-widescreen{margin-left:80%}.column.is-0-widescreen{flex:none;width:0%}.column.is-offset-0-widescreen{margin-left:0%}.column.is-1-widescreen{flex:none;width:8.33333337%}.column.is-offset-1-widescreen{margin-left:8.33333337%}.column.is-2-widescreen{flex:none;width:16.66666674%}.column.is-offset-2-widescreen{margin-left:16.66666674%}.column.is-3-widescreen{flex:none;width:25%}.column.is-offset-3-widescreen{margin-left:25%}.column.is-4-widescreen{flex:none;width:33.33333337%}.column.is-offset-4-widescreen{margin-left:33.33333337%}.column.is-5-widescreen{flex:none;width:41.66666674%}.column.is-offset-5-widescreen{margin-left:41.66666674%}.column.is-6-widescreen{flex:none;width:50%}.column.is-offset-6-widescreen{margin-left:50%}.column.is-7-widescreen{flex:none;width:58.33333337%}.column.is-offset-7-widescreen{margin-left:58.33333337%}.column.is-8-widescreen{flex:none;width:66.66666674%}.column.is-offset-8-widescreen{margin-left:66.66666674%}.column.is-9-widescreen{flex:none;width:75%}.column.is-offset-9-widescreen{margin-left:75%}.column.is-10-widescreen{flex:none;width:83.33333337%}.column.is-offset-10-widescreen{margin-left:83.33333337%}.column.is-11-widescreen{flex:none;width:91.66666674%}.column.is-offset-11-widescreen{margin-left:91.66666674%}.column.is-12-widescreen{flex:none;width:100%}.column.is-offset-12-widescreen{margin-left:100%}}@media screen and (min-width: 1408px){.column.is-narrow-fullhd{flex:none;width:unset}.column.is-full-fullhd{flex:none;width:100%}.column.is-three-quarters-fullhd{flex:none;width:75%}.column.is-two-thirds-fullhd{flex:none;width:66.6666%}.column.is-half-fullhd{flex:none;width:50%}.column.is-one-third-fullhd{flex:none;width:33.3333%}.column.is-one-quarter-fullhd{flex:none;width:25%}.column.is-one-fifth-fullhd{flex:none;width:20%}.column.is-two-fifths-fullhd{flex:none;width:40%}.column.is-three-fifths-fullhd{flex:none;width:60%}.column.is-four-fifths-fullhd{flex:none;width:80%}.column.is-offset-three-quarters-fullhd{margin-left:75%}.column.is-offset-two-thirds-fullhd{margin-left:66.6666%}.column.is-offset-half-fullhd{margin-left:50%}.column.is-offset-one-third-fullhd{margin-left:33.3333%}.column.is-offset-one-quarter-fullhd{margin-left:25%}.column.is-offset-one-fifth-fullhd{margin-left:20%}.column.is-offset-two-fifths-fullhd{margin-left:40%}.column.is-offset-three-fifths-fullhd{margin-left:60%}.column.is-offset-four-fifths-fullhd{margin-left:80%}.column.is-0-fullhd{flex:none;width:0%}.column.is-offset-0-fullhd{margin-left:0%}.column.is-1-fullhd{flex:none;width:8.33333337%}.column.is-offset-1-fullhd{margin-left:8.33333337%}.column.is-2-fullhd{flex:none;width:16.66666674%}.column.is-offset-2-fullhd{margin-left:16.66666674%}.column.is-3-fullhd{flex:none;width:25%}.column.is-offset-3-fullhd{margin-left:25%}.column.is-4-fullhd{flex:none;width:33.33333337%}.column.is-offset-4-fullhd{margin-left:33.33333337%}.column.is-5-fullhd{flex:none;width:41.66666674%}.column.is-offset-5-fullhd{margin-left:41.66666674%}.column.is-6-fullhd{flex:none;width:50%}.column.is-offset-6-fullhd{margin-left:50%}.column.is-7-fullhd{flex:none;width:58.33333337%}.column.is-offset-7-fullhd{margin-left:58.33333337%}.column.is-8-fullhd{flex:none;width:66.66666674%}.column.is-offset-8-fullhd{margin-left:66.66666674%}.column.is-9-fullhd{flex:none;width:75%}.column.is-offset-9-fullhd{margin-left:75%}.column.is-10-fullhd{flex:none;width:83.33333337%}.column.is-offset-10-fullhd{margin-left:83.33333337%}.column.is-11-fullhd{flex:none;width:91.66666674%}.column.is-offset-11-fullhd{margin-left:91.66666674%}.column.is-12-fullhd{flex:none;width:100%}.column.is-offset-12-fullhd{margin-left:100%}}.columns{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}.columns:last-child{margin-bottom:-.75rem}.columns:not(:last-child){margin-bottom:calc(1.5rem - .75rem)}.columns.is-centered{justify-content:center}.columns.is-gapless{margin-left:0;margin-right:0;margin-top:0}.columns.is-gapless>.column{margin:0;padding:0 !important}.columns.is-gapless:not(:last-child){margin-bottom:1.5rem}.columns.is-gapless:last-child{margin-bottom:0}.columns.is-mobile{display:flex}.columns.is-multiline{flex-wrap:wrap}.columns.is-vcentered{align-items:center}@media screen and (min-width: 769px),print{.columns:not(.is-desktop){display:flex}}@media screen and (min-width: 1056px){.columns.is-desktop{display:flex}}.columns.is-variable{--columnGap: 0.75rem;margin-left:calc(-1 * var(--columnGap));margin-right:calc(-1 * var(--columnGap))}.columns.is-variable>.column{padding-left:var(--columnGap);padding-right:var(--columnGap)}.columns.is-variable.is-0{--columnGap: 0rem}@media screen and (max-width: 768px){.columns.is-variable.is-0-mobile{--columnGap: 0rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-0-tablet{--columnGap: 0rem}}@media screen and (min-width: 769px) and (max-width: 1055px){.columns.is-variable.is-0-tablet-only{--columnGap: 0rem}}@media screen and (max-width: 1055px){.columns.is-variable.is-0-touch{--columnGap: 0rem}}@media screen and (min-width: 1056px){.columns.is-variable.is-0-desktop{--columnGap: 0rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){.columns.is-variable.is-0-desktop-only{--columnGap: 0rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-0-widescreen{--columnGap: 0rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){.columns.is-variable.is-0-widescreen-only{--columnGap: 0rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-0-fullhd{--columnGap: 0rem}}.columns.is-variable.is-1{--columnGap: .25rem}@media screen and (max-width: 768px){.columns.is-variable.is-1-mobile{--columnGap: .25rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-1-tablet{--columnGap: .25rem}}@media screen and (min-width: 769px) and (max-width: 1055px){.columns.is-variable.is-1-tablet-only{--columnGap: .25rem}}@media screen and (max-width: 1055px){.columns.is-variable.is-1-touch{--columnGap: .25rem}}@media screen and (min-width: 1056px){.columns.is-variable.is-1-desktop{--columnGap: .25rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){.columns.is-variable.is-1-desktop-only{--columnGap: .25rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-1-widescreen{--columnGap: .25rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){.columns.is-variable.is-1-widescreen-only{--columnGap: .25rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-1-fullhd{--columnGap: .25rem}}.columns.is-variable.is-2{--columnGap: .5rem}@media screen and (max-width: 768px){.columns.is-variable.is-2-mobile{--columnGap: .5rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-2-tablet{--columnGap: .5rem}}@media screen and (min-width: 769px) and (max-width: 1055px){.columns.is-variable.is-2-tablet-only{--columnGap: .5rem}}@media screen and (max-width: 1055px){.columns.is-variable.is-2-touch{--columnGap: .5rem}}@media screen and (min-width: 1056px){.columns.is-variable.is-2-desktop{--columnGap: .5rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){.columns.is-variable.is-2-desktop-only{--columnGap: .5rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-2-widescreen{--columnGap: .5rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){.columns.is-variable.is-2-widescreen-only{--columnGap: .5rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-2-fullhd{--columnGap: .5rem}}.columns.is-variable.is-3{--columnGap: .75rem}@media screen and (max-width: 768px){.columns.is-variable.is-3-mobile{--columnGap: .75rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-3-tablet{--columnGap: .75rem}}@media screen and (min-width: 769px) and (max-width: 1055px){.columns.is-variable.is-3-tablet-only{--columnGap: .75rem}}@media screen and (max-width: 1055px){.columns.is-variable.is-3-touch{--columnGap: .75rem}}@media screen and (min-width: 1056px){.columns.is-variable.is-3-desktop{--columnGap: .75rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){.columns.is-variable.is-3-desktop-only{--columnGap: .75rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-3-widescreen{--columnGap: .75rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){.columns.is-variable.is-3-widescreen-only{--columnGap: .75rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-3-fullhd{--columnGap: .75rem}}.columns.is-variable.is-4{--columnGap: 1rem}@media screen and (max-width: 768px){.columns.is-variable.is-4-mobile{--columnGap: 1rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-4-tablet{--columnGap: 1rem}}@media screen and (min-width: 769px) and (max-width: 1055px){.columns.is-variable.is-4-tablet-only{--columnGap: 1rem}}@media screen and (max-width: 1055px){.columns.is-variable.is-4-touch{--columnGap: 1rem}}@media screen and (min-width: 1056px){.columns.is-variable.is-4-desktop{--columnGap: 1rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){.columns.is-variable.is-4-desktop-only{--columnGap: 1rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-4-widescreen{--columnGap: 1rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){.columns.is-variable.is-4-widescreen-only{--columnGap: 1rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-4-fullhd{--columnGap: 1rem}}.columns.is-variable.is-5{--columnGap: 1.25rem}@media screen and (max-width: 768px){.columns.is-variable.is-5-mobile{--columnGap: 1.25rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-5-tablet{--columnGap: 1.25rem}}@media screen and (min-width: 769px) and (max-width: 1055px){.columns.is-variable.is-5-tablet-only{--columnGap: 1.25rem}}@media screen and (max-width: 1055px){.columns.is-variable.is-5-touch{--columnGap: 1.25rem}}@media screen and (min-width: 1056px){.columns.is-variable.is-5-desktop{--columnGap: 1.25rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){.columns.is-variable.is-5-desktop-only{--columnGap: 1.25rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-5-widescreen{--columnGap: 1.25rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){.columns.is-variable.is-5-widescreen-only{--columnGap: 1.25rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-5-fullhd{--columnGap: 1.25rem}}.columns.is-variable.is-6{--columnGap: 1.5rem}@media screen and (max-width: 768px){.columns.is-variable.is-6-mobile{--columnGap: 1.5rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-6-tablet{--columnGap: 1.5rem}}@media screen and (min-width: 769px) and (max-width: 1055px){.columns.is-variable.is-6-tablet-only{--columnGap: 1.5rem}}@media screen and (max-width: 1055px){.columns.is-variable.is-6-touch{--columnGap: 1.5rem}}@media screen and (min-width: 1056px){.columns.is-variable.is-6-desktop{--columnGap: 1.5rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){.columns.is-variable.is-6-desktop-only{--columnGap: 1.5rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-6-widescreen{--columnGap: 1.5rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){.columns.is-variable.is-6-widescreen-only{--columnGap: 1.5rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-6-fullhd{--columnGap: 1.5rem}}.columns.is-variable.is-7{--columnGap: 1.75rem}@media screen and (max-width: 768px){.columns.is-variable.is-7-mobile{--columnGap: 1.75rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-7-tablet{--columnGap: 1.75rem}}@media screen and (min-width: 769px) and (max-width: 1055px){.columns.is-variable.is-7-tablet-only{--columnGap: 1.75rem}}@media screen and (max-width: 1055px){.columns.is-variable.is-7-touch{--columnGap: 1.75rem}}@media screen and (min-width: 1056px){.columns.is-variable.is-7-desktop{--columnGap: 1.75rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){.columns.is-variable.is-7-desktop-only{--columnGap: 1.75rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-7-widescreen{--columnGap: 1.75rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){.columns.is-variable.is-7-widescreen-only{--columnGap: 1.75rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-7-fullhd{--columnGap: 1.75rem}}.columns.is-variable.is-8{--columnGap: 2rem}@media screen and (max-width: 768px){.columns.is-variable.is-8-mobile{--columnGap: 2rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-8-tablet{--columnGap: 2rem}}@media screen and (min-width: 769px) and (max-width: 1055px){.columns.is-variable.is-8-tablet-only{--columnGap: 2rem}}@media screen and (max-width: 1055px){.columns.is-variable.is-8-touch{--columnGap: 2rem}}@media screen and (min-width: 1056px){.columns.is-variable.is-8-desktop{--columnGap: 2rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){.columns.is-variable.is-8-desktop-only{--columnGap: 2rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-8-widescreen{--columnGap: 2rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){.columns.is-variable.is-8-widescreen-only{--columnGap: 2rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-8-fullhd{--columnGap: 2rem}}.tile{align-items:stretch;display:block;flex-basis:0;flex-grow:1;flex-shrink:1;min-height:min-content}.tile.is-ancestor{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}.tile.is-ancestor:last-child{margin-bottom:-.75rem}.tile.is-ancestor:not(:last-child){margin-bottom:.75rem}.tile.is-child{margin:0 !important}.tile.is-parent{padding:.75rem}.tile.is-vertical{flex-direction:column}.tile.is-vertical>.tile.is-child:not(:last-child){margin-bottom:1.5rem !important}@media screen and (min-width: 769px),print{.tile:not(.is-child){display:flex}.tile.is-1{flex:none;width:8.33333337%}.tile.is-2{flex:none;width:16.66666674%}.tile.is-3{flex:none;width:25%}.tile.is-4{flex:none;width:33.33333337%}.tile.is-5{flex:none;width:41.66666674%}.tile.is-6{flex:none;width:50%}.tile.is-7{flex:none;width:58.33333337%}.tile.is-8{flex:none;width:66.66666674%}.tile.is-9{flex:none;width:75%}.tile.is-10{flex:none;width:83.33333337%}.tile.is-11{flex:none;width:91.66666674%}.tile.is-12{flex:none;width:100%}}.hero{align-items:stretch;display:flex;flex-direction:column;justify-content:space-between}.hero .navbar{background:none}.hero .tabs ul{border-bottom:none}.hero.is-white{background-color:#fff;color:#0a0a0a}.hero.is-white a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-white strong{color:inherit}.hero.is-white .title{color:#0a0a0a}.hero.is-white .subtitle{color:rgba(10,10,10,0.9)}.hero.is-white .subtitle a:not(.button),.hero.is-white .subtitle strong{color:#0a0a0a}@media screen and (max-width: 1055px){.hero.is-white .navbar-menu{background-color:#fff}}.hero.is-white .navbar-item,.hero.is-white .navbar-link{color:rgba(10,10,10,0.7)}.hero.is-white a.navbar-item:hover,.hero.is-white a.navbar-item.is-active,.hero.is-white .navbar-link:hover,.hero.is-white .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}.hero.is-white .tabs a{color:#0a0a0a;opacity:0.9}.hero.is-white .tabs a:hover{opacity:1}.hero.is-white .tabs li.is-active a{color:#fff !important;opacity:1}.hero.is-white .tabs.is-boxed a,.hero.is-white .tabs.is-toggle a{color:#0a0a0a}.hero.is-white .tabs.is-boxed a:hover,.hero.is-white .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-white .tabs.is-boxed li.is-active a,.hero.is-white .tabs.is-boxed li.is-active a:hover,.hero.is-white .tabs.is-toggle li.is-active a,.hero.is-white .tabs.is-toggle li.is-active a:hover{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}.hero.is-white.is-bold{background-image:linear-gradient(141deg, #e8e3e4 0%, #fff 71%, #fff 100%)}@media screen and (max-width: 768px){.hero.is-white.is-bold .navbar-menu{background-image:linear-gradient(141deg, #e8e3e4 0%, #fff 71%, #fff 100%)}}.hero.is-black{background-color:#0a0a0a;color:#fff}.hero.is-black a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-black strong{color:inherit}.hero.is-black .title{color:#fff}.hero.is-black .subtitle{color:rgba(255,255,255,0.9)}.hero.is-black .subtitle a:not(.button),.hero.is-black .subtitle strong{color:#fff}@media screen and (max-width: 1055px){.hero.is-black .navbar-menu{background-color:#0a0a0a}}.hero.is-black .navbar-item,.hero.is-black .navbar-link{color:rgba(255,255,255,0.7)}.hero.is-black a.navbar-item:hover,.hero.is-black a.navbar-item.is-active,.hero.is-black .navbar-link:hover,.hero.is-black .navbar-link.is-active{background-color:#000;color:#fff}.hero.is-black .tabs a{color:#fff;opacity:0.9}.hero.is-black .tabs a:hover{opacity:1}.hero.is-black .tabs li.is-active a{color:#0a0a0a !important;opacity:1}.hero.is-black .tabs.is-boxed a,.hero.is-black .tabs.is-toggle a{color:#fff}.hero.is-black .tabs.is-boxed a:hover,.hero.is-black .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-black .tabs.is-boxed li.is-active a,.hero.is-black .tabs.is-boxed li.is-active a:hover,.hero.is-black .tabs.is-toggle li.is-active a,.hero.is-black .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#0a0a0a}.hero.is-black.is-bold{background-image:linear-gradient(141deg, #000 0%, #0a0a0a 71%, #181616 100%)}@media screen and (max-width: 768px){.hero.is-black.is-bold .navbar-menu{background-image:linear-gradient(141deg, #000 0%, #0a0a0a 71%, #181616 100%)}}.hero.is-light{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}.hero.is-light a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-light strong{color:inherit}.hero.is-light .title{color:rgba(0,0,0,0.7)}.hero.is-light .subtitle{color:rgba(0,0,0,0.9)}.hero.is-light .subtitle a:not(.button),.hero.is-light .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1055px){.hero.is-light .navbar-menu{background-color:#f5f5f5}}.hero.is-light .navbar-item,.hero.is-light .navbar-link{color:rgba(0,0,0,0.7)}.hero.is-light a.navbar-item:hover,.hero.is-light a.navbar-item.is-active,.hero.is-light .navbar-link:hover,.hero.is-light .navbar-link.is-active{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}.hero.is-light .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}.hero.is-light .tabs a:hover{opacity:1}.hero.is-light .tabs li.is-active a{color:#f5f5f5 !important;opacity:1}.hero.is-light .tabs.is-boxed a,.hero.is-light .tabs.is-toggle a{color:rgba(0,0,0,0.7)}.hero.is-light .tabs.is-boxed a:hover,.hero.is-light .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-light .tabs.is-boxed li.is-active a,.hero.is-light .tabs.is-boxed li.is-active a:hover,.hero.is-light .tabs.is-toggle li.is-active a,.hero.is-light .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#f5f5f5}.hero.is-light.is-bold{background-image:linear-gradient(141deg, #dfd8d9 0%, #f5f5f5 71%, #fff 100%)}@media screen and (max-width: 768px){.hero.is-light.is-bold .navbar-menu{background-image:linear-gradient(141deg, #dfd8d9 0%, #f5f5f5 71%, #fff 100%)}}.hero.is-dark,.content kbd.hero{background-color:#363636;color:#fff}.hero.is-dark a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.content kbd.hero a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-dark strong,.content kbd.hero strong{color:inherit}.hero.is-dark .title,.content kbd.hero .title{color:#fff}.hero.is-dark .subtitle,.content kbd.hero .subtitle{color:rgba(255,255,255,0.9)}.hero.is-dark .subtitle a:not(.button),.content kbd.hero .subtitle a:not(.button),.hero.is-dark .subtitle strong,.content kbd.hero .subtitle strong{color:#fff}@media screen and (max-width: 1055px){.hero.is-dark .navbar-menu,.content kbd.hero .navbar-menu{background-color:#363636}}.hero.is-dark .navbar-item,.content kbd.hero .navbar-item,.hero.is-dark .navbar-link,.content kbd.hero .navbar-link{color:rgba(255,255,255,0.7)}.hero.is-dark a.navbar-item:hover,.content kbd.hero a.navbar-item:hover,.hero.is-dark a.navbar-item.is-active,.content kbd.hero a.navbar-item.is-active,.hero.is-dark .navbar-link:hover,.content kbd.hero .navbar-link:hover,.hero.is-dark .navbar-link.is-active,.content kbd.hero .navbar-link.is-active{background-color:#292929;color:#fff}.hero.is-dark .tabs a,.content kbd.hero .tabs a{color:#fff;opacity:0.9}.hero.is-dark .tabs a:hover,.content kbd.hero .tabs a:hover{opacity:1}.hero.is-dark .tabs li.is-active a,.content kbd.hero .tabs li.is-active a{color:#363636 !important;opacity:1}.hero.is-dark .tabs.is-boxed a,.content kbd.hero .tabs.is-boxed a,.hero.is-dark .tabs.is-toggle a,.content kbd.hero .tabs.is-toggle a{color:#fff}.hero.is-dark .tabs.is-boxed a:hover,.content kbd.hero .tabs.is-boxed a:hover,.hero.is-dark .tabs.is-toggle a:hover,.content kbd.hero .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-dark .tabs.is-boxed li.is-active a,.content kbd.hero .tabs.is-boxed li.is-active a,.hero.is-dark .tabs.is-boxed li.is-active a:hover,.hero.is-dark .tabs.is-toggle li.is-active a,.content kbd.hero .tabs.is-toggle li.is-active a,.hero.is-dark .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#363636}.hero.is-dark.is-bold,.content kbd.hero.is-bold{background-image:linear-gradient(141deg, #1f191a 0%, #363636 71%, #46403f 100%)}@media screen and (max-width: 768px){.hero.is-dark.is-bold .navbar-menu,.content kbd.hero.is-bold .navbar-menu{background-image:linear-gradient(141deg, #1f191a 0%, #363636 71%, #46403f 100%)}}.hero.is-primary,.docstring>section>a.hero.docs-sourcelink{background-color:#4eb5de;color:#fff}.hero.is-primary a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.docstring>section>a.hero.docs-sourcelink a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-primary strong,.docstring>section>a.hero.docs-sourcelink strong{color:inherit}.hero.is-primary .title,.docstring>section>a.hero.docs-sourcelink .title{color:#fff}.hero.is-primary .subtitle,.docstring>section>a.hero.docs-sourcelink .subtitle{color:rgba(255,255,255,0.9)}.hero.is-primary .subtitle a:not(.button),.docstring>section>a.hero.docs-sourcelink .subtitle a:not(.button),.hero.is-primary .subtitle strong,.docstring>section>a.hero.docs-sourcelink .subtitle strong{color:#fff}@media screen and (max-width: 1055px){.hero.is-primary .navbar-menu,.docstring>section>a.hero.docs-sourcelink .navbar-menu{background-color:#4eb5de}}.hero.is-primary .navbar-item,.docstring>section>a.hero.docs-sourcelink .navbar-item,.hero.is-primary .navbar-link,.docstring>section>a.hero.docs-sourcelink .navbar-link{color:rgba(255,255,255,0.7)}.hero.is-primary a.navbar-item:hover,.docstring>section>a.hero.docs-sourcelink a.navbar-item:hover,.hero.is-primary a.navbar-item.is-active,.docstring>section>a.hero.docs-sourcelink a.navbar-item.is-active,.hero.is-primary .navbar-link:hover,.docstring>section>a.hero.docs-sourcelink .navbar-link:hover,.hero.is-primary .navbar-link.is-active,.docstring>section>a.hero.docs-sourcelink .navbar-link.is-active{background-color:#39acda;color:#fff}.hero.is-primary .tabs a,.docstring>section>a.hero.docs-sourcelink .tabs a{color:#fff;opacity:0.9}.hero.is-primary .tabs a:hover,.docstring>section>a.hero.docs-sourcelink .tabs a:hover{opacity:1}.hero.is-primary .tabs li.is-active a,.docstring>section>a.hero.docs-sourcelink .tabs li.is-active a{color:#4eb5de !important;opacity:1}.hero.is-primary .tabs.is-boxed a,.docstring>section>a.hero.docs-sourcelink .tabs.is-boxed a,.hero.is-primary .tabs.is-toggle a,.docstring>section>a.hero.docs-sourcelink .tabs.is-toggle a{color:#fff}.hero.is-primary .tabs.is-boxed a:hover,.docstring>section>a.hero.docs-sourcelink .tabs.is-boxed a:hover,.hero.is-primary .tabs.is-toggle a:hover,.docstring>section>a.hero.docs-sourcelink .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-primary .tabs.is-boxed li.is-active a,.docstring>section>a.hero.docs-sourcelink .tabs.is-boxed li.is-active a,.hero.is-primary .tabs.is-boxed li.is-active a:hover,.hero.is-primary .tabs.is-toggle li.is-active a,.docstring>section>a.hero.docs-sourcelink .tabs.is-toggle li.is-active a,.hero.is-primary .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#4eb5de}.hero.is-primary.is-bold,.docstring>section>a.hero.is-bold.docs-sourcelink{background-image:linear-gradient(141deg, #1bc7de 0%, #4eb5de 71%, #5fa9e7 100%)}@media screen and (max-width: 768px){.hero.is-primary.is-bold .navbar-menu,.docstring>section>a.hero.is-bold.docs-sourcelink .navbar-menu{background-image:linear-gradient(141deg, #1bc7de 0%, #4eb5de 71%, #5fa9e7 100%)}}.hero.is-link{background-color:#2e63b8;color:#fff}.hero.is-link a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-link strong{color:inherit}.hero.is-link .title{color:#fff}.hero.is-link .subtitle{color:rgba(255,255,255,0.9)}.hero.is-link .subtitle a:not(.button),.hero.is-link .subtitle strong{color:#fff}@media screen and (max-width: 1055px){.hero.is-link .navbar-menu{background-color:#2e63b8}}.hero.is-link .navbar-item,.hero.is-link .navbar-link{color:rgba(255,255,255,0.7)}.hero.is-link a.navbar-item:hover,.hero.is-link a.navbar-item.is-active,.hero.is-link .navbar-link:hover,.hero.is-link .navbar-link.is-active{background-color:#2958a4;color:#fff}.hero.is-link .tabs a{color:#fff;opacity:0.9}.hero.is-link .tabs a:hover{opacity:1}.hero.is-link .tabs li.is-active a{color:#2e63b8 !important;opacity:1}.hero.is-link .tabs.is-boxed a,.hero.is-link .tabs.is-toggle a{color:#fff}.hero.is-link .tabs.is-boxed a:hover,.hero.is-link .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-link .tabs.is-boxed li.is-active a,.hero.is-link .tabs.is-boxed li.is-active a:hover,.hero.is-link .tabs.is-toggle li.is-active a,.hero.is-link .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#2e63b8}.hero.is-link.is-bold{background-image:linear-gradient(141deg, #1b6098 0%, #2e63b8 71%, #2d51d2 100%)}@media screen and (max-width: 768px){.hero.is-link.is-bold .navbar-menu{background-image:linear-gradient(141deg, #1b6098 0%, #2e63b8 71%, #2d51d2 100%)}}.hero.is-info{background-color:#209cee;color:#fff}.hero.is-info a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-info strong{color:inherit}.hero.is-info .title{color:#fff}.hero.is-info .subtitle{color:rgba(255,255,255,0.9)}.hero.is-info .subtitle a:not(.button),.hero.is-info .subtitle strong{color:#fff}@media screen and (max-width: 1055px){.hero.is-info .navbar-menu{background-color:#209cee}}.hero.is-info .navbar-item,.hero.is-info .navbar-link{color:rgba(255,255,255,0.7)}.hero.is-info a.navbar-item:hover,.hero.is-info a.navbar-item.is-active,.hero.is-info .navbar-link:hover,.hero.is-info .navbar-link.is-active{background-color:#1190e3;color:#fff}.hero.is-info .tabs a{color:#fff;opacity:0.9}.hero.is-info .tabs a:hover{opacity:1}.hero.is-info .tabs li.is-active a{color:#209cee !important;opacity:1}.hero.is-info .tabs.is-boxed a,.hero.is-info .tabs.is-toggle a{color:#fff}.hero.is-info .tabs.is-boxed a:hover,.hero.is-info .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-info .tabs.is-boxed li.is-active a,.hero.is-info .tabs.is-boxed li.is-active a:hover,.hero.is-info .tabs.is-toggle li.is-active a,.hero.is-info .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#209cee}.hero.is-info.is-bold{background-image:linear-gradient(141deg, #05a6d6 0%, #209cee 71%, #3287f5 100%)}@media screen and (max-width: 768px){.hero.is-info.is-bold .navbar-menu{background-image:linear-gradient(141deg, #05a6d6 0%, #209cee 71%, #3287f5 100%)}}.hero.is-success{background-color:#22c35b;color:#fff}.hero.is-success a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-success strong{color:inherit}.hero.is-success .title{color:#fff}.hero.is-success .subtitle{color:rgba(255,255,255,0.9)}.hero.is-success .subtitle a:not(.button),.hero.is-success .subtitle strong{color:#fff}@media screen and (max-width: 1055px){.hero.is-success .navbar-menu{background-color:#22c35b}}.hero.is-success .navbar-item,.hero.is-success .navbar-link{color:rgba(255,255,255,0.7)}.hero.is-success a.navbar-item:hover,.hero.is-success a.navbar-item.is-active,.hero.is-success .navbar-link:hover,.hero.is-success .navbar-link.is-active{background-color:#1ead51;color:#fff}.hero.is-success .tabs a{color:#fff;opacity:0.9}.hero.is-success .tabs a:hover{opacity:1}.hero.is-success .tabs li.is-active a{color:#22c35b !important;opacity:1}.hero.is-success .tabs.is-boxed a,.hero.is-success .tabs.is-toggle a{color:#fff}.hero.is-success .tabs.is-boxed a:hover,.hero.is-success .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-success .tabs.is-boxed li.is-active a,.hero.is-success .tabs.is-boxed li.is-active a:hover,.hero.is-success .tabs.is-toggle li.is-active a,.hero.is-success .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#22c35b}.hero.is-success.is-bold{background-image:linear-gradient(141deg, #12a02c 0%, #22c35b 71%, #1fdf83 100%)}@media screen and (max-width: 768px){.hero.is-success.is-bold .navbar-menu{background-image:linear-gradient(141deg, #12a02c 0%, #22c35b 71%, #1fdf83 100%)}}.hero.is-warning{background-color:#ffdd57;color:rgba(0,0,0,0.7)}.hero.is-warning a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-warning strong{color:inherit}.hero.is-warning .title{color:rgba(0,0,0,0.7)}.hero.is-warning .subtitle{color:rgba(0,0,0,0.9)}.hero.is-warning .subtitle a:not(.button),.hero.is-warning .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1055px){.hero.is-warning .navbar-menu{background-color:#ffdd57}}.hero.is-warning .navbar-item,.hero.is-warning .navbar-link{color:rgba(0,0,0,0.7)}.hero.is-warning a.navbar-item:hover,.hero.is-warning a.navbar-item.is-active,.hero.is-warning .navbar-link:hover,.hero.is-warning .navbar-link.is-active{background-color:#ffd83e;color:rgba(0,0,0,0.7)}.hero.is-warning .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}.hero.is-warning .tabs a:hover{opacity:1}.hero.is-warning .tabs li.is-active a{color:#ffdd57 !important;opacity:1}.hero.is-warning .tabs.is-boxed a,.hero.is-warning .tabs.is-toggle a{color:rgba(0,0,0,0.7)}.hero.is-warning .tabs.is-boxed a:hover,.hero.is-warning .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-warning .tabs.is-boxed li.is-active a,.hero.is-warning .tabs.is-boxed li.is-active a:hover,.hero.is-warning .tabs.is-toggle li.is-active a,.hero.is-warning .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#ffdd57}.hero.is-warning.is-bold{background-image:linear-gradient(141deg, #ffae24 0%, #ffdd57 71%, #fffa71 100%)}@media screen and (max-width: 768px){.hero.is-warning.is-bold .navbar-menu{background-image:linear-gradient(141deg, #ffae24 0%, #ffdd57 71%, #fffa71 100%)}}.hero.is-danger{background-color:#da0b00;color:#fff}.hero.is-danger a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-danger strong{color:inherit}.hero.is-danger .title{color:#fff}.hero.is-danger .subtitle{color:rgba(255,255,255,0.9)}.hero.is-danger .subtitle a:not(.button),.hero.is-danger .subtitle strong{color:#fff}@media screen and (max-width: 1055px){.hero.is-danger .navbar-menu{background-color:#da0b00}}.hero.is-danger .navbar-item,.hero.is-danger .navbar-link{color:rgba(255,255,255,0.7)}.hero.is-danger a.navbar-item:hover,.hero.is-danger a.navbar-item.is-active,.hero.is-danger .navbar-link:hover,.hero.is-danger .navbar-link.is-active{background-color:#c10a00;color:#fff}.hero.is-danger .tabs a{color:#fff;opacity:0.9}.hero.is-danger .tabs a:hover{opacity:1}.hero.is-danger .tabs li.is-active a{color:#da0b00 !important;opacity:1}.hero.is-danger .tabs.is-boxed a,.hero.is-danger .tabs.is-toggle a{color:#fff}.hero.is-danger .tabs.is-boxed a:hover,.hero.is-danger .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-danger .tabs.is-boxed li.is-active a,.hero.is-danger .tabs.is-boxed li.is-active a:hover,.hero.is-danger .tabs.is-toggle li.is-active a,.hero.is-danger .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#da0b00}.hero.is-danger.is-bold{background-image:linear-gradient(141deg, #a70013 0%, #da0b00 71%, #f43500 100%)}@media screen and (max-width: 768px){.hero.is-danger.is-bold .navbar-menu{background-image:linear-gradient(141deg, #a70013 0%, #da0b00 71%, #f43500 100%)}}.hero.is-small .hero-body,#documenter .docs-sidebar form.docs-search>input.hero .hero-body{padding:1.5rem}@media screen and (min-width: 769px),print{.hero.is-medium .hero-body{padding:9rem 4.5rem}}@media screen and (min-width: 769px),print{.hero.is-large .hero-body{padding:18rem 6rem}}.hero.is-halfheight .hero-body,.hero.is-fullheight .hero-body,.hero.is-fullheight-with-navbar .hero-body{align-items:center;display:flex}.hero.is-halfheight .hero-body>.container,.hero.is-fullheight .hero-body>.container,.hero.is-fullheight-with-navbar .hero-body>.container{flex-grow:1;flex-shrink:1}.hero.is-halfheight{min-height:50vh}.hero.is-fullheight{min-height:100vh}.hero-video{overflow:hidden}.hero-video video{left:50%;min-height:100%;min-width:100%;position:absolute;top:50%;transform:translate3d(-50%, -50%, 0)}.hero-video.is-transparent{opacity:0.3}@media screen and (max-width: 768px){.hero-video{display:none}}.hero-buttons{margin-top:1.5rem}@media screen and (max-width: 768px){.hero-buttons .button{display:flex}.hero-buttons .button:not(:last-child){margin-bottom:0.75rem}}@media screen and (min-width: 769px),print{.hero-buttons{display:flex;justify-content:center}.hero-buttons .button:not(:last-child){margin-right:1.5rem}}.hero-head,.hero-foot{flex-grow:0;flex-shrink:0}.hero-body{flex-grow:1;flex-shrink:0;padding:3rem 1.5rem}@media screen and (min-width: 769px),print{.hero-body{padding:3rem 3rem}}.section{padding:3rem 1.5rem}@media screen and (min-width: 1056px){.section{padding:3rem 3rem}.section.is-medium{padding:9rem 4.5rem}.section.is-large{padding:18rem 6rem}}.footer{background-color:#fafafa;padding:3rem 1.5rem 6rem}h1 .docs-heading-anchor,h1 .docs-heading-anchor:hover,h1 .docs-heading-anchor:visited,h2 .docs-heading-anchor,h2 .docs-heading-anchor:hover,h2 .docs-heading-anchor:visited,h3 .docs-heading-anchor,h3 .docs-heading-anchor:hover,h3 .docs-heading-anchor:visited,h4 .docs-heading-anchor,h4 .docs-heading-anchor:hover,h4 .docs-heading-anchor:visited,h5 .docs-heading-anchor,h5 .docs-heading-anchor:hover,h5 .docs-heading-anchor:visited,h6 .docs-heading-anchor,h6 .docs-heading-anchor:hover,h6 .docs-heading-anchor:visited{color:#222}h1 .docs-heading-anchor-permalink,h2 .docs-heading-anchor-permalink,h3 .docs-heading-anchor-permalink,h4 .docs-heading-anchor-permalink,h5 .docs-heading-anchor-permalink,h6 .docs-heading-anchor-permalink{visibility:hidden;vertical-align:middle;margin-left:0.5em;font-size:0.7rem}h1 .docs-heading-anchor-permalink::before,h2 .docs-heading-anchor-permalink::before,h3 .docs-heading-anchor-permalink::before,h4 .docs-heading-anchor-permalink::before,h5 .docs-heading-anchor-permalink::before,h6 .docs-heading-anchor-permalink::before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f0c1"}h1:hover .docs-heading-anchor-permalink,h2:hover .docs-heading-anchor-permalink,h3:hover .docs-heading-anchor-permalink,h4:hover .docs-heading-anchor-permalink,h5:hover .docs-heading-anchor-permalink,h6:hover .docs-heading-anchor-permalink{visibility:visible}.docs-dark-only{display:none !important}pre{position:relative;overflow:hidden}pre code,pre code.hljs{padding:0 .75rem !important;overflow:auto;display:block}pre code:first-of-type,pre code.hljs:first-of-type{padding-top:0.5rem !important}pre code:last-of-type,pre code.hljs:last-of-type{padding-bottom:0.5rem !important}pre .copy-button{opacity:0.2;transition:opacity 0.2s;position:absolute;right:0em;top:0em;padding:0.5em;width:2.5em;height:2.5em;background:transparent;border:none;font-family:"Font Awesome 6 Free";color:#222;cursor:pointer;text-align:center}pre .copy-button:focus,pre .copy-button:hover{opacity:1;background:rgba(34,34,34,0.1);color:#2e63b8}pre .copy-button.success{color:#259a12;opacity:1}pre .copy-button.error{color:#cb3c33;opacity:1}pre:hover .copy-button{opacity:1}.admonition{background-color:#b5b5b5;border-style:solid;border-width:1px;border-color:#363636;border-radius:4px;font-size:1rem}.admonition strong{color:currentColor}.admonition.is-small,#documenter .docs-sidebar form.docs-search>input.admonition{font-size:.75rem}.admonition.is-medium{font-size:1.25rem}.admonition.is-large{font-size:1.5rem}.admonition.is-default{background-color:#b5b5b5;border-color:#363636}.admonition.is-default>.admonition-header{background-color:#363636;color:#fff}.admonition.is-default>.admonition-body{color:#fff}.admonition.is-info{background-color:#def0fc;border-color:#209cee}.admonition.is-info>.admonition-header{background-color:#209cee;color:#fff}.admonition.is-info>.admonition-body{color:rgba(0,0,0,0.7)}.admonition.is-success{background-color:#bdf4d1;border-color:#22c35b}.admonition.is-success>.admonition-header{background-color:#22c35b;color:#fff}.admonition.is-success>.admonition-body{color:rgba(0,0,0,0.7)}.admonition.is-warning{background-color:#fff3c5;border-color:#ffdd57}.admonition.is-warning>.admonition-header{background-color:#ffdd57;color:rgba(0,0,0,0.7)}.admonition.is-warning>.admonition-body{color:rgba(0,0,0,0.7)}.admonition.is-danger{background-color:#ffaba7;border-color:#da0b00}.admonition.is-danger>.admonition-header{background-color:#da0b00;color:#fff}.admonition.is-danger>.admonition-body{color:rgba(0,0,0,0.7)}.admonition.is-compat{background-color:#bdeff5;border-color:#1db5c9}.admonition.is-compat>.admonition-header{background-color:#1db5c9;color:#fff}.admonition.is-compat>.admonition-body{color:rgba(0,0,0,0.7)}.admonition-header{color:#fff;background-color:#363636;align-items:center;font-weight:700;justify-content:space-between;line-height:1.25;padding:0.5rem .75rem;position:relative}.admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;margin-right:.75rem;content:"\f06a"}details.admonition.is-details>.admonition-header{list-style:none}details.admonition.is-details>.admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f055"}details.admonition.is-details[open]>.admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f056"}.admonition-body{color:#222;padding:0.5rem .75rem}.admonition-body pre{background-color:#f5f5f5}.admonition-body code{background-color:rgba(0,0,0,0.05)}.docstring{margin-bottom:1em;background-color:rgba(0,0,0,0);border:1px solid #dbdbdb;box-shadow:2px 2px 3px rgba(10,10,10,0.1);max-width:100%}.docstring>header{cursor:pointer;display:flex;flex-grow:1;align-items:stretch;padding:0.5rem .75rem;background-color:#f5f5f5;box-shadow:0 0.125em 0.25em rgba(10,10,10,0.1);box-shadow:none;border-bottom:1px solid #dbdbdb}.docstring>header code{background-color:transparent}.docstring>header .docstring-article-toggle-button{min-width:1.1rem;padding:0.2rem 0.2rem 0.2rem 0}.docstring>header .docstring-binding{margin-right:0.3em}.docstring>header .docstring-category{margin-left:0.3em}.docstring>section{position:relative;padding:.75rem .75rem;border-bottom:1px solid #dbdbdb}.docstring>section:last-child{border-bottom:none}.docstring>section>a.docs-sourcelink{transition:opacity 0.3s;opacity:0;position:absolute;right:.375rem;bottom:.375rem}.docstring>section>a.docs-sourcelink:focus{opacity:1 !important}.docstring:hover>section>a.docs-sourcelink{opacity:0.2}.docstring:focus-within>section>a.docs-sourcelink{opacity:0.2}.docstring>section:hover a.docs-sourcelink{opacity:1}.documenter-example-output{background-color:#fff}.outdated-warning-overlay{position:fixed;top:0;left:0;right:0;box-shadow:0 0 10px rgba(0,0,0,0.3);z-index:999;background-color:#ffaba7;color:rgba(0,0,0,0.7);border-bottom:3px solid #da0b00;padding:10px 35px;text-align:center;font-size:15px}.outdated-warning-overlay .outdated-warning-closer{position:absolute;top:calc(50% - 10px);right:18px;cursor:pointer;width:12px}.outdated-warning-overlay a{color:#2e63b8}.outdated-warning-overlay a:hover{color:#363636}.content pre{border:1px solid #dbdbdb}.content code{font-weight:inherit}.content a code{color:#2e63b8}.content h1 code,.content h2 code,.content h3 code,.content h4 code,.content h5 code,.content h6 code{color:#222}.content table{display:block;width:initial;max-width:100%;overflow-x:auto}.content blockquote>ul:first-child,.content blockquote>ol:first-child,.content .admonition-body>ul:first-child,.content .admonition-body>ol:first-child{margin-top:0}pre,code{font-variant-ligatures:no-contextual}.breadcrumb a.is-disabled{cursor:default;pointer-events:none}.breadcrumb a.is-disabled,.breadcrumb a.is-disabled:hover{color:#222}.hljs{background:initial !important}.katex .katex-mathml{top:0;right:0}.katex-display,mjx-container,.MathJax_Display{margin:0.5em 0 !important}html{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto}li.no-marker{list-style:none}#documenter .docs-main>article{overflow-wrap:break-word}#documenter .docs-main>article .math-container{overflow-x:auto;overflow-y:hidden}@media screen and (min-width: 1056px){#documenter .docs-main{max-width:52rem;margin-left:20rem;padding-right:1rem}}@media screen and (max-width: 1055px){#documenter .docs-main{width:100%}#documenter .docs-main>article{max-width:52rem;margin-left:auto;margin-right:auto;margin-bottom:1rem;padding:0 1rem}#documenter .docs-main>header,#documenter .docs-main>nav{max-width:100%;width:100%;margin:0}}#documenter .docs-main header.docs-navbar{background-color:#fff;border-bottom:1px solid #dbdbdb;z-index:2;min-height:4rem;margin-bottom:1rem;display:flex}#documenter .docs-main header.docs-navbar .breadcrumb{flex-grow:1}#documenter .docs-main header.docs-navbar .docs-sidebar-button{display:block;font-size:1.5rem;padding-bottom:0.1rem;margin-right:1rem}#documenter .docs-main header.docs-navbar .docs-right{display:flex;white-space:nowrap;gap:1rem;align-items:center}#documenter .docs-main header.docs-navbar .docs-right .docs-icon,#documenter .docs-main header.docs-navbar .docs-right .docs-label{display:inline-block}#documenter .docs-main header.docs-navbar .docs-right .docs-label{padding:0;margin-left:0.3em}@media screen and (max-width: 1055px){#documenter .docs-main header.docs-navbar .docs-right .docs-navbar-link{margin-left:0.4rem;margin-right:0.4rem}}#documenter .docs-main header.docs-navbar>*{margin:auto 0}@media screen and (max-width: 1055px){#documenter .docs-main header.docs-navbar{position:sticky;top:0;padding:0 1rem;transition-property:top, box-shadow;-webkit-transition-property:top, box-shadow;transition-duration:0.3s;-webkit-transition-duration:0.3s}#documenter .docs-main header.docs-navbar.headroom--not-top{box-shadow:.2rem 0rem .4rem #bbb;transition-duration:0.7s;-webkit-transition-duration:0.7s}#documenter .docs-main header.docs-navbar.headroom--unpinned.headroom--not-top.headroom--not-bottom{top:-4.5rem;transition-duration:0.7s;-webkit-transition-duration:0.7s}}#documenter .docs-main section.footnotes{border-top:1px solid #dbdbdb}#documenter .docs-main section.footnotes li .tag:first-child,#documenter .docs-main section.footnotes li .docstring>section>a.docs-sourcelink:first-child,#documenter .docs-main section.footnotes li .content kbd:first-child,.content #documenter .docs-main section.footnotes li kbd:first-child{margin-right:1em;margin-bottom:0.4em}#documenter .docs-main .docs-footer{display:flex;flex-wrap:wrap;margin-left:0;margin-right:0;border-top:1px solid #dbdbdb;padding-top:1rem;padding-bottom:1rem}@media screen and (max-width: 1055px){#documenter .docs-main .docs-footer{padding-left:1rem;padding-right:1rem}}#documenter .docs-main .docs-footer .docs-footer-nextpage,#documenter .docs-main .docs-footer .docs-footer-prevpage{flex-grow:1}#documenter .docs-main .docs-footer .docs-footer-nextpage{text-align:right}#documenter .docs-main .docs-footer .flexbox-break{flex-basis:100%;height:0}#documenter .docs-main .docs-footer .footer-message{font-size:0.8em;margin:0.5em auto 0 auto;text-align:center}#documenter .docs-sidebar{display:flex;flex-direction:column;color:#0a0a0a;background-color:#f5f5f5;border-right:1px solid #dbdbdb;padding:0;flex:0 0 18rem;z-index:5;font-size:1rem;position:fixed;left:-18rem;width:18rem;height:100%;transition:left 0.3s}#documenter .docs-sidebar.visible{left:0;box-shadow:.4rem 0rem .8rem #bbb}@media screen and (min-width: 1056px){#documenter .docs-sidebar.visible{box-shadow:none}}@media screen and (min-width: 1056px){#documenter .docs-sidebar{left:0;top:0}}#documenter .docs-sidebar .docs-logo{margin-top:1rem;padding:0 1rem}#documenter .docs-sidebar .docs-logo>img{max-height:6rem;margin:auto}#documenter .docs-sidebar .docs-package-name{flex-shrink:0;font-size:1.5rem;font-weight:700;text-align:center;white-space:nowrap;overflow:hidden;padding:0.5rem 0}#documenter .docs-sidebar .docs-package-name .docs-autofit{max-width:16.2rem}#documenter .docs-sidebar .docs-package-name a,#documenter .docs-sidebar .docs-package-name a:hover{color:#0a0a0a}#documenter .docs-sidebar .docs-version-selector{border-top:1px solid #dbdbdb;display:none;padding:0.5rem}#documenter .docs-sidebar .docs-version-selector.visible{display:flex}#documenter .docs-sidebar ul.docs-menu{flex-grow:1;user-select:none;border-top:1px solid #dbdbdb;padding-bottom:1.5rem}#documenter .docs-sidebar ul.docs-menu>li>.tocitem{font-weight:bold}#documenter .docs-sidebar ul.docs-menu>li li{font-size:.95rem;margin-left:1em;border-left:1px solid #dbdbdb}#documenter .docs-sidebar ul.docs-menu input.collapse-toggle{display:none}#documenter .docs-sidebar ul.docs-menu ul.collapsed{display:none}#documenter .docs-sidebar ul.docs-menu input:checked~ul.collapsed{display:block}#documenter .docs-sidebar ul.docs-menu label.tocitem{display:flex}#documenter .docs-sidebar ul.docs-menu label.tocitem .docs-label{flex-grow:2}#documenter .docs-sidebar ul.docs-menu label.tocitem .docs-chevron{display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1;font-size:.75rem;margin-left:1rem;margin-top:auto;margin-bottom:auto}#documenter .docs-sidebar ul.docs-menu label.tocitem .docs-chevron::before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f054"}#documenter .docs-sidebar ul.docs-menu input:checked~label.tocitem .docs-chevron::before{content:"\f078"}#documenter .docs-sidebar ul.docs-menu .tocitem{display:block;padding:0.5rem 0.5rem}#documenter .docs-sidebar ul.docs-menu .tocitem,#documenter .docs-sidebar ul.docs-menu .tocitem:hover{color:#0a0a0a;background:#f5f5f5}#documenter .docs-sidebar ul.docs-menu a.tocitem:hover,#documenter .docs-sidebar ul.docs-menu label.tocitem:hover{color:#0a0a0a;background-color:#ebebeb}#documenter .docs-sidebar ul.docs-menu li.is-active{border-top:1px solid #dbdbdb;border-bottom:1px solid #dbdbdb;background-color:#fff}#documenter .docs-sidebar ul.docs-menu li.is-active .tocitem,#documenter .docs-sidebar ul.docs-menu li.is-active .tocitem:hover{background-color:#fff;color:#0a0a0a}#documenter .docs-sidebar ul.docs-menu li.is-active ul.internal .tocitem:hover{background-color:#ebebeb;color:#0a0a0a}#documenter .docs-sidebar ul.docs-menu>li.is-active:first-child{border-top:none}#documenter .docs-sidebar ul.docs-menu ul.internal{margin:0 0.5rem 0.5rem;border-top:1px solid #dbdbdb}#documenter .docs-sidebar ul.docs-menu ul.internal li{font-size:.85rem;border-left:none;margin-left:0;margin-top:0.5rem}#documenter .docs-sidebar ul.docs-menu ul.internal .tocitem{width:100%;padding:0}#documenter .docs-sidebar ul.docs-menu ul.internal .tocitem::before{content:"⚬";margin-right:0.4em}#documenter .docs-sidebar form.docs-search{margin:auto;margin-top:0.5rem;margin-bottom:0.5rem}#documenter .docs-sidebar form.docs-search>input{width:14.4rem}#documenter .docs-sidebar #documenter-search-query{color:#707070;width:14.4rem;box-shadow:inset 0 1px 2px rgba(10,10,10,0.1)}@media screen and (min-width: 1056px){#documenter .docs-sidebar ul.docs-menu{overflow-y:auto;-webkit-overflow-scroll:touch}#documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar{width:.3rem;background:none}#documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar-thumb{border-radius:5px 0px 0px 5px;background:#e0e0e0}#documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar-thumb:hover{background:#ccc}}@media screen and (max-width: 1055px){#documenter .docs-sidebar{overflow-y:auto;-webkit-overflow-scroll:touch}#documenter .docs-sidebar::-webkit-scrollbar{width:.3rem;background:none}#documenter .docs-sidebar::-webkit-scrollbar-thumb{border-radius:5px 0px 0px 5px;background:#e0e0e0}#documenter .docs-sidebar::-webkit-scrollbar-thumb:hover{background:#ccc}}kbd.search-modal-key-hints{border-radius:0.25rem;border:1px solid rgba(0,0,0,0.6);box-shadow:0 2px 0 1px rgba(0,0,0,0.6);cursor:default;font-size:0.9rem;line-height:1.5;min-width:0.75rem;text-align:center;padding:0.1rem 0.3rem;position:relative;top:-1px}.search-min-width-50{min-width:50%}.search-min-height-100{min-height:100%}.search-modal-card-body{max-height:calc(100vh - 15rem)}.search-result-link{border-radius:0.7em;transition:all 300ms}.search-result-link:hover,.search-result-link:focus{background-color:rgba(0,128,128,0.1)}.search-result-link .property-search-result-badge,.search-result-link .search-filter{transition:all 300ms}.property-search-result-badge,.search-filter{padding:0.15em 0.5em;font-size:0.8em;font-style:italic;text-transform:none !important;line-height:1.5;color:#f5f5f5;background-color:rgba(51,65,85,0.501961);border-radius:0.6rem}.search-result-link:hover .property-search-result-badge,.search-result-link:hover .search-filter,.search-result-link:focus .property-search-result-badge,.search-result-link:focus .search-filter{color:#f1f5f9;background-color:#333}.search-filter{color:#333;background-color:#f5f5f5;transition:all 300ms}.search-filter:hover,.search-filter:focus{color:#333}.search-filter-selected{color:#f5f5f5;background-color:rgba(139,0,139,0.5)}.search-filter-selected:hover,.search-filter-selected:focus{color:#f5f5f5}.search-result-highlight{background-color:#ffdd57;color:black}.search-divider{border-bottom:1px solid #dbdbdb}.search-result-title{width:85%;color:#333}.search-result-code-title{font-size:0.875rem;font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace}#search-modal .modal-card-body::-webkit-scrollbar,#search-modal .filter-tabs::-webkit-scrollbar{height:10px;width:10px;background-color:transparent}#search-modal .modal-card-body::-webkit-scrollbar-thumb,#search-modal .filter-tabs::-webkit-scrollbar-thumb{background-color:gray;border-radius:1rem}#search-modal .modal-card-body::-webkit-scrollbar-track,#search-modal .filter-tabs::-webkit-scrollbar-track{-webkit-box-shadow:inset 0 0 6px rgba(0,0,0,0.6);background-color:transparent}.w-100{width:100%}.gap-2{gap:0.5rem}.gap-4{gap:1rem}.gap-8{gap:2rem}.ansi span.sgr1{font-weight:bolder}.ansi span.sgr2{font-weight:lighter}.ansi span.sgr3{font-style:italic}.ansi span.sgr4{text-decoration:underline}.ansi span.sgr7{color:#fff;background-color:#222}.ansi span.sgr8{color:transparent}.ansi span.sgr8 span{color:transparent}.ansi span.sgr9{text-decoration:line-through}.ansi span.sgr30{color:#242424}.ansi span.sgr31{color:#a7201f}.ansi span.sgr32{color:#066f00}.ansi span.sgr33{color:#856b00}.ansi span.sgr34{color:#2149b0}.ansi span.sgr35{color:#7d4498}.ansi span.sgr36{color:#007989}.ansi span.sgr37{color:gray}.ansi span.sgr40{background-color:#242424}.ansi span.sgr41{background-color:#a7201f}.ansi span.sgr42{background-color:#066f00}.ansi span.sgr43{background-color:#856b00}.ansi span.sgr44{background-color:#2149b0}.ansi span.sgr45{background-color:#7d4498}.ansi span.sgr46{background-color:#007989}.ansi span.sgr47{background-color:gray}.ansi span.sgr90{color:#616161}.ansi span.sgr91{color:#cb3c33}.ansi span.sgr92{color:#0e8300}.ansi span.sgr93{color:#a98800}.ansi span.sgr94{color:#3c5dcd}.ansi span.sgr95{color:#9256af}.ansi span.sgr96{color:#008fa3}.ansi span.sgr97{color:#f5f5f5}.ansi span.sgr100{background-color:#616161}.ansi span.sgr101{background-color:#cb3c33}.ansi span.sgr102{background-color:#0e8300}.ansi span.sgr103{background-color:#a98800}.ansi span.sgr104{background-color:#3c5dcd}.ansi span.sgr105{background-color:#9256af}.ansi span.sgr106{background-color:#008fa3}.ansi span.sgr107{background-color:#f5f5f5}code.language-julia-repl>span.hljs-meta{color:#066f00;font-weight:bolder}/*! + Theme: Default + Description: Original highlight.js style + Author: (c) Ivan Sagalaev + Maintainer: @highlightjs/core-team + Website: https://highlightjs.org/ + License: see project LICENSE + Touched: 2021 +*/pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#F3F3F3;color:#444}.hljs-comment{color:#697070}.hljs-tag,.hljs-punctuation{color:#444a}.hljs-tag .hljs-name,.hljs-tag .hljs-attr{color:#444}.hljs-keyword,.hljs-attribute,.hljs-selector-tag,.hljs-meta .hljs-keyword,.hljs-doctag,.hljs-name{font-weight:bold}.hljs-type,.hljs-string,.hljs-number,.hljs-selector-id,.hljs-selector-class,.hljs-quote,.hljs-template-tag,.hljs-deletion{color:#880000}.hljs-title,.hljs-section{color:#880000;font-weight:bold}.hljs-regexp,.hljs-symbol,.hljs-variable,.hljs-template-variable,.hljs-link,.hljs-selector-attr,.hljs-operator,.hljs-selector-pseudo{color:#ab5656}.hljs-literal{color:#695}.hljs-built_in,.hljs-bullet,.hljs-code,.hljs-addition{color:#397300}.hljs-meta{color:#1f7199}.hljs-meta .hljs-string{color:#38a}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:bold} diff --git a/previews/PR1107/assets/themeswap.js b/previews/PR1107/assets/themeswap.js new file mode 100644 index 000000000..9f5eebe6a --- /dev/null +++ b/previews/PR1107/assets/themeswap.js @@ -0,0 +1,84 @@ +// Small function to quickly swap out themes. Gets put into the tag.. +function set_theme_from_local_storage() { + // Initialize the theme to null, which means default + var theme = null; + // If the browser supports the localstorage and is not disabled then try to get the + // documenter theme + if (window.localStorage != null) { + // Get the user-picked theme from localStorage. May be `null`, which means the default + // theme. + theme = window.localStorage.getItem("documenter-theme"); + } + // Check if the users preference is for dark color scheme + var darkPreference = + window.matchMedia("(prefers-color-scheme: dark)").matches === true; + // Initialize a few variables for the loop: + // + // - active: will contain the index of the theme that should be active. Note that there + // is no guarantee that localStorage contains sane values. If `active` stays `null` + // we either could not find the theme or it is the default (primary) theme anyway. + // Either way, we then need to stick to the primary theme. + // + // - disabled: style sheets that should be disabled (i.e. all the theme style sheets + // that are not the currently active theme) + var active = null; + var disabled = []; + var primaryLightTheme = null; + var primaryDarkTheme = null; + for (var i = 0; i < document.styleSheets.length; i++) { + var ss = document.styleSheets[i]; + // The tag of each style sheet is expected to have a data-theme-name attribute + // which must contain the name of the theme. The names in localStorage much match this. + var themename = ss.ownerNode.getAttribute("data-theme-name"); + // attribute not set => non-theme stylesheet => ignore + if (themename === null) continue; + // To distinguish the default (primary) theme, it needs to have the data-theme-primary + // attribute set. + if (ss.ownerNode.getAttribute("data-theme-primary") !== null) { + primaryLightTheme = themename; + } + // Check if the theme is primary dark theme so that we could store its name in darkTheme + if (ss.ownerNode.getAttribute("data-theme-primary-dark") !== null) { + primaryDarkTheme = themename; + } + // If we find a matching theme (and it's not the default), we'll set active to non-null + if (themename === theme) active = i; + // Store the style sheets of inactive themes so that we could disable them + if (themename !== theme) disabled.push(ss); + } + var activeTheme = null; + if (active !== null) { + // If we did find an active theme, we'll (1) add the theme--$(theme) class to + document.getElementsByTagName("html")[0].className = "theme--" + theme; + activeTheme = theme; + } else { + // If we did _not_ find an active theme, then we need to fall back to the primary theme + // which can either be dark or light, depending on the user's OS preference. + var activeTheme = darkPreference ? primaryDarkTheme : primaryLightTheme; + // In case it somehow happens that the relevant primary theme was not found in the + // preceding loop, we abort without doing anything. + if (activeTheme === null) { + console.error("Unable to determine primary theme."); + return; + } + // When switching to the primary light theme, then we must not have a class name + // for the tag. That's only for non-primary or the primary dark theme. + if (darkPreference) { + document.getElementsByTagName("html")[0].className = + "theme--" + activeTheme; + } else { + document.getElementsByTagName("html")[0].className = ""; + } + } + for (var i = 0; i < document.styleSheets.length; i++) { + var ss = document.styleSheets[i]; + // The tag of each style sheet is expected to have a data-theme-name attribute + // which must contain the name of the theme. The names in localStorage much match this. + var themename = ss.ownerNode.getAttribute("data-theme-name"); + // attribute not set => non-theme stylesheet => ignore + if (themename === null) continue; + // we'll disable all the stylesheets, except for the active one + ss.disabled = !(themename == activeTheme); + } +} +set_theme_from_local_storage(); diff --git a/previews/PR1107/assets/warner.js b/previews/PR1107/assets/warner.js new file mode 100644 index 000000000..3f6f5d008 --- /dev/null +++ b/previews/PR1107/assets/warner.js @@ -0,0 +1,52 @@ +function maybeAddWarning() { + // DOCUMENTER_NEWEST is defined in versions.js, DOCUMENTER_CURRENT_VERSION and DOCUMENTER_STABLE + // in siteinfo.js. + // If either of these are undefined something went horribly wrong, so we abort. + if ( + window.DOCUMENTER_NEWEST === undefined || + window.DOCUMENTER_CURRENT_VERSION === undefined || + window.DOCUMENTER_STABLE === undefined + ) { + return; + } + + // Current version is not a version number, so we can't tell if it's the newest version. Abort. + if (!/v(\d+\.)*\d+/.test(window.DOCUMENTER_CURRENT_VERSION)) { + return; + } + + // Current version is newest version, so no need to add a warning. + if (window.DOCUMENTER_NEWEST === window.DOCUMENTER_CURRENT_VERSION) { + return; + } + + // Add a noindex meta tag (unless one exists) so that search engines don't index this version of the docs. + if (document.body.querySelector('meta[name="robots"]') === null) { + const meta = document.createElement("meta"); + meta.name = "robots"; + meta.content = "noindex"; + + document.getElementsByTagName("head")[0].appendChild(meta); + } + + const div = document.createElement("div"); + div.classList.add("outdated-warning-overlay"); + const closer = document.createElement("button"); + closer.classList.add("outdated-warning-closer", "delete"); + closer.addEventListener("click", function () { + document.body.removeChild(div); + }); + const href = window.documenterBaseURL + "/../" + window.DOCUMENTER_STABLE; + div.innerHTML = + 'This documentation is not for the latest stable release, but for either the development version or an older release.
Click here to go to the documentation for the latest stable release.'; + div.appendChild(closer); + document.body.appendChild(div); +} + +if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", maybeAddWarning); +} else { + maybeAddWarning(); +} diff --git a/previews/PR1107/dynamic_sparse_arrays/index.html b/previews/PR1107/dynamic_sparse_arrays/index.html new file mode 100644 index 000000000..c6f13d896 --- /dev/null +++ b/previews/PR1107/dynamic_sparse_arrays/index.html @@ -0,0 +1,8 @@ + +Dynamic Sparse Arrays · Coluna.jl

Dynamic Sparse Arrays

This package aims to provide dynamic sparse vectors and matrices in Julia. Unlike the sparse arrays provided in SparseArrays, arrays from this package have unfixed sizes. It means that we can add or delete rows and columns after the instantiation of the array.

DynamicSparseArrays is a registered package.

Introduction

Coluna is a branch-cut-and-price framework. It means that Coluna's algorithms dynamically generate constraints and variables. Therefore, the coefficient matrix (which is usually sparse) must support the addition of new rows and columns.

For this purpose, we implemented the packed-memory array data structure to handle the dynamic sparse vector introduced in the following papers:

BENDER, Michael A. et HU, Haodong. An adaptive packed-memory array. ACM Transactions on Database Systems (TODS), 2007, vol. 32, no 4, p. 26.

BENDER, Michael A., DEMAINE, Erik D., et FARACH-COLTON, Martin. Cache-oblivious B-trees. SIAM Journal on Computing, 2005, vol. 35, no 2, p. 341-358.

On top of the packed-memory array, we implemented the data structure introduced in the following paper to handle the dynamic sparse matrix.

WHEATMAN, Brian et XU, Helen. Packed Compressed Sparse Row: A Dynamic Graph Representation. In : 2018 IEEE High Performance extreme Computing Conference (HPEC). IEEE, 2018. p. 1-7.

The implementation may vary from the description in the papers. If you find some enhancements, please contact guimarqu.

Overview

The packed-memory array (PackedMemoryArray{K,T}) is a Vector{Union{Nothing,Tuple{K,T}}} where K is the type of the keys and T is the type of the values. We keep empty entries (i.e. Nothing) in the array to add new values later fast. Non-empty entries are sorted by ascending key order. The array is virtually split into segments of equal size. The goal is to maintain the density (i.e. number of non-empty values/size of the segment) of each segment between pre-defined bounds. We also consider the density of certain unions of segments represented by nodes of the tree in gray. The root node of the tree is the union of all segments, thus the whole array. When one node of the tree has a density outside the allowed bounds, we need to rebalance the parent. It means that we redistribute the empty and non-empty entries to fit the density bounds. If the density bounds are not respected at the root node, we resize the array.

On top of the packed-memory array, there is the (PackedCSC{K,T}). This is a particular case of a matrix where values are of type T, row keys of type K, and column keys of type Int. Each column of the matrix (partition) is delimited by a semaphore which is a non-empty entry with a reserved key value defined by the semaphore_key function. In the example, the first partition has its semaphore at position 1, starts at position 2, and finishes at position 9. At position 10, it's the semaphore that signals the beginning of the second partition. In each partition, non-empty entries are sorted by ascending key order.

As you can see, the PackedCSC{K,T} is not well suited to the matrix. Indeed, each column is associated with a partition. If you have a column with only zero values, the array will contain a partition with only empty entries. Lastly, the type of column key is Int. Therefore, built on top of PackedCSC{K,T}, MappedCSC{K,L,T} corrects all these shortcomings. This data structure just associates a column key of type L to each partition of PackedCSC{K,T}.

Dynamic Sparse Arrays

Architecture overview.

+

References

DynamicSparseArrays.dynamicsparsevecFunction
dynamicsparsevec(I, V, [combine, n])

Creates a dynamic sparse vector S of length n such that S[I[k]] = S[V[k]]. The combine operator is used to combine the values of V that have same id in I.

source
DynamicSparseArrays.dynamicsparseFunction
dynamicsparse(I, J, V, [m, n])

Creates a dynamic sparse matrix S of dimensions m×n such that S[I[k], J[k]] = V[k].

source
dynamicsparse(Ti, Tj, Tv [; fill_mode = true])

Creates an empty dynamic sparse matrix with row keys of type Ti, column keys of type Tj, and non-zero values of type Tv. By default, the matrix is returned in a "fill mode". This allows the user to fill the matrix with non-zero entries. All the write operations are stored in a Dict. When the matrix is filled, the user must call closefillmode!(matrix).

source
diff --git a/previews/PR1107/index.html b/previews/PR1107/index.html new file mode 100644 index 000000000..8b4877acc --- /dev/null +++ b/previews/PR1107/index.html @@ -0,0 +1,7 @@ + +Introduction · Coluna.jl

Introduction

Warning

We assume that readers are familiar with integer programming and exact optimization methods.

Coluna is under active development. Some features are undocumented because they are very experimental. Current users are expected to read the source code.

Coluna is a framework written in Julia to implement a decomposition approach to optimize block-structured mixed-integer programs (MIP).

Coluna relies on the tools of the JuMP-dev community at both ends of the problem treatment. It uses the JuMP modeling language upfront and MathOptInterface (MOI) to delegate master and subproblems to MIP solvers.

The user introduces an original MIP that models his problem using the JuMP along our specific extension BlockDecomposition that offers a syntax to specify the problem decomposition. Coluna reformulates the original MIP using Dantzig-Wolfe and Benders decomposition techniques. Then, Coluna optimizes the reformulation using the algorithm chosen by the user.

Coluna offers a "black-box" implementation of the branch-and-cut-and-price algorithm:

  1. The input is the set of constraints and variables of the MIP in its natural/compact formulation (formulated with JuMP or MOI).
  2. BlockDecomposition allows the user to provide Coluna with his decomposition of the model. The BlockDecomposition syntax allows the user to implicitly define subsystems in the MIP on which the decomposition is based. These subsystems are described by rows and/or columns indices.
  3. The reformulation associated with the decomposition defined by the user is automatically generated by Coluna, without requiring any input from the user to define master columns, their reduced cost, pricing/separation problem, or Lagrangian bound.
  4. A default column (and cut) generation procedure is implemented. It relies on underlying MOI optimizers to handle master and subproblems. However, the user can use pricing callbacks to solve the subproblems.
  5. A branching scheme that preserves the pricing problem structure is offered by default; it runs based on priorities and directives specified by the user on the original variables.

Installation

Coluna is a package for Julia 1.6+.

It requires JuMP to model the problem, BlockDecomposition to define the decomposition, and a MIP solver supported by MathOptInterface to optimize the master and the subproblems.

You can install Coluna and its dependencies through the package manager of Julia by entering :

] add Coluna

Acknowledgements

Atoptima, Mathematical Optimization Society (MOS), Région Nouvelle-Aquitaine, University of Bordeaux, and Inria

diff --git a/previews/PR1107/man/algorithm/index.html b/previews/PR1107/man/algorithm/index.html new file mode 100644 index 000000000..623d381a4 --- /dev/null +++ b/previews/PR1107/man/algorithm/index.html @@ -0,0 +1,103 @@ + +Built-in algorithms · Coluna.jl

Built-in Algorithms

Branch-and-Bound

Branch-and-Bound algorithm aims to find an optimal solution of a MIP by successive divisions of the search space. An introduction to the Branch-and-Bound algorithm can be found here.

Coluna provides a generic Branch-and-Bound algorithm whose three main elements can be easily modified:

Coluna.Algorithm.TreeSearchAlgorithmType
Coluna.Algorithm.TreeSearchAlgorithm(
+    conqueralg::AbstractConquerAlgorithm = ColCutGenConquer(),
+    dividealg::AbstractDivideAlgorithm = Branching(),
+    explorestrategy::AbstractExploreStrategy = DepthFirstStrategy(),
+    maxnumnodes = 100000,
+    opennodeslimit = 100,
+    timelimit = -1, # -1 means no time limit
+    opt_atol::Float64 = DEF_OPTIMALITY_ATOL,
+    opt_rtol::Float64 = DEF_OPTIMALITY_RTOL,
+    branchingtreefile = "",
+    jsonfile = "",
+    print_node_info = true
+)

This algorithm is a branch and bound that uses a search tree to optimize the reformulation. At each node in the tree, it applies conqueralg to evaluate the node and improve the bounds, dividealg to generate branching constraints, and explorestrategy to select the next node to treat.

The three main elements of the algorithm are:

  • the conquer strategy (conqueralg): evaluation of the problem at a node of the Branch-and-Bound tree. Depending on the type of decomposition used ahead of the Branch-and-Bound, you can use either Column Generation (if your problem is decomposed following Dantzig-Wolfe transformation) and/or Cut Generation (for Dantzig-Wolfe and Benders decompositions).
  • the branching strategy (dividealg): how to create new branches i.e. how to divide the search space
  • the explore strategy (explorestrategy): the evaluation order of your nodes

Parameters:

  • maxnumnodes : maximum number of nodes explored by the algorithm
  • opennodeslimit : maximum number of nodes waiting to be explored
  • timelimit : time limit in seconds of the algorithm
  • opt_atol : optimality absolute tolerance (alpha)
  • opt_rtol : optimality relative tolerance (alpha)

Options:

  • branchingtreefile : name of the file in which the algorithm writes an overview of the branching tree
  • jsonfile : name of the file in which the algorithm writes the solution in JSON format
  • print_node_info : log the tree into the console

Warning: if you set a name for the branchingtreefile AND the jsonfile, the algorithm will only write in the json file.

source

Conquer, divide algorithms and the explore strategy available with the TreeSearchAlgorithm are listed in the following mind map.

mindmap + TreeSearchAlgorithm + (conquer) + BendersConquer + ColCutGenConquer + RestrMasterLpConquer + (divide) + NoBranching + ClassicBranching + StrongBranching + (explore) + DepthFirstStrategy + BestDualBoundStrategy

Conquer algorithms

Missing docstring.

Missing docstring for Algorithm.BendersConquer. Check Documenter's build log for details.

Coluna.Algorithm.ColCutGenConquerType
Coluna.Algorithm.ColCutGenConquer(
+    colgen = ColumnGeneration(),
+    cutgen = CutCallbacks(),
+    primal_heuristics = ParameterizedHeuristic[ParamRestrictedMasterHeuristic()],
+    max_nb_cut_rounds = 3
+)

Column-and-cut-generation based algorithm to find primal and dual bounds for a problem decomposed using Dantzig-Wolfe paradigm.

Parameters :

  • colgen: column generation algorithm
  • cutgen: cut generation algorithm
  • primal_heuristics: heuristics to find a feasible solution
  • max_nb_cut_rounds : number of cut generation done by the algorithm
source
Missing docstring.

Missing docstring for Algorithm.RestrMasterLpConquer. Check Documenter's build log for details.

Divide algorithms

Coluna.Algorithm.ClassicBranchingType
ClassicBranching(
+    selection_criterion = MostFractionalCriterion()
+    rules = [Branching.PrioritisedBranchingRule(SingleVarBranchingRule(), 1.0, 1.0)]
+    int_tol = 1e-6
+)

Chooses the best candidate according to a selection criterion and generates the two children.

Parameters

  • selection_criterion: selection criterion to choose the best candidate
  • rules: branching rules to generate the candidates
  • int_tol: tolerance to determine if a variable is integer

It is implemented as a specific case of the strong branching algorithm.

source

Strong branching is the main algorithm that we provide and it is the default implementation of the Branching submodule. You can have more information about the algorithm by reading the Branching submodule documentation.

Coluna.Algorithm.StrongBranchingType
StrongBranching(
+    phases = [],
+    rules = [Branching.PrioritisedBranchingRule(SingleVarBranchingRule(), 1.0, 1.0)],
+    selection_criterion = MostFractionalCriterion(),
+    verbose = true,
+    int_tol = 1e-6
+)

The algorithm that performs a (multi-phase) (strong) branching in a tree search algorithm.

Strong branching is a procedure that heuristically selects a branching constraint that potentially gives the best progress of the dual bound. The procedure selects a collection of branching candidates based on their branching rule and their score. Then, the procedure evaluates the progress of the dual bound in both branches of each branching candidate by solving both potential children using a conquer algorithm. The candidate that has the largest product of dual bound improvements in the branches is chosen to be the branching constraint.

When the dual bound improvement produced by the branching constraint is difficult to compute (e.g. time-consuming in the context of column generation), one can let the branching algorithm quickly estimate the dual bound improvement of each candidate and retain the most promising branching candidates. This is called a phase. The goal is to first evaluate a large number of candidates with a very fast conquer algorithm and retain a certain number of promising ones. Then, over the phases, it evaluates the improvement with a more precise conquer algorithm and restrict the number of retained candidates until only one is left.

Parameters:

source

All the possible algorithms that can be used within the strong branching are listed in the following mind map.

mindmap + StrongBranching + (phases) + (conquer) + BendersConquer + ColCutGenConquer + RestrMasterLpConquer + (score) + ProductScore + TreeDepthScore + (rules) + SingleVarBranchingRule + (selection_criterion) + FirstFoundCriterion + MostFractionalCriterion

Explore strategies

Cut generation algorithms

Coluna.Algorithm.BendersCutGenerationType
Coluna.Algorithm.BendersCutGeneration(
+    restr_master_solve_alg = SolveLpForm(get_dual_sol = true, relax_integrality = true),
+    restr_master_optimizer_id = 1,
+    separation_solve_alg = SolveLpForm(get_dual_sol = true, relax_integrality = true)
+    max_nb_iterations::Int = 100,
+)

Benders cut generation algorithm that can be applied to a formulation reformulated using Benders decomposition.

This algorithm is an implementation of the generic algorithm provided by the Benders submodule.

Parameters:

  • restr_master_solve_alg: algorithm to solve the restricted master problem
  • restr_master_optimizer_id: optimizer id to use to solve the restricted master problem
  • separation_solve_alg: algorithm to solve the separation problem (must be a LP solver that returns a dual solution)

Option:

  • max_nb_iterations: maximum number of iterations

About the output

At each iteration, the Benders cut generation algorithm show following statistics:

<it=  6> <et= 0.05> <mst= 0.00> <sp= 0.00> <cuts= 0> <master=  293.5000>

where:

  • it stands for the current number of iterations of the algorithm
  • et is the elapsed time in seconds since Coluna has started the optimisation
  • mst is the time in seconds spent solving the master problem at the current iteration
  • sp is the time in seconds spent solving the separation problem at the current iteration
  • cuts is the number of cuts generated at the current iteration
  • master is the objective value of the master problem at the current iteration

Debug options (print at each iteration):

  • debug_print_master: print the master problem
  • debug_print_master_primal_solution: print the master problem with the primal solution
  • debug_print_master_dual_solution: print the master problem with the dual solution (make sure the restr_master_solve_alg returns a dual solution)
  • debug_print_subproblem: print the subproblem
  • debug_print_subproblem_primal_solution: print the subproblem with the primal solution
  • debug_print_subproblem_dual_solution: print the subproblem with the dual solution
  • debug_print_generated_cuts: print the generated cuts
source
Coluna.Algorithm.CutCallbacksType
CutCallbacks(
+    call_robust_facultative = true,
+    call_robust_essential = true,
+    tol::Float64 = 1e-6
+)

Runs the cut user callbacks attached to a formulation.

Parameters:

  • call_robust_facultative: if true, call all the robust facultative cut user callbacks (i.e. user cut callbacks)
  • call_robust_essential: if true, call all the robust essential cut user callbacks (i.e. lazy constraint callbacks)
  • tol: tolerance used to determine if a cut is violated

See the JuMP documentation for more information about user callbacks and the tutorials in the Coluna documentation for examples of user callbacks.

source

Column generation algorithms

Coluna.Algorithm.ColumnGenerationType
Coluna.Algorithm.ColumnGeneration(
+    restr_master_solve_alg = SolveLpForm(get_dual_sol = true),
+    pricing_prob_solve_alg = SolveIpForm(
+        moi_params = MoiOptimize(
+            deactivate_artificial_vars = false,
+            enforce_integrality = false
+        )
+    ),
+    essential_cut_gen_alg = CutCallbacks(call_robust_facultative = false),
+    max_nb_iterations = 1000,
+    log_print_frequency = 1,
+    redcost_tol = 1e-4,
+    show_column_already_inserted_warning = true,
+    cleanup_threshold = 10000,
+    cleanup_ratio = 0.66,
+    smoothing_stabilization = 0.0 # should be in [0, 1],
+)

Column generation algorithm that can be applied to formulation reformulated using Dantzig-Wolfe decomposition.

This algorithm first solves the linear relaxation of the master (master LP) using restr_master_solve_alg. Then, it solves the subproblems by calling pricing_prob_solve_alg to get the columns that have the best reduced costs and that hence, may improve the master LP's objective the most.

In order for the algorithm to converge towards the optimal solution of the master LP, it suffices that the pricing oracle returns, at each iteration, a negative reduced cost solution if one exists. The algorithm stops when all subproblems fail to generate a column with negative (positive) reduced cost in the case of a minimization (maximization) problem or when it reaches the maximum number of iterations.

Parameters:

  • restr_master_solve_alg: algorithm to optimize the master LP
  • pricing_prob_solve_alg: algorithm to optimize the subproblems
  • essential_cut_gen_alg: algorithm to generate essential cuts which is run when the solution of the master LP is integer.

Options:

  • max_nb_iterations: maximum number of iterations
  • log_print_frequency: display frequency of iterations statistics

Undocumented parameters are in alpha version.

About the ouput

At each iteration (depending on log_print_frequency), the column generation algorithm can display following statistics.

<it= 90> <et=15.62> <mst= 0.02> <sp= 0.05> <cols= 4> <al= 0.00> <DB=  300.2921> <mlp=  310.3000> <PB=310.3000>

Here are their meanings :

  • it stands for the current number of iterations of the algorithm
  • et is the elapsed time in seconds since Coluna has started the optimisation
  • mst is the time in seconds spent solving the master LP at the current iteration
  • sp is the time in seconds spent solving the subproblems at the current iteration
  • cols is the number of column generated by the subproblems at the current iteration
  • al is the smoothing factor of the stabilisation at the current iteration (alpha version)
  • DB is the dual bound of the master LP at the current iteration
  • mlp is the objective value of the master LP at the current iteration
  • PB is the objective value of the best primal solution found by Coluna at the current iteration
source

External call to optimize a linear program

Coluna.Algorithm.SolveLpFormType
Coluna.Algorithm.SolveLpForm(
+    get_ip_primal_sol = false,
+    get_dual_sol = false,
+    relax_integrality = false,
+    get_dual_bound = false,
+    silent = true
+)

Solve a linear program stored in a formulation using its first optimizer. This algorithm works only if the optimizer is interfaced with MathOptInterface.

You can define the optimizer using the default_optimizer attribute of Coluna or with the method specify! from BlockDecomposition

Parameters:

  • get_ip_primal_sol: update the primal solution of the formulation if equals true
  • get_dual_sol: retrieve the dual solution and store it in the ouput if equals true
  • relax_integrality: relax integer variables of the formulation before optimization if equals true
  • get_dual_bound: store the dual objective value in the output if equals true
  • silent: set MOI.Silent() to its value

Undocumented parameters are alpha.

source

External call to optimize a mixed-integer program / combinatorial problem

Coluna.Algorithm.SolveIpFormType
Coluna.Algorithm.SolveIpForm(
+    optimizer_id = 1
+    moi_params = MoiOptimize()
+    user_params = UserOptimize()
+    custom_params = CustomOptimize()
+)

Solve an optimization problem. This algorithm can call different type of optimizers :

  • subsolver interfaced with MathOptInterface to optimize a mixed integer program
  • pricing callback defined by the user
  • custom optimizer to solve a custom model

You can specify an optimizer using the default_optimizer attribute of Coluna or with the method specify! from BlockDecomposition. If you want to define several optimizers for a given subproblem, you must use specify!:

specify!(subproblem, optimizers = [optimizer1, optimizer2, optimizer3])

Value of optimizer_id is the position of the optimizer you want to use. For example, if optimizer_id is equal to 2, the algorithm will use optimizer2.

By default, the algorihm uses the first optimizer or the default optimizer if no optimizer has been specified through specify!.

Depending on the type of the optimizer chosen, the algorithm will use one the three configurations :

  • moi_params for subsolver interfaced with MathOptInterface
  • user_params for pricing callbacks
  • custom_params for custom solvers

Custom solver is undocumented because alpha.

source
Coluna.Algorithm.MoiOptimizeType
MoiOptimize(
+    time_limit = 600
+    deactivate_artificial_vars = false
+    enforce_integrality = false
+    get_dual_bound = true
+)

Configuration for an optimizer that calls a subsolver through MathOptInterface.

Parameters:

  • time_limit: in seconds
  • deactivate_artificial_vars: deactivate all artificial variables of the formulation if equals true
  • enforce_integrality: enforce integer variables that are relaxed if equals true
  • get_dual_bound: store the dual objective value in the output if equals true
source
Coluna.Algorithm.UserOptimizeType
UserOptimize(
+    max_nb_ip_primal_sols = 50
+)

Configuration for an optimizer that calls a pricing callback to solve the problem.

Parameters:

  • max_nb_ip_primal_sols: maximum number of solutions returned by the callback kept
source
diff --git a/previews/PR1107/man/blockdecomposition/index.html b/previews/PR1107/man/blockdecomposition/index.html new file mode 100644 index 000000000..6b3057570 --- /dev/null +++ b/previews/PR1107/man/blockdecomposition/index.html @@ -0,0 +1,25 @@ + +Setup decomposition using BlockDecomposition · Coluna.jl

Setup decomposition with BlockDecomposition

BlockDecomposition allows the user to perform two types of decomposition using BlockDecomposition.@dantzig_wolfe_decomposition and BlockDecomposition.@benders_decomposition.

For both decompositions, the index-set of the subproblems is declared through an BlockDecomposition.@axis. It returns an array. Each value of the array is a subproblem index wrapped into a BlockDecomposition.AxisId. Each time BlockDecomposition finds an AxisId in the indices of a variable and a constraint, it knows to which subproblem the variable or the constraint belongs.

The macro creates a decomposition tree where the root is the master and the depth is the number of nested decompositions. A classic Dantzig-Wolfe or Benders decomposition produces a decomposition tree of depth 1. At the moment, nested decomposition is not supported.

You can get the subproblem membership of all variables and constraints using the method BlockDecomposition.annotation.

BlockDecomposition does not change the JuMP model. It decorates the model with additional information. All this information is stored in the ext field of the JuMP model.

Errors and warnings

BlockDecomposition.MasterVarInDwSpType

Error thrown when a master variable is in a constraint that belongs to a Dantzig-Wolfe subproblem.

You can retrieve the JuMP variable and the JuMP constraint where the error occurs:

error.variable
+error.constraint
source

References

BlockDecomposition.BlockModelFunction
BlockModel(optimizer [, direct_model = false])

Return a JuMP model which BlockDecomposition will decompose using instructions given by the user.

If you define direct_model = true, the method creates the model with JuMP.direct_model, otherwise it uses JuMP.Model.

source

These are the methods to decompose a JuMP model :

BlockDecomposition.@axisMacro
@axis(name, collection)

Declare collection as an index-set of subproblems. You can access the axis using the variable name.

Examples

Consider a formulation that has a decomposition which gives raise to 5 subproblems. Let {1,2,3,4,5} be the index-set of the subproblems.

To perform this decomposition with BlockDecomposition, we must declare an axis that contains the index-set of the subproblems :

julia> L = 1:5
+1:5
+
+julia> @axis(K, L)
+BlockDecomposition.Axis{:K, Int64}(:K, BlockDecomposition.AxisId{:K, Int64}[1, 2, 3, 4, 5])
+
+julia> K[1]
+1
+
+julia> typeof(K[1])
+BlockDecomposition.AxisId{:K, Int64}

The elements of the axis are AxisId. You must use AxisId in the indices of the variables and the constraints that you declare otherwise BlockDecomposition assign them to the master problem.

@variable(model, x[l in L]) # x[l] belongs to the master for any l ∈ L
+@variable(model, y[k in K]) # y[k], k ∈ K, belongs to subproblem k (because K is an axis)
source
BlockDecomposition.@benders_decompositionMacro
@benders_decomposition(model, name, axis)

Register a Benders decomposition on the JuMP model model where the index-set of the subproblems is defined by the axis axis.

Create a variable name from which the user can access decomposition tree.

source
BlockDecomposition.@dantzig_wolfe_decompositionMacro
@dantzig_wolfe_decomposition(model, name, axis)

Register a Dantzig-Wolfe decomposition on the JuMP model model where the index-set of the subproblems is defined by the axis axis.

Create a variable name from which the user can access the decomposition tree.

source

These are the methods to set additional information to the decomposition (multiplicity and optimizers) :

BlockDecomposition.getmasterFunction
getmaster(node) -> MasterForm

Return an object that wraps the annotation that describes the master formulation of a decomposition stored at the node of the decomposition tree.

This method is not defined if the node is a leaf of the decomposition tree.

source
BlockDecomposition.getsubproblemsFunction
getsubproblems(node) -> Vector{SubproblemForm}

Return a vector of objects that wrap the annotations that describe subproblem formulations of a decomposition stored at node of the decomposition tree.

This method is not defined if the node is a leaf of the decomposition tree.

source
BlockDecomposition.specify!Function
specify!(
+    subproblem, 
+    lower_multiplicity = 1,
+    upper_multiplicity = 1,
+    solver = nothing
+)

Method that allows the user to specify additional property of the subproblems.

The multiplicity of subproblem is the number of times that the same independant block shaped by the subproblem in the coefficient matrix appears in the model. It is equivalent to the number of solutions to the subproblem that can appear in the solution of the original problem.

The solver of the subproblem is the way the subproblem will be optimized. It can be either a function (pricing callback), an optimizer of MathOptInterface (e.g. Gurobi.Optimizer, CPLEX.Optimizer, Glpk.Optimizer... with attributes), or nothing. In the latter case, the solver will use a default optimizer that should be defined in the parameters of the main solver.

Advanced usage : The user can use several solvers to optimize a subproblem :

specify!(subproblem, solver = [Gurobi.Optimizer, my_callback, my_second_callback])

Coluna always uses the first solver by default. Be cautious because changes are always buffered to all solvers. So you may degrade performances if you use a lot of solvers.

source

This method helps you to check your decomposition :

BlockDecomposition.annotationFunction
annotation(node)

Return the annotation that describes the master/subproblem of a given node of the decomposition tree.

annotation(model, variable)
+annotation(model, constraint)

Return the subproblem to which a variable or a constraint belongs.

source
diff --git a/previews/PR1107/man/callbacks/index.html b/previews/PR1107/man/callbacks/index.html new file mode 100644 index 000000000..176e33799 --- /dev/null +++ b/previews/PR1107/man/callbacks/index.html @@ -0,0 +1,7 @@ + +User-defined Callbacks · Coluna.jl

User-defined Callbacks

Callbacks are functions defined by the user that allow him to take over part of the default conquer algorithm. The more classical callbacks in Branch-and-Cut and Branch-and-Price solvers are:

  • Pricing callback (only in Branch-and-Price solvers) that takes over the procedure to determine whether the current master LP solution is optimum or produces an entering variable with negative reduced cost by solving subproblems
  • Separation callback that takes over the procedure to determine whether the current master LP solution is feasible or produces a valid problem constraint that is violated
  • Branching callback that takes over the procedure to determine whether the current master LP solution is integer or produces a valid branching disjunctive constraint that rules out the current fractional solution.
Note

You can't change the original formulation in a callback because Coluna does not propagate the changes into the reformulation and does not check if the solutions found are still feasible.

Pricing callbacks

Pricing callbacks let you define how to solve the subproblems of a Dantzig-Wolfe decomposition to generate a new entering column in the master program. This callback is useful when you know an efficient algorithm to solve the subproblems, i.e. an algorithm better than solving the subproblem with a MIP solver.

See the example in the tutorial section.

Errors and Warnings

Coluna.Algorithm.IncorrectPricingDualBoundType
IncorrectPricingDualBound

Error thrown when transmitting a dual bound larger than the primal bound of the best solution to the pricing subproblem found in a run of the pricing callback.

source
Coluna.Algorithm.MissingPricingDualBoundType
MissingPricingDualBound

Error thrown when the pricing callback does not transmit any dual bound. Make sure you call MOI.submit(model, BD.PricingDualBound(cbdata), db) in your pricing callback.

source
Coluna.Algorithm.MultiplePricingDualBoundsType
MultiplePricingDualBounds

Error thrown when the pricing transmits multiple dual bound. Make sure you call MOI.submit(model, BD.PricingDualBound(cbdata), db) only once in your pricing callback.

source

Separation callbacks

Separation callbacks let you define how to separate cuts or constraints.

Facultative & essential cuts (user cut & lazy constraint)

This callback allows you to add cuts to the master problem. The cuts must be expressed in terms of the original variables. Then, Coluna expresses them over the master variables. You can find an example of essential cut separation and facultative cut separation in the JuMP documentation.

diff --git a/previews/PR1107/man/config/index.html b/previews/PR1107/man/config/index.html new file mode 100644 index 000000000..52a1b7949 --- /dev/null +++ b/previews/PR1107/man/config/index.html @@ -0,0 +1,11 @@ + +Configuration · Coluna.jl

Coluna Configuration

todo

Raw Parameters

todo

params

Coluna.ParamsType
Coluna.Params(
+    solver = Coluna.Algorithm.TreeSearchAlgorithm(),
+    global_art_var_cost = 10e6,
+    local_art_var_cost = 10e4
+)

Parameters of Coluna :

  • solver is the algorithm used to optimize the reformulation.
  • global_art_var_cost is the cost of the global artificial variables in the master
  • local_art_var_cost is the cost of the local artificial variables in the master
source

default_optimizer

todo

Other Supported Parameters

From BlockDecomposition

From MathOptInterface

todo

diff --git a/previews/PR1107/man/decomposition/index.html b/previews/PR1107/man/decomposition/index.html new file mode 100644 index 000000000..fb94ec6e9 --- /dev/null +++ b/previews/PR1107/man/decomposition/index.html @@ -0,0 +1,38 @@ + +Decomposition paradigms · Coluna.jl

Dantzig-Wolfe and Benders decompositions

Coluna is a framework to optimize mixed-integer programs that you can decompose. In other words, if you remove the linking constraints or linking variables from your program, you'll get sets of constraints (blocks) that you can solve independently.

Decompositions are typically used on programs whose constraints or variables can be divided into a set of "easy" constraints (respectively easy variables) and a set of "hard" constraints (respectively hard variables). Decomposing on constraints leads to Dantzig-Wolfe transformation while decomposing on variables leads to the Benders transformation. Both of these decompositions are implemented in Coluna.

Dantzig-Wolfe

Original formulation

Let's consider the following coefficient matrix that has a block diagonal structure in gray and some linking constraints in blue :

Dantzig-Wolfe decomposition

You penalize the violation of the linking constraints in the objective function. You can then solve the blocks independently.

The Dantzig-Wolfe reformulation gives rise to a master problem with an exponential number of variables. Coluna dynamically generates these variables by solving the subproblems. It's the column generation algorithm.

Let's consider the following original formulation in which we partition variables into two vectors $x_1$ and $x_2$ :

\[\begin{aligned} +\min \quad& c_1' x_1 + c_2' x_2 & \\ +\text{s.t.} \quad& A_1 x_1 + A_2 x_2 \geq b & (1)\\ +& D_1 x_1 \quad \quad \quad \geq d_1 & (2) \\ +& \quad \quad \quad D_2 x_2 \geq d_2 & (3) \\ +\end{aligned}\]

  • variables $x_1$ and $x_2$ are the original variables of the problem (duty: OriginalVar)
  • constraints $(1)$ are the linking constraints (duty: OriginalConstr)
  • constraints $(2)$ shapes the first subproblem (duty: OriginalConstr)
  • constraints $(3)$ shapes the second subproblem (duty: OriginalConstr)

Master

When you apply a Dantzig-Wofe decomposition to this formulation, Coluna reformulates it into the following master problem :

\[\begin{aligned} +\min \quad& \sum\limits_{q \in Q_1} c_1' \tilde{x_1}^q \lambda_q + \sum\limits_{q \in Q_2} c_2' \tilde{x_2}^q \lambda_q + f'a \\ +\text{s.t.} \quad& \sum\limits_{q \in Q_1} A_1 \tilde{x_1}^q \lambda_q + \sum\limits_{q \in Q_2} A_2 \tilde{x_2}^q \lambda_q + a \geq b & (1)\\ +& L_1 \leq \sum\limits_{q \in Q_1} \tilde{z}_1\lambda_q \leq U_1 & (2)\\ +& L_2 \leq \sum\limits_{q \in Q_2} \tilde{z}_2\lambda_q \leq U_2 & (3)\\ +& \lambda_q \geq 0, \quad q \in Q_1 \cup Q_2 +\end{aligned}\]

where:

  • set $Q_1$ is the index set of the solutions to the first subproblem
  • set $Q_2$ is the index set of the solutions to the second subproblem
  • set of the solutions to the first is $\{\tilde{x}^q_1\}_{q \in Q_1}$ (duty: MasterRepPricingVar)
  • set of the solutions to the second subproblem is $\{\tilde{x}^q_2\}_{q \in Q_2}$ respectively (duty: MasterRepPricingVar)
  • constraint $(1)$ is the reformulation of the linking constraints (duty: MasterMixedConstr)
  • constraint $(2)$ is the convexity constraint of the first subproblem and involves the lower $L_1$ and upper $U_1$ multiplicity of the subproblem (duty: MasterConvexityConstr)
  • constraint $(3)$ is the convexity constraint of the second subproblem and involves the lower $L_2$ and upper $U_2$ multiplicity of the subproblem (duty: MasterConvexityConstr)
  • variables $\tilde{z}_1$ and $\tilde{z}_2$ are representative of pricing setup variables in the master (always equal to $1$) (duty: MasterRepPricingVar)
  • variables $\lambda_q$ are the columns (duty: MasterCol)
  • variable $a$ is the artificial variable (duty: MasterArtVar)

At the beginning of the column generation algorithm, the master formulation does not have any master columns. Therefore, the master may be infeasible. To prevent this, Coluna adds a local artificial variable $a$ specific to each constraint of the master and a global artificial variable. Costs $f$ of artificial and global artificial variables can be defined in Coluna.Params.

Lower and upper multiplicities of subproblems are $1$ by default. However, when some subproblems are identical (same coefficient matrix and right-hand side), you can avoid solving all of them at each iteration by defining only one subproblem and setting its multiplicity to the number of times it appears. See this tutorial to get an example of Dantzig-Wolfe decomposition with identical subproblems.

Pricing Subproblem

Subproblems take the following form (here, it's the first subproblem):

\[\begin{aligned} +\min \quad& \bar{c_1}' x_1 + z_1\\ +\text{s.t.} \quad& D_1x_1 \geq d_1 & (1)\\ +& \quad x_1 \geq 0 +\end{aligned}\]

where:

  • vector $\bar{c}$ is the reduced cost of the subproblem variables computed by the column generation algorithm.
  • variables $x_1$ are the subproblem variables (duty: DwSpPricingVar)
  • constraint $(1)$ is the subproblem constraint (duty: DwSpPureConstr)
  • variable $z_1$ is the pricing setup variable (always equal to $1$) (duty: DwSpSetupVar)

Benders

Original formulation

Let's consider the following coefficient matrix that has a block diagonal structure in gray and some linking variables in blue :

Benders decomposition

The intuition behind Benders decomposition is that some hard problems can become much easier with some of their variables fixed. Benders aims to divide the variables of the problem into two "levels": the 1st level variables which, once fixed, make it easier to find a solution for the remaining variables, the so-called 2nd-level variables.

The question is how to set the 1st level variables. Benders' theory proceeds by the successive generation of cuts: given a 1st-level solution, we ask the following questions:

  • Is the subproblem infeasible? If so, then the 1st-level solution is not correct and must be eliminated. A feasibility cut will be derived from the dual subproblem and added to the master.
  • Does the aggregation of the master and subproblem solutions give rise to an optimal solution to the problem? It depends on a criterion that can be computed. If it is the case, we are done, else, we derive an optimality cut from the dual subproblem and add it into the master.

Formally, given an original MIP:

\[\begin{aligned} +\min \quad& cx + fy & \\ +\text{s.t.} \quad& Ax \geq a & (2) \\ +& Ey \geq e & (3) \\ +& Bx + Dy \geq d & (4)\\ +& x, y \geq 0, ~ x \in \mathbb{Z}^n\\ +\end{aligned}\]

where:

  • variables $x$ are the 1st-level variables (duty: OriginalVar)
  • variables $y$ are the 2nd-level variables (duty: OriginalVar)
  • constraints (2) are the 1st-level constraints (duty: OriginalConstr)
  • constraints (3) are the 2nd-level constraints (duty: OriginalConstr)
  • constraints (4) are the linking constraints (duty: OriginalConstr)

Master

When you apply a Benders decomposition to this formulation, Coluna reformulates it into the following master problem :

\[\begin{aligned} +\min \quad& cx + \sum\limits_{k \in K}\eta_k & \\ +\text{s.t.} \quad& Ax \geq a & (5)\\ +& <~\text{benders cuts}~> & (6) \\ +& \eta_k \in \mathbb{R} \quad \forall k \in K\\ +\end{aligned}\]

where:

  • variables $x$ are the 1st-level variables (duty: MasterBendFirstStageVar)
  • variables $\eta$ are the second stage cost variables (duty: MasterBendSecondStageCostVar)
  • constraints (5) are the first-level constraints (duty: MasterPureConstr)
  • constraints (6) are the benders cuts (duty: ``)

Note that the $\eta$ variables are free.

Separation subproblem

Here is the form of a given separation subproblem:

\[\begin{aligned} +\min \quad& fy & \\ +\text{s.t.} \quad& Dy \geq d - B\bar{x} & (7) \\ +& Ey \geq e & (8) \\ +& y \geq 0 \\ +\end{aligned}\]

where:

  • variables $y$ are the 2nd-level variables (duty: BendSpSepVar)
  • values $\bar{x}$ are a solution to the master problem
  • constraints (7) are the linking constraints with the 1st-level variables fixed to $\bar{x}$ (duty: BendSpTechnologicalConstr)
  • constraints (8) are the 2nd-level constraints (duty: BendSpPureConstr)

Note that in the special case where the master problem is unbounded, the shape of the subproblem is slightly modified. See the API section to get more information.

diff --git a/previews/PR1107/qa/index.html b/previews/PR1107/qa/index.html new file mode 100644 index 000000000..7ab9c5f47 --- /dev/null +++ b/previews/PR1107/qa/index.html @@ -0,0 +1,28 @@ + +Q&A · Coluna.jl

Question & Answer

Default algorithms of Coluna do not beat the commercial solver I usually use. Is it normal ?

Yes it is.

Solvers such as Gurobi, Cplex ... are handy powerful black-box tools. They can run a very efficient presolve step to simplify the formulation, automatically apply lots of valid inequalities (such as MIR or cover cuts), choose good branching strategies, or also run heuristics. However, when your formulation reaches a certain size, commercial solvers may run for hours without finding anything. This is the point where you may want to decompose your formulation.

Coluna is a framework, not a solver. It provides algorithms to try column generation on your problem very easily. Then, you can devise your own branch-cut-and-price algorithm on top of Coluna's algorithms. to scale up and hopefully beats the commercial solver.

To start customizing Coluna for your own problem, you can separate valid inequalities or call your own algorithm that optimizes subproblems.

I'm using Gurobi as a subsolver

My license prevents me from running several environments at the same time. How can I use a single environment for the master and all subproblems?

You can use the Gurobi.Env constructor to create a single environment and pass it to the optimizers.

const GRB_ENV = Gurobi.Env()
+
+coluna = optimizer_with_attributes(
+    Coluna.Optimizer,
+    "params" => Coluna.Params(
+        solver = Coluna.Algorithm.TreeSearchAlgorithm() # default branch-cut-and-price
+    ),
+    "default_optimizer" => () -> Gurobi.Optimizer(GRB_ENV)
+);

If you get a Gurobi error 10002, you should wrap the Gurobi environment as a reference to initialize it during runtime instead of compile time (reference).

const GRB_ENV_REF = Ref{Gurobi.Env}()
+
+function __init__()
+    GRB_ENV_REF[] = Gurobi.Env()
+    return nothing
+end
+
+coluna = optimizer_with_attributes(
+    Coluna.Optimizer,
+    "params" => Coluna.Params(
+        solver = Coluna.Algorithm.TreeSearchAlgorithm() # default branch-cut-and-price
+    ),
+    "default_optimizer" => () -> Gurobi.Optimizer(GRB_ENV_REF[])
+);

How to disable all outputs from Gurobi?

You can refer to the following article from Gurobi's knowledge base.

We confirm that adding the following entry in the gurobi.env file works with Gurobi 10+:

LogToConsole 0
diff --git a/previews/PR1107/search_index.js b/previews/PR1107/search_index.js new file mode 100644 index 000000000..2080b4118 --- /dev/null +++ b/previews/PR1107/search_index.js @@ -0,0 +1,3 @@ +var documenterSearchIndex = {"docs": +[{"location":"man/decomposition/#Dantzig-Wolfe-and-Benders-decompositions","page":"Decomposition paradigms","title":"Dantzig-Wolfe and Benders decompositions","text":"","category":"section"},{"location":"man/decomposition/","page":"Decomposition paradigms","title":"Decomposition paradigms","text":"Coluna is a framework to optimize mixed-integer programs that you can decompose. In other words, if you remove the linking constraints or linking variables from your program, you'll get sets of constraints (blocks) that you can solve independently.","category":"page"},{"location":"man/decomposition/","page":"Decomposition paradigms","title":"Decomposition paradigms","text":"Decompositions are typically used on programs whose constraints or variables can be divided into a set of \"easy\" constraints (respectively easy variables) and a set of \"hard\" constraints (respectively hard variables). Decomposing on constraints leads to Dantzig-Wolfe transformation while decomposing on variables leads to the Benders transformation. Both of these decompositions are implemented in Coluna. ","category":"page"},{"location":"man/decomposition/#Dantzig-Wolfe","page":"Decomposition paradigms","title":"Dantzig-Wolfe","text":"","category":"section"},{"location":"man/decomposition/#Original-formulation","page":"Decomposition paradigms","title":"Original formulation","text":"","category":"section"},{"location":"man/decomposition/","page":"Decomposition paradigms","title":"Decomposition paradigms","text":"Let's consider the following coefficient matrix that has a block diagonal structure in gray and some linking constraints in blue :","category":"page"},{"location":"man/decomposition/","page":"Decomposition paradigms","title":"Decomposition paradigms","text":"(Image: Dantzig-Wolfe decomposition)","category":"page"},{"location":"man/decomposition/","page":"Decomposition paradigms","title":"Decomposition paradigms","text":"You penalize the violation of the linking constraints in the objective function. You can then solve the blocks independently.","category":"page"},{"location":"man/decomposition/","page":"Decomposition paradigms","title":"Decomposition paradigms","text":"The Dantzig-Wolfe reformulation gives rise to a master problem with an exponential number of variables. Coluna dynamically generates these variables by solving the subproblems. It's the column generation algorithm.","category":"page"},{"location":"man/decomposition/","page":"Decomposition paradigms","title":"Decomposition paradigms","text":"Let's consider the following original formulation in which we partition variables into two vectors x_1 and x_2 :","category":"page"},{"location":"man/decomposition/","page":"Decomposition paradigms","title":"Decomposition paradigms","text":"beginaligned\nmin quad c_1 x_1 + c_2 x_2 \ntextst quad A_1 x_1 + A_2 x_2 geq b (1)\n D_1 x_1 quad quad quad geq d_1 (2) \n quad quad quad D_2 x_2 geq d_2 (3) \nendaligned","category":"page"},{"location":"man/decomposition/","page":"Decomposition paradigms","title":"Decomposition paradigms","text":"variables x_1 and x_2 are the original variables of the problem (duty: OriginalVar)\nconstraints (1) are the linking constraints (duty: OriginalConstr)\nconstraints (2) shapes the first subproblem (duty: OriginalConstr)\nconstraints (3) shapes the second subproblem (duty: OriginalConstr)","category":"page"},{"location":"man/decomposition/#Master","page":"Decomposition paradigms","title":"Master","text":"","category":"section"},{"location":"man/decomposition/","page":"Decomposition paradigms","title":"Decomposition paradigms","text":"When you apply a Dantzig-Wofe decomposition to this formulation, Coluna reformulates it into the following master problem :","category":"page"},{"location":"man/decomposition/","page":"Decomposition paradigms","title":"Decomposition paradigms","text":"beginaligned\nmin quad sumlimits_q in Q_1 c_1 tildex_1^q lambda_q + sumlimits_q in Q_2 c_2 tildex_2^q lambda_q + fa \ntextst quad sumlimits_q in Q_1 A_1 tildex_1^q lambda_q + sumlimits_q in Q_2 A_2 tildex_2^q lambda_q + a geq b (1)\n L_1 leq sumlimits_q in Q_1 tildez_1lambda_q leq U_1 (2)\n L_2 leq sumlimits_q in Q_2 tildez_2lambda_q leq U_2 (3)\n lambda_q geq 0 quad q in Q_1 cup Q_2\nendaligned","category":"page"},{"location":"man/decomposition/","page":"Decomposition paradigms","title":"Decomposition paradigms","text":"where:","category":"page"},{"location":"man/decomposition/","page":"Decomposition paradigms","title":"Decomposition paradigms","text":"set Q_1 is the index set of the solutions to the first subproblem \nset Q_2 is the index set of the solutions to the second subproblem\nset of the solutions to the first is tildex^q_1_q in Q_1 (duty: MasterRepPricingVar)\nset of the solutions to the second subproblem is tildex^q_2_q in Q_2 respectively (duty: MasterRepPricingVar)\nconstraint (1) is the reformulation of the linking constraints (duty: MasterMixedConstr)\nconstraint (2) is the convexity constraint of the first subproblem and involves the lower L_1 and upper U_1 multiplicity of the subproblem (duty: MasterConvexityConstr)\nconstraint (3) is the convexity constraint of the second subproblem and involves the lower L_2 and upper U_2 multiplicity of the subproblem (duty: MasterConvexityConstr)\nvariables tildez_1 and tildez_2 are representative of pricing setup variables in the master (always equal to 1) (duty: MasterRepPricingVar)\nvariables lambda_q are the columns (duty: MasterCol)\nvariable a is the artificial variable (duty: MasterArtVar)","category":"page"},{"location":"man/decomposition/","page":"Decomposition paradigms","title":"Decomposition paradigms","text":"At the beginning of the column generation algorithm, the master formulation does not have any master columns. Therefore, the master may be infeasible. To prevent this, Coluna adds a local artificial variable a specific to each constraint of the master and a global artificial variable. Costs f of artificial and global artificial variables can be defined in Coluna.Params.","category":"page"},{"location":"man/decomposition/","page":"Decomposition paradigms","title":"Decomposition paradigms","text":"Lower and upper multiplicities of subproblems are 1 by default. However, when some subproblems are identical (same coefficient matrix and right-hand side), you can avoid solving all of them at each iteration by defining only one subproblem and setting its multiplicity to the number of times it appears. See this tutorial to get an example of Dantzig-Wolfe decomposition with identical subproblems. ","category":"page"},{"location":"man/decomposition/#Pricing-Subproblem","page":"Decomposition paradigms","title":"Pricing Subproblem","text":"","category":"section"},{"location":"man/decomposition/","page":"Decomposition paradigms","title":"Decomposition paradigms","text":"Subproblems take the following form (here, it's the first subproblem):","category":"page"},{"location":"man/decomposition/","page":"Decomposition paradigms","title":"Decomposition paradigms","text":"beginaligned\nmin quad barc_1 x_1 + z_1\ntextst quad D_1x_1 geq d_1 (1)\n quad x_1 geq 0\nendaligned","category":"page"},{"location":"man/decomposition/","page":"Decomposition paradigms","title":"Decomposition paradigms","text":"where:","category":"page"},{"location":"man/decomposition/","page":"Decomposition paradigms","title":"Decomposition paradigms","text":"vector barc is the reduced cost of the subproblem variables computed by the column generation algorithm. \nvariables x_1 are the subproblem variables (duty: DwSpPricingVar)\nconstraint (1) is the subproblem constraint (duty: DwSpPureConstr)\nvariable z_1 is the pricing setup variable (always equal to 1) (duty: DwSpSetupVar)","category":"page"},{"location":"man/decomposition/#Benders","page":"Decomposition paradigms","title":"Benders","text":"","category":"section"},{"location":"man/decomposition/#Original-formulation-2","page":"Decomposition paradigms","title":"Original formulation","text":"","category":"section"},{"location":"man/decomposition/","page":"Decomposition paradigms","title":"Decomposition paradigms","text":"Let's consider the following coefficient matrix that has a block diagonal structure in gray and some linking variables in blue :","category":"page"},{"location":"man/decomposition/","page":"Decomposition paradigms","title":"Decomposition paradigms","text":"(Image: Benders decomposition)","category":"page"},{"location":"man/decomposition/","page":"Decomposition paradigms","title":"Decomposition paradigms","text":"The intuition behind Benders decomposition is that some hard problems can become much easier with some of their variables fixed. Benders aims to divide the variables of the problem into two \"levels\": the 1st level variables which, once fixed, make it easier to find a solution for the remaining variables, the so-called 2nd-level variables.","category":"page"},{"location":"man/decomposition/","page":"Decomposition paradigms","title":"Decomposition paradigms","text":"The question is how to set the 1st level variables. Benders' theory proceeds by the successive generation of cuts: given a 1st-level solution, we ask the following questions:","category":"page"},{"location":"man/decomposition/","page":"Decomposition paradigms","title":"Decomposition paradigms","text":"Is the subproblem infeasible? If so, then the 1st-level solution is not correct and must be eliminated. A feasibility cut will be derived from the dual subproblem and added to the master.\nDoes the aggregation of the master and subproblem solutions give rise to an optimal solution to the problem? It depends on a criterion that can be computed. If it is the case, we are done, else, we derive an optimality cut from the dual subproblem and add it into the master.","category":"page"},{"location":"man/decomposition/","page":"Decomposition paradigms","title":"Decomposition paradigms","text":"Formally, given an original MIP:","category":"page"},{"location":"man/decomposition/","page":"Decomposition paradigms","title":"Decomposition paradigms","text":"beginaligned\nmin quad cx + fy \ntextst quad Ax geq a (2) \n Ey geq e (3) \n Bx + Dy geq d (4)\n x y geq 0 x in mathbbZ^n\nendaligned","category":"page"},{"location":"man/decomposition/","page":"Decomposition paradigms","title":"Decomposition paradigms","text":"where:","category":"page"},{"location":"man/decomposition/","page":"Decomposition paradigms","title":"Decomposition paradigms","text":"variables x are the 1st-level variables (duty: OriginalVar)\nvariables y are the 2nd-level variables (duty: OriginalVar)\nconstraints (2) are the 1st-level constraints (duty: OriginalConstr)\nconstraints (3) are the 2nd-level constraints (duty: OriginalConstr)\nconstraints (4) are the linking constraints (duty: OriginalConstr)","category":"page"},{"location":"man/decomposition/#Master-2","page":"Decomposition paradigms","title":"Master","text":"","category":"section"},{"location":"man/decomposition/","page":"Decomposition paradigms","title":"Decomposition paradigms","text":"When you apply a Benders decomposition to this formulation, Coluna reformulates it into the following master problem :","category":"page"},{"location":"man/decomposition/","page":"Decomposition paradigms","title":"Decomposition paradigms","text":"beginaligned\nmin quad cx + sumlimits_k in Keta_k \ntextst quad Ax geq a (5)\n textbenders cuts (6) \n eta_k in mathbbR quad forall k in K\nendaligned","category":"page"},{"location":"man/decomposition/","page":"Decomposition paradigms","title":"Decomposition paradigms","text":"where:","category":"page"},{"location":"man/decomposition/","page":"Decomposition paradigms","title":"Decomposition paradigms","text":"variables x are the 1st-level variables (duty: MasterBendFirstStageVar)\nvariables eta are the second stage cost variables (duty: MasterBendSecondStageCostVar)\nconstraints (5) are the first-level constraints (duty: MasterPureConstr)\nconstraints (6) are the benders cuts (duty: ``)","category":"page"},{"location":"man/decomposition/","page":"Decomposition paradigms","title":"Decomposition paradigms","text":"Note that the eta variables are free.","category":"page"},{"location":"man/decomposition/#Separation-subproblem","page":"Decomposition paradigms","title":"Separation subproblem","text":"","category":"section"},{"location":"man/decomposition/","page":"Decomposition paradigms","title":"Decomposition paradigms","text":"Here is the form of a given separation subproblem:","category":"page"},{"location":"man/decomposition/","page":"Decomposition paradigms","title":"Decomposition paradigms","text":"beginaligned\nmin quad fy \ntextst quad Dy geq d - Bbarx (7) \n Ey geq e (8) \n y geq 0 \nendaligned","category":"page"},{"location":"man/decomposition/","page":"Decomposition paradigms","title":"Decomposition paradigms","text":"where:","category":"page"},{"location":"man/decomposition/","page":"Decomposition paradigms","title":"Decomposition paradigms","text":"variables y are the 2nd-level variables (duty: BendSpSepVar)\nvalues barx are a solution to the master problem \nconstraints (7) are the linking constraints with the 1st-level variables fixed to barx (duty: BendSpTechnologicalConstr)\nconstraints (8) are the 2nd-level constraints (duty: BendSpPureConstr)","category":"page"},{"location":"man/decomposition/","page":"Decomposition paradigms","title":"Decomposition paradigms","text":"Note that in the special case where the master problem is unbounded, the shape of the subproblem is slightly modified. See the API section to get more information.","category":"page"},{"location":"man/decomposition/","page":"Decomposition paradigms","title":"Decomposition paradigms","text":"","category":"page"},{"location":"api/algos/#Algorithms","page":"Algorithms","title":"Algorithms","text":"","category":"section"},{"location":"api/algos/","page":"Algorithms","title":"Algorithms","text":"danger: Danger\nWork in progress.","category":"page"},{"location":"api/algos/#Parameters-of-an-algorithm","page":"Algorithms","title":"Parameters of an algorithm","text":"","category":"section"},{"location":"api/algos/","page":"Algorithms","title":"Algorithms","text":"From a user perspective, the algorithms are objects that contains a set of parameters.","category":"page"},{"location":"api/algos/","page":"Algorithms","title":"Algorithms","text":"The object must inherit from Coluna.AlgoAPI.AbstractAlgorithm. We usually provide a keyword constructor to define default values for parameters and therefore ease the definition of the object.","category":"page"},{"location":"api/algos/","page":"Algorithms","title":"Algorithms","text":"struct MyCustomAlgorithm <: Coluna.AlgoAPI.AbstractAlgorithm\n param1::Int\n param2::Float64\n child_algo::Coluna.AlgoAPI.AbstractAlgorithm\nend\n\n# Help the user to define the algorithm:\nfunction MyCustomAlgorithm(;\n param1 = 1,\n param2 = 2,\n child_algo = AnotherAlgorithm()\n) \n return MyCustomAlgorithm(param1, param2, child_algo)\nend","category":"page"},{"location":"api/algos/","page":"Algorithms","title":"Algorithms","text":"Algorithms can use other algorithms. They are organized as a tree structure.","category":"page"},{"location":"api/algos/","page":"Algorithms","title":"Algorithms","text":"** Example for the TreeSearchAlgorithm **:","category":"page"},{"location":"api/algos/","page":"Algorithms","title":"Algorithms","text":"graph TD\n TreeSearchAlgorithm ---> ColCutGenConquer\n TreeSearchAlgorithm ---> ClassicBranching\n ColCutGenConquer --> ColumnGeneration\n ColCutGenConquer --> RestrictedHeuristicMaster\n RestrictedHeuristicMaster --> SolveIpForm#2\n ColumnGeneration --> SolveLpForm#1\n ColumnGeneration --> SolveIpForm#1\n ColumnGeneration --> CutCallbacks\n","category":"page"},{"location":"api/algos/","page":"Algorithms","title":"Algorithms","text":"Coluna.AlgoAPI.AbstractAlgorithm","category":"page"},{"location":"api/algos/#Coluna.AlgoAPI.AbstractAlgorithm","page":"Algorithms","title":"Coluna.AlgoAPI.AbstractAlgorithm","text":"Supertype for algorithms parameters. Data structures that inherit from this type are intented for the users. The convention is to define the data structure together with a constructor that contains only kw args.\n\nFor instance:\n\n struct MyAlgorithmParams <: AbstractAlgorithmParams\n param1::Int\n param2::Int\n MyAlgorithmParams(; param1::Int = 1, param2::Int = 2) = new(param1, param2)\n end\n\n\n\n\n\n","category":"type"},{"location":"api/algos/#Init","page":"Algorithms","title":"Init","text":"","category":"section"},{"location":"api/algos/#Parameters-checking","page":"Algorithms","title":"Parameters checking","text":"","category":"section"},{"location":"api/algos/","page":"Algorithms","title":"Algorithms","text":"When Coluna starts, it initializes the algorithms chosen by the user. A most important step is to check the consistency of the parameters supplied by the user and the compatibility of the algorithms with the model that will be received (usually MathProg.Reformulation). Algorithms usually have many parameters and are sometimes interdependent and nested. It is crucial to ensure that the user-supplied parameters are correct and give hints to fix them otherwise.","category":"page"},{"location":"api/algos/","page":"Algorithms","title":"Algorithms","text":"The entry-point of the parameter consistency checking is the following method:","category":"page"},{"location":"api/algos/","page":"Algorithms","title":"Algorithms","text":"Coluna.Algorithm.check_alg_parameters","category":"page"},{"location":"api/algos/#Coluna.Algorithm.check_alg_parameters","page":"Algorithms","title":"Coluna.Algorithm.check_alg_parameters","text":"check_alg_parameters(top_algo, reform) -> Vector{Tuple{Symbol, AbstractAlgorithm, Any}}\n\nChecks the consistency of the parameters of the top algorithm and its children algorithms. Returns a vector of tuples (name of the parameter, algorithm, value of the parameter) that lists all the inconsistencies found in the algorithms tree.\n\n\n\n\n\n","category":"function"},{"location":"api/algos/","page":"Algorithms","title":"Algorithms","text":"Developer of an algorithm must implement the following methods:","category":"page"},{"location":"api/algos/","page":"Algorithms","title":"Algorithms","text":"Coluna.Algorithm.check_parameter","category":"page"},{"location":"api/algos/#Units-usage","page":"Algorithms","title":"Units usage","text":"","category":"section"},{"location":"api/algos/","page":"Algorithms","title":"Algorithms","text":"Coluna.AlgoAPI.get_child_algorithms\nColuna.AlgoAPI.get_units_usage","category":"page"},{"location":"api/algos/#Run","page":"Algorithms","title":"Run","text":"","category":"section"},{"location":"api/algos/","page":"Algorithms","title":"Algorithms","text":"Coluna.AlgoAPI.run!","category":"page"},{"location":"api/algos/#Coluna.AlgoAPI.run!","page":"Algorithms","title":"Coluna.AlgoAPI.run!","text":"run!(algo::AbstractAlgorithm, env, model, input)\n\nDefault method to call an algorithm.\n\n\n\n\n\n","category":"function"},{"location":"api/algos/","page":"Algorithms","title":"Algorithms","text":"","category":"page"},{"location":"dynamic_sparse_arrays/#Dynamic-Sparse-Arrays","page":"Dynamic Sparse Arrays","title":"Dynamic Sparse Arrays","text":"","category":"section"},{"location":"dynamic_sparse_arrays/","page":"Dynamic Sparse Arrays","title":"Dynamic Sparse Arrays","text":"This package aims to provide dynamic sparse vectors and matrices in Julia. Unlike the sparse arrays provided in SparseArrays, arrays from this package have unfixed sizes. It means that we can add or delete rows and columns after the instantiation of the array.","category":"page"},{"location":"dynamic_sparse_arrays/","page":"Dynamic Sparse Arrays","title":"Dynamic Sparse Arrays","text":"DynamicSparseArrays is a registered package.","category":"page"},{"location":"dynamic_sparse_arrays/#Introduction","page":"Dynamic Sparse Arrays","title":"Introduction","text":"","category":"section"},{"location":"dynamic_sparse_arrays/","page":"Dynamic Sparse Arrays","title":"Dynamic Sparse Arrays","text":"Coluna is a branch-cut-and-price framework. It means that Coluna's algorithms dynamically generate constraints and variables. Therefore, the coefficient matrix (which is usually sparse) must support the addition of new rows and columns.","category":"page"},{"location":"dynamic_sparse_arrays/","page":"Dynamic Sparse Arrays","title":"Dynamic Sparse Arrays","text":"For this purpose, we implemented the packed-memory array data structure to handle the dynamic sparse vector introduced in the following papers:","category":"page"},{"location":"dynamic_sparse_arrays/","page":"Dynamic Sparse Arrays","title":"Dynamic Sparse Arrays","text":"BENDER, Michael A. et HU, Haodong. An adaptive packed-memory array. ACM Transactions on Database Systems (TODS), 2007, vol. 32, no 4, p. 26.","category":"page"},{"location":"dynamic_sparse_arrays/","page":"Dynamic Sparse Arrays","title":"Dynamic Sparse Arrays","text":"BENDER, Michael A., DEMAINE, Erik D., et FARACH-COLTON, Martin. Cache-oblivious B-trees. SIAM Journal on Computing, 2005, vol. 35, no 2, p. 341-358.","category":"page"},{"location":"dynamic_sparse_arrays/","page":"Dynamic Sparse Arrays","title":"Dynamic Sparse Arrays","text":"On top of the packed-memory array, we implemented the data structure introduced in the following paper to handle the dynamic sparse matrix.","category":"page"},{"location":"dynamic_sparse_arrays/","page":"Dynamic Sparse Arrays","title":"Dynamic Sparse Arrays","text":"WHEATMAN, Brian et XU, Helen. Packed Compressed Sparse Row: A Dynamic Graph Representation. In : 2018 IEEE High Performance extreme Computing Conference (HPEC). IEEE, 2018. p. 1-7.","category":"page"},{"location":"dynamic_sparse_arrays/","page":"Dynamic Sparse Arrays","title":"Dynamic Sparse Arrays","text":"The implementation may vary from the description in the papers. If you find some enhancements, please contact guimarqu.","category":"page"},{"location":"dynamic_sparse_arrays/#Overview","page":"Dynamic Sparse Arrays","title":"Overview","text":"","category":"section"},{"location":"dynamic_sparse_arrays/","page":"Dynamic Sparse Arrays","title":"Dynamic Sparse Arrays","text":"The packed-memory array (PackedMemoryArray{K,T}) is a Vector{Union{Nothing,Tuple{K,T}}} where K is the type of the keys and T is the type of the values. We keep empty entries (i.e. Nothing) in the array to add new values later fast. Non-empty entries are sorted by ascending key order. The array is virtually split into segments of equal size. The goal is to maintain the density (i.e. number of non-empty values/size of the segment) of each segment between pre-defined bounds. We also consider the density of certain unions of segments represented by nodes of the tree in gray. The root node of the tree is the union of all segments, thus the whole array. When one node of the tree has a density outside the allowed bounds, we need to rebalance the parent. It means that we redistribute the empty and non-empty entries to fit the density bounds. If the density bounds are not respected at the root node, we resize the array.","category":"page"},{"location":"dynamic_sparse_arrays/","page":"Dynamic Sparse Arrays","title":"Dynamic Sparse Arrays","text":"On top of the packed-memory array, there is the (PackedCSC{K,T}). This is a particular case of a matrix where values are of type T, row keys of type K, and column keys of type Int. Each column of the matrix (partition) is delimited by a semaphore which is a non-empty entry with a reserved key value defined by the semaphore_key function. In the example, the first partition has its semaphore at position 1, starts at position 2, and finishes at position 9. At position 10, it's the semaphore that signals the beginning of the second partition. In each partition, non-empty entries are sorted by ascending key order.","category":"page"},{"location":"dynamic_sparse_arrays/","page":"Dynamic Sparse Arrays","title":"Dynamic Sparse Arrays","text":"As you can see, the PackedCSC{K,T} is not well suited to the matrix. Indeed, each column is associated with a partition. If you have a column with only zero values, the array will contain a partition with only empty entries. Lastly, the type of column key is Int. Therefore, built on top of PackedCSC{K,T}, MappedCSC{K,L,T} corrects all these shortcomings. This data structure just associates a column key of type L to each partition of PackedCSC{K,T}.","category":"page"},{"location":"dynamic_sparse_arrays/","page":"Dynamic Sparse Arrays","title":"Dynamic Sparse Arrays","text":"
","category":"page"},{"location":"dynamic_sparse_arrays/","page":"Dynamic Sparse Arrays","title":"Dynamic Sparse Arrays","text":"(Image: Dynamic Sparse Arrays)","category":"page"},{"location":"dynamic_sparse_arrays/","page":"Dynamic Sparse Arrays","title":"Dynamic Sparse Arrays","text":"

Architecture overview.

\n
","category":"page"},{"location":"dynamic_sparse_arrays/#References","page":"Dynamic Sparse Arrays","title":"References","text":"","category":"section"},{"location":"dynamic_sparse_arrays/","page":"Dynamic Sparse Arrays","title":"Dynamic Sparse Arrays","text":" CurrentModule = DynamicSparseArrays","category":"page"},{"location":"dynamic_sparse_arrays/","page":"Dynamic Sparse Arrays","title":"Dynamic Sparse Arrays","text":"dynamicsparsevec\ndynamicsparse","category":"page"},{"location":"dynamic_sparse_arrays/#DynamicSparseArrays.dynamicsparsevec","page":"Dynamic Sparse Arrays","title":"DynamicSparseArrays.dynamicsparsevec","text":"dynamicsparsevec(I, V, [combine, n])\n\nCreates a dynamic sparse vector S of length n such that S[I[k]] = S[V[k]]. The combine operator is used to combine the values of V that have same id in I.\n\n\n\n\n\n","category":"function"},{"location":"dynamic_sparse_arrays/#DynamicSparseArrays.dynamicsparse","page":"Dynamic Sparse Arrays","title":"DynamicSparseArrays.dynamicsparse","text":"dynamicsparse(I, J, V, [m, n])\n\nCreates a dynamic sparse matrix S of dimensions m×n such that S[I[k], J[k]] = V[k].\n\n\n\n\n\ndynamicsparse(Ti, Tj, Tv [; fill_mode = true])\n\nCreates an empty dynamic sparse matrix with row keys of type Ti, column keys of type Tj, and non-zero values of type Tv. By default, the matrix is returned in a \"fill mode\". This allows the user to fill the matrix with non-zero entries. All the write operations are stored in a Dict. When the matrix is filled, the user must call closefillmode!(matrix).\n\n\n\n\n\n","category":"function"},{"location":"dynamic_sparse_arrays/","page":"Dynamic Sparse Arrays","title":"Dynamic Sparse Arrays","text":"","category":"page"},{"location":"api/branching/","page":"Branching","title":"Branching","text":"CurrentModule = Coluna","category":"page"},{"location":"api/branching/#Branching-API","page":"Branching","title":"Branching API","text":"","category":"section"},{"location":"api/branching/","page":"Branching","title":"Branching","text":"Coluna provides default implementations for the branching algorithm and the strong branching algorithms. Both implementations are built on top of an API that we describe here.","category":"page"},{"location":"api/branching/#Candidates-selection","page":"Branching","title":"Candidates selection","text":"","category":"section"},{"location":"api/branching/","page":"Branching","title":"Branching","text":"Candidates selection is the first step (and sometimes the only step) of any branching algorithm. It chooses what are the possible branching constraints that will generate the children of the current node of the branch-and-bound tree.","category":"page"},{"location":"api/branching/","page":"Branching","title":"Branching","text":"Coluna provides the following function for this step:","category":"page"},{"location":"api/branching/","page":"Branching","title":"Branching","text":"Branching.select!","category":"page"},{"location":"api/branching/#Coluna.Branching.select!","page":"Branching","title":"Coluna.Branching.select!","text":"Candidates selection for branching algorithms.\n\n\n\n\n\n","category":"function"},{"location":"api/branching/","page":"Branching","title":"Branching","text":"It works as follows.","category":"page"},{"location":"api/branching/","page":"Branching","title":"Branching","text":"The user chooses one or several branching rules that indicate the type of branching he wants to perform. This may be on a single variable or on a linear expression of variables for instance. ","category":"page"},{"location":"api/branching/","page":"Branching","title":"Branching","text":"The branching rule must implement apply_branching_rule that generates the candidates. The latter are the variables or expressions on which the branch-and-bound may branch with additional information that is requested by Coluna's branching implementation through the API.","category":"page"},{"location":"api/branching/","page":"Branching","title":"Branching","text":"Then, candidates are sorted according to a selection criterion (e.g. most fractional). The algorithm keeps a certain number of candidates (one for classic branching, and several for strong branching). It generates the children of each candidate kept. At last, it returns the candidates kept.","category":"page"},{"location":"api/branching/#Branching-rule","page":"Branching","title":"Branching rule","text":"","category":"section"},{"location":"api/branching/","page":"Branching","title":"Branching","text":"Branching.AbstractBranchingRule\nBranching.apply_branching_rule","category":"page"},{"location":"api/branching/#Coluna.Branching.AbstractBranchingRule","page":"Branching","title":"Coluna.Branching.AbstractBranchingRule","text":"Supertype of branching rules.\n\n\n\n\n\n","category":"type"},{"location":"api/branching/#Coluna.Branching.apply_branching_rule","page":"Branching","title":"Coluna.Branching.apply_branching_rule","text":"Returns all candidates that satisfy a given branching rule.\n\n\n\n\n\n","category":"function"},{"location":"api/branching/#Candidate","page":"Branching","title":"Candidate","text":"","category":"section"},{"location":"api/branching/","page":"Branching","title":"Branching","text":"Branching.AbstractBranchingCandidate\nBranching.getdescription\nBranching.get_lhs\nBranching.get_local_id\nBranching.generate_children!","category":"page"},{"location":"api/branching/#Coluna.Branching.AbstractBranchingCandidate","page":"Branching","title":"Coluna.Branching.AbstractBranchingCandidate","text":"A branching candidate is a data structure that contain all information needed to generate children of a node.\n\n\n\n\n\n","category":"type"},{"location":"api/branching/#Coluna.Branching.getdescription","page":"Branching","title":"Coluna.Branching.getdescription","text":"Returns a string which serves to print the branching rule in the logs.\n\n\n\n\n\n","category":"function"},{"location":"api/branching/#Coluna.Branching.get_lhs","page":"Branching","title":"Coluna.Branching.get_lhs","text":"Returns the left-hand side of the candidate.\n\n\n\n\n\n","category":"function"},{"location":"api/branching/#Coluna.Branching.get_local_id","page":"Branching","title":"Coluna.Branching.get_local_id","text":"Returns the generation id of the candidiate.\n\n\n\n\n\n","category":"function"},{"location":"api/branching/#Coluna.Branching.generate_children!","page":"Branching","title":"Coluna.Branching.generate_children!","text":"generate_children!(branching_context, branching_candidate, lhs, env, reform, node)\n\nThis method generates the children of a node described by branching_candidate.\n\n\n\n\n\n","category":"function"},{"location":"api/branching/#Selection-criterion","page":"Branching","title":"Selection criterion","text":"","category":"section"},{"location":"api/branching/","page":"Branching","title":"Branching","text":"Branching.AbstractSelectionCriterion\nBranching.select_candidates!","category":"page"},{"location":"api/branching/#Coluna.Branching.AbstractSelectionCriterion","page":"Branching","title":"Coluna.Branching.AbstractSelectionCriterion","text":"Supertype of selection criteria of branching candidates.\n\nA selection criterion provides a way to keep only the most promising branching candidates. To create a new selection criterion, one needs to create a subtype of AbstractSelectionCriterion and implements the method select_candidates!.\n\n\n\n\n\n","category":"type"},{"location":"api/branching/#Coluna.Branching.select_candidates!","page":"Branching","title":"Coluna.Branching.select_candidates!","text":"Sort branching candidates according to the selection criterion and remove excess ones.\n\n\n\n\n\n","category":"function"},{"location":"api/branching/#Branching-API-2","page":"Branching","title":"Branching API","text":"","category":"section"},{"location":"api/branching/","page":"Branching","title":"Branching","text":"Branching.get_selection_nb_candidates\nBranching.branching_context_type\nBranching.new_context\nBranching.get_int_tol\nBranching.get_rules\nBranching.get_selection_criterion","category":"page"},{"location":"api/branching/#Coluna.Branching.get_selection_nb_candidates","page":"Branching","title":"Coluna.Branching.get_selection_nb_candidates","text":"Returns the number of candidates that the candidates selection step must return.\n\n\n\n\n\n","category":"function"},{"location":"api/branching/#Coluna.Branching.branching_context_type","page":"Branching","title":"Coluna.Branching.branching_context_type","text":"Returns the type of context required by the algorithm parameters.\n\n\n\n\n\n","category":"function"},{"location":"api/branching/#Coluna.Branching.new_context","page":"Branching","title":"Coluna.Branching.new_context","text":"Creates a context.\n\n\n\n\n\n","category":"function"},{"location":"api/branching/#Coluna.Branching.get_int_tol","page":"Branching","title":"Coluna.Branching.get_int_tol","text":"Returns integer tolerance.\n\n\n\n\n\n","category":"function"},{"location":"api/branching/#Coluna.Branching.get_rules","page":"Branching","title":"Coluna.Branching.get_rules","text":"Returns branching rules.\n\n\n\n\n\n","category":"function"},{"location":"api/branching/#Coluna.Branching.get_selection_criterion","page":"Branching","title":"Coluna.Branching.get_selection_criterion","text":"Returns the selection criterion.\n\n\n\n\n\n","category":"function"},{"location":"api/branching/","page":"Branching","title":"Branching","text":"Method advanced_select! is part of the API but presented just below.","category":"page"},{"location":"api/branching/#Advanced-candidates-selection","page":"Branching","title":"Advanced candidates selection","text":"","category":"section"},{"location":"api/branching/","page":"Branching","title":"Branching","text":"If the candidates' selection returns several candidates will all their children, advanced candidates selection must keep only one of them.","category":"page"},{"location":"api/branching/","page":"Branching","title":"Branching","text":"The advanced candidates' selection is the place to evaluate the children to get relevant additional key performance indicators about each branching candidate.","category":"page"},{"location":"api/branching/","page":"Branching","title":"Branching","text":"Coluna provides the following function for this step.","category":"page"},{"location":"api/branching/","page":"Branching","title":"Branching","text":"Branching.advanced_select!","category":"page"},{"location":"api/branching/#Coluna.Branching.advanced_select!","page":"Branching","title":"Coluna.Branching.advanced_select!","text":"Advanced candidates selection that selects candidates by evaluating their children.\n\n\n\n\n\n","category":"function"},{"location":"api/branching/","page":"Branching","title":"Branching","text":"Coluna has two default implementations for this method:","category":"page"},{"location":"api/branching/","page":"Branching","title":"Branching","text":"for the classic branching that does nothing because the candidates selection returns 1 candidate\nfor the strong branching that performs several evaluations of the candidates.","category":"page"},{"location":"api/branching/","page":"Branching","title":"Branching","text":"Let us focus on the strong branching. Strong branching is a procedure that heuristically selects a branching constraint that potentially gives the best progress of the dual bound. The procedure selects a collection of branching candidates based on their branching rule (done in classic candidate selection) and their score (done in advanced candidate selection). Then, the procedure evaluates the progress of the dual bound in both branches of each branching candidate by solving both potential children using a conquer algorithm. The candidate that has the largest score is chosen to be the branching constraint.","category":"page"},{"location":"api/branching/","page":"Branching","title":"Branching","text":"However, the score can be difficult to compute. For instance, when the score is based on dual bound improvement produced by the branching constraint which is time-consuming to evaluate in the context of column generation Therefore, one can let the branching algorithm quickly estimate the score of each candidate and retain the most promising branching candidates. This is called a phase. The goal is to first evaluate a large number of candidates with a very fast conquer algorithm and retain a certain number of promising ones. Then, over the phases, it evaluates the improvement with a more precise conquer algorithm and restricts the number of retained candidates until only one is left.","category":"page"},{"location":"api/branching/#Strong-Branching-API","page":"Branching","title":"Strong Branching API","text":"","category":"section"},{"location":"api/branching/","page":"Branching","title":"Branching","text":"Branching.get_units_to_restore_for_conquer\nBranching.get_phases\nBranching.get_score\nBranching.get_conquer\nBranching.get_max_nb_candidates","category":"page"},{"location":"api/branching/#Coluna.Branching.get_units_to_restore_for_conquer","page":"Branching","title":"Coluna.Branching.get_units_to_restore_for_conquer","text":"Returns the storage units that must be restored by the conquer algorithm called by the strong branching phase.\n\n\n\n\n\n","category":"function"},{"location":"api/branching/#Coluna.Branching.get_phases","page":"Branching","title":"Coluna.Branching.get_phases","text":"Returns all phases context of the strong branching algorithm.\n\n\n\n\n\n","category":"function"},{"location":"api/branching/#Coluna.Branching.get_score","page":"Branching","title":"Coluna.Branching.get_score","text":"Returns the type of score used to rank the candidates at a given strong branching phase.\n\n\n\n\n\n","category":"function"},{"location":"api/branching/#Coluna.Branching.get_conquer","page":"Branching","title":"Coluna.Branching.get_conquer","text":"Returns the conquer algorithm used to evaluate the candidate's children at a given strong branching phase.\n\n\n\n\n\n","category":"function"},{"location":"api/branching/#Coluna.Branching.get_max_nb_candidates","page":"Branching","title":"Coluna.Branching.get_max_nb_candidates","text":"Returns the maximum number of candidates kept at the end of a given strong branching phase.\n\n\n\n\n\n","category":"function"},{"location":"api/branching/","page":"Branching","title":"Branching","text":"The following methods are part of the API but have a default implementation. We advise you to not change them.","category":"page"},{"location":"api/branching/","page":"Branching","title":"Branching","text":"Branching.perform_branching_phase!\nBranching.eval_candidate!\nBranching.eval_child_of_candidate!","category":"page"},{"location":"api/branching/#Coluna.Branching.eval_child_of_candidate!","page":"Branching","title":"Coluna.Branching.eval_child_of_candidate!","text":"Evaluate children of a candidate.\n\n\n\n\n\n","category":"function"},{"location":"api/branching/#Score","page":"Branching","title":"Score","text":"","category":"section"},{"location":"api/branching/","page":"Branching","title":"Branching","text":"Branching.AbstractBranchingScore\nBranching.compute_score","category":"page"},{"location":"api/branching/#Coluna.Branching.AbstractBranchingScore","page":"Branching","title":"Coluna.Branching.AbstractBranchingScore","text":"Supertype of branching scores.\n\n\n\n\n\n","category":"type"},{"location":"api/branching/#Coluna.Branching.compute_score","page":"Branching","title":"Coluna.Branching.compute_score","text":"Returns the score of a candidate.\n\n\n\n\n\n","category":"function"},{"location":"api/branching/","page":"Branching","title":"Branching","text":"","category":"page"},{"location":"api/storage/","page":"Storage","title":"Storage","text":"EditURL = \"storage.jl\"","category":"page"},{"location":"api/storage/#Storage-API","page":"Storage","title":"Storage API","text":"","category":"section"},{"location":"api/storage/","page":"Storage","title":"Storage","text":" CurrentModule = Coluna","category":"page"},{"location":"api/storage/#API","page":"Storage","title":"API","text":"","category":"section"},{"location":"api/storage/","page":"Storage","title":"Storage","text":"To summarize from a developer's point of view, there is a one-to-one correspondence between storage unit types and record types. This correspondence is implemented by methods record_type(StorageUnitType) and storage_unit_type(RecordType).","category":"page"},{"location":"api/storage/","page":"Storage","title":"Storage","text":"The developer must also implement methods storage_unit(StorageUnitType) and record(RecordType, id, model, storage_unit) that must call constructors of the custom storage unit and one of its associated records. Arguments of record allow the developer to record the state of entities from both the storage unit and the model.","category":"page"},{"location":"api/storage/","page":"Storage","title":"Storage","text":"At last, he must implement restore_from_record!(storage_unit, model, record) to restore the state of the entities represented by the storage unit. Entities can be in the storage unit, the model, or both of them.","category":"page"},{"location":"api/storage/","page":"Storage","title":"Storage","text":" ColunaBase.record_type\n ColunaBase.storage_unit_type\n ColunaBase.storage_unit\n ColunaBase.record\n ColunaBase.restore_from_record!","category":"page"},{"location":"api/storage/#Coluna.ColunaBase.record_type","page":"Storage","title":"Coluna.ColunaBase.record_type","text":"Returns the type of record stored in a type of storage unit.\n\n\n\n\n\n","category":"function"},{"location":"api/storage/#Coluna.ColunaBase.storage_unit_type","page":"Storage","title":"Coluna.ColunaBase.storage_unit_type","text":"Returns the type of storage unit that stores a type of record.\n\n\n\n\n\n","category":"function"},{"location":"api/storage/#Coluna.ColunaBase.storage_unit","page":"Storage","title":"Coluna.ColunaBase.storage_unit","text":"Returns a storage unit from a given type.\n\n\n\n\n\n","category":"function"},{"location":"api/storage/#Coluna.ColunaBase.record","page":"Storage","title":"Coluna.ColunaBase.record","text":"Creates a record of information from the model or a storage unit.\n\n\n\n\n\n","category":"function"},{"location":"api/storage/#Coluna.ColunaBase.restore_from_record!","page":"Storage","title":"Coluna.ColunaBase.restore_from_record!","text":"Restore information from the model or the storage unit that is recorded in a record.\n\n\n\n\n\n","category":"function"},{"location":"api/storage/","page":"Storage","title":"Storage","text":"","category":"page"},{"location":"api/storage/","page":"Storage","title":"Storage","text":"This page was generated using Literate.jl.","category":"page"},{"location":"api/storage/","page":"Storage","title":"Storage","text":"","category":"page"},{"location":"start/other_pbs/","page":"Other classic problems","title":"Other classic problems","text":"Here is a non-exhaustive list of classic problems tackled with Coluna:","category":"page"},{"location":"start/other_pbs/","page":"Other classic problems","title":"Other classic problems","text":"Generalized Assignement and some variants using pricing callback and cut callback\nBin Packing","category":"page"},{"location":"start/other_pbs/","page":"Other classic problems","title":"Other classic problems","text":"","category":"page"},{"location":"start/start/","page":"Column generation","title":"Column generation","text":"EditURL = \"start.jl\"","category":"page"},{"location":"start/start/#tuto_gen_assignement","page":"Column generation","title":"Column generation with the Generalized Assignment Problem","text":"","category":"section"},{"location":"start/start/","page":"Column generation","title":"Column generation","text":"This quick start guide introduces the main features of Coluna through the example of the Generalized Assignment Problem.","category":"page"},{"location":"start/start/#Classic-model-solved-with-MIP-solver","page":"Column generation","title":"Classic model solved with MIP solver","text":"","category":"section"},{"location":"start/start/","page":"Column generation","title":"Column generation","text":"Consider a set of machines M and a set of jobs J. A machine m has a resource capacity Q_m . A job j assigned to a machine m has a cost c_mj and consumes w_mj resource units of the machine m. The goal is to minimize the sum of job costs while assigning each job to a machine and not exceeding the capacity of each machine.","category":"page"},{"location":"start/start/","page":"Column generation","title":"Column generation","text":"Let x_mj equal to one if job j is assigned to machine m; 0 otherwise. The problem has the original formulation:","category":"page"},{"location":"start/start/","page":"Column generation","title":"Column generation","text":"beginalignedat4\ntextGAP equiv min mathrlapsum_m in Msum_j in J c_mj x_mj \ntextst sum_m in M x_mj = 1 quad j in J \n sum_j in J w_mj x_mj leq Q_m  quad quad m in M \n x_mj in 01 m in M j in J\nendalignedat","category":"page"},{"location":"start/start/","page":"Column generation","title":"Column generation","text":"Let us consider the following instance.","category":"page"},{"location":"start/start/","page":"Column generation","title":"Column generation","text":"M = 1:3;\nJ = 1:15;\nc = [12.7 22.5 8.9 20.8 13.6 12.4 24.8 19.1 11.5 17.4 24.7 6.8 21.7 14.3 10.5; 19.1 24.8 24.4 23.6 16.1 20.6 15.0 9.5 7.9 11.3 22.6 8.0 21.5 14.7 23.2; 18.6 14.1 22.7 9.9 24.2 24.5 20.8 12.9 17.7 11.9 18.7 10.1 9.1 8.9 7.7; 13.1 16.2 16.8 16.7 9.0 16.9 17.9 12.1 17.5 22.0 19.9 14.6 18.2 19.6 24.2];\nw = [61 70 57 82 51 74 98 64 86 80 69 79 60 76 78; 50 57 61 83 81 79 63 99 82 59 83 91 59 99 91;91 81 66 63 59 81 87 90 65 55 57 68 92 91 86; 62 79 73 60 75 66 68 99 69 60 56 100 67 68 54];\nQ = [1020 1460 1530];\nnothing #hide","category":"page"},{"location":"start/start/","page":"Column generation","title":"Column generation","text":"We write the model with JuMP, a domain-specific modeling language for mathematical optimization embedded in Julia. We optimize with GLPK. If you are not familiar with the JuMP package, you may want to check its documentation.","category":"page"},{"location":"start/start/","page":"Column generation","title":"Column generation","text":"using JuMP, GLPK;\nnothing #hide","category":"page"},{"location":"start/start/","page":"Column generation","title":"Column generation","text":"A JuMP model for the original formulation is:","category":"page"},{"location":"start/start/","page":"Column generation","title":"Column generation","text":"model = Model(GLPK.Optimizer)\n@variable(model, x[m in M, j in J], Bin);\n@constraint(model, cov[j in J], sum(x[m, j] for m in M) >= 1);\n@constraint(model, knp[m in M], sum(w[m, j] * x[m, j] for j in J) <= Q[m]);\n@objective(model, Min, sum(c[m, j] * x[m, j] for m in M, j in J));\nnothing #hide","category":"page"},{"location":"start/start/","page":"Column generation","title":"Column generation","text":"We optimize the instance and retrieve the objective value.","category":"page"},{"location":"start/start/","page":"Column generation","title":"Column generation","text":"optimize!(model);\nobjective_value(model)","category":"page"},{"location":"start/start/#Try-column-generation-easily-with-Coluna-and-BlockDecomposition","page":"Column generation","title":"Try column generation easily with Coluna and BlockDecomposition","text":"","category":"section"},{"location":"start/start/","page":"Column generation","title":"Column generation","text":"This model has a block structure: each knapsack constraint defines an independent block and the set-partitioning constraints couple these independent blocks. By applying the Dantzig-Wolfe reformulation, each knapsack constraint forms a tractable subproblem and the set-partitioning constraints are handled in a master problem.","category":"page"},{"location":"start/start/","page":"Column generation","title":"Column generation","text":"To write the model, you need JuMP and BlockDecomposition. The latter is an extension built on top of JuMP to model Dantzig-Wolfe and Benders decompositions. You will find more documentation about BlockDecomposition in the Decomposition & reformulation To optimize the problem, you need Coluna and a Julia package that provides a MIP solver such as GLPK.","category":"page"},{"location":"start/start/","page":"Column generation","title":"Column generation","text":"Since we have already loaded JuMP and GLPK, we just need:","category":"page"},{"location":"start/start/","page":"Column generation","title":"Column generation","text":"using BlockDecomposition, Coluna;\nnothing #hide","category":"page"},{"location":"start/start/","page":"Column generation","title":"Column generation","text":"Next, you instantiate the solver and define the algorithm that you use to optimize the problem. In this case, the algorithm is a classic branch-and-price provided by Coluna.","category":"page"},{"location":"start/start/","page":"Column generation","title":"Column generation","text":"coluna = optimizer_with_attributes(\n Coluna.Optimizer,\n \"params\" => Coluna.Params(\n solver = Coluna.Algorithm.TreeSearchAlgorithm() # default branch-cut-and-price\n ),\n \"default_optimizer\" => GLPK.Optimizer # GLPK for the master & the subproblems\n);\nnothing #hide","category":"page"},{"location":"start/start/","page":"Column generation","title":"Column generation","text":"In BlockDecomposition, an axis is an index set of subproblems. Let M_axis be the index set of machines; it defines an axis along which we can implement the desired decomposition.","category":"page"},{"location":"start/start/","page":"Column generation","title":"Column generation","text":"@axis(M_axis, M);\nnothing #hide","category":"page"},{"location":"start/start/","page":"Column generation","title":"Column generation","text":"In this example, the axis M_axis defines one knapsack subproblem for each machine. For instance, the first machine index is 1 and is of type BlockDecomposition.AxisId:","category":"page"},{"location":"start/start/","page":"Column generation","title":"Column generation","text":"M_axis[1]\n\ntypeof(M_axis[1])","category":"page"},{"location":"start/start/","page":"Column generation","title":"Column generation","text":"Jobs are not involved in the decomposition, set J of jobs thus stays as a classic range.","category":"page"},{"location":"start/start/","page":"Column generation","title":"Column generation","text":"The model takes the form:","category":"page"},{"location":"start/start/","page":"Column generation","title":"Column generation","text":"model = BlockModel(coluna);\nnothing #hide","category":"page"},{"location":"start/start/","page":"Column generation","title":"Column generation","text":"You can write BlockModel(coluna; direct_model = true) to pass names of variables and constraints to Coluna.","category":"page"},{"location":"start/start/","page":"Column generation","title":"Column generation","text":"@variable(model, x[m in M_axis, j in J], Bin);\n@constraint(model, cov[j in J], sum(x[m, j] for m in M_axis) >= 1);\n@constraint(model, knp[m in M_axis], sum(w[m, j] * x[m, j] for j in J) <= Q[m]);\n@objective(model, Min, sum(c[m, j] * x[m, j] for m in M_axis, j in J));\nnothing #hide","category":"page"},{"location":"start/start/","page":"Column generation","title":"Column generation","text":"This is the same model as above except that we use a BlockModel instead of a Model and M_axis as the set of machines instead of M. Therefore, BlockDecomposition will know which variables and constraints are involved in subproblems because one of their indices is a BlockDecomposition.AxisId.","category":"page"},{"location":"start/start/","page":"Column generation","title":"Column generation","text":"You then apply a Dantzig-Wolfe decomposition along M_axis:","category":"page"},{"location":"start/start/","page":"Column generation","title":"Column generation","text":"@dantzig_wolfe_decomposition(model, decomposition, M_axis)","category":"page"},{"location":"start/start/","page":"Column generation","title":"Column generation","text":"where decomposition is a variable that contains information about the decomposition.","category":"page"},{"location":"start/start/","page":"Column generation","title":"Column generation","text":"decomposition","category":"page"},{"location":"start/start/","page":"Column generation","title":"Column generation","text":"Once the decomposition is defined, you can retrieve the master and the subproblems to give additional information to the solver.","category":"page"},{"location":"start/start/","page":"Column generation","title":"Column generation","text":"master = getmaster(decomposition)\nsubproblems = getsubproblems(decomposition)","category":"page"},{"location":"start/start/","page":"Column generation","title":"Column generation","text":"The multiplicity of a subproblem is the number of times that the same independent block shaped by the subproblem appears in the model. This multiplicity also specifies the number of solutions to the subproblem that can appear in the solution to the original problem.","category":"page"},{"location":"start/start/","page":"Column generation","title":"Column generation","text":"In this GAP instance, the upper multiplicity is 1 because every subproblem is different, i.e., every machine is different and used at most once.","category":"page"},{"location":"start/start/","page":"Column generation","title":"Column generation","text":"The lower multiplicity is 0 because a machine may stay unused. The multiplicity specifications take the form:","category":"page"},{"location":"start/start/","page":"Column generation","title":"Column generation","text":"specify!.(subproblems, lower_multiplicity = 0, upper_multiplicity = 1)\ngetsubproblems(decomposition)","category":"page"},{"location":"start/start/","page":"Column generation","title":"Column generation","text":"The model is now fully defined. To solve it, you need to call:","category":"page"},{"location":"start/start/","page":"Column generation","title":"Column generation","text":"optimize!(model)","category":"page"},{"location":"start/start/","page":"Column generation","title":"Column generation","text":"You can find more information about the output of the column generation algorithm ColumnGeneration.","category":"page"},{"location":"start/start/","page":"Column generation","title":"Column generation","text":"Finally, you can retrieve the solution to the original formulation with JuMP methods. For example, if we want to know if job 3 is assigned to machine 1:","category":"page"},{"location":"start/start/","page":"Column generation","title":"Column generation","text":"value(x[1,3])","category":"page"},{"location":"start/start/","page":"Column generation","title":"Column generation","text":"","category":"page"},{"location":"start/start/","page":"Column generation","title":"Column generation","text":"This page was generated using Literate.jl.","category":"page"},{"location":"start/start/","page":"Column generation","title":"Column generation","text":"","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"CurrentModule = Coluna","category":"page"},{"location":"api/benders/#api_benders","page":"Benders","title":"Benders cut generation","text":"","category":"section"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"Coluna provides an interface and generic functions to implement a Benders cut generation algorithm.","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"In this section, we are first going to present the generic functions, the implementation with some theory backgrounds and then give the references of the interface. The default implementation is based on the paper of ","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"You can find the generic functions and the interface in the Benders submodule and the default implementation in the Algorithm submodule at src/Algorithm/benders.","category":"page"},{"location":"api/benders/#Context","page":"Benders","title":"Context","text":"","category":"section"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"The Benders submodule provides an interface and generic functions to implement a benders cut generation algorithm. The implementation depends on an object called context.","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"Coluna.Benders.AbstractBendersContext","category":"page"},{"location":"api/benders/#Coluna.Benders.AbstractBendersContext","page":"Benders","title":"Coluna.Benders.AbstractBendersContext","text":"Supertype for the objects to which belongs the implementation of the Benders cut generation and that stores any kind of information during the execution of the Bender cut generation algorithm.\n\n\n\n\n\n","category":"type"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"Benders provides two types of context:","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"Coluna.Algorithm.BendersContext\nColuna.Algorithm.BendersPrinterContext","category":"page"},{"location":"api/benders/#Coluna.Algorithm.BendersContext","page":"Benders","title":"Coluna.Algorithm.BendersContext","text":"BendersContext(reformulation, algo_params) -> BendersContext\n\nDefault implementation of the Benders algorithm.\n\n\n\n\n\n","category":"type"},{"location":"api/benders/#Coluna.Algorithm.BendersPrinterContext","page":"Benders","title":"Coluna.Algorithm.BendersPrinterContext","text":"BendersPrinterContext(reformulation, algo_params) -> BendersPrinterContext\n\nCreates a context to run the default implementation of the Benders algorithm together with a printer that prints information about the algorithm execution.\n\n\n\n\n\n","category":"type"},{"location":"api/benders/#Generic-functions","page":"Benders","title":"Generic functions","text":"","category":"section"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"Generic functions are the core of the Benders cut generation algorithm. There are three generic functions:","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"Coluna.Benders.run_benders_loop!","category":"page"},{"location":"api/benders/#Coluna.Benders.run_benders_loop!","page":"Benders","title":"Coluna.Benders.run_benders_loop!","text":"Main loop of the Benders cut generation algorithm.\n\n\n\n\n\n","category":"function"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"See ...","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"Coluna.Benders.run_benders_iteration!","category":"page"},{"location":"api/benders/#Coluna.Benders.run_benders_iteration!","page":"Benders","title":"Coluna.Benders.run_benders_iteration!","text":"Runs one iteration of a Benders cut generation algorithm.\n\n\n\n\n\n","category":"function"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"See ...","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"These functions are independent of any other submodule of Coluna. You can use them to implement your own Benders cut generation algorithm.","category":"page"},{"location":"api/benders/#Reformulation","page":"Benders","title":"Reformulation","text":"","category":"section"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"The default implementation works with a reformulated problem contained in MathProg.Reformulation where master and subproblems are MathProg.Formulation objects.","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"The master has the following form:","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"beginaligned\nmin quad cx + sum_k in K eta_k \ntextst quad Ax geq a (1) \n text benders cuts (2) \n l_1 leq x leq u_1 (3) \n eta_k in mathbbR forall k in K quad (4)\nendaligned","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"where x are first-stage variables, eta_k is the second-stage cost variable for the subproblem k, constraints (1) are the first-stage constraints, constraints (2) are the Benders cuts, constraints (3) are the bounds on the first-stage variables, and expression (4) shows that second-stage variables are free.","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"The subproblems have the following form:","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"beginaligned\nmin quad fy + colorgray mathbf1z + mathbf1z \ntextst quad Dy colorgray + z geq d - Bbarx (5) quad colorblue(pi) \n Ey colorgray + z geq e (6) quad colorblue(rho) \n l_2 leq y leq u_2 (7) quad colorblue(sigma)\nendaligned","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"where y are second-stage variables, z and z are artificial variables (in grey because they are deactivated by default), constraints (5) are the reformulation of linking constraints using the first-stage solution barx, constraints (6) are the second-stage constraints, and constraints (7) are the bounds on the second-stage variables. In blue, we define the dual variables associated to these constraints.","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"References:","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"Coluna.Benders.is_minimization\nColuna.Benders.get_reform\nColuna.Benders.get_master\nColuna.Benders.get_benders_subprobs","category":"page"},{"location":"api/benders/#Coluna.Benders.is_minimization","page":"Benders","title":"Coluna.Benders.is_minimization","text":"Returns true if the objective sense is minimization, false otherwise.\n\n\n\n\n\n","category":"function"},{"location":"api/benders/#Coluna.Benders.get_reform","page":"Benders","title":"Coluna.Benders.get_reform","text":"Returns Benders reformulation.\n\n\n\n\n\n","category":"function"},{"location":"api/benders/#Coluna.Benders.get_master","page":"Benders","title":"Coluna.Benders.get_master","text":"Returns the master problem.\n\n\n\n\n\n","category":"function"},{"location":"api/benders/#Coluna.Benders.get_benders_subprobs","page":"Benders","title":"Coluna.Benders.get_benders_subprobs","text":"Returns the separation subproblems.\n\n\n\n\n\n","category":"function"},{"location":"api/benders/#Main-loop","page":"Benders","title":"Main loop","text":"","category":"section"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"This is a description of how the Coluna.Benders.run_benders_loop! generic function behaves with the default implementation.","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"The loop stops if one of the following conditions is met:","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"the master is infeasible\na separation subproblem is infeasible\nthe time limit is reached\nthe maximum number of iterations is reached\nno new cut is generated","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"The default implementation returns:","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"Coluna.Algorithm.BendersOutput","category":"page"},{"location":"api/benders/#Coluna.Algorithm.BendersOutput","page":"Benders","title":"Coluna.Algorithm.BendersOutput","text":"Output of the default implementation of the Benders algorithm.\n\nIt contains:\n\ninfeasible: the original problem is infeasible\ntime_limit_reached: the time limit was reached\nmlp: the final bound obtained with the Benders cut algorithm\nip_primal_sol: the best primal solution to the original problem found by the Benders cut algorithm\n\n\n\n\n\n","category":"type"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"References:","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"Coluna.Benders.setup_reformulation!\nColuna.Benders.stop_benders\nColuna.Benders.after_benders_iteration\nColuna.Benders.AbstractBendersOutput\nColuna.Benders.benders_output_type\nColuna.Benders.new_output","category":"page"},{"location":"api/benders/#Coluna.Benders.setup_reformulation!","page":"Benders","title":"Coluna.Benders.setup_reformulation!","text":"Prepares the reformulation before starting the Benders cut generation algorithm.\n\n\n\n\n\n","category":"function"},{"location":"api/benders/#Coluna.Benders.stop_benders","page":"Benders","title":"Coluna.Benders.stop_benders","text":"Returns true if the Benders cut generation algorithm must stop, false otherwise.\n\n\n\n\n\n","category":"function"},{"location":"api/benders/#Coluna.Benders.after_benders_iteration","page":"Benders","title":"Coluna.Benders.after_benders_iteration","text":"Placeholder method called after each iteration of the Benders cut generation algorithm.\n\n\n\n\n\n","category":"function"},{"location":"api/benders/#Coluna.Benders.AbstractBendersOutput","page":"Benders","title":"Coluna.Benders.AbstractBendersOutput","text":"Supertype for the custom objects that will store the output of the Benders cut generation algorithm.\n\n\n\n\n\n","category":"type"},{"location":"api/benders/#Coluna.Benders.benders_output_type","page":"Benders","title":"Coluna.Benders.benders_output_type","text":"benders_output_type(context) -> Type{<:AbstractBendersOutput}\n\nReturns the type of the custom object that will store the output of the Benders cut generation algorithm.\n\n\n\n\n\n","category":"function"},{"location":"api/benders/#Coluna.Benders.new_output","page":"Benders","title":"Coluna.Benders.new_output","text":"Returns a new instance of the custom object that stores the output of the Benders cut generation algorithm.\n\n\n\n\n\n","category":"function"},{"location":"api/benders/#Benders-cut-generation-iteration","page":"Benders","title":"Benders cut generation iteration","text":"","category":"section"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"This is a description of how the Coluna.Benders.run_benders_iteration! generic function behaves with the default implementation.","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"These are the main steps of a Benders cut generation iteration without stabilization. Click on the step to go to the corresponding section.","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"flowchart TB;\n id1(Optimize master)\n id2(Treat unbounded master)\n id3(Setup separation subproblems)\n id4(Separation subproblem iterator)\n id5(Optimize separation subproblem)\n id6(Push cut into set)\n id9(Master is unbounded?)\n id10(Error)\n id7(Insert cuts)\n id11(Build primal solution)\n id8(Iteration output)\n id1 --unbounded--> id2\n id2 --certificate--> id3\n id1 -- optimal --> id3\n id3 --> id4\n id4 -- subproblem --> id5\n id5 --> id6\n id6 --> id4\n id4 -- end --> id9\n id9 -- yes --> id10\n id9 -- no --> id7\n id7 --> id11\n id11 --> id8\n click id1 href \"#Master-optimization\" \"Link to doc\"\n click id2 href \"#Unbounded-master-case\" \"Link to doc\"\n click id3 href \"#Setup-separation-subproblems\" \"Link to doc\"\n click id4 href \"#Subproblem-iterator\" \"Link to doc\"\n click id5 href \"#Separation-subproblem-optimization\" \"Link to doc\"\n click id6 href \"#Set-of-generated-cuts\" \"Link to doc\"\n click id9 href \"#Unboundedness-check\" \"Link to doc\"\n click id11 href \"#Current-primal-solution\" \"Link to doc\"\n click id7 href \"#Cuts-insertion\" \"Link to doc\"\n click id8 href \"#Iteration-output\" \"Link to doc\"","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"In the default implementation, some sections may have different behaviors depending on the result of previous steps.","category":"page"},{"location":"api/benders/#Master-optimization","page":"Benders","title":"Master optimization","text":"","category":"section"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"This operation consists in optimizing the master problem in order to find a first-level solution barx.","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"In the default implementation, master optimization can be performed using SolveLpForm (LP solver) or SolveIpForm (MILP solver). When getting the solution, we store the current value of second stage variables bareta_k as incumbent value (see Coluna.MathProg.getcurincval).","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"It returns an object of the following type:","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"Coluna.Algorithm.BendersMasterResult","category":"page"},{"location":"api/benders/#Coluna.Algorithm.BendersMasterResult","page":"Benders","title":"Coluna.Algorithm.BendersMasterResult","text":"Output of the default implementation of the Benders.optimize_master_problem! method.\n\nIt contains:\n\nip_solver: true if the master problem is solved with a MIP solver and involves integral variables, false otherwise.\nresult: the result of the master problem optimization stored in an OptimizationState object.\ninfeasible: true if the master at the current iteration is infeasible; false otherwise.\nunbounded: true if the master at the current iteration is unbounded; false otherwise.\ncertificate: true if the master at the current iteration is unbounded and if the current result is a dual infeasibility certificate, false otherwise.\n\n\n\n\n\n","category":"type"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"References:","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"Coluna.Benders.optimize_master_problem!","category":"page"},{"location":"api/benders/#Coluna.Benders.optimize_master_problem!","page":"Benders","title":"Coluna.Benders.optimize_master_problem!","text":"optimize_master_problem!(master, context, env) -> MasterResult\n\nReturns an instance of a custom object MasterResult that implements the following methods:\n\nis_unbounded(res::MasterResult) -> Bool\nis_infeasible(res::MasterResult) -> Bool\nis_certificate(res::MasterResult) -> Bool\nget_primal_sol(res::MasterResult) -> Union{Nothing, PrimalSolution}\n\n\n\n\n\n","category":"function"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"Go back to the cut generation iteration diagram.","category":"page"},{"location":"api/benders/#Unbounded-master-case","page":"Benders","title":"Unbounded master case","text":"","category":"section"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"Second stage cost eta_k variables are free. As a consequence, the master problem is unbounded when there is no optimality Benders cuts.","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"In this case, Coluna.Benders.treat_unbounded_master_problem_case! is called. The main goal of the default implementation of this method is to get the dual infeasibility certificate of the master problem.","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"If the master has been solved with a MIP solver at the previous step, we need to relax the integrality constraints to get a dual infeasibility certificate.","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"If the solver does not provide a dual infeasibility certificate, the implementation has an \"emergency\" routine to provide a first-stage feasible solution by solving the master LP with cost of second stage variables set to zero. We recommend using a solver that provides a dual infeasibility certificate and avoiding the \"emergency\" routine.","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"References:","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"Coluna.Benders.treat_unbounded_master_problem_case!","category":"page"},{"location":"api/benders/#Coluna.Benders.treat_unbounded_master_problem_case!","page":"Benders","title":"Coluna.Benders.treat_unbounded_master_problem_case!","text":"treat_unbounded_master_problem_case!(master, context, env) -> MasterResult\n\nWhen after a call to optimize_master_problem!, the master is unbounded, this method is called. Returns an instance of a custom object MasterResult.\n\n\n\n\n\n","category":"function"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"Go back to the cut generation iteration diagram.","category":"page"},{"location":"api/benders/#Setup-separation-subproblems","page":"Benders","title":"Setup separation subproblems","text":"","category":"section"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"info: Info\nThe separation subproblems differs depending on whether the restricted master is unbounded or not:if the restricted master is optimal, the generic function calls Coluna.Benders.update_sp_rhs!\nif the restricted master is unbounded, the generic function calls Coluna.Benders.setup_separation_for_unbounded_master_case!","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"Default implementation of Coluna.Benders.update_sp_rhs! updates the right-hand side of the linking constraints (5).","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"Reference:","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"Coluna.Benders.update_sp_rhs!","category":"page"},{"location":"api/benders/#Coluna.Benders.update_sp_rhs!","page":"Benders","title":"Coluna.Benders.update_sp_rhs!","text":"update_sp_rhs!(context, sp, mast_primal_sol)\n\nUpdates the right-hand side of the separation problem sp by fixing the first-level solution obtained by solving the master problem mast_primal_sol.\n\n\n\n\n\n","category":"function"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"Default implementation of Coluna.Benders.setup_separation_for_unbounded_master_case! gives rise to the formulation proposed in Lemma 2 of Bonami et al:","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"beginaligned\n(SepB) equiv min quad fy + colorgray mathbf1z + mathbf1z \ntextst quad Dy colorgray + z geq -Bbarx (5a) quad colorblue(pi) \n Ey colorgray + z geq 0 (6a) quad colorblue(rho) \n y geq 0 (7a) quad colorblue(sigma)\nendaligned","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"where y are second-stage variables, z and z are artificial variables (in grey because they are deactivated by default), and barx is an unbounded ray of the restricted master.","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"Reference:","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"Coluna.Benders.setup_separation_for_unbounded_master_case!","category":"page"},{"location":"api/benders/#Coluna.Benders.setup_separation_for_unbounded_master_case!","page":"Benders","title":"Coluna.Benders.setup_separation_for_unbounded_master_case!","text":"setup_separation_for_unbounded_master_case!(context, sp, mast_primal_sol)\n\nUpdates the separation problem to derive a cut when the master problem is unbounded.\n\n\n\n\n\n","category":"function"},{"location":"api/benders/#Subproblem-iterator","page":"Benders","title":"Subproblem iterator","text":"","category":"section"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"Not implemented yet.","category":"page"},{"location":"api/benders/#Separation-subproblem-optimization","page":"Benders","title":"Separation subproblem optimization","text":"","category":"section"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"The default implementation first optimize the subproblem without the artificial variables z and z. In the case where it finds (barpi barrho barsigma) an optimal dual solution to the subproblem, the following cut is generated:","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"eta_k + barpiBx geq dbarpi + barrhoe + barsigma_leq l_2 + barsigma_geq u_2","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"with barsigma_leq l_2 (respectively barsigma_geq u_2) the dual of the left part (respectively the right part) of constraint l_2 leq y leq u_2 of the subproblem. ","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"In the case where it finds the subproblem infeasible, it calls Coluna.Benders.treat_infeasible_separation_problem_case!. The default implementation of this method activates the artificial variables z and z, sets the cost of second stage variables to 0, and optimizes the subproblem again.","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"If a solution with no artificial variables is found, the following cut is generated:","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"barpiBx geq dbarpi + barrhoe + barsigma_leq l_2 + barsigma_geq u_2","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"Both methods return an object of the following type:","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"Coluna.Algorithm.BendersSeparationResult","category":"page"},{"location":"api/benders/#Coluna.Algorithm.BendersSeparationResult","page":"Benders","title":"Coluna.Algorithm.BendersSeparationResult","text":"Output of the default implementation of the Benders.optimize_separation_problem! and Benders.treat_infeasible_separation_problem_case! methods.\n\nIt contains:\n\nsecond_stage_estimation_in_master: the value of the second stage cost variable in the solution to the master problem.\nsecond_stage_cost: the value of the second stage cost variable in the solution to the separation problem.\nlp_primal_sol: the primal solution to the separation problem.\ninfeasible: true if the current separation problem is infeasible; false otherwise.\nunbounded: true if the current separation problem is unbounded; false otherwise.\ncut: the cut generated by the separation problem.\ninfeasible_treatment: true if this object is an output of the Benders.treat_infeasible_separation_problem_case! method; false otherwise.\nunbounded_master: true if the separation subproblem has the form of Lemma 2 to separate a cut to truncate an unbounded ray of the restricted master problem; false otherwise.\n\n\n\n\n\n","category":"type"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"References:","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"Coluna.Benders.optimize_separation_problem!\nColuna.Benders.treat_infeasible_separation_problem_case!","category":"page"},{"location":"api/benders/#Coluna.Benders.optimize_separation_problem!","page":"Benders","title":"Coluna.Benders.optimize_separation_problem!","text":"optimize_separation_problem!(context, sp_to_solve, env, unbounded_master) -> SeparationResult\n\nReturns an instance of a custom object SeparationResult that implements the following methods:\n\nis_unbounded(res::SeparationResult) -> Bool\nis_infeasible(res::SeparationResult) -> Bool\nget_obj_val(res::SeparationResult) -> Float64\nget_primal_sol(res::SeparationResult) -> Union{Nothing, PrimalSolution}\nget_dual_sp_sol(res::SeparationResult) -> Union{Nothing, DualSolution}\n\n\n\n\n\n","category":"function"},{"location":"api/benders/#Coluna.Benders.treat_infeasible_separation_problem_case!","page":"Benders","title":"Coluna.Benders.treat_infeasible_separation_problem_case!","text":"treat_infeasible_separation_problem_case!(context, sp_to_solve, env, unbounded_master) -> SeparationResult\n\nWhen after a call to optimize_separation_problem!, the separation problem is infeasible, this method is called. Returns an instance of a custom object SeparationResult.\n\n\n\n\n\n","category":"function"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"Go back to the cut generation iteration diagram.","category":"page"},{"location":"api/benders/#Set-of-generated-cuts","page":"Benders","title":"Set of generated cuts","text":"","category":"section"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"You can define your data structure to manage the cuts generated at a given iteration. Columns are inserted after the optimization of all the separation subproblems to allow the parallelization of the latter.","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"In the default implementation, cuts are represented by the following data structure:","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"Coluna.Algorithm.GeneratedCut","category":"page"},{"location":"api/benders/#Coluna.Algorithm.GeneratedCut","page":"Benders","title":"Coluna.Algorithm.GeneratedCut","text":"Solution to the separation problem together with its corresponding benders cut.\n\nIt contains:\n\nmin_sense: true if it's a minimization problem; false otherwise.\nlhs: the left-hand side of the cut.\nrhs: the right-hand side of the cut.\ndual_sol: an optimal dual solution to the separation problem.\n\n\n\n\n\n","category":"type"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"We use the following data structures to store the cuts and the primal solutions to the subproblems:","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"Coluna.Algorithm.CutsSet\nColuna.Algorithm.SepSolSet","category":"page"},{"location":"api/benders/#Coluna.Algorithm.CutsSet","page":"Benders","title":"Coluna.Algorithm.CutsSet","text":"Stores a collection of cuts.\n\nIt contains cuts a vector of GeneratedCut objects.\n\n\n\n\n\n","category":"type"},{"location":"api/benders/#Coluna.Algorithm.SepSolSet","page":"Benders","title":"Coluna.Algorithm.SepSolSet","text":"Primal solutions to the separation problems optimized at the current iteration. This is used to build a primal solution.\n\nIt contains sols a vector of primal solutions. \n\n\n\n\n\n","category":"type"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"The default implementation of push_in_set! has the responsibility to check if the cut is violated. Given bareta_k solution to the restricted master and bary solution to the separation problem, the cut is considered as violated when:","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"the separation subproblem was infeasible\nor bareta_k geq fbary ","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"References:","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"Coluna.Benders.set_of_cuts\nColuna.Benders.set_of_sep_sols\nColuna.Benders.push_in_set!","category":"page"},{"location":"api/benders/#Coluna.Benders.set_of_cuts","page":"Benders","title":"Coluna.Benders.set_of_cuts","text":"Returns an empty container that will store all the cuts generated by the separation problems during an iteration of the Benders cut generation algorithm. One must be able to iterate on this container to insert the cuts in the master problem.\n\n\n\n\n\n","category":"function"},{"location":"api/benders/#Coluna.Benders.set_of_sep_sols","page":"Benders","title":"Coluna.Benders.set_of_sep_sols","text":"Returns an empty container that will store the primal solutions to the separation problems at a given iteration of the Benders cut generation algorithm.\n\n\n\n\n\n","category":"function"},{"location":"api/benders/#Coluna.Benders.push_in_set!","page":"Benders","title":"Coluna.Benders.push_in_set!","text":"push_in_set!(context, cut_pool, sep_result) -> Bool\n\nInserts a cut in the set of cuts generated at a given iteration of the Benders cut generation algorithm. The cut_pool structure is generated by set_of_cuts(context).\n\npush_in_set!(context, sep_sp_sols, sep_result) -> Bool\n\nInserts a primal solution to a separation problem in the set of primal solutions generated at a given iteration of the Benders cut generation algorithm. The sep_sp_sols structure is generated by set_of_sep_sols(context).\n\nReturns true if the cut or the primal solution was inserted in the set, false otherwise.\n\n\n\n\n\n","category":"function"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"Go back to the cut generation iteration diagram.","category":"page"},{"location":"api/benders/#Unboundedness-check","page":"Benders","title":"Unboundedness check","text":"","category":"section"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"info: Info\nThis check is performed only when the restricted master is unbounded.","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"To perform this check, we need a solution to each separation problem.","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"Let (bareta_k)_k in K be the value of second stage variables in the dual infeasibility certificate of the restricted master. Let bary be an optimal solution to the separation problem (SepB).","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"As indicated by Bonami et al., if fbary leq sumlimits_k in K bareta_k, then the original problem is unbounded (by definition of an unbounded ray of the original problem).","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"References:","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"Coluna.Benders.master_is_unbounded","category":"page"},{"location":"api/benders/#Coluna.Benders.master_is_unbounded","page":"Benders","title":"Coluna.Benders.master_is_unbounded","text":"Returns true if the master has been proven unbounded, false otherwise.\n\n\n\n\n\n","category":"function"},{"location":"api/benders/#Cuts-insertion","page":"Benders","title":"Cuts insertion","text":"","category":"section"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"The default implementation inserts into the master all the cuts stored in the CutsSet object.","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"Reference:","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"Coluna.Benders.insert_cuts!","category":"page"},{"location":"api/benders/#Coluna.Benders.insert_cuts!","page":"Benders","title":"Coluna.Benders.insert_cuts!","text":"Inserts the cuts into the master.\n\n\n\n\n\n","category":"function"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"Go back to the cut generation iteration diagram.","category":"page"},{"location":"api/benders/#Current-primal-solution","page":"Benders","title":"Current primal solution","text":"","category":"section"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"Lorem ipsum.","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"References:","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"Coluna.Benders.build_primal_solution","category":"page"},{"location":"api/benders/#Coluna.Benders.build_primal_solution","page":"Benders","title":"Coluna.Benders.build_primal_solution","text":"Builds a primal solution to the original problem from the primal solution to the master problem and the primal solutions to the separation problems.\n\n\n\n\n\n","category":"function"},{"location":"api/benders/#Iteration-output","page":"Benders","title":"Iteration output","text":"","category":"section"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"Coluna.Algorithm.BendersIterationOutput","category":"page"},{"location":"api/benders/#Coluna.Algorithm.BendersIterationOutput","page":"Benders","title":"Coluna.Algorithm.BendersIterationOutput","text":"Output of the default implementation of an iteration of the Benders algorithm.\n\nIt contains:\n\nmin_sense: the original problem is a minimization problem\nnb_new_cuts: the number of new cuts added to the master problem\nip_primal_sol: the primal solution to the original problem found during this iteration\ninfeasible: the original problem is infeasible\ntime_limit_reached: the time limit was reached\nmaster: the solution value to the master problem\n\n\n\n\n\n","category":"type"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"References:","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"Coluna.Benders.AbstractBendersIterationOutput\nColuna.Benders.benders_iteration_output_type\nColuna.Benders.new_iteration_output","category":"page"},{"location":"api/benders/#Coluna.Benders.AbstractBendersIterationOutput","page":"Benders","title":"Coluna.Benders.AbstractBendersIterationOutput","text":"Supertype for the custom objects that will store the output of a Benders iteration.\n\n\n\n\n\n","category":"type"},{"location":"api/benders/#Coluna.Benders.benders_iteration_output_type","page":"Benders","title":"Coluna.Benders.benders_iteration_output_type","text":"benders_iteration_output_type(context) -> Type{<:AbstractBendersIterationOutput}\n\nReturns the type of the custom object that will store the output of a Benders iteration.\n\n\n\n\n\n","category":"function"},{"location":"api/benders/#Coluna.Benders.new_iteration_output","page":"Benders","title":"Coluna.Benders.new_iteration_output","text":"Returns a new instance of the custom object that stores the output of a Benders iteration.\n\n\n\n\n\n","category":"function"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"Go back to the cut generation iteration diagram.","category":"page"},{"location":"api/benders/#Getters-for-Result-data-structures","page":"Benders","title":"Getters for Result data structures","text":"","category":"section"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"Method name Master Separation\nis_unbounded X X\nis_infeasible X X\nis_certificate X \nget_primal_sol X X\nget_dual_sol X \nget_obj_val X X","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"Coluna.Benders.is_unbounded\nColuna.Benders.is_infeasible\nColuna.Benders.is_certificate\nColuna.Benders.get_primal_sol\nColuna.Benders.get_dual_sol\nColuna.Benders.get_obj_val","category":"page"},{"location":"api/benders/#Coluna.Benders.is_unbounded","page":"Benders","title":"Coluna.Benders.is_unbounded","text":"Returns true if the problem is unbounded, false otherwise.\n\n\n\n\n\n","category":"function"},{"location":"api/benders/#Coluna.Benders.is_infeasible","page":"Benders","title":"Coluna.Benders.is_infeasible","text":"Returns true if the master is infeasible, false otherwise.\n\n\n\n\n\n","category":"function"},{"location":"api/benders/#Coluna.Benders.is_certificate","page":"Benders","title":"Coluna.Benders.is_certificate","text":"Returns the certificate of dual infeasibility if the master is unbounded, nothing otherwise.\n\n\n\n\n\n","category":"function"},{"location":"api/benders/#Coluna.Benders.get_primal_sol","page":"Benders","title":"Coluna.Benders.get_primal_sol","text":"Returns the primal solution of the master problem if it exists, nothing otherwise.\n\n\n\n\n\n","category":"function"},{"location":"api/benders/#Coluna.Benders.get_dual_sol","page":"Benders","title":"Coluna.Benders.get_dual_sol","text":"Returns the dual solution of the separation problem if it exists; nothing otherwise.\n\n\n\n\n\n","category":"function"},{"location":"api/benders/#Coluna.Benders.get_obj_val","page":"Benders","title":"Coluna.Benders.get_obj_val","text":"Returns the objective value of the master or separation problem.\n\n\n\n\n\n","category":"function"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"Go back to the cut generation iteration diagram.","category":"page"},{"location":"api/benders/#Stabilization","page":"Benders","title":"Stabilization","text":"","category":"section"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"Not implemented yet.","category":"page"},{"location":"api/benders/","page":"Benders","title":"Benders","text":"","category":"page"},{"location":"start/identical_sp/","page":"Identical subproblems","title":"Identical subproblems","text":"EditURL = \"identical_sp.jl\"","category":"page"},{"location":"start/identical_sp/#tuto_identical_sp","page":"Identical subproblems","title":"Identical subproblems","text":"","category":"section"},{"location":"start/identical_sp/","page":"Identical subproblems","title":"Identical subproblems","text":"Let us see an example of resolution using the advantage of identical subproblems with Dantzig-Wolfe and a variant of the Generalized Assignment Problem.","category":"page"},{"location":"start/identical_sp/","page":"Identical subproblems","title":"Identical subproblems","text":"Consider a set of machine type T = 1:nb_machine_types and a set of jobs J = 1:nb_jobs. A machine type t has a resource capacity Q[t] and the factory contains U[t] machines of type t. A job j assigned to a machine of type t has a cost c[t,j] and consumes w[t,j] resource units of the machine of type t.","category":"page"},{"location":"start/identical_sp/","page":"Identical subproblems","title":"Identical subproblems","text":"Consider the following instance :","category":"page"},{"location":"start/identical_sp/","page":"Identical subproblems","title":"Identical subproblems","text":"nb_machine_types = 2;\nnb_jobs = 8;\nJ = 1:nb_jobs;\nQ = [10, 15];\nU = [3, 2]; # 3 machines of type 1 & 2 machines of type 2\nc = [10 11 13 11 12 14 15 8; 20 21 23 21 22 24 25 18];\nw = [4 4 5 4 4 3 4 5; 5 5 6 5 5 4 5 6];\n\n\n#Here is the JuMP model to optimize this instance with a classic solver :\n\nusing JuMP, GLPK;\n\nT1 = [1, 2, 3]; # U[1] machines\nT2 = [4, 5]; # U[2] machines\nM = union(T1, T2);\nm2t = [1, 1, 1, 2, 2]; # machine id -> type id\n\nmodel = Model(GLPK.Optimizer);\n@variable(model, x[M, J], Bin); # 1 if job j assigned to machine m\n@constraint(model, cov[j in J], sum(x[m,j] for m in M) == 1);\n@constraint(model, knp[m in M], sum(w[m2t[m],j] * x[m,j] for j in J) <= Q[m2t[m]]);\n@objective(model, Min, sum(c[m2t[m],j] * x[m,j] for m in M, j in J));\n\noptimize!(model);\nobjective_value(model)","category":"page"},{"location":"start/identical_sp/","page":"Identical subproblems","title":"Identical subproblems","text":"You can decompose over the machines by defining an axis on M. However, if you want to take advantage of the identical subproblems, you must define the formulation as follows :","category":"page"},{"location":"start/identical_sp/","page":"Identical subproblems","title":"Identical subproblems","text":"using BlockDecomposition, Coluna, JuMP, GLPK;\nconst BD = BlockDecomposition\n\ncoluna = optimizer_with_attributes(\n Coluna.Optimizer,\n \"params\" => Coluna.Params(\n solver = Coluna.Algorithm.TreeSearchAlgorithm() # default BCP\n ),\n \"default_optimizer\" => GLPK.Optimizer # GLPK for the master & the subproblems\n);\n\n@axis(T, 1:nb_machine_types);\n\nmodel = BlockModel(coluna);\n@variable(model, x[T, J], Bin); # 1 if job j assigned to machine m\n@constraint(model, cov[j in J], sum(x[t,j] for t in T) == 1);\n@constraint(model, knp[t in T], sum(w[t] * x[t,j] for j in J) <= Q[t]);\n@objective(model, Min, sum(c[t,j] * x[t,j] for t in T, j in J));\nnothing #hide","category":"page"},{"location":"start/identical_sp/","page":"Identical subproblems","title":"Identical subproblems","text":"We assign jobs to a type of machine and we define one knapsack constraint for each type. This formulation cannot be solved as it stands with a commercial solver.","category":"page"},{"location":"start/identical_sp/","page":"Identical subproblems","title":"Identical subproblems","text":"Then, we decompose and specify the multiplicity of each knapsack subproblem :","category":"page"},{"location":"start/identical_sp/","page":"Identical subproblems","title":"Identical subproblems","text":"@dantzig_wolfe_decomposition(model, dec_on_types, T);\nsps = getsubproblems(dec_on_types)\nfor t in T\n specify!(sps[t], lower_multiplicity = 0, upper_multiplicity = U[t]);\nend\ngetsubproblems(dec_on_types)","category":"page"},{"location":"start/identical_sp/","page":"Identical subproblems","title":"Identical subproblems","text":"We see that subproblem for machine type 1 has an upper multiplicity equals to 3, and the second subproblem for machine type 2 has an upper multiplicity equals to 2. It means that we can use at most 3 machines of type 1 and at most 2 machines of type 2.","category":"page"},{"location":"start/identical_sp/","page":"Identical subproblems","title":"Identical subproblems","text":"We can then optimize","category":"page"},{"location":"start/identical_sp/","page":"Identical subproblems","title":"Identical subproblems","text":"optimize!(model);\nnothing #hide","category":"page"},{"location":"start/identical_sp/","page":"Identical subproblems","title":"Identical subproblems","text":"and retrieve the disaggregated solution","category":"page"},{"location":"start/identical_sp/","page":"Identical subproblems","title":"Identical subproblems","text":"for t in T\n assignment_patterns = BD.getsolutions(model, t);\n for pattern in assignment_patterns\n nb_times_pattern_used = BD.value(pattern);\n jobs_in_pattern = [];\n for j in J\n if BD.value(pattern, x[t, j]) ≈ 1\n push!(jobs_in_pattern, j);\n end\n end\n println(\"Pattern of machine type $t used $nb_times_pattern_used times : $jobs_in_pattern\");\n end\nend","category":"page"},{"location":"start/identical_sp/","page":"Identical subproblems","title":"Identical subproblems","text":"","category":"page"},{"location":"start/identical_sp/","page":"Identical subproblems","title":"Identical subproblems","text":"This page was generated using Literate.jl.","category":"page"},{"location":"start/identical_sp/","page":"Identical subproblems","title":"Identical subproblems","text":"","category":"page"},{"location":"qa/#Question-and-Answer","page":"Q&A","title":"Question & Answer","text":"","category":"section"},{"location":"qa/#Default-algorithms-of-Coluna-do-not-beat-the-commercial-solver-I-usually-use.-Is-it-normal-?","page":"Q&A","title":"Default algorithms of Coluna do not beat the commercial solver I usually use. Is it normal ?","text":"","category":"section"},{"location":"qa/","page":"Q&A","title":"Q&A","text":"Yes it is.","category":"page"},{"location":"qa/","page":"Q&A","title":"Q&A","text":"Solvers such as Gurobi, Cplex ... are handy powerful black-box tools. They can run a very efficient presolve step to simplify the formulation, automatically apply lots of valid inequalities (such as MIR or cover cuts), choose good branching strategies, or also run heuristics. However, when your formulation reaches a certain size, commercial solvers may run for hours without finding anything. This is the point where you may want to decompose your formulation.","category":"page"},{"location":"qa/","page":"Q&A","title":"Q&A","text":"Coluna is a framework, not a solver. It provides algorithms to try column generation on your problem very easily. Then, you can devise your own branch-cut-and-price algorithm on top of Coluna's algorithms. to scale up and hopefully beats the commercial solver.","category":"page"},{"location":"qa/","page":"Q&A","title":"Q&A","text":"To start customizing Coluna for your own problem, you can separate valid inequalities or call your own algorithm that optimizes subproblems.","category":"page"},{"location":"qa/#I'm-using-Gurobi-as-a-subsolver","page":"Q&A","title":"I'm using Gurobi as a subsolver","text":"","category":"section"},{"location":"qa/#My-license-prevents-me-from-running-several-environments-at-the-same-time.-How-can-I-use-a-single-environment-for-the-master-and-all-subproblems?","page":"Q&A","title":"My license prevents me from running several environments at the same time. How can I use a single environment for the master and all subproblems?","text":"","category":"section"},{"location":"qa/","page":"Q&A","title":"Q&A","text":"You can use the Gurobi.Env constructor to create a single environment and pass it to the optimizers.","category":"page"},{"location":"qa/","page":"Q&A","title":"Q&A","text":"const GRB_ENV = Gurobi.Env()\n\ncoluna = optimizer_with_attributes(\n Coluna.Optimizer,\n \"params\" => Coluna.Params(\n solver = Coluna.Algorithm.TreeSearchAlgorithm() # default branch-cut-and-price\n ),\n \"default_optimizer\" => () -> Gurobi.Optimizer(GRB_ENV)\n);","category":"page"},{"location":"qa/","page":"Q&A","title":"Q&A","text":"If you get a Gurobi error 10002, you should wrap the Gurobi environment as a reference to initialize it during runtime instead of compile time (reference).","category":"page"},{"location":"qa/","page":"Q&A","title":"Q&A","text":"const GRB_ENV_REF = Ref{Gurobi.Env}()\n\nfunction __init__()\n GRB_ENV_REF[] = Gurobi.Env()\n return nothing\nend\n\ncoluna = optimizer_with_attributes(\n Coluna.Optimizer,\n \"params\" => Coluna.Params(\n solver = Coluna.Algorithm.TreeSearchAlgorithm() # default branch-cut-and-price\n ),\n \"default_optimizer\" => () -> Gurobi.Optimizer(GRB_ENV_REF[])\n);","category":"page"},{"location":"qa/#How-to-disable-all-outputs-from-Gurobi?","page":"Q&A","title":"How to disable all outputs from Gurobi?","text":"","category":"section"},{"location":"qa/","page":"Q&A","title":"Q&A","text":"You can refer to the following article from Gurobi's knowledge base.","category":"page"},{"location":"qa/","page":"Q&A","title":"Q&A","text":"We confirm that adding the following entry in the gurobi.env file works with Gurobi 10+:","category":"page"},{"location":"qa/","page":"Q&A","title":"Q&A","text":"LogToConsole 0","category":"page"},{"location":"qa/","page":"Q&A","title":"Q&A","text":"","category":"page"},{"location":"start/cuts/","page":"Cut Generation","title":"Cut Generation","text":"EditURL = \"cuts.jl\"","category":"page"},{"location":"start/cuts/#tuto_cut_callback","page":"Cut Generation","title":"Valid inequalities","text":"","category":"section"},{"location":"start/cuts/","page":"Cut Generation","title":"Cut Generation","text":"Now let us consider a variant of the Generalized Assignment Problem in which we have to pay f[m] to use machine m.","category":"page"},{"location":"start/cuts/","page":"Cut Generation","title":"Cut Generation","text":"Consider the following instance:","category":"page"},{"location":"start/cuts/","page":"Cut Generation","title":"Cut Generation","text":"J = 1:10\nM = 1:5\nc = [10.13 15.6 15.54 13.41 17.08;19.58 16.83 10.75 15.8 14.89;14.23 17.36 16.05 14.49 18.96;16.47 16.38 18.14 15.46 11.64;17.87 18.25 13.12 19.16 16.33;11.09 16.76 15.5 12.08 13.06;15.19 13.86 16.08 19.47 15.79;10.79 18.96 16.11 19.78 15.55;12.03 19.03 16.01 14.46 12.77;14.48 11.75 16.97 19.95 18.32];\nw = [5, 4, 5, 6, 8, 9, 5, 8, 10, 7];\nQ = [25, 24, 31, 28, 24];\nf = [105, 103, 109, 112, 100];\nnothing #hide","category":"page"},{"location":"start/cuts/","page":"Cut Generation","title":"Cut Generation","text":"We define the dependencies:","category":"page"},{"location":"start/cuts/","page":"Cut Generation","title":"Cut Generation","text":"using JuMP, BlockDecomposition, Coluna, GLPK;\nnothing #hide","category":"page"},{"location":"start/cuts/","page":"Cut Generation","title":"Cut Generation","text":"We parametrize the solver. We solve only the root node of the branch-and-bound tree and we use a column and cut generation algorithm to conquer (optimize) this node.","category":"page"},{"location":"start/cuts/","page":"Cut Generation","title":"Cut Generation","text":"coluna = JuMP.optimizer_with_attributes(\n Coluna.Optimizer,\n \"params\" => Coluna.Params(\n solver = Coluna.Algorithm.TreeSearchAlgorithm(\n conqueralg = Coluna.Algorithm.ColCutGenConquer(\n max_nb_cut_rounds = 20\n ),\n branchingtreefile = \"tree2.dot\",\n maxnumnodes = 1\n )\n ),\n \"default_optimizer\" => GLPK.Optimizer\n);\nnothing #hide","category":"page"},{"location":"start/cuts/#Column-generation","page":"Cut Generation","title":"Column generation","text":"","category":"section"},{"location":"start/cuts/","page":"Cut Generation","title":"Cut Generation","text":"We write the model:","category":"page"},{"location":"start/cuts/","page":"Cut Generation","title":"Cut Generation","text":"model = BlockModel(coluna; direct_model = true);\n@axis(M_axis, M)\n@variable(model, x[j in J, m in M_axis], Bin);\n@variable(model, y[m in M_axis], Bin);\n@constraint(model, setpartitioning[j in J], sum(x[j,m] for m in M_axis) == 1);\n@constraint(model, knp[m in M_axis], sum(w[j]*x[j,m] for j in J) <= Q[m] * y[m]);\n@objective(model, Min, sum(c[j,m] * x[j,m] for m in M_axis, j in J) + sum(f[m] * y[m] for m in M_axis));\n\n@dantzig_wolfe_decomposition(model, dec, M_axis);\nsp = getsubproblems(dec);\nspecify!.(sp, lower_multiplicity = 0);\nnothing #hide","category":"page"},{"location":"start/cuts/","page":"Cut Generation","title":"Cut Generation","text":"We optimize:","category":"page"},{"location":"start/cuts/","page":"Cut Generation","title":"Cut Generation","text":"optimize!(model)","category":"page"},{"location":"start/cuts/","page":"Cut Generation","title":"Cut Generation","text":"The final dual bound is:","category":"page"},{"location":"start/cuts/","page":"Cut Generation","title":"Cut Generation","text":"db1 = objective_bound(model)","category":"page"},{"location":"start/cuts/#Strengthen-with-valid-inequalities","page":"Cut Generation","title":"Strengthen with valid inequalities","text":"","category":"section"},{"location":"start/cuts/","page":"Cut Generation","title":"Cut Generation","text":"Let H be the set of configurations of open machines (h[m] = 1 if machine m open; 0 otherwise) such that all jobs can be assigned : sum(h'Q) >= sum(w) i.e. the total capacity of the open machines must exceed the total weight of the jobs.","category":"page"},{"location":"start/cuts/","page":"Cut Generation","title":"Cut Generation","text":"H = Vector{Int}[]\nfor h in digits.(1:(2^length(M) - 1), base=2, pad=length(M))\n if sum(h'Q) >= sum(w)\n push!(H, h)\n end\nend\nH","category":"page"},{"location":"start/cuts/","page":"Cut Generation","title":"Cut Generation","text":"Let ȳ be the solution to the linear relaxation of the problem. Let us try to express ȳ as a linear expression of the configurations. If ȳ ∈ conv H, we can derive a cut because the optimal integer solution to the problem uses one of the configurations of H.","category":"page"},{"location":"start/cuts/","page":"Cut Generation","title":"Cut Generation","text":"We need MathOptInterface to define the cut callback:","category":"page"},{"location":"start/cuts/","page":"Cut Generation","title":"Cut Generation","text":"using MathOptInterface","category":"page"},{"location":"start/cuts/","page":"Cut Generation","title":"Cut Generation","text":"The separation algorithm looks for the non-negative coefficients χ[k], k = 1:length(H), : max sum(χ[k] for k in 1:length(H)) such that sum(χ[k]* h for (k,h) in enumerate(H)) <= ̄ȳ. If the objective value is less than 1, we must add a cut.","category":"page"},{"location":"start/cuts/","page":"Cut Generation","title":"Cut Generation","text":"Since the separation algorithm is a linear program, strong duality applies. So we separate these cuts with the dual.","category":"page"},{"location":"start/cuts/","page":"Cut Generation","title":"Cut Generation","text":"fc_sep_m = Model(GLPK.Optimizer)\n@variable(fc_sep_m, ψ[m in M] >= 0) # one variable for each constraint\n@constraint(fc_sep_m, config_dual[h in H], ψ'h >= 1) # one constraint for each χ[k]\nMathOptInterface.set(fc_sep_m, MathOptInterface.Silent(), true)","category":"page"},{"location":"start/cuts/","page":"Cut Generation","title":"Cut Generation","text":"The objective is min ȳ'ψ = sum(χ[k] for k in 1:length(H)). Let ψ* be an optimal solution to the dual. If ȳ'ψ* < 1, then ψ*'y >= 1 is a valid inequality.","category":"page"},{"location":"start/cuts/","page":"Cut Generation","title":"Cut Generation","text":"function fenchel_cuts_separation(cbdata)\n println(\"Fenchel cuts separation callback...\")\n ȳ = [callback_value(cbdata, y[m]) for m in M_axis]\n @objective(fc_sep_m, Min, ȳ'ψ) # update objective\n optimize!(fc_sep_m)\n if objective_value(fc_sep_m) < 1\n con = @build_constraint(value.(ψ)'y >= 1) # valid inequality.\n MathOptInterface.submit(model, MathOptInterface.UserCut(cbdata), con)\n end\nend\n\nMathOptInterface.set(model, MathOptInterface.UserCutCallback(), fenchel_cuts_separation);\nnothing #hide","category":"page"},{"location":"start/cuts/","page":"Cut Generation","title":"Cut Generation","text":"We optimize:","category":"page"},{"location":"start/cuts/","page":"Cut Generation","title":"Cut Generation","text":"optimize!(model)","category":"page"},{"location":"start/cuts/","page":"Cut Generation","title":"Cut Generation","text":"Valid inequalities significantly improve the previous dual bound:","category":"page"},{"location":"start/cuts/","page":"Cut Generation","title":"Cut Generation","text":"db2 = objective_bound(model)\n\n\ndb2","category":"page"},{"location":"start/cuts/","page":"Cut Generation","title":"Cut Generation","text":"","category":"page"},{"location":"start/cuts/","page":"Cut Generation","title":"Cut Generation","text":"This page was generated using Literate.jl.","category":"page"},{"location":"start/cuts/","page":"Cut Generation","title":"Cut Generation","text":"","category":"page"},{"location":"man/config/#Coluna-Configuration","page":"Configuration","title":"Coluna Configuration","text":"","category":"section"},{"location":"man/config/","page":"Configuration","title":"Configuration","text":"todo","category":"page"},{"location":"man/config/#Raw-Parameters","page":"Configuration","title":"Raw Parameters","text":"","category":"section"},{"location":"man/config/","page":"Configuration","title":"Configuration","text":"todo","category":"page"},{"location":"man/config/#params","page":"Configuration","title":"params","text":"","category":"section"},{"location":"man/config/","page":"Configuration","title":"Configuration","text":"CurrentModule = Coluna","category":"page"},{"location":"man/config/","page":"Configuration","title":"Configuration","text":"Params","category":"page"},{"location":"man/config/#Coluna.Params","page":"Configuration","title":"Coluna.Params","text":"Coluna.Params(\n solver = Coluna.Algorithm.TreeSearchAlgorithm(),\n global_art_var_cost = 10e6,\n local_art_var_cost = 10e4\n)\n\nParameters of Coluna :\n\nsolver is the algorithm used to optimize the reformulation.\nglobal_art_var_cost is the cost of the global artificial variables in the master\nlocal_art_var_cost is the cost of the local artificial variables in the master\n\n\n\n\n\n","category":"type"},{"location":"man/config/","page":"Configuration","title":"Configuration","text":"CurrentModule = nothing","category":"page"},{"location":"man/config/#default_optimizer","page":"Configuration","title":"default_optimizer","text":"","category":"section"},{"location":"man/config/","page":"Configuration","title":"Configuration","text":"todo","category":"page"},{"location":"man/config/#Other-Supported-Parameters","page":"Configuration","title":"Other Supported Parameters","text":"","category":"section"},{"location":"man/config/#From-BlockDecomposition","page":"Configuration","title":"From BlockDecomposition","text":"","category":"section"},{"location":"man/config/","page":"Configuration","title":"Configuration","text":"CurrentModule = BlockDecomposition","category":"page"},{"location":"man/config/","page":"Configuration","title":"Configuration","text":"objectiveprimalbound!\nobjectivedualbound!","category":"page"},{"location":"man/config/#BlockDecomposition.objectiveprimalbound!","page":"Configuration","title":"BlockDecomposition.objectiveprimalbound!","text":"objectiveprimalbound!(model, pb)\n\nDefine a primal bound on the optimal objective value (upper bound for a minimisation, lower bound for a maximisation).\n\n\n\n\n\n","category":"function"},{"location":"man/config/#BlockDecomposition.objectivedualbound!","page":"Configuration","title":"BlockDecomposition.objectivedualbound!","text":"objectivedualbound!(model, db)\n\nDefine a dual bound on the optimal objective value. (lower bound for a minimisation, upper bound for a maximisation)\n\n\n\n\n\n","category":"function"},{"location":"man/config/","page":"Configuration","title":"Configuration","text":"CurrentModule = nothing","category":"page"},{"location":"man/config/#From-MathOptInterface","page":"Configuration","title":"From MathOptInterface","text":"","category":"section"},{"location":"man/config/","page":"Configuration","title":"Configuration","text":"todo","category":"page"},{"location":"man/config/","page":"Configuration","title":"Configuration","text":"","category":"page"},{"location":"start/custom_data/","page":"Custom data","title":"Custom data","text":"EditURL = \"custom_data.jl\"","category":"page"},{"location":"start/custom_data/#tuto_custom_data","page":"Custom data","title":"Custom Variables and Cuts","text":"","category":"section"},{"location":"start/custom_data/","page":"Custom data","title":"Custom data","text":"Coluna allows users to attach custom data to variables and constraints. This data is useful to store information about the variables or constraints in a custom format much easier to process than extracted information from the formulation (coefficient matrix, bounds, costs, and right-hand side).","category":"page"},{"location":"start/custom_data/","page":"Custom data","title":"Custom data","text":"In this example, we will show how to attach custom data to variables and constraints and use them to separate non-robust cuts. We will use the Bin Packing problem as an example.","category":"page"},{"location":"start/custom_data/","page":"Custom data","title":"Custom data","text":"Let us consider a Bin Packing problem with only 3 items such that any pair of items fits into one bin but the 3 items do not. The objective function is to minimize the number of bins being used. Pricing is done by inspection over the 6 combinations of items (3 pairs and 3 singletons). The master LP solution has 1.5 bins at the root node, each 0.5 corresponding to a bin with one of the possible pairs of items.","category":"page"},{"location":"start/custom_data/","page":"Custom data","title":"Custom data","text":"In this example, we will show you how to use non-robust cuts to improve the master LP solution at the root node. Obviously, Coluna is able to solve this instance by branching on the number of bins but the limit one on the number of nodes prevents it to be solved without cuts.","category":"page"},{"location":"start/custom_data/","page":"Custom data","title":"Custom data","text":"We define the dependencies:","category":"page"},{"location":"start/custom_data/","page":"Custom data","title":"Custom data","text":"using JuMP, BlockDecomposition, Coluna, GLPK;\nnothing #hide","category":"page"},{"location":"start/custom_data/","page":"Custom data","title":"Custom data","text":"We define the solver.","category":"page"},{"location":"start/custom_data/","page":"Custom data","title":"Custom data","text":"coluna = JuMP.optimizer_with_attributes(\n Coluna.Optimizer,\n \"default_optimizer\" => GLPK.Optimizer,\n \"params\" => Coluna.Params(\n solver = Coluna.Algorithm.TreeSearchAlgorithm(\n conqueralg = Coluna.Algorithm.ColCutGenConquer(\n colgen = Coluna.Algorithm.ColumnGeneration(\n pricing_prob_solve_alg = Coluna.Algorithm.SolveIpForm(\n optimizer_id = 1\n ))\n ),\n maxnumnodes = 1 # we only treat the root node.\n )\n )\n);\nnothing #hide","category":"page"},{"location":"start/custom_data/","page":"Custom data","title":"Custom data","text":"Let's define the model. Let's B the set of bins and I the set of items. We introduce variable y_b that is equal to 1 if a bin b is used and 0 otherwise. We introduce variable x_bi that is equal to 1 if item i is put in a bin b and 0 otherwise.","category":"page"},{"location":"start/custom_data/","page":"Custom data","title":"Custom data","text":"model = BlockModel(coluna);\nnothing #hide","category":"page"},{"location":"start/custom_data/","page":"Custom data","title":"Custom data","text":"We must assign three items:","category":"page"},{"location":"start/custom_data/","page":"Custom data","title":"Custom data","text":"I = [1, 2, 3];\nnothing #hide","category":"page"},{"location":"start/custom_data/","page":"Custom data","title":"Custom data","text":"And we have three bins:","category":"page"},{"location":"start/custom_data/","page":"Custom data","title":"Custom data","text":"B = [1, 2, 3];\nnothing #hide","category":"page"},{"location":"start/custom_data/","page":"Custom data","title":"Custom data","text":"Each bin is defining a subproblem, we declare our axis:","category":"page"},{"location":"start/custom_data/","page":"Custom data","title":"Custom data","text":"@axis(axis, collect(B));\nnothing #hide","category":"page"},{"location":"start/custom_data/","page":"Custom data","title":"Custom data","text":"We declare subproblem variables y[b]:","category":"page"},{"location":"start/custom_data/","page":"Custom data","title":"Custom data","text":"@variable(model, y[b in axis], Bin);\nnothing #hide","category":"page"},{"location":"start/custom_data/","page":"Custom data","title":"Custom data","text":"And x[b,i]:","category":"page"},{"location":"start/custom_data/","page":"Custom data","title":"Custom data","text":"@variable(model, x[b in axis, i in I], Bin);\nnothing #hide","category":"page"},{"location":"start/custom_data/","page":"Custom data","title":"Custom data","text":"Each item must be assigned to one bin:","category":"page"},{"location":"start/custom_data/","page":"Custom data","title":"Custom data","text":"@constraint(model, sp[i in I], sum(x[b,i] for b in axis) == 1);\nnothing #hide","category":"page"},{"location":"start/custom_data/","page":"Custom data","title":"Custom data","text":"We minimize the number of bins and we declare the decomposition:","category":"page"},{"location":"start/custom_data/","page":"Custom data","title":"Custom data","text":"@objective(model, Min, sum(y[b] for b in axis))\n@dantzig_wolfe_decomposition(model, dec, axis);\nnothing #hide","category":"page"},{"location":"start/custom_data/#Custom-data-for-non-robust-cuts","page":"Custom data","title":"Custom data for non-robust cuts","text":"","category":"section"},{"location":"start/custom_data/","page":"Custom data","title":"Custom data","text":"As said previously, at the end of the column generation at the root node, the master LP solution has 1.5 bins. It corresponds to three bins, each of them used 0.5 times containing one pair (1,2), (1, 3), or (2, 3) of items. We are going to introduce the following non-robust cut to make the master LP solution integral:","category":"page"},{"location":"start/custom_data/","page":"Custom data","title":"Custom data","text":"sumlimits_s in Siflength(s) geq 2 λ_s leq 1","category":"page"},{"location":"start/custom_data/","page":"Custom data","title":"Custom data","text":"where :","category":"page"},{"location":"start/custom_data/","page":"Custom data","title":"Custom data","text":"S is the set of possible bin assignments generated by the pricing problem.\nlength(s) the number of items in bin assignment s in S.","category":"page"},{"location":"start/custom_data/","page":"Custom data","title":"Custom data","text":"This cut means that we cannot have more than one bin with at least two items.","category":"page"},{"location":"start/custom_data/","page":"Custom data","title":"Custom data","text":"But the problem is that the cut is expressed over the master column and we don't have access to these variables from the JuMP model. To address this problem, Coluna offers a way to compute the coefficient of a column in a constraint by implementing the following method:","category":"page"},{"location":"start/custom_data/","page":"Custom data","title":"Custom data","text":" Coluna.MathProg.computecoeff","category":"page"},{"location":"start/custom_data/#Coluna.MathProg.computecoeff","page":"Custom data","title":"Coluna.MathProg.computecoeff","text":"computecoeff(var_custom_data, constr_custom_data) -> Float64\n\nDispatches on the type of custom data attached to the variable and the constraint to compute the coefficient of the variable in the constraint.\n\n\n\n\n\n","category":"function"},{"location":"start/custom_data/","page":"Custom data","title":"Custom data","text":"We therefore need to attach custom data to the master columns and the non-robust cut to use the method compute_coeff.","category":"page"},{"location":"start/custom_data/","page":"Custom data","title":"Custom data","text":"For every subproblem solution s, we define custom data with the number of items in the bin.","category":"page"},{"location":"start/custom_data/","page":"Custom data","title":"Custom data","text":"struct MyCustomVarData <: BlockDecomposition.AbstractCustomData\n nb_items::Int\nend\nBlockDecomposition.customvars!(model, MyCustomVarData);\nnothing #hide","category":"page"},{"location":"start/custom_data/","page":"Custom data","title":"Custom data","text":"We define custom data for the cut that will contain the minimum number of items in a bin that can be used. The value will be 2 in this example.","category":"page"},{"location":"start/custom_data/","page":"Custom data","title":"Custom data","text":"struct MyCustomCutData <: BlockDecomposition.AbstractCustomData\n min_items::Int\nend\nBlockDecomposition.customconstrs!(model, MyCustomCutData);\nnothing #hide","category":"page"},{"location":"start/custom_data/","page":"Custom data","title":"Custom data","text":"We implement the computecoeff method for the custom data we defined.","category":"page"},{"location":"start/custom_data/","page":"Custom data","title":"Custom data","text":"function Coluna.MathProg.computecoeff(\n var_custom_data::MyCustomVarData, constr_custom_data::MyCustomCutData\n)\n return (var_custom_data.nb_items >= constr_custom_data.min_items) ? 1.0 : 0.0\nend","category":"page"},{"location":"start/custom_data/#Pricing-callback","page":"Custom data","title":"Pricing callback","text":"","category":"section"},{"location":"start/custom_data/","page":"Custom data","title":"Custom data","text":"We define the pricing callback that will generate the bin with best-reduced cost. Be careful, when using non-robust cuts, you must take into account the contribution of the non-robust cuts to the reduced cost of your solution.","category":"page"},{"location":"start/custom_data/","page":"Custom data","title":"Custom data","text":"function my_pricing_callback(cbdata)\n # Get the reduced costs of the original variables.\n I = [1, 2, 3]\n b = BlockDecomposition.callback_spid(cbdata, model)\n\n rc_y = BlockDecomposition.callback_reduced_cost(cbdata, y[b])\n rc_x = [BlockDecomposition.callback_reduced_cost(cbdata, x[b, i]) for i in I]\n\n # Get the dual values of the custom cuts (to calculate contributions of\n # non-robust cuts to the cost of the solution).\n custduals = Tuple{Int, Float64}[]\n for (_, constr) in Coluna.MathProg.getconstrs(cbdata.form.parent_formulation)\n if typeof(constr.custom_data) == MyCustomCutData\n push!(custduals, (\n constr.custom_data.min_items,\n Coluna.MathProg.getcurincval(cbdata.form.parent_formulation, constr)\n ))\n end\n end\n\n # Pricing by inspection.\n sols = [[1], [2], [3], [1, 2], [1, 3], [2, 3]]\n best_s = Int[]\n best_rc = Inf\n for s in sols\n rc_s = rc_y + sum(rc_x[i] for i in s) # reduced cost of the subproblem variables\n if !isempty(custduals)\n # contribution of the non-robust cuts\n rc_s -= sum((length(s) >= minits) ? dual : 0.0 for (minits, dual) in custduals)\n end\n if rc_s < best_rc\n best_rc = rc_s\n best_s = s\n end\n end\n @show best_s\n # build the best one and submit\n solcost = best_rc\n solvars = JuMP.VariableRef[]\n solvarvals = Float64[]\n for i in best_s\n push!(solvars, x[b, i])\n push!(solvarvals, 1.0)\n end\n push!(solvars, y[b])\n push!(solvarvals, 1.0)\n # submit the solution\n MOI.submit(\n model, BlockDecomposition.PricingSolution(cbdata),\n solcost,\n solvars,\n solvarvals,\n MyCustomVarData(length(best_s)) # attach a custom data to the column\n )\n\n MOI.submit(model, BlockDecomposition.PricingDualBound(cbdata), solcost)\n return\nend","category":"page"},{"location":"start/custom_data/","page":"Custom data","title":"Custom data","text":"The pricing callback is done, we define it as the solver of our pricing problem.","category":"page"},{"location":"start/custom_data/","page":"Custom data","title":"Custom data","text":"subproblems = BlockDecomposition.getsubproblems(dec)\nBlockDecomposition.specify!.(\n subproblems,\n solver = my_pricing_callback\n);\nnothing #hide","category":"page"},{"location":"start/custom_data/#Non-robust-cut-separation-callback.","page":"Custom data","title":"Non-robust cut separation callback.","text":"","category":"section"},{"location":"start/custom_data/","page":"Custom data","title":"Custom data","text":"We now define the cut separation callback for our non-robust cut. This is the same callback as the one used for robust cuts. There is just one slight difference when you submit the non-robust cut. Since cuts are expressed over the master variables and these variables are inaccessible from the JuMP model, you'll submit a constraint with an empty left-hand side and you'll leave Coluna populate the left-hand side with the values returned by Coluna.MathProg.computecoeff.","category":"page"},{"location":"start/custom_data/","page":"Custom data","title":"Custom data","text":"So let's define the callback. Basically, if the solution uses more than one bin with two items, The cut is added to the model.","category":"page"},{"location":"start/custom_data/","page":"Custom data","title":"Custom data","text":"function custom_cut_sep(cbdata)\n # Compute the constraint violation by iterating over the master solution.\n viol = -1.0\n for (varid, varval) in cbdata.orig_sol\n var = Coluna.MathProg.getvar(cbdata.form, varid)\n if !isnothing(var.custom_data)\n if var.custom_data.nb_items >= 2\n viol += varval\n end\n end\n end\n # Add the cut (at most one variable with 2 or more of the 3 items) if violated.\n if viol > 0.001\n MOI.submit(\n model, MOI.UserCut(cbdata),\n JuMP.ScalarConstraint(\n JuMP.AffExpr(0.0), # We cannot express the left-hand side so we push 0.\n MOI.LessThan(1.0)\n ),\n MyCustomCutData(2) # Cut custom data.\n )\n end\n return\nend\n\nMOI.set(model, MOI.UserCutCallback(), custom_cut_sep)\nJuMP.optimize!(model)","category":"page"},{"location":"start/custom_data/","page":"Custom data","title":"Custom data","text":"We see on the output that the algorithm has converged a first time before a cut is added. Coluna then starts a new iteration taking into account the cut. We notice here an improvement of the value of the dual bound: before the cut, we converge towards 1.5. After the cut, we reach 2.0.","category":"page"},{"location":"start/custom_data/","page":"Custom data","title":"Custom data","text":"","category":"page"},{"location":"start/custom_data/","page":"Custom data","title":"Custom data","text":"This page was generated using Literate.jl.","category":"page"},{"location":"start/custom_data/","page":"Custom data","title":"Custom data","text":"","category":"page"},{"location":"api/algorithm/#Algorithm-API","page":"Algorithm API","title":"Algorithm API","text":"","category":"section"},{"location":"api/algorithm/","page":"Algorithm API","title":"Algorithm API","text":"danger: Danger\nThis is WIP. The API will change in future releases.","category":"page"},{"location":"api/algorithm/","page":"Algorithm API","title":"Algorithm API","text":"An algorithm is a procedure that given a model and and input performs some operations and returns an output.","category":"page"},{"location":"api/algorithm/","page":"Algorithm API","title":"Algorithm API","text":"run!","category":"page"},{"location":"api/algorithm/","page":"Algorithm API","title":"Algorithm API","text":"Parameters of an algorithm may contain its child algorithms which used by it. Therefore, the algoirthm tree is formed, in which the root is the algorithm called to solver the model (root algorithm should be an optimization algorithm, see below). ","category":"page"},{"location":"api/algorithm/","page":"Algorithm API","title":"Algorithm API","text":"TODO: explain why the parent algorithm must manage the records/storages of child algorithm.","category":"page"},{"location":"api/algorithm/","page":"Algorithm API","title":"Algorithm API","text":"Algorithms are divided into two types : \"manager algorithms\" and \"worker algorithms\". Worker algorithms just continue the calculation. They do not store and restore units as they suppose it is done by their master algorithms. Manager algorithms may divide the calculation flow into parts. Therefore, they store and restore units to make sure that their child worker algorithms have units prepared. A worker algorithm cannot have child manager algorithms. ","category":"page"},{"location":"api/algorithm/","page":"Algorithm API","title":"Algorithm API","text":"Examples of manager algorithms : TreeSearchAlgorithm (which covers both BCP algorithm and diving algorithm), conquer algorithms, strong branching, branching rule algorithms (which create child nodes). Examples of worker algorithms : column generation, SolveIpForm, SolveLpForm, cut separation, pricing algorithms, etc.","category":"page"},{"location":"api/algorithm/#Optimization-algorithms","page":"Algorithm API","title":"Optimization algorithms","text":"","category":"section"},{"location":"api/algorithm/","page":"Algorithm API","title":"Algorithm API","text":"Optimization algorithms return an OptimizationState.","category":"page"},{"location":"api/algorithm/","page":"Algorithm API","title":"Algorithm API","text":"OptimizationState","category":"page"},{"location":"api/algorithm/#Conventions","page":"Algorithm API","title":"Conventions","text":"","category":"section"},{"location":"api/algorithm/","page":"Algorithm API","title":"Algorithm API","text":"TODO: WIP","category":"page"},{"location":"api/algorithm/","page":"Algorithm API","title":"Algorithm API","text":"infeasible: infinite bounds, no solution, infeasible termination status.","category":"page"},{"location":"api/algorithm/","page":"Algorithm API","title":"Algorithm API","text":"","category":"page"},{"location":"api/treesearch/#Tree-search-API","page":"TreeSearch","title":"Tree search API","text":"","category":"section"},{"location":"api/treesearch/","page":"TreeSearch","title":"TreeSearch","text":"danger: Danger\nUpdate needed.","category":"page"},{"location":"api/treesearch/","page":"TreeSearch","title":"TreeSearch","text":"Now, we define the two concepts we'll use in the tree search algorithms: the node and the search space. The third concept is the explore strategy and implemented in Coluna.","category":"page"},{"location":"api/treesearch/","page":"TreeSearch","title":"TreeSearch","text":"Every tree search algorithm must be associated to a search space.","category":"page"},{"location":"api/treesearch/#Implementing-tree-search-interface","page":"TreeSearch","title":"Implementing tree search interface","text":"","category":"section"},{"location":"api/treesearch/","page":"TreeSearch","title":"TreeSearch","text":"First, we indicate the type of search space used by our algorithms. Note that the type of the search space can depends on the configuration of the algorithm. So there is a 1-to-n relation between tree search algorithm configurations and search space. because one search space can be used by several tree search algorithms configuration.","category":"page"},{"location":"api/treesearch/","page":"TreeSearch","title":"TreeSearch","text":"Now, we implement the method that calls the constructor of a search space. The type of the search space is known from above method. A search space may receive information from the tree-search algorithm. The model, and input arguments are the same than those received by the tree search algorithm.","category":"page"},{"location":"api/treesearch/","page":"TreeSearch","title":"TreeSearch","text":"We implement the method that returns the root node. The definition of the root node depends on the search space.","category":"page"},{"location":"api/treesearch/","page":"TreeSearch","title":"TreeSearch","text":"Then, we implement the method that converts the branching rules into nodes for the tree search algorithm.","category":"page"},{"location":"api/treesearch/","page":"TreeSearch","title":"TreeSearch","text":"We implement the node_change method to update the search space called by the tree search algorithm just after it finishes to evaluate a node and chooses the next one. Be careful, this method is not called after the evaluation of a node when there is no more unevaluated nodes (i.e. tree exploration is finished).","category":"page"},{"location":"api/treesearch/","page":"TreeSearch","title":"TreeSearch","text":"There are two ways to store the state of a formulation at a given node. We can distribute information across the nodes or store the whole state at each node. We follow the second way (so we don't need previous).","category":"page"},{"location":"api/treesearch/","page":"TreeSearch","title":"TreeSearch","text":"Method after_conquer is a callback to do some operations after the conquer of a node and before the divide. Here, we update the best solution found after the conquer algorithm. We implement one method for each search space.","category":"page"},{"location":"api/treesearch/","page":"TreeSearch","title":"TreeSearch","text":"We implement getters to retrieve the input from the search space and the node. The input is passed to the conquer and the divide algorithms.","category":"page"},{"location":"api/treesearch/","page":"TreeSearch","title":"TreeSearch","text":"At last, we implement methods that will return the output of the tree search algorithms. We return the cost of the best solution found. We write one method for each search space.","category":"page"},{"location":"api/treesearch/#API","page":"TreeSearch","title":"API","text":"","category":"section"},{"location":"api/treesearch/#Search-space","page":"TreeSearch","title":"Search space","text":"","category":"section"},{"location":"api/treesearch/","page":"TreeSearch","title":"TreeSearch","text":"Coluna.TreeSearch.AbstractSearchSpace\nColuna.TreeSearch.search_space_type\nColuna.TreeSearch.new_space","category":"page"},{"location":"api/treesearch/#Coluna.TreeSearch.AbstractSearchSpace","page":"TreeSearch","title":"Coluna.TreeSearch.AbstractSearchSpace","text":"Contains the definition of the problem tackled by the tree search algorithm and how the nodes and transitions of the tree search space will be explored.\n\n\n\n\n\n","category":"type"},{"location":"api/treesearch/#Coluna.TreeSearch.search_space_type","page":"TreeSearch","title":"Coluna.TreeSearch.search_space_type","text":"Returns the type of search space depending on the tree-search algorithm and its parameters.\n\n\n\n\n\n","category":"function"},{"location":"api/treesearch/#Coluna.TreeSearch.new_space","page":"TreeSearch","title":"Coluna.TreeSearch.new_space","text":"Creates and returns the search space of a tree search algorithm, its model, and its input.\n\n\n\n\n\n","category":"function"},{"location":"api/treesearch/#Node","page":"TreeSearch","title":"Node","text":"","category":"section"},{"location":"api/treesearch/","page":"TreeSearch","title":"TreeSearch","text":"Coluna.TreeSearch.AbstractNode\nColuna.TreeSearch.new_root\nColuna.TreeSearch.get_parent\nColuna.TreeSearch.get_priority","category":"page"},{"location":"api/treesearch/#Coluna.TreeSearch.AbstractNode","page":"TreeSearch","title":"Coluna.TreeSearch.AbstractNode","text":"A subspace obtained by successive divisions of the search space.\n\n\n\n\n\n","category":"type"},{"location":"api/treesearch/#Coluna.TreeSearch.new_root","page":"TreeSearch","title":"Coluna.TreeSearch.new_root","text":"Creates and returns the root node of a search space.\n\n\n\n\n\n","category":"function"},{"location":"api/treesearch/#Coluna.TreeSearch.get_parent","page":"TreeSearch","title":"Coluna.TreeSearch.get_parent","text":"Returns the parent of a node; nothing if the node is the root.\n\n\n\n\n\n","category":"function"},{"location":"api/treesearch/#Coluna.TreeSearch.get_priority","page":"TreeSearch","title":"Coluna.TreeSearch.get_priority","text":"Returns the priority of the node depending on the explore strategy.\n\n\n\n\n\n","category":"function"},{"location":"api/treesearch/","page":"TreeSearch","title":"TreeSearch","text":"Additional methods needed for Coluna's algorithms:","category":"page"},{"location":"api/treesearch/","page":"TreeSearch","title":"TreeSearch","text":"Coluna.TreeSearch.get_opt_state\nColuna.TreeSearch.get_records\nColuna.TreeSearch.get_branch_description\nColuna.TreeSearch.isroot","category":"page"},{"location":"api/treesearch/#Coluna.TreeSearch.get_branch_description","page":"TreeSearch","title":"Coluna.TreeSearch.get_branch_description","text":"Returns a String to display the branching constraint.\n\n\n\n\n\n","category":"function"},{"location":"api/treesearch/#Coluna.TreeSearch.isroot","page":"TreeSearch","title":"Coluna.TreeSearch.isroot","text":"Returns true is the node is root; false otherwise.\n\n\n\n\n\n","category":"function"},{"location":"api/treesearch/#Tree-search-algorithm","page":"TreeSearch","title":"Tree search algorithm","text":"","category":"section"},{"location":"api/treesearch/","page":"TreeSearch","title":"TreeSearch","text":"Coluna.TreeSearch.AbstractExploreStrategy\nColuna.TreeSearch.tree_search\nColuna.TreeSearch.children\nColuna.TreeSearch.stop\nColuna.TreeSearch.tree_search_output","category":"page"},{"location":"api/treesearch/#Coluna.TreeSearch.AbstractExploreStrategy","page":"TreeSearch","title":"Coluna.TreeSearch.AbstractExploreStrategy","text":"Algorithm that chooses next node to evaluated in the tree search algorithm.\n\n\n\n\n\n","category":"type"},{"location":"api/treesearch/#Coluna.TreeSearch.tree_search","page":"TreeSearch","title":"Coluna.TreeSearch.tree_search","text":"Generic implementation of the tree search algorithm for a given explore strategy.\n\n\n\n\n\n","category":"function"},{"location":"api/treesearch/#Coluna.TreeSearch.children","page":"TreeSearch","title":"Coluna.TreeSearch.children","text":"Evaluate and generate children. This method has a specific implementation for Coluna.\n\n\n\n\n\n","category":"function"},{"location":"api/treesearch/#Coluna.TreeSearch.stop","page":"TreeSearch","title":"Coluna.TreeSearch.stop","text":"Returns true if stopping criteria are met; false otherwise.\n\n\n\n\n\n","category":"function"},{"location":"api/treesearch/#Coluna.TreeSearch.tree_search_output","page":"TreeSearch","title":"Coluna.TreeSearch.tree_search_output","text":"Returns the output of the tree search algorithm.\n\n\n\n\n\n","category":"function"},{"location":"api/treesearch/#Tree-search-algorithm-for-Coluna","page":"TreeSearch","title":"Tree search algorithm for Coluna","text":"","category":"section"},{"location":"api/treesearch/","page":"TreeSearch","title":"TreeSearch","text":"Coluna.Algorithm.AbstractColunaSearchSpace","category":"page"},{"location":"api/treesearch/#Coluna.Algorithm.AbstractColunaSearchSpace","page":"TreeSearch","title":"Coluna.Algorithm.AbstractColunaSearchSpace","text":"Search space for tree search algorithms in Coluna.\n\n\n\n\n\n","category":"type"},{"location":"api/treesearch/","page":"TreeSearch","title":"TreeSearch","text":"The children method has a specific implementation for AbstractColunaSearchSpace` that involves following methods:","category":"page"},{"location":"api/treesearch/","page":"TreeSearch","title":"TreeSearch","text":"Coluna.Algorithm.get_previous\nColuna.Algorithm.set_previous!\nColuna.Algorithm.node_change!\nColuna.Algorithm.get_divide\nColuna.Algorithm.get_reformulation\nColuna.Algorithm.get_input\nColuna.Algorithm.after_conquer!\nColuna.Algorithm.new_children","category":"page"},{"location":"api/treesearch/#Coluna.Algorithm.get_previous","page":"TreeSearch","title":"Coluna.Algorithm.get_previous","text":"Returns the previous node explored by the tree search algorithm.\n\n\n\n\n\n","category":"function"},{"location":"api/treesearch/#Coluna.Algorithm.set_previous!","page":"TreeSearch","title":"Coluna.Algorithm.set_previous!","text":"Sets the previous node explored by the tree search algorithm.\n\n\n\n\n\n","category":"function"},{"location":"api/treesearch/#Coluna.Algorithm.node_change!","page":"TreeSearch","title":"Coluna.Algorithm.node_change!","text":"Methods to perform operations before the tree search algorithm evaluates a node (current). This is useful to restore the state of the formulation for instance.\n\n\n\n\n\n","category":"function"},{"location":"api/treesearch/#Coluna.Algorithm.get_divide","page":"TreeSearch","title":"Coluna.Algorithm.get_divide","text":"Returns the divide algorithm.\n\n\n\n\n\n","category":"function"},{"location":"api/treesearch/#Coluna.Algorithm.get_reformulation","page":"TreeSearch","title":"Coluna.Algorithm.get_reformulation","text":"Returns the reformulation that will be passed to an algorithm.\n\n\n\n\n\n","category":"function"},{"location":"api/treesearch/#Coluna.Algorithm.get_input","page":"TreeSearch","title":"Coluna.Algorithm.get_input","text":"Returns the input that will be passed to an algorithm. The input can be built from information contained in a search space and a node.\n\n\n\n\n\n","category":"function"},{"location":"api/treesearch/#Coluna.Algorithm.after_conquer!","page":"TreeSearch","title":"Coluna.Algorithm.after_conquer!","text":"Methods to perform operations after the conquer algorithms. It receives the output of the conquer algorithm.\n\n\n\n\n\n","category":"function"},{"location":"api/treesearch/#Coluna.Algorithm.new_children","page":"TreeSearch","title":"Coluna.Algorithm.new_children","text":"Creates and returns the children of a node associated to a search space.\n\n\n\n\n\n","category":"function"},{"location":"api/treesearch/","page":"TreeSearch","title":"TreeSearch","text":"","category":"page"},{"location":"api/treesearch/","page":"TreeSearch","title":"TreeSearch","text":"This page was generated using Literate.jl.","category":"page"},{"location":"api/treesearch/","page":"TreeSearch","title":"TreeSearch","text":"","category":"page"},{"location":"start/pricing/","page":"Pricing callback","title":"Pricing callback","text":"EditURL = \"pricing.jl\"","category":"page"},{"location":"start/pricing/#tuto_pricing_callback","page":"Pricing callback","title":"Pricing callback","text":"","category":"section"},{"location":"start/pricing/","page":"Pricing callback","title":"Pricing callback","text":"The pricing callback lets you define how to solve the subproblems of a Dantzig-Wolfe decomposition to generate a new entering column in the master program. This callback is useful when you know an efficient algorithm to solve the subproblems, i.e. an algorithm better than solving the subproblem with a MIP solver.","category":"page"},{"location":"start/pricing/","page":"Pricing callback","title":"Pricing callback","text":"First, we load the packages and define aliases :","category":"page"},{"location":"start/pricing/","page":"Pricing callback","title":"Pricing callback","text":"using Coluna, BlockDecomposition, JuMP, MathOptInterface, GLPK;\nconst BD = BlockDecomposition;\nconst MOI = MathOptInterface;\nnothing #hide","category":"page"},{"location":"start/pricing/","page":"Pricing callback","title":"Pricing callback","text":"Let us see an example with the following generalized assignment problem :","category":"page"},{"location":"start/pricing/","page":"Pricing callback","title":"Pricing callback","text":"M = 1:3;\nJ = 1:15;\nc = [12.7 22.5 8.9 20.8 13.6 12.4 24.8 19.1 11.5 17.4 24.7 6.8 21.7 14.3 10.5; 19.1 24.8 24.4 23.6 16.1 20.6 15.0 9.5 7.9 11.3 22.6 8.0 21.5 14.7 23.2; 18.6 14.1 22.7 9.9 24.2 24.5 20.8 12.9 17.7 11.9 18.7 10.1 9.1 8.9 7.7; 13.1 16.2 16.8 16.7 9.0 16.9 17.9 12.1 17.5 22.0 19.9 14.6 18.2 19.6 24.2];\nw = [61 70 57 82 51 74 98 64 86 80 69 79 60 76 78; 50 57 61 83 81 79 63 99 82 59 83 91 59 99 91;91 81 66 63 59 81 87 90 65 55 57 68 92 91 86; 62 79 73 60 75 66 68 99 69 60 56 100 67 68 54];\nQ = [1020 1460 1530];\nnothing #hide","category":"page"},{"location":"start/pricing/","page":"Pricing callback","title":"Pricing callback","text":"with the following Coluna configuration","category":"page"},{"location":"start/pricing/","page":"Pricing callback","title":"Pricing callback","text":"coluna = optimizer_with_attributes(\n Coluna.Optimizer,\n \"params\" => Coluna.Params(\n solver = Coluna.Algorithm.TreeSearchAlgorithm() # default BCP\n ),\n \"default_optimizer\" => GLPK.Optimizer # GLPK for the master & the subproblems\n);\nnothing #hide","category":"page"},{"location":"start/pricing/","page":"Pricing callback","title":"Pricing callback","text":"for which the JuMP model takes the form:","category":"page"},{"location":"start/pricing/","page":"Pricing callback","title":"Pricing callback","text":"model = BlockModel(coluna);\n\n@axis(M_axis, M);\n\n@variable(model, x[m in M_axis, j in J], Bin);\n@constraint(model, cov[j in J], sum(x[m,j] for m in M_axis) == 1);\n@objective(model, Min, sum(c[m,j]*x[m,j] for m in M_axis, j in J));\n@dantzig_wolfe_decomposition(model, dwdec, M_axis);\nnothing #hide","category":"page"},{"location":"start/pricing/","page":"Pricing callback","title":"Pricing callback","text":"where, as you can see, we omitted the knapsack constraints. These constraints are implicitly defined by the algorithm called in the pricing callback.","category":"page"},{"location":"start/pricing/","page":"Pricing callback","title":"Pricing callback","text":"Let's use a knapsack algorithm defined by the following function to solve the knapsack subproblems:","category":"page"},{"location":"start/pricing/","page":"Pricing callback","title":"Pricing callback","text":"function solve_knapsack(cost, weight, capacity)\n sp_model = Model(GLPK.Optimizer)\n items = 1:length(weight)\n @variable(sp_model, x[i in items], Bin)\n @constraint(sp_model, weight' * x <= capacity)\n @objective(sp_model, Min, cost' * x)\n optimize!(sp_model)\n x_val = value.(x)\n return filter(i -> x_val[i] ≈ 1, collect(items))\nend","category":"page"},{"location":"start/pricing/","page":"Pricing callback","title":"Pricing callback","text":"You can replace the content of the function with any algorithm that solves the knapsack problem (such as algorithms provided by the unregistered package Knapsacks).","category":"page"},{"location":"start/pricing/","page":"Pricing callback","title":"Pricing callback","text":"The pricing callback is a function. It takes as argument cbdata which is a data structure that allows the user to interact with Coluna within the pricing callback.","category":"page"},{"location":"start/pricing/","page":"Pricing callback","title":"Pricing callback","text":"function my_pricing_callback(cbdata)\n # Retrieve the index of the subproblem (it will be one of the values in M_axis)\n cur_machine = BD.callback_spid(cbdata, model)\n\n # Uncomment to see that the pricing callback is called.\n # println(\"Pricing callback for machine $(cur_machine).\")\n\n # Retrieve reduced costs of subproblem variables\n red_costs = [BD.callback_reduced_cost(cbdata, x[cur_machine, j]) for j in J]\n\n # Run the knapsack algorithm\n jobs_assigned_to_cur_machine = solve_knapsack(red_costs, w[cur_machine, :], Q[cur_machine])\n\n # Create the solution (send only variables with non-zero values)\n sol_vars = [x[cur_machine, j] for j in jobs_assigned_to_cur_machine]\n sol_vals = [1.0 for _ in jobs_assigned_to_cur_machine]\n sol_cost = sum(red_costs[j] for j in jobs_assigned_to_cur_machine)\n\n # Submit the solution to the subproblem to Coluna\n MOI.submit(model, BD.PricingSolution(cbdata), sol_cost, sol_vars, sol_vals)\n\n # Submit the dual bound to the solution of the subproblem\n # This bound is used to compute the contribution of the subproblem to the lagrangian\n # bound in column generation.\n MOI.submit(model, BD.PricingDualBound(cbdata), sol_cost) # optimal solution\n return\nend","category":"page"},{"location":"start/pricing/","page":"Pricing callback","title":"Pricing callback","text":"The pricing callback is provided to Coluna using the keyword solver in the method specify!.","category":"page"},{"location":"start/pricing/","page":"Pricing callback","title":"Pricing callback","text":"subproblems = BD.getsubproblems(dwdec);\nBD.specify!.(subproblems, lower_multiplicity = 0, solver = my_pricing_callback);\nnothing #hide","category":"page"},{"location":"start/pricing/","page":"Pricing callback","title":"Pricing callback","text":"You can then optimize :","category":"page"},{"location":"start/pricing/","page":"Pricing callback","title":"Pricing callback","text":"optimize!(model);\nnothing #hide","category":"page"},{"location":"start/pricing/","page":"Pricing callback","title":"Pricing callback","text":"and retrieve the information you need as usual :","category":"page"},{"location":"start/pricing/","page":"Pricing callback","title":"Pricing callback","text":"objective_value(model)","category":"page"},{"location":"start/pricing/","page":"Pricing callback","title":"Pricing callback","text":"","category":"page"},{"location":"start/pricing/","page":"Pricing callback","title":"Pricing callback","text":"This page was generated using Literate.jl.","category":"page"},{"location":"start/pricing/","page":"Pricing callback","title":"Pricing callback","text":"","category":"page"},{"location":"start/initial_columns/","page":"Initial columns callback","title":"Initial columns callback","text":"EditURL = \"initial_columns.jl\"","category":"page"},{"location":"start/initial_columns/#Initial-columns","page":"Initial columns callback","title":"Initial columns","text":"","category":"section"},{"location":"start/initial_columns/","page":"Initial columns callback","title":"Initial columns callback","text":"The initial columns callback let you provide initial columns associated to each problem ahead the optimization. This callback is useful when you have an efficient heuristic that finds feasible solutions to the problem. You can then extract columns from the solutions and give them to Coluna through the callback. You have to make sure the columns you provide are feasible because Coluna won't check their feasibility. The cost of the columns will be computed using the perennial cost of subproblem variables.","category":"page"},{"location":"start/initial_columns/","page":"Initial columns callback","title":"Initial columns callback","text":"Let us see an example with the following generalized assignment problem :","category":"page"},{"location":"start/initial_columns/","page":"Initial columns callback","title":"Initial columns callback","text":"M = 1:3;\nJ = 1:5;\nc = [1 1 1 1 1; 1.2 1.2 1.1 1.1 1; 1.3 1.3 1.1 1.2 1.4];\nQ = [3, 2, 3];\nnothing #hide","category":"page"},{"location":"start/initial_columns/","page":"Initial columns callback","title":"Initial columns callback","text":"with the following Coluna configuration","category":"page"},{"location":"start/initial_columns/","page":"Initial columns callback","title":"Initial columns callback","text":"using JuMP, GLPK, BlockDecomposition, Coluna;\n\ncoluna = optimizer_with_attributes(\n Coluna.Optimizer,\n \"params\" => Coluna.Params(\n solver = Coluna.Algorithm.TreeSearchAlgorithm() # default branch-cut-and-price\n ),\n \"default_optimizer\" => GLPK.Optimizer # GLPK for the master & the subproblems\n);\nnothing #hide","category":"page"},{"location":"start/initial_columns/","page":"Initial columns callback","title":"Initial columns callback","text":"for which the JuMP model takes the form:","category":"page"},{"location":"start/initial_columns/","page":"Initial columns callback","title":"Initial columns callback","text":"@axis(M_axis, M);\nmodel = BlockModel(coluna);\n\n@variable(model, x[m in M_axis, j in J], Bin);\n@constraint(model, cov[j in J], sum(x[m, j] for m in M_axis) >= 1);\n@constraint(model, knp[m in M_axis], sum(x[m, j] for j in J) <= Q[m]);\n@objective(model, Min, sum(c[m, j] * x[m, j] for m in M_axis, j in J));\n\n@dantzig_wolfe_decomposition(model, decomposition, M_axis)\n\nsubproblems = getsubproblems(decomposition)\nspecify!.(subproblems, lower_multiplicity = 0, upper_multiplicity = 1)","category":"page"},{"location":"start/initial_columns/","page":"Initial columns callback","title":"Initial columns callback","text":"Let's consider that the following assignment patterns are good candidates:","category":"page"},{"location":"start/initial_columns/","page":"Initial columns callback","title":"Initial columns callback","text":"machine1 = [[1,2,4], [1,3,4], [2,3,4], [2,3,5]];\nmachine2 = [[1,2], [1,5], [2,5], [3,4]];\nmachine3 = [[1,2,3], [1,3,4], [1,3,5], [2,3,4]];\n\ninitial_columns = [machine1, machine2, machine3];\nnothing #hide","category":"page"},{"location":"start/initial_columns/","page":"Initial columns callback","title":"Initial columns callback","text":"We can write the initial columns callback:","category":"page"},{"location":"start/initial_columns/","page":"Initial columns callback","title":"Initial columns callback","text":"function initial_columns_callback(cbdata)\n # Retrieve the index of the subproblem (it will be one of the values in M_axis)\n spid = BlockDecomposition.callback_spid(cbdata, model)\n println(\"initial columns callback $spid\")\n\n # Retrieve assignment patterns of a given machine\n for col in initial_columns[spid]\n # Create the column in the good representation\n vars = [x[spid, j] for j in col]\n vals = [1.0 for _ in col]\n\n # Submit the column\n MOI.submit(model, BlockDecomposition.InitialColumn(cbdata), vars, vals)\n end\nend","category":"page"},{"location":"start/initial_columns/","page":"Initial columns callback","title":"Initial columns callback","text":"The initial columns callback is a function. It takes as argument cbdata which is a data structure that allows the user to interact with Coluna within the callback.","category":"page"},{"location":"start/initial_columns/","page":"Initial columns callback","title":"Initial columns callback","text":"We provide the initial columns callback to Coluna through the following method:","category":"page"},{"location":"start/initial_columns/","page":"Initial columns callback","title":"Initial columns callback","text":"MOI.set(model, BlockDecomposition.InitialColumnsCallback(), initial_columns_callback)","category":"page"},{"location":"start/initial_columns/","page":"Initial columns callback","title":"Initial columns callback","text":"You can then optimize:","category":"page"},{"location":"start/initial_columns/","page":"Initial columns callback","title":"Initial columns callback","text":"optimize!(model)","category":"page"},{"location":"start/initial_columns/","page":"Initial columns callback","title":"Initial columns callback","text":"","category":"page"},{"location":"start/initial_columns/","page":"Initial columns callback","title":"Initial columns callback","text":"This page was generated using Literate.jl.","category":"page"},{"location":"start/initial_columns/","page":"Initial columns callback","title":"Initial columns callback","text":"","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"EditURL = \"advanced_demo.jl\"","category":"page"},{"location":"start/advanced_demo/#Advanced-tutorial-Location-Routing","page":"Column Generation and Benders on Location Routing","title":"Advanced tutorial - Location Routing","text":"","category":"section"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"We demonstrate the main features of Coluna on a variant of the Location Routing problem. In the Location Routing Problem, we are given a set of facilities and a set of customers. Each customer must be delivered by a route starting from one facility. Each facility has a setup cost, while the cost of a route is the distance traveled.","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"A route is defined as a vector of locations that satisfies the following rules:","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"it must start from an open facility location\nit can finish at any customer (open route variant)\nits length is limited (the maximum number of visited locations is equal to a constant nb_positions)","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"Our objective is to minimize the sum of fixed costs for opening facilities and the total traveled distance while ensuring that each customer is covered by a route.","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"In this tutorial, we will show you how to solve this problem by applying:","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"a direct approach with JuMP and a MILP solver (without Coluna)\na branch-and-price algorithm provided by Coluna, which uses a custom pricing callback to optimize pricing subproblems\na robust branch-cut-and-price algorithm, which separates valid inequalities on the original arc variables (so-called \"robust\" cuts)\na non-robust branch-cut-and-price algorithm, which separates valid inequalities on the route variables of the Dantzig-Wolfe reformulation (so-called \"non-robust\" cuts)\na multi-stage column generation algorithm using two different pricing solvers\na classic Benders decomposition approach, which uses the LP relaxation of the subproblem","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"For illustration purposes, we use a small instance with 2 facilities and 7 customers. The maximum length of a route is fixed to 4. We also provide a larger instance in the last section of the tutorial.","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"nb_positions = 4\nfacilities_fixed_costs = [120, 150]\nfacilities = [1, 2]\ncustomers = [3, 4, 5, 6, 7, 8, 9]\narc_costs =\n [\n 0.0 25.3 25.4 25.4 35.4 37.4 31.9 24.6 34.2;\n 25.3 0.0 21.2 16.2 27.1 26.8 17.8 16.7 23.2;\n 25.4 21.2 0.0 14.2 23.4 23.8 18.3 17.0 21.6;\n 25.4 16.2 14.2 0.0 28.6 28.8 22.6 15.6 29.5;\n 35.4 27.1 23.4 28.6 0.0 42.1 30.4 24.9 39.1;\n 37.4 26.8 23.8 28.8 42.1 0.0 32.4 29.5 38.2;\n 31.9 17.8 18.3 22.6 30.4 32.4 0.0 22.5 30.7;\n 24.6 16.7 17.0 15.6 24.9 29.5 22.5 0.0 21.4;\n 34.2 23.2 21.6 29.5 39.1 38.2 30.7 21.4 0.0;\n ]\nlocations = vcat(facilities, customers)\nnb_customers = length(customers)\nnb_facilities = length(facilities)\npositions = 1:nb_positions;\nnothing #hide","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"In this tutorial, we will use the following packages:","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"using JuMP, HiGHS, GLPK, BlockDecomposition, Coluna;\nnothing #hide","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"We want to set an upper bound nb_routes_per_facility on the number of routes starting from a facility. This limit is computed as follows:","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"# We compute the minimum number of routes needed to visit all customers:\nnb_routes = Int(ceil(nb_customers / nb_positions))\n# We define the upper bound `nb_routes_per_facility`:\nnb_routes_per_facility = min(Int(ceil(nb_routes / nb_facilities)) * 2, nb_routes)\nroutes_per_facility = 1:nb_routes_per_facility;\nnothing #hide","category":"page"},{"location":"start/advanced_demo/#Direct-model","page":"Column Generation and Benders on Location Routing","title":"Direct model","text":"","category":"section"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"First, we solve the problem by a direct approach, using the HiGHS solver. We start by creating a JuMP model:","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"model = JuMP.Model(HiGHS.Optimizer);\nnothing #hide","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"We declare 3 types of binary variables:","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"# y[j] equals 1 if facility j is open; 0 otherwise.\n@variable(model, y[j in facilities], Bin)\n\n# z[u,v] equals 1 if a vehicle travels from u to v; 0 otherwise\n@variable(model, z[u in locations, v in locations], Bin)\n\n# x[i,j,k,p] equals 1 if customer i is delivered from facility j at position p of route k; 0 otherwise\n@variable(model, x[i in customers, j in facilities, k in routes_per_facility, p in positions], Bin);\nnothing #hide","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"We define the constraints:","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"# each customer is visited once\n@constraint(model, cov[i in customers],\n sum(x[i, j, k, p] for j in facilities, k in routes_per_facility, p in positions) == 1)\n\n# a facility is open if there is a route starting from it\n@constraint(model, setup[j in facilities, k in routes_per_facility],\n sum(x[i, j, k, 1] for i in customers) <= y[j])\n\n# flow conservation\n@constraint(model, flow_conservation[j in facilities, k in routes_per_facility, p in positions; p > 1],\n sum(x[i, j, k, p] for i in customers) <= sum(x[i, j, k, p-1] for i in customers))\n\n# there is an arc between two customers whose demand is satisfied by the same route at consecutive positions\n@constraint(model, route_arc[i in customers, l in customers, j in facilities, k in routes_per_facility, p in positions; p > 1 && i != l],\n z[i, l] >= x[l, j, k, p] + x[i, j, k, p-1] - 1)\n\n# there is an arc between facility `j` and the first customer visited by route `k` from facility `j`\n@constraint(model, start_arc[i in customers, j in facilities, k in routes_per_facility],\n z[j, i] >= x[i, j, k, 1]);\nnothing #hide","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"We set the objective function:","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"@objective(model, Min,\n sum(arc_costs[u, v] * z[u, v] for u in locations, v in locations)\n +\n sum(facilities_fixed_costs[j] * y[j] for j in facilities));\nnothing #hide","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"and we optimize the model:","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"optimize!(model)\nobjective_value(model)","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"We find an optimal solution involving two routes starting from facility 1:","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"1 -> 8 -> 9 -> 3 -> 6\n1 -> 4 -> 5 -> 7","category":"page"},{"location":"start/advanced_demo/#Dantzig-Wolfe-decomposition-and-Branch-and-Price","page":"Column Generation and Benders on Location Routing","title":"Dantzig-Wolfe decomposition and Branch-and-Price","text":"","category":"section"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"One can solve the problem by exploiting its structure with a Dantzig-Wolfe decomposition approach. The subproblem induced by such decomposition amounts to generate routes starting from each facility. A possible decomposition is to consider a subproblem associated with each vehicle, generating the vehicle route. However, for a given facility, the vehicles that are identical will give rise to the same subproblem and route solutions. So instead of this decomposition with several identical subproblems for each facility, we define below a single subproblem per facility. For each subproblem, we define its multiplicity, i.e. we bound the number of solutions of this subproblem that can be used in a master solution.","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"The following method creates the model according to the decomposition described:","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"function create_model(optimizer, pricing_algorithms)\n # A user should resort to axes to communicate to Coluna how to decompose a formulation.\n # For our problem, we declare an axis over the facilities, thus `facilities_axis` contain subproblem indices.\n # We must use `facilities_axis` instead of `facilities` in the declaration of the\n # variables and constraints that belong to pricing subproblems.\n @axis(facilities_axis, collect(facilities))\n\n # We declare a `BlockModel` instead of `Model`.\n model = BlockModel(optimizer)\n\n # `y[j]` is a master variable equal to 1 if the facility j is open; 0 otherwise\n @variable(model, y[j in facilities], Bin)\n\n # `x[i,j]` is a subproblem variable equal to 1 if customer i is delivered from facility j; 0 otherwise.\n @variable(model, x[i in customers, j in facilities_axis], Bin)\n # `z[u,v]` is assimilated to a subproblem variable equal to 1 if a vehicle travels from u to v; 0 otherwise.\n # we don't use the `facilities_axis` axis here because the `z` variables are defined as\n # representatives of the subproblem variables later in the model.\n @variable(model, z[u in locations, v in locations], Bin)\n\n # `cov` constraints are master constraints ensuring that each customer is visited once.\n @constraint(model, cov[i in customers],\n sum(x[i, j] for j in facilities) >= 1)\n\n # `open_facilities` are master constraints ensuring that the depot is open if one vehicle\n # leaves it.\n @constraint(model, open_facility[j in facilities],\n sum(z[j, i] for i in customers) <= y[j] * nb_routes_per_facility)\n\n # We don't need to describe the subproblem constraints because we use a pricing callback.\n\n # We set the objective function:\n @objective(model, Min,\n sum(arc_costs[u, v] * z[u, v] for u in locations, v in locations) +\n sum(facilities_fixed_costs[j] * y[j] for j in facilities)\n )\n\n # We perform decomposition over the facilities.\n @dantzig_wolfe_decomposition(model, dec, facilities_axis)\n\n # Subproblems generate routes starting from each facility.\n # The number of routes from each facility is at most `nb_routes_per_facility`.\n subproblems = BlockDecomposition.getsubproblems(dec)\n specify!.(subproblems, lower_multiplicity=0, upper_multiplicity=nb_routes_per_facility, solver=pricing_algorithms)\n\n # We define `z` as a subproblem variable common to all subproblems.\n # Each implicit variable `z` replaces a sum of explicit `z'` variables: `z[u,v] = sum(z'[j,u,v] for j in facilities_axis)`\n # This way the model is simplified, and column generation is accelerated as the reduced cost for pair `z[u,v]` is calculated only once\n # instead of performing the same reduced cost calculation for variables `z'[j,u,v]`, `j in facilities_axis`.\n subproblemrepresentative.(z, Ref(subproblems))\n\n return model, x, y, z, cov\nend;\nnothing #hide","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"Contrary to the direct model, we do not add constraints to ensure the feasibility of the routes because we solve our subproblems in a pricing callback. The user who implements the pricing callback has the responsibility to create only feasible routes.","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"We setup Coluna:","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"coluna = optimizer_with_attributes(\n Coluna.Optimizer,\n \"params\" => Coluna.Params(\n solver=Coluna.Algorithm.TreeSearchAlgorithm( ## default branch-and-bound of Coluna\n maxnumnodes=100,\n conqueralg=Coluna.ColCutGenConquer() ## default column and cut generation of Coluna\n ) ## default branch-cut-and-price\n ),\n \"default_optimizer\" => GLPK.Optimizer # GLPK for the master & the subproblems\n);\nnothing #hide","category":"page"},{"location":"start/advanced_demo/#Pricing-callback","page":"Column Generation and Benders on Location Routing","title":"Pricing callback","text":"","category":"section"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"If the user declares all the necessary subproblem constraints and possibly additional subproblem variables to describe the set of feasible subproblem solutions, Coluna may perform automatic Dantzig-Wolfe decomposition in which the pricing subproblems are solved by applying a (default) MIP solver. In our case, applying a MIP solver is not the most efficient way to solve the pricing problem. Therefore, we implement an ad-hoc algorithm for solving the pricing subproblems and declare it as a pricing callback. In our pricing callback for a given facility, we inspect all feasible routes enumerated before calling the branch-cut-and-price algorithm. The inspection algorithm calculates the reduced cost for each enumerated route and returns a route with the minimum reduced cost.","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"We first define a structure to store the routes:","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"mutable struct Route\n length::Int # record the length of the route (number of visited customers + 1)\n path::Vector{Int} # record the sequence of visited customers\nend;\nnothing #hide","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"We can reduce the number of enumerated routes by exploiting the following property. Consider two routes starting from the same facility and visiting the same subset of locations (customers). These two routes correspond to columns with the same vector of coefficients in master constraints. A solution containing the route with a larger traveled distance (i.e., larger route original cost) is dominated: this dominated route can be replaced by the other route without increasing the total solution cost. Therefore, for each subset of locations of a size not exceeding the maximum one, the enumeration procedure keeps only one route visiting this subset, the one with the smallest cost.","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"A method that computes the cost of a route:","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"function route_original_cost(arc_costs, route::Route)\n route_cost = 0.0\n path = route.path\n path_length = route.length\n for i in 1:(path_length-1)\n route_cost += arc_costs[path[i], path[i+1]]\n end\n return route_cost\nend;\nnothing #hide","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"This procedure finds a least-cost sequence of visiting the given set of customers starting from a given facility.","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"function best_visit_sequence(arc_costs, cust_subset, facility_id)\n # generate all the possible visit orders\n set_size = size(cust_subset)[1]\n all_paths = collect(multiset_permutations(cust_subset, set_size))\n all_routes = Vector{Route}()\n for path in all_paths\n # add the first index i.e. the facility id\n enpath = vcat([facility_id], path)\n # length of the route = 1 + number of visited customers\n route = Route(set_size + 1, enpath)\n push!(all_routes, route)\n end\n # compute each route original cost\n routes_costs = map(r ->\n (r, route_original_cost(arc_costs, r)), all_routes)\n # keep only the best visit sequence\n tmp = argmin([c for (_, c) in routes_costs])\n (best_order, _) = routes_costs[tmp]\n return best_order\nend;\nnothing #hide","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"We are now able to compute a dominating route for all the possible customers' subsets, given a facility id:","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"using Combinatorics\n\nfunction best_route_forall_cust_subsets(arc_costs, customers, facility_id, max_size)\n best_routes = Vector{Route}()\n all_subsets = Vector{Vector{Int}}()\n for subset_size in 1:max_size\n subsets = collect(combinations(customers, subset_size))\n for s in subsets\n push!(all_subsets, s)\n end\n end\n for s in all_subsets\n route_s = best_visit_sequence(arc_costs, s, facility_id)\n push!(best_routes, route_s)\n end\n return best_routes\nend;\nnothing #hide","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"We store all the information given by the enumeration phase in a dictionary. For each facility id, we match a vector of routes that are the best visiting sequences for each possible subset of customers.","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"routes_per_facility = Dict(\n j => best_route_forall_cust_subsets(arc_costs, customers, j, nb_positions) for j in facilities\n)","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"Our pricing callback must compute the reduced cost of each route, given the reduced cost of the subproblem variables x and z. Remember that subproblem variables z are implicitly defined by master representative variables z. We remark that z variables participate only in the objective function. Thus their reduced costs are initially equal to the original costs (i.e., objective coefficients) This is not true anymore after adding branching constraints and robust cuts involving variables z.","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"We need methods to compute the contributions to the reduced cost of the x and z variables:","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"function x_contribution(route::Route, j::Int, x_red_costs)\n x = 0.0\n visited_customers = route.path[2:route.length]\n for i in visited_customers\n x += x_red_costs[\"x_$(i)_$(j)\"]\n end\n return x\nend;\n\nfunction z_contribution(route::Route, z_red_costs)\n z = 0.0\n for i in 1:(route.length-1)\n current_position = route.path[i]\n next_position = route.path[i+1]\n z += z_red_costs[\"z_$(current_position)_$(next_position)\"]\n end\n return z\nend;\nnothing #hide","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"We are now able to write our pricing callback:","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"function pricing_callback(cbdata)\n # Get the id of the facility.\n j = BlockDecomposition.indice(BlockDecomposition.callback_spid(cbdata, model))\n\n # Retrieve variables reduced costs.\n z_red_costs = Dict(\n \"z_$(u)_$(v)\" => BlockDecomposition.callback_reduced_cost(cbdata, z[u, v]) for u in locations, v in locations)\n x_red_costs = Dict(\n \"x_$(i)_$(j)\" => BlockDecomposition.callback_reduced_cost(cbdata, x[i, j]) for i in customers\n )\n\n # Keep route with minimum reduced cost.\n red_costs_j = map(r -> (\n r,\n x_contribution(r, j, x_red_costs) + z_contribution(r, z_red_costs) # the reduced cost of a route is the sum of the contribution of the variables\n ), routes_per_facility[j]\n )\n min_index = argmin([x for (_, x) in red_costs_j])\n (best_route, min_reduced_cost) = red_costs_j[min_index]\n\n # Retrieve the route's arcs.\n best_route_arcs = Vector{Tuple{Int,Int}}()\n for i in 1:(best_route.length-1)\n push!(best_route_arcs, (best_route.path[i], best_route.path[i+1]))\n end\n best_route_customers = best_route.path[2:best_route.length]\n\n # Create the solution (send only variables with non-zero values).\n z_vars = [z[u, v] for (u, v) in best_route_arcs]\n x_vars = [x[i, j] for i in best_route_customers]\n sol_vars = vcat(z_vars, x_vars)\n sol_vals = ones(Float64, length(z_vars) + length(x_vars))\n sol_cost = min_reduced_cost\n\n # Submit the solution to the subproblem to Coluna.\n MOI.submit(model, BlockDecomposition.PricingSolution(cbdata), sol_cost, sol_vars, sol_vals)\n\n # Submit the dual bound to the solution of the subproblem.\n # This bound is used to compute the contribution of the subproblem to the lagrangian\n # bound in column generation.\n MOI.submit(model, BlockDecomposition.PricingDualBound(cbdata), sol_cost) ## optimal solution\n\nend;\nnothing #hide","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"Create the model:","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"model, x, y, z, _ = create_model(coluna, pricing_callback);\nnothing #hide","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"Solve:","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"JuMP.optimize!(model)","category":"page"},{"location":"start/advanced_demo/#Strengthening-the-master-with-linear-valid-inequalities-on-the-original-variables-(so-called-\"robust\"-cuts)","page":"Column Generation and Benders on Location Routing","title":"Strengthening the master with linear valid inequalities on the original variables (so-called \"robust\" cuts)","text":"","category":"section"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"To improve the quality of the linear relaxation, a family of classic facility location valid inequalities can be used:","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"x_ij leq y_j forall i in I forall j in J","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"where I is the set of customers and J the set of facilities.","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"We declare a structure representing an inequality in this family:","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"struct OpenFacilityInequality\n facility_id::Int\n customer_id::Int\nend","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"To identify violated valid inequalities from a current master LP solution, we proceed by enumeration (i.e. iterating over all pairs of customer and facility). Enumeration separation procedure is implemented in the following callback.","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"function valid_inequalities_callback(cbdata)\n # Get variables valuations and store them in dictionaries.\n x_vals = Dict(\n \"x_$(i)_$(j)\" => BlockDecomposition.callback_value(cbdata, x[i, j]) for i in customers, j in facilities\n )\n y_vals = Dict(\n \"y_$(j)\" => BlockDecomposition.callback_value(cbdata, y[j]) for j in facilities\n )\n\n # Separate the valid inequalities (i.e. retrieve the inequalities that are violated by\n # the current solution) by enumeration.\n inequalities = OpenFacilityInequality[]\n\n for j in facilities\n y_j = y_vals[\"y_$(j)\"]\n for i in customers\n x_i_j = x_vals[\"x_$(i)_$(j)\"]\n if x_i_j > y_j\n push!(inequalities, OpenFacilityInequality(j, i))\n end\n end\n end\n\n # Add the valid inequalities to the model.\n for ineq in inequalities\n constr = JuMP.@build_constraint(x[ineq.customer_id, ineq.facility_id] <= y[ineq.facility_id])\n MOI.submit(model, MOI.UserCut(cbdata), constr)\n end\nend;\nnothing #hide","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"We re-declare the model and optimize it with these valid inequalities:","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"model, x, y, z, _ = create_model(coluna, pricing_callback);\nMOI.set(model, MOI.UserCutCallback(), valid_inequalities_callback);\nJuMP.optimize!(model)","category":"page"},{"location":"start/advanced_demo/#Strengthening-the-master-with-valid-inequalities-on-the-column-generation-variables-(so-called-\"non-robust\"-cuts)","page":"Column Generation and Benders on Location Routing","title":"Strengthening the master with valid inequalities on the column generation variables (so-called \"non-robust\" cuts)","text":"","category":"section"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"In order to further strengthen the linear relaxation of the Dantzig-Wolfe reformulation, we separate a family of subset-row cuts, which is a subfamily of Chvátal-Gomory rank-1 cuts (R1C), obtained from the set-partitioning constraints. These cuts cannot be expressed as a linear combination of the original variables of the model. Instead, they are expressed with the master columns variables λ_k, k in K, where K is the set of generated columns or set of solutions returned by the pricing subproblems. Subset-row cuts are \"non-robust\" in the sense that they modify the structure of the pricing subproblems, and not just the reduced cost of subproblem variables. Thus, the implementation of the pricing callback should be updated to take into account dual costs associated with non-robust cutting planes.","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"Each Chvátal-Gomory rank-1 cut is characterized by a subset of set-partitioning constraints, or equivalently by a subset C of customers, and a multiplier alpha_i for each customer iin C:","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"sum_k in K lfloor sum_i in C alpha_i tildex^k_ij lambda_k rfloor leq lfloor sum_iin C alpha_i rfloor C subseteq I","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"where tildex^k_ij is the value of the variable x_ij in the k-th column generated. For subset-row cuts, C=3, and alpha_i=frac12, iin C.","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"Since we obtain subset-row cuts based on set-partitioning constraints, we must be able to differentiate them from the other constraints of the model. To do this, we exploit a feature of Coluna that allows us to attach custom data to the constraints and variables of a model, via the add-ons of BlockDecomposition package.","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"First, we create special custom data with the only information we need to characterize our cover constraints: the customer id that corresponds to this constraint.","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"struct CoverConstrData <: BlockDecomposition.AbstractCustomData\n customer::Int\nend","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"We re-create the model:","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"(model, x, y, z, cov) = create_model(coluna, pricing_callback);\nnothing #hide","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"We declare our custom data to Coluna and we attach one custom data to each cover constraint","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"BlockDecomposition.customconstrs!(model, CoverConstrData);\n\nfor i in customers\n customdata!(cov[i], CoverConstrData(i))\nend","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"We perform the separation by enumeration (i.e. iterating over all subsets of customers of size three).","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"The subset-row cut has the following form:","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"sum_k in K tildealpha(C k) lambda_k leq 1 C subseteq I C = 3","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"where coefficient tildealpha(C k) equals 1 if route k visits at least two customers of C; 0 otherwise.","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"For instance, if we consider separating a cut over constraints cov[3], cov[6] and cov[8], then the route 1->4->6->7 has a zero coefficient while the route 1->4->6->3 has a coefficient equal to one.","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"Since columns are generated dynamically, we cannot pre-compute the coefficients of columns in the subset-row cuts. Instead, coefficients are computed dynamically via a user-defined computecoeff method which takes a cut and a column as arguments. To recognize which cut and which column are passed to the method, custom data structures are attached to the cut constraints and the master variables. When a new column is generated, Coluna computes its coefficients in the original constraints and robust cuts using coefficients of subproblem variables in the master constraints. Coluna retrieves coefficients of the new column in the non-robust cuts by calling the computecoeff method for the column and each such cut. When a new non-robust cut is generated, Coluna retrieves the coefficients of columns in this cut by calling the computecoeff method for the cut and all existing columns.","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"We now proceed to the implementation of necessary data structures and methods needed to support the subset-row cuts. First, we attach a custom data structure to master columns λ_k associated with a given route k. They record the set of customers that are visited by the given route k.","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"Thus, to each λ_k, we associate a R1cVarData structure that carries the customers it visits.","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"struct R1cVarData <: BlockDecomposition.AbstractCustomData\n visited_locations::Vector{Int}\nend","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"Then, we attach a R1cCutData custom data structure to the subset-row cuts. It contains the set C of customers characterizing the cut.","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"struct R1cCutData <: BlockDecomposition.AbstractCustomData\n cov_constrs::Vector{Int}\nend","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"We declare our custom data to Coluna via BlockDecomposition add-ons:","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"BlockDecomposition.customvars!(model, R1cVarData)\nBlockDecomposition.customconstrs!(model, [CoverConstrData, R1cCutData]);\nnothing #hide","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"The next method calculates the coefficients of a column λ_k in a subset-row cut:","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"function Coluna.MathProg.computecoeff(\n var_custom_data::R1cVarData, constr_custom_data::R1cCutData\n)\n return floor(1 / 2 * length(var_custom_data.visited_locations ∩ constr_custom_data.cov_constrs))\nend","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"We also need to define a second method for the case of the cover constraints. Indeed, we use custom data to know the customer attached to each cover constraint There is no contribution of the non-robust part of the coefficient of the λ_k, so the method returns 0.","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"function Coluna.MathProg.computecoeff(::R1cVarData, ::CoverConstrData)\n return 0\nend","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"We are now able to write our rank-one cut callback completely:","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"function r1c_callback(cbdata)\n original_sol = cbdata.orig_sol\n master = Coluna.MathProg.getmodel(original_sol)\n # Retrieve the cover constraints.\n cov_constrs = Int[]\n for constr in values(Coluna.MathProg.getconstrs(master))\n constr_custom_data = Coluna.MathProg.getcustomdata(master, constr)\n if typeof(constr_custom_data) <: CoverConstrData\n push!(cov_constrs, constr_custom_data.customer)\n end\n end\n\n # Retrieve the master columns λ and their values in the current fractional solution\n lambdas = Tuple{Float64,Coluna.MathProg.Variable}[]\n for (var_id, val) in original_sol\n if Coluna.MathProg.getduty(var_id) <= Coluna.MathProg.MasterCol\n push!(lambdas, (val, Coluna.MathProg.getvar(master, var_id)))\n end\n end\n\n # Separate the valid subset-row cuts violated by the current solution.\n # For a fixed subset of customers of size three, iterate on the master columns\n # and check if lhs > 1:\n for cov_constr_subset in collect(combinations(cov_constrs, 3))\n lhs = 0\n for lambda in lambdas\n (val, var) = lambda\n var_custom_data = Coluna.MathProg.getcustomdata(master, var)\n if !isnothing(var_custom_data)\n coeff = floor(1 / 2 * length(var_custom_data.visited_locations ∩ cov_constr_subset))\n lhs += coeff * val\n end\n end\n if lhs > 1\n # Create the constraint and add it to the model.\n MOI.submit(model,\n MOI.UserCut(cbdata),\n JuMP.ScalarConstraint(JuMP.AffExpr(0.0), MOI.LessThan(1.0)),\n R1cCutData(cov_constr_subset)\n )\n end\n end\nend;\nnothing #hide","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"When creating non-robust constraints, only the linear (i.e., robust) part is passed to the model. In our case, the constraint 0 <= 1 is passed. As explained above, the non-robust part is computed by calling the computecoeff method using the structure of type R1cCutData provided.","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"Finally, we need to update our pricing callback to take into account the active non-robust cuts. The contribution of these cuts to the reduced cost of a column is not captured by the reduced cost of subproblem variables. We must therefore take this contribution into account manually, by inquiring the set of existing non-robust cuts and their values in the current dual solution.","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"The contribution of a subset-row cut to the reduced cost of a route is managed by the following method:","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"function r1c_contrib(route::Route, custduals)\n cost = 0\n if !isempty(custduals)\n for (r1c_cov_constrs, dual) in custduals\n coeff = floor(1 / 2 * length(route.path ∩ r1c_cov_constrs))\n cost += coeff * dual\n end\n end\n return cost\nend;\nnothing #hide","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"We re-write our pricing callback to:","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"retrieve the dual cost of the subset-row cuts\ntake into account the contribution of the subset-row cuts in the reduced cost of the route\nattach custom data to the route so that its coefficient in the existing non-robust cuts can be computed","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"function pricing_callback(cbdata)\n j = BlockDecomposition.indice(BlockDecomposition.callback_spid(cbdata, model))\n z_red_costs = Dict(\n \"z_$(u)_$(v)\" => BlockDecomposition.callback_reduced_cost(cbdata, z[u, v]) for u in locations, v in locations\n )\n x_red_costs = Dict(\n \"x_$(i)_$(j)\" => BlockDecomposition.callback_reduced_cost(cbdata, x[i, j]) for i in customers\n )\n\n # FIRST CHANGE HERE:\n # Get the dual values of the constraints of the specific type to compute the contributions of\n # non-robust cuts to the cost of the solution:\n master = cbdata.form.parent_formulation\n custduals = Tuple{Vector{Int},Float64}[]\n for (_, constr) in Coluna.MathProg.getconstrs(master)\n constr_custom_data = Coluna.MathProg.getcustomdata(master, constr)\n if typeof(constr_custom_data) == R1cCutData\n push!(custduals, (\n constr_custom_data.cov_constrs,\n Coluna.MathProg.getcurincval(master, constr)\n ))\n end\n end\n # END OF FIRST CHANGE\n\n # SECOND CHANGE HERE:\n # Keep route with the minimum reduced cost: contribution of the subproblem variables and\n # the non-robust cuts.\n red_costs_j = map(r -> (\n r,\n x_contribution(r, j, x_red_costs) + z_contribution(r, z_red_costs) - r1c_contrib(r, custduals)\n ), routes_per_facility[j]\n )\n # END OF SECOND CHANGE\n min_index = argmin([x for (_, x) in red_costs_j])\n best_route, min_reduced_cost = red_costs_j[min_index]\n\n best_route_arcs = Tuple{Int,Int}[]\n for i in 1:(best_route.length-1)\n push!(best_route_arcs, (best_route.path[i], best_route.path[i+1]))\n end\n best_route_customers = best_route.path[2:best_route.length]\n z_vars = [z[u, v] for (u, v) in best_route_arcs]\n x_vars = [x[i, j] for i in best_route_customers]\n sol_vars = vcat(z_vars, x_vars)\n sol_vals = ones(Float64, length(z_vars) + length(x_vars))\n sol_cost = min_reduced_cost\n\n # Submit the solution of the subproblem to Coluna\n # THIRD CHANGE HERE:\n # You must attach the visited customers in the structure of type `R1cVarData` to the solution of the subproblem\n MOI.submit(\n model, BlockDecomposition.PricingSolution(cbdata), sol_cost, sol_vars, sol_vals,\n R1cVarData(best_route.path)\n )\n # END OF THIRD CHANGE\n MOI.submit(model, BlockDecomposition.PricingDualBound(cbdata), sol_cost)\nend\n\nMOI.set(model, MOI.UserCutCallback(), r1c_callback);\nJuMP.optimize!(model)","category":"page"},{"location":"start/advanced_demo/#Multi-stage-pricing-callback","page":"Column Generation and Benders on Location Routing","title":"Multi-stage pricing callback","text":"","category":"section"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"In this section, we implement a pricing heuristic that can be used together with the exact pricing callback to generate subproblems solutions.","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"The idea of the heuristic is very simple:","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"Given a facility j, the heuristic finds the closest customer to j and adds it to the route.\nThen, while the reduced cost keeps improving and the maximum length of the route is not reached, the heuristic computes and adds to the route the nearest neighbor to the last customer of the route.","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"We first define an auxiliary function used to compute the route tail's nearest neighbor at each step:","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"function add_nearest_neighbor(route::Route, customers, costs)\n # Get the last customer of the route.\n loc = last(route.path)\n # Initialize its nearest neighbor to zero and mincost to infinity.\n (nearest, mincost) = (0, Inf)\n # Compute nearest and mincost.\n for i in customers\n if !(i in route.path) # implying in particular (i != loc)\n if (costs[loc, i] < mincost)\n nearest = i\n mincost = costs[loc, i]\n end\n end\n end\n # Add the last customer's nearest neighbor to the route.\n if nearest != 0\n push!(route.path, nearest)\n route.length += 1\n end\nend;\nnothing #hide","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"We then define our heuristic for the enumeration of the routes, the method returns the best route found by the heuristic together with its cost:","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"function enumeration_heuristic(x_red_costs, z_red_costs, j)\n # Initialize our \"greedy best route\".\n best_route = Route(1, [j])\n # Initialize the route's cost to zero.\n current_redcost = 0.0\n old_redcost = Inf\n\n # main loop\n while (current_redcost < old_redcost)\n add_nearest_neighbor(best_route, customers, arc_costs)\n old_redcost = current_redcost\n current_redcost = x_contribution(best_route, j, x_red_costs) +\n z_contribution(best_route, z_red_costs)\n # Max length is reached.\n if best_route.length == nb_positions\n break\n end\n end\n return (best_route, current_redcost)\nend;\nnothing #hide","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"We can now define our heuristic pricing callback:","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"function approx_pricing(cbdata)\n\n j = BlockDecomposition.indice(BlockDecomposition.callback_spid(cbdata, model))\n z_red_costs = Dict(\n \"z_$(u)_$(v)\" => BlockDecomposition.callback_reduced_cost(cbdata, z[u, v]) for u in locations, v in locations\n )\n x_red_costs = Dict(\n \"x_$(i)_$(j)\" => BlockDecomposition.callback_reduced_cost(cbdata, x[i, j]) for i in customers\n )\n\n # Call the heuristic to elect the \"greedy best route\":\n best_route, sol_cost = enumeration_heuristic(x_red_costs, z_red_costs, j)\n\n # Build the solution:\n best_route_arcs = Vector{Tuple{Int,Int}}()\n for i in 1:(best_route.length-1)\n push!(best_route_arcs, (best_route.path[i], best_route.path[i+1]))\n end\n best_route_customers = best_route.path[2:length(best_route.path)]\n\n z_vars = [z[u, v] for (u, v) in best_route_arcs]\n x_vars = [x[i, j] for i in best_route_customers]\n sol_vars = vcat(z_vars, x_vars)\n sol_vals = ones(Float64, length(z_vars) + length(x_vars))\n\n MOI.submit(model, BlockDecomposition.PricingSolution(cbdata), sol_cost, sol_vars, sol_vals)\n # As the procedure is inexact, no dual bound can be computed, we set it to -Inf.\n MOI.submit(model, BlockDecomposition.PricingDualBound(cbdata), -Inf)\nend;\nnothing #hide","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"We set the solver; colgen_stages_pricing_solvers indicates which solver to use first (here it is approx_pricing)","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"coluna = JuMP.optimizer_with_attributes(\n Coluna.Optimizer,\n \"default_optimizer\" => GLPK.Optimizer,\n \"params\" => Coluna.Params(\n solver=Coluna.Algorithm.BranchCutAndPriceAlgorithm(\n maxnumnodes=100,\n colgen_stages_pricing_solvers=[2, 1]\n )\n )\n);\nnothing #hide","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"We add the two pricing algorithms to our model:","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"model, x, y, z, cov = create_model(coluna, [approx_pricing, pricing_callback]);\nnothing #hide","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"We declare our custom data to Coluna:","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"BlockDecomposition.customvars!(model, R1cVarData)\nBlockDecomposition.customconstrs!(model, [CoverConstrData, R1cCutData]);\nfor i in customers\n customdata!(cov[i], CoverConstrData(i))\nend","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"Optimize:","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"JuMP.optimize!(model)","category":"page"},{"location":"start/advanced_demo/#Benders-decomposition","page":"Column Generation and Benders on Location Routing","title":"Benders decomposition","text":"","category":"section"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"In this section, we show how one can solve the linear relaxation of the master program of a Benders Decomposition approach to this facility location demo problem.","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"The first-stage decisions consist in choosing a subset of facilities to open. The second-stage decisions consist in choosing the routes that are assigned to each facility. The second stage problem is an integer program, so for simplicity, we use its linear relaxation instead. To improve the quality of this relaxation, we enumerate the routes and use one variable per route. As this approach is practical only for small instances, we use it only for illustration purposes. For larger instances, we would have to implement a column generation approach to solve the subproblem, i.e., the Benders cut separation problem.","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"In the same spirit as the above models, we use the variables. Let y[j] equal 1 if the facility j is open and 0 otherwise. Let λ[j,k] equal 1 if route k starting from facility j is selected and 0 otherwise.","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"Since there is only one subproblem in the second stage, we introduce a fake axis that contains only one element. This approach can be generalized to the case where customer demand uncertainty is expressed with scenarios. In this case, we would have one subproblem for each scenario, and the axis would have been defined for the set of scenarios. In our case, the set of scenarios consists of one \"fake\" scenario.","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"fake = 1\n@axis(axis, collect(fake:fake))\n\ncoluna = JuMP.optimizer_with_attributes(\n Coluna.Optimizer,\n \"params\" => Coluna.Params(solver=Coluna.Algorithm.BendersCutGeneration()\n ),\n \"default_optimizer\" => GLPK.Optimizer\n)\n\nmodel = BlockModel(coluna);\nnothing #hide","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"We introduce auxiliary structures to improve the clarity of the code.","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"# routes covering customer i from facility j.\ncovering_routes = Dict(\n (j, i) => findall(r -> (i in r.path), routes_per_facility[j]) for i in customers, j in facilities\n);\n# routes costs from facility j.\nroutes_costs = Dict(\n j => [route_original_cost(arc_costs, r) for r in routes_per_facility[j]] for j in facilities\n);\nnothing #hide","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"We declare the variables.","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"@variable(model, 0 <= y[j in facilities] <= 1) ## 1st stage\n@variable(model, 0 <= λ[f in axis, j in facilities, k in 1:length(routes_per_facility[j])] <= 1); ## 2nd stage\nnothing #hide","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"We declare the constraints.","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"# Linking constraints\n@constraint(model, open[fake in axis, j in facilities, k in 1:length(routes_per_facility[j])],\n y[j] >= λ[fake, j, k])\n\n# Second-stage constraints\n@constraint(model, cover[fake in axis, i in customers],\n sum(λ[fake, j, k] for j in facilities, k in covering_routes[(j, i)]) >= 1)\n\n# Second-stage constraints\n@constraint(model, limit_nb_routes[fake in axis, j in facilities],\n sum(λ[fake, j, q] for q in 1:length(routes_per_facility[j])) <= nb_routes_per_facility\n)\n\n# First-stage constraint\n# This constraint is redundant, we add it in order not to start with an empty master problem\n@constraint(model, min_opening,\n sum(y[j] for j in facilities) >= 1)\n\n@objective(model, Min,\n sum(facilities_fixed_costs[j] * y[j] for j in facilities) +\n sum(routes_costs[j][k] * λ[fake, j, k] for j in facilities, k in 1:length(routes_per_facility[j])));\nnothing #hide","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"We perform the decomposition over the axis and we optimize the problem.","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"@benders_decomposition(model, dec, axis)\nJuMP.optimize!(model)","category":"page"},{"location":"start/advanced_demo/#Example-of-comparison-of-the-dual-bounds","page":"Column Generation and Benders on Location Routing","title":"Example of comparison of the dual bounds","text":"","category":"section"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"In this section, we use a larger instance with 3 facilities and 13 customers. We solve only the root node and look at the dual bound:","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"with the standard column generation (without cut separation)\nby adding robust cuts\nby adding non-robust cuts\nby adding both robust and non-robust cuts","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"nb_positions = 6\nfacilities_fixed_costs = [120, 150, 110]\nfacilities = [1, 2, 3]\ncustomers = [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]\narc_costs = [\n 0.0 125.6 148.9 182.2 174.9 126.2 158.6 172.9 127.4 133.1 152.6 183.8 182.4 176.9 120.7 129.5;\n 123.6 0.0 175.0 146.7 191.0 130.4 142.5 139.3 130.1 133.3 163.8 127.8 139.3 128.4 186.4 115.6;\n 101.5 189.6 0.0 198.2 150.5 159.6 128.3 133.0 195.1 167.3 187.3 178.1 171.7 161.5 142.9 142.1;\n 159.4 188.4 124.7 0.0 174.5 174.0 142.6 102.5 135.5 184.4 121.6 112.1 139.9 105.5 190.9 140.7;\n 157.7 160.3 184.2 196.1 0.0 115.5 175.2 153.5 137.7 141.3 109.5 107.7 125.3 151.0 133.1 140.6;\n 145.2 120.4 106.7 138.8 157.3 0.0 153.6 192.2 153.2 184.4 133.6 164.9 163.6 126.3 121.3 161.4;\n 182.6 152.1 178.8 184.1 150.8 163.5 0.0 164.1 104.0 100.5 117.3 156.1 115.1 168.6 186.5 100.2;\n 144.9 193.8 146.1 191.4 136.8 172.7 108.1 0.0 131.0 166.3 116.4 187.0 161.3 148.2 162.1 116.0;\n 173.4 199.1 132.9 133.2 139.8 112.7 138.1 118.8 0.0 173.4 131.8 180.6 191.0 133.9 178.7 108.7;\n 150.5 171.0 163.8 171.5 116.3 149.1 124.0 192.5 188.8 0.0 112.2 188.7 197.3 144.9 110.7 186.6;\n 153.6 104.4 141.1 124.7 121.1 137.5 190.3 177.1 194.4 135.3 0.0 146.4 132.7 103.2 150.3 118.4;\n 112.5 133.7 187.1 170.0 130.2 177.7 159.2 169.9 183.8 101.6 156.2 0.0 114.7 169.3 149.9 125.3;\n 151.5 165.6 162.1 133.4 159.4 200.5 132.7 199.9 136.8 121.3 118.1 123.4 0.0 104.8 197.1 134.4;\n 195.0 101.1 194.1 160.1 147.1 164.6 137.2 138.6 166.7 191.2 169.2 186.0 171.2 0.0 106.8 150.9;\n 158.2 152.7 104.0 136.0 168.9 175.7 139.2 163.2 102.7 153.3 185.9 164.0 113.2 200.7 0.0 127.4;\n 136.6 174.3 103.2 131.4 107.8 191.6 115.1 127.6 163.2 123.2 173.3 133.0 120.5 176.9 173.8 0.0;\n]\n\nlocations = vcat(facilities, customers)\nnb_customers = length(customers)\nnb_facilities = length(facilities)\npositions = 1:nb_positions;\n\nroutes_per_facility = Dict(\n j => best_route_forall_cust_subsets(arc_costs, customers, j, nb_positions) for j in facilities\n);\nnothing #hide","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"We set maxnumnodes to zero to optimize only the root node:","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"coluna = optimizer_with_attributes(\n Coluna.Optimizer,\n \"params\" => Coluna.Params(\n solver=Coluna.Algorithm.TreeSearchAlgorithm(\n maxnumnodes=0,\n conqueralg=Coluna.ColCutGenConquer()\n )\n ),\n \"default_optimizer\" => GLPK.Optimizer\n);\nnothing #hide","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"We define a method to call both valid_inequalities_callback and r1c_callback:","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"function cuts_callback(cbdata)\n valid_inequalities_callback(cbdata)\n r1c_callback(cbdata)\nend\n\nfunction attach_data(model, cov)\n BlockDecomposition.customvars!(model, R1cVarData)\n BlockDecomposition.customconstrs!(model, [CoverConstrData, R1cCutData])\n for i in customers\n customdata!(cov[i], CoverConstrData(i))\n end\nend;\nnothing #hide","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"First, we solve the root node with the \"raw\" decomposition model:","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"model, x, y, z, cov = create_model(coluna, pricing_callback)\nattach_data(model, cov)","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"dual bound found after optimization = 1588.00","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"Then, we re-solve it with the robust cuts:","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"model, x, y, z, cov = create_model(coluna, pricing_callback)\nattach_data(model, cov)\nMOI.set(model, MOI.UserCutCallback(), valid_inequalities_callback);\nnothing #hide","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"dual bound found after optimization = 1591.55","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"And with non-robust cuts:","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"model, x, y, z, cov = create_model(coluna, pricing_callback)\nattach_data(model, cov)\nMOI.set(model, MOI.UserCutCallback(), r1c_callback);\nnothing #hide","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"dual bound found after optimization = 1598.26","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"Finally we add both robust and non-robust cuts:","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"model, x, y, z, cov = create_model(coluna, pricing_callback)\nattach_data(model, cov)\nMOI.set(model, MOI.UserCutCallback(), cuts_callback);\nnothing #hide","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"dual bound found after optimization = 1600.63","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"This page was generated using Literate.jl.","category":"page"},{"location":"start/advanced_demo/","page":"Column Generation and Benders on Location Routing","title":"Column Generation and Benders on Location Routing","text":"","category":"page"},{"location":"man/callbacks/#User-defined-Callbacks","page":"User-defined Callbacks","title":"User-defined Callbacks","text":"","category":"section"},{"location":"man/callbacks/","page":"User-defined Callbacks","title":"User-defined Callbacks","text":"Callbacks are functions defined by the user that allow him to take over part of the default conquer algorithm. The more classical callbacks in Branch-and-Cut and Branch-and-Price solvers are:","category":"page"},{"location":"man/callbacks/","page":"User-defined Callbacks","title":"User-defined Callbacks","text":"Pricing callback (only in Branch-and-Price solvers) that takes over the procedure to determine whether the current master LP solution is optimum or produces an entering variable with negative reduced cost by solving subproblems\nSeparation callback that takes over the procedure to determine whether the current master LP solution is feasible or produces a valid problem constraint that is violated\nBranching callback that takes over the procedure to determine whether the current master LP solution is integer or produces a valid branching disjunctive constraint that rules out the current fractional solution.","category":"page"},{"location":"man/callbacks/","page":"User-defined Callbacks","title":"User-defined Callbacks","text":"note: Note\nYou can't change the original formulation in a callback because Coluna does not propagate the changes into the reformulation and does not check if the solutions found are still feasible.","category":"page"},{"location":"man/callbacks/#Pricing-callbacks","page":"User-defined Callbacks","title":"Pricing callbacks","text":"","category":"section"},{"location":"man/callbacks/","page":"User-defined Callbacks","title":"User-defined Callbacks","text":"Pricing callbacks let you define how to solve the subproblems of a Dantzig-Wolfe decomposition to generate a new entering column in the master program. This callback is useful when you know an efficient algorithm to solve the subproblems, i.e. an algorithm better than solving the subproblem with a MIP solver.","category":"page"},{"location":"man/callbacks/","page":"User-defined Callbacks","title":"User-defined Callbacks","text":"See the example in the tutorial section.","category":"page"},{"location":"man/callbacks/#Errors-and-Warnings","page":"User-defined Callbacks","title":"Errors and Warnings","text":"","category":"section"},{"location":"man/callbacks/","page":"User-defined Callbacks","title":"User-defined Callbacks","text":"Algorithm.IncorrectPricingDualBound\nAlgorithm.MissingPricingDualBound\nAlgorithm.MultiplePricingDualBounds","category":"page"},{"location":"man/callbacks/#Coluna.Algorithm.IncorrectPricingDualBound","page":"User-defined Callbacks","title":"Coluna.Algorithm.IncorrectPricingDualBound","text":"IncorrectPricingDualBound\n\nError thrown when transmitting a dual bound larger than the primal bound of the best solution to the pricing subproblem found in a run of the pricing callback.\n\n\n\n\n\n","category":"type"},{"location":"man/callbacks/#Coluna.Algorithm.MissingPricingDualBound","page":"User-defined Callbacks","title":"Coluna.Algorithm.MissingPricingDualBound","text":"MissingPricingDualBound\n\nError thrown when the pricing callback does not transmit any dual bound. Make sure you call MOI.submit(model, BD.PricingDualBound(cbdata), db) in your pricing callback.\n\n\n\n\n\n","category":"type"},{"location":"man/callbacks/#Coluna.Algorithm.MultiplePricingDualBounds","page":"User-defined Callbacks","title":"Coluna.Algorithm.MultiplePricingDualBounds","text":"MultiplePricingDualBounds\n\nError thrown when the pricing transmits multiple dual bound. Make sure you call MOI.submit(model, BD.PricingDualBound(cbdata), db) only once in your pricing callback.\n\n\n\n\n\n","category":"type"},{"location":"man/callbacks/#Separation-callbacks","page":"User-defined Callbacks","title":"Separation callbacks","text":"","category":"section"},{"location":"man/callbacks/","page":"User-defined Callbacks","title":"User-defined Callbacks","text":"Separation callbacks let you define how to separate cuts or constraints.","category":"page"},{"location":"man/callbacks/#Facultative-and-essential-cuts-(user-cut-and-lazy-constraint)","page":"User-defined Callbacks","title":"Facultative & essential cuts (user cut & lazy constraint)","text":"","category":"section"},{"location":"man/callbacks/","page":"User-defined Callbacks","title":"User-defined Callbacks","text":"This callback allows you to add cuts to the master problem. The cuts must be expressed in terms of the original variables. Then, Coluna expresses them over the master variables. You can find an example of essential cut separation and facultative cut separation in the JuMP documentation.","category":"page"},{"location":"man/callbacks/","page":"User-defined Callbacks","title":"User-defined Callbacks","text":"","category":"page"},{"location":"man/blockdecomposition/#Setup-decomposition-with-BlockDecomposition","page":"Setup decomposition using BlockDecomposition","title":"Setup decomposition with BlockDecomposition","text":"","category":"section"},{"location":"man/blockdecomposition/","page":"Setup decomposition using BlockDecomposition","title":"Setup decomposition using BlockDecomposition","text":"BlockDecomposition allows the user to perform two types of decomposition using BlockDecomposition.@dantzig_wolfe_decomposition and BlockDecomposition.@benders_decomposition.","category":"page"},{"location":"man/blockdecomposition/","page":"Setup decomposition using BlockDecomposition","title":"Setup decomposition using BlockDecomposition","text":"For both decompositions, the index-set of the subproblems is declared through an BlockDecomposition.@axis. It returns an array. Each value of the array is a subproblem index wrapped into a BlockDecomposition.AxisId. Each time BlockDecomposition finds an AxisId in the indices of a variable and a constraint, it knows to which subproblem the variable or the constraint belongs.","category":"page"},{"location":"man/blockdecomposition/","page":"Setup decomposition using BlockDecomposition","title":"Setup decomposition using BlockDecomposition","text":"The macro creates a decomposition tree where the root is the master and the depth is the number of nested decompositions. A classic Dantzig-Wolfe or Benders decomposition produces a decomposition tree of depth 1. At the moment, nested decomposition is not supported.","category":"page"},{"location":"man/blockdecomposition/","page":"Setup decomposition using BlockDecomposition","title":"Setup decomposition using BlockDecomposition","text":"You can get the subproblem membership of all variables and constraints using the method BlockDecomposition.annotation.","category":"page"},{"location":"man/blockdecomposition/","page":"Setup decomposition using BlockDecomposition","title":"Setup decomposition using BlockDecomposition","text":"BlockDecomposition does not change the JuMP model. It decorates the model with additional information. All this information is stored in the ext field of the JuMP model.","category":"page"},{"location":"man/blockdecomposition/","page":"Setup decomposition using BlockDecomposition","title":"Setup decomposition using BlockDecomposition","text":"CurrentModule = BlockDecomposition","category":"page"},{"location":"man/blockdecomposition/#Errors-and-warnings","page":"Setup decomposition using BlockDecomposition","title":"Errors and warnings","text":"","category":"section"},{"location":"man/blockdecomposition/","page":"Setup decomposition using BlockDecomposition","title":"Setup decomposition using BlockDecomposition","text":"MasterVarInDwSp\nVarsOfSameDwSpInMaster","category":"page"},{"location":"man/blockdecomposition/#BlockDecomposition.MasterVarInDwSp","page":"Setup decomposition using BlockDecomposition","title":"BlockDecomposition.MasterVarInDwSp","text":"Error thrown when a master variable is in a constraint that belongs to a Dantzig-Wolfe subproblem.\n\nYou can retrieve the JuMP variable and the JuMP constraint where the error occurs:\n\nerror.variable\nerror.constraint\n\n\n\n\n\n","category":"type"},{"location":"man/blockdecomposition/#BlockDecomposition.VarsOfSameDwSpInMaster","page":"Setup decomposition using BlockDecomposition","title":"BlockDecomposition.VarsOfSameDwSpInMaster","text":"Warning when a master constraint involves variables that belong to the same Dantzig-Wolfe subproblem. It means you can move the constraint in a subproblem.\n\n\n\n\n\n","category":"type"},{"location":"man/blockdecomposition/#References","page":"Setup decomposition using BlockDecomposition","title":"References","text":"","category":"section"},{"location":"man/blockdecomposition/","page":"Setup decomposition using BlockDecomposition","title":"Setup decomposition using BlockDecomposition","text":"BlockModel","category":"page"},{"location":"man/blockdecomposition/#BlockDecomposition.BlockModel","page":"Setup decomposition using BlockDecomposition","title":"BlockDecomposition.BlockModel","text":"BlockModel(optimizer [, direct_model = false])\n\nReturn a JuMP model which BlockDecomposition will decompose using instructions given by the user.\n\nIf you define direct_model = true, the method creates the model with JuMP.direct_model, otherwise it uses JuMP.Model.\n\n\n\n\n\n","category":"function"},{"location":"man/blockdecomposition/","page":"Setup decomposition using BlockDecomposition","title":"Setup decomposition using BlockDecomposition","text":"These are the methods to decompose a JuMP model :","category":"page"},{"location":"man/blockdecomposition/","page":"Setup decomposition using BlockDecomposition","title":"Setup decomposition using BlockDecomposition","text":"@axis\n@benders_decomposition\n@dantzig_wolfe_decomposition","category":"page"},{"location":"man/blockdecomposition/#BlockDecomposition.@axis","page":"Setup decomposition using BlockDecomposition","title":"BlockDecomposition.@axis","text":"@axis(name, collection)\n\nDeclare collection as an index-set of subproblems. You can access the axis using the variable name.\n\nExamples\n\nConsider a formulation that has a decomposition which gives raise to 5 subproblems. Let {1,2,3,4,5} be the index-set of the subproblems.\n\nTo perform this decomposition with BlockDecomposition, we must declare an axis that contains the index-set of the subproblems :\n\njulia> L = 1:5\n1:5\n\njulia> @axis(K, L)\nBlockDecomposition.Axis{:K, Int64}(:K, BlockDecomposition.AxisId{:K, Int64}[1, 2, 3, 4, 5])\n\njulia> K[1]\n1\n\njulia> typeof(K[1])\nBlockDecomposition.AxisId{:K, Int64}\n\nThe elements of the axis are AxisId. You must use AxisId in the indices of the variables and the constraints that you declare otherwise BlockDecomposition assign them to the master problem.\n\n@variable(model, x[l in L]) # x[l] belongs to the master for any l ∈ L\n@variable(model, y[k in K]) # y[k], k ∈ K, belongs to subproblem k (because K is an axis)\n\n\n\n\n\n","category":"macro"},{"location":"man/blockdecomposition/#BlockDecomposition.@benders_decomposition","page":"Setup decomposition using BlockDecomposition","title":"BlockDecomposition.@benders_decomposition","text":"@benders_decomposition(model, name, axis)\n\nRegister a Benders decomposition on the JuMP model model where the index-set of the subproblems is defined by the axis axis.\n\nCreate a variable name from which the user can access decomposition tree.\n\n\n\n\n\n","category":"macro"},{"location":"man/blockdecomposition/#BlockDecomposition.@dantzig_wolfe_decomposition","page":"Setup decomposition using BlockDecomposition","title":"BlockDecomposition.@dantzig_wolfe_decomposition","text":"@dantzig_wolfe_decomposition(model, name, axis)\n\nRegister a Dantzig-Wolfe decomposition on the JuMP model model where the index-set of the subproblems is defined by the axis axis.\n\nCreate a variable name from which the user can access the decomposition tree.\n\n\n\n\n\n","category":"macro"},{"location":"man/blockdecomposition/","page":"Setup decomposition using BlockDecomposition","title":"Setup decomposition using BlockDecomposition","text":"These are the methods to set additional information to the decomposition (multiplicity and optimizers) :","category":"page"},{"location":"man/blockdecomposition/","page":"Setup decomposition using BlockDecomposition","title":"Setup decomposition using BlockDecomposition","text":"getmaster\ngetsubproblems\nspecify!","category":"page"},{"location":"man/blockdecomposition/#BlockDecomposition.getmaster","page":"Setup decomposition using BlockDecomposition","title":"BlockDecomposition.getmaster","text":"getmaster(node) -> MasterForm\n\nReturn an object that wraps the annotation that describes the master formulation of a decomposition stored at the node of the decomposition tree.\n\nThis method is not defined if the node is a leaf of the decomposition tree.\n\n\n\n\n\n","category":"function"},{"location":"man/blockdecomposition/#BlockDecomposition.getsubproblems","page":"Setup decomposition using BlockDecomposition","title":"BlockDecomposition.getsubproblems","text":"getsubproblems(node) -> Vector{SubproblemForm}\n\nReturn a vector of objects that wrap the annotations that describe subproblem formulations of a decomposition stored at node of the decomposition tree.\n\nThis method is not defined if the node is a leaf of the decomposition tree.\n\n\n\n\n\n","category":"function"},{"location":"man/blockdecomposition/#BlockDecomposition.specify!","page":"Setup decomposition using BlockDecomposition","title":"BlockDecomposition.specify!","text":"specify!(\n subproblem, \n lower_multiplicity = 1,\n upper_multiplicity = 1,\n solver = nothing\n)\n\nMethod that allows the user to specify additional property of the subproblems.\n\nThe multiplicity of subproblem is the number of times that the same independant block shaped by the subproblem in the coefficient matrix appears in the model. It is equivalent to the number of solutions to the subproblem that can appear in the solution of the original problem.\n\nThe solver of the subproblem is the way the subproblem will be optimized. It can be either a function (pricing callback), an optimizer of MathOptInterface (e.g. Gurobi.Optimizer, CPLEX.Optimizer, Glpk.Optimizer... with attributes), or nothing. In the latter case, the solver will use a default optimizer that should be defined in the parameters of the main solver.\n\nAdvanced usage : The user can use several solvers to optimize a subproblem : \n\nspecify!(subproblem, solver = [Gurobi.Optimizer, my_callback, my_second_callback])\n\nColuna always uses the first solver by default. Be cautious because changes are always buffered to all solvers. So you may degrade performances if you use a lot of solvers.\n\n\n\n\n\n","category":"function"},{"location":"man/blockdecomposition/","page":"Setup decomposition using BlockDecomposition","title":"Setup decomposition using BlockDecomposition","text":"This method helps you to check your decomposition :","category":"page"},{"location":"man/blockdecomposition/","page":"Setup decomposition using BlockDecomposition","title":"Setup decomposition using BlockDecomposition","text":"annotation","category":"page"},{"location":"man/blockdecomposition/#BlockDecomposition.annotation","page":"Setup decomposition using BlockDecomposition","title":"BlockDecomposition.annotation","text":"annotation(node)\n\nReturn the annotation that describes the master/subproblem of a given node of the decomposition tree.\n\nannotation(model, variable)\nannotation(model, constraint)\n\nReturn the subproblem to which a variable or a constraint belongs.\n\n\n\n\n\n","category":"function"},{"location":"man/blockdecomposition/","page":"Setup decomposition using BlockDecomposition","title":"Setup decomposition using BlockDecomposition","text":"CurrentModule = nothing","category":"page"},{"location":"man/blockdecomposition/","page":"Setup decomposition using BlockDecomposition","title":"Setup decomposition using BlockDecomposition","text":"","category":"page"},{"location":"man/algorithm/","page":"Built-in algorithms","title":"Built-in algorithms","text":"CurrentModule = Coluna","category":"page"},{"location":"man/algorithm/#Built-in-Algorithms","page":"Built-in algorithms","title":"Built-in Algorithms","text":"","category":"section"},{"location":"man/algorithm/#Branch-and-Bound","page":"Built-in algorithms","title":"Branch-and-Bound","text":"","category":"section"},{"location":"man/algorithm/","page":"Built-in algorithms","title":"Built-in algorithms","text":"Branch-and-Bound algorithm aims to find an optimal solution of a MIP by successive divisions of the search space. An introduction to the Branch-and-Bound algorithm can be found here. ","category":"page"},{"location":"man/algorithm/","page":"Built-in algorithms","title":"Built-in algorithms","text":"Coluna provides a generic Branch-and-Bound algorithm whose three main elements can be easily modified:","category":"page"},{"location":"man/algorithm/","page":"Built-in algorithms","title":"Built-in algorithms","text":"Algorithm.TreeSearchAlgorithm","category":"page"},{"location":"man/algorithm/#Coluna.Algorithm.TreeSearchAlgorithm","page":"Built-in algorithms","title":"Coluna.Algorithm.TreeSearchAlgorithm","text":"Coluna.Algorithm.TreeSearchAlgorithm(\n conqueralg::AbstractConquerAlgorithm = ColCutGenConquer(),\n dividealg::AbstractDivideAlgorithm = Branching(),\n explorestrategy::AbstractExploreStrategy = DepthFirstStrategy(),\n maxnumnodes = 100000,\n opennodeslimit = 100,\n timelimit = -1, # -1 means no time limit\n opt_atol::Float64 = DEF_OPTIMALITY_ATOL,\n opt_rtol::Float64 = DEF_OPTIMALITY_RTOL,\n branchingtreefile = \"\",\n jsonfile = \"\",\n print_node_info = true\n)\n\nThis algorithm is a branch and bound that uses a search tree to optimize the reformulation. At each node in the tree, it applies conqueralg to evaluate the node and improve the bounds, dividealg to generate branching constraints, and explorestrategy to select the next node to treat.\n\nThe three main elements of the algorithm are:\n\nthe conquer strategy (conqueralg): evaluation of the problem at a node of the Branch-and-Bound tree. Depending on the type of decomposition used ahead of the Branch-and-Bound, you can use either Column Generation (if your problem is decomposed following Dantzig-Wolfe transformation) and/or Cut Generation (for Dantzig-Wolfe and Benders decompositions). \nthe branching strategy (dividealg): how to create new branches i.e. how to divide the search space\nthe explore strategy (explorestrategy): the evaluation order of your nodes \n\nParameters: \n\nmaxnumnodes : maximum number of nodes explored by the algorithm\nopennodeslimit : maximum number of nodes waiting to be explored\ntimelimit : time limit in seconds of the algorithm\nopt_atol : optimality absolute tolerance (alpha)\nopt_rtol : optimality relative tolerance (alpha)\n\nOptions:\n\nbranchingtreefile : name of the file in which the algorithm writes an overview of the branching tree\njsonfile : name of the file in which the algorithm writes the solution in JSON format\nprint_node_info : log the tree into the console\n\nWarning: if you set a name for the branchingtreefile AND the jsonfile, the algorithm will only write in the json file.\n\n\n\n\n\n","category":"type"},{"location":"man/algorithm/","page":"Built-in algorithms","title":"Built-in algorithms","text":"Conquer, divide algorithms and the explore strategy available with the TreeSearchAlgorithm are listed in the following mind map. ","category":"page"},{"location":"man/algorithm/","page":"Built-in algorithms","title":"Built-in algorithms","text":"mindmap\n TreeSearchAlgorithm\n (conquer)\n BendersConquer\n ColCutGenConquer\n RestrMasterLpConquer\n (divide)\n NoBranching\n ClassicBranching\n StrongBranching\n (explore)\n DepthFirstStrategy\n BestDualBoundStrategy","category":"page"},{"location":"man/algorithm/#Conquer-algorithms","page":"Built-in algorithms","title":"Conquer algorithms","text":"","category":"section"},{"location":"man/algorithm/","page":"Built-in algorithms","title":"Built-in algorithms","text":"Algorithm.BendersConquer\nAlgorithm.ColCutGenConquer\nAlgorithm.RestrMasterLpConquer","category":"page"},{"location":"man/algorithm/#Coluna.Algorithm.ColCutGenConquer","page":"Built-in algorithms","title":"Coluna.Algorithm.ColCutGenConquer","text":"Coluna.Algorithm.ColCutGenConquer(\n colgen = ColumnGeneration(),\n cutgen = CutCallbacks(),\n primal_heuristics = ParameterizedHeuristic[ParamRestrictedMasterHeuristic()],\n max_nb_cut_rounds = 3\n)\n\nColumn-and-cut-generation based algorithm to find primal and dual bounds for a problem decomposed using Dantzig-Wolfe paradigm.\n\nParameters :\n\ncolgen: column generation algorithm\ncutgen: cut generation algorithm\nprimal_heuristics: heuristics to find a feasible solution\nmax_nb_cut_rounds : number of cut generation done by the algorithm\n\n\n\n\n\n","category":"type"},{"location":"man/algorithm/#Divide-algorithms","page":"Built-in algorithms","title":"Divide algorithms","text":"","category":"section"},{"location":"man/algorithm/","page":"Built-in algorithms","title":"Built-in algorithms","text":"Algorithm.NoBranching\nAlgorithm.ClassicBranching","category":"page"},{"location":"man/algorithm/#Coluna.Algorithm.NoBranching","page":"Built-in algorithms","title":"Coluna.Algorithm.NoBranching","text":"Divide algorithm that does nothing. It does not generate any child.\n\n\n\n\n\n","category":"type"},{"location":"man/algorithm/#Coluna.Algorithm.ClassicBranching","page":"Built-in algorithms","title":"Coluna.Algorithm.ClassicBranching","text":"ClassicBranching(\n selection_criterion = MostFractionalCriterion()\n rules = [Branching.PrioritisedBranchingRule(SingleVarBranchingRule(), 1.0, 1.0)]\n int_tol = 1e-6\n)\n\nChooses the best candidate according to a selection criterion and generates the two children.\n\nParameters\n\nselection_criterion: selection criterion to choose the best candidate\nrules: branching rules to generate the candidates\nint_tol: tolerance to determine if a variable is integer\n\nIt is implemented as a specific case of the strong branching algorithm.\n\n\n\n\n\n","category":"type"},{"location":"man/algorithm/","page":"Built-in algorithms","title":"Built-in algorithms","text":"Strong branching is the main algorithm that we provide and it is the default implementation of the Branching submodule. You can have more information about the algorithm by reading the Branching submodule documentation.","category":"page"},{"location":"man/algorithm/","page":"Built-in algorithms","title":"Built-in algorithms","text":"Algorithm.StrongBranching","category":"page"},{"location":"man/algorithm/#Coluna.Algorithm.StrongBranching","page":"Built-in algorithms","title":"Coluna.Algorithm.StrongBranching","text":"StrongBranching(\n phases = [],\n rules = [Branching.PrioritisedBranchingRule(SingleVarBranchingRule(), 1.0, 1.0)],\n selection_criterion = MostFractionalCriterion(),\n verbose = true,\n int_tol = 1e-6\n)\n\nThe algorithm that performs a (multi-phase) (strong) branching in a tree search algorithm.\n\nStrong branching is a procedure that heuristically selects a branching constraint that potentially gives the best progress of the dual bound. The procedure selects a collection of branching candidates based on their branching rule and their score. Then, the procedure evaluates the progress of the dual bound in both branches of each branching candidate by solving both potential children using a conquer algorithm. The candidate that has the largest product of dual bound improvements in the branches is chosen to be the branching constraint.\n\nWhen the dual bound improvement produced by the branching constraint is difficult to compute (e.g. time-consuming in the context of column generation), one can let the branching algorithm quickly estimate the dual bound improvement of each candidate and retain the most promising branching candidates. This is called a phase. The goal is to first evaluate a large number of candidates with a very fast conquer algorithm and retain a certain number of promising ones. Then, over the phases, it evaluates the improvement with a more precise conquer algorithm and restrict the number of retained candidates until only one is left.\n\nParameters:\n\nphases: a vector of Coluna.Algorithm.BranchingPhase\nrules: a vector of Coluna.Algorithm.Branching.PrioritisedBranchingRule\nselection_criterion: a selection criterion to choose the initial candidates\nverbose: if true, print the progress of the strong branching procedure\nint_tol: tolerance to determine if a variable is integer\n\n\n\n\n\n","category":"type"},{"location":"man/algorithm/","page":"Built-in algorithms","title":"Built-in algorithms","text":"All the possible algorithms that can be used within the strong branching are listed in the following mind map.","category":"page"},{"location":"man/algorithm/","page":"Built-in algorithms","title":"Built-in algorithms","text":"mindmap\n StrongBranching\n (phases)\n (conquer)\n BendersConquer\n ColCutGenConquer\n RestrMasterLpConquer\n (score)\n ProductScore\n TreeDepthScore\n (rules)\n SingleVarBranchingRule\n (selection_criterion)\n FirstFoundCriterion\n MostFractionalCriterion","category":"page"},{"location":"man/algorithm/#Explore-strategies","page":"Built-in algorithms","title":"Explore strategies","text":"","category":"section"},{"location":"man/algorithm/","page":"Built-in algorithms","title":"Built-in algorithms","text":"TreeSearch.DepthFirstStrategy\nTreeSearch.BestDualBoundStrategy","category":"page"},{"location":"man/algorithm/#Coluna.TreeSearch.DepthFirstStrategy","page":"Built-in algorithms","title":"Coluna.TreeSearch.DepthFirstStrategy","text":"Explore the tree search space with a depth-first strategy. The next visited node is the last one pushed in the stack of unexplored nodes.\n\n\n\n\n\n","category":"type"},{"location":"man/algorithm/#Coluna.TreeSearch.BestDualBoundStrategy","page":"Built-in algorithms","title":"Coluna.TreeSearch.BestDualBoundStrategy","text":"Explore the tree search space with a best-first strategy. The next visited node is the one with the highest local dual bound.\n\n\n\n\n\n","category":"type"},{"location":"man/algorithm/#Cut-generation-algorithms","page":"Built-in algorithms","title":"Cut generation algorithms","text":"","category":"section"},{"location":"man/algorithm/","page":"Built-in algorithms","title":"Built-in algorithms","text":"Algorithm.BendersCutGeneration","category":"page"},{"location":"man/algorithm/#Coluna.Algorithm.BendersCutGeneration","page":"Built-in algorithms","title":"Coluna.Algorithm.BendersCutGeneration","text":"Coluna.Algorithm.BendersCutGeneration(\n restr_master_solve_alg = SolveLpForm(get_dual_sol = true, relax_integrality = true),\n restr_master_optimizer_id = 1,\n separation_solve_alg = SolveLpForm(get_dual_sol = true, relax_integrality = true)\n max_nb_iterations::Int = 100,\n)\n\nBenders cut generation algorithm that can be applied to a formulation reformulated using Benders decomposition.\n\nThis algorithm is an implementation of the generic algorithm provided by the Benders submodule.\n\nParameters:\n\nrestr_master_solve_alg: algorithm to solve the restricted master problem\nrestr_master_optimizer_id: optimizer id to use to solve the restricted master problem\nseparation_solve_alg: algorithm to solve the separation problem (must be a LP solver that returns a dual solution)\n\nOption:\n\nmax_nb_iterations: maximum number of iterations\n\nAbout the output\n\nAt each iteration, the Benders cut generation algorithm show following statistics:\n\n \n\nwhere:\n\nit stands for the current number of iterations of the algorithm\net is the elapsed time in seconds since Coluna has started the optimisation\nmst is the time in seconds spent solving the master problem at the current iteration\nsp is the time in seconds spent solving the separation problem at the current iteration\ncuts is the number of cuts generated at the current iteration\nmaster is the objective value of the master problem at the current iteration\n\nDebug options (print at each iteration):\n\ndebug_print_master: print the master problem\ndebug_print_master_primal_solution: print the master problem with the primal solution\ndebug_print_master_dual_solution: print the master problem with the dual solution (make sure the restr_master_solve_alg returns a dual solution)\ndebug_print_subproblem: print the subproblem\ndebug_print_subproblem_primal_solution: print the subproblem with the primal solution\ndebug_print_subproblem_dual_solution: print the subproblem with the dual solution\ndebug_print_generated_cuts: print the generated cuts\n\n\n\n\n\n","category":"type"},{"location":"man/algorithm/","page":"Built-in algorithms","title":"Built-in algorithms","text":"Algorithm.CutCallbacks","category":"page"},{"location":"man/algorithm/#Coluna.Algorithm.CutCallbacks","page":"Built-in algorithms","title":"Coluna.Algorithm.CutCallbacks","text":"CutCallbacks(\n call_robust_facultative = true,\n call_robust_essential = true,\n tol::Float64 = 1e-6\n)\n\nRuns the cut user callbacks attached to a formulation.\n\nParameters:\n\ncall_robust_facultative: if true, call all the robust facultative cut user callbacks (i.e. user cut callbacks)\ncall_robust_essential: if true, call all the robust essential cut user callbacks (i.e. lazy constraint callbacks)\ntol: tolerance used to determine if a cut is violated\n\nSee the JuMP documentation for more information about user callbacks and the tutorials in the Coluna documentation for examples of user callbacks.\n\n\n\n\n\n","category":"type"},{"location":"man/algorithm/#Column-generation-algorithms","page":"Built-in algorithms","title":"Column generation algorithms","text":"","category":"section"},{"location":"man/algorithm/","page":"Built-in algorithms","title":"Built-in algorithms","text":"Algorithm.ColumnGeneration","category":"page"},{"location":"man/algorithm/#Coluna.Algorithm.ColumnGeneration","page":"Built-in algorithms","title":"Coluna.Algorithm.ColumnGeneration","text":"Coluna.Algorithm.ColumnGeneration(\n restr_master_solve_alg = SolveLpForm(get_dual_sol = true),\n pricing_prob_solve_alg = SolveIpForm(\n moi_params = MoiOptimize(\n deactivate_artificial_vars = false,\n enforce_integrality = false\n )\n ),\n essential_cut_gen_alg = CutCallbacks(call_robust_facultative = false),\n max_nb_iterations = 1000,\n log_print_frequency = 1,\n redcost_tol = 1e-4,\n show_column_already_inserted_warning = true,\n cleanup_threshold = 10000,\n cleanup_ratio = 0.66,\n smoothing_stabilization = 0.0 # should be in [0, 1],\n)\n\nColumn generation algorithm that can be applied to formulation reformulated using Dantzig-Wolfe decomposition. \n\nThis algorithm first solves the linear relaxation of the master (master LP) using restr_master_solve_alg. Then, it solves the subproblems by calling pricing_prob_solve_alg to get the columns that have the best reduced costs and that hence, may improve the master LP's objective the most.\n\nIn order for the algorithm to converge towards the optimal solution of the master LP, it suffices that the pricing oracle returns, at each iteration, a negative reduced cost solution if one exists. The algorithm stops when all subproblems fail to generate a column with negative (positive) reduced cost in the case of a minimization (maximization) problem or when it reaches the maximum number of iterations.\n\nParameters: \n\nrestr_master_solve_alg: algorithm to optimize the master LP\npricing_prob_solve_alg: algorithm to optimize the subproblems\nessential_cut_gen_alg: algorithm to generate essential cuts which is run when the solution of the master LP is integer.\n\nOptions:\n\nmax_nb_iterations: maximum number of iterations\nlog_print_frequency: display frequency of iterations statistics\n\nUndocumented parameters are in alpha version.\n\nAbout the ouput\n\nAt each iteration (depending on log_print_frequency), the column generation algorithm can display following statistics.\n\n \n\nHere are their meanings :\n\nit stands for the current number of iterations of the algorithm\net is the elapsed time in seconds since Coluna has started the optimisation\nmst is the time in seconds spent solving the master LP at the current iteration\nsp is the time in seconds spent solving the subproblems at the current iteration\ncols is the number of column generated by the subproblems at the current iteration\nal is the smoothing factor of the stabilisation at the current iteration (alpha version)\nDB is the dual bound of the master LP at the current iteration\nmlp is the objective value of the master LP at the current iteration\nPB is the objective value of the best primal solution found by Coluna at the current iteration\n\n\n\n\n\n","category":"type"},{"location":"man/algorithm/#External-call-to-optimize-a-linear-program","page":"Built-in algorithms","title":"External call to optimize a linear program","text":"","category":"section"},{"location":"man/algorithm/","page":"Built-in algorithms","title":"Built-in algorithms","text":"Algorithm.SolveLpForm","category":"page"},{"location":"man/algorithm/#Coluna.Algorithm.SolveLpForm","page":"Built-in algorithms","title":"Coluna.Algorithm.SolveLpForm","text":"Coluna.Algorithm.SolveLpForm(\n get_ip_primal_sol = false,\n get_dual_sol = false,\n relax_integrality = false,\n get_dual_bound = false,\n silent = true\n)\n\nSolve a linear program stored in a formulation using its first optimizer. This algorithm works only if the optimizer is interfaced with MathOptInterface.\n\nYou can define the optimizer using the default_optimizer attribute of Coluna or with the method specify! from BlockDecomposition\n\nParameters:\n\nget_ip_primal_sol: update the primal solution of the formulation if equals true\nget_dual_sol: retrieve the dual solution and store it in the ouput if equals true\nrelax_integrality: relax integer variables of the formulation before optimization if equals true\nget_dual_bound: store the dual objective value in the output if equals true\nsilent: set MOI.Silent() to its value\n\nUndocumented parameters are alpha.\n\n\n\n\n\n","category":"type"},{"location":"man/algorithm/#External-call-to-optimize-a-mixed-integer-program-/-combinatorial-problem","page":"Built-in algorithms","title":"External call to optimize a mixed-integer program / combinatorial problem","text":"","category":"section"},{"location":"man/algorithm/","page":"Built-in algorithms","title":"Built-in algorithms","text":"Algorithm.SolveIpForm\nAlgorithm.MoiOptimize\nAlgorithm.UserOptimize\nAlgorithm.CustomOptimize","category":"page"},{"location":"man/algorithm/#Coluna.Algorithm.SolveIpForm","page":"Built-in algorithms","title":"Coluna.Algorithm.SolveIpForm","text":"Coluna.Algorithm.SolveIpForm(\n optimizer_id = 1\n moi_params = MoiOptimize()\n user_params = UserOptimize()\n custom_params = CustomOptimize()\n)\n\nSolve an optimization problem. This algorithm can call different type of optimizers :\n\nsubsolver interfaced with MathOptInterface to optimize a mixed integer program\npricing callback defined by the user\ncustom optimizer to solve a custom model\n\nYou can specify an optimizer using the default_optimizer attribute of Coluna or with the method specify! from BlockDecomposition. If you want to define several optimizers for a given subproblem, you must use specify!:\n\nspecify!(subproblem, optimizers = [optimizer1, optimizer2, optimizer3])\n\nValue of optimizer_id is the position of the optimizer you want to use. For example, if optimizer_id is equal to 2, the algorithm will use optimizer2.\n\nBy default, the algorihm uses the first optimizer or the default optimizer if no optimizer has been specified through specify!.\n\nDepending on the type of the optimizer chosen, the algorithm will use one the three configurations : \n\nmoi_params for subsolver interfaced with MathOptInterface\nuser_params for pricing callbacks\ncustom_params for custom solvers\n\nCustom solver is undocumented because alpha.\n\n\n\n\n\n","category":"type"},{"location":"man/algorithm/#Coluna.Algorithm.MoiOptimize","page":"Built-in algorithms","title":"Coluna.Algorithm.MoiOptimize","text":"MoiOptimize(\n time_limit = 600\n deactivate_artificial_vars = false\n enforce_integrality = false\n get_dual_bound = true\n)\n\nConfiguration for an optimizer that calls a subsolver through MathOptInterface.\n\nParameters:\n\ntime_limit: in seconds\ndeactivate_artificial_vars: deactivate all artificial variables of the formulation if equals true\nenforce_integrality: enforce integer variables that are relaxed if equals true\nget_dual_bound: store the dual objective value in the output if equals true\n\n\n\n\n\n","category":"type"},{"location":"man/algorithm/#Coluna.Algorithm.UserOptimize","page":"Built-in algorithms","title":"Coluna.Algorithm.UserOptimize","text":"UserOptimize(\n max_nb_ip_primal_sols = 50\n)\n\nConfiguration for an optimizer that calls a pricing callback to solve the problem.\n\nParameters:\n\nmax_nb_ip_primal_sols: maximum number of solutions returned by the callback kept\n\n\n\n\n\n","category":"type"},{"location":"man/algorithm/#Coluna.Algorithm.CustomOptimize","page":"Built-in algorithms","title":"Coluna.Algorithm.CustomOptimize","text":"CustomOptimize()\n\nConfiguration for an optimizer that calls a custom solver to solve a custom model.\n\n\n\n\n\n","category":"type"},{"location":"man/algorithm/","page":"Built-in algorithms","title":"Built-in algorithms","text":"","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"CurrentModule = Coluna","category":"page"},{"location":"api/colgen/#Column-generation","page":"ColGen","title":"Column generation","text":"","category":"section"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Coluna provides an interface and generic functions to implement a multi-stage column generation algorithm together with a default implementation of this algorithm.","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"In this section, we are first going to present the generic functions, the implementation with some theory backgrounds and then give the references of the interface.","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"You can find the generic functions and the interface in the ColGen submodule and the default implementation in the Algorithm submodule at src/Algorithm/colgen.","category":"page"},{"location":"api/colgen/#Context","page":"ColGen","title":"Context","text":"","category":"section"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"The ColGen submodule provides an interface and generic functions to implement a column generation algorithm. The implementation depends on an object called context.","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Coluna.ColGen.AbstractColGenContext","category":"page"},{"location":"api/colgen/#Coluna.ColGen.AbstractColGenContext","page":"ColGen","title":"Coluna.ColGen.AbstractColGenContext","text":"Supertype for the objects to which belongs the implementation of the column generation and that stores any kind of information during the execution of the column generation algorithm.\n\nIMPORTANT: implementation of the column generation mainly depends on the context type.\n\n\n\n\n\n","category":"type"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Coluna provides two types of context:","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Coluna.Algorithm.ColGenContext\nColuna.Algorithm.ColGenPrinterContext","category":"page"},{"location":"api/colgen/#Coluna.Algorithm.ColGenContext","page":"ColGen","title":"Coluna.Algorithm.ColGenContext","text":"ColGenContext(reformulation, algo_params) -> ColGenContext\n\nCreates a context to run the default implementation of the column generation algorithm.\n\n\n\n\n\n","category":"type"},{"location":"api/colgen/#Coluna.Algorithm.ColGenPrinterContext","page":"ColGen","title":"Coluna.Algorithm.ColGenPrinterContext","text":"ColGenPrinterContext(reformulation, algo_params) -> ColGenPrinterContext\n\nCreates a context to run the default implementation of the column generation algorithm together with a printer that prints information about the algorithm execution.\n\n\n\n\n\n","category":"type"},{"location":"api/colgen/#Generic-functions","page":"ColGen","title":"Generic functions","text":"","category":"section"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Generic functions are the core of the column generation algorithm. There are three generic functions:","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Coluna.ColGen.run!","category":"page"},{"location":"api/colgen/#Coluna.ColGen.run!","page":"ColGen","title":"Coluna.ColGen.run!","text":"run!(ctx, env, ip_primal_sol; iter = 1) -> AbstractColGenOutput\n\nRuns the column generation algorithm.\n\nArguments are:\n\nctx: column generation context\nenv: Coluna environment\nip_primal_sol: current best primal solution to the master problem\niter: iteration number (default: 1)\n\nThis function is responsible for initializing the column generation context, the reformulation, and the stabilization. We iterate on the loop each time the phase or the stage changes.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"See the main loop section for more details.","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Coluna.ColGen.run_colgen_phase!","category":"page"},{"location":"api/colgen/#Coluna.ColGen.run_colgen_phase!","page":"ColGen","title":"Coluna.ColGen.run_colgen_phase!","text":"run_colgen_phase!(ctx, phase, stage, env, ip_primal_sol, stab; iter = 1) -> AbstractColGenPhaseOutput\n\nRuns a phase of the column generation algorithm.\n\nArguments are:\n\nctx: column generation context\nphase: current column generation phase\nstage: current column generation stage\nenv: Coluna environment\nip_primal_sol: current best primal solution to the master problem\nstab: stabilization\niter: iteration number (default: 1)\n\nThis function is responsible for running the column generation iterations until the phase is finished.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"See the phase loop section for more details.","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Coluna.ColGen.run_colgen_iteration!","category":"page"},{"location":"api/colgen/#Coluna.ColGen.run_colgen_iteration!","page":"ColGen","title":"Coluna.ColGen.run_colgen_iteration!","text":"run_colgen_iteration!(context, phase, stage, env, ip_primal_sol, stab) -> AbstractColGenIterationOutput\n\nRuns an iteration of column generation.\n\nArguments are:\n\ncontext: column generation context\nphase: current column generation phase\nstage: current column generation stage\nenv: Coluna environment\nip_primal_sol: current best primal solution to the master problem\nstab: stabilization\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"See the column generation iteration section for more details.","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"They are independent of any other submodule of Coluna. You can use them to implement your own column generation algorithm.","category":"page"},{"location":"api/colgen/#Reformulation","page":"ColGen","title":"Reformulation","text":"","category":"section"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"The default implementation works with a reformulated problem contained in MathProg.Reformulation where master and subproblems are MathProg.Formulation objects.","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"The master has the following form:","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"beginaligned\nmin quad sum_k in K c^k lambda^k+barc y \ntextst quad sum_k in K A^k lambda^k+barA y geq a (1)\n l_k leq mathbf1 lambda^k leq u_k (2) \n barl leq y leq baru (3)\nendaligned","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"where lambda are the master columns, y are the pure master variables, constraints (1) are the linking constraints, constraints (2) are the convexity constraints that depend on l_k and u_k (e.g. the lower and upper multiplicity of the subproblem k respectively), and constraints (3) are the bounds on the pure master variables.","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"The subproblems have the following form:","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"beginaligned\nmin quad cx + 0z \ntextst quad Bx geq b \n 1 leq z leq 1\nendaligned","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"where x are the subproblem variables, z is a setup variable that always takes the value one in a solution to the subproblem.","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"The coefficients of the columns in constraints (1) and (2) of the master are computed using representative variables of the subproblems. You can read this section (TODO Natacha) to understand how we map the subproblem solutions into master columns.","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"References:","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Coluna.ColGen.get_reform\nColuna.ColGen.get_master\nColuna.ColGen.get_pricing_subprobs\nColuna.ColGen.is_minimization","category":"page"},{"location":"api/colgen/#Coluna.ColGen.get_reform","page":"ColGen","title":"Coluna.ColGen.get_reform","text":"Returns Dantzig-Wolfe reformulation.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/#Coluna.ColGen.get_master","page":"ColGen","title":"Coluna.ColGen.get_master","text":"Returns master formulation.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/#Coluna.ColGen.get_pricing_subprobs","page":"ColGen","title":"Coluna.ColGen.get_pricing_subprobs","text":"get_pricing_subprobs(ctx) -> Vector{Tuple{SuproblemId, SpFormulation}}\n\nReturns subproblem formulations.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/#Coluna.ColGen.is_minimization","page":"ColGen","title":"Coluna.ColGen.is_minimization","text":"Returns true if the objective sense is minimization; false otherwise.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/#Main-loop","page":"ColGen","title":"Main loop","text":"","category":"section"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"This is a description of how the Coluna.ColGen.run! generic function behaves in the default implementation.","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"The main loop stops when the Coluna.ColGen.stop_colgen method returns true. This is the case when one of the following conditions holds: ","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"the master or a pricing subproblem is infeasible\nthe time limit is reached\nthe maximum number of iterations is reached","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Otherwise, the main loop runs until there is no more phase or stage to execute.","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"The method returns:","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Coluna.Algorithm.ColGenOutput","category":"page"},{"location":"api/colgen/#Coluna.Algorithm.ColGenOutput","page":"ColGen","title":"Coluna.Algorithm.ColGenOutput","text":"Output of the default implementation of the column generation algorithm.\n\n\n\n\n\n","category":"type"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"References:","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Coluna.ColGen.stop_colgen\nColuna.ColGen.setup_reformulation!\nColuna.ColGen.setup_context!\nColuna.ColGen.AbstractColGenOutput\nColuna.ColGen.colgen_output_type\nColuna.ColGen.new_output","category":"page"},{"location":"api/colgen/#Coluna.ColGen.stop_colgen","page":"ColGen","title":"Coluna.ColGen.stop_colgen","text":"Returns true when the column generation algorithm must stop; false otherwise.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/#Coluna.ColGen.setup_reformulation!","page":"ColGen","title":"Coluna.ColGen.setup_reformulation!","text":"Setup the reformulation for the given phase.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/#Coluna.ColGen.setup_context!","page":"ColGen","title":"Coluna.ColGen.setup_context!","text":"Setup the context for the given phase.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/#Coluna.ColGen.AbstractColGenOutput","page":"ColGen","title":"Coluna.ColGen.AbstractColGenOutput","text":"Supertype for the objects that contains the output of the column generation algorithm.\n\n\n\n\n\n","category":"type"},{"location":"api/colgen/#Coluna.ColGen.colgen_output_type","page":"ColGen","title":"Coluna.ColGen.colgen_output_type","text":"colgen_output_type(ctx) -> Type{<:AbstractColGenOutput}\n\nReturns the type of the column generation output associated to the context.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/#Coluna.ColGen.new_output","page":"ColGen","title":"Coluna.ColGen.new_output","text":"new_output(OutputType, colgen_phase_output) -> OutputType\n\nReturns the column generation output where colgen_phase_output is the output of the last column generation phase executed.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/#Phase-loop","page":"ColGen","title":"Phase loop","text":"","category":"section"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"This is a description of how the Coluna.ColGen.run_colgen_phase! generic function behaves in the default implementation.","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"This function is responsible for maintaining the incumbent dual bound and the incumbent master IP primal solution.","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"The phase loop stops when the Coluna.ColGen.stop_colgen_phase method returns true. This is the case when one of the following conditions holds:","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"the maximum number of iterations is reached\nthe time limit is reached\nthe master is infeasible\nthe master is unbounded\na pricing subproblem is infeasible\na pricing subproblem is unbounded\nthere is no new column generated at the last iteration\nthere is a new constraint or valid inequality in the master\nthe incumbent dual bound and the primal master LP solution value converged","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"The method returns:","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Coluna.Algorithm.ColGenPhaseOutput","category":"page"},{"location":"api/colgen/#Coluna.Algorithm.ColGenPhaseOutput","page":"ColGen","title":"Coluna.Algorithm.ColGenPhaseOutput","text":"Output of the default implementation of a phase of the column generation algorithm.\n\n\n\n\n\n","category":"type"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"References:","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Coluna.ColGen.stop_colgen_phase\nColuna.ColGen.before_colgen_iteration\nColuna.ColGen.after_colgen_iteration\nColuna.ColGen.is_better_dual_bound","category":"page"},{"location":"api/colgen/#Coluna.ColGen.stop_colgen_phase","page":"ColGen","title":"Coluna.ColGen.stop_colgen_phase","text":"Returns true if the column generation phase must stop.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/#Coluna.ColGen.before_colgen_iteration","page":"ColGen","title":"Coluna.ColGen.before_colgen_iteration","text":"Placeholder method called before the column generation iteration. Does nothing by default but can be redefined to print some informations for instance. We strongly advise users against the use of this method to modify the context or the reformulation.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/#Coluna.ColGen.after_colgen_iteration","page":"ColGen","title":"Coluna.ColGen.after_colgen_iteration","text":"Placeholder method called after the column generation iteration. Does nothing by default but can be redefined to print some informations for instance. We strongly advise users against the use of this method to modify the context or the reformulation.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/#Coluna.ColGen.is_better_dual_bound","page":"ColGen","title":"Coluna.ColGen.is_better_dual_bound","text":"Returns true if new_dual_bound is better than dual_bound; false otherwise.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/#Phase-iterator","page":"ColGen","title":"Phase iterator","text":"","category":"section"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"In the first iterations, the restricted master LP contains a few columns and may be infeasible. To prevent this, we introduced artificial variables v and we activate/deactivate these variables depending on whether we want to prove the infeasibility of the master LP or find the optimal LP solution. The default implementation provides three phases:","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Coluna.Algorithm.ColGenPhase0\nColuna.Algorithm.ColGenPhase1\nColuna.Algorithm.ColGenPhase2","category":"page"},{"location":"api/colgen/#Coluna.Algorithm.ColGenPhase0","page":"ColGen","title":"Coluna.Algorithm.ColGenPhase0","text":"Phase 0 is a mix of phase 1 and phase 2. It sets a very large cost to artifical variables to force them to be removed from the master LP solution. If the final master LP solution contains artifical variables either the master is infeasible or the cost of artificial variables is not large enough. Phase 1 must be run.\n\n\n\n\n\n","category":"type"},{"location":"api/colgen/#Coluna.Algorithm.ColGenPhase1","page":"ColGen","title":"Coluna.Algorithm.ColGenPhase1","text":"Phase 1 sets the cost of variables to 0 except for artifical variables. The goal is to find a solution to the master LP problem that has no artificial variables.\n\n\n\n\n\n","category":"type"},{"location":"api/colgen/#Coluna.Algorithm.ColGenPhase2","page":"ColGen","title":"Coluna.Algorithm.ColGenPhase2","text":"Phase 2 solves the master LP without artificial variables. To start, it requires a set of columns that forms a feasible solution to the LP master. This set is found with phase 1.\n\n\n\n\n\n","category":"type"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Column generation always starts with Phase 0.","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"The default implementation of the phase iterator belongs to the following type:","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Coluna.Algorithm.ColunaColGenPhaseIterator","category":"page"},{"location":"api/colgen/#Coluna.Algorithm.ColunaColGenPhaseIterator","page":"ColGen","title":"Coluna.Algorithm.ColunaColGenPhaseIterator","text":"Type for the default implementation of the sequence of phases.\n\n\n\n\n\n","category":"type"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Transitions between the phases depend on four conditions:","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"(A) the presence of artificial variables in the master LP solution\n(B) the generation of new essential constraints (may happen when a new master IP solution is found)\n(C) the current stage is exact\n(D) column generation converged ","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Transitions are the following:","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"flowchart TB;\n id1(Phase 0)\n id2(Phase 1)\n id3(Phase 2)\n id4(end)\n id5(error)\n id1 --A & !B & C--> id2\n id1 --!A & !B & C & D--> id4\n id1 -- otherwise --> id1\n id2 --!A & !B--> id3\n id2 --A & C & D--> id4\n id2 -- otherwise --> id2\n id3 -- !B & C & D --> id4\n id3 -- otherwise --> id3\n id3 -- B --> id2\n id3 -- A --> id5\n style id5 stroke:#f66","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"References:","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Coluna.ColGen.AbstractColGenPhase\nColuna.ColGen.AbstractColGenPhaseIterator\nColuna.ColGen.new_phase_iterator\nColuna.ColGen.initial_phase\nColuna.ColGen.decrease_stage\nColuna.ColGen.next_phase","category":"page"},{"location":"api/colgen/#Coluna.ColGen.AbstractColGenPhase","page":"ColGen","title":"Coluna.ColGen.AbstractColGenPhase","text":"A phase of the column generation. Each phase is associated with a specific set up of the reformulation.\n\n\n\n\n\n","category":"type"},{"location":"api/colgen/#Coluna.ColGen.AbstractColGenPhaseIterator","page":"ColGen","title":"Coluna.ColGen.AbstractColGenPhaseIterator","text":"An iterator that indicates how phases follow each other.\n\n\n\n\n\n","category":"type"},{"location":"api/colgen/#Coluna.ColGen.new_phase_iterator","page":"ColGen","title":"Coluna.ColGen.new_phase_iterator","text":"Returns a new phase iterator.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/#Coluna.ColGen.initial_phase","page":"ColGen","title":"Coluna.ColGen.initial_phase","text":"Returns the phase with which the column generation algorithm must start.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/#Coluna.ColGen.decrease_stage","page":"ColGen","title":"Coluna.ColGen.decrease_stage","text":"Returns the next stage involving a \"more exact solver\" than the current one. Returns nothing if the algorithm has already reached the exact phase (last phase).\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/#Coluna.ColGen.next_phase","page":"ColGen","title":"Coluna.ColGen.next_phase","text":"Returns the next phase of the column generation algorithm. Returns nothing if the algorithm must stop.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/#Phase-output","page":"ColGen","title":"Phase output","text":"","category":"section"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Coluna.ColGen.AbstractColGenPhaseOutput\nColuna.ColGen.colgen_phase_output_type\nColuna.ColGen.new_phase_output","category":"page"},{"location":"api/colgen/#Coluna.ColGen.AbstractColGenPhaseOutput","page":"ColGen","title":"Coluna.ColGen.AbstractColGenPhaseOutput","text":"Supertype for the objects that contains the output of a column generation phase.\n\n\n\n\n\n","category":"type"},{"location":"api/colgen/#Coluna.ColGen.colgen_phase_output_type","page":"ColGen","title":"Coluna.ColGen.colgen_phase_output_type","text":"colgen_phase_outputype(ctx) -> Type{<:AbstractColGenPhaseOutput}\n\nReturns the type of the column generation phase output associated to the context.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/#Coluna.ColGen.new_phase_output","page":"ColGen","title":"Coluna.ColGen.new_phase_output","text":"new_phase_output(OutputType, min_sense, phase, stage, colgen_iter_output, iter, inc_dual_bound) -> OutputType\n\nReturns the column generation phase output.\n\nArguments of this function are:\n\nOutputType: the type of the column generation phase output\nmin_sense: true if it is a minimization problem; false otherwise\nphase: the current column generation phase\nstage: the current column generation stage\ncol_gen_iter_output: the last column generation iteration output\niter: the last iteration number\ninc_dual_bound: the current incumbent dual bound\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/#Stages","page":"ColGen","title":"Stages","text":"","category":"section"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"A stage is a set of consecutive iterations in which we use a given pricing solver. The aim is to speed up the resolution of the pricing problem by first using an approximate but fast pricing algorithm and then switching to increasingly less heuristic algorithms until the last stage where an exact solver is used. and an exact solver at the last stage. Given a pricing solver, when the column generation does not progress anymore or the pricing solver does not return any new column, the default implementation switch to a more exact pricing solver. Stages are created using the stages_pricing_solver_ids of the ColumnGenerationAlgorithm parameter object. The default implementation implements the interface around the following object:","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Coluna.Algorithm.ColGenStageIterator","category":"page"},{"location":"api/colgen/#Coluna.Algorithm.ColGenStageIterator","page":"ColGen","title":"Coluna.Algorithm.ColGenStageIterator","text":"Default implementation of the column generation stages works as follows.\n\nConsider a set {A,B,C} of subproblems each of them associated to the following sets of pricing solvers: {a1, a2, a3}, {b1, b2}, {c1, c2, c3, c4}. Pricing solvers a1, b1, c1 are exact solvers; others are heuristic.\n\nThe column generation algorithm will run the following stages:\n\nstage 4 with pricing solvers {a3, b2, c4}\nstage 3 with pricing solvers {a2, b1, c3}\nstage 2 with pricing solvers {a1, b1, c2}\nstage 1 with pricing solvers {a1, b1, c1} (exact stage)\n\nColumn generation moves from one stage to another when all solvers find no column.\n\n\n\n\n\n","category":"type"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"References:","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Coluna.ColGen.AbstractColGenStage\nColuna.ColGen.AbstractColGenStageIterator\nColuna.ColGen.new_stage_iterator\nColuna.ColGen.initial_stage\nColuna.ColGen.next_stage\nColuna.ColGen.get_pricing_subprob_optimizer\nColuna.ColGen.stage_id\nColuna.ColGen.is_exact_stage","category":"page"},{"location":"api/colgen/#Coluna.ColGen.AbstractColGenStage","page":"ColGen","title":"Coluna.ColGen.AbstractColGenStage","text":"A stage of the column generation algorithm. Each stage is associated to a specific solver for each pricing subproblem.\n\n\n\n\n\n","category":"type"},{"location":"api/colgen/#Coluna.ColGen.AbstractColGenStageIterator","page":"ColGen","title":"Coluna.ColGen.AbstractColGenStageIterator","text":"An iterator that indicates how stages follow each other.\n\n\n\n\n\n","category":"type"},{"location":"api/colgen/#Coluna.ColGen.new_stage_iterator","page":"ColGen","title":"Coluna.ColGen.new_stage_iterator","text":"Returns a new stage iterator.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/#Coluna.ColGen.initial_stage","page":"ColGen","title":"Coluna.ColGen.initial_stage","text":"Returns the stage at which the column generation algorithm must start.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/#Coluna.ColGen.next_stage","page":"ColGen","title":"Coluna.ColGen.next_stage","text":"Returns the next stage that column generation must use.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/#Coluna.ColGen.get_pricing_subprob_optimizer","page":"ColGen","title":"Coluna.ColGen.get_pricing_subprob_optimizer","text":"Returns the optimizer for the pricing subproblem associated to the given stage.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/#Coluna.ColGen.stage_id","page":"ColGen","title":"Coluna.ColGen.stage_id","text":"Returns the id of the stage.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/#Coluna.ColGen.is_exact_stage","page":"ColGen","title":"Coluna.ColGen.is_exact_stage","text":"Returns true if the stage uses an exact solver for the pricing subproblem; false otherwise.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/#Column-generation-iteration","page":"ColGen","title":"Column generation iteration","text":"","category":"section"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"This is a description of how the Coluna.ColGen.run_colgen_iteration! generic function behaves in the default implementation.","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"These are the main steps of a column generation iteration without stabilization. Click on the step to go to the relevant section.","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"flowchart TB;\n id1(Optimize master LP)\n id2{{Solution to master LP is integer?}}\n id3(Update incumbent primal solution if better than current one)\n id4(Compute reduced cost of subproblem variables)\n id5{{Subproblem iterator}}\n id6(Optimize pricing subproblem)\n id7(Push subproblem solution into set)\n id8(Compute dual bound)\n id9(Insert columns)\n id10(Iteration output)\n id1 --> id2\n id2 --yes--> id3\n id2 --no--> id4\n id3 --> id4\n id4 --> id5\n id5 --subproblem--> id6\n id6 --> id7\n id7 --> id5\n id5 --end--> id8\n id8 --> id9\n id9 --> id10\n click id1 href \"#Optimize-master-LP\" \"Link to doc\"\n click id2 href \"#Check-integrality-of-the-master-LP-solution\" \"Link to doc\"\n click id3 href \"#Update-incumbent-primal-solution\" \"Link to doc\"\n click id4 href \"#Reduced-costs-calculation\" \"Link to doc\"\n click id5 href \"#Pricing-subproblem-iterator\" \"Link to doc\"\n click id6 href \"#Pricing-subproblem-optimization\" \"Link to doc\"\n click id7 href \"#Set-of-generated-columns\" \"Link to doc\"\n click id8 href \"#Dual-bound-calculation\" \"Link to doc\"\n click id9 href \"#Columns-insertion\" \"Link to doc\"\n click id10 href \"#Iteration-output\" \"Link to doc\"","category":"page"},{"location":"api/colgen/#Optimize-master-LP","page":"ColGen","title":"Optimize master LP","text":"","category":"section"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"At each iteration, the algorithm requires a dual solution to the master LP to compute the reduced cost of subproblem variables.","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"The default implementation optimizes the master with an LP solver through MathOptInterface. It returns a primal and a dual solution.","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"In the default implementation, the master LP output is in the following data structure:","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Coluna.Algorithm.ColGenMasterResult","category":"page"},{"location":"api/colgen/#Coluna.Algorithm.ColGenMasterResult","page":"ColGen","title":"Coluna.Algorithm.ColGenMasterResult","text":"Output of the ColGen.optimize_master_lp_problem! method.\n\nContains result, an OptimizationState object that is the output of the SolveLpForm algorithm called to optimize the master LP problem.\n\n\n\n\n\n","category":"type"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"References:","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Coluna.ColGen.optimize_master_lp_problem!","category":"page"},{"location":"api/colgen/#Coluna.ColGen.optimize_master_lp_problem!","page":"ColGen","title":"Coluna.ColGen.optimize_master_lp_problem!","text":"optimize_master_lp_problem!(master, context, env) -> MasterResult\n\nReturns an instance of a custom object MasterResult that implements the following methods:\n\nget_obj_val: objective value of the master (mandatory)\nget_primal_sol: primal solution to the master (optional)\nget_dual_sol: dual solution to the master (mandatory otherwise column generation stops)\n\nIt should at least return a dual solution (obtained with LP optimization or subgradient) otherwise column generation cannot continue.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"You can see the additional methods to implement in the result data structures section.","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Go back to the column generation iteration overview.","category":"page"},{"location":"api/colgen/#Check-the-integrality-of-the-master-LP-solution","page":"ColGen","title":"Check the integrality of the master LP solution","text":"","category":"section"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"The algorithm checks the integrality of the primal solution to the master LP to improve the global primal bound of the branch-cut-price algorithm.","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"In the default implementation, the integrality check is done using the MathProg.proj_cols_is_integer method. It implements the procedure described in the paper (TODO). Basically, it sorts the column used in the master LP primal solution in lexicographic order. It assigns a weight to each column equal to the value of the column in the master LP solution. It then forms columns of weight one by accumulating the columns of the fractional solution. If columns are integral, the solution is integral. This is a heuristic procedure so it can miss some integer solutions.","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"If the solution is integral, the essential cut callback is called to make sure it is feasible.","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"References:","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Coluna.ColGen.check_primal_ip_feasibility!\nColuna.ColGen.is_better_primal_sol","category":"page"},{"location":"api/colgen/#Coluna.ColGen.check_primal_ip_feasibility!","page":"ColGen","title":"Coluna.ColGen.check_primal_ip_feasibility!","text":"Returns a primal solution expressed in the original problem variables if the current master LP solution is integer feasible; nothing otherwise.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/#Coluna.ColGen.is_better_primal_sol","page":"ColGen","title":"Coluna.ColGen.is_better_primal_sol","text":"Returns true if the new master IP primal solution is better than the current; false otherwise.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Go back to the column generation iteration overview.","category":"page"},{"location":"api/colgen/#Update-incumbent-primal-solution","page":"ColGen","title":"Update incumbent primal solution","text":"","category":"section"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"If the solution to master LP is integral and better than the current best one, we need to update the incumbent. This solution is then used by the tree-search algorithm in the bounding mechanism that prunes the nodes.","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"References:","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Coluna.ColGen.update_inc_primal_sol!","category":"page"},{"location":"api/colgen/#Coluna.ColGen.update_inc_primal_sol!","page":"ColGen","title":"Coluna.ColGen.update_inc_primal_sol!","text":"Updates the current master IP primal solution.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Go back to the column generation iteration overview.","category":"page"},{"location":"api/colgen/#Reduced-costs-calculation","page":"ColGen","title":"Reduced costs calculation","text":"","category":"section"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Reduced costs calculation is written as a math operation in the run_colgen_iteration! generic function. As a consequence, the dual solution to the master LP and the implementation of the two following methods must return data structures that support math operations.","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"To speed up this operation, we cache data in the following data structure:","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Coluna.Algorithm.ReducedCostsCalculationHelper","category":"page"},{"location":"api/colgen/#Coluna.Algorithm.ReducedCostsCalculationHelper","page":"ColGen","title":"Coluna.Algorithm.ReducedCostsCalculationHelper","text":"Extracted information to speed-up calculation of reduced costs of subproblem representatives and pure master variables. We extract from the master the information we need to compute the reduced cost of DW subproblem variables:\n\ndw_subprob_c contains the perenial cost of DW subproblem representative variables\ndw_subprob_A is a submatrix of the master coefficient matrix that involves only DW subproblem representative variables.\n\nWe also extract from the master the information we need to compute the reduced cost of pure master variables:\n\npure_master_c contains the perenial cost of pure master variables\npure_master_A is a submatrix of the master coefficient matrix that involves only pure master variables.\n\nCalculation is c - transpose(A) * master_lp_dual_solution.\n\nThis information is given to the generic implementation of the column generation algorithm through methods:\n\nColGen.getsubprobvarorigcosts \nColGen.getorigcoefmatrix\n\n\n\n\n\n","category":"type"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Reduced costs calculation also requires the implementation of the two following methods:","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Coluna.ColGen.update_master_constrs_dual_vals!\nColuna.ColGen.update_reduced_costs!\nColuna.ColGen.get_subprob_var_orig_costs\nColuna.ColGen.get_subprob_var_coef_matrix\nColuna.ColGen.update_sp_vars_red_costs!","category":"page"},{"location":"api/colgen/#Coluna.ColGen.update_master_constrs_dual_vals!","page":"ColGen","title":"Coluna.ColGen.update_master_constrs_dual_vals!","text":"Updates dual value of the master constraints. Dual values of the constraints can be used when the pricing solver supports non-robust cuts.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/#Coluna.ColGen.update_reduced_costs!","page":"ColGen","title":"Coluna.ColGen.update_reduced_costs!","text":"Method that you can implement if you want to store the reduced cost of subproblem variables in the context.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/#Coluna.ColGen.get_subprob_var_orig_costs","page":"ColGen","title":"Coluna.ColGen.get_subprob_var_orig_costs","text":"Returns the original cost c of subproblems variables. to compute reduced cost ̄c = c - transpose(A) * π.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/#Coluna.ColGen.get_subprob_var_coef_matrix","page":"ColGen","title":"Coluna.ColGen.get_subprob_var_coef_matrix","text":"Returns the coefficient matrix A of subproblem variables in the master to compute reduced cost ̄c = c - transpose(A) * π.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/#Coluna.ColGen.update_sp_vars_red_costs!","page":"ColGen","title":"Coluna.ColGen.update_sp_vars_red_costs!","text":"Updates reduced costs of variables of a given subproblem.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Go back to the column generation iteration overview.","category":"page"},{"location":"api/colgen/#Pricing-subproblem-iterator","page":"ColGen","title":"Pricing subproblem iterator","text":"","category":"section"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"The pricing strategy is basically an iterator used to iterate over the pricing subproblems to optimize at each iteration of the column generation. The context can serve as a memory of the pricing strategy to change the way we iterate over subproblems between each column generation iteration.","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"The default implementation iterates over all subproblems.","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Here are the references for the interface:","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Coluna.ColGen.AbstractPricingStrategy\nColuna.ColGen.get_pricing_strategy\nColuna.ColGen.pricing_strategy_iterate","category":"page"},{"location":"api/colgen/#Coluna.ColGen.AbstractPricingStrategy","page":"ColGen","title":"Coluna.ColGen.AbstractPricingStrategy","text":"A pricing strategy defines how we iterate on pricing subproblems. A default pricing strategy consists in iterating on all pricing subproblems.\n\nBasically, this object is used like this:\n\n pricing_strategy = ColGen.get_pricing_strategy(ctx, phase)\n next = ColGen.pricing_strategy_iterate(pricing_strategy)\n while !isnothing(next)\n (sp_id, sp_to_solve), state = next\n # Solve the subproblem `sp_to_solve`.\n next = ColGen.pricing_strategy_iterate(pricing_strategy, state)\n end\n\n\n\n\n\n","category":"type"},{"location":"api/colgen/#Coluna.ColGen.get_pricing_strategy","page":"ColGen","title":"Coluna.ColGen.get_pricing_strategy","text":"get_pricing_strategy(ctx, phase) -> AbstractPricingStrategy\n\nReturns the pricing strategy object.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/#Coluna.ColGen.pricing_strategy_iterate","page":"ColGen","title":"Coluna.ColGen.pricing_strategy_iterate","text":"pricing_strategy_iterate(pricing_strategy) -> ((sp_id, sp_to_solve), state)\npricing_strategy_iterate(pricing_strategy, state) -> ((sp_id, sp_to_solve), state)\n\nReturns an iterator with the first pricing subproblem that must be optimized. The next subproblem is returned by a call to Base.iterate using the information provided by this method.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Go back to the column generation iteration overview.","category":"page"},{"location":"api/colgen/#Pricing-subproblem-optimization","page":"ColGen","title":"Pricing subproblem optimization","text":"","category":"section"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"At each iteration, the algorithm requires primal solutions to the pricing subproblems. The generic function supports multi-column generation so you can return any number of solutions.","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"The default implementation supports optimization of the pricing subproblems using a MILP solver or a pricing callback. Non-robust valid inequalities are not supported by MILP solvers as they change the structure of the subproblems. When using a pricing callback, you must be aware of how Coluna calculates the reduced cost of a column:","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"The reduced cost of a column is split into three contributions:","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"the contribution of the subproblem variables that is the primal solution cost given the reduced cost of subproblem variables\nthe contribution of the non-robust constraints (i.e. master constraints that cannot be expressed using subproblem variables except the convexity constraint) that is not supported by MILP solver but that you must take into account in the pricing callback\nthe contribution of the master convexity constraint that is automatically taken into account by Coluna once the primal solution is returned.","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Therefore, when you use a pricing callback, you must not discard some columns based only on the primal solution cost because you don't know the contribution of the convexity constraint.","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Coluna.Algorithm.GeneratedColumn \nColuna.Algorithm.ColGenPricingResult","category":"page"},{"location":"api/colgen/#Coluna.Algorithm.GeneratedColumn","page":"ColGen","title":"Coluna.Algorithm.GeneratedColumn","text":"Solution to a pricing subproblem after a given optimization.\n\nIt contains:\n\ncolumn: the solution stored as a PrimalSolution object\nred_cost: the reduced cost of the column\nmin_obj: a boolean indicating if the objective is to minimize or maximize\n\n\n\n\n\n","category":"type"},{"location":"api/colgen/#Coluna.Algorithm.ColGenPricingResult","page":"ColGen","title":"Coluna.Algorithm.ColGenPricingResult","text":"Output of the default implementation of ColGen.optimize_pricing_problem!.\n\nIt contains:\n\nresult: the output of the SolveIpForm algorithm called to optimize the pricing subproblem\ncolumns: a vector of GeneratedColumn objects obtained by processing of the output of pricing subproblem optimization, it stores the reduced cost of each column\nbest_red_cost: the best reduced cost of the columns\n\n\n\n\n\n","category":"type"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"References:","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Coluna.ColGen.optimize_pricing_problem!","category":"page"},{"location":"api/colgen/#Coluna.ColGen.optimize_pricing_problem!","page":"ColGen","title":"Coluna.ColGen.optimize_pricing_problem!","text":"optimize_pricing_problem!(ctx, sp, env, optimizer, mast_dual_sol) -> PricingResult\n\nReturns a custom object PricingResult that must implement the following functions:\n\nget_primal_sols: array of primal solution to the pricing subproblem \nget_primal_bound: best reduced cost (optional ?)\nget_dual_bound: dual bound of the pricing subproblem (used to compute the master dual bound)\nmaster_dual_sol: dual solution pi^textout to the master problem used to compute the real reduced cost of the column when stabilization is active\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"You can see the additional methods to implement in the result data structures section.","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Go back to the column generation iteration overview.","category":"page"},{"location":"api/colgen/#Set-of-generated-columns","page":"ColGen","title":"Set of generated columns","text":"","category":"section"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"You can define your data structure to manage the columns generated at a given iteration. Columns are inserted after the optimization of all pricing subproblems to allow the parallelization of the latter.","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"In the default implementation, we use the following data structure:","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Coluna.Algorithm.ColumnsSet\nColuna.Algorithm.SubprobPrimalSolsSet","category":"page"},{"location":"api/colgen/#Coluna.Algorithm.ColumnsSet","page":"ColGen","title":"Coluna.Algorithm.ColumnsSet","text":"Stores a collection of columns.\n\nIt contains:\n\ncolumns: a vector of GeneratedColumn objects by all pricing subproblems that will be inserted into the master\nsubprob_primal_solutions: an object that stores the best columns generated by each pricing subproblem at this iteration.\n\n\n\n\n\n","category":"type"},{"location":"api/colgen/#Coluna.Algorithm.SubprobPrimalSolsSet","page":"ColGen","title":"Coluna.Algorithm.SubprobPrimalSolsSet","text":"Columns generated at the current iteration that forms the \"current primal solution\". This is used to compute the Lagragian dual bound.\n\nIt contains:\n\nprimal_sols a dictionary that maps a formulation id to the best primal solution found by the pricing subproblem associated to this formulation\nimprove_master a dictionary that maps a formulation id to a boolean indicating if the best primal solution found by the pricing subproblem associated to this formulation has negative reduced cost\n\nThis structure also helps to compute the subgradient of the Lagrangian function.\n\n\n\n\n\n","category":"type"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"In the default implementation, push_in_set! is responsible for checking if the column has improving reduced cost. Only columns with improving reduced cost are inserted in the set. The push_in_set! is also responsible to insert he best primal solution to each pricing problem into the SubprobPrimalSolsSet object.","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"References:","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Coluna.ColGen.set_of_columns\nColuna.ColGen.push_in_set!","category":"page"},{"location":"api/colgen/#Coluna.ColGen.set_of_columns","page":"ColGen","title":"Coluna.ColGen.set_of_columns","text":"Returns an empty container that will store all the columns generated by the pricing problems during an iteration of the column generation algorithm. One must be able to iterate on this container to insert the columns in the master problem.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/#Coluna.ColGen.push_in_set!","page":"ColGen","title":"Coluna.ColGen.push_in_set!","text":"Pushes the column in the set of columns generated at a given iteration of the column generation algorithm. Columns stored in the set will then be considered for insertion in the master problem. Returns true if column was inserted in the set, false otherwise.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Go back to the column generation iteration overview.","category":"page"},{"location":"api/colgen/#Dual-bound-calculation","page":"ColGen","title":"Dual bound calculation","text":"","category":"section"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"In the default implementation, given a vector pi geq 0 of dual values to the master constraints (1), the Lagrangian dual function is given by:","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"L(pi) = pi a + sum_k in K max_l_k leq mathbf1 lambda^k leq u^k (c^k - pi A^k)lambda^k + max_ barl leq y leq baru (barc - pi barA)y","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Let:","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"element z_k(pi) leq min_i (c^k_i - pi A^k_i) be a lower bound on the solution value of the pricing problem\nelement barz_j(pi) = barc - pi barA be the reduced cost of pure master variable y_j","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Then, the Lagrangian dual function can be lower bounded by:","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"L(pi) geq pi a + sum_k in K max z_k(pi) cdot l_k z_k(pi) cdot u_k + sum_j in J max barz_j(pi) cdot barl_j barz_j(pi) cdot baru_j","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"More precisely:","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"the first term is the contribution of the master obtained by removing the contribution of the convexity constraints (computed by ColGen.Algorithm._convexity_contrib), and the pure master variables (but you should see the third term) from the master LP solution value\nthe second term is the contribution of the subproblem variables which is the sum of the best solution value of each pricing subproblem multiplied by the lower and upper multiplicity of the subproblem depending on whether the reduced cost is negative or positive (this is computed by ColGen.Algorithm._subprob_contrib)\nthe third term is the contribution of the pure master variables which is taken into account by master LP value.","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Therefore, we can compute the Lagrangian dual bound as follows:","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"master_lp_obj_val - convexity_contrib + sp_contrib","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"However, if the smoothing stabilization is active, we compute the dual bound at the sep-point. As a consequence, we can't use the master LP value because it corresponds to the dual solution at the out-point. We therefore need to compute the lagrangian dual bound by strictly applying the above formula.","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"References:","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Coluna.ColGen.compute_sp_init_pb\nColuna.ColGen.compute_sp_init_db\nColuna.ColGen.compute_dual_bound","category":"page"},{"location":"api/colgen/#Coluna.ColGen.compute_sp_init_pb","page":"ColGen","title":"Coluna.ColGen.compute_sp_init_pb","text":"Returns an initial primal bound for a pricing subproblem. Default value should be +/- infinite depending on the optimization sense.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/#Coluna.ColGen.compute_sp_init_db","page":"ColGen","title":"Coluna.ColGen.compute_sp_init_db","text":"Returns an initial dual bound for a pricing subproblem. Default value should be +/- infinite depending on the optimization sense.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/#Coluna.ColGen.compute_dual_bound","page":"ColGen","title":"Coluna.ColGen.compute_dual_bound","text":"compute_dual_bound(ctx, phase, master_lp_obj_val, master_dbs, generated_columns, mast_dual_sol) -> Float64\n\nCaculates the dual bound at a given iteration of column generation. The dual bound is composed of:\n\nmaster_lp_obj_val: objective value of the master LP problem\nmaster_dbs: dual values of the pricing subproblems\nthe contribution of the master convexity constraints that you should compute from mast_dual_sol.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Go back to the column generation iteration overview.","category":"page"},{"location":"api/colgen/#Columns-insertion","page":"ColGen","title":"Columns insertion","text":"","category":"section"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"The default implementation inserts into the master all the columns stored in the ColumnsSet object.","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Reference:","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Coluna.ColGen.insert_columns!","category":"page"},{"location":"api/colgen/#Coluna.ColGen.insert_columns!","page":"ColGen","title":"Coluna.ColGen.insert_columns!","text":"Inserts columns into the master. Returns the number of columns inserted. Implementation is responsible for checking if the column must be inserted and warn the user if something unexpected happens.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Go back to the column generation iteration overview.","category":"page"},{"location":"api/colgen/#Iteration-output","page":"ColGen","title":"Iteration output","text":"","category":"section"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Coluna.Algorithm.ColGenIterationOutput","category":"page"},{"location":"api/colgen/#Coluna.Algorithm.ColGenIterationOutput","page":"ColGen","title":"Coluna.Algorithm.ColGenIterationOutput","text":"Object for the output of an iteration of the column generation default implementation.\n\n\n\n\n\n","category":"type"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"References:","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Coluna.ColGen.AbstractColGenIterationOutput\nColuna.ColGen.colgen_iteration_output_type\nColuna.ColGen.new_iteration_output","category":"page"},{"location":"api/colgen/#Coluna.ColGen.AbstractColGenIterationOutput","page":"ColGen","title":"Coluna.ColGen.AbstractColGenIterationOutput","text":"Supertype for the objects that contains the output of a column generation iteration.\n\n\n\n\n\n","category":"type"},{"location":"api/colgen/#Coluna.ColGen.colgen_iteration_output_type","page":"ColGen","title":"Coluna.ColGen.colgen_iteration_output_type","text":"colgen_iteration_output_type(ctx) -> Type{<:AbstractColGenIterationOutput}\n\nReturns the type of the column generation iteration output associated to the context.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/#Coluna.ColGen.new_iteration_output","page":"ColGen","title":"Coluna.ColGen.new_iteration_output","text":"new_iteration_output(::Type{<:AbstractColGenIterationOutput}, args...) -> AbstractColGenIterationOutput\n\nArguments (i.e. arg...) of this function are the following:\n\nmin_sense: true if the objective is a minimization function; false otherwise\nmlp: the optimal solution value of the master LP\ndb: the Lagrangian dual bound\nnb_new_cols: the number of columns inserted into the master\nnew_cut_in_master: true if valid inequalities or new constraints added into the master; false otherwise\ninfeasible_master: true if the master is proven infeasible; false otherwise\nunbounded_master: true if the master is unbounded; false otherwise\ninfeasible_subproblem: true if a pricing subproblem is proven infeasible; false otherwise\nunbounded_subproblem: true if a pricing subproblem is unbounded; false otherwise\ntime_limit_reached: true if time limit is reached; false otherwise\nmaster_primal_sol: the primal master LP solution\nip_primal_sol: the incumbent primal master solution\ndual_sol: the dual master LP solution\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Go back to the column generation iteration overview.","category":"page"},{"location":"api/colgen/#Getters-for-Result-data-structures","page":"ColGen","title":"Getters for Result data structures","text":"","category":"section"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Method name Master Pricing\nis_unbounded X X\nis_infeasible X X\nget_primal_sol X \nget_primal_sols X\nget_dual_sol X \nget_obj_val X \nget_primal_bound X\nget_dual_bound X","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"References:","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Coluna.ColGen.is_unbounded\nColuna.ColGen.is_infeasible\nColuna.ColGen.get_primal_sol\nColuna.ColGen.get_primal_sols\nColuna.ColGen.get_dual_sol\nColuna.ColGen.get_obj_val\nColuna.ColGen.get_primal_bound","category":"page"},{"location":"api/colgen/#Coluna.ColGen.is_unbounded","page":"ColGen","title":"Coluna.ColGen.is_unbounded","text":"Returns true if a master or pricing problem result is unbounded; false otherwise.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/#Coluna.ColGen.is_infeasible","page":"ColGen","title":"Coluna.ColGen.is_infeasible","text":"Returns true if a master or pricing problem result is infeasible; false otherwise.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/#Coluna.ColGen.get_primal_sol","page":"ColGen","title":"Coluna.ColGen.get_primal_sol","text":"Returns primal solution to the master LP problem.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/#Coluna.ColGen.get_primal_sols","page":"ColGen","title":"Coluna.ColGen.get_primal_sols","text":"Array of primal solutions to the pricing subproblem\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/#Coluna.ColGen.get_dual_sol","page":"ColGen","title":"Coluna.ColGen.get_dual_sol","text":"Returns dual solution to the master optimization problem.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/#Coluna.ColGen.get_obj_val","page":"ColGen","title":"Coluna.ColGen.get_obj_val","text":"Returns the optimal objective value of the master LP problem.\" See optimize_master_lp_problem!.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/#Coluna.ColGen.get_primal_bound","page":"ColGen","title":"Coluna.ColGen.get_primal_bound","text":"Returns primal bound of the pricing subproblem; nothing if no primal bound is available and the initial dual bound returned by compute_sp_init_pb will be used to compute the pseudo dual bound.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Go back to the column generation iteration overview.","category":"page"},{"location":"api/colgen/#Getters-for-Output-data-structures","page":"ColGen","title":"Getters for Output data structures","text":"","category":"section"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Method name ColGen Phase Iteration\nget_nb_new_cols X\nget_master_ip_primal_sol X X X\nget_master_lp_primal_sol X \nget_master_dual_sol X \nget_dual_bound X X\nget_master_lp_primal_bound X \nis_infeasible X ","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"References:","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Coluna.ColGen.get_nb_new_cols\nColuna.ColGen.get_master_ip_primal_sol\nColuna.ColGen.get_master_lp_primal_sol\nColuna.ColGen.get_master_dual_sol\nColuna.ColGen.get_master_lp_primal_bound","category":"page"},{"location":"api/colgen/#Coluna.ColGen.get_nb_new_cols","page":"ColGen","title":"Coluna.ColGen.get_nb_new_cols","text":"Returns the number of new columns inserted into the master at the end of an iteration.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/#Coluna.ColGen.get_master_ip_primal_sol","page":"ColGen","title":"Coluna.ColGen.get_master_ip_primal_sol","text":"Returns the incumbent primal master IP solution at the end of an iteration or a phase.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/#Coluna.ColGen.get_master_lp_primal_sol","page":"ColGen","title":"Coluna.ColGen.get_master_lp_primal_sol","text":"Returns the primal master LP solution found at the last iteration of the column generation algorithm.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/#Coluna.ColGen.get_master_dual_sol","page":"ColGen","title":"Coluna.ColGen.get_master_dual_sol","text":"Returns the dual master LP solution found at the last iteration of the column generation algorithm.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/#Coluna.ColGen.get_master_lp_primal_bound","page":"ColGen","title":"Coluna.ColGen.get_master_lp_primal_bound","text":"Returns the master LP solution value at the last iteration of the column generation algorithm.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Go back to the column generation iteration overview.","category":"page"},{"location":"api/colgen/#Stabilization","page":"ColGen","title":"Stabilization","text":"","category":"section"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Coluna provides a default implementation of the smoothing stabilization with a self-adjusted alpha parameter, 0 leq alpha 1.","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"At each iteration of the column generation algorithm, instead of generating columns for the dual solution to the master LP, we generate columns for a perturbed dual solution defined as follows:","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"pi^textsep = alpha pi^textin + (1-alpha) pi^textout","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"where pi^textin is the dual solution that gives the best Lagrangian dual bound so far (also called stabilization center) and pi^textout is the dual solution to the master LP at the current iteration. This solution is returned by the default implementation of Coluna.ColGen.get_stab_dual_sol.","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Some elements of the column generation change when using stabilization.","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Columns are generated using the smoothed dual solution pi^textsep but we still need to compute the reduced cost of the columns using the original dual solution pi^textout.\nThe dual bound is computed using the smoothed dual solution pi^textsep.\nThe pseudo bound is computed using the smoothed dual solution pi^textsep.\nThe smoothed dual bound can result in the generation of no improving columns. This is called a misprice. In that case, we need to move away from the stabilization center pi^textin by decreasing alpha.","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"When using self-adjusted stabilization, the smoothing coefficient alpha is adjusted to make the smoothed dual solution pi^textsep closer to the best possible dual solution on the line between pi^textin and pi^textout (i.e. where the subgradient of the current primal solution is perpendicular to the latter line). To compute the subgradient, we use the following data structure:","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Coluna.Algorithm.SubgradientCalculationHelper","category":"page"},{"location":"api/colgen/#Coluna.Algorithm.SubgradientCalculationHelper","page":"ColGen","title":"Coluna.Algorithm.SubgradientCalculationHelper","text":"Precompute information to speed-up calculation of subgradient of master variables. We extract from the master follwowing information:\n\na contains the perenial rhs of all master constraints except convexity constraints;\nA is a submatrix of the master coefficient matrix that involves only representative of original variables (pure master vars + DW subproblem represtative vars) \n\nCalculation is a - A * (m .* z) where :\n\nm contains a multiplicity factor for each variable involved in the calculation (lower or upper sp multiplicity depending on variable reduced cost);\nz is the concatenation of the solution to the master (for pure master vars) and pricing subproblems (for DW subproblem represtative vars).\n\nOperation m .* z \"mimics\" a solution in the original space.\n\n\n\n\n\n","category":"type"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"References:","category":"page"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"Coluna.ColGen.setup_stabilization!\nColuna.ColGen.update_stabilization_after_master_optim!\nColuna.ColGen.get_stab_dual_sol\nColuna.ColGen.check_misprice\nColuna.ColGen.update_stabilization_after_pricing_optim!\nColuna.ColGen.update_stabilization_after_misprice!\nColuna.ColGen.update_stabilization_after_iter!\nColuna.ColGen.get_output_str","category":"page"},{"location":"api/colgen/#Coluna.ColGen.setup_stabilization!","page":"ColGen","title":"Coluna.ColGen.setup_stabilization!","text":"Returns an instance of a data structure that contain information about the stabilization used in the column generation algorithm.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/#Coluna.ColGen.update_stabilization_after_master_optim!","page":"ColGen","title":"Coluna.ColGen.update_stabilization_after_master_optim!","text":"update_stabilization_after_master_optim!(stab, phase, mast_dual_sol) -> Bool\n\nUpdate stabilization after master optimization where mast_dual_sol is the dual solution to the master problem. Returns true if the stabilization will change the dual solution used for the pricing in the current column generation iteration, false otherwise.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/#Coluna.ColGen.get_stab_dual_sol","page":"ColGen","title":"Coluna.ColGen.get_stab_dual_sol","text":"Returns the dual solution used for the pricing in the current column generation iteration (stabilized dual solution).\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/#Coluna.ColGen.check_misprice","page":"ColGen","title":"Coluna.ColGen.check_misprice","text":"Returns true if the stabilized dual solution leads to a misprice, false otherwise.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/#Coluna.ColGen.update_stabilization_after_pricing_optim!","page":"ColGen","title":"Coluna.ColGen.update_stabilization_after_pricing_optim!","text":"Updates stabilization after pricing optimization where:\n\nmast_dual_sol is the dual solution to the master problem\npseudo_db is the pseudo dual bound of the problem after optimization of the pricing problems\nsmooth_dual_sol is the current smoothed dual solution\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/#Coluna.ColGen.update_stabilization_after_misprice!","page":"ColGen","title":"Coluna.ColGen.update_stabilization_after_misprice!","text":"Updates stabilization after a misprice. Argument mast_dual_sol is the dual solution to the master problem.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/#Coluna.ColGen.update_stabilization_after_iter!","page":"ColGen","title":"Coluna.ColGen.update_stabilization_after_iter!","text":"Updates stabilization after an iteration of the column generation algorithm. Arguments:\n\nstab is the stabilization data structure\nctx is the column generation context\nmaster is the master problem\ngenerated_columns is the set of generated columns\nmast_dual_sol is the dual solution to the master problem\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/#Coluna.ColGen.get_output_str","page":"ColGen","title":"Coluna.ColGen.get_output_str","text":"Returns a string with a short information about the stabilization.\n\n\n\n\n\n","category":"function"},{"location":"api/colgen/","page":"ColGen","title":"ColGen","text":"","category":"page"},{"location":"api/presolve/#Presolve","page":"Presolve","title":"Presolve","text":"","category":"section"},{"location":"api/presolve/","page":"Presolve","title":"Presolve","text":"Currently, the presolve algorithm supports only the Dantzig-Wolfe decomposition.","category":"page"},{"location":"api/presolve/","page":"Presolve","title":"Presolve","text":"The presolve algorithm operates on matrix representations of the formulation. It requires two representations of the master formulation:","category":"page"},{"location":"api/presolve/","page":"Presolve","title":"Presolve","text":"the restricted master that contains master column variables, pure master variables and artificial variables;\nthe representative master that contains subproblem representative variables and pure master variables;","category":"page"},{"location":"api/presolve/","page":"Presolve","title":"Presolve","text":"and the representation of the pricing subproblems.","category":"page"},{"location":"api/presolve/","page":"Presolve","title":"Presolve","text":"The current presolve operations available are the following (taxonomy of Achterberg et al. 2016):","category":"page"},{"location":"api/presolve/","page":"Presolve","title":"Presolve","text":"model cleanup & removal of redundant constraints\nbound strengthening\nremoval of fixed variables","category":"page"},{"location":"api/presolve/#Partial-solution","page":"Presolve","title":"Partial solution","text":"","category":"section"},{"location":"api/presolve/","page":"Presolve","title":"Presolve","text":"The presolve algorithm has the responsibility to define and fix a partial solution when it exists. When a variable x has a value barx 0 (resp. barx 0) in the partial solution, it means that x has a lower (resp. upper) bounds barx that will definitely be part of the solution at the current branch-and-bound node and its successors.","category":"page"},{"location":"api/presolve/","page":"Presolve","title":"Presolve","text":"In other words, the partial solution describes a minimal distance of the variables from zero in the all the solutions to a problem at a given branch-and-bound node. It always restricts the domain of the variables (i.e. increase distance from zero). The only way to relax the domains is to backtrack to an ancestor of the current branch-and-bound node (i.e. go back to a previous partial solution).","category":"page"},{"location":"api/presolve/#Augmenting-the-partial-solution","page":"Presolve","title":"Augmenting the partial solution","text":"","category":"section"},{"location":"api/presolve/","page":"Presolve","title":"Presolve","text":"Consider a local partial solution (barx^rm pure barlambda) (where barx^rm pure is the vector of values for pure master variables, and barlambda is the vector of values for master columns), which should be added to the global partial solution (bary^rm pure bartheta): ","category":"page"},{"location":"api/presolve/","page":"Presolve","title":"Presolve","text":"augment the global partial solution: (bary^rm pure bartheta)leftarrow(barx^rm pure+bary^rm pure barlambda+bartheta). \nupdate the right-hand side values of the master constraints: rm rhs_ileftarrow rm rhs_i - A^rm purecdotbarx^rm pure - A^rm colcdotbar lambda, where A^rm pure is the matrix of coefficients of pure master constraints and A^rm col is the matrix of coefficients of master columns.\nupdate subproblem multiplicities U_kleftarrow U_k - sum_qin Q_kbarlambda_q, and L_kleftarrow maxleft0 L_k - sum_qin Q_kbarlambda_qright, where Q_k is the set of indices of columns associated with solutions from subproblem k.\nupdate the bounds of pure master variables and representative master variables using the representative local partial solution: barx^rm repr = sum_qin Qs_qcdot barlambda_q, where Q is the total number of columns, and s_q is the subproblem solution associated with column lambda_q.","category":"page"},{"location":"api/presolve/#Updating-bounds-of-pure-and-representative-master-variables","page":"Presolve","title":"Updating bounds of pure & representative master variables","text":"","category":"section"},{"location":"api/presolve/","page":"Presolve","title":"Presolve","text":"Consider a pure master variable x^rm pure_j with barx^rm pure_jneq 0 and bounds lb_jub_j before augmenting the partial solution. ","category":"page"},{"location":"api/presolve/","page":"Presolve","title":"Presolve","text":"If barx^rm pure_j 0, then we have lb_jleftarrow 0, ub_jleftarrow ub_j - barx^rm pure_j.","category":"page"},{"location":"api/presolve/","page":"Presolve","title":"Presolve","text":"If barx^rm pure_j 0, then we have lb_jleftarrow lb_j - barx^rm pure_j, ub_jleftarrow 0.","category":"page"},{"location":"api/presolve/","page":"Presolve","title":"Presolve","text":"Consider a representative master variable x^rm repr_j with bounds lb^g_j ub^g_j before augmenting the partial solution. Assume that x^rm repr_j represents exactly one variable x^k_j in subproblem k with bounds lb^l_j ub^l_j before augmenting the partial solution. This assumption should be verified before augmenting the partial solution! For the clarity of presentation, we omit index j for the remainder of this section.","category":"page"},{"location":"api/presolve/","page":"Presolve","title":"Presolve","text":"After augmenting the partial solution, the following inequalities should be satisfied: $ lb^g - \\bar{x}^{\\rm repr}\\leq x^{\\rm repr} \\leq ub^g - \\bar{x}^{\\rm repr}.$","category":"page"},{"location":"api/presolve/","page":"Presolve","title":"Presolve","text":"At the same time, we should have minlb^lcdot L_k lb^lcdot U_kleq x^rm reprleq maxub^lcdot U_k ub^lcdot L_k","category":"page"},{"location":"api/presolve/","page":"Presolve","title":"Presolve","text":"Thus, the following update should be done $ lb^g\\leftarrow \\max\\left{lb^g - \\bar{x}^{\\rm repr},\\; \\min{lb^l\\cdot Lk,\\; lb^l\\cdot Uk}\\right}$ $ ub^g\\leftarrow \\min\\left{ub^g - \\bar{x}^{\\rm repr},\\; \\max{ub^l\\cdot Uk,\\; ub^l\\cdot Lk}\\right}$","category":"page"},{"location":"api/presolve/","page":"Presolve","title":"Presolve","text":"Example 1: 0leq x^kleq 3, 0leq x^rm reprleq 6, L_k=0, U_k=2. Let barx^rm repr=2. Then after augmenting the partial solution, we have $ \\max\\left{-2,\\; 0\\right}\\leq x^{\\rm repr} \\leq \\min\\left{4,\\; 3\\right} \\Rightarrow 0 \\leq x^{\\rm repr} \\leq 3$","category":"page"},{"location":"api/presolve/","page":"Presolve","title":"Presolve","text":"Example 2: 0leq x^kleq 5, 3leq x^rm reprleq 6, L_k=0, U_k=2. Let barx^rm repr=2. Then after augmenting the partial solution, we have $ \\max\\left{1,\\; 0\\right}\\leq x'{\\rm repr} \\leq \\min\\left{4,\\; 5\\right} \\Rightarrow 1 \\leq x'{\\rm repr} \\leq 4$","category":"page"},{"location":"api/presolve/","page":"Presolve","title":"Presolve","text":"Example 3: -1leq x^kleq 4, -2leq x^rm reprleq 2, L_k=0, U_k=2. Let barx^rm repr=-1. Then after augmenting the partial solution, we have $ \\max\\left{-1,\\; -1\\right}\\leq x^{\\rm repr} \\leq \\min\\left{3,\\; 4\\right} \\Rightarrow -1 \\leq x^{\\rm repr} \\leq 3$","category":"page"},{"location":"api/presolve/","page":"Presolve","title":"Presolve","text":"","category":"page"},{"location":"#Introduction","page":"Introduction","title":"Introduction","text":"","category":"section"},{"location":"","page":"Introduction","title":"Introduction","text":"warning: Warning\nWe assume that readers are familiar with integer programming and exact optimization methods.Coluna is under active development. Some features are undocumented because they are very experimental. Current users are expected to read the source code.","category":"page"},{"location":"","page":"Introduction","title":"Introduction","text":"Coluna is a framework written in Julia to implement a decomposition approach to optimize block-structured mixed-integer programs (MIP). ","category":"page"},{"location":"","page":"Introduction","title":"Introduction","text":"Coluna relies on the tools of the JuMP-dev community at both ends of the problem treatment. It uses the JuMP modeling language upfront and MathOptInterface (MOI) to delegate master and subproblems to MIP solvers. ","category":"page"},{"location":"","page":"Introduction","title":"Introduction","text":"The user introduces an original MIP that models his problem using the JuMP along our specific extension BlockDecomposition that offers a syntax to specify the problem decomposition. Coluna reformulates the original MIP using Dantzig-Wolfe and Benders decomposition techniques. Then, Coluna optimizes the reformulation using the algorithm chosen by the user.","category":"page"},{"location":"","page":"Introduction","title":"Introduction","text":"Coluna offers a \"black-box\" implementation of the branch-and-cut-and-price algorithm:","category":"page"},{"location":"","page":"Introduction","title":"Introduction","text":"The input is the set of constraints and variables of the MIP in its natural/compact formulation (formulated with JuMP or MOI). \nBlockDecomposition allows the user to provide Coluna with his decomposition of the model. The BlockDecomposition syntax allows the user to implicitly define subsystems in the MIP on which the decomposition is based. These subsystems are described by rows and/or columns indices.\nThe reformulation associated with the decomposition defined by the user is automatically generated by Coluna, without requiring any input from the user to define master columns, their reduced cost, pricing/separation problem, or Lagrangian bound.\nA default column (and cut) generation procedure is implemented. It relies on underlying MOI optimizers to handle master and subproblems. However, the user can use pricing callbacks to solve the subproblems.\nA branching scheme that preserves the pricing problem structure is offered by default; it runs based on priorities and directives specified by the user on the original variables.","category":"page"},{"location":"#Installation","page":"Introduction","title":"Installation","text":"","category":"section"},{"location":"","page":"Introduction","title":"Introduction","text":"Coluna is a package for Julia 1.6+.","category":"page"},{"location":"","page":"Introduction","title":"Introduction","text":"It requires JuMP to model the problem, BlockDecomposition to define the decomposition, and a MIP solver supported by MathOptInterface to optimize the master and the subproblems.","category":"page"},{"location":"","page":"Introduction","title":"Introduction","text":"You can install Coluna and its dependencies through the package manager of Julia by entering :","category":"page"},{"location":"","page":"Introduction","title":"Introduction","text":"] add Coluna","category":"page"},{"location":"#Acknowledgements","page":"Introduction","title":"Acknowledgements","text":"","category":"section"},{"location":"","page":"Introduction","title":"Introduction","text":"Atoptima, Mathematical Optimization Society (MOS), Région Nouvelle-Aquitaine, University of Bordeaux, and Inria","category":"page"},{"location":"","page":"Introduction","title":"Introduction","text":"","category":"page"}] +} diff --git a/previews/PR1107/siteinfo.js b/previews/PR1107/siteinfo.js new file mode 100644 index 000000000..44f2dab52 --- /dev/null +++ b/previews/PR1107/siteinfo.js @@ -0,0 +1 @@ +var DOCUMENTER_CURRENT_VERSION = "previews/PR1107"; diff --git a/previews/PR1107/start/2echelons_VRP.jl b/previews/PR1107/start/2echelons_VRP.jl new file mode 100644 index 000000000..30404ce4c --- /dev/null +++ b/previews/PR1107/start/2echelons_VRP.jl @@ -0,0 +1 @@ +TODO \ No newline at end of file diff --git a/previews/PR1107/start/advanced_demo.jl b/previews/PR1107/start/advanced_demo.jl new file mode 100644 index 000000000..47f9d25be --- /dev/null +++ b/previews/PR1107/start/advanced_demo.jl @@ -0,0 +1,932 @@ +# # Advanced tutorial - Location Routing + +# We demonstrate the main features of Coluna on a variant of the Location Routing problem. +# In the Location Routing Problem, we are given a set of facilities and a set of customers. +# Each customer must be delivered by a route starting from one facility. Each facility has +# a setup cost, while the cost of a route is the distance traveled. + +# A route is defined as a vector of locations that satisfies the following rules: +# - it must start from an open facility location +# - it can finish at any customer (open route variant) +# - its length is limited (the maximum number of visited locations is equal to a constant `nb_positions`) + +# Our objective is to minimize the sum of fixed costs for opening facilities and the total traveled distance +# while ensuring that each customer is covered by a route. + + +# In this tutorial, we will show you how to solve this problem by applying: +# - a direct approach with JuMP and a MILP solver (without Coluna) +# - a branch-and-price algorithm provided by Coluna, which uses a custom pricing callback to optimize pricing subproblems +# - a robust branch-cut-and-price algorithm, which separates valid inequalities on the original arc variables (so-called "robust" cuts) +# - a non-robust branch-cut-and-price algorithm, which separates valid inequalities on the route variables of the Dantzig-Wolfe reformulation (so-called "non-robust" cuts) +# - a multi-stage column generation algorithm using two different pricing solvers +# - a classic Benders decomposition approach, which uses the LP relaxation of the subproblem + +# For illustration purposes, we use a small instance with 2 facilities and 7 customers. +# The maximum length of a route is fixed to 4. +# We also provide a larger instance in the last section of the tutorial. + +nb_positions = 4 +facilities_fixed_costs = [120, 150] +facilities = [1, 2] +customers = [3, 4, 5, 6, 7, 8, 9] +arc_costs = + [ + 0.0 25.3 25.4 25.4 35.4 37.4 31.9 24.6 34.2; + 25.3 0.0 21.2 16.2 27.1 26.8 17.8 16.7 23.2; + 25.4 21.2 0.0 14.2 23.4 23.8 18.3 17.0 21.6; + 25.4 16.2 14.2 0.0 28.6 28.8 22.6 15.6 29.5; + 35.4 27.1 23.4 28.6 0.0 42.1 30.4 24.9 39.1; + 37.4 26.8 23.8 28.8 42.1 0.0 32.4 29.5 38.2; + 31.9 17.8 18.3 22.6 30.4 32.4 0.0 22.5 30.7; + 24.6 16.7 17.0 15.6 24.9 29.5 22.5 0.0 21.4; + 34.2 23.2 21.6 29.5 39.1 38.2 30.7 21.4 0.0; + ] +locations = vcat(facilities, customers) +nb_customers = length(customers) +nb_facilities = length(facilities) +positions = 1:nb_positions; + +# In this tutorial, we will use the following packages: + +using JuMP, HiGHS, GLPK, BlockDecomposition, Coluna; + +# We want to set an upper bound `nb_routes_per_facility` on the number of routes starting from a facility. +# This limit is computed as follows: + +## We compute the minimum number of routes needed to visit all customers: +nb_routes = Int(ceil(nb_customers / nb_positions)) +## We define the upper bound `nb_routes_per_facility`: +nb_routes_per_facility = min(Int(ceil(nb_routes / nb_facilities)) * 2, nb_routes) +routes_per_facility = 1:nb_routes_per_facility; + +# ## Direct model + +# First, we solve the problem by a direct approach, using the HiGHS solver. +# We start by creating a JuMP model: + +model = JuMP.Model(HiGHS.Optimizer); + +# We declare 3 types of binary variables: + +## y[j] equals 1 if facility j is open; 0 otherwise. +@variable(model, y[j in facilities], Bin) + +## z[u,v] equals 1 if a vehicle travels from u to v; 0 otherwise +@variable(model, z[u in locations, v in locations], Bin) + +## x[i,j,k,p] equals 1 if customer i is delivered from facility j at position p of route k; 0 otherwise +@variable(model, x[i in customers, j in facilities, k in routes_per_facility, p in positions], Bin); + + +# We define the constraints: + +## each customer is visited once +@constraint(model, cov[i in customers], + sum(x[i, j, k, p] for j in facilities, k in routes_per_facility, p in positions) == 1) + +## a facility is open if there is a route starting from it +@constraint(model, setup[j in facilities, k in routes_per_facility], + sum(x[i, j, k, 1] for i in customers) <= y[j]) + +## flow conservation +@constraint(model, flow_conservation[j in facilities, k in routes_per_facility, p in positions; p > 1], + sum(x[i, j, k, p] for i in customers) <= sum(x[i, j, k, p-1] for i in customers)) + +## there is an arc between two customers whose demand is satisfied by the same route at consecutive positions +@constraint(model, route_arc[i in customers, l in customers, j in facilities, k in routes_per_facility, p in positions; p > 1 && i != l], + z[i, l] >= x[l, j, k, p] + x[i, j, k, p-1] - 1) + +## there is an arc between facility `j` and the first customer visited by route `k` from facility `j` +@constraint(model, start_arc[i in customers, j in facilities, k in routes_per_facility], + z[j, i] >= x[i, j, k, 1]); + +# We set the objective function: + +@objective(model, Min, + sum(arc_costs[u, v] * z[u, v] for u in locations, v in locations) + + + sum(facilities_fixed_costs[j] * y[j] for j in facilities)); + +# and we optimize the model: + +optimize!(model) +objective_value(model) + +# We find an optimal solution involving two routes starting from facility 1: +# - `1` -> `8` -> `9` -> `3` -> `6` +# - `1` -> `4` -> `5` -> `7` + +# ## Dantzig-Wolfe decomposition and Branch-and-Price + +# One can solve the problem by exploiting its structure with a Dantzig-Wolfe decomposition approach. +# The subproblem induced by such decomposition amounts to generate routes starting from each facility. +# A possible decomposition is to consider a subproblem associated with each vehicle, generating the vehicle route. +# However, for a given facility, the vehicles that are identical will give rise to the same subproblem and route solutions. +# So instead of this decomposition with several identical subproblems for each facility, we define below a single subproblem per facility. +# For each subproblem, we define its multiplicity, i.e. we bound the number of solutions of this subproblem that can be used in a master solution. + +# The following method creates the model according to the decomposition described: +function create_model(optimizer, pricing_algorithms) + ## A user should resort to axes to communicate to Coluna how to decompose a formulation. + ## For our problem, we declare an axis over the facilities, thus `facilities_axis` contain subproblem indices. + ## We must use `facilities_axis` instead of `facilities` in the declaration of the + ## variables and constraints that belong to pricing subproblems. + @axis(facilities_axis, collect(facilities)) + + ## We declare a `BlockModel` instead of `Model`. + model = BlockModel(optimizer) + + ## `y[j]` is a master variable equal to 1 if the facility j is open; 0 otherwise + @variable(model, y[j in facilities], Bin) + + ## `x[i,j]` is a subproblem variable equal to 1 if customer i is delivered from facility j; 0 otherwise. + @variable(model, x[i in customers, j in facilities_axis], Bin) + ## `z[u,v]` is assimilated to a subproblem variable equal to 1 if a vehicle travels from u to v; 0 otherwise. + ## we don't use the `facilities_axis` axis here because the `z` variables are defined as + ## representatives of the subproblem variables later in the model. + @variable(model, z[u in locations, v in locations], Bin) + + ## `cov` constraints are master constraints ensuring that each customer is visited once. + @constraint(model, cov[i in customers], + sum(x[i, j] for j in facilities) >= 1) + + ## `open_facilities` are master constraints ensuring that the depot is open if one vehicle + ## leaves it. + @constraint(model, open_facility[j in facilities], + sum(z[j, i] for i in customers) <= y[j] * nb_routes_per_facility) + + ## We don't need to describe the subproblem constraints because we use a pricing callback. + + ## We set the objective function: + @objective(model, Min, + sum(arc_costs[u, v] * z[u, v] for u in locations, v in locations) + + sum(facilities_fixed_costs[j] * y[j] for j in facilities) + ) + + ## We perform decomposition over the facilities. + @dantzig_wolfe_decomposition(model, dec, facilities_axis) + + ## Subproblems generate routes starting from each facility. + ## The number of routes from each facility is at most `nb_routes_per_facility`. + subproblems = BlockDecomposition.getsubproblems(dec) + specify!.(subproblems, lower_multiplicity=0, upper_multiplicity=nb_routes_per_facility, solver=pricing_algorithms) + + ## We define `z` as a subproblem variable common to all subproblems. + ## Each implicit variable `z` replaces a sum of explicit `z'` variables: `z[u,v] = sum(z'[j,u,v] for j in facilities_axis)` + ## This way the model is simplified, and column generation is accelerated as the reduced cost for pair `z[u,v]` is calculated only once + ## instead of performing the same reduced cost calculation for variables `z'[j,u,v]`, `j in facilities_axis`. + subproblemrepresentative.(z, Ref(subproblems)) + + return model, x, y, z, cov +end; + +# Contrary to the direct model, we do not add constraints to ensure the +# feasibility of the routes because we solve our subproblems in a pricing callback. +# The user who implements the pricing callback has the responsibility to create only feasible routes. + +# We setup Coluna: + +coluna = optimizer_with_attributes( + Coluna.Optimizer, + "params" => Coluna.Params( + solver=Coluna.Algorithm.TreeSearchAlgorithm( ## default branch-and-bound of Coluna + maxnumnodes=100, + conqueralg=Coluna.ColCutGenConquer() ## default column and cut generation of Coluna + ) ## default branch-cut-and-price + ), + "default_optimizer" => GLPK.Optimizer # GLPK for the master & the subproblems +); + +# ### Pricing callback + +# If the user declares all the necessary subproblem constraints and possibly additional subproblem variables +# to describe the set of feasible subproblem solutions, Coluna may perform automatic Dantzig-Wolfe +# decomposition in which the pricing subproblems are solved by applying a (default) MIP solver. +# In our case, applying a MIP solver is not the most efficient way to solve the pricing problem. +# Therefore, we implement an ad-hoc algorithm for solving the pricing subproblems and declare it as a pricing callback. +# In our pricing callback for a given facility, we inspect all feasible routes enumerated before calling the branch-cut-and-price algorithm. +# The inspection algorithm calculates the reduced cost for each enumerated route and returns a route with the minimum reduced cost. + +# We first define a structure to store the routes: +mutable struct Route + length::Int # record the length of the route (number of visited customers + 1) + path::Vector{Int} # record the sequence of visited customers +end; + +# We can reduce the number of enumerated routes by exploiting the following property. +# Consider two routes starting from the same facility and visiting the same subset of locations (customers). +# These two routes correspond to columns with the same vector of coefficients in master constraints. +# A solution containing the route with a larger traveled distance (i.e., larger route original cost) is dominated: +# this dominated route can be replaced by the other route without increasing the total solution cost. +# Therefore, for each subset of locations of a size not exceeding the maximum one, +# the enumeration procedure keeps only one route visiting this subset, the one with the smallest cost. + +# A method that computes the cost of a route: +function route_original_cost(arc_costs, route::Route) + route_cost = 0.0 + path = route.path + path_length = route.length + for i in 1:(path_length-1) + route_cost += arc_costs[path[i], path[i+1]] + end + return route_cost +end; + +# This procedure finds a least-cost sequence of visiting the given set of customers starting from a given facility. + +function best_visit_sequence(arc_costs, cust_subset, facility_id) + ## generate all the possible visit orders + set_size = size(cust_subset)[1] + all_paths = collect(multiset_permutations(cust_subset, set_size)) + all_routes = Vector{Route}() + for path in all_paths + ## add the first index i.e. the facility id + enpath = vcat([facility_id], path) + ## length of the route = 1 + number of visited customers + route = Route(set_size + 1, enpath) + push!(all_routes, route) + end + ## compute each route original cost + routes_costs = map(r -> + (r, route_original_cost(arc_costs, r)), all_routes) + ## keep only the best visit sequence + tmp = argmin([c for (_, c) in routes_costs]) + (best_order, _) = routes_costs[tmp] + return best_order +end; + +# We are now able to compute a dominating route for all the possible customers' subsets, +# given a facility id: + +using Combinatorics + +function best_route_forall_cust_subsets(arc_costs, customers, facility_id, max_size) + best_routes = Vector{Route}() + all_subsets = Vector{Vector{Int}}() + for subset_size in 1:max_size + subsets = collect(combinations(customers, subset_size)) + for s in subsets + push!(all_subsets, s) + end + end + for s in all_subsets + route_s = best_visit_sequence(arc_costs, s, facility_id) + push!(best_routes, route_s) + end + return best_routes +end; + +# We store all the information given by the enumeration phase in a dictionary. +# For each facility id, we match a vector of routes that are the best visiting sequences +# for each possible subset of customers. + +routes_per_facility = Dict( + j => best_route_forall_cust_subsets(arc_costs, customers, j, nb_positions) for j in facilities +) + +# Our pricing callback must compute the reduced cost of each route, +# given the reduced cost of the subproblem variables `x` and `z`. +# Remember that subproblem variables `z` are implicitly defined by master representative variables `z`. +# We remark that `z` variables participate only in the objective function. +# Thus their reduced costs are initially equal to the original costs (i.e., objective coefficients) +# This is not true anymore after adding branching constraints and robust cuts involving variables `z`. + +# We need methods to compute the contributions to the reduced cost of the `x` and `z` variables: + +function x_contribution(route::Route, j::Int, x_red_costs) + x = 0.0 + visited_customers = route.path[2:route.length] + for i in visited_customers + x += x_red_costs["x_$(i)_$(j)"] + end + return x +end; + +function z_contribution(route::Route, z_red_costs) + z = 0.0 + for i in 1:(route.length-1) + current_position = route.path[i] + next_position = route.path[i+1] + z += z_red_costs["z_$(current_position)_$(next_position)"] + end + return z +end; + +# We are now able to write our pricing callback: + +function pricing_callback(cbdata) + ## Get the id of the facility. + j = BlockDecomposition.indice(BlockDecomposition.callback_spid(cbdata, model)) + + ## Retrieve variables reduced costs. + z_red_costs = Dict( + "z_$(u)_$(v)" => BlockDecomposition.callback_reduced_cost(cbdata, z[u, v]) for u in locations, v in locations) + x_red_costs = Dict( + "x_$(i)_$(j)" => BlockDecomposition.callback_reduced_cost(cbdata, x[i, j]) for i in customers + ) + + ## Keep route with minimum reduced cost. + red_costs_j = map(r -> ( + r, + x_contribution(r, j, x_red_costs) + z_contribution(r, z_red_costs) # the reduced cost of a route is the sum of the contribution of the variables + ), routes_per_facility[j] + ) + min_index = argmin([x for (_, x) in red_costs_j]) + (best_route, min_reduced_cost) = red_costs_j[min_index] + + ## Retrieve the route's arcs. + best_route_arcs = Vector{Tuple{Int,Int}}() + for i in 1:(best_route.length-1) + push!(best_route_arcs, (best_route.path[i], best_route.path[i+1])) + end + best_route_customers = best_route.path[2:best_route.length] + + ## Create the solution (send only variables with non-zero values). + z_vars = [z[u, v] for (u, v) in best_route_arcs] + x_vars = [x[i, j] for i in best_route_customers] + sol_vars = vcat(z_vars, x_vars) + sol_vals = ones(Float64, length(z_vars) + length(x_vars)) + sol_cost = min_reduced_cost + + ## Submit the solution to the subproblem to Coluna. + MOI.submit(model, BlockDecomposition.PricingSolution(cbdata), sol_cost, sol_vars, sol_vals) + + ## Submit the dual bound to the solution of the subproblem. + ## This bound is used to compute the contribution of the subproblem to the lagrangian + ## bound in column generation. + MOI.submit(model, BlockDecomposition.PricingDualBound(cbdata), sol_cost) ## optimal solution + +end; + +# Create the model: +model, x, y, z, _ = create_model(coluna, pricing_callback); + +# Solve: +JuMP.optimize!(model) + + +# ### Strengthening the master with linear valid inequalities on the original variables (so-called "robust" cuts) + +# To improve the quality of the linear relaxation, a family of classic facility location valid inequalities can be used: +# +# ```math +# x_{ij} \leq y_j\; \forall i \in I, \forall j \in J +# ``` +# where $I$ is the set of customers and $J$ the set of facilities. + +# We declare a structure representing an inequality in this family: +struct OpenFacilityInequality + facility_id::Int + customer_id::Int +end + +# To identify violated valid inequalities from a current master LP solution, +# we proceed by enumeration (i.e. iterating over all pairs of customer and facility). +# Enumeration separation procedure is implemented in the following callback. + +function valid_inequalities_callback(cbdata) + ## Get variables valuations and store them in dictionaries. + x_vals = Dict( + "x_$(i)_$(j)" => BlockDecomposition.callback_value(cbdata, x[i, j]) for i in customers, j in facilities + ) + y_vals = Dict( + "y_$(j)" => BlockDecomposition.callback_value(cbdata, y[j]) for j in facilities + ) + + ## Separate the valid inequalities (i.e. retrieve the inequalities that are violated by + ## the current solution) by enumeration. + inequalities = OpenFacilityInequality[] + + for j in facilities + y_j = y_vals["y_$(j)"] + for i in customers + x_i_j = x_vals["x_$(i)_$(j)"] + if x_i_j > y_j + push!(inequalities, OpenFacilityInequality(j, i)) + end + end + end + + ## Add the valid inequalities to the model. + for ineq in inequalities + constr = JuMP.@build_constraint(x[ineq.customer_id, ineq.facility_id] <= y[ineq.facility_id]) + MOI.submit(model, MOI.UserCut(cbdata), constr) + end +end; + +# We re-declare the model and optimize it with these valid inequalities: +model, x, y, z, _ = create_model(coluna, pricing_callback); +MOI.set(model, MOI.UserCutCallback(), valid_inequalities_callback); +JuMP.optimize!(model) + + +# ### Strengthening the master with valid inequalities on the column generation variables (so-called "non-robust" cuts) + +# In order to further strengthen the linear relaxation of the Dantzig-Wolfe reformulation, +# we separate a family of subset-row cuts, which is a subfamily of Chvátal-Gomory rank-1 cuts (R1C), +# obtained from the set-partitioning constraints. +# These cuts cannot be expressed as a linear combination of the original variables of the model. +# Instead, they are expressed with the master columns variables $λ_k$, $k \in K$, where $K$ is the set of generated columns +# or set of solutions returned by the pricing subproblems. +# Subset-row cuts are "non-robust" in the sense that they modify the structure of the pricing subproblems, +# and not just the reduced cost of subproblem variables. Thus, the implementation of the pricing callback should +# be updated to take into account dual costs associated with non-robust cutting planes. + +# Each Chvátal-Gomory rank-1 cut is characterized by a subset of set-partitioning constraints, or equivalently by a subset $C$ of customers, +# and a multiplier $\alpha_i$ for each customer $i\in C$: +# ```math +# \sum_{k \in K} \lfloor \sum_{i \in C} \alpha_i \tilde{x}^k_{i,j} \lambda_{k} \rfloor \leq \lfloor \sum_{i\in C} \alpha_i \rfloor, \; C \subseteq I, +# ``` +# where $\tilde{x}^k_{ij}$ is the value of the variable $x_{ij}$ in the $k$-th column generated. +# For subset-row cuts, $|C|=3$, and $\alpha_i=\frac{1}{2}$, $i\in C$. + +# Since we obtain subset-row cuts based on set-partitioning constraints, we must be able to +# differentiate them from the other constraints of the model. +# To do this, we exploit a feature of Coluna that allows us to attach custom data to the +# constraints and variables of a model, via the add-ons of BlockDecomposition package. + +# First, we create special custom data with the only information we need to characterize +# our cover constraints: the customer id that corresponds to this constraint. +struct CoverConstrData <: BlockDecomposition.AbstractCustomData + customer::Int +end + +# We re-create the model: +(model, x, y, z, cov) = create_model(coluna, pricing_callback); + +# We declare our custom data to Coluna and we attach one custom data to each cover constraint +BlockDecomposition.customconstrs!(model, CoverConstrData); + +for i in customers + customdata!(cov[i], CoverConstrData(i)) +end + +# We perform the separation by enumeration (i.e. iterating over all subsets of customers of size three). + +# The subset-row cut has the following form: +# ```math +# \sum_{k \in K} \tilde{\alpha}(C, k) \lambda_{k} \leq 1\; C \subseteq I, |C| = 3, +# ``` +# where coefficient $\tilde{\alpha}(C, k)$ equals $1$ if route $k$ visits at least two customers of $C$; $0$ otherwise. + +# For instance, if we consider separating a cut over constraints `cov[3]`, `cov[6]` and `cov[8]`, +# then the route `1`->`4`->`6`->`7` has a zero coefficient while the route `1`->`4`->`6`->`3` +# has a coefficient equal to one. + +# Since columns are generated dynamically, we cannot pre-compute the coefficients of columns in the subset-row cuts. +# Instead, coefficients are computed dynamically via a user-defined `computecoeff` method which takes +# a cut and a column as arguments. To recognize which cut and which column are passed to the method, +# custom data structures are attached to the cut constraints and the master variables. +# When a new column is generated, Coluna computes its coefficients in the original constraints and robust cuts +# using coefficients of subproblem variables in the master constraints. +# Coluna retrieves coefficients of the new column in the non-robust cuts by calling the `computecoeff` method for the column and each such cut. +# When a new non-robust cut is generated, Coluna retrieves the coefficients of columns in this cut by calling the `computecoeff` method for the cut and all existing columns. + +# We now proceed to the implementation of necessary data structures and methods needed to support the subset-row cuts. +# First, we attach a custom data structure to master columns `λ_k` associated with a given route `k`. +# They record the set of customers that are visited by the given route `k`. + +# Thus, to each `λ_k`, we associate a `R1cVarData` structure that carries the customers it visits. +struct R1cVarData <: BlockDecomposition.AbstractCustomData + visited_locations::Vector{Int} +end + +# Then, we attach a `R1cCutData` custom data structure to the subset-row cuts. +# It contains the set $C$ of customers characterizing the cut. +struct R1cCutData <: BlockDecomposition.AbstractCustomData + cov_constrs::Vector{Int} +end + +# We declare our custom data to Coluna via BlockDecomposition add-ons: +BlockDecomposition.customvars!(model, R1cVarData) +BlockDecomposition.customconstrs!(model, [CoverConstrData, R1cCutData]); + +# The next method calculates the coefficients of a column `λ_k` in a subset-row cut: +function Coluna.MathProg.computecoeff( + var_custom_data::R1cVarData, constr_custom_data::R1cCutData +) + return floor(1 / 2 * length(var_custom_data.visited_locations ∩ constr_custom_data.cov_constrs)) +end + +# We also need to define a second method for the case of the cover constraints. +# Indeed, we use custom data to know the customer attached to each cover constraint +# There is no contribution of the non-robust part of the coefficient of the `λ_k`, so +# the method returns 0. +function Coluna.MathProg.computecoeff(::R1cVarData, ::CoverConstrData) + return 0 +end + +# We are now able to write our rank-one cut callback completely: +function r1c_callback(cbdata) + original_sol = cbdata.orig_sol + master = Coluna.MathProg.getmodel(original_sol) + ## Retrieve the cover constraints. + cov_constrs = Int[] + for constr in values(Coluna.MathProg.getconstrs(master)) + constr_custom_data = Coluna.MathProg.getcustomdata(master, constr) + if typeof(constr_custom_data) <: CoverConstrData + push!(cov_constrs, constr_custom_data.customer) + end + end + + ## Retrieve the master columns λ and their values in the current fractional solution + lambdas = Tuple{Float64,Coluna.MathProg.Variable}[] + for (var_id, val) in original_sol + if Coluna.MathProg.getduty(var_id) <= Coluna.MathProg.MasterCol + push!(lambdas, (val, Coluna.MathProg.getvar(master, var_id))) + end + end + + ## Separate the valid subset-row cuts violated by the current solution. + ## For a fixed subset of customers of size three, iterate on the master columns + ## and check if lhs > 1: + for cov_constr_subset in collect(combinations(cov_constrs, 3)) + lhs = 0 + for lambda in lambdas + (val, var) = lambda + var_custom_data = Coluna.MathProg.getcustomdata(master, var) + if !isnothing(var_custom_data) + coeff = floor(1 / 2 * length(var_custom_data.visited_locations ∩ cov_constr_subset)) + lhs += coeff * val + end + end + if lhs > 1 + ## Create the constraint and add it to the model. + MOI.submit(model, + MOI.UserCut(cbdata), + JuMP.ScalarConstraint(JuMP.AffExpr(0.0), MOI.LessThan(1.0)), + R1cCutData(cov_constr_subset) + ) + end + end +end; + +# When creating non-robust constraints, only the linear (i.e., robust) part is passed to the model. +# In our case, the constraint `0 <= 1` is passed. +# As explained above, the non-robust part is computed by calling the `computecoeff` method using +# the structure of type `R1cCutData` provided. + +# Finally, we need to update our pricing callback to take into account the active non-robust cuts. +# The contribution of these cuts to the reduced cost of a column is not captured by the reduced cost +# of subproblem variables. We must therefore take this contribution into account manually, by inquiring +# the set of existing non-robust cuts and their values in the current dual solution. + +# The contribution of a subset-row cut to the reduced cost of a route is managed by the following method: +function r1c_contrib(route::Route, custduals) + cost = 0 + if !isempty(custduals) + for (r1c_cov_constrs, dual) in custduals + coeff = floor(1 / 2 * length(route.path ∩ r1c_cov_constrs)) + cost += coeff * dual + end + end + return cost +end; + +# We re-write our pricing callback to: +# - retrieve the dual cost of the subset-row cuts +# - take into account the contribution of the subset-row cuts in the reduced cost of the route +# - attach custom data to the route so that its coefficient in the existing non-robust cuts can be computed +function pricing_callback(cbdata) + j = BlockDecomposition.indice(BlockDecomposition.callback_spid(cbdata, model)) + z_red_costs = Dict( + "z_$(u)_$(v)" => BlockDecomposition.callback_reduced_cost(cbdata, z[u, v]) for u in locations, v in locations + ) + x_red_costs = Dict( + "x_$(i)_$(j)" => BlockDecomposition.callback_reduced_cost(cbdata, x[i, j]) for i in customers + ) + + ## FIRST CHANGE HERE: + ## Get the dual values of the constraints of the specific type to compute the contributions of + ## non-robust cuts to the cost of the solution: + master = cbdata.form.parent_formulation + custduals = Tuple{Vector{Int},Float64}[] + for (_, constr) in Coluna.MathProg.getconstrs(master) + constr_custom_data = Coluna.MathProg.getcustomdata(master, constr) + if typeof(constr_custom_data) == R1cCutData + push!(custduals, ( + constr_custom_data.cov_constrs, + Coluna.MathProg.getcurincval(master, constr) + )) + end + end + ## END OF FIRST CHANGE + + ## SECOND CHANGE HERE: + ## Keep route with the minimum reduced cost: contribution of the subproblem variables and + ## the non-robust cuts. + red_costs_j = map(r -> ( + r, + x_contribution(r, j, x_red_costs) + z_contribution(r, z_red_costs) - r1c_contrib(r, custduals) + ), routes_per_facility[j] + ) + ## END OF SECOND CHANGE + min_index = argmin([x for (_, x) in red_costs_j]) + best_route, min_reduced_cost = red_costs_j[min_index] + + best_route_arcs = Tuple{Int,Int}[] + for i in 1:(best_route.length-1) + push!(best_route_arcs, (best_route.path[i], best_route.path[i+1])) + end + best_route_customers = best_route.path[2:best_route.length] + z_vars = [z[u, v] for (u, v) in best_route_arcs] + x_vars = [x[i, j] for i in best_route_customers] + sol_vars = vcat(z_vars, x_vars) + sol_vals = ones(Float64, length(z_vars) + length(x_vars)) + sol_cost = min_reduced_cost + + ## Submit the solution of the subproblem to Coluna + ## THIRD CHANGE HERE: + ## You must attach the visited customers in the structure of type `R1cVarData` to the solution of the subproblem + MOI.submit( + model, BlockDecomposition.PricingSolution(cbdata), sol_cost, sol_vars, sol_vals, + R1cVarData(best_route.path) + ) + ## END OF THIRD CHANGE + MOI.submit(model, BlockDecomposition.PricingDualBound(cbdata), sol_cost) +end + +MOI.set(model, MOI.UserCutCallback(), r1c_callback); +JuMP.optimize!(model) + +# ### Multi-stage pricing callback + +# In this section, we implement a pricing heuristic that can be used together with the exact +# pricing callback to generate subproblems solutions. + +# The idea of the heuristic is very simple: + +# - Given a facility `j`, the heuristic finds the closest customer to `j` and adds it to the route. +# - Then, while the reduced cost keeps improving and the maximum length of the route is not reached, the heuristic computes and adds to the route the nearest neighbor to the last customer of the route. + +# We first define an auxiliary function used to compute the route tail's nearest neighbor at each step: +function add_nearest_neighbor(route::Route, customers, costs) + ## Get the last customer of the route. + loc = last(route.path) + ## Initialize its nearest neighbor to zero and mincost to infinity. + (nearest, mincost) = (0, Inf) + ## Compute nearest and mincost. + for i in customers + if !(i in route.path) # implying in particular (i != loc) + if (costs[loc, i] < mincost) + nearest = i + mincost = costs[loc, i] + end + end + end + ## Add the last customer's nearest neighbor to the route. + if nearest != 0 + push!(route.path, nearest) + route.length += 1 + end +end; + +# We then define our heuristic for the enumeration of the routes, the method returns the best route found by the heuristic together with its cost: +function enumeration_heuristic(x_red_costs, z_red_costs, j) + ## Initialize our "greedy best route". + best_route = Route(1, [j]) + ## Initialize the route's cost to zero. + current_redcost = 0.0 + old_redcost = Inf + + ## main loop + while (current_redcost < old_redcost) + add_nearest_neighbor(best_route, customers, arc_costs) + old_redcost = current_redcost + current_redcost = x_contribution(best_route, j, x_red_costs) + + z_contribution(best_route, z_red_costs) + ## Max length is reached. + if best_route.length == nb_positions + break + end + end + return (best_route, current_redcost) +end; + +# We can now define our heuristic pricing callback: +function approx_pricing(cbdata) + + j = BlockDecomposition.indice(BlockDecomposition.callback_spid(cbdata, model)) + z_red_costs = Dict( + "z_$(u)_$(v)" => BlockDecomposition.callback_reduced_cost(cbdata, z[u, v]) for u in locations, v in locations + ) + x_red_costs = Dict( + "x_$(i)_$(j)" => BlockDecomposition.callback_reduced_cost(cbdata, x[i, j]) for i in customers + ) + + ## Call the heuristic to elect the "greedy best route": + best_route, sol_cost = enumeration_heuristic(x_red_costs, z_red_costs, j) + + ## Build the solution: + best_route_arcs = Vector{Tuple{Int,Int}}() + for i in 1:(best_route.length-1) + push!(best_route_arcs, (best_route.path[i], best_route.path[i+1])) + end + best_route_customers = best_route.path[2:length(best_route.path)] + + z_vars = [z[u, v] for (u, v) in best_route_arcs] + x_vars = [x[i, j] for i in best_route_customers] + sol_vars = vcat(z_vars, x_vars) + sol_vals = ones(Float64, length(z_vars) + length(x_vars)) + + MOI.submit(model, BlockDecomposition.PricingSolution(cbdata), sol_cost, sol_vars, sol_vals) + ## As the procedure is inexact, no dual bound can be computed, we set it to -Inf. + MOI.submit(model, BlockDecomposition.PricingDualBound(cbdata), -Inf) +end; + +# We set the solver; `colgen_stages_pricing_solvers` indicates which solver to use first (here it is `approx_pricing`) +coluna = JuMP.optimizer_with_attributes( + Coluna.Optimizer, + "default_optimizer" => GLPK.Optimizer, + "params" => Coluna.Params( + solver=Coluna.Algorithm.BranchCutAndPriceAlgorithm( + maxnumnodes=100, + colgen_stages_pricing_solvers=[2, 1] + ) + ) +); +# We add the two pricing algorithms to our model: +model, x, y, z, cov = create_model(coluna, [approx_pricing, pricing_callback]); +# We declare our custom data to Coluna: +BlockDecomposition.customvars!(model, R1cVarData) +BlockDecomposition.customconstrs!(model, [CoverConstrData, R1cCutData]); +for i in customers + customdata!(cov[i], CoverConstrData(i)) +end + +# Optimize: +JuMP.optimize!(model) + + +# ## Benders decomposition + +# In this section, we show how one can solve the linear relaxation of the master program of +# a Benders Decomposition approach to this facility location demo problem. + +# The first-stage decisions consist in choosing a subset of facilities to open. +# The second-stage decisions consist in choosing the routes that are assigned to each facility. +# The second stage problem is an integer program, so for simplicity, we use its linear relaxation instead. To improve the quality of this +# relaxation, we enumerate the routes and use one variable per route. As this approach is practical only for small instances, +# we use it only for illustration purposes. For larger instances, we would have to implement a column generation approach +# to solve the subproblem, i.e., the Benders cut separation problem. + +# In the same spirit as the above models, we use the variables. +# Let `y[j]` equal 1 if the facility `j` is open and 0 otherwise. +# Let `λ[j,k]` equal 1 if route `k` starting from facility `j` is selected and 0 otherwise. + +# Since there is only one subproblem in the second stage, we introduce a fake axis that contains +# only one element. This approach can be generalized to the case where customer demand uncertainty is expressed with scenarios. +# In this case, we would have one subproblem for each scenario, and the axis would have been defined for the set of scenarios. +# In our case, the set of scenarios consists of one "fake" scenario. + +fake = 1 +@axis(axis, collect(fake:fake)) + +coluna = JuMP.optimizer_with_attributes( + Coluna.Optimizer, + "params" => Coluna.Params(solver=Coluna.Algorithm.BendersCutGeneration() + ), + "default_optimizer" => GLPK.Optimizer +) + +model = BlockModel(coluna); + + +# We introduce auxiliary structures to improve the clarity of the code. + +## routes covering customer i from facility j. +covering_routes = Dict( + (j, i) => findall(r -> (i in r.path), routes_per_facility[j]) for i in customers, j in facilities +); +## routes costs from facility j. +routes_costs = Dict( + j => [route_original_cost(arc_costs, r) for r in routes_per_facility[j]] for j in facilities +); + +# We declare the variables. +@variable(model, 0 <= y[j in facilities] <= 1) ## 1st stage +@variable(model, 0 <= λ[f in axis, j in facilities, k in 1:length(routes_per_facility[j])] <= 1); ## 2nd stage + +# We declare the constraints. + +## Linking constraints +@constraint(model, open[fake in axis, j in facilities, k in 1:length(routes_per_facility[j])], + y[j] >= λ[fake, j, k]) + +## Second-stage constraints +@constraint(model, cover[fake in axis, i in customers], + sum(λ[fake, j, k] for j in facilities, k in covering_routes[(j, i)]) >= 1) + +## Second-stage constraints +@constraint(model, limit_nb_routes[fake in axis, j in facilities], + sum(λ[fake, j, q] for q in 1:length(routes_per_facility[j])) <= nb_routes_per_facility +) + +## First-stage constraint +## This constraint is redundant, we add it in order not to start with an empty master problem +@constraint(model, min_opening, + sum(y[j] for j in facilities) >= 1) + +@objective(model, Min, + sum(facilities_fixed_costs[j] * y[j] for j in facilities) + + sum(routes_costs[j][k] * λ[fake, j, k] for j in facilities, k in 1:length(routes_per_facility[j]))); + +# We perform the decomposition over the axis and we optimize the problem. +@benders_decomposition(model, dec, axis) +JuMP.optimize!(model) + +# ## Example of comparison of the dual bounds + +# In this section, we use a larger instance with 3 facilities and 13 customers. We solve only the root node and look at the dual bound: +# - with the standard column generation (without cut separation) +# - by adding robust cuts +# - by adding non-robust cuts +# - by adding both robust and non-robust cuts + +nb_positions = 6 +facilities_fixed_costs = [120, 150, 110] +facilities = [1, 2, 3] +customers = [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] +arc_costs = [ + 0.0 125.6 148.9 182.2 174.9 126.2 158.6 172.9 127.4 133.1 152.6 183.8 182.4 176.9 120.7 129.5; + 123.6 0.0 175.0 146.7 191.0 130.4 142.5 139.3 130.1 133.3 163.8 127.8 139.3 128.4 186.4 115.6; + 101.5 189.6 0.0 198.2 150.5 159.6 128.3 133.0 195.1 167.3 187.3 178.1 171.7 161.5 142.9 142.1; + 159.4 188.4 124.7 0.0 174.5 174.0 142.6 102.5 135.5 184.4 121.6 112.1 139.9 105.5 190.9 140.7; + 157.7 160.3 184.2 196.1 0.0 115.5 175.2 153.5 137.7 141.3 109.5 107.7 125.3 151.0 133.1 140.6; + 145.2 120.4 106.7 138.8 157.3 0.0 153.6 192.2 153.2 184.4 133.6 164.9 163.6 126.3 121.3 161.4; + 182.6 152.1 178.8 184.1 150.8 163.5 0.0 164.1 104.0 100.5 117.3 156.1 115.1 168.6 186.5 100.2; + 144.9 193.8 146.1 191.4 136.8 172.7 108.1 0.0 131.0 166.3 116.4 187.0 161.3 148.2 162.1 116.0; + 173.4 199.1 132.9 133.2 139.8 112.7 138.1 118.8 0.0 173.4 131.8 180.6 191.0 133.9 178.7 108.7; + 150.5 171.0 163.8 171.5 116.3 149.1 124.0 192.5 188.8 0.0 112.2 188.7 197.3 144.9 110.7 186.6; + 153.6 104.4 141.1 124.7 121.1 137.5 190.3 177.1 194.4 135.3 0.0 146.4 132.7 103.2 150.3 118.4; + 112.5 133.7 187.1 170.0 130.2 177.7 159.2 169.9 183.8 101.6 156.2 0.0 114.7 169.3 149.9 125.3; + 151.5 165.6 162.1 133.4 159.4 200.5 132.7 199.9 136.8 121.3 118.1 123.4 0.0 104.8 197.1 134.4; + 195.0 101.1 194.1 160.1 147.1 164.6 137.2 138.6 166.7 191.2 169.2 186.0 171.2 0.0 106.8 150.9; + 158.2 152.7 104.0 136.0 168.9 175.7 139.2 163.2 102.7 153.3 185.9 164.0 113.2 200.7 0.0 127.4; + 136.6 174.3 103.2 131.4 107.8 191.6 115.1 127.6 163.2 123.2 173.3 133.0 120.5 176.9 173.8 0.0; +] + +locations = vcat(facilities, customers) +nb_customers = length(customers) +nb_facilities = length(facilities) +positions = 1:nb_positions; + +routes_per_facility = Dict( + j => best_route_forall_cust_subsets(arc_costs, customers, j, nb_positions) for j in facilities +); + +# We set `maxnumnodes` to zero to optimize only the root node: +coluna = optimizer_with_attributes( + Coluna.Optimizer, + "params" => Coluna.Params( + solver=Coluna.Algorithm.TreeSearchAlgorithm( + maxnumnodes=0, + conqueralg=Coluna.ColCutGenConquer() + ) + ), + "default_optimizer" => GLPK.Optimizer +); + + +# We define a method to call both `valid_inequalities_callback` and `r1c_callback`: +function cuts_callback(cbdata) + valid_inequalities_callback(cbdata) + r1c_callback(cbdata) +end + +function attach_data(model, cov) + BlockDecomposition.customvars!(model, R1cVarData) + BlockDecomposition.customconstrs!(model, [CoverConstrData, R1cCutData]) + for i in customers + customdata!(cov[i], CoverConstrData(i)) + end +end; + +# First, we solve the root node with the "raw" decomposition model: +model, x, y, z, cov = create_model(coluna, pricing_callback) +attach_data(model, cov) + +# dual bound found after optimization = 1588.00 + +# Then, we re-solve it with the robust cuts: +model, x, y, z, cov = create_model(coluna, pricing_callback) +attach_data(model, cov) +MOI.set(model, MOI.UserCutCallback(), valid_inequalities_callback); + +# dual bound found after optimization = 1591.55 + + +# And with non-robust cuts: +model, x, y, z, cov = create_model(coluna, pricing_callback) +attach_data(model, cov) +MOI.set(model, MOI.UserCutCallback(), r1c_callback); + +# dual bound found after optimization = 1598.26 + +# Finally we add both robust and non-robust cuts: +model, x, y, z, cov = create_model(coluna, pricing_callback) +attach_data(model, cov) +MOI.set(model, MOI.UserCutCallback(), cuts_callback); + +# dual bound found after optimization = 1600.63 diff --git a/previews/PR1107/start/advanced_demo/index.html b/previews/PR1107/start/advanced_demo/index.html new file mode 100644 index 000000000..098c95adb --- /dev/null +++ b/previews/PR1107/start/advanced_demo/index.html @@ -0,0 +1,1485 @@ + +Column Generation and Benders on Location Routing · Coluna.jl

Advanced tutorial - Location Routing

We demonstrate the main features of Coluna on a variant of the Location Routing problem. In the Location Routing Problem, we are given a set of facilities and a set of customers. Each customer must be delivered by a route starting from one facility. Each facility has a setup cost, while the cost of a route is the distance traveled.

A route is defined as a vector of locations that satisfies the following rules:

  • it must start from an open facility location
  • it can finish at any customer (open route variant)
  • its length is limited (the maximum number of visited locations is equal to a constant nb_positions)

Our objective is to minimize the sum of fixed costs for opening facilities and the total traveled distance while ensuring that each customer is covered by a route.

In this tutorial, we will show you how to solve this problem by applying:

  • a direct approach with JuMP and a MILP solver (without Coluna)
  • a branch-and-price algorithm provided by Coluna, which uses a custom pricing callback to optimize pricing subproblems
  • a robust branch-cut-and-price algorithm, which separates valid inequalities on the original arc variables (so-called "robust" cuts)
  • a non-robust branch-cut-and-price algorithm, which separates valid inequalities on the route variables of the Dantzig-Wolfe reformulation (so-called "non-robust" cuts)
  • a multi-stage column generation algorithm using two different pricing solvers
  • a classic Benders decomposition approach, which uses the LP relaxation of the subproblem

For illustration purposes, we use a small instance with 2 facilities and 7 customers. The maximum length of a route is fixed to 4. We also provide a larger instance in the last section of the tutorial.

nb_positions = 4
+facilities_fixed_costs = [120, 150]
+facilities = [1, 2]
+customers = [3, 4, 5, 6, 7, 8, 9]
+arc_costs =
+    [
+        0.0 25.3 25.4 25.4 35.4 37.4 31.9 24.6 34.2;
+        25.3 0.0 21.2 16.2 27.1 26.8 17.8 16.7 23.2;
+        25.4 21.2 0.0 14.2 23.4 23.8 18.3 17.0 21.6;
+        25.4 16.2 14.2 0.0 28.6 28.8 22.6 15.6 29.5;
+        35.4 27.1 23.4 28.6 0.0 42.1 30.4 24.9 39.1;
+        37.4 26.8 23.8 28.8 42.1 0.0 32.4 29.5 38.2;
+        31.9 17.8 18.3 22.6 30.4 32.4 0.0 22.5 30.7;
+        24.6 16.7 17.0 15.6 24.9 29.5 22.5 0.0 21.4;
+        34.2 23.2 21.6 29.5 39.1 38.2 30.7 21.4 0.0;
+    ]
+locations = vcat(facilities, customers)
+nb_customers = length(customers)
+nb_facilities = length(facilities)
+positions = 1:nb_positions;

In this tutorial, we will use the following packages:

using JuMP, HiGHS, GLPK, BlockDecomposition, Coluna;

We want to set an upper bound nb_routes_per_facility on the number of routes starting from a facility. This limit is computed as follows:

# We compute the minimum number of routes needed to visit all customers:
+nb_routes = Int(ceil(nb_customers / nb_positions))
+# We define the upper bound `nb_routes_per_facility`:
+nb_routes_per_facility = min(Int(ceil(nb_routes / nb_facilities)) * 2, nb_routes)
+routes_per_facility = 1:nb_routes_per_facility;

Direct model

First, we solve the problem by a direct approach, using the HiGHS solver. We start by creating a JuMP model:

model = JuMP.Model(HiGHS.Optimizer);

We declare 3 types of binary variables:

# y[j] equals 1 if facility j is open; 0 otherwise.
+@variable(model, y[j in facilities], Bin)
+
+# z[u,v] equals 1 if a vehicle travels from u to v; 0 otherwise
+@variable(model, z[u in locations, v in locations], Bin)
+
+# x[i,j,k,p] equals 1 if customer i is delivered from facility j at position p of route k; 0 otherwise
+@variable(model, x[i in customers, j in facilities, k in routes_per_facility, p in positions], Bin);

We define the constraints:

# each customer is visited once
+@constraint(model, cov[i in customers],
+    sum(x[i, j, k, p] for j in facilities, k in routes_per_facility, p in positions) == 1)
+
+# a facility is open if there is a route starting from it
+@constraint(model, setup[j in facilities, k in routes_per_facility],
+    sum(x[i, j, k, 1] for i in customers) <= y[j])
+
+# flow conservation
+@constraint(model, flow_conservation[j in facilities, k in routes_per_facility, p in positions; p > 1],
+    sum(x[i, j, k, p] for i in customers) <= sum(x[i, j, k, p-1] for i in customers))
+
+# there is an arc between two customers whose demand is satisfied by the same route at consecutive positions
+@constraint(model, route_arc[i in customers, l in customers, j in facilities, k in routes_per_facility, p in positions; p > 1 && i != l],
+    z[i, l] >= x[l, j, k, p] + x[i, j, k, p-1] - 1)
+
+# there is an arc between facility `j` and the first customer visited by route `k` from facility `j`
+@constraint(model, start_arc[i in customers, j in facilities, k in routes_per_facility],
+    z[j, i] >= x[i, j, k, 1]);

We set the objective function:

@objective(model, Min,
+    sum(arc_costs[u, v] * z[u, v] for u in locations, v in locations)
+    +
+    sum(facilities_fixed_costs[j] * y[j] for j in facilities));

and we optimize the model:

optimize!(model)
+objective_value(model)
289.8

We find an optimal solution involving two routes starting from facility 1:

  • 1 -> 8 -> 9 -> 3 -> 6
  • 1 -> 4 -> 5 -> 7

Dantzig-Wolfe decomposition and Branch-and-Price

One can solve the problem by exploiting its structure with a Dantzig-Wolfe decomposition approach. The subproblem induced by such decomposition amounts to generate routes starting from each facility. A possible decomposition is to consider a subproblem associated with each vehicle, generating the vehicle route. However, for a given facility, the vehicles that are identical will give rise to the same subproblem and route solutions. So instead of this decomposition with several identical subproblems for each facility, we define below a single subproblem per facility. For each subproblem, we define its multiplicity, i.e. we bound the number of solutions of this subproblem that can be used in a master solution.

The following method creates the model according to the decomposition described:

function create_model(optimizer, pricing_algorithms)
+    # A user should resort to axes to communicate to Coluna how to decompose a formulation.
+    # For our problem, we declare an axis over the facilities, thus `facilities_axis` contain subproblem indices.
+    # We must use `facilities_axis` instead of `facilities` in the declaration of the
+    # variables and constraints that belong to pricing subproblems.
+    @axis(facilities_axis, collect(facilities))
+
+    # We declare a `BlockModel` instead of `Model`.
+    model = BlockModel(optimizer)
+
+    # `y[j]` is a master variable equal to 1 if the facility j is open; 0 otherwise
+    @variable(model, y[j in facilities], Bin)
+
+    # `x[i,j]` is a subproblem variable equal to 1 if customer i is delivered from facility j; 0 otherwise.
+    @variable(model, x[i in customers, j in facilities_axis], Bin)
+    # `z[u,v]` is assimilated to a subproblem variable equal to 1 if a vehicle travels from u to v; 0 otherwise.
+    # we don't use the `facilities_axis` axis here because the `z` variables are defined as
+    # representatives of the subproblem variables later in the model.
+    @variable(model, z[u in locations, v in locations], Bin)
+
+    # `cov` constraints are master constraints ensuring that each customer is visited once.
+    @constraint(model, cov[i in customers],
+        sum(x[i, j] for j in facilities) >= 1)
+
+    # `open_facilities` are master constraints ensuring that the depot is open if one vehicle
+    # leaves it.
+    @constraint(model, open_facility[j in facilities],
+        sum(z[j, i] for i in customers) <= y[j] * nb_routes_per_facility)
+
+    # We don't need to describe the subproblem constraints because we use a pricing callback.
+
+    # We set the objective function:
+    @objective(model, Min,
+        sum(arc_costs[u, v] * z[u, v] for u in locations, v in locations) +
+        sum(facilities_fixed_costs[j] * y[j] for j in facilities)
+    )
+
+    # We perform decomposition over the facilities.
+    @dantzig_wolfe_decomposition(model, dec, facilities_axis)
+
+    # Subproblems generate routes starting from each facility.
+    # The number of routes from each facility is at most `nb_routes_per_facility`.
+    subproblems = BlockDecomposition.getsubproblems(dec)
+    specify!.(subproblems, lower_multiplicity=0, upper_multiplicity=nb_routes_per_facility, solver=pricing_algorithms)
+
+    # We define `z` as a subproblem variable common to all subproblems.
+    # Each implicit variable `z` replaces a sum of explicit `z'` variables: `z[u,v] = sum(z'[j,u,v] for j in facilities_axis)`
+    # This way the model is simplified, and column generation is accelerated as the reduced cost for pair `z[u,v]` is calculated only once
+    # instead of performing the same reduced cost calculation for variables `z'[j,u,v]`, `j in facilities_axis`.
+    subproblemrepresentative.(z, Ref(subproblems))
+
+    return model, x, y, z, cov
+end;

Contrary to the direct model, we do not add constraints to ensure the feasibility of the routes because we solve our subproblems in a pricing callback. The user who implements the pricing callback has the responsibility to create only feasible routes.

We setup Coluna:

coluna = optimizer_with_attributes(
+    Coluna.Optimizer,
+    "params" => Coluna.Params(
+        solver=Coluna.Algorithm.TreeSearchAlgorithm( ## default branch-and-bound of Coluna
+            maxnumnodes=100,
+            conqueralg=Coluna.ColCutGenConquer() ## default column and cut generation of Coluna
+        ) ## default branch-cut-and-price
+    ),
+    "default_optimizer" => GLPK.Optimizer # GLPK for the master & the subproblems
+);

Pricing callback

If the user declares all the necessary subproblem constraints and possibly additional subproblem variables to describe the set of feasible subproblem solutions, Coluna may perform automatic Dantzig-Wolfe decomposition in which the pricing subproblems are solved by applying a (default) MIP solver. In our case, applying a MIP solver is not the most efficient way to solve the pricing problem. Therefore, we implement an ad-hoc algorithm for solving the pricing subproblems and declare it as a pricing callback. In our pricing callback for a given facility, we inspect all feasible routes enumerated before calling the branch-cut-and-price algorithm. The inspection algorithm calculates the reduced cost for each enumerated route and returns a route with the minimum reduced cost.

We first define a structure to store the routes:

mutable struct Route
+    length::Int # record the length of the route (number of visited customers + 1)
+    path::Vector{Int} # record the sequence of visited customers
+end;

We can reduce the number of enumerated routes by exploiting the following property. Consider two routes starting from the same facility and visiting the same subset of locations (customers). These two routes correspond to columns with the same vector of coefficients in master constraints. A solution containing the route with a larger traveled distance (i.e., larger route original cost) is dominated: this dominated route can be replaced by the other route without increasing the total solution cost. Therefore, for each subset of locations of a size not exceeding the maximum one, the enumeration procedure keeps only one route visiting this subset, the one with the smallest cost.

A method that computes the cost of a route:

function route_original_cost(arc_costs, route::Route)
+    route_cost = 0.0
+    path = route.path
+    path_length = route.length
+    for i in 1:(path_length-1)
+        route_cost += arc_costs[path[i], path[i+1]]
+    end
+    return route_cost
+end;

This procedure finds a least-cost sequence of visiting the given set of customers starting from a given facility.

function best_visit_sequence(arc_costs, cust_subset, facility_id)
+    # generate all the possible visit orders
+    set_size = size(cust_subset)[1]
+    all_paths = collect(multiset_permutations(cust_subset, set_size))
+    all_routes = Vector{Route}()
+    for path in all_paths
+        # add the first index i.e. the facility id
+        enpath = vcat([facility_id], path)
+        # length of the route = 1 + number of visited customers
+        route = Route(set_size + 1, enpath)
+        push!(all_routes, route)
+    end
+    # compute each route original cost
+    routes_costs = map(r ->
+            (r, route_original_cost(arc_costs, r)), all_routes)
+    # keep only the best visit sequence
+    tmp = argmin([c for (_, c) in routes_costs])
+    (best_order, _) = routes_costs[tmp]
+    return best_order
+end;

We are now able to compute a dominating route for all the possible customers' subsets, given a facility id:

using Combinatorics
+
+function best_route_forall_cust_subsets(arc_costs, customers, facility_id, max_size)
+    best_routes = Vector{Route}()
+    all_subsets = Vector{Vector{Int}}()
+    for subset_size in 1:max_size
+        subsets = collect(combinations(customers, subset_size))
+        for s in subsets
+            push!(all_subsets, s)
+        end
+    end
+    for s in all_subsets
+        route_s = best_visit_sequence(arc_costs, s, facility_id)
+        push!(best_routes, route_s)
+    end
+    return best_routes
+end;

We store all the information given by the enumeration phase in a dictionary. For each facility id, we match a vector of routes that are the best visiting sequences for each possible subset of customers.

routes_per_facility = Dict(
+    j => best_route_forall_cust_subsets(arc_costs, customers, j, nb_positions) for j in facilities
+)
Dict{Int64, Vector{Main.Route}} with 2 entries:
+  2 => [Route(2, [2, 3]), Route(2, [2, 4]), Route(2, [2, 5]), Route(2, [2, 6]),…
+  1 => [Route(2, [1, 3]), Route(2, [1, 4]), Route(2, [1, 5]), Route(2, [1, 6]),…

Our pricing callback must compute the reduced cost of each route, given the reduced cost of the subproblem variables x and z. Remember that subproblem variables z are implicitly defined by master representative variables z. We remark that z variables participate only in the objective function. Thus their reduced costs are initially equal to the original costs (i.e., objective coefficients) This is not true anymore after adding branching constraints and robust cuts involving variables z.

We need methods to compute the contributions to the reduced cost of the x and z variables:

function x_contribution(route::Route, j::Int, x_red_costs)
+    x = 0.0
+    visited_customers = route.path[2:route.length]
+    for i in visited_customers
+        x += x_red_costs["x_$(i)_$(j)"]
+    end
+    return x
+end;
+
+function z_contribution(route::Route, z_red_costs)
+    z = 0.0
+    for i in 1:(route.length-1)
+        current_position = route.path[i]
+        next_position = route.path[i+1]
+        z += z_red_costs["z_$(current_position)_$(next_position)"]
+    end
+    return z
+end;

We are now able to write our pricing callback:

function pricing_callback(cbdata)
+    # Get the id of the facility.
+    j = BlockDecomposition.indice(BlockDecomposition.callback_spid(cbdata, model))
+
+    # Retrieve variables reduced costs.
+    z_red_costs = Dict(
+        "z_$(u)_$(v)" => BlockDecomposition.callback_reduced_cost(cbdata, z[u, v]) for u in locations, v in locations)
+    x_red_costs = Dict(
+        "x_$(i)_$(j)" => BlockDecomposition.callback_reduced_cost(cbdata, x[i, j]) for i in customers
+    )
+
+    # Keep route with minimum reduced cost.
+    red_costs_j = map(r -> (
+            r,
+            x_contribution(r, j, x_red_costs) + z_contribution(r, z_red_costs) # the reduced cost of a route is the sum of the contribution of the variables
+        ), routes_per_facility[j]
+    )
+    min_index = argmin([x for (_, x) in red_costs_j])
+    (best_route, min_reduced_cost) = red_costs_j[min_index]
+
+    # Retrieve the route's arcs.
+    best_route_arcs = Vector{Tuple{Int,Int}}()
+    for i in 1:(best_route.length-1)
+        push!(best_route_arcs, (best_route.path[i], best_route.path[i+1]))
+    end
+    best_route_customers = best_route.path[2:best_route.length]
+
+    # Create the solution (send only variables with non-zero values).
+    z_vars = [z[u, v] for (u, v) in best_route_arcs]
+    x_vars = [x[i, j] for i in best_route_customers]
+    sol_vars = vcat(z_vars, x_vars)
+    sol_vals = ones(Float64, length(z_vars) + length(x_vars))
+    sol_cost = min_reduced_cost
+
+    # Submit the solution to the subproblem to Coluna.
+    MOI.submit(model, BlockDecomposition.PricingSolution(cbdata), sol_cost, sol_vars, sol_vals)
+
+    # Submit the dual bound to the solution of the subproblem.
+    # This bound is used to compute the contribution of the subproblem to the lagrangian
+    # bound in column generation.
+    MOI.submit(model, BlockDecomposition.PricingDualBound(cbdata), sol_cost) ## optimal solution
+
+end;

Create the model:

model, x, y, z, _ = create_model(coluna, pricing_callback);

Solve:

JuMP.optimize!(model)
Coluna
+Version 0.7.0 | https://github.com/atoptima/Coluna.jl
+***************************************************************************************
+**** B&B tree root node
+**** Local DB = -Inf, global bounds: [ -Inf , Inf ], time = 6.25 sec.
+***************************************************************************************
+  <st= 1> <it=  1> <et=14.33> <mst= 0.75> <sp= 0.00> <cols= 2> <al= 0.00> <DB=-89725.0000> <mlp=70000.0000> <PB=Inf>
+  <st= 1> <it=  2> <et=14.56> <mst= 0.04> <sp= 0.00> <cols= 2> <al= 0.00> <DB=-89624.5000> <mlp=30132.7000> <PB=Inf>
+  <st= 1> <it=  3> <et=14.97> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB= -550.8000> <mlp=  327.4000> <PB=327.4000>
+  <st= 1> <it=  4> <et=14.97> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB= -370.2000> <mlp=  327.4000> <PB=327.4000>
+  <st= 1> <it=  5> <et=14.98> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB= -364.0000> <mlp=  327.4000> <PB=327.4000>
+  <st= 1> <it=  6> <et=14.98> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB= -301.0000> <mlp=  327.4000> <PB=327.4000>
+  <st= 1> <it=  7> <et=14.98> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  -84.9000> <mlp=  306.9667> <PB=327.4000>
+  <st= 1> <it=  8> <et=14.98> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  121.6500> <mlp=  281.7500> <PB=327.4000>
+  <st= 1> <it=  9> <et=14.98> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  184.1500> <mlp=  281.7500> <PB=327.4000>
+  <st= 1> <it= 10> <et=14.98> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  243.9500> <mlp=  281.7500> <PB=327.4000>
+  <st= 1> <it= 11> <et=14.98> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  265.7000> <mlp=  279.3000> <PB=327.4000>
+  <st= 1> <it= 12> <et=14.98> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  267.8750> <mlp=  276.9750> <PB=327.4000>
+  <st= 1> <it= 13> <et=14.98> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  272.7750> <mlp=  276.9750> <PB=327.4000>
+  <st= 1> <it= 14> <et=14.98> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  271.3750> <mlp=  276.9750> <PB=327.4000>
+  <st= 1> <it= 15> <et=14.98> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  264.0250> <mlp=  276.8250> <PB=327.4000>
+  <st= 1> <it= 16> <et=14.98> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  274.3500> <mlp=  275.7500> <PB=327.4000>
+  <st= 1> <it= 17> <et=14.99> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  274.7500> <mlp=  275.7500> <PB=327.4000>
+  <st= 1> <it= 18> <et=14.99> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  275.7500> <mlp=  275.7500> <PB=327.4000>
+***************************************************************************************
+**** B&B tree node N°3, parent N°1, depth 1, 1 untreated node
+**** Local DB = 275.7500, global bounds: [ -Inf , 297.7000 ], time = 16.86 sec.
+**** Branching constraint: z[4,3]<=0.0
+***************************************************************************************
+  <st= 1> <it=  1> <et=17.68> <mst= 0.09> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  274.1500> <mlp=  276.6500> <PB=297.7000>
+  <st= 1> <it=  2> <et=17.68> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  274.1000> <mlp=  276.5000> <PB=297.7000>
+  <st= 1> <it=  3> <et=17.68> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  276.5000> <mlp=  276.5000> <PB=297.7000>
+***************************************************************************************
+**** B&B tree node N°5, parent N°3, depth 2, 2 untreated nodes
+**** Local DB = 276.5000, global bounds: [ 275.7500 , 297.7000 ], time = 17.69 sec.
+**** Branching constraint: z[1,4]<=0.0
+***************************************************************************************
+  <st= 1> <it=  1> <et=17.69> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  257.5000> <mlp=  279.1000> <PB=297.7000>
+  <st= 1> <it=  2> <et=17.69> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  277.2500> <mlp=  277.2500> <PB=297.7000>
+***************************************************************************************
+**** B&B tree node N°7, parent N°5, depth 3, 3 untreated nodes
+**** Local DB = 277.2500, global bounds: [ 275.7500 , 297.7000 ], time = 17.69 sec.
+**** Branching constraint: z[3,6]<=0.0
+***************************************************************************************
+  <st= 1> <it=  1> <et=17.69> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB= -103.2200> <mlp=  288.4200> <PB=297.7000>
+  <st= 1> <it=  2> <et=17.69> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  235.7231> <mlp=  280.6154> <PB=297.7000>
+  <st= 1> <it=  3> <et=17.69> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  211.1375> <mlp=  278.9875> <PB=297.7000>
+  <st= 1> <it=  4> <et=17.69> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  274.6875> <mlp=  278.9875> <PB=297.7000>
+  <st= 1> <it=  5> <et=17.69> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  275.8500> <mlp=  278.4500> <PB=297.7000>
+  <st= 1> <it=  6> <et=17.70> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  278.4500> <mlp=  278.4500> <PB=297.7000>
+***************************************************************************************
+**** B&B tree node N°9, parent N°7, depth 4, 4 untreated nodes
+**** Local DB = 278.4500, global bounds: [ 275.7500 , 297.7000 ], time = 17.70 sec.
+**** Branching constraint: z[3,4]<=0.0
+***************************************************************************************
+  <st= 1> <it=  1> <et=17.70> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB= -101.3500> <mlp=  305.7500> <PB=297.7000>
+  <st= 1> <it=  2> <et=17.70> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  222.5600> <mlp=  301.4000> <PB=297.7000>
+  <st= 1> <it=  3> <et=17.70> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  275.7000> <mlp=  296.3000> <PB=297.7000>
+  <st= 1> <it=  4> <et=17.70> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  284.3000> <mlp=  296.3000> <PB=297.7000>
+  <st= 1> <it=  5> <et=17.70> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  285.3500> <mlp=  294.2500> <PB=297.7000>
+  <st= 1> <it=  6> <et=17.70> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  289.9500> <mlp=  294.2500> <PB=297.7000>
+  <st= 1> <it=  7> <et=17.70> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  288.4500> <mlp=  294.2500> <PB=297.7000>
+  <st= 1> <it=  8> <et=17.70> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  292.7500> <mlp=  294.2500> <PB=297.7000>
+  <st= 1> <it=  9> <et=17.70> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  291.7000> <mlp=  293.5000> <PB=293.5000>
+  <st= 1> <it= 10> <et=17.71> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  293.5000> <mlp=  293.5000> <PB=293.5000>
+***************************************************************************************
+**** B&B tree node N°8, parent N°7, depth 4, 3 untreated nodes
+**** Local DB = 278.4500, global bounds: [ 275.7500 , 293.5000 ], time = 17.72 sec.
+**** Branching constraint: z[3,4]>=1.0
+***************************************************************************************
+  <st= 1> <it=  1> <et=17.72> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  136.0000> <mlp=  307.6000> <PB=293.5000>
+  <st= 1> <it=  2> <et=17.72> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  291.7000> <mlp=  298.9000> <PB=293.5000>
+  <st= 1> <it=  3> <et=17.73> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  293.3000> <mlp=  298.9000> <PB=293.5000>
+  <st= 1> <it=  4> <et=17.73> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  291.5000> <mlp=  296.3000> <PB=293.5000>
+  <st= 1> <it=  5> <et=17.73> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  296.3000> <mlp=  296.3000> <PB=293.5000>
+***************************************************************************************
+**** B&B tree node N°6, parent N°5, depth 3, 2 untreated nodes
+**** Local DB = 277.2500, global bounds: [ 275.7500 , 293.5000 ], time = 17.73 sec.
+**** Branching constraint: z[3,6]>=1.0
+***************************************************************************************
+  <st= 1> <it=  1> <et=17.73> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  267.7000> <mlp=  279.7000> <PB=293.5000>
+  <st= 1> <it=  2> <et=17.73> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  277.9000> <mlp=  279.7000> <PB=293.5000>
+  <st= 1> <it=  3> <et=17.73> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  279.2500> <mlp=  279.2500> <PB=293.5000>
+***************************************************************************************
+**** B&B tree node N°11, parent N°6, depth 4, 3 untreated nodes
+**** Local DB = 279.2500, global bounds: [ 275.7500 , 293.5000 ], time = 17.73 sec.
+**** Branching constraint: z[7,3]<=0.0
+***************************************************************************************
+  <st= 1> <it=  1> <et=17.73> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  278.5000> <mlp=  280.5000> <PB=293.5000>
+  <st= 1> <it=  2> <et=17.73> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  280.5000> <mlp=  280.5000> <PB=293.5000>
+***************************************************************************************
+**** B&B tree node N°13, parent N°11, depth 5, 4 untreated nodes
+**** Local DB = 280.5000, global bounds: [ 275.7500 , 293.5000 ], time = 17.73 sec.
+**** Branching constraint: y[1]<=0.0
+***************************************************************************************
+  <st= 1> <it=  1> <et=17.74> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=-4808.4500> <mlp=  304.4500> <PB=293.5000>
+  <st= 1> <it=  2> <et=17.74> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  252.1000> <mlp=  288.9000> <PB=293.5000>
+  <st= 1> <it=  3> <et=17.74> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  279.7000> <mlp=  284.3000> <PB=293.5000>
+  <st= 1> <it=  4> <et=17.74> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  283.7250> <mlp=  283.7250> <PB=293.5000>
+***************************************************************************************
+**** B&B tree node N°15, parent N°13, depth 6, 5 untreated nodes
+**** Local DB = 283.7250, global bounds: [ 275.7500 , 293.5000 ], time = 17.74 sec.
+**** Branching constraint: z[7,4]<=0.0
+***************************************************************************************
+  <st= 1> <it=  1> <et=17.74> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  121.9000> <mlp=  318.7000> <PB=293.5000>
+  <st= 1> <it=  2> <et=17.74> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  270.4500> <mlp=  293.1500> <PB=293.5000>
+  <st= 1> <it=  3> <et=17.74> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  286.0800> <mlp=  290.8800> <PB=293.5000>
+  <st= 1> <it=  4> <et=17.75> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  286.0000> <mlp=  290.4000> <PB=293.5000>
+  <st= 1> <it=  5> <et=17.75> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  290.4000> <mlp=  290.4000> <PB=293.5000>
+***************************************************************************************
+**** B&B tree node N°17, parent N°15, depth 7, 6 untreated nodes
+**** Local DB = 290.4000, global bounds: [ 275.7500 , 293.5000 ], time = 17.75 sec.
+**** Branching constraint: z[5,3]<=0.0
+***************************************************************************************
+  <st= 1> <it=  1> <et=17.75> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=-9853.7000> <mlp=  318.7000> <PB=293.5000>
+  <st= 1> <it=  2> <et=17.77> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  264.0000> <mlp=  317.7333> <PB=293.5000>
+  <st= 1> <it=  3> <et=17.77> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  293.1000> <mlp=  304.3000> <PB=293.5000>
+  <st= 1> <it=  4> <et=17.77> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  296.9000> <mlp=  304.3000> <PB=293.5000>
+***************************************************************************************
+**** B&B tree node N°16, parent N°15, depth 7, 5 untreated nodes
+**** Local DB = 290.4000, global bounds: [ 275.7500 , 293.5000 ], time = 17.77 sec.
+**** Branching constraint: z[5,3]>=1.0
+***************************************************************************************
+  <st= 1> <it=  1> <et=17.78> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  168.2000> <mlp=  334.2000> <PB=293.5000>
+  <st= 1> <it=  2> <et=17.78> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  282.5000> <mlp=  311.5000> <PB=293.5000>
+  <st= 1> <it=  3> <et=17.78> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  294.2000> <mlp=  298.6000> <PB=293.5000>
+***************************************************************************************
+**** B&B tree node N°14, parent N°13, depth 6, 4 untreated nodes
+**** Local DB = 283.7250, global bounds: [ 275.7500 , 293.5000 ], time = 17.78 sec.
+**** Branching constraint: z[7,4]>=1.0
+***************************************************************************************
+  <st= 1> <it=  1> <et=17.78> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  295.3000> <mlp=  299.5000> <PB=293.5000>
+***************************************************************************************
+**** B&B tree node N°12, parent N°11, depth 5, 3 untreated nodes
+**** Local DB = 280.5000, global bounds: [ 275.7500 , 293.5000 ], time = 17.78 sec.
+**** Branching constraint: y[1]>=1.0
+***************************************************************************************
+  <st= 1> <it=  1> <et=17.78> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  281.0000> <mlp=  304.6000> <PB=293.5000>
+  <st= 1> <it=  2> <et=17.78> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  292.8000> <mlp=  292.8000> <PB=292.8000>
+***************************************************************************************
+**** B&B tree node N°10, parent N°6, depth 4, 2 untreated nodes
+**** Local DB = 279.2500, global bounds: [ 275.7500 , 292.8000 ], time = 17.78 sec.
+**** Branching constraint: z[7,3]>=1.0
+***************************************************************************************
+  <st= 1> <it=  1> <et=17.78> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  243.8000> <mlp=  307.8000> <PB=292.8000>
+  <st= 1> <it=  2> <et=17.79> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  286.4000> <mlp=  293.8000> <PB=292.8000>
+  <st= 1> <it=  3> <et=17.79> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  284.6000> <mlp=  293.8000> <PB=292.8000>
+  <st= 1> <it=  4> <et=17.79> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  293.8000> <mlp=  293.8000> <PB=292.8000>
+***************************************************************************************
+**** B&B tree node N°4, parent N°3, depth 2, 1 untreated node
+**** Local DB = 276.5000, global bounds: [ 275.7500 , 292.8000 ], time = 17.79 sec.
+**** Branching constraint: z[1,4]>=1.0
+***************************************************************************************
+  <st= 1> <it=  1> <et=17.79> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=   76.3000> <mlp=  297.7000> <PB=292.8000>
+  <st= 1> <it=  2> <et=17.79> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  277.1400> <mlp=  279.5400> <PB=292.8000>
+  <st= 1> <it=  3> <et=17.79> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  278.5400> <mlp=  279.5400> <PB=292.8000>
+  <st= 1> <it=  4> <et=17.79> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  279.3400> <mlp=  279.3400> <PB=292.8000>
+***************************************************************************************
+**** B&B tree node N°19, parent N°4, depth 3, 2 untreated nodes
+**** Local DB = 279.3400, global bounds: [ 275.7500 , 290.3000 ], time = 17.79 sec.
+**** Branching constraint: z[7,3]<=0.0
+***************************************************************************************
+  <st= 1> <it=  1> <et=17.79> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  263.9714> <mlp=  286.0571> <PB=290.3000>
+  <st= 1> <it=  2> <et=17.79> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  267.7200> <mlp=  281.6400> <PB=290.3000>
+  <st= 1> <it=  3> <et=17.79> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  281.6400> <mlp=  281.6400> <PB=290.3000>
+***************************************************************************************
+**** B&B tree node N°21, parent N°19, depth 4, 3 untreated nodes
+**** Local DB = 281.6400, global bounds: [ 275.7500 , 290.3000 ], time = 17.80 sec.
+**** Branching constraint: z[3,6]<=0.0
+***************************************************************************************
+  <st= 1> <it=  1> <et=17.80> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  101.9000> <mlp=  322.7000> <PB=290.3000>
+  <st= 1> <it=  2> <et=17.80> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  288.8500> <mlp=  293.9500> <PB=290.3000>
+  <st= 1> <it=  3> <et=17.80> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  291.4000> <mlp=  291.4000> <PB=290.3000>
+***************************************************************************************
+**** B&B tree node N°20, parent N°19, depth 4, 2 untreated nodes
+**** Local DB = 281.6400, global bounds: [ 275.7500 , 290.3000 ], time = 17.80 sec.
+**** Branching constraint: z[3,6]>=1.0
+***************************************************************************************
+  <st= 1> <it=  1> <et=17.80> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  285.1400> <mlp=  285.1400> <PB=290.3000>
+***************************************************************************************
+**** B&B tree node N°23, parent N°20, depth 5, 3 untreated nodes
+**** Local DB = 285.1400, global bounds: [ 275.7500 , 290.3000 ], time = 17.80 sec.
+**** Branching constraint: x[3,2]<=0.0
+***************************************************************************************
+  <st= 1> <it=  1> <et=17.80> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  224.7000> <mlp=  307.1000> <PB=290.3000>
+  <st= 1> <it=  2> <et=17.80> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  285.7500> <mlp=  293.8500> <PB=290.3000>
+  <st= 1> <it=  3> <et=17.80> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  289.8000> <mlp=  289.8000> <PB=289.8000>
+***************************************************************************************
+**** B&B tree node N°22, parent N°20, depth 5, 2 untreated nodes
+**** Local DB = 285.1400, global bounds: [ 275.7500 , 289.8000 ], time = 17.80 sec.
+**** Branching constraint: x[3,2]>=1.0
+***************************************************************************************
+  <st= 1> <it=  1> <et=17.81> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  125.6000> <mlp=  292.8000> <PB=289.8000>
+  <st= 1> <it=  2> <et=17.81> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  238.0286> <mlp=  292.8000> <PB=289.8000>
+  <st= 1> <it=  3> <et=17.81> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  291.8000> <mlp=  292.8000> <PB=289.8000>
+***************************************************************************************
+**** B&B tree node N°18, parent N°4, depth 3, 1 untreated node
+**** Local DB = 279.3400, global bounds: [ 275.7500 , 289.8000 ], time = 17.81 sec.
+**** Branching constraint: z[7,3]>=1.0
+***************************************************************************************
+  <st= 1> <it=  1> <et=17.81> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  238.5667> <mlp=  290.3000> <PB=289.8000>
+  <st= 1> <it=  2> <et=17.81> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  259.0667> <mlp=  283.7333> <PB=289.8000>
+  <st= 1> <it=  3> <et=17.81> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  282.0800> <mlp=  282.0800> <PB=289.8000>
+***************************************************************************************
+**** B&B tree node N°25, parent N°18, depth 4, 2 untreated nodes
+**** Local DB = 282.0800, global bounds: [ 275.7500 , 289.8000 ], time = 17.81 sec.
+**** Branching constraint: z[1,5]<=0.0
+***************************************************************************************
+  <st= 1> <it=  1> <et=17.82> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  285.9200> <mlp=  285.9200> <PB=289.8000>
+***************************************************************************************
+**** B&B tree node N°27, parent N°25, depth 5, 3 untreated nodes
+**** Local DB = 285.9200, global bounds: [ 275.7500 , 289.8000 ], time = 17.82 sec.
+**** Branching constraint: y[1]<=0.0
+***************************************************************************************
+  <st= 1> <it=  1> <et=17.82> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB= 5283.7600> <mlp= 5286.7600> <PB=289.8000>
+# <st= 1> <it=  2> <et=18.20> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB= 5000.0000> <mlp= 5000.0000> <PB=289.8000>
+***************************************************************************************
+**** B&B tree node N°26, parent N°25, depth 5, 2 untreated nodes
+**** Local DB = 285.9200, global bounds: [ 275.7500 , 289.8000 ], time = 18.21 sec.
+**** Branching constraint: y[1]>=1.0
+***************************************************************************************
+  <st= 1> <it=  1> <et=18.21> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  280.1000> <mlp=  301.1000> <PB=289.8000>
+  <st= 1> <it=  2> <et=18.21> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  290.6000> <mlp=  290.6000> <PB=289.8000>
+***************************************************************************************
+**** B&B tree node N°24, parent N°18, depth 4, 1 untreated node
+**** Local DB = 282.0800, global bounds: [ 275.7500 , 289.8000 ], time = 18.21 sec.
+**** Branching constraint: z[1,5]>=1.0
+***************************************************************************************
+  <st= 1> <it=  1> <et=18.21> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  180.1000> <mlp=  290.3000> <PB=289.8000>
+  <st= 1> <it=  2> <et=18.21> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  281.2000> <mlp=  290.3000> <PB=289.8000>
+  <st= 1> <it=  3> <et=18.21> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  273.3000> <mlp=  290.3000> <PB=289.8000>
+  <st= 1> <it=  4> <et=18.23> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  284.5000> <mlp=  290.3000> <PB=289.8000>
+  <st= 1> <it=  5> <et=18.23> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  290.3000> <mlp=  290.3000> <PB=289.8000>
+***************************************************************************************
+**** B&B tree node N°2, parent N°1, depth 1, 0 untreated node
+**** Local DB = 275.7500, global bounds: [ 275.7500 , 289.8000 ], time = 18.23 sec.
+**** Branching constraint: z[4,3]>=1.0
+***************************************************************************************
+  <st= 1> <it=  1> <et=18.24> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  217.6000> <mlp=  284.3200> <PB=289.8000>
+  <st= 1> <it=  2> <et=18.24> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  273.1000> <mlp=  279.3000> <PB=289.8000>
+  <st= 1> <it=  3> <et=18.24> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  273.9000> <mlp=  279.3000> <PB=289.8000>
+  <st= 1> <it=  4> <et=18.24> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  278.6250> <mlp=  278.6250> <PB=289.8000>
+***************************************************************************************
+**** B&B tree node N°29, parent N°2, depth 2, 1 untreated node
+**** Local DB = 278.6250, global bounds: [ 289.8000 , 289.8000 ], time = 18.24 sec.
+**** Branching constraint: z[1,4]<=0.0
+***************************************************************************************
+  <st= 1> <it=  1> <et=18.24> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  -74.2000> <mlp=  301.0000> <PB=289.8000>
+  <st= 1> <it=  2> <et=18.24> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  278.0750> <mlp=  280.4750> <PB=289.8000>
+  <st= 1> <it=  3> <et=18.24> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  280.4750> <mlp=  280.4750> <PB=289.8000>
+***************************************************************************************
+**** B&B tree node N°31, parent N°29, depth 3, 2 untreated nodes
+**** Local DB = 280.4750, global bounds: [ 278.6250 , 289.8000 ], time = 18.24 sec.
+**** Branching constraint: z[1,5]<=0.0
+***************************************************************************************
+  <st= 1> <it=  1> <et=18.25> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  278.9250> <mlp=  282.7250> <PB=289.8000>
+  <st= 1> <it=  2> <et=18.25> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  282.7250> <mlp=  282.7250> <PB=289.8000>
+***************************************************************************************
+**** B&B tree node N°33, parent N°31, depth 4, 3 untreated nodes
+**** Local DB = 282.7250, global bounds: [ 278.6250 , 289.8000 ], time = 18.25 sec.
+**** Branching constraint: y[1]<=0.0
+***************************************************************************************
+  <st= 1> <it=  1> <et=18.25> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  253.0000> <mlp=  291.0000> <PB=289.8000>
+  <st= 1> <it=  2> <et=18.25> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  286.2500> <mlp=  286.2500> <PB=289.8000>
+***************************************************************************************
+**** B&B tree node N°35, parent N°33, depth 5, 4 untreated nodes
+**** Local DB = 286.2500, global bounds: [ 278.6250 , 289.8000 ], time = 18.25 sec.
+**** Branching constraint: z[8,5]<=0.0
+***************************************************************************************
+  <st= 1> <it=  1> <et=18.25> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  260.3000> <mlp=  298.1000> <PB=289.8000>
+  <st= 1> <it=  2> <et=18.25> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  280.2500> <mlp=  288.6500> <PB=289.8000>
+  <st= 1> <it=  3> <et=18.25> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  288.6500> <mlp=  288.6500> <PB=289.8000>
+***************************************************************************************
+**** B&B tree node N°37, parent N°35, depth 6, 5 untreated nodes
+**** Local DB = 288.6500, global bounds: [ 278.6250 , 289.8000 ], time = 18.25 sec.
+**** Branching constraint: z[3,6]<=0.0
+***************************************************************************************
+  <st= 1> <it=  1> <et=18.26> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  158.7000> <mlp=  315.9000> <PB=289.8000>
+  <st= 1> <it=  2> <et=18.26> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  276.5500> <mlp=  311.2500> <PB=289.8000>
+  <st= 1> <it=  3> <et=18.26> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  289.6000> <mlp=  303.2000> <PB=289.8000>
+  <st= 1> <it=  4> <et=18.26> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  298.7000> <mlp=  303.2000> <PB=289.8000>
+***************************************************************************************
+**** B&B tree node N°36, parent N°35, depth 6, 4 untreated nodes
+**** Local DB = 288.6500, global bounds: [ 278.6250 , 289.8000 ], time = 18.26 sec.
+**** Branching constraint: z[3,6]>=1.0
+***************************************************************************************
+  <st= 1> <it=  1> <et=18.26> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  225.9000> <mlp=  311.5000> <PB=289.8000>
+  <st= 1> <it=  2> <et=18.26> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  287.3000> <mlp=  303.3667> <PB=289.8000>
+  <st= 1> <it=  3> <et=18.26> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  298.7000> <mlp=  298.7000> <PB=289.8000>
+***************************************************************************************
+**** B&B tree node N°34, parent N°33, depth 5, 3 untreated nodes
+**** Local DB = 286.2500, global bounds: [ 278.6250 , 289.8000 ], time = 18.26 sec.
+**** Branching constraint: z[8,5]>=1.0
+***************************************************************************************
+  <st= 1> <it=  1> <et=18.26> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  249.4000> <mlp=  307.4000> <PB=289.8000>
+  <st= 1> <it=  2> <et=18.27> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  294.3000> <mlp=  297.9000> <PB=289.8000>
+***************************************************************************************
+**** B&B tree node N°32, parent N°31, depth 4, 2 untreated nodes
+**** Local DB = 282.7250, global bounds: [ 278.6250 , 289.8000 ], time = 18.27 sec.
+**** Branching constraint: y[1]>=1.0
+***************************************************************************************
+  <st= 1> <it=  1> <et=18.27> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  282.7000> <mlp=  302.7000> <PB=289.8000>
+  <st= 1> <it=  2> <et=18.27> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  284.3000> <mlp=  302.7000> <PB=289.8000>
+  <st= 1> <it=  3> <et=18.27> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  293.5000> <mlp=  293.5000> <PB=289.8000>
+***************************************************************************************
+**** B&B tree node N°30, parent N°29, depth 3, 1 untreated node
+**** Local DB = 280.4750, global bounds: [ 278.6250 , 289.8000 ], time = 18.27 sec.
+**** Branching constraint: z[1,5]>=1.0
+***************************************************************************************
+  <st= 1> <it=  1> <et=18.27> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  118.2000> <mlp=  301.0000> <PB=289.8000>
+  <st= 1> <it=  2> <et=18.27> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  214.6000> <mlp=  301.0000> <PB=289.8000>
+  <st= 1> <it=  3> <et=18.27> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  290.3000> <mlp=  297.2333> <PB=289.8000>
+***************************************************************************************
+**** B&B tree node N°28, parent N°2, depth 2, 0 untreated node
+**** Local DB = 278.6250, global bounds: [ 278.6250 , 289.8000 ], time = 18.27 sec.
+**** Branching constraint: z[1,4]>=1.0
+***************************************************************************************
+  <st= 1> <it=  1> <et=18.27> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  119.9000> <mlp=  299.7000> <PB=289.8000>
+  <st= 1> <it=  2> <et=18.27> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  170.8000> <mlp=  299.7000> <PB=289.8000>
+  <st= 1> <it=  3> <et=18.28> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  284.9375> <mlp=  286.7125> <PB=289.8000>
+  <st= 1> <it=  4> <et=18.28> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  286.7125> <mlp=  286.7125> <PB=289.8000>
+***************************************************************************************
+**** B&B tree node N°39, parent N°28, depth 3, 1 untreated node
+**** Local DB = 286.7125, global bounds: [ 289.8000 , 289.8000 ], time = 18.28 sec.
+**** Branching constraint: z[1,8]<=0.0
+***************************************************************************************
+  <st= 1> <it=  1> <et=18.28> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  206.9000> <mlp=  292.5909> <PB=289.8000>
+  <st= 1> <it=  2> <et=18.28> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  289.5875> <mlp=  289.5875> <PB=289.8000>
+***************************************************************************************
+**** B&B tree node N°41, parent N°39, depth 4, 2 untreated nodes
+**** Local DB = 289.5875, global bounds: [ 286.7125 , 289.8000 ], time = 18.28 sec.
+**** Branching constraint: x[8,1]<=0.0
+***************************************************************************************
+  <st= 1> <it=  1> <et=18.28> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  278.3000> <mlp=  292.9000> <PB=289.8000>
+  <st= 1> <it=  2> <et=18.28> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  283.9000> <mlp=  292.9000> <PB=289.8000>
+  <st= 1> <it=  3> <et=18.29> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  286.8000> <mlp=  292.9000> <PB=289.8000>
+  <st= 1> <it=  4> <et=18.29> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  289.5000> <mlp=  292.9000> <PB=289.8000>
+  <st= 1> <it=  5> <et=18.29> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  292.9000> <mlp=  292.9000> <PB=289.8000>
+***************************************************************************************
+**** B&B tree node N°40, parent N°39, depth 4, 1 untreated node
+**** Local DB = 289.5875, global bounds: [ 286.7125 , 289.8000 ], time = 18.29 sec.
+**** Branching constraint: x[8,1]>=1.0
+***************************************************************************************
+  <st= 1> <it=  1> <et=18.29> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  268.8000> <mlp=  302.0000> <PB=289.8000>
+  <st= 1> <it=  2> <et=18.29> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  279.5000> <mlp=  293.7000> <PB=289.8000>
+  <st= 1> <it=  3> <et=18.29> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  293.7000> <mlp=  293.7000> <PB=289.8000>
+***************************************************************************************
+**** B&B tree node N°38, parent N°28, depth 3, 0 untreated node
+**** Local DB = 286.7125, global bounds: [ 286.7125 , 289.8000 ], time = 18.29 sec.
+**** Branching constraint: z[1,8]>=1.0
+***************************************************************************************
+  <st= 1> <it=  1> <et=18.29> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  254.3000> <mlp=  290.5000> <PB=289.8000>
+  <st= 1> <it=  2> <et=18.29> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  290.5000> <mlp=  290.5000> <PB=289.8000>
+ ──────────────────────────────────────────────────────────────────────────
+                                  Time                    Allocations
+                         ───────────────────────   ────────────────────────
+    Tot / % measured:         69.7s /  26.3%           4.84GiB /  23.4%
+
+ Section         ncalls     time    %tot     avg     alloc    %tot      avg
+ ──────────────────────────────────────────────────────────────────────────
+ Coluna               1    18.4s  100.0%   18.4s   1.13GiB  100.0%  1.13GiB
+   SolveLpForm      147    895ms    4.9%  6.09ms   44.9MiB    3.9%   313KiB
+ ──────────────────────────────────────────────────────────────────────────
+[ Info: Terminated
+[ Info: Primal bound: 289.8
+[ Info: Dual bound: 289.8

Strengthening the master with linear valid inequalities on the original variables (so-called "robust" cuts)

To improve the quality of the linear relaxation, a family of classic facility location valid inequalities can be used:

\[x_{ij} \leq y_j\; \forall i \in I, \forall j \in J\]

where $I$ is the set of customers and $J$ the set of facilities.

We declare a structure representing an inequality in this family:

struct OpenFacilityInequality
+    facility_id::Int
+    customer_id::Int
+end

To identify violated valid inequalities from a current master LP solution, we proceed by enumeration (i.e. iterating over all pairs of customer and facility). Enumeration separation procedure is implemented in the following callback.

function valid_inequalities_callback(cbdata)
+    # Get variables valuations and store them in dictionaries.
+    x_vals = Dict(
+        "x_$(i)_$(j)" => BlockDecomposition.callback_value(cbdata, x[i, j]) for i in customers, j in facilities
+    )
+    y_vals = Dict(
+        "y_$(j)" => BlockDecomposition.callback_value(cbdata, y[j]) for j in facilities
+    )
+
+    # Separate the valid inequalities (i.e. retrieve the inequalities that are violated by
+    # the current solution) by enumeration.
+    inequalities = OpenFacilityInequality[]
+
+    for j in facilities
+        y_j = y_vals["y_$(j)"]
+        for i in customers
+            x_i_j = x_vals["x_$(i)_$(j)"]
+            if x_i_j > y_j
+                push!(inequalities, OpenFacilityInequality(j, i))
+            end
+        end
+    end
+
+    # Add the valid inequalities to the model.
+    for ineq in inequalities
+        constr = JuMP.@build_constraint(x[ineq.customer_id, ineq.facility_id] <= y[ineq.facility_id])
+        MOI.submit(model, MOI.UserCut(cbdata), constr)
+    end
+end;

We re-declare the model and optimize it with these valid inequalities:

model, x, y, z, _ = create_model(coluna, pricing_callback);
+MOI.set(model, MOI.UserCutCallback(), valid_inequalities_callback);
+JuMP.optimize!(model)
Coluna
+Version 0.7.0 | https://github.com/atoptima/Coluna.jl
+***************************************************************************************
+**** B&B tree root node
+**** Local DB = -Inf, global bounds: [ -Inf , Inf ], time = 0.00 sec.
+***************************************************************************************
+  <st= 1> <it=  1> <et= 0.00> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=-89725.0000> <mlp=70000.0000> <PB=Inf>
+  <st= 1> <it=  2> <et= 0.00> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=-89624.5000> <mlp=30132.7000> <PB=Inf>
+Cut separation callback adds 0 new essential cuts and 0 new facultative cuts.
+  <st= 1> <it=  3> <et= 0.08> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB= -550.8000> <mlp=  327.4000> <PB=327.4000>
+  <st= 1> <it=  4> <et= 0.08> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB= -370.2000> <mlp=  327.4000> <PB=327.4000>
+  <st= 1> <it=  5> <et= 0.08> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB= -364.0000> <mlp=  327.4000> <PB=327.4000>
+  <st= 1> <it=  6> <et= 0.08> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB= -301.0000> <mlp=  327.4000> <PB=327.4000>
+  <st= 1> <it=  7> <et= 0.08> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  -84.9000> <mlp=  306.9667> <PB=327.4000>
+  <st= 1> <it=  8> <et= 0.08> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  121.6500> <mlp=  281.7500> <PB=327.4000>
+  <st= 1> <it=  9> <et= 0.08> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  184.1500> <mlp=  281.7500> <PB=327.4000>
+  <st= 1> <it= 10> <et= 0.09> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  243.9500> <mlp=  281.7500> <PB=327.4000>
+  <st= 1> <it= 11> <et= 0.10> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  265.7000> <mlp=  279.3000> <PB=327.4000>
+  <st= 1> <it= 12> <et= 0.11> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  267.8750> <mlp=  276.9750> <PB=327.4000>
+  <st= 1> <it= 13> <et= 0.11> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  272.7750> <mlp=  276.9750> <PB=327.4000>
+  <st= 1> <it= 14> <et= 0.11> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  271.3750> <mlp=  276.9750> <PB=327.4000>
+  <st= 1> <it= 15> <et= 0.11> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  264.0250> <mlp=  276.8250> <PB=327.4000>
+  <st= 1> <it= 16> <et= 0.11> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  274.3500> <mlp=  275.7500> <PB=327.4000>
+  <st= 1> <it= 17> <et= 0.11> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  274.7500> <mlp=  275.7500> <PB=327.4000>
+  <st= 1> <it= 18> <et= 0.11> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  275.7500> <mlp=  275.7500> <PB=327.4000>
+Cut separation callback adds 0 new essential cuts and 7 new facultative cuts.
+avg. viol. = 0.12, max. viol. = 0.12, zero viol. = 0.
+  <st= 1> <it=  1> <et= 0.55> <mst= 0.00> <sp= 0.02> <cols= 2> <al= 0.00> <DB=  278.2000> <mlp=  283.5000> <PB=327.4000>
+  <st= 1> <it=  2> <et= 0.55> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  281.4125> <mlp=  283.3125> <PB=327.4000>
+  <st= 1> <it=  3> <et= 0.55> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  282.8750> <mlp=  283.0750> <PB=327.4000>
+  <st= 1> <it=  4> <et= 0.56> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  283.0750> <mlp=  283.0750> <PB=327.4000>
+Cut separation callback adds 0 new essential cuts and 7 new facultative cuts.
+avg. viol. = 0.12, max. viol. = 0.12, zero viol. = 0.
+Cut separation callback adds 0 new essential cuts and 0 new facultative cuts.
+  <st= 1> <it=  1> <et= 0.56> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  268.0667> <mlp=  289.8000> <PB=289.8000>
+  <st= 1> <it=  2> <et= 0.56> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  281.3286> <mlp=  289.8000> <PB=289.8000>
+  <st= 1> <it=  3> <et= 0.56> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  289.8000> <mlp=  289.8000> <PB=289.8000>
+ ──────────────────────────────────────────────────────────────────────────
+                                  Time                    Allocations
+                         ───────────────────────   ────────────────────────
+    Tot / % measured:         704ms /  79.4%           76.7MiB /  75.8%
+
+ Section         ncalls     time    %tot     avg     alloc    %tot      avg
+ ──────────────────────────────────────────────────────────────────────────
+ Coluna               1    559ms  100.0%   559ms   58.1MiB  100.0%  58.1MiB
+   SolveLpForm       25   4.50ms    0.8%   180μs   1.65MiB    2.8%  67.6KiB
+ ──────────────────────────────────────────────────────────────────────────
+[ Info: Terminated
+[ Info: Primal bound: 289.8
+[ Info: Dual bound: 289.79999999999995

Strengthening the master with valid inequalities on the column generation variables (so-called "non-robust" cuts)

In order to further strengthen the linear relaxation of the Dantzig-Wolfe reformulation, we separate a family of subset-row cuts, which is a subfamily of Chvátal-Gomory rank-1 cuts (R1C), obtained from the set-partitioning constraints. These cuts cannot be expressed as a linear combination of the original variables of the model. Instead, they are expressed with the master columns variables $λ_k$, $k \in K$, where $K$ is the set of generated columns or set of solutions returned by the pricing subproblems. Subset-row cuts are "non-robust" in the sense that they modify the structure of the pricing subproblems, and not just the reduced cost of subproblem variables. Thus, the implementation of the pricing callback should be updated to take into account dual costs associated with non-robust cutting planes.

Each Chvátal-Gomory rank-1 cut is characterized by a subset of set-partitioning constraints, or equivalently by a subset $C$ of customers, and a multiplier $\alpha_i$ for each customer $i\in C$:

\[\sum_{k \in K} \lfloor \sum_{i \in C} \alpha_i \tilde{x}^k_{i,j} \lambda_{k} \rfloor \leq \lfloor \sum_{i\in C} \alpha_i \rfloor, \; C \subseteq I,\]

where $\tilde{x}^k_{ij}$ is the value of the variable $x_{ij}$ in the $k$-th column generated. For subset-row cuts, $|C|=3$, and $\alpha_i=\frac{1}{2}$, $i\in C$.

Since we obtain subset-row cuts based on set-partitioning constraints, we must be able to differentiate them from the other constraints of the model. To do this, we exploit a feature of Coluna that allows us to attach custom data to the constraints and variables of a model, via the add-ons of BlockDecomposition package.

First, we create special custom data with the only information we need to characterize our cover constraints: the customer id that corresponds to this constraint.

struct CoverConstrData <: BlockDecomposition.AbstractCustomData
+    customer::Int
+end

We re-create the model:

(model, x, y, z, cov) = create_model(coluna, pricing_callback);

We declare our custom data to Coluna and we attach one custom data to each cover constraint

BlockDecomposition.customconstrs!(model, CoverConstrData);
+
+for i in customers
+    customdata!(cov[i], CoverConstrData(i))
+end

We perform the separation by enumeration (i.e. iterating over all subsets of customers of size three).

The subset-row cut has the following form:

\[\sum_{k \in K} \tilde{\alpha}(C, k) \lambda_{k} \leq 1\; C \subseteq I, |C| = 3,\]

where coefficient $\tilde{\alpha}(C, k)$ equals $1$ if route $k$ visits at least two customers of $C$; $0$ otherwise.

For instance, if we consider separating a cut over constraints cov[3], cov[6] and cov[8], then the route 1->4->6->7 has a zero coefficient while the route 1->4->6->3 has a coefficient equal to one.

Since columns are generated dynamically, we cannot pre-compute the coefficients of columns in the subset-row cuts. Instead, coefficients are computed dynamically via a user-defined computecoeff method which takes a cut and a column as arguments. To recognize which cut and which column are passed to the method, custom data structures are attached to the cut constraints and the master variables. When a new column is generated, Coluna computes its coefficients in the original constraints and robust cuts using coefficients of subproblem variables in the master constraints. Coluna retrieves coefficients of the new column in the non-robust cuts by calling the computecoeff method for the column and each such cut. When a new non-robust cut is generated, Coluna retrieves the coefficients of columns in this cut by calling the computecoeff method for the cut and all existing columns.

We now proceed to the implementation of necessary data structures and methods needed to support the subset-row cuts. First, we attach a custom data structure to master columns λ_k associated with a given route k. They record the set of customers that are visited by the given route k.

Thus, to each λ_k, we associate a R1cVarData structure that carries the customers it visits.

struct R1cVarData <: BlockDecomposition.AbstractCustomData
+    visited_locations::Vector{Int}
+end

Then, we attach a R1cCutData custom data structure to the subset-row cuts. It contains the set $C$ of customers characterizing the cut.

struct R1cCutData <: BlockDecomposition.AbstractCustomData
+    cov_constrs::Vector{Int}
+end

We declare our custom data to Coluna via BlockDecomposition add-ons:

BlockDecomposition.customvars!(model, R1cVarData)
+BlockDecomposition.customconstrs!(model, [CoverConstrData, R1cCutData]);

The next method calculates the coefficients of a column λ_k in a subset-row cut:

function Coluna.MathProg.computecoeff(
+    var_custom_data::R1cVarData, constr_custom_data::R1cCutData
+)
+    return floor(1 / 2 * length(var_custom_data.visited_locations ∩ constr_custom_data.cov_constrs))
+end

We also need to define a second method for the case of the cover constraints. Indeed, we use custom data to know the customer attached to each cover constraint There is no contribution of the non-robust part of the coefficient of the λ_k, so the method returns 0.

function Coluna.MathProg.computecoeff(::R1cVarData, ::CoverConstrData)
+    return 0
+end

We are now able to write our rank-one cut callback completely:

function r1c_callback(cbdata)
+    original_sol = cbdata.orig_sol
+    master = Coluna.MathProg.getmodel(original_sol)
+    # Retrieve the cover constraints.
+    cov_constrs = Int[]
+    for constr in values(Coluna.MathProg.getconstrs(master))
+        constr_custom_data = Coluna.MathProg.getcustomdata(master, constr)
+        if typeof(constr_custom_data) <: CoverConstrData
+            push!(cov_constrs, constr_custom_data.customer)
+        end
+    end
+
+    # Retrieve the master columns λ and their values in the current fractional solution
+    lambdas = Tuple{Float64,Coluna.MathProg.Variable}[]
+    for (var_id, val) in original_sol
+        if Coluna.MathProg.getduty(var_id) <= Coluna.MathProg.MasterCol
+            push!(lambdas, (val, Coluna.MathProg.getvar(master, var_id)))
+        end
+    end
+
+    # Separate the valid subset-row cuts violated by the current solution.
+    # For a fixed subset of customers of size three, iterate on the master columns
+    # and check if lhs > 1:
+    for cov_constr_subset in collect(combinations(cov_constrs, 3))
+        lhs = 0
+        for lambda in lambdas
+            (val, var) = lambda
+            var_custom_data = Coluna.MathProg.getcustomdata(master, var)
+            if !isnothing(var_custom_data)
+                coeff = floor(1 / 2 * length(var_custom_data.visited_locations ∩ cov_constr_subset))
+                lhs += coeff * val
+            end
+        end
+        if lhs > 1
+            # Create the constraint and add it to the model.
+            MOI.submit(model,
+                MOI.UserCut(cbdata),
+                JuMP.ScalarConstraint(JuMP.AffExpr(0.0), MOI.LessThan(1.0)),
+                R1cCutData(cov_constr_subset)
+            )
+        end
+    end
+end;

When creating non-robust constraints, only the linear (i.e., robust) part is passed to the model. In our case, the constraint 0 <= 1 is passed. As explained above, the non-robust part is computed by calling the computecoeff method using the structure of type R1cCutData provided.

Finally, we need to update our pricing callback to take into account the active non-robust cuts. The contribution of these cuts to the reduced cost of a column is not captured by the reduced cost of subproblem variables. We must therefore take this contribution into account manually, by inquiring the set of existing non-robust cuts and their values in the current dual solution.

The contribution of a subset-row cut to the reduced cost of a route is managed by the following method:

function r1c_contrib(route::Route, custduals)
+    cost = 0
+    if !isempty(custduals)
+        for (r1c_cov_constrs, dual) in custduals
+            coeff = floor(1 / 2 * length(route.path ∩ r1c_cov_constrs))
+            cost += coeff * dual
+        end
+    end
+    return cost
+end;

We re-write our pricing callback to:

  • retrieve the dual cost of the subset-row cuts
  • take into account the contribution of the subset-row cuts in the reduced cost of the route
  • attach custom data to the route so that its coefficient in the existing non-robust cuts can be computed
function pricing_callback(cbdata)
+    j = BlockDecomposition.indice(BlockDecomposition.callback_spid(cbdata, model))
+    z_red_costs = Dict(
+        "z_$(u)_$(v)" => BlockDecomposition.callback_reduced_cost(cbdata, z[u, v]) for u in locations, v in locations
+    )
+    x_red_costs = Dict(
+        "x_$(i)_$(j)" => BlockDecomposition.callback_reduced_cost(cbdata, x[i, j]) for i in customers
+    )
+
+    # FIRST CHANGE HERE:
+    # Get the dual values of the constraints of the specific type to compute the contributions of
+    # non-robust cuts to the cost of the solution:
+    master = cbdata.form.parent_formulation
+    custduals = Tuple{Vector{Int},Float64}[]
+    for (_, constr) in Coluna.MathProg.getconstrs(master)
+        constr_custom_data = Coluna.MathProg.getcustomdata(master, constr)
+        if typeof(constr_custom_data) == R1cCutData
+            push!(custduals, (
+                constr_custom_data.cov_constrs,
+                Coluna.MathProg.getcurincval(master, constr)
+            ))
+        end
+    end
+    # END OF FIRST CHANGE
+
+    # SECOND CHANGE HERE:
+    # Keep route with the minimum reduced cost: contribution of the subproblem variables and
+    # the non-robust cuts.
+    red_costs_j = map(r -> (
+            r,
+            x_contribution(r, j, x_red_costs) + z_contribution(r, z_red_costs) - r1c_contrib(r, custduals)
+        ), routes_per_facility[j]
+    )
+    # END OF SECOND CHANGE
+    min_index = argmin([x for (_, x) in red_costs_j])
+    best_route, min_reduced_cost = red_costs_j[min_index]
+
+    best_route_arcs = Tuple{Int,Int}[]
+    for i in 1:(best_route.length-1)
+        push!(best_route_arcs, (best_route.path[i], best_route.path[i+1]))
+    end
+    best_route_customers = best_route.path[2:best_route.length]
+    z_vars = [z[u, v] for (u, v) in best_route_arcs]
+    x_vars = [x[i, j] for i in best_route_customers]
+    sol_vars = vcat(z_vars, x_vars)
+    sol_vals = ones(Float64, length(z_vars) + length(x_vars))
+    sol_cost = min_reduced_cost
+
+    # Submit the solution of the subproblem to Coluna
+    # THIRD CHANGE HERE:
+    # You must attach the visited customers in the structure of type `R1cVarData` to the solution of the subproblem
+    MOI.submit(
+        model, BlockDecomposition.PricingSolution(cbdata), sol_cost, sol_vars, sol_vals,
+        R1cVarData(best_route.path)
+    )
+    # END OF THIRD CHANGE
+    MOI.submit(model, BlockDecomposition.PricingDualBound(cbdata), sol_cost)
+end
+
+MOI.set(model, MOI.UserCutCallback(), r1c_callback);
+JuMP.optimize!(model)
Coluna
+Version 0.7.0 | https://github.com/atoptima/Coluna.jl
+***************************************************************************************
+**** B&B tree root node
+**** Local DB = -Inf, global bounds: [ -Inf , Inf ], time = 0.00 sec.
+***************************************************************************************
+  <st= 1> <it=  1> <et= 1.17> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=-89725.0000> <mlp=70000.0000> <PB=Inf>
+  <st= 1> <it=  2> <et= 1.19> <mst= 0.00> <sp= 0.02> <cols= 2> <al= 0.00> <DB=-89624.5000> <mlp=30132.7000> <PB=Inf>
+Cut separation callback adds 0 new essential cuts and 0 new facultative cuts.
+  <st= 1> <it=  3> <et= 1.19> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB= -550.8000> <mlp=  327.4000> <PB=327.4000>
+  <st= 1> <it=  4> <et= 1.19> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB= -370.2000> <mlp=  327.4000> <PB=327.4000>
+  <st= 1> <it=  5> <et= 1.19> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB= -364.0000> <mlp=  327.4000> <PB=327.4000>
+  <st= 1> <it=  6> <et= 1.20> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB= -301.0000> <mlp=  327.4000> <PB=327.4000>
+  <st= 1> <it=  7> <et= 1.20> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  -84.9000> <mlp=  306.9667> <PB=327.4000>
+  <st= 1> <it=  8> <et= 1.20> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  121.6500> <mlp=  281.7500> <PB=327.4000>
+  <st= 1> <it=  9> <et= 1.20> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  184.1500> <mlp=  281.7500> <PB=327.4000>
+  <st= 1> <it= 10> <et= 1.20> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  243.9500> <mlp=  281.7500> <PB=327.4000>
+  <st= 1> <it= 11> <et= 1.20> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  265.7000> <mlp=  279.3000> <PB=327.4000>
+  <st= 1> <it= 12> <et= 1.20> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  267.8750> <mlp=  276.9750> <PB=327.4000>
+  <st= 1> <it= 13> <et= 1.20> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  272.7750> <mlp=  276.9750> <PB=327.4000>
+  <st= 1> <it= 14> <et= 1.20> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  271.3750> <mlp=  276.9750> <PB=327.4000>
+  <st= 1> <it= 15> <et= 1.20> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  264.0250> <mlp=  276.8250> <PB=327.4000>
+  <st= 1> <it= 16> <et= 1.20> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  274.3500> <mlp=  275.7500> <PB=327.4000>
+  <st= 1> <it= 17> <et= 1.20> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  274.7500> <mlp=  275.7500> <PB=327.4000>
+  <st= 1> <it= 18> <et= 1.21> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  275.7500> <mlp=  275.7500> <PB=327.4000>
+Cut separation callback adds 0 new essential cuts and 17 new facultative cuts.
+avg. viol. = 17.35, max. viol. = 21.00, zero viol. = 0.
+Cut separation callback adds 0 new essential cuts and 0 new facultative cuts.
+  <st= 1> <it=  1> <et= 1.49> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=-79206.3000> <mlp=  297.7000> <PB=297.7000>
+  <st= 1> <it=  2> <et= 1.49> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=   69.1000> <mlp=  297.7000> <PB=297.7000>
+  <st= 1> <it=  3> <et= 1.51> <mst= 0.00> <sp= 0.02> <cols= 2> <al= 0.00> <DB=  224.1000> <mlp=  294.5000> <PB=297.7000>
+  <st= 1> <it=  4> <et= 1.52> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  200.7667> <mlp=  294.5000> <PB=297.7000>
+  <st= 1> <it=  5> <et= 1.52> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  232.5000> <mlp=  294.5000> <PB=297.7000>
+  <st= 1> <it=  6> <et= 1.52> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  226.6000> <mlp=  294.5000> <PB=297.7000>
+  <st= 1> <it=  7> <et= 1.52> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  220.1462> <mlp=  294.5000> <PB=297.7000>
+  <st= 1> <it=  8> <et= 1.53> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  188.1400> <mlp=  294.5000> <PB=297.7000>
+Cut separation callback adds 0 new essential cuts and 0 new facultative cuts.
+  <st= 1> <it=  9> <et= 1.53> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  227.6222> <mlp=  289.8000> <PB=289.8000>
+  <st= 1> <it= 10> <et= 1.53> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  238.3000> <mlp=  289.8000> <PB=289.8000>
+  <st= 1> <it= 11> <et= 1.54> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  214.3636> <mlp=  289.5273> <PB=289.8000>
+  <st= 1> <it= 12> <et= 1.54> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  235.7286> <mlp=  286.5571> <PB=289.8000>
+  <st= 1> <it= 13> <et= 1.54> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  220.7143> <mlp=  285.5429> <PB=289.8000>
+  <st= 1> <it= 14> <et= 1.54> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  249.7143> <mlp=  285.5429> <PB=289.8000>
+  <st= 1> <it= 15> <et= 1.56> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  228.6286> <mlp=  285.5429> <PB=289.8000>
+  <st= 1> <it= 16> <et= 1.57> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  214.2833> <mlp=  284.1833> <PB=289.8000>
+  <st= 1> <it= 17> <et= 1.57> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  248.9556> <mlp=  283.6667> <PB=289.8000>
+  <st= 1> <it= 18> <et= 1.57> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  249.6000> <mlp=  283.6000> <PB=289.8000>
+  <st= 1> <it= 19> <et= 1.58> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  263.8000> <mlp=  283.6000> <PB=289.8000>
+  <st= 1> <it= 20> <et= 1.58> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  281.5727> <mlp=  283.5364> <PB=289.8000>
+  <st= 1> <it= 21> <et= 1.58> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  281.6091> <mlp=  283.5364> <PB=289.8000>
+  <st= 1> <it= 22> <et= 1.59> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  282.7000> <mlp=  283.4400> <PB=289.8000>
+  <st= 1> <it= 23> <et= 1.59> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  281.9600> <mlp=  283.4400> <PB=289.8000>
+  <st= 1> <it= 24> <et= 1.59> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  283.3727> <mlp=  283.3727> <PB=289.8000>
+Cut separation callback adds 0 new essential cuts and 16 new facultative cuts.
+avg. viol. = 41.00, max. viol. = 46.00, zero viol. = 0.
+  <st= 1> <it=  1> <et= 1.62> <mst= 0.00> <sp= 0.02> <cols= 2> <al= 0.00> <DB=-59558.4000> <mlp=  289.8000> <PB=289.8000>
+  <st= 1> <it=  2> <et= 1.62> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB= -200.0000> <mlp=  289.8000> <PB=289.8000>
+  <st= 1> <it=  3> <et= 1.63> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  165.4000> <mlp=  289.8000> <PB=289.8000>
+  <st= 1> <it=  4> <et= 1.63> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  225.4000> <mlp=  289.8000> <PB=289.8000>
+  <st= 1> <it=  5> <et= 1.64> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  220.8000> <mlp=  289.8000> <PB=289.8000>
+  <st= 1> <it=  6> <et= 1.64> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  187.2000> <mlp=  289.8000> <PB=289.8000>
+  <st= 1> <it=  7> <et= 1.64> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  179.2000> <mlp=  289.8000> <PB=289.8000>
+  <st= 1> <it=  8> <et= 1.66> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  245.0000> <mlp=  289.8000> <PB=289.8000>
+  <st= 1> <it=  9> <et= 1.66> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  244.8000> <mlp=  289.8000> <PB=289.8000>
+  <st= 1> <it= 10> <et= 1.67> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  273.6000> <mlp=  289.8000> <PB=289.8000>
+  <st= 1> <it= 11> <et= 1.67> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  272.6000> <mlp=  289.8000> <PB=289.8000>
+  <st= 1> <it= 12> <et= 1.67> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  284.2000> <mlp=  289.8000> <PB=289.8000>
+  <st= 1> <it= 13> <et= 1.68> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  287.9500> <mlp=  289.8000> <PB=289.8000>
+  <st= 1> <it= 14> <et= 1.69> <mst= 0.00> <sp= 0.01> <cols= 1> <al= 0.00> <DB=  288.0000> <mlp=  289.8000> <PB=289.8000>
+  <st= 1> <it= 15> <et= 1.69> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  289.6000> <mlp=  289.8000> <PB=289.8000>
+  <st= 1> <it= 16> <et= 1.70> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  289.8000> <mlp=  289.8000> <PB=289.8000>
+ ──────────────────────────────────────────────────────────────────────────
+                                  Time                    Allocations
+                         ───────────────────────   ────────────────────────
+    Tot / % measured:         3.09s /  55.0%            403MiB /  80.3%
+
+ Section         ncalls     time    %tot     avg     alloc    %tot      avg
+ ──────────────────────────────────────────────────────────────────────────
+ Coluna               1    1.70s  100.0%   1.70s    324MiB  100.0%   324MiB
+   SolveLpForm       58   20.1ms    1.2%   347μs   8.26MiB    2.5%   146KiB
+ ──────────────────────────────────────────────────────────────────────────
+[ Info: Terminated
+[ Info: Primal bound: 289.79999999999995
+[ Info: Dual bound: 289.7999999999999

Multi-stage pricing callback

In this section, we implement a pricing heuristic that can be used together with the exact pricing callback to generate subproblems solutions.

The idea of the heuristic is very simple:

  • Given a facility j, the heuristic finds the closest customer to j and adds it to the route.
  • Then, while the reduced cost keeps improving and the maximum length of the route is not reached, the heuristic computes and adds to the route the nearest neighbor to the last customer of the route.

We first define an auxiliary function used to compute the route tail's nearest neighbor at each step:

function add_nearest_neighbor(route::Route, customers, costs)
+    # Get the last customer of the route.
+    loc = last(route.path)
+    # Initialize its nearest neighbor to zero and mincost to infinity.
+    (nearest, mincost) = (0, Inf)
+    # Compute nearest and mincost.
+    for i in customers
+        if !(i in route.path) # implying in particular (i != loc)
+            if (costs[loc, i] < mincost)
+                nearest = i
+                mincost = costs[loc, i]
+            end
+        end
+    end
+    # Add the last customer's nearest neighbor to the route.
+    if nearest != 0
+        push!(route.path, nearest)
+        route.length += 1
+    end
+end;

We then define our heuristic for the enumeration of the routes, the method returns the best route found by the heuristic together with its cost:

function enumeration_heuristic(x_red_costs, z_red_costs, j)
+    # Initialize our "greedy best route".
+    best_route = Route(1, [j])
+    # Initialize the route's cost to zero.
+    current_redcost = 0.0
+    old_redcost = Inf
+
+    # main loop
+    while (current_redcost < old_redcost)
+        add_nearest_neighbor(best_route, customers, arc_costs)
+        old_redcost = current_redcost
+        current_redcost = x_contribution(best_route, j, x_red_costs) +
+                          z_contribution(best_route, z_red_costs)
+        # Max length is reached.
+        if best_route.length == nb_positions
+            break
+        end
+    end
+    return (best_route, current_redcost)
+end;

We can now define our heuristic pricing callback:

function approx_pricing(cbdata)
+
+    j = BlockDecomposition.indice(BlockDecomposition.callback_spid(cbdata, model))
+    z_red_costs = Dict(
+        "z_$(u)_$(v)" => BlockDecomposition.callback_reduced_cost(cbdata, z[u, v]) for u in locations, v in locations
+    )
+    x_red_costs = Dict(
+        "x_$(i)_$(j)" => BlockDecomposition.callback_reduced_cost(cbdata, x[i, j]) for i in customers
+    )
+
+    # Call the heuristic to elect the "greedy best route":
+    best_route, sol_cost = enumeration_heuristic(x_red_costs, z_red_costs, j)
+
+    # Build the solution:
+    best_route_arcs = Vector{Tuple{Int,Int}}()
+    for i in 1:(best_route.length-1)
+        push!(best_route_arcs, (best_route.path[i], best_route.path[i+1]))
+    end
+    best_route_customers = best_route.path[2:length(best_route.path)]
+
+    z_vars = [z[u, v] for (u, v) in best_route_arcs]
+    x_vars = [x[i, j] for i in best_route_customers]
+    sol_vars = vcat(z_vars, x_vars)
+    sol_vals = ones(Float64, length(z_vars) + length(x_vars))
+
+    MOI.submit(model, BlockDecomposition.PricingSolution(cbdata), sol_cost, sol_vars, sol_vals)
+    # As the procedure is inexact, no dual bound can be computed, we set it to -Inf.
+    MOI.submit(model, BlockDecomposition.PricingDualBound(cbdata), -Inf)
+end;

We set the solver; colgen_stages_pricing_solvers indicates which solver to use first (here it is approx_pricing)

coluna = JuMP.optimizer_with_attributes(
+    Coluna.Optimizer,
+    "default_optimizer" => GLPK.Optimizer,
+    "params" => Coluna.Params(
+        solver=Coluna.Algorithm.BranchCutAndPriceAlgorithm(
+            maxnumnodes=100,
+            colgen_stages_pricing_solvers=[2, 1]
+        )
+    )
+);

We add the two pricing algorithms to our model:

model, x, y, z, cov = create_model(coluna, [approx_pricing, pricing_callback]);

We declare our custom data to Coluna:

BlockDecomposition.customvars!(model, R1cVarData)
+BlockDecomposition.customconstrs!(model, [CoverConstrData, R1cCutData]);
+for i in customers
+    customdata!(cov[i], CoverConstrData(i))
+end

Optimize:

JuMP.optimize!(model)
Coluna
+Version 0.7.0 | https://github.com/atoptima/Coluna.jl
+***************************************************************************************
+**** B&B tree root node
+**** Local DB = -Inf, global bounds: [ -Inf , Inf ], time = 0.00 sec.
+***************************************************************************************
+  <st= 2> <it=  1> <et= 0.34> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=      -Inf> <mlp=70000.0000> <PB=Inf>
+  <st= 2> <it=  2> <et= 0.34> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=      -Inf> <mlp=40114.4000> <PB=Inf>
+  <st= 2> <it=  3> <et= 0.34> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=      -Inf> <mlp=40114.4000> <PB=Inf>
+  <st= 1> <it=  4> <et= 0.34> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=-119113.8000> <mlp=40114.4000> <PB=Inf>
+  <st= 1> <it=  5> <et= 0.34> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB= -431.9000> <mlp=  309.1000> <PB=309.1000>
+  <st= 1> <it=  6> <et= 0.34> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB= -403.5000> <mlp=  309.1000> <PB=309.1000>
+  <st= 1> <it=  7> <et= 0.34> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB= -298.5000> <mlp=  309.1000> <PB=309.1000>
+  <st= 1> <it=  8> <et= 0.34> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  -91.8333> <mlp=  309.1000> <PB=309.1000>
+  <st= 1> <it=  9> <et= 0.34> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  -67.3000> <mlp=  309.1000> <PB=309.1000>
+  <st= 1> <it= 10> <et= 0.35> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  -76.5000> <mlp=  309.1000> <PB=309.1000>
+  <st= 1> <it= 11> <et= 0.35> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB= -187.1000> <mlp=  309.1000> <PB=309.1000>
+  <st= 1> <it= 12> <et= 0.35> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  -90.5000> <mlp=  309.1000> <PB=309.1000>
+  <st= 1> <it= 13> <et= 0.35> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  -87.1500> <mlp=  304.8500> <PB=309.1000>
+  <st= 1> <it= 14> <et= 0.35> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  266.9500> <mlp=  279.8500> <PB=309.1000>
+  <st= 1> <it= 15> <et= 0.35> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  270.2250> <mlp=  277.2750> <PB=309.1000>
+  <st= 1> <it= 16> <et= 0.35> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  272.0750> <mlp=  277.2750> <PB=309.1000>
+  <st= 1> <it= 17> <et= 0.35> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  271.2000> <mlp=  277.0000> <PB=309.1000>
+  <st= 1> <it= 18> <et= 0.35> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  273.6750> <mlp=  276.2750> <PB=309.1000>
+  <st= 1> <it= 19> <et= 0.35> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  272.6750> <mlp=  276.2750> <PB=309.1000>
+  <st= 1> <it= 20> <et= 0.35> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  275.2250> <mlp=  275.8250> <PB=309.1000>
+  <st= 1> <it= 21> <et= 0.35> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  275.3500> <mlp=  275.7500> <PB=309.1000>
+  <st= 1> <it= 22> <et= 0.36> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  275.7500> <mlp=  275.7500> <PB=309.1000>
+***************************************************************************************
+**** B&B tree node N°3, parent N°1, depth 1, 1 untreated node
+**** Local DB = 275.7500, global bounds: [ -Inf , 293.8000 ], time = 0.38 sec.
+**** Branching constraint: z[4,3]<=0.0
+***************************************************************************************
+  <st= 2> <it=  1> <et= 0.38> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=      -Inf> <mlp=  276.5500> <PB=293.8000>
+  <st= 1> <it=  2> <et= 0.39> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  272.7500> <mlp=  276.5500> <PB=293.8000>
+  <st= 1> <it=  3> <et= 0.39> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  276.1500> <mlp=  276.5500> <PB=293.8000>
+  <st= 1> <it=  4> <et= 0.39> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  276.5000> <mlp=  276.5000> <PB=293.8000>
+***************************************************************************************
+**** B&B tree node N°5, parent N°3, depth 2, 2 untreated nodes
+**** Local DB = 276.5000, global bounds: [ 275.7500 , 293.8000 ], time = 0.39 sec.
+**** Branching constraint: z[1,4]<=0.0
+***************************************************************************************
+  <st= 2> <it=  1> <et= 0.39> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=      -Inf> <mlp=  277.6000> <PB=293.8000>
+  <st= 1> <it=  2> <et= 0.39> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  270.6000> <mlp=  277.6000> <PB=293.8000>
+  <st= 1> <it=  3> <et= 0.39> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  277.2500> <mlp=  277.2500> <PB=293.8000>
+***************************************************************************************
+**** B&B tree node N°7, parent N°5, depth 3, 3 untreated nodes
+**** Local DB = 277.2500, global bounds: [ 275.7500 , 293.8000 ], time = 0.39 sec.
+**** Branching constraint: z[3,6]<=0.0
+***************************************************************************************
+  <st= 2> <it=  1> <et= 0.39> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=      -Inf> <mlp=  278.4500> <PB=293.8000>
+  <st= 1> <it=  2> <et= 0.39> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  278.4500> <mlp=  278.4500> <PB=293.8000>
+***************************************************************************************
+**** B&B tree node N°9, parent N°7, depth 4, 4 untreated nodes
+**** Local DB = 278.4500, global bounds: [ 275.7500 , 293.8000 ], time = 0.39 sec.
+**** Branching constraint: z[3,4]<=0.0
+***************************************************************************************
+  <st= 2> <it=  1> <et= 0.40> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=      -Inf> <mlp=  295.3500> <PB=293.8000>
+  <st= 1> <it=  2> <et= 0.40> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  116.0500> <mlp=  295.3500> <PB=293.8000>
+  <st= 1> <it=  3> <et= 0.40> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  180.0500> <mlp=  294.2500> <PB=293.8000>
+  <st= 1> <it=  4> <et= 0.40> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  289.4500> <mlp=  294.2500> <PB=293.8000>
+  <st= 1> <it=  5> <et= 0.40> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  291.3500> <mlp=  294.2500> <PB=293.8000>
+  <st= 1> <it=  6> <et= 0.42> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  292.7500> <mlp=  294.2500> <PB=293.8000>
+  <st= 1> <it=  7> <et= 0.42> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  292.1000> <mlp=  293.5000> <PB=293.5000>
+  <st= 1> <it=  8> <et= 0.42> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  292.3000> <mlp=  293.5000> <PB=293.5000>
+  <st= 1> <it=  9> <et= 0.42> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  293.5000> <mlp=  293.5000> <PB=293.5000>
+***************************************************************************************
+**** B&B tree node N°8, parent N°7, depth 4, 3 untreated nodes
+**** Local DB = 278.4500, global bounds: [ 275.7500 , 293.5000 ], time = 0.42 sec.
+**** Branching constraint: z[3,4]>=1.0
+***************************************************************************************
+  <st= 2> <it=  1> <et= 0.43> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=      -Inf> <mlp=  312.0000> <PB=293.5000>
+  <st= 1> <it=  2> <et= 0.43> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  130.2000> <mlp=  312.0000> <PB=293.5000>
+  <st= 1> <it=  3> <et= 0.43> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  288.7000> <mlp=  298.9000> <PB=293.5000>
+  <st= 1> <it=  4> <et= 0.43> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  292.9000> <mlp=  298.9000> <PB=293.5000>
+  <st= 1> <it=  5> <et= 0.43> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  288.3000> <mlp=  298.9000> <PB=293.5000>
+  <st= 1> <it=  6> <et= 0.43> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  290.3000> <mlp=  298.9000> <PB=293.5000>
+  <st= 1> <it=  7> <et= 0.43> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  296.3000> <mlp=  296.3000> <PB=293.5000>
+***************************************************************************************
+**** B&B tree node N°6, parent N°5, depth 3, 2 untreated nodes
+**** Local DB = 277.2500, global bounds: [ 275.7500 , 293.5000 ], time = 0.43 sec.
+**** Branching constraint: z[3,6]>=1.0
+***************************************************************************************
+  <st= 2> <it=  1> <et= 0.43> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=      -Inf> <mlp=  293.8000> <PB=293.5000>
+  <st= 1> <it=  2> <et= 0.43> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  112.8000> <mlp=  293.8000> <PB=293.5000>
+  <st= 1> <it=  3> <et= 0.43> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  255.9000> <mlp=  279.7000> <PB=293.5000>
+  <st= 1> <it=  4> <et= 0.43> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  277.9000> <mlp=  279.7000> <PB=293.5000>
+  <st= 1> <it=  5> <et= 0.44> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  279.2500> <mlp=  279.2500> <PB=293.5000>
+***************************************************************************************
+**** B&B tree node N°11, parent N°6, depth 4, 3 untreated nodes
+**** Local DB = 279.2500, global bounds: [ 275.7500 , 293.5000 ], time = 0.44 sec.
+**** Branching constraint: z[7,3]<=0.0
+***************************************************************************************
+  <st= 2> <it=  1> <et= 0.44> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=      -Inf> <mlp=  280.5000> <PB=293.5000>
+  <st= 1> <it=  2> <et= 0.44> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  280.5000> <mlp=  280.5000> <PB=293.5000>
+***************************************************************************************
+**** B&B tree node N°13, parent N°11, depth 5, 4 untreated nodes
+**** Local DB = 280.5000, global bounds: [ 275.7500 , 293.5000 ], time = 0.44 sec.
+**** Branching constraint: y[1]<=0.0
+***************************************************************************************
+  <st= 2> <it=  1> <et= 0.44> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=      -Inf> <mlp=  316.2000> <PB=293.5000>
+  <st= 1> <it=  2> <et= 0.44> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=-4860.0000> <mlp=  316.2000> <PB=293.5000>
+  <st= 1> <it=  3> <et= 0.44> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  259.1200> <mlp=  287.2400> <PB=293.5000>
+  <st= 1> <it=  4> <et= 0.44> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  256.1750> <mlp=  283.7250> <PB=293.5000>
+  <st= 1> <it=  5> <et= 0.44> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  278.7250> <mlp=  283.7250> <PB=293.5000>
+  <st= 1> <it=  6> <et= 0.45> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  283.7250> <mlp=  283.7250> <PB=293.5000>
+***************************************************************************************
+**** B&B tree node N°15, parent N°13, depth 6, 5 untreated nodes
+**** Local DB = 283.7250, global bounds: [ 275.7500 , 293.5000 ], time = 0.45 sec.
+**** Branching constraint: z[7,4]<=0.0
+***************************************************************************************
+  <st= 2> <it=  1> <et= 0.45> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=      -Inf> <mlp=  302.4833> <PB=293.5000>
+  <st= 1> <it=  2> <et= 0.45> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  215.5833> <mlp=  302.4833> <PB=293.5000>
+  <st= 1> <it=  3> <et= 0.45> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  241.6833> <mlp=  302.4833> <PB=293.5000>
+  <st= 1> <it=  4> <et= 0.45> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  270.6500> <mlp=  292.3500> <PB=293.5000>
+  <st= 1> <it=  5> <et= 0.45> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  272.8500> <mlp=  292.3500> <PB=293.5000>
+  <st= 1> <it=  6> <et= 0.45> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  290.4000> <mlp=  290.4000> <PB=293.5000>
+***************************************************************************************
+**** B&B tree node N°17, parent N°15, depth 7, 6 untreated nodes
+**** Local DB = 290.4000, global bounds: [ 275.7500 , 293.5000 ], time = 0.45 sec.
+**** Branching constraint: z[5,3]<=0.0
+***************************************************************************************
+  <st= 2> <it=  1> <et= 0.45> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=      -Inf> <mlp=  318.7000> <PB=293.5000>
+  <st= 1> <it=  2> <et= 0.45> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  261.1000> <mlp=  318.7000> <PB=293.5000>
+  <st= 1> <it=  3> <et= 0.46> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  296.9000> <mlp=  304.3000> <PB=293.5000>
+***************************************************************************************
+**** B&B tree node N°16, parent N°15, depth 7, 5 untreated nodes
+**** Local DB = 290.4000, global bounds: [ 275.7500 , 293.5000 ], time = 0.46 sec.
+**** Branching constraint: z[5,3]>=1.0
+***************************************************************************************
+  <st= 2> <it=  1> <et= 0.46> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=      -Inf> <mlp=  334.2000> <PB=293.5000>
+  <st= 1> <it=  2> <et= 0.46> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=-2713.1143> <mlp=  334.2000> <PB=293.5000>
+  <st= 1> <it=  3> <et= 0.46> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  273.9000> <mlp=  311.5000> <PB=293.5000>
+  <st= 1> <it=  4> <et= 0.46> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  296.0000> <mlp=  298.6000> <PB=293.5000>
+***************************************************************************************
+**** B&B tree node N°14, parent N°13, depth 6, 4 untreated nodes
+**** Local DB = 283.7250, global bounds: [ 275.7500 , 293.5000 ], time = 0.46 sec.
+**** Branching constraint: z[7,4]>=1.0
+***************************************************************************************
+  <st= 2> <it=  1> <et= 0.46> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=      -Inf> <mlp=  299.5000> <PB=293.5000>
+  <st= 1> <it=  2> <et= 0.46> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  110.1000> <mlp=  299.5000> <PB=293.5000>
+  <st= 1> <it=  3> <et= 0.46> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  293.8000> <mlp=  299.5000> <PB=293.5000>
+***************************************************************************************
+**** B&B tree node N°12, parent N°11, depth 5, 3 untreated nodes
+**** Local DB = 280.5000, global bounds: [ 275.7500 , 293.5000 ], time = 0.46 sec.
+**** Branching constraint: y[1]>=1.0
+***************************************************************************************
+  <st= 2> <it=  1> <et= 0.46> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=      -Inf> <mlp=  304.6000> <PB=293.5000>
+  <st= 1> <it=  2> <et= 0.46> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  247.6000> <mlp=  304.6000> <PB=293.5000>
+  <st= 1> <it=  3> <et= 0.47> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  277.5000> <mlp=  304.6000> <PB=293.5000>
+  <st= 1> <it=  4> <et= 0.47> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  268.6000> <mlp=  304.6000> <PB=293.5000>
+  <st= 1> <it=  5> <et= 0.47> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  281.0000> <mlp=  304.6000> <PB=293.5000>
+  <st= 1> <it=  6> <et= 0.47> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  289.8000> <mlp=  292.8000> <PB=292.8000>
+  <st= 1> <it=  7> <et= 0.47> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  292.8000> <mlp=  292.8000> <PB=292.8000>
+***************************************************************************************
+**** B&B tree node N°10, parent N°6, depth 4, 2 untreated nodes
+**** Local DB = 279.2500, global bounds: [ 275.7500 , 292.8000 ], time = 0.47 sec.
+**** Branching constraint: z[7,3]>=1.0
+***************************************************************************************
+  <st= 2> <it=  1> <et= 0.47> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=      -Inf> <mlp=  293.8000> <PB=292.8000>
+  <st= 1> <it=  2> <et= 0.47> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  284.6000> <mlp=  293.8000> <PB=292.8000>
+  <st= 1> <it=  3> <et= 0.47> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  292.8000> <mlp=  293.8000> <PB=292.8000>
+***************************************************************************************
+**** B&B tree node N°4, parent N°3, depth 2, 1 untreated node
+**** Local DB = 276.5000, global bounds: [ 275.7500 , 292.8000 ], time = 0.47 sec.
+**** Branching constraint: z[1,4]>=1.0
+***************************************************************************************
+  <st= 2> <it=  1> <et= 0.47> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=      -Inf> <mlp=  291.2000> <PB=292.8000>
+  <st= 1> <it=  2> <et= 0.47> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  151.0000> <mlp=  291.2000> <PB=292.8000>
+  <st= 1> <it=  3> <et= 0.47> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  257.0200> <mlp=  279.3400> <PB=292.8000>
+  <st= 1> <it=  4> <et= 0.47> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  279.3400> <mlp=  279.3400> <PB=292.8000>
+***************************************************************************************
+**** B&B tree node N°19, parent N°4, depth 3, 2 untreated nodes
+**** Local DB = 279.3400, global bounds: [ 275.7500 , 290.3000 ], time = 0.48 sec.
+**** Branching constraint: z[7,3]<=0.0
+***************************************************************************************
+  <st= 2> <it=  1> <et= 0.48> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=      -Inf> <mlp=  292.8000> <PB=290.3000>
+  <st= 1> <it=  2> <et= 0.48> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  171.6000> <mlp=  292.8000> <PB=290.3000>
+  <st= 1> <it=  3> <et= 0.48> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  281.6400> <mlp=  281.6400> <PB=290.3000>
+***************************************************************************************
+**** B&B tree node N°21, parent N°19, depth 4, 3 untreated nodes
+**** Local DB = 281.6400, global bounds: [ 275.7500 , 290.3000 ], time = 0.48 sec.
+**** Branching constraint: z[3,6]<=0.0
+***************************************************************************************
+  <st= 2> <it=  1> <et= 0.48> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=      -Inf> <mlp=  293.9500> <PB=290.3000>
+  <st= 1> <it=  2> <et= 0.48> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  288.8500> <mlp=  293.9500> <PB=290.3000>
+  <st= 1> <it=  3> <et= 0.48> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  289.9000> <mlp=  291.4000> <PB=290.3000>
+  <st= 1> <it=  4> <et= 0.50> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  291.4000> <mlp=  291.4000> <PB=290.3000>
+***************************************************************************************
+**** B&B tree node N°20, parent N°19, depth 4, 2 untreated nodes
+**** Local DB = 281.6400, global bounds: [ 275.7500 , 290.3000 ], time = 0.50 sec.
+**** Branching constraint: z[3,6]>=1.0
+***************************************************************************************
+  <st= 2> <it=  1> <et= 0.50> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=      -Inf> <mlp=  292.8000> <PB=290.3000>
+  <st= 1> <it=  2> <et= 0.51> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  235.2000> <mlp=  292.8000> <PB=290.3000>
+  <st= 1> <it=  3> <et= 0.51> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  235.4000> <mlp=  289.8000> <PB=289.8000>
+  <st= 1> <it=  4> <et= 0.51> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  285.1400> <mlp=  285.1400> <PB=289.8000>
+***************************************************************************************
+**** B&B tree node N°23, parent N°20, depth 5, 3 untreated nodes
+**** Local DB = 285.1400, global bounds: [ 275.7500 , 289.8000 ], time = 0.51 sec.
+**** Branching constraint: x[3,2]<=0.0
+***************************************************************************************
+  <st= 2> <it=  1> <et= 0.51> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=      -Inf> <mlp=  289.8000> <PB=289.8000>
+  <st= 1> <it=  2> <et= 0.51> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  286.2000> <mlp=  289.8000> <PB=289.8000>
+  <st= 1> <it=  3> <et= 0.51> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  289.8000> <mlp=  289.8000> <PB=289.8000>
+***************************************************************************************
+**** B&B tree node N°22, parent N°20, depth 5, 2 untreated nodes
+**** Local DB = 285.1400, global bounds: [ 275.7500 , 289.8000 ], time = 0.51 sec.
+**** Branching constraint: x[3,2]>=1.0
+***************************************************************************************
+  <st= 2> <it=  1> <et= 0.51> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=      -Inf> <mlp=  292.8000> <PB=289.8000>
+  <st= 1> <it=  2> <et= 0.51> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  292.8000> <mlp=  292.8000> <PB=289.8000>
+***************************************************************************************
+**** B&B tree node N°18, parent N°4, depth 3, 1 untreated node
+**** Local DB = 279.3400, global bounds: [ 275.7500 , 289.8000 ], time = 0.51 sec.
+**** Branching constraint: z[7,3]>=1.0
+***************************************************************************************
+  <st= 2> <it=  1> <et= 0.51> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=      -Inf> <mlp=  290.3000> <PB=289.8000>
+  <st= 1> <it=  2> <et= 0.52> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=   77.9000> <mlp=  290.3000> <PB=289.8000>
+  <st= 1> <it=  3> <et= 0.52> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  183.2333> <mlp=  290.3000> <PB=289.8000>
+  <st= 1> <it=  4> <et= 0.52> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  273.9600> <mlp=  286.7600> <PB=289.8000>
+  <st= 1> <it=  5> <et= 0.52> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  274.5200> <mlp=  282.9200> <PB=289.8000>
+  <st= 1> <it=  6> <et= 0.52> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  282.0800> <mlp=  282.0800> <PB=289.8000>
+***************************************************************************************
+**** B&B tree node N°25, parent N°18, depth 4, 2 untreated nodes
+**** Local DB = 282.0800, global bounds: [ 275.7500 , 289.8000 ], time = 0.52 sec.
+**** Branching constraint: z[1,5]<=0.0
+***************************************************************************************
+  <st= 2> <it=  1> <et= 0.52> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=      -Inf> <mlp=  285.9200> <PB=289.8000>
+  <st= 1> <it=  2> <et= 0.52> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  285.9200> <mlp=  285.9200> <PB=289.8000>
+***************************************************************************************
+**** B&B tree node N°27, parent N°25, depth 5, 3 untreated nodes
+**** Local DB = 285.9200, global bounds: [ 275.7500 , 289.8000 ], time = 0.52 sec.
+**** Branching constraint: y[1]<=0.0
+***************************************************************************************
+  <st= 2> <it=  1> <et= 0.52> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=      -Inf> <mlp= 5286.7600> <PB=289.8000>
+  <st= 1> <it=  2> <et= 0.52> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB= 5281.7600> <mlp= 5286.7600> <PB=289.8000>
+# <st= 1> <it=  3> <et= 0.73> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB= 5000.0000> <mlp= 5000.0000> <PB=289.8000>
+***************************************************************************************
+**** B&B tree node N°26, parent N°25, depth 5, 2 untreated nodes
+**** Local DB = 285.9200, global bounds: [ 275.7500 , 289.8000 ], time = 0.73 sec.
+**** Branching constraint: y[1]>=1.0
+***************************************************************************************
+  <st= 2> <it=  1> <et= 0.73> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=      -Inf> <mlp=  301.1000> <PB=289.8000>
+  <st= 1> <it=  2> <et= 0.73> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  280.1000> <mlp=  301.1000> <PB=289.8000>
+  <st= 1> <it=  3> <et= 0.73> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  290.6000> <mlp=  290.6000> <PB=289.8000>
+***************************************************************************************
+**** B&B tree node N°24, parent N°18, depth 4, 1 untreated node
+**** Local DB = 282.0800, global bounds: [ 275.7500 , 289.8000 ], time = 0.73 sec.
+**** Branching constraint: z[1,5]>=1.0
+***************************************************************************************
+  <st= 2> <it=  1> <et= 0.73> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=      -Inf> <mlp=  290.3000> <PB=289.8000>
+  <st= 1> <it=  2> <et= 0.73> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  248.6000> <mlp=  290.3000> <PB=289.8000>
+  <st= 1> <it=  3> <et= 0.73> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  280.2500> <mlp=  290.3000> <PB=289.8000>
+  <st= 1> <it=  4> <et= 0.73> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  281.8000> <mlp=  290.3000> <PB=289.8000>
+  <st= 1> <it=  5> <et= 0.74> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  272.6000> <mlp=  290.3000> <PB=289.8000>
+  <st= 1> <it=  6> <et= 0.74> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  289.2000> <mlp=  290.3000> <PB=289.8000>
+  <st= 1> <it=  7> <et= 0.74> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  290.3000> <mlp=  290.3000> <PB=289.8000>
+***************************************************************************************
+**** B&B tree node N°2, parent N°1, depth 1, 0 untreated node
+**** Local DB = 275.7500, global bounds: [ 275.7500 , 289.8000 ], time = 0.74 sec.
+**** Branching constraint: z[4,3]>=1.0
+***************************************************************************************
+  <st= 2> <it=  1> <et= 0.74> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=      -Inf> <mlp=  299.7000> <PB=289.8000>
+  <st= 1> <it=  2> <et= 0.74> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  168.0333> <mlp=  299.7000> <PB=289.8000>
+  <st= 1> <it=  3> <et= 0.74> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  -51.9000> <mlp=  299.7000> <PB=289.8000>
+  <st= 1> <it=  4> <et= 0.74> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  251.9000> <mlp=  281.5400> <PB=289.8000>
+  <st= 1> <it=  5> <et= 0.74> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  273.9000> <mlp=  279.3000> <PB=289.8000>
+  <st= 1> <it=  6> <et= 0.74> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  278.6250> <mlp=  278.6250> <PB=289.8000>
+***************************************************************************************
+**** B&B tree node N°29, parent N°2, depth 2, 1 untreated node
+**** Local DB = 278.6250, global bounds: [ 289.8000 , 289.8000 ], time = 0.74 sec.
+**** Branching constraint: z[1,4]<=0.0
+***************************************************************************************
+  <st= 2> <it=  1> <et= 0.75> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=      -Inf> <mlp=  280.4750> <PB=289.8000>
+  <st= 1> <it=  2> <et= 0.75> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  278.0750> <mlp=  280.4750> <PB=289.8000>
+  <st= 1> <it=  3> <et= 0.75> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  280.0750> <mlp=  280.4750> <PB=289.8000>
+  <st= 1> <it=  4> <et= 0.75> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  280.4750> <mlp=  280.4750> <PB=289.8000>
+***************************************************************************************
+**** B&B tree node N°31, parent N°29, depth 3, 2 untreated nodes
+**** Local DB = 280.4750, global bounds: [ 278.6250 , 289.8000 ], time = 0.75 sec.
+**** Branching constraint: z[1,5]<=0.0
+***************************************************************************************
+  <st= 2> <it=  1> <et= 0.75> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=      -Inf> <mlp=  282.7250> <PB=289.8000>
+  <st= 1> <it=  2> <et= 0.75> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  278.9250> <mlp=  282.7250> <PB=289.8000>
+  <st= 1> <it=  3> <et= 0.75> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  282.7250> <mlp=  282.7250> <PB=289.8000>
+***************************************************************************************
+**** B&B tree node N°33, parent N°31, depth 4, 3 untreated nodes
+**** Local DB = 282.7250, global bounds: [ 278.6250 , 289.8000 ], time = 0.75 sec.
+**** Branching constraint: y[1]<=0.0
+***************************************************************************************
+  <st= 2> <it=  1> <et= 0.75> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=      -Inf> <mlp=  290.6600> <PB=289.8000>
+  <st= 1> <it=  2> <et= 0.76> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  259.1800> <mlp=  290.6600> <PB=289.8000>
+  <st= 1> <it=  3> <et= 0.76> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  264.9800> <mlp=  290.6600> <PB=289.8000>
+  <st= 1> <it=  4> <et= 0.76> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  277.8500> <mlp=  287.4500> <PB=289.8000>
+  <st= 1> <it=  5> <et= 0.76> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  285.8500> <mlp=  286.2500> <PB=289.8000>
+  <st= 1> <it=  6> <et= 0.76> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  286.2500> <mlp=  286.2500> <PB=289.8000>
+***************************************************************************************
+**** B&B tree node N°35, parent N°33, depth 5, 4 untreated nodes
+**** Local DB = 286.2500, global bounds: [ 278.6250 , 289.8000 ], time = 0.76 sec.
+**** Branching constraint: z[8,5]<=0.0
+***************************************************************************************
+  <st= 2> <it=  1> <et= 0.76> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=      -Inf> <mlp=  290.6600> <PB=289.8000>
+  <st= 1> <it=  2> <et= 0.76> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  274.5800> <mlp=  290.6600> <PB=289.8000>
+  <st= 1> <it=  3> <et= 0.76> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  288.6500> <mlp=  288.6500> <PB=289.8000>
+***************************************************************************************
+**** B&B tree node N°37, parent N°35, depth 6, 5 untreated nodes
+**** Local DB = 288.6500, global bounds: [ 278.6250 , 289.8000 ], time = 0.76 sec.
+**** Branching constraint: z[3,6]<=0.0
+***************************************************************************************
+  <st= 2> <it=  1> <et= 0.78> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=      -Inf> <mlp=  315.4000> <PB=289.8000>
+  <st= 1> <it=  2> <et= 0.78> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  269.8000> <mlp=  315.4000> <PB=289.8000>
+  <st= 1> <it=  3> <et= 0.78> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  271.8000> <mlp=  312.2000> <PB=289.8000>
+  <st= 1> <it=  4> <et= 0.78> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  298.1000> <mlp=  304.9000> <PB=289.8000>
+***************************************************************************************
+**** B&B tree node N°36, parent N°35, depth 6, 4 untreated nodes
+**** Local DB = 288.6500, global bounds: [ 278.6250 , 289.8000 ], time = 0.78 sec.
+**** Branching constraint: z[3,6]>=1.0
+***************************************************************************************
+  <st= 2> <it=  1> <et= 0.78> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=      -Inf> <mlp=  306.1000> <PB=289.8000>
+  <st= 1> <it=  2> <et= 0.79> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  291.3000> <mlp=  306.1000> <PB=289.8000>
+***************************************************************************************
+**** B&B tree node N°34, parent N°33, depth 5, 3 untreated nodes
+**** Local DB = 286.2500, global bounds: [ 278.6250 , 289.8000 ], time = 0.79 sec.
+**** Branching constraint: z[8,5]>=1.0
+***************************************************************************************
+  <st= 2> <it=  1> <et= 0.79> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=      -Inf> <mlp=  315.0000> <PB=289.8000>
+  <st= 1> <it=  2> <et= 0.79> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=-3160.0667> <mlp=  315.0000> <PB=289.8000>
+  <st= 1> <it=  3> <et= 0.79> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  292.3500> <mlp=  303.4500> <PB=289.8000>
+***************************************************************************************
+**** B&B tree node N°32, parent N°31, depth 4, 2 untreated nodes
+**** Local DB = 282.7250, global bounds: [ 278.6250 , 289.8000 ], time = 0.79 sec.
+**** Branching constraint: y[1]>=1.0
+***************************************************************************************
+  <st= 2> <it=  1> <et= 0.79> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=      -Inf> <mlp=  302.7000> <PB=289.8000>
+  <st= 1> <it=  2> <et= 0.79> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  279.1000> <mlp=  302.7000> <PB=289.8000>
+  <st= 1> <it=  3> <et= 0.79> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  291.9000> <mlp=  295.1000> <PB=289.8000>
+***************************************************************************************
+**** B&B tree node N°30, parent N°29, depth 3, 1 untreated node
+**** Local DB = 280.4750, global bounds: [ 278.6250 , 289.8000 ], time = 0.79 sec.
+**** Branching constraint: z[1,5]>=1.0
+***************************************************************************************
+  <st= 2> <it=  1> <et= 0.79> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=      -Inf> <mlp=  303.9000> <PB=289.8000>
+  <st= 1> <it=  2> <et= 0.79> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  148.9000> <mlp=  303.9000> <PB=289.8000>
+  <st= 1> <it=  3> <et= 0.79> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  205.7500> <mlp=  303.7500> <PB=289.8000>
+  <st= 1> <it=  4> <et= 0.80> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  291.1667> <mlp=  297.2333> <PB=289.8000>
+***************************************************************************************
+**** B&B tree node N°28, parent N°2, depth 2, 0 untreated node
+**** Local DB = 278.6250, global bounds: [ 278.6250 , 289.8000 ], time = 0.80 sec.
+**** Branching constraint: z[1,4]>=1.0
+***************************************************************************************
+  <st= 2> <it=  1> <et= 0.80> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=      -Inf> <mlp=  293.5000> <PB=289.8000>
+  <st= 1> <it=  2> <et= 0.80> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  188.7000> <mlp=  293.5000> <PB=289.8000>
+  <st= 1> <it=  3> <et= 0.80> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  239.6333> <mlp=  289.6111> <PB=289.8000>
+  <st= 1> <it=  4> <et= 0.80> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  286.7125> <mlp=  286.7125> <PB=289.8000>
+***************************************************************************************
+**** B&B tree node N°39, parent N°28, depth 3, 1 untreated node
+**** Local DB = 286.7125, global bounds: [ 289.8000 , 289.8000 ], time = 0.80 sec.
+**** Branching constraint: z[1,8]<=0.0
+***************************************************************************************
+  <st= 2> <it=  1> <et= 0.80> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=      -Inf> <mlp=  289.5875> <PB=289.8000>
+  <st= 1> <it=  2> <et= 0.80> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  289.5875> <mlp=  289.5875> <PB=289.8000>
+***************************************************************************************
+**** B&B tree node N°41, parent N°39, depth 4, 2 untreated nodes
+**** Local DB = 289.5875, global bounds: [ 286.7125 , 289.8000 ], time = 0.80 sec.
+**** Branching constraint: x[6,1]<=0.0
+***************************************************************************************
+  <st= 2> <it=  1> <et= 0.80> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=      -Inf> <mlp=  303.8500> <PB=289.8000>
+  <st= 1> <it=  2> <et= 0.81> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  242.9500> <mlp=  303.8500> <PB=289.8000>
+  <st= 1> <it=  3> <et= 0.81> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  290.4000> <mlp=  297.4000> <PB=289.8000>
+***************************************************************************************
+**** B&B tree node N°40, parent N°39, depth 4, 1 untreated node
+**** Local DB = 289.5875, global bounds: [ 286.7125 , 289.8000 ], time = 0.81 sec.
+**** Branching constraint: x[6,1]>=1.0
+***************************************************************************************
+  <st= 2> <it=  1> <et= 0.81> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=      -Inf> <mlp=  292.9000> <PB=289.8000>
+  <st= 1> <it=  2> <et= 0.81> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  255.8000> <mlp=  292.9000> <PB=289.8000>
+  <st= 1> <it=  3> <et= 0.81> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  283.1000> <mlp=  292.9000> <PB=289.8000>
+  <st= 1> <it=  4> <et= 0.81> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  292.9000> <mlp=  292.9000> <PB=289.8000>
+***************************************************************************************
+**** B&B tree node N°38, parent N°28, depth 3, 0 untreated node
+**** Local DB = 286.7125, global bounds: [ 286.7125 , 289.8000 ], time = 0.81 sec.
+**** Branching constraint: z[1,8]>=1.0
+***************************************************************************************
+  <st= 2> <it=  1> <et= 0.81> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=      -Inf> <mlp=  290.5000> <PB=289.8000>
+  <st= 1> <it=  2> <et= 0.81> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  264.7667> <mlp=  290.5000> <PB=289.8000>
+  <st= 1> <it=  3> <et= 0.81> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  290.5000> <mlp=  290.5000> <PB=289.8000>
+ ──────────────────────────────────────────────────────────────────────────
+                                  Time                    Allocations
+                         ───────────────────────   ────────────────────────
+    Tot / % measured:         1.44s /  56.6%            227MiB /  71.2%
+
+ Section         ncalls     time    %tot     avg     alloc    %tot      avg
+ ──────────────────────────────────────────────────────────────────────────
+ Coluna               1    814ms  100.0%   814ms    162MiB  100.0%   162MiB
+   SolveLpForm      181   38.0ms    4.7%   210μs   17.6MiB   10.9%   100KiB
+ ──────────────────────────────────────────────────────────────────────────
+[ Info: Terminated
+[ Info: Primal bound: 289.8
+[ Info: Dual bound: 289.7999999999999

Benders decomposition

In this section, we show how one can solve the linear relaxation of the master program of a Benders Decomposition approach to this facility location demo problem.

The first-stage decisions consist in choosing a subset of facilities to open. The second-stage decisions consist in choosing the routes that are assigned to each facility. The second stage problem is an integer program, so for simplicity, we use its linear relaxation instead. To improve the quality of this relaxation, we enumerate the routes and use one variable per route. As this approach is practical only for small instances, we use it only for illustration purposes. For larger instances, we would have to implement a column generation approach to solve the subproblem, i.e., the Benders cut separation problem.

In the same spirit as the above models, we use the variables. Let y[j] equal 1 if the facility j is open and 0 otherwise. Let λ[j,k] equal 1 if route k starting from facility j is selected and 0 otherwise.

Since there is only one subproblem in the second stage, we introduce a fake axis that contains only one element. This approach can be generalized to the case where customer demand uncertainty is expressed with scenarios. In this case, we would have one subproblem for each scenario, and the axis would have been defined for the set of scenarios. In our case, the set of scenarios consists of one "fake" scenario.

fake = 1
+@axis(axis, collect(fake:fake))
+
+coluna = JuMP.optimizer_with_attributes(
+    Coluna.Optimizer,
+    "params" => Coluna.Params(solver=Coluna.Algorithm.BendersCutGeneration()
+    ),
+    "default_optimizer" => GLPK.Optimizer
+)
+
+model = BlockModel(coluna);

We introduce auxiliary structures to improve the clarity of the code.

# routes covering customer i from facility j.
+covering_routes = Dict(
+    (j, i) => findall(r -> (i in r.path), routes_per_facility[j]) for i in customers, j in facilities
+);
+# routes costs from facility j.
+routes_costs = Dict(
+    j => [route_original_cost(arc_costs, r) for r in routes_per_facility[j]] for j in facilities
+);

We declare the variables.

@variable(model, 0 <= y[j in facilities] <= 1) ## 1st stage
+@variable(model, 0 <= λ[f in axis, j in facilities, k in 1:length(routes_per_facility[j])] <= 1); ## 2nd stage

We declare the constraints.

# Linking constraints
+@constraint(model, open[fake in axis, j in facilities, k in 1:length(routes_per_facility[j])],
+    y[j] >= λ[fake, j, k])
+
+# Second-stage constraints
+@constraint(model, cover[fake in axis, i in customers],
+    sum(λ[fake, j, k] for j in facilities, k in covering_routes[(j, i)]) >= 1)
+
+# Second-stage constraints
+@constraint(model, limit_nb_routes[fake in axis, j in facilities],
+    sum(λ[fake, j, q] for q in 1:length(routes_per_facility[j])) <= nb_routes_per_facility
+)
+
+# First-stage constraint
+# This constraint is redundant, we add it in order not to start with an empty master problem
+@constraint(model, min_opening,
+    sum(y[j] for j in facilities) >= 1)
+
+@objective(model, Min,
+    sum(facilities_fixed_costs[j] * y[j] for j in facilities) +
+    sum(routes_costs[j][k] * λ[fake, j, k] for j in facilities, k in 1:length(routes_per_facility[j])));

We perform the decomposition over the axis and we optimize the problem.

@benders_decomposition(model, dec, axis)
+JuMP.optimize!(model)
Coluna
+Version 0.7.0 | https://github.com/atoptima/Coluna.jl
+<it=  1> <et= 3.00> <mst= 0.07> <sp= 0.36> <cuts= 1> <master=    0.0000>
+<it=  2> <et= 3.52> <mst= 0.31> <sp= 0.00> <cuts= 1> <master=  120.0000>
+<it=  3> <et= 3.53> <mst= 0.00> <sp= 0.00> <cuts= 1> <master=  127.9962>
+<it=  4> <et= 3.53> <mst= 0.00> <sp= 0.00> <cuts= 1> <master=  269.4996>
+<it=  5> <et= 3.53> <mst= 0.00> <sp= 0.00> <cuts= 1> <master=  270.0777>
+<it=  6> <et= 3.53> <mst= 0.00> <sp= 0.00> <cuts= 1> <master=  271.1080>
+<it=  7> <et= 3.53> <mst= 0.00> <sp= 0.00> <cuts= 1> <master=  271.7932>
+<it=  8> <et= 3.53> <mst= 0.00> <sp= 0.00> <cuts= 1> <master=  271.8133>
+<it=  9> <et= 3.54> <mst= 0.00> <sp= 0.00> <cuts= 1> <master=  271.8222>
+<it= 10> <et= 3.59> <mst= 0.00> <sp= 0.00> <cuts= 0> <master=  271.8231>
+ ──────────────────────────────────────────────────────────────────────────
+                                  Time                    Allocations
+                         ───────────────────────   ────────────────────────
+    Tot / % measured:         8.42s /  43.7%            478MiB /  33.6%
+
+ Section         ncalls     time    %tot     avg     alloc    %tot      avg
+ ──────────────────────────────────────────────────────────────────────────
+ Coluna               1    3.68s  100.0%   3.68s    160MiB  100.0%   160MiB
+   SolveLpForm       20    754ms   20.5%  37.7ms   42.8MiB   26.7%  2.14MiB
+ ──────────────────────────────────────────────────────────────────────────
+[ Info: Terminated
+[ Info: Primal bound: Inf
+[ Info: Dual bound: 271.8230769230769

Example of comparison of the dual bounds

In this section, we use a larger instance with 3 facilities and 13 customers. We solve only the root node and look at the dual bound:

  • with the standard column generation (without cut separation)
  • by adding robust cuts
  • by adding non-robust cuts
  • by adding both robust and non-robust cuts
nb_positions = 6
+facilities_fixed_costs = [120, 150, 110]
+facilities = [1, 2, 3]
+customers = [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
+arc_costs = [
+    0.0 125.6 148.9 182.2 174.9 126.2 158.6 172.9 127.4 133.1 152.6 183.8 182.4 176.9 120.7 129.5;
+    123.6 0.0 175.0 146.7 191.0 130.4 142.5 139.3 130.1 133.3 163.8 127.8 139.3 128.4 186.4 115.6;
+    101.5 189.6 0.0 198.2 150.5 159.6 128.3 133.0 195.1 167.3 187.3 178.1 171.7 161.5 142.9 142.1;
+    159.4 188.4 124.7 0.0 174.5 174.0 142.6 102.5 135.5 184.4 121.6 112.1 139.9 105.5 190.9 140.7;
+    157.7 160.3 184.2 196.1 0.0 115.5 175.2 153.5 137.7 141.3 109.5 107.7 125.3 151.0 133.1 140.6;
+    145.2 120.4 106.7 138.8 157.3 0.0 153.6 192.2 153.2 184.4 133.6 164.9 163.6 126.3 121.3 161.4;
+    182.6 152.1 178.8 184.1 150.8 163.5 0.0 164.1 104.0 100.5 117.3 156.1 115.1 168.6 186.5 100.2;
+    144.9 193.8 146.1 191.4 136.8 172.7 108.1 0.0 131.0 166.3 116.4 187.0 161.3 148.2 162.1 116.0;
+    173.4 199.1 132.9 133.2 139.8 112.7 138.1 118.8 0.0 173.4 131.8 180.6 191.0 133.9 178.7 108.7;
+    150.5 171.0 163.8 171.5 116.3 149.1 124.0 192.5 188.8 0.0 112.2 188.7 197.3 144.9 110.7 186.6;
+    153.6 104.4 141.1 124.7 121.1 137.5 190.3 177.1 194.4 135.3 0.0 146.4 132.7 103.2 150.3 118.4;
+    112.5 133.7 187.1 170.0 130.2 177.7 159.2 169.9 183.8 101.6 156.2 0.0 114.7 169.3 149.9 125.3;
+    151.5 165.6 162.1 133.4 159.4 200.5 132.7 199.9 136.8 121.3 118.1 123.4 0.0 104.8 197.1 134.4;
+    195.0 101.1 194.1 160.1 147.1 164.6 137.2 138.6 166.7 191.2 169.2 186.0 171.2 0.0 106.8 150.9;
+    158.2 152.7 104.0 136.0 168.9 175.7 139.2 163.2 102.7 153.3 185.9 164.0 113.2 200.7 0.0 127.4;
+    136.6 174.3 103.2 131.4 107.8 191.6 115.1 127.6 163.2 123.2 173.3 133.0 120.5 176.9 173.8 0.0;
+]
+
+locations = vcat(facilities, customers)
+nb_customers = length(customers)
+nb_facilities = length(facilities)
+positions = 1:nb_positions;
+
+routes_per_facility = Dict(
+    j => best_route_forall_cust_subsets(arc_costs, customers, j, nb_positions) for j in facilities
+);

We set maxnumnodes to zero to optimize only the root node:

coluna = optimizer_with_attributes(
+    Coluna.Optimizer,
+    "params" => Coluna.Params(
+        solver=Coluna.Algorithm.TreeSearchAlgorithm(
+            maxnumnodes=0,
+            conqueralg=Coluna.ColCutGenConquer()
+        )
+    ),
+    "default_optimizer" => GLPK.Optimizer
+);

We define a method to call both valid_inequalities_callback and r1c_callback:

function cuts_callback(cbdata)
+    valid_inequalities_callback(cbdata)
+    r1c_callback(cbdata)
+end
+
+function attach_data(model, cov)
+    BlockDecomposition.customvars!(model, R1cVarData)
+    BlockDecomposition.customconstrs!(model, [CoverConstrData, R1cCutData])
+    for i in customers
+        customdata!(cov[i], CoverConstrData(i))
+    end
+end;

First, we solve the root node with the "raw" decomposition model:

model, x, y, z, cov = create_model(coluna, pricing_callback)
+attach_data(model, cov)

dual bound found after optimization = 1588.00

Then, we re-solve it with the robust cuts:

model, x, y, z, cov = create_model(coluna, pricing_callback)
+attach_data(model, cov)
+MOI.set(model, MOI.UserCutCallback(), valid_inequalities_callback);

dual bound found after optimization = 1591.55

And with non-robust cuts:

model, x, y, z, cov = create_model(coluna, pricing_callback)
+attach_data(model, cov)
+MOI.set(model, MOI.UserCutCallback(), r1c_callback);

dual bound found after optimization = 1598.26

Finally we add both robust and non-robust cuts:

model, x, y, z, cov = create_model(coluna, pricing_callback)
+attach_data(model, cov)
+MOI.set(model, MOI.UserCutCallback(), cuts_callback);

dual bound found after optimization = 1600.63


This page was generated using Literate.jl.

diff --git a/previews/PR1107/start/custom_data.jl b/previews/PR1107/start/custom_data.jl new file mode 100644 index 000000000..c795291f1 --- /dev/null +++ b/previews/PR1107/start/custom_data.jl @@ -0,0 +1,248 @@ +# # [Custom Variables and Cuts](@id tuto_custom_data) +# +# Coluna allows users to attach custom data to variables and constraints. +# This data is useful to store information about the variables or constraints in a custom +# format much easier to process than extracted information from the formulation +# (coefficient matrix, bounds, costs, and right-hand side). +# +# In this example, we will show how to attach custom data to variables and constraints and +# use them to separate non-robust cuts. We will use the Bin Packing problem as an example. +# +# Let us consider a Bin Packing problem with only 3 items such that any pair of items +# fits into one bin but the 3 items do not. The objective function is to minimize the number +# of bins being used. Pricing is done by inspection over the 6 combinations of items (3 pairs and 3 +# singletons). The master LP solution has 1.5 bins at the root node, +# each 0.5 corresponding to a bin with one of the possible pairs of items. +# +# In this example, we will show you how to use non-robust cuts to improve the master LP +# solution at the root node. +# Obviously, Coluna is able to solve this instance by branching on the +# number of bins but the limit one on the number of nodes prevents it to be solved without +# cuts. +# +# We define the dependencies: + +using JuMP, BlockDecomposition, Coluna, GLPK; + +# We define the solver. + +coluna = JuMP.optimizer_with_attributes( + Coluna.Optimizer, + "default_optimizer" => GLPK.Optimizer, + "params" => Coluna.Params( + solver = Coluna.Algorithm.TreeSearchAlgorithm( + conqueralg = Coluna.Algorithm.ColCutGenConquer( + colgen = Coluna.Algorithm.ColumnGeneration( + pricing_prob_solve_alg = Coluna.Algorithm.SolveIpForm( + optimizer_id = 1 + )) + ), + maxnumnodes = 1 # we only treat the root node. + ) + ) +); + + +# Let's define the model. +# Let's $B$ the set of bins and $I$ the set of items. +# We introduce variable $y_b$ that is equal to 1 if a bin $b$ is used and 0 otherwise. +# We introduce variable $x_{b,i}$ that is equal to 1 if item $i$ is put in a bin $b$ and 0 otherwise. + +model = BlockModel(coluna); + +# We must assign three items: +I = [1, 2, 3]; + +# And we have three bins: +B = [1, 2, 3]; + +# Each bin is defining a subproblem, we declare our axis: +@axis(axis, collect(B)); + +# We declare subproblem variables `y[b]`: + +@variable(model, y[b in axis], Bin); + +# And `x[b,i]`: + +@variable(model, x[b in axis, i in I], Bin); + +# Each item must be assigned to one bin: + +@constraint(model, sp[i in I], sum(x[b,i] for b in axis) == 1); + +# We minimize the number of bins and we declare the decomposition: + +@objective(model, Min, sum(y[b] for b in axis)) +@dantzig_wolfe_decomposition(model, dec, axis); + + +# ## Custom data for non-robust cuts + +# As said previously, at the end of the column generation at the root node, +# the master LP solution has 1.5 bins. It corresponds to three bins, each of them used 0.5 times +# containing one pair `(1,2)`, `(1, 3)`, or `(2, 3)` of items. +# We are going to introduce the following non-robust cut to make the master LP solution integral: + +# $$\sum\limits_{s \in S~if~length(s) \geq 2} λ_s \leq 1$$ +# +# where : +# - $S$ is the set of possible bin assignments generated by the pricing problem. +# - $length(s)$ the number of items in bin assignment $s \in S$. +# This cut means that we cannot have more than one bin with at least two items. + +# But the problem is that the cut is expressed over the master column and we don't have +# access to these variables from the JuMP model. +# To address this problem, Coluna offers a way to compute the coefficient of a column in a +# constraint by implementing the following method: +# +# ```@docs +# Coluna.MathProg.computecoeff +# ``` +# +# +# We therefore need to attach custom data to the master columns and the non-robust cut to +# use the method `compute_coeff`. +# +# For every subproblem solution $s$, we define custom data with the number of items in the bin. + +struct MyCustomVarData <: BlockDecomposition.AbstractCustomData + nb_items::Int +end +BlockDecomposition.customvars!(model, MyCustomVarData); + + +# We define custom data for the cut that will contain the minimum number of items +# in a bin that can be used. The value will be `2` in this example. +struct MyCustomCutData <: BlockDecomposition.AbstractCustomData + min_items::Int +end +BlockDecomposition.customconstrs!(model, MyCustomCutData); + +# We implement the `computecoeff` method for the custom data we defined. + +function Coluna.MathProg.computecoeff( + var_custom_data::MyCustomVarData, constr_custom_data::MyCustomCutData +) + return (var_custom_data.nb_items >= constr_custom_data.min_items) ? 1.0 : 0.0 +end + +# ## Pricing callback + +# We define the pricing callback that will generate the bin with best-reduced cost. +# Be careful, when using non-robust cuts, you must take into account the contribution of the +# non-robust cuts to the reduced cost of your solution. + +function my_pricing_callback(cbdata) + ## Get the reduced costs of the original variables. + I = [1, 2, 3] + b = BlockDecomposition.callback_spid(cbdata, model) + + rc_y = BlockDecomposition.callback_reduced_cost(cbdata, y[b]) + rc_x = [BlockDecomposition.callback_reduced_cost(cbdata, x[b, i]) for i in I] + + ## Get the dual values of the custom cuts (to calculate contributions of + ## non-robust cuts to the cost of the solution). + custduals = Tuple{Int, Float64}[] + for (_, constr) in Coluna.MathProg.getconstrs(cbdata.form.parent_formulation) + if typeof(constr.custom_data) == MyCustomCutData + push!(custduals, ( + constr.custom_data.min_items, + Coluna.MathProg.getcurincval(cbdata.form.parent_formulation, constr) + )) + end + end + + ## Pricing by inspection. + sols = [[1], [2], [3], [1, 2], [1, 3], [2, 3]] + best_s = Int[] + best_rc = Inf + for s in sols + rc_s = rc_y + sum(rc_x[i] for i in s) # reduced cost of the subproblem variables + if !isempty(custduals) + ## contribution of the non-robust cuts + rc_s -= sum((length(s) >= minits) ? dual : 0.0 for (minits, dual) in custduals) + end + if rc_s < best_rc + best_rc = rc_s + best_s = s + end + end + @show best_s + ## build the best one and submit + solcost = best_rc + solvars = JuMP.VariableRef[] + solvarvals = Float64[] + for i in best_s + push!(solvars, x[b, i]) + push!(solvarvals, 1.0) + end + push!(solvars, y[b]) + push!(solvarvals, 1.0) + ## submit the solution + MOI.submit( + model, BlockDecomposition.PricingSolution(cbdata), + solcost, + solvars, + solvarvals, + MyCustomVarData(length(best_s)) # attach a custom data to the column + ) + + MOI.submit(model, BlockDecomposition.PricingDualBound(cbdata), solcost) + return +end + +# The pricing callback is done, we define it as the solver of our pricing problem. +subproblems = BlockDecomposition.getsubproblems(dec) +BlockDecomposition.specify!.( + subproblems, + solver = my_pricing_callback +); + + +# ## Non-robust cut separation callback. + +# We now define the cut separation callback for our non-robust cut. +# This is the same callback as the one used for robust cuts. +# There is just one slight difference when you submit the non-robust cut. +# Since cuts are expressed over the master variables and these variables are inaccessible from +# the JuMP model, you'll submit a constraint with an empty left-hand side and you'll leave Coluna +# populate the left-hand side with the values returned by `Coluna.MathProg.computecoeff`. + +# So let's define the callback. +# Basically, if the solution uses more than one bin with two items, +# The cut is added to the model. +function custom_cut_sep(cbdata) + ## Compute the constraint violation by iterating over the master solution. + viol = -1.0 + for (varid, varval) in cbdata.orig_sol + var = Coluna.MathProg.getvar(cbdata.form, varid) + if !isnothing(var.custom_data) + if var.custom_data.nb_items >= 2 + viol += varval + end + end + end + ## Add the cut (at most one variable with 2 or more of the 3 items) if violated. + if viol > 0.001 + MOI.submit( + model, MOI.UserCut(cbdata), + JuMP.ScalarConstraint( + JuMP.AffExpr(0.0), # We cannot express the left-hand side so we push 0. + MOI.LessThan(1.0) + ), + MyCustomCutData(2) # Cut custom data. + ) + end + return +end + +MOI.set(model, MOI.UserCutCallback(), custom_cut_sep) +JuMP.optimize!(model) + +# We see on the output that the algorithm has converged a first time before a cut is added. +# Coluna then starts a new iteration taking into account the cut. +# We notice here an improvement of the value of the dual bound: before the cut, +# we converge towards 1.5. After the cut, we reach 2.0. + + diff --git a/previews/PR1107/start/custom_data/index.html b/previews/PR1107/start/custom_data/index.html new file mode 100644 index 000000000..3b48f0a3b --- /dev/null +++ b/previews/PR1107/start/custom_data/index.html @@ -0,0 +1,166 @@ + +Custom data · Coluna.jl

Custom Variables and Cuts

Coluna allows users to attach custom data to variables and constraints. This data is useful to store information about the variables or constraints in a custom format much easier to process than extracted information from the formulation (coefficient matrix, bounds, costs, and right-hand side).

In this example, we will show how to attach custom data to variables and constraints and use them to separate non-robust cuts. We will use the Bin Packing problem as an example.

Let us consider a Bin Packing problem with only 3 items such that any pair of items fits into one bin but the 3 items do not. The objective function is to minimize the number of bins being used. Pricing is done by inspection over the 6 combinations of items (3 pairs and 3 singletons). The master LP solution has 1.5 bins at the root node, each 0.5 corresponding to a bin with one of the possible pairs of items.

In this example, we will show you how to use non-robust cuts to improve the master LP solution at the root node. Obviously, Coluna is able to solve this instance by branching on the number of bins but the limit one on the number of nodes prevents it to be solved without cuts.

We define the dependencies:

using JuMP, BlockDecomposition, Coluna, GLPK;

We define the solver.

coluna = JuMP.optimizer_with_attributes(
+    Coluna.Optimizer,
+    "default_optimizer" => GLPK.Optimizer,
+    "params" => Coluna.Params(
+        solver = Coluna.Algorithm.TreeSearchAlgorithm(
+            conqueralg = Coluna.Algorithm.ColCutGenConquer(
+                colgen = Coluna.Algorithm.ColumnGeneration(
+                            pricing_prob_solve_alg = Coluna.Algorithm.SolveIpForm(
+                                optimizer_id = 1
+                            ))
+            ),
+            maxnumnodes = 1 # we only treat the root node.
+        )
+    )
+);

Let's define the model. Let's $B$ the set of bins and $I$ the set of items. We introduce variable $y_b$ that is equal to 1 if a bin $b$ is used and 0 otherwise. We introduce variable $x_{b,i}$ that is equal to 1 if item $i$ is put in a bin $b$ and 0 otherwise.

model = BlockModel(coluna);

We must assign three items:

I = [1, 2, 3];

And we have three bins:

B = [1, 2, 3];

Each bin is defining a subproblem, we declare our axis:

@axis(axis, collect(B));

We declare subproblem variables y[b]:

@variable(model, y[b in axis], Bin);

And x[b,i]:

@variable(model, x[b in axis, i in I], Bin);

Each item must be assigned to one bin:

@constraint(model, sp[i in I], sum(x[b,i] for b in axis) == 1);

We minimize the number of bins and we declare the decomposition:

@objective(model, Min, sum(y[b] for b in axis))
+@dantzig_wolfe_decomposition(model, dec, axis);

Custom data for non-robust cuts

As said previously, at the end of the column generation at the root node, the master LP solution has 1.5 bins. It corresponds to three bins, each of them used 0.5 times containing one pair (1,2), (1, 3), or (2, 3) of items. We are going to introduce the following non-robust cut to make the master LP solution integral:

\[\sum\limits_{s \in S~if~length(s) \geq 2} λ_s \leq 1\]

where :

  • $S$ is the set of possible bin assignments generated by the pricing problem.
  • $length(s)$ the number of items in bin assignment $s \in S$.

This cut means that we cannot have more than one bin with at least two items.

But the problem is that the cut is expressed over the master column and we don't have access to these variables from the JuMP model. To address this problem, Coluna offers a way to compute the coefficient of a column in a constraint by implementing the following method:

Coluna.MathProg.computecoeffFunction
computecoeff(var_custom_data, constr_custom_data) -> Float64

Dispatches on the type of custom data attached to the variable and the constraint to compute the coefficient of the variable in the constraint.

source

We therefore need to attach custom data to the master columns and the non-robust cut to use the method compute_coeff.

For every subproblem solution $s$, we define custom data with the number of items in the bin.

struct MyCustomVarData <: BlockDecomposition.AbstractCustomData
+    nb_items::Int
+end
+BlockDecomposition.customvars!(model, MyCustomVarData);

We define custom data for the cut that will contain the minimum number of items in a bin that can be used. The value will be 2 in this example.

struct MyCustomCutData <: BlockDecomposition.AbstractCustomData
+    min_items::Int
+end
+BlockDecomposition.customconstrs!(model, MyCustomCutData);

We implement the computecoeff method for the custom data we defined.

function Coluna.MathProg.computecoeff(
+    var_custom_data::MyCustomVarData, constr_custom_data::MyCustomCutData
+)
+    return (var_custom_data.nb_items >= constr_custom_data.min_items) ? 1.0 : 0.0
+end

Pricing callback

We define the pricing callback that will generate the bin with best-reduced cost. Be careful, when using non-robust cuts, you must take into account the contribution of the non-robust cuts to the reduced cost of your solution.

function my_pricing_callback(cbdata)
+    # Get the reduced costs of the original variables.
+    I = [1, 2, 3]
+    b = BlockDecomposition.callback_spid(cbdata, model)
+
+    rc_y = BlockDecomposition.callback_reduced_cost(cbdata, y[b])
+    rc_x = [BlockDecomposition.callback_reduced_cost(cbdata, x[b, i]) for i in I]
+
+    # Get the dual values of the custom cuts (to calculate contributions of
+    # non-robust cuts to the cost of the solution).
+    custduals = Tuple{Int, Float64}[]
+    for (_, constr) in Coluna.MathProg.getconstrs(cbdata.form.parent_formulation)
+        if typeof(constr.custom_data) == MyCustomCutData
+            push!(custduals, (
+                constr.custom_data.min_items,
+                Coluna.MathProg.getcurincval(cbdata.form.parent_formulation, constr)
+            ))
+        end
+    end
+
+    # Pricing by inspection.
+    sols = [[1], [2], [3], [1, 2], [1, 3], [2, 3]]
+    best_s = Int[]
+    best_rc = Inf
+    for s in sols
+        rc_s = rc_y + sum(rc_x[i] for i in s) # reduced cost of the subproblem variables
+        if !isempty(custduals)
+            # contribution of the non-robust cuts
+            rc_s -= sum((length(s) >= minits) ? dual : 0.0 for (minits, dual) in custduals)
+        end
+        if rc_s < best_rc
+            best_rc = rc_s
+            best_s = s
+        end
+    end
+    @show best_s
+    # build the best one and submit
+    solcost = best_rc
+    solvars = JuMP.VariableRef[]
+    solvarvals = Float64[]
+    for i in best_s
+        push!(solvars, x[b, i])
+        push!(solvarvals, 1.0)
+    end
+    push!(solvars, y[b])
+    push!(solvarvals, 1.0)
+    # submit the solution
+    MOI.submit(
+        model, BlockDecomposition.PricingSolution(cbdata),
+        solcost,
+        solvars,
+        solvarvals,
+        MyCustomVarData(length(best_s)) # attach a custom data to the column
+    )
+
+    MOI.submit(model, BlockDecomposition.PricingDualBound(cbdata), solcost)
+    return
+end
my_pricing_callback (generic function with 1 method)

The pricing callback is done, we define it as the solver of our pricing problem.

subproblems = BlockDecomposition.getsubproblems(dec)
+BlockDecomposition.specify!.(
+    subproblems,
+    solver = my_pricing_callback
+);

Non-robust cut separation callback.

We now define the cut separation callback for our non-robust cut. This is the same callback as the one used for robust cuts. There is just one slight difference when you submit the non-robust cut. Since cuts are expressed over the master variables and these variables are inaccessible from the JuMP model, you'll submit a constraint with an empty left-hand side and you'll leave Coluna populate the left-hand side with the values returned by Coluna.MathProg.computecoeff.

So let's define the callback. Basically, if the solution uses more than one bin with two items, The cut is added to the model.

function custom_cut_sep(cbdata)
+    # Compute the constraint violation by iterating over the master solution.
+    viol = -1.0
+    for (varid, varval) in cbdata.orig_sol
+        var = Coluna.MathProg.getvar(cbdata.form, varid)
+        if !isnothing(var.custom_data)
+            if var.custom_data.nb_items >= 2
+                viol += varval
+            end
+        end
+    end
+    # Add the cut (at most one variable with 2 or more of the 3 items) if violated.
+    if viol > 0.001
+        MOI.submit(
+            model, MOI.UserCut(cbdata),
+            JuMP.ScalarConstraint(
+                JuMP.AffExpr(0.0), # We cannot express the left-hand side so we push 0.
+                MOI.LessThan(1.0)
+            ),
+            MyCustomCutData(2) # Cut custom data.
+        )
+    end
+    return
+end
+
+MOI.set(model, MOI.UserCutCallback(), custom_cut_sep)
+JuMP.optimize!(model)
Coluna
+Version 0.7.0 | https://github.com/atoptima/Coluna.jl
+***************************************************************************************
+**** B&B tree root node
+**** Local DB = -Inf, global bounds: [ -Inf , Inf ], time = 0.00 sec.
+***************************************************************************************
+best_s = [1, 2]
+best_s = [1, 2]
+best_s = [1, 2]
+  <st= 1> <it=  1> <et= 0.96> <mst= 0.01> <sp= 0.00> <cols= 3> <al= 0.00> <DB=-29997.0000> <mlp=60000.0000> <PB=Inf>
+best_s = [1, 3]
+best_s = [1, 3]
+best_s = [1, 3]
+  <st= 1> <it=  2> <et= 0.98> <mst= 0.02> <sp= 0.00> <cols= 3> <al= 0.00> <DB=-29999.0000> <mlp=30001.0000> <PB=Inf>
+best_s = [2, 3]
+best_s = [2, 3]
+best_s = [2, 3]
+  <st= 1> <it=  3> <et= 0.98> <mst= 0.00> <sp= 0.00> <cols= 3> <al= 0.00> <DB=-10001.0000> <mlp=20002.0000> <PB=Inf>
+best_s = [1]
+best_s = [1]
+best_s = [1]
+  <st= 1> <it=  4> <et= 0.98> <mst= 0.00> <sp= 0.00> <cols= 3> <al= 0.00> <DB=    3.0000> <mlp=15001.5000> <PB=Inf>
+best_s = [3]
+best_s = [3]
+best_s = [3]
+  <st= 1> <it=  5> <et= 0.98> <mst= 0.00> <sp= 0.00> <cols= 3> <al= 0.00> <DB=-19995.0000> <mlp=10002.0000> <PB=Inf>
+best_s = [2]
+best_s = [2]
+best_s = [2]
+  <st= 1> <it=  6> <et= 0.98> <mst= 0.00> <sp= 0.00> <cols= 3> <al= 0.00> <DB=-19995.0000> <mlp=10002.0000> <PB=Inf>
+Cut separation callback adds 0 new essential cuts and 0 new facultative cuts.
+best_s = [1]
+best_s = [1]
+best_s = [1]
+  <st= 1> <it=  7> <et= 0.98> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=    3.0000> <mlp=    3.0000> <PB=3.0000>
+ ──────────────────────────────────────────────────────────────────────────
+                                  Time                    Allocations
+                         ───────────────────────   ────────────────────────
+    Tot / % measured:         4.51s /  21.7%           2.30GiB /   3.2%
+
+ Section         ncalls     time    %tot     avg     alloc    %tot      avg
+ ──────────────────────────────────────────────────────────────────────────
+ Coluna               1    980ms  100.0%   980ms   75.6MiB  100.0%  75.6MiB
+   SolveLpForm        7   30.0ms    3.1%  4.29ms   1.96MiB    2.6%   286KiB
+ ──────────────────────────────────────────────────────────────────────────
+[ Info: Terminated
+[ Info: Primal bound: 3.0
+[ Info: Dual bound: 3.0

We see on the output that the algorithm has converged a first time before a cut is added. Coluna then starts a new iteration taking into account the cut. We notice here an improvement of the value of the dual bound: before the cut, we converge towards 1.5. After the cut, we reach 2.0.


This page was generated using Literate.jl.

diff --git a/previews/PR1107/start/cuts.jl b/previews/PR1107/start/cuts.jl new file mode 100644 index 000000000..37a8a5770 --- /dev/null +++ b/previews/PR1107/start/cuts.jl @@ -0,0 +1,122 @@ +# # [Valid inequalities](@id tuto_cut_callback) + +# Now let us consider a variant of the Generalized Assignment Problem in which we have to +# pay `f[m]` to use machine `m`. + +# Consider the following instance: + +J = 1:10 +M = 1:5 +c = [10.13 15.6 15.54 13.41 17.08;19.58 16.83 10.75 15.8 14.89;14.23 17.36 16.05 14.49 18.96;16.47 16.38 18.14 15.46 11.64;17.87 18.25 13.12 19.16 16.33;11.09 16.76 15.5 12.08 13.06;15.19 13.86 16.08 19.47 15.79;10.79 18.96 16.11 19.78 15.55;12.03 19.03 16.01 14.46 12.77;14.48 11.75 16.97 19.95 18.32]; +w = [5, 4, 5, 6, 8, 9, 5, 8, 10, 7]; +Q = [25, 24, 31, 28, 24]; +f = [105, 103, 109, 112, 100]; + +# We define the dependencies: + +using JuMP, BlockDecomposition, Coluna, GLPK; + +# We parametrize the solver. +# We solve only the root node of the branch-and-bound tree and we use a column and cut +# generation algorithm to conquer (optimize) this node. + +coluna = JuMP.optimizer_with_attributes( + Coluna.Optimizer, + "params" => Coluna.Params( + solver = Coluna.Algorithm.TreeSearchAlgorithm( + conqueralg = Coluna.Algorithm.ColCutGenConquer( + max_nb_cut_rounds = 20 + ), + branchingtreefile = "tree2.dot", + maxnumnodes = 1 + ) + ), + "default_optimizer" => GLPK.Optimizer +); + +# ## Column generation + +# We write the model: + +model = BlockModel(coluna; direct_model = true); +@axis(M_axis, M) +@variable(model, x[j in J, m in M_axis], Bin); +@variable(model, y[m in M_axis], Bin); +@constraint(model, setpartitioning[j in J], sum(x[j,m] for m in M_axis) == 1); +@constraint(model, knp[m in M_axis], sum(w[j]*x[j,m] for j in J) <= Q[m] * y[m]); +@objective(model, Min, sum(c[j,m] * x[j,m] for m in M_axis, j in J) + sum(f[m] * y[m] for m in M_axis)); + +@dantzig_wolfe_decomposition(model, dec, M_axis); +sp = getsubproblems(dec); +specify!.(sp, lower_multiplicity = 0); + +# We optimize: + +optimize!(model) + +# The final dual bound is: + +db1 = objective_bound(model) + +# ## Strengthen with valid inequalities + +# Let `H` be the set of configurations of open machines (`h[m] = 1` if machine m open; `0` otherwise) +# such that all jobs can be assigned : `sum(h'Q) >= sum(w)` +# i.e. the total capacity of the open machines must exceed the total weight of the jobs. + +H = Vector{Int}[] +for h in digits.(1:(2^length(M) - 1), base=2, pad=length(M)) + if sum(h'Q) >= sum(w) + push!(H, h) + end +end +H + +# Let `ȳ` be the solution to the linear relaxation of the problem. +# Let us try to express `ȳ` as a linear expression of the configurations. +# If `ȳ ∈ conv H`, we can derive a cut because the optimal integer solution to the problem uses one of the configurations of H. + +# We need MathOptInterface to define the cut callback: + +using MathOptInterface + +# The separation algorithm looks for the non-negative coefficients `χ[k]`, `k = 1:length(H)`, : +# `max sum(χ[k] for k in 1:length(H))` such that `sum(χ[k]* h for (k,h) in enumerate(H)) <= ̄ȳ`. +# If the objective value is less than 1, we must add a cut. + +# Since the separation algorithm is a linear program, strong duality applies. +# So we separate these cuts with the dual. + +fc_sep_m = Model(GLPK.Optimizer) +@variable(fc_sep_m, ψ[m in M] >= 0) # one variable for each constraint +@constraint(fc_sep_m, config_dual[h in H], ψ'h >= 1) # one constraint for each χ[k] +MathOptInterface.set(fc_sep_m, MathOptInterface.Silent(), true) + +# The objective is `min ȳ'ψ` = `sum(χ[k] for k in 1:length(H))`. +# Let `ψ*` be an optimal solution to the dual. If `ȳ'ψ* < 1`, then `ψ*'y >= 1` is a valid inequality. + +function fenchel_cuts_separation(cbdata) + println("Fenchel cuts separation callback...") + ȳ = [callback_value(cbdata, y[m]) for m in M_axis] + @objective(fc_sep_m, Min, ȳ'ψ) # update objective + optimize!(fc_sep_m) + if objective_value(fc_sep_m) < 1 + con = @build_constraint(value.(ψ)'y >= 1) # valid inequality. + MathOptInterface.submit(model, MathOptInterface.UserCut(cbdata), con) + end +end + +MathOptInterface.set(model, MathOptInterface.UserCutCallback(), fenchel_cuts_separation); + +# We optimize: + +optimize!(model) + +# Valid inequalities significantly improve the previous dual bound: + +db2 = objective_bound(model) + + +db2 + + diff --git a/previews/PR1107/start/cuts/index.html b/previews/PR1107/start/cuts/index.html new file mode 100644 index 000000000..f02331536 --- /dev/null +++ b/previews/PR1107/start/cuts/index.html @@ -0,0 +1,181 @@ + +Cut Generation · Coluna.jl

Valid inequalities

Now let us consider a variant of the Generalized Assignment Problem in which we have to pay f[m] to use machine m.

Consider the following instance:

J = 1:10
+M = 1:5
+c = [10.13 15.6 15.54 13.41 17.08;19.58 16.83 10.75 15.8 14.89;14.23 17.36 16.05 14.49 18.96;16.47 16.38 18.14 15.46 11.64;17.87 18.25 13.12 19.16 16.33;11.09 16.76 15.5 12.08 13.06;15.19 13.86 16.08 19.47 15.79;10.79 18.96 16.11 19.78 15.55;12.03 19.03 16.01 14.46 12.77;14.48 11.75 16.97 19.95 18.32];
+w = [5, 4, 5, 6, 8, 9, 5, 8, 10, 7];
+Q = [25,  24,  31,  28,  24];
+f = [105, 103, 109, 112, 100];

We define the dependencies:

using JuMP, BlockDecomposition, Coluna, GLPK;

We parametrize the solver. We solve only the root node of the branch-and-bound tree and we use a column and cut generation algorithm to conquer (optimize) this node.

coluna = JuMP.optimizer_with_attributes(
+    Coluna.Optimizer,
+    "params" => Coluna.Params(
+        solver = Coluna.Algorithm.TreeSearchAlgorithm(
+            conqueralg = Coluna.Algorithm.ColCutGenConquer(
+                max_nb_cut_rounds = 20
+            ),
+            branchingtreefile = "tree2.dot",
+            maxnumnodes = 1
+        )
+    ),
+    "default_optimizer" => GLPK.Optimizer
+);

Column generation

We write the model:

model = BlockModel(coluna; direct_model = true);
+@axis(M_axis, M)
+@variable(model, x[j in J, m in M_axis], Bin);
+@variable(model, y[m in M_axis], Bin);
+@constraint(model, setpartitioning[j in J], sum(x[j,m] for m in M_axis) == 1);
+@constraint(model, knp[m in M_axis], sum(w[j]*x[j,m] for j in J) <= Q[m] * y[m]);
+@objective(model, Min, sum(c[j,m] * x[j,m] for m in M_axis, j in J) + sum(f[m] * y[m] for m in M_axis));
+
+@dantzig_wolfe_decomposition(model, dec, M_axis);
+sp = getsubproblems(dec);
+specify!.(sp, lower_multiplicity = 0);

We optimize:

optimize!(model)
Coluna
+Version 0.7.0 | https://github.com/atoptima/Coluna.jl
+***************************************************************************************
+**** B&B tree root node
+**** Local DB = -Inf, global bounds: [ -Inf , Inf ], time = 0.27 sec.
+***************************************************************************************
+  <st= 1> <it=  1> <et= 0.37> <mst= 0.00> <sp= 0.00> <cols= 5> <al= 0.00> <DB=-129136.2200> <mlp=100000.0000> <PB=Inf>
+  <st= 1> <it=  2> <et= 0.37> <mst= 0.00> <sp= 0.00> <cols= 5> <al= 0.00> <DB=-138882.3000> <mlp=30340.5500> <PB=Inf>
+  <st= 1> <it=  3> <et= 0.37> <mst= 0.00> <sp= 0.00> <cols= 5> <al= 0.00> <DB=-129002.2333> <mlp=10387.9067> <PB=Inf>
+  <st= 1> <it=  4> <et= 0.37> <mst= 0.00> <sp= 0.00> <cols= 5> <al= 0.00> <DB=-116197.4433> <mlp=  418.5400> <PB=Inf>
+  <st= 1> <it=  5> <et= 0.37> <mst= 0.00> <sp= 0.00> <cols= 5> <al= 0.00> <DB=  226.4800> <mlp=  418.5400> <PB=Inf>
+  <st= 1> <it=  6> <et= 0.37> <mst= 0.00> <sp= 0.00> <cols= 5> <al= 0.00> <DB=  352.6265> <mlp=  409.0543> <PB=Inf>
+  <st= 1> <it=  7> <et= 0.38> <mst= 0.00> <sp= 0.00> <cols= 5> <al= 0.00> <DB=  310.9300> <mlp=  405.7200> <PB=Inf>
+  <st= 1> <it=  8> <et= 0.38> <mst= 0.00> <sp= 0.00> <cols= 5> <al= 0.00> <DB=  368.9929> <mlp=  402.2214> <PB=Inf>
+  <st= 1> <it=  9> <et= 0.38> <mst= 0.00> <sp= 0.00> <cols= 5> <al= 0.00> <DB=  365.0175> <mlp=  400.4725> <PB=Inf>
+  <st= 1> <it= 10> <et= 0.38> <mst= 0.00> <sp= 0.00> <cols= 3> <al= 0.00> <DB=  392.9157> <mlp=  398.5957> <PB=Inf>
+  <st= 1> <it= 11> <et= 0.38> <mst= 0.00> <sp= 0.00> <cols= 3> <al= 0.00> <DB=  382.3189> <mlp=  398.4844> <PB=Inf>
+  <st= 1> <it= 12> <et= 0.38> <mst= 0.00> <sp= 0.00> <cols= 3> <al= 0.00> <DB=  392.0920> <mlp=  397.3807> <PB=Inf>
+  <st= 1> <it= 13> <et= 0.38> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  395.0896> <mlp=  396.7196> <PB=Inf>
+  <st= 1> <it= 14> <et= 0.38> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  395.5096> <mlp=  396.7196> <PB=Inf>
+  <st= 1> <it= 15> <et= 0.38> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  394.8988> <mlp=  396.6188> <PB=Inf>
+  <st= 1> <it= 16> <et= 0.39> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  395.4817> <mlp=  396.4117> <PB=Inf>
+  <st= 1> <it= 17> <et= 0.39> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  396.2954> <mlp=  396.2954> <PB=Inf>
+ ──────────────────────────────────────────────────────────────────────────
+                                  Time                    Allocations
+                         ───────────────────────   ────────────────────────
+    Tot / % measured:         2.65s /  21.0%            228MiB /  25.2%
+
+ Section         ncalls     time    %tot     avg     alloc    %tot      avg
+ ──────────────────────────────────────────────────────────────────────────
+ Coluna               1    556ms  100.0%   556ms   57.4MiB  100.0%  57.4MiB
+   SolveLpForm       17   3.18ms    0.6%   187μs   1.49MiB    2.6%  89.8KiB
+ ──────────────────────────────────────────────────────────────────────────
+[ Info: Terminated
+[ Info: Primal bound: Inf
+[ Info: Dual bound: 396.29541664666664

The final dual bound is:

db1 = objective_bound(model)
396.29541664666664

Strengthen with valid inequalities

Let H be the set of configurations of open machines (h[m] = 1 if machine m open; 0 otherwise) such that all jobs can be assigned : sum(h'Q) >= sum(w) i.e. the total capacity of the open machines must exceed the total weight of the jobs.

H = Vector{Int}[]
+for h in digits.(1:(2^length(M) - 1), base=2, pad=length(M))
+    if sum(h'Q) >= sum(w)
+        push!(H, h)
+    end
+end
+H
16-element Vector{Vector{Int64}}:
+ [1, 1, 1, 0, 0]
+ [1, 1, 0, 1, 0]
+ [1, 0, 1, 1, 0]
+ [0, 1, 1, 1, 0]
+ [1, 1, 1, 1, 0]
+ [1, 1, 0, 0, 1]
+ [1, 0, 1, 0, 1]
+ [0, 1, 1, 0, 1]
+ [1, 1, 1, 0, 1]
+ [1, 0, 0, 1, 1]
+ [0, 1, 0, 1, 1]
+ [1, 1, 0, 1, 1]
+ [0, 0, 1, 1, 1]
+ [1, 0, 1, 1, 1]
+ [0, 1, 1, 1, 1]
+ [1, 1, 1, 1, 1]

Let be the solution to the linear relaxation of the problem. Let us try to express as a linear expression of the configurations. If ȳ ∈ conv H, we can derive a cut because the optimal integer solution to the problem uses one of the configurations of H.

We need MathOptInterface to define the cut callback:

using MathOptInterface

The separation algorithm looks for the non-negative coefficients χ[k], k = 1:length(H), : max sum(χ[k] for k in 1:length(H)) such that sum(χ[k]* h for (k,h) in enumerate(H)) <= ̄ȳ. If the objective value is less than 1, we must add a cut.

Since the separation algorithm is a linear program, strong duality applies. So we separate these cuts with the dual.

fc_sep_m = Model(GLPK.Optimizer)
+@variable(fc_sep_m, ψ[m in M] >= 0) # one variable for each constraint
+@constraint(fc_sep_m, config_dual[h in H], ψ'h >= 1) # one constraint for each χ[k]
+MathOptInterface.set(fc_sep_m, MathOptInterface.Silent(), true)

The objective is min ȳ'ψ = sum(χ[k] for k in 1:length(H)). Let ψ* be an optimal solution to the dual. If ȳ'ψ* < 1, then ψ*'y >= 1 is a valid inequality.

function fenchel_cuts_separation(cbdata)
+    println("Fenchel cuts separation callback...")
+    ȳ = [callback_value(cbdata, y[m]) for m in M_axis]
+    @objective(fc_sep_m, Min, ȳ'ψ) # update objective
+    optimize!(fc_sep_m)
+    if objective_value(fc_sep_m) < 1
+        con = @build_constraint(value.(ψ)'y >= 1) # valid inequality.
+        MathOptInterface.submit(model, MathOptInterface.UserCut(cbdata), con)
+    end
+end
+
+MathOptInterface.set(model, MathOptInterface.UserCutCallback(), fenchel_cuts_separation);

We optimize:

optimize!(model)
Coluna
+Version 0.7.0 | https://github.com/atoptima/Coluna.jl
+***************************************************************************************
+**** B&B tree root node
+**** Local DB = -Inf, global bounds: [ -Inf , Inf ], time = 0.00 sec.
+***************************************************************************************
+  <st= 1> <it=  1> <et= 0.00> <mst= 0.00> <sp= 0.00> <cols= 5> <al= 0.00> <DB=-129136.2200> <mlp=100000.0000> <PB=Inf>
+  <st= 1> <it=  2> <et= 0.00> <mst= 0.00> <sp= 0.00> <cols= 5> <al= 0.00> <DB=-139045.0100> <mlp=30340.5500> <PB=Inf>
+  <st= 1> <it=  3> <et= 0.01> <mst= 0.00> <sp= 0.00> <cols= 5> <al= 0.00> <DB=-129366.4000> <mlp=20324.0700> <PB=Inf>
+  <st= 1> <it=  4> <et= 0.01> <mst= 0.00> <sp= 0.00> <cols= 5> <al= 0.00> <DB=-138963.0225> <mlp=10409.6475> <PB=Inf>
+  <st= 1> <it=  5> <et= 0.01> <mst= 0.00> <sp= 0.00> <cols= 5> <al= 0.00> <DB=-116680.1467> <mlp= 3762.3733> <PB=Inf>
+  <st= 1> <it=  6> <et= 0.01> <mst= 0.00> <sp= 0.00> <cols= 5> <al= 0.00> <DB=  338.8240> <mlp=  412.3300> <PB=Inf>
+  <st= 1> <it=  7> <et= 0.01> <mst= 0.00> <sp= 0.00> <cols= 5> <al= 0.00> <DB=  368.4725> <mlp=  408.4225> <PB=Inf>
+  <st= 1> <it=  8> <et= 0.01> <mst= 0.00> <sp= 0.00> <cols= 4> <al= 0.00> <DB=  387.0237> <mlp=  401.4150> <PB=Inf>
+  <st= 1> <it=  9> <et= 0.01> <mst= 0.00> <sp= 0.00> <cols= 3> <al= 0.00> <DB=  387.9907> <mlp=  399.3757> <PB=Inf>
+  <st= 1> <it= 10> <et= 0.01> <mst= 0.00> <sp= 0.00> <cols= 3> <al= 0.00> <DB=  392.3535> <mlp=  397.7405> <PB=Inf>
+  <st= 1> <it= 11> <et= 0.01> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  393.8619> <mlp=  396.8694> <PB=Inf>
+  <st= 1> <it= 12> <et= 0.01> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  395.0354> <mlp=  396.4754> <PB=Inf>
+  <st= 1> <it= 13> <et= 0.01> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  396.2954> <mlp=  396.2954> <PB=Inf>
+Fenchel cuts separation callback...
+Cut separation callback adds 0 new essential cuts and 1 new facultative cuts.
+avg. viol. = 0.54, max. viol. = 0.54, zero viol. = 0.
+  <st= 1> <it=  1> <et= 1.44> <mst= 0.00> <sp= 0.00> <cols= 4> <al= 0.00> <DB=  378.4833> <mlp=  400.0300> <PB=Inf>
+  <st= 1> <it=  2> <et= 1.44> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  398.3356> <mlp=  399.2656> <PB=Inf>
+  <st= 1> <it=  3> <et= 1.44> <mst= 0.00> <sp= 0.00> <cols= 3> <al= 0.00> <DB=  398.1915> <mlp=  398.9556> <PB=Inf>
+  <st= 1> <it=  4> <et= 1.44> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  398.9050> <mlp=  398.9050> <PB=Inf>
+Fenchel cuts separation callback...
+Cut separation callback adds 0 new essential cuts and 1 new facultative cuts.
+avg. viol. = 0.50, max. viol. = 0.50, zero viol. = 0.
+  <st= 1> <it=  1> <et= 1.45> <mst= 0.00> <sp= 0.00> <cols= 3> <al= 0.00> <DB=  397.8726> <mlp=  399.7207> <PB=Inf>
+  <st= 1> <it=  2> <et= 1.45> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  399.6563> <mlp=  399.6563> <PB=Inf>
+Fenchel cuts separation callback...
+Cut separation callback adds 0 new essential cuts and 1 new facultative cuts.
+avg. viol. = 0.26, max. viol. = 0.26, zero viol. = 0.
+  <st= 1> <it=  1> <et= 1.45> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  401.7597> <mlp=  401.7597> <PB=Inf>
+Fenchel cuts separation callback...
+Cut separation callback adds 0 new essential cuts and 1 new facultative cuts.
+avg. viol. = 0.42, max. viol. = 0.42, zero viol. = 0.
+  <st= 1> <it=  1> <et= 1.45> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  403.5883> <mlp=  403.5883> <PB=Inf>
+Fenchel cuts separation callback...
+Cut separation callback adds 0 new essential cuts and 1 new facultative cuts.
+avg. viol. = 0.24, max. viol. = 0.24, zero viol. = 0.
+  <st= 1> <it=  1> <et= 1.45> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  405.8977> <mlp=  405.8977> <PB=Inf>
+Fenchel cuts separation callback...
+Cut separation callback adds 0 new essential cuts and 1 new facultative cuts.
+avg. viol. = 0.21, max. viol. = 0.21, zero viol. = 0.
+  <st= 1> <it=  1> <et= 1.45> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  407.3509> <mlp=  407.3509> <PB=Inf>
+Fenchel cuts separation callback...
+Cut separation callback adds 0 new essential cuts and 1 new facultative cuts.
+avg. viol. = 0.15, max. viol. = 0.15, zero viol. = 0.
+  <st= 1> <it=  1> <et= 1.46> <mst= 0.00> <sp= 0.00> <cols= 5> <al= 0.00> <DB=-15322.0149> <mlp=  886.3318> <PB=Inf>
+  <st= 1> <it=  2> <et= 1.46> <mst= 0.00> <sp= 0.00> <cols= 4> <al= 0.00> <DB=  433.8362> <mlp=  444.2746> <PB=Inf>
+  <st= 1> <it=  3> <et= 1.46> <mst= 0.00> <sp= 0.00> <cols= 3> <al= 0.00> <DB=  434.8000> <mlp=  440.3950> <PB=Inf>
+  <st= 1> <it=  4> <et= 1.46> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  438.0050> <mlp=  440.3950> <PB=Inf>
+  <st= 1> <it=  5> <et= 1.46> <mst= 0.00> <sp= 0.00> <cols= 4> <al= 0.00> <DB=  437.4383> <mlp=  440.3950> <PB=Inf>
+  <st= 1> <it=  6> <et= 1.46> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  439.2200> <mlp=  440.3950> <PB=Inf>
+  <st= 1> <it=  7> <et= 1.46> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  440.1325> <mlp=  440.3950> <PB=Inf>
+  <st= 1> <it=  8> <et= 1.46> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  440.3950> <mlp=  440.3950> <PB=Inf>
+Fenchel cuts separation callback...
+Cut separation callback adds 0 new essential cuts and 0 new facultative cuts.
+Cut separation callback adds 0 new essential cuts and 0 new facultative cuts.
+ ──────────────────────────────────────────────────────────────────────────
+                                  Time                    Allocations
+                         ───────────────────────   ────────────────────────
+    Tot / % measured:         3.50s /  41.8%            364MiB /  54.4%
+
+ Section         ncalls     time    %tot     avg     alloc    %tot      avg
+ ──────────────────────────────────────────────────────────────────────────
+ Coluna               1    1.46s  100.0%   1.46s    198MiB  100.0%   198MiB
+   SolveLpForm       31   7.64ms    0.5%   246μs   3.63MiB    1.8%   120KiB
+ ──────────────────────────────────────────────────────────────────────────
+[ Info: Terminated
+[ Info: Primal bound: 444.40000000000003
+[ Info: Dual bound: 440.395

Valid inequalities significantly improve the previous dual bound:

db2 = objective_bound(model)
+
+
+db2
440.395

This page was generated using Literate.jl.

diff --git a/previews/PR1107/start/identical_sp.jl b/previews/PR1107/start/identical_sp.jl new file mode 100644 index 000000000..582aa1e13 --- /dev/null +++ b/previews/PR1107/start/identical_sp.jl @@ -0,0 +1,100 @@ +# # [Identical subproblems](@id tuto_identical_sp) + +# Let us see an example of resolution using the advantage of identical subproblems with Dantzig-Wolfe and a variant of the Generalized Assignment Problem. + +# Consider a set of machine type `T = 1:nb_machine_types` and a set of jobs `J = 1:nb_jobs`. +# A machine type `t` has a resource capacity `Q[t]` and the factory contains `U[t]` machines of type `t`. +# A job `j` assigned to a machine of type `t` has a cost `c[t,j]` and consumes `w[t,j]` resource units of the machine of type `t`. + +# Consider the following instance : + +nb_machine_types = 2; +nb_jobs = 8; +J = 1:nb_jobs; +Q = [10, 15]; +U = [3, 2]; # 3 machines of type 1 & 2 machines of type 2 +c = [10 11 13 11 12 14 15 8; 20 21 23 21 22 24 25 18]; +w = [4 4 5 4 4 3 4 5; 5 5 6 5 5 4 5 6]; + + +#Here is the JuMP model to optimize this instance with a classic solver : + +using JuMP, GLPK; + +T1 = [1, 2, 3]; # U[1] machines +T2 = [4, 5]; # U[2] machines +M = union(T1, T2); +m2t = [1, 1, 1, 2, 2]; # machine id -> type id + +model = Model(GLPK.Optimizer); +@variable(model, x[M, J], Bin); # 1 if job j assigned to machine m +@constraint(model, cov[j in J], sum(x[m,j] for m in M) == 1); +@constraint(model, knp[m in M], sum(w[m2t[m],j] * x[m,j] for j in J) <= Q[m2t[m]]); +@objective(model, Min, sum(c[m2t[m],j] * x[m,j] for m in M, j in J)); + +optimize!(model); +objective_value(model) + + +# You can decompose over the machines by defining an axis on `M`. +# However, if you want to take advantage of the identical subproblems, you must +# define the formulation as follows : + +using BlockDecomposition, Coluna, JuMP, GLPK; +const BD = BlockDecomposition + +coluna = optimizer_with_attributes( + Coluna.Optimizer, + "params" => Coluna.Params( + solver = Coluna.Algorithm.TreeSearchAlgorithm() # default BCP + ), + "default_optimizer" => GLPK.Optimizer # GLPK for the master & the subproblems +); + +@axis(T, 1:nb_machine_types); + +model = BlockModel(coluna); +@variable(model, x[T, J], Bin); # 1 if job j assigned to machine m +@constraint(model, cov[j in J], sum(x[t,j] for t in T) == 1); +@constraint(model, knp[t in T], sum(w[t] * x[t,j] for j in J) <= Q[t]); +@objective(model, Min, sum(c[t,j] * x[t,j] for t in T, j in J)); + + + +# We assign jobs to a type of machine and we define one knapsack constraint for +# each type. This formulation cannot be solved as it stands with a commercial solver. +# +# Then, we decompose and specify the multiplicity of each knapsack subproblem : + + +@dantzig_wolfe_decomposition(model, dec_on_types, T); +sps = getsubproblems(dec_on_types) +for t in T + specify!(sps[t], lower_multiplicity = 0, upper_multiplicity = U[t]); +end +getsubproblems(dec_on_types) + +# We see that subproblem for machine type 1 has an upper multiplicity equals to 3, +# and the second subproblem for machine type 2 has an upper multiplicity equals to 2. +# It means that we can use at most 3 machines of type 1 and at most 2 machines of type 2. + +# We can then optimize + +optimize!(model); + + +# and retrieve the disaggregated solution + +for t in T + assignment_patterns = BD.getsolutions(model, t); + for pattern in assignment_patterns + nb_times_pattern_used = BD.value(pattern); + jobs_in_pattern = []; + for j in J + if BD.value(pattern, x[t, j]) ≈ 1 + push!(jobs_in_pattern, j); + end + end + println("Pattern of machine type $t used $nb_times_pattern_used times : $jobs_in_pattern"); + end +end diff --git a/previews/PR1107/start/identical_sp/index.html b/previews/PR1107/start/identical_sp/index.html new file mode 100644 index 000000000..06b7b53c4 --- /dev/null +++ b/previews/PR1107/start/identical_sp/index.html @@ -0,0 +1,137 @@ + +Identical subproblems · Coluna.jl

Identical subproblems

Let us see an example of resolution using the advantage of identical subproblems with Dantzig-Wolfe and a variant of the Generalized Assignment Problem.

Consider a set of machine type T = 1:nb_machine_types and a set of jobs J = 1:nb_jobs. A machine type t has a resource capacity Q[t] and the factory contains U[t] machines of type t. A job j assigned to a machine of type t has a cost c[t,j] and consumes w[t,j] resource units of the machine of type t.

Consider the following instance :

nb_machine_types = 2;
+nb_jobs = 8;
+J = 1:nb_jobs;
+Q = [10, 15];
+U = [3, 2];  # 3 machines of type 1 & 2 machines of type 2
+c = [10 11 13 11 12 14 15 8; 20 21 23 21 22 24 25 18];
+w = [4 4 5 4 4 3 4 5; 5 5 6 5 5 4 5 6];
+
+
+#Here is the JuMP model to optimize this instance with a classic solver :
+
+using JuMP, GLPK;
+
+T1 = [1, 2, 3]; # U[1] machines
+T2 = [4, 5]; # U[2] machines
+M = union(T1, T2);
+m2t = [1, 1, 1, 2, 2]; # machine id -> type id
+
+model = Model(GLPK.Optimizer);
+@variable(model, x[M, J], Bin); # 1 if job j assigned to machine m
+@constraint(model, cov[j in J], sum(x[m,j] for m in M) == 1);
+@constraint(model, knp[m in M], sum(w[m2t[m],j] * x[m,j] for j in J) <= Q[m2t[m]]);
+@objective(model, Min, sum(c[m2t[m],j] * x[m,j] for m in M, j in J));
+
+optimize!(model);
+objective_value(model)
114.0

You can decompose over the machines by defining an axis on M. However, if you want to take advantage of the identical subproblems, you must define the formulation as follows :

using BlockDecomposition, Coluna, JuMP, GLPK;
+const BD = BlockDecomposition
+
+coluna = optimizer_with_attributes(
+    Coluna.Optimizer,
+    "params" => Coluna.Params(
+        solver = Coluna.Algorithm.TreeSearchAlgorithm() # default BCP
+    ),
+    "default_optimizer" => GLPK.Optimizer # GLPK for the master & the subproblems
+);
+
+@axis(T, 1:nb_machine_types);
+
+model = BlockModel(coluna);
+@variable(model, x[T, J], Bin); # 1 if job j assigned to machine m
+@constraint(model, cov[j in J], sum(x[t,j] for t in T) == 1);
+@constraint(model, knp[t in T], sum(w[t] * x[t,j] for j in J) <= Q[t]);
+@objective(model, Min, sum(c[t,j] * x[t,j] for t in T, j in J));

We assign jobs to a type of machine and we define one knapsack constraint for each type. This formulation cannot be solved as it stands with a commercial solver.

Then, we decompose and specify the multiplicity of each knapsack subproblem :

@dantzig_wolfe_decomposition(model, dec_on_types, T);
+sps = getsubproblems(dec_on_types)
+for t in T
+    specify!(sps[t], lower_multiplicity = 0, upper_multiplicity = U[t]);
+end
+getsubproblems(dec_on_types)
2-element Vector{BlockDecomposition.SubproblemForm}:
+ Subproblem formulation for T = 1 contains :	 0.0 <= multiplicity <= 3.0
+
+ Subproblem formulation for T = 2 contains :	 0.0 <= multiplicity <= 2.0
+

We see that subproblem for machine type 1 has an upper multiplicity equals to 3, and the second subproblem for machine type 2 has an upper multiplicity equals to 2. It means that we can use at most 3 machines of type 1 and at most 2 machines of type 2.

We can then optimize

optimize!(model);
Coluna
+Version 0.7.0 | https://github.com/atoptima/Coluna.jl
+***************************************************************************************
+**** B&B tree root node
+**** Local DB = -Inf, global bounds: [ -Inf , Inf ], time = 0.00 sec.
+***************************************************************************************
+  <st= 1> <it=  1> <et= 0.00> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=-39828.0000> <mlp=80000.0000> <PB=Inf>
+  <st= 1> <it=  2> <et= 0.00> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=-69747.0000> <mlp=50059.0000> <PB=Inf>
+  <st= 1> <it=  3> <et= 0.00> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=-89715.0000> <mlp=30082.0000> <PB=Inf>
+  <st= 1> <it=  4> <et= 0.00> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=-79810.0000> <mlp=20131.0000> <PB=Inf>
+  <st= 1> <it=  5> <et= 0.00> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=-89721.0000> <mlp=10139.0000> <PB=Inf>
+  <st= 1> <it=  6> <et= 0.00> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=-39936.0000> <mlp=10139.0000> <PB=Inf>
+  <st= 1> <it=  7> <et= 0.00> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB= -176.0000> <mlp=  124.0000> <PB=Inf>
+  <st= 1> <it=  8> <et= 0.00> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=   -6.0000> <mlp=  124.0000> <PB=Inf>
+  <st= 1> <it=  9> <et= 0.01> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  114.0000> <mlp=  114.0000> <PB=Inf>
+***************************************************************************************
+**** B&B tree node N°3, parent N°1, depth 1, 1 untreated node
+**** Local DB = 114.0000, global bounds: [ -Inf , Inf ], time = 0.01 sec.
+**** Branching constraint: x[1,6]<=0.0
+***************************************************************************************
+  <st= 1> <it=  1> <et= 0.01> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=   64.0000> <mlp=  124.0000> <PB=Inf>
+  <st= 1> <it=  2> <et= 0.01> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=   84.0000> <mlp=  124.0000> <PB=Inf>
+  <st= 1> <it=  3> <et= 0.01> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  114.0000> <mlp=  114.0000> <PB=Inf>
+***************************************************************************************
+**** B&B tree node N°5, parent N°3, depth 2, 2 untreated nodes
+**** Local DB = 114.0000, global bounds: [ 114.0000 , Inf ], time = 0.01 sec.
+**** Branching constraint: x[1,5]<=0.0
+***************************************************************************************
+  <st= 1> <it=  1> <et= 0.01> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=   64.0000> <mlp=  124.0000> <PB=Inf>
+  <st= 1> <it=  2> <et= 0.01> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=   94.0000> <mlp=  114.0000> <PB=Inf>
+  <st= 1> <it=  3> <et= 0.01> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  114.0000> <mlp=  114.0000> <PB=Inf>
+┌ Warning: No candidate generated. No children will be generated. However, the node is not conquered.
+└ @ Coluna.Branching ~/work/Coluna.jl/Coluna.jl/src/Branching/Branching.jl:294
+┌ Warning: The solution to the master is not integral and the projection on the original variables is integral.
+│ Your reformulation involves subproblems with upper multiplicity greater than 1.
+│ Column generation algorithm could not create an integral solution to the master using the column generated.
+│ In order to generate columns that can lead to an integral solution, you may have to use a branching scheme that changes the structure of the subproblems.
+│ This is not provided by the default implementation of the branching algorithm in the current version of Coluna.
+└ @ Coluna.Algorithm ~/work/Coluna.jl/Coluna.jl/src/Algorithm/branching/branchingalgo.jl:114
+***************************************************************************************
+**** B&B tree node N°4, parent N°3, depth 2, 1 untreated node
+**** Local DB = 114.0000, global bounds: [ 114.0000 , Inf ], time = 0.01 sec.
+**** Branching constraint: x[1,5]>=1.0
+***************************************************************************************
+  <st= 1> <it=  1> <et= 0.01> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=-29886.0000> <mlp=  114.0000> <PB=Inf>
+  <st= 1> <it=  2> <et= 0.01> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=-49761.0000> <mlp=  114.0000> <PB=Inf>
+  <st= 1> <it=  3> <et= 0.01> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  114.0000> <mlp=  114.0000> <PB=Inf>
+***************************************************************************************
+**** B&B tree node N°2, parent N°1, depth 1, 0 untreated node
+**** Local DB = 114.0000, global bounds: [ 114.0000 , 114.0000 ], time = 0.01 sec.
+**** Branching constraint: x[1,6]>=1.0
+***************************************************************************************
+ ──────────────────────────────────────────────────────────────────────────
+                                  Time                    Allocations
+                         ───────────────────────   ────────────────────────
+    Tot / % measured:         2.29s /   0.4%            165MiB /   2.2%
+
+ Section         ncalls     time    %tot     avg     alloc    %tot      avg
+ ──────────────────────────────────────────────────────────────────────────
+ Coluna               1   10.0ms  100.0%  10.0ms   3.67MiB  100.0%  3.67MiB
+   SolveLpForm       18   2.27ms   22.8%   126μs    930KiB   24.8%  51.7KiB
+ ──────────────────────────────────────────────────────────────────────────
+[ Info: Terminated
+[ Info: Primal bound: 114.0
+[ Info: Dual bound: 114.0

and retrieve the disaggregated solution

for t in T
+    assignment_patterns = BD.getsolutions(model, t);
+    for pattern in assignment_patterns
+        nb_times_pattern_used = BD.value(pattern);
+        jobs_in_pattern = [];
+        for j in J
+            if BD.value(pattern, x[t, j]) ≈ 1
+                push!(jobs_in_pattern, j);
+            end
+        end
+        println("Pattern of machine type $t used $nb_times_pattern_used times : $jobs_in_pattern");
+    end
+end
Pattern of machine type 1 used 1.0 times : Any[1, 8]
+Pattern of machine type 1 used 1.0 times : Any[2, 4]
+Pattern of machine type 1 used 1.0 times : Any[3, 5]
+Pattern of machine type 2 used 1.0 times : Any[6, 7]

This page was generated using Literate.jl.

diff --git a/previews/PR1107/start/initial_columns.jl b/previews/PR1107/start/initial_columns.jl new file mode 100644 index 000000000..90c2e2f74 --- /dev/null +++ b/previews/PR1107/start/initial_columns.jl @@ -0,0 +1,83 @@ +# # Initial columns + +# The initial columns callback let you provide initial columns associated to each problem +# ahead the optimization. +# This callback is useful when you have an efficient heuristic that finds feasible solutions +# to the problem. You can then extract columns from the solutions and give them to Coluna +# through the callback. +# You have to make sure the columns you provide are feasible because Coluna won't check their +# feasibility. +# The cost of the columns will be computed using the perennial cost of subproblem variables. + +# Let us see an example with the following generalized assignment problem : + +M = 1:3; +J = 1:5; +c = [1 1 1 1 1; 1.2 1.2 1.1 1.1 1; 1.3 1.3 1.1 1.2 1.4]; +Q = [3, 2, 3]; + +# with the following Coluna configuration + +using JuMP, GLPK, BlockDecomposition, Coluna; + +coluna = optimizer_with_attributes( + Coluna.Optimizer, + "params" => Coluna.Params( + solver = Coluna.Algorithm.TreeSearchAlgorithm() # default branch-cut-and-price + ), + "default_optimizer" => GLPK.Optimizer # GLPK for the master & the subproblems +); + +# for which the JuMP model takes the form: + +@axis(M_axis, M); +model = BlockModel(coluna); + +@variable(model, x[m in M_axis, j in J], Bin); +@constraint(model, cov[j in J], sum(x[m, j] for m in M_axis) >= 1); +@constraint(model, knp[m in M_axis], sum(x[m, j] for j in J) <= Q[m]); +@objective(model, Min, sum(c[m, j] * x[m, j] for m in M_axis, j in J)); + +@dantzig_wolfe_decomposition(model, decomposition, M_axis) + +subproblems = getsubproblems(decomposition) +specify!.(subproblems, lower_multiplicity = 0, upper_multiplicity = 1) + + +# Let's consider that the following assignment patterns are good candidates: + +machine1 = [[1,2,4], [1,3,4], [2,3,4], [2,3,5]]; +machine2 = [[1,2], [1,5], [2,5], [3,4]]; +machine3 = [[1,2,3], [1,3,4], [1,3,5], [2,3,4]]; + +initial_columns = [machine1, machine2, machine3]; + +# We can write the initial columns callback: + +function initial_columns_callback(cbdata) + ## Retrieve the index of the subproblem (it will be one of the values in M_axis) + spid = BlockDecomposition.callback_spid(cbdata, model) + println("initial columns callback $spid") + + ## Retrieve assignment patterns of a given machine + for col in initial_columns[spid] + ## Create the column in the good representation + vars = [x[spid, j] for j in col] + vals = [1.0 for _ in col] + + ## Submit the column + MOI.submit(model, BlockDecomposition.InitialColumn(cbdata), vars, vals) + end +end + +# The initial columns callback is a function. +# It takes as argument `cbdata` which is a data structure +# that allows the user to interact with Coluna within the callback. + +# We provide the initial columns callback to Coluna through the following method: + +MOI.set(model, BlockDecomposition.InitialColumnsCallback(), initial_columns_callback) + +# You can then optimize: + +optimize!(model) diff --git a/previews/PR1107/start/initial_columns/index.html b/previews/PR1107/start/initial_columns/index.html new file mode 100644 index 000000000..2abc3c986 --- /dev/null +++ b/previews/PR1107/start/initial_columns/index.html @@ -0,0 +1,74 @@ + +Initial columns callback · Coluna.jl

Initial columns

The initial columns callback let you provide initial columns associated to each problem ahead the optimization. This callback is useful when you have an efficient heuristic that finds feasible solutions to the problem. You can then extract columns from the solutions and give them to Coluna through the callback. You have to make sure the columns you provide are feasible because Coluna won't check their feasibility. The cost of the columns will be computed using the perennial cost of subproblem variables.

Let us see an example with the following generalized assignment problem :

M = 1:3;
+J = 1:5;
+c = [1 1 1 1 1; 1.2 1.2 1.1 1.1 1; 1.3 1.3 1.1 1.2 1.4];
+Q = [3, 2, 3];

with the following Coluna configuration

using JuMP, GLPK, BlockDecomposition, Coluna;
+
+coluna = optimizer_with_attributes(
+    Coluna.Optimizer,
+    "params" => Coluna.Params(
+        solver = Coluna.Algorithm.TreeSearchAlgorithm() # default branch-cut-and-price
+    ),
+    "default_optimizer" => GLPK.Optimizer # GLPK for the master & the subproblems
+);

for which the JuMP model takes the form:

@axis(M_axis, M);
+model = BlockModel(coluna);
+
+@variable(model, x[m in M_axis, j in J], Bin);
+@constraint(model, cov[j in J], sum(x[m, j] for m in M_axis) >= 1);
+@constraint(model, knp[m in M_axis], sum(x[m, j] for j in J) <= Q[m]);
+@objective(model, Min, sum(c[m, j] * x[m, j] for m in M_axis, j in J));
+
+@dantzig_wolfe_decomposition(model, decomposition, M_axis)
+
+subproblems = getsubproblems(decomposition)
+specify!.(subproblems, lower_multiplicity = 0, upper_multiplicity = 1)
3-element Vector{Nothing}:
+ nothing
+ nothing
+ nothing

Let's consider that the following assignment patterns are good candidates:

machine1 = [[1,2,4], [1,3,4], [2,3,4], [2,3,5]];
+machine2 = [[1,2], [1,5], [2,5], [3,4]];
+machine3 = [[1,2,3], [1,3,4], [1,3,5], [2,3,4]];
+
+initial_columns = [machine1, machine2, machine3];

We can write the initial columns callback:

function initial_columns_callback(cbdata)
+    # Retrieve the index of the subproblem (it will be one of the values in M_axis)
+    spid = BlockDecomposition.callback_spid(cbdata, model)
+    println("initial columns callback $spid")
+
+    # Retrieve assignment patterns of a given machine
+    for col in initial_columns[spid]
+        # Create the column in the good representation
+        vars = [x[spid, j] for j in col]
+        vals = [1.0 for _ in col]
+
+        # Submit the column
+        MOI.submit(model, BlockDecomposition.InitialColumn(cbdata), vars, vals)
+    end
+end
initial_columns_callback (generic function with 1 method)

The initial columns callback is a function. It takes as argument cbdata which is a data structure that allows the user to interact with Coluna within the callback.

We provide the initial columns callback to Coluna through the following method:

MOI.set(model, BlockDecomposition.InitialColumnsCallback(), initial_columns_callback)

You can then optimize:

optimize!(model)
Coluna
+Version 0.7.0 | https://github.com/atoptima/Coluna.jl
+initial columns callback 3
+initial columns callback 2
+initial columns callback 1
+***************************************************************************************
+**** B&B tree root node
+**** Local DB = -Inf, global bounds: [ -Inf , Inf ], time = 0.46 sec.
+***************************************************************************************
+  <st= 1> <it=  1> <et= 0.46> <mst= 0.00> <sp= 0.00> <cols= 3> <al= 0.00> <DB=   -5.3000> <mlp=    5.2000> <PB=Inf>
+  <st= 1> <it=  2> <et= 0.46> <mst= 0.00> <sp= 0.00> <cols= 3> <al= 0.00> <DB=    4.9500> <mlp=    5.2000> <PB=Inf>
+  <st= 1> <it=  3> <et= 0.46> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=    5.1000> <mlp=    5.1000> <PB=5.1000>
+ ──────────────────────────────────────────────────────────────────────────
+                                  Time                    Allocations
+                         ───────────────────────   ────────────────────────
+    Tot / % measured:         1.83s /  25.3%            154MiB /  28.1%
+
+ Section         ncalls     time    %tot     avg     alloc    %tot      avg
+ ──────────────────────────────────────────────────────────────────────────
+ Coluna               1    464ms  100.0%   464ms   43.1MiB  100.0%  43.1MiB
+   SolveLpForm        3    449μs    0.1%   150μs    149KiB    0.3%  49.5KiB
+ ──────────────────────────────────────────────────────────────────────────
+[ Info: Terminated
+[ Info: Primal bound: 5.1
+[ Info: Dual bound: 5.1

This page was generated using Literate.jl.

diff --git a/previews/PR1107/start/other_pbs/index.html b/previews/PR1107/start/other_pbs/index.html new file mode 100644 index 000000000..36a18a6fa --- /dev/null +++ b/previews/PR1107/start/other_pbs/index.html @@ -0,0 +1,7 @@ + +Other classic problems · Coluna.jl

Here is a non-exhaustive list of classic problems tackled with Coluna:

diff --git a/previews/PR1107/start/pricing.jl b/previews/PR1107/start/pricing.jl new file mode 100644 index 000000000..73c4ee4c4 --- /dev/null +++ b/previews/PR1107/start/pricing.jl @@ -0,0 +1,108 @@ +# # [Pricing callback](@id tuto_pricing_callback) + +# The pricing callback lets you define how to solve the subproblems of a Dantzig-Wolfe +# decomposition to generate a new entering column in the master program. +# This callback is useful when you know an efficient algorithm to solve the subproblems, +# i.e. an algorithm better than solving the subproblem with a MIP solver. + +# First, we load the packages and define aliases : + +using Coluna, BlockDecomposition, JuMP, MathOptInterface, GLPK; +const BD = BlockDecomposition; +const MOI = MathOptInterface; + +# Let us see an example with the following generalized assignment problem : + +M = 1:3; +J = 1:15; +c = [12.7 22.5 8.9 20.8 13.6 12.4 24.8 19.1 11.5 17.4 24.7 6.8 21.7 14.3 10.5; 19.1 24.8 24.4 23.6 16.1 20.6 15.0 9.5 7.9 11.3 22.6 8.0 21.5 14.7 23.2; 18.6 14.1 22.7 9.9 24.2 24.5 20.8 12.9 17.7 11.9 18.7 10.1 9.1 8.9 7.7; 13.1 16.2 16.8 16.7 9.0 16.9 17.9 12.1 17.5 22.0 19.9 14.6 18.2 19.6 24.2]; +w = [61 70 57 82 51 74 98 64 86 80 69 79 60 76 78; 50 57 61 83 81 79 63 99 82 59 83 91 59 99 91;91 81 66 63 59 81 87 90 65 55 57 68 92 91 86; 62 79 73 60 75 66 68 99 69 60 56 100 67 68 54]; +Q = [1020 1460 1530]; + +# with the following Coluna configuration + +coluna = optimizer_with_attributes( + Coluna.Optimizer, + "params" => Coluna.Params( + solver = Coluna.Algorithm.TreeSearchAlgorithm() # default BCP + ), + "default_optimizer" => GLPK.Optimizer # GLPK for the master & the subproblems +); + +# for which the JuMP model takes the form: + +model = BlockModel(coluna); + +@axis(M_axis, M); + +@variable(model, x[m in M_axis, j in J], Bin); +@constraint(model, cov[j in J], sum(x[m,j] for m in M_axis) == 1); +@objective(model, Min, sum(c[m,j]*x[m,j] for m in M_axis, j in J)); +@dantzig_wolfe_decomposition(model, dwdec, M_axis); + +# where, as you can see, we omitted the knapsack constraints. +# These constraints are implicitly defined by the algorithm called in the pricing callback. + +# Let's use a knapsack algorithm defined by the following function to solve the knapsack +# subproblems: + +function solve_knapsack(cost, weight, capacity) + sp_model = Model(GLPK.Optimizer) + items = 1:length(weight) + @variable(sp_model, x[i in items], Bin) + @constraint(sp_model, weight' * x <= capacity) + @objective(sp_model, Min, cost' * x) + optimize!(sp_model) + x_val = value.(x) + return filter(i -> x_val[i] ≈ 1, collect(items)) +end + +# You can replace the content of the function with any algorithm that solves the knapsack +# problem (such as algorithms provided by the unregistered package +# [Knapsacks](https://github.com/rafaelmartinelli/Knapsacks.jl)). + +# The pricing callback is a function. +# It takes as argument `cbdata` which is a data structure +# that allows the user to interact with Coluna within the pricing callback. + +function my_pricing_callback(cbdata) + ## Retrieve the index of the subproblem (it will be one of the values in M_axis) + cur_machine = BD.callback_spid(cbdata, model) + + ## Uncomment to see that the pricing callback is called. + ## println("Pricing callback for machine $(cur_machine).") + + ## Retrieve reduced costs of subproblem variables + red_costs = [BD.callback_reduced_cost(cbdata, x[cur_machine, j]) for j in J] + + ## Run the knapsack algorithm + jobs_assigned_to_cur_machine = solve_knapsack(red_costs, w[cur_machine, :], Q[cur_machine]) + + ## Create the solution (send only variables with non-zero values) + sol_vars = [x[cur_machine, j] for j in jobs_assigned_to_cur_machine] + sol_vals = [1.0 for _ in jobs_assigned_to_cur_machine] + sol_cost = sum(red_costs[j] for j in jobs_assigned_to_cur_machine) + + ## Submit the solution to the subproblem to Coluna + MOI.submit(model, BD.PricingSolution(cbdata), sol_cost, sol_vars, sol_vals) + + ## Submit the dual bound to the solution of the subproblem + ## This bound is used to compute the contribution of the subproblem to the lagrangian + ## bound in column generation. + MOI.submit(model, BD.PricingDualBound(cbdata), sol_cost) # optimal solution + return +end + +# The pricing callback is provided to Coluna using the keyword `solver` in the method +# `specify!`. + +subproblems = BD.getsubproblems(dwdec); +BD.specify!.(subproblems, lower_multiplicity = 0, solver = my_pricing_callback); + +# You can then optimize : + +optimize!(model); + +# and retrieve the information you need as usual : + +objective_value(model) diff --git a/previews/PR1107/start/pricing/index.html b/previews/PR1107/start/pricing/index.html new file mode 100644 index 000000000..e615a0f14 --- /dev/null +++ b/previews/PR1107/start/pricing/index.html @@ -0,0 +1,63 @@ + +Pricing callback · Coluna.jl

Pricing callback

The pricing callback lets you define how to solve the subproblems of a Dantzig-Wolfe decomposition to generate a new entering column in the master program. This callback is useful when you know an efficient algorithm to solve the subproblems, i.e. an algorithm better than solving the subproblem with a MIP solver.

First, we load the packages and define aliases :

using Coluna, BlockDecomposition, JuMP, MathOptInterface, GLPK;
+const BD = BlockDecomposition;
+const MOI = MathOptInterface;

Let us see an example with the following generalized assignment problem :

M = 1:3;
+J = 1:15;
+c = [12.7 22.5 8.9 20.8 13.6 12.4 24.8 19.1 11.5 17.4 24.7 6.8 21.7 14.3 10.5; 19.1 24.8 24.4 23.6 16.1 20.6 15.0 9.5 7.9 11.3 22.6 8.0 21.5 14.7 23.2;  18.6 14.1 22.7 9.9 24.2 24.5 20.8 12.9 17.7 11.9 18.7 10.1 9.1 8.9 7.7; 13.1 16.2 16.8 16.7 9.0 16.9 17.9 12.1 17.5 22.0 19.9 14.6 18.2 19.6 24.2];
+w = [61 70 57 82 51 74 98 64 86 80 69 79 60 76 78; 50 57 61 83 81 79 63 99 82 59 83 91 59 99 91;91 81 66 63 59 81 87 90 65 55 57 68 92 91 86; 62 79 73 60 75 66 68 99 69 60 56 100 67 68 54];
+Q = [1020 1460 1530];

with the following Coluna configuration

coluna = optimizer_with_attributes(
+    Coluna.Optimizer,
+    "params" => Coluna.Params(
+        solver = Coluna.Algorithm.TreeSearchAlgorithm() # default BCP
+    ),
+    "default_optimizer" => GLPK.Optimizer # GLPK for the master & the subproblems
+);

for which the JuMP model takes the form:

model = BlockModel(coluna);
+
+@axis(M_axis, M);
+
+@variable(model, x[m in M_axis, j in J], Bin);
+@constraint(model, cov[j in J], sum(x[m,j] for m in M_axis) == 1);
+@objective(model, Min, sum(c[m,j]*x[m,j] for m in M_axis, j in J));
+@dantzig_wolfe_decomposition(model, dwdec, M_axis);

where, as you can see, we omitted the knapsack constraints. These constraints are implicitly defined by the algorithm called in the pricing callback.

Let's use a knapsack algorithm defined by the following function to solve the knapsack subproblems:

function solve_knapsack(cost, weight, capacity)
+    sp_model = Model(GLPK.Optimizer)
+    items = 1:length(weight)
+    @variable(sp_model, x[i in items], Bin)
+    @constraint(sp_model, weight' * x <= capacity)
+    @objective(sp_model, Min, cost' * x)
+    optimize!(sp_model)
+    x_val = value.(x)
+    return filter(i -> x_val[i] ≈ 1, collect(items))
+end
solve_knapsack (generic function with 1 method)

You can replace the content of the function with any algorithm that solves the knapsack problem (such as algorithms provided by the unregistered package Knapsacks).

The pricing callback is a function. It takes as argument cbdata which is a data structure that allows the user to interact with Coluna within the pricing callback.

function my_pricing_callback(cbdata)
+    # Retrieve the index of the subproblem (it will be one of the values in M_axis)
+    cur_machine = BD.callback_spid(cbdata, model)
+
+    # Uncomment to see that the pricing callback is called.
+    # println("Pricing callback for machine $(cur_machine).")
+
+    # Retrieve reduced costs of subproblem variables
+    red_costs = [BD.callback_reduced_cost(cbdata, x[cur_machine, j]) for j in J]
+
+    # Run the knapsack algorithm
+    jobs_assigned_to_cur_machine = solve_knapsack(red_costs, w[cur_machine, :], Q[cur_machine])
+
+    # Create the solution (send only variables with non-zero values)
+    sol_vars = [x[cur_machine, j] for j in jobs_assigned_to_cur_machine]
+    sol_vals = [1.0 for _ in jobs_assigned_to_cur_machine]
+    sol_cost = sum(red_costs[j] for j in jobs_assigned_to_cur_machine)
+
+    # Submit the solution to the subproblem to Coluna
+    MOI.submit(model, BD.PricingSolution(cbdata), sol_cost, sol_vars, sol_vals)
+
+    # Submit the dual bound to the solution of the subproblem
+    # This bound is used to compute the contribution of the subproblem to the lagrangian
+    # bound in column generation.
+    MOI.submit(model, BD.PricingDualBound(cbdata), sol_cost) # optimal solution
+    return
+end
my_pricing_callback (generic function with 1 method)

The pricing callback is provided to Coluna using the keyword solver in the method specify!.

subproblems = BD.getsubproblems(dwdec);
+BD.specify!.(subproblems, lower_multiplicity = 0, solver = my_pricing_callback);

You can then optimize :

optimize!(model);
+nothing #hide

and retrieve the information you need as usual :

objective_value(model)

This page was generated using Literate.jl.

diff --git a/previews/PR1107/start/start.jl b/previews/PR1107/start/start.jl new file mode 100644 index 000000000..e52fd037d --- /dev/null +++ b/previews/PR1107/start/start.jl @@ -0,0 +1,153 @@ +# # [Column generation with the Generalized Assignment Problem](@id tuto_gen_assignement) + +# This quick start guide introduces the main features of Coluna through the example of the +# Generalized Assignment Problem. + +# ## Classic model solved with MIP solver + +# Consider a set of machines `M` and a set of jobs `J`. +# A machine $m$ has a resource capacity $Q_m$ . +# A job $j$ assigned to a machine $m$ has a cost $c_{mj}$ and consumes $w_{mj}$ resource units +# of the machine $m$. +# The goal is to minimize the sum of job costs while assigning each job to a machine and not +# exceeding the capacity of each machine. + +# Let $x_{mj}$ equal to one if job $j$ is assigned to machine $m$; $0$ otherwise. +# The problem has the original formulation: + +# ```math +# \begin{alignedat}{4} +# \text{[GAP]} \equiv \min \mathrlap{\sum_{m \in M}\sum_{j \in J} c_{mj} x_{mj}} \\ +# \text{s.t.} && \sum_{m \in M} x_{mj} &= 1 \quad& j \in J \\ +# && \sum_{j \in J} w_{mj} x_{mj} &\leq Q_m  \quad \quad& m \in M \\ +# && x_{mj} &\in \{0,1\} &m \in M,\; j \in J +# \end{alignedat} +# ``` + +# Let us consider the following instance. + +M = 1:3; +J = 1:15; +c = [12.7 22.5 8.9 20.8 13.6 12.4 24.8 19.1 11.5 17.4 24.7 6.8 21.7 14.3 10.5; 19.1 24.8 24.4 23.6 16.1 20.6 15.0 9.5 7.9 11.3 22.6 8.0 21.5 14.7 23.2; 18.6 14.1 22.7 9.9 24.2 24.5 20.8 12.9 17.7 11.9 18.7 10.1 9.1 8.9 7.7; 13.1 16.2 16.8 16.7 9.0 16.9 17.9 12.1 17.5 22.0 19.9 14.6 18.2 19.6 24.2]; +w = [61 70 57 82 51 74 98 64 86 80 69 79 60 76 78; 50 57 61 83 81 79 63 99 82 59 83 91 59 99 91;91 81 66 63 59 81 87 90 65 55 57 68 92 91 86; 62 79 73 60 75 66 68 99 69 60 56 100 67 68 54]; +Q = [1020 1460 1530]; + +# We write the model with [JuMP](https://github.com/jump-dev/JuMP.jl), a domain-specific modeling +# language for mathematical optimization embedded in Julia. We optimize with GLPK. +# If you are not familiar with the JuMP package, you may want to check its +# [documentation](https://jump.dev/JuMP.jl/stable/). + +using JuMP, GLPK; + +# A JuMP model for the original formulation is: + +model = Model(GLPK.Optimizer) +@variable(model, x[m in M, j in J], Bin); +@constraint(model, cov[j in J], sum(x[m, j] for m in M) >= 1); +@constraint(model, knp[m in M], sum(w[m, j] * x[m, j] for j in J) <= Q[m]); +@objective(model, Min, sum(c[m, j] * x[m, j] for m in M, j in J)); + +# We optimize the instance and retrieve the objective value. + +optimize!(model); +objective_value(model) + +# ## Try column generation easily with Coluna and BlockDecomposition + +# This model has a block structure: each knapsack constraint defines +# an independent block and the set-partitioning constraints couple these independent +# blocks. By applying the Dantzig-Wolfe reformulation, each knapsack constraint forms +# a tractable subproblem and the set-partitioning constraints are handled in a master problem. + +# To write the model, you need JuMP and BlockDecomposition. +# The latter is an extension built on top of JuMP to model Dantzig-Wolfe and Benders decompositions. +# You will find more documentation about BlockDecomposition in the +# [Decomposition & reformulation](@ref) +# To optimize the problem, you need Coluna and a Julia package that provides a MIP solver such as GLPK. + +# Since we have already loaded JuMP and GLPK, we just need: + +using BlockDecomposition, Coluna; + +# Next, you instantiate the solver and define the algorithm that you use to optimize the problem. +# In this case, the algorithm is a classic branch-and-price provided by Coluna. + +coluna = optimizer_with_attributes( + Coluna.Optimizer, + "params" => Coluna.Params( + solver = Coluna.Algorithm.TreeSearchAlgorithm() # default branch-cut-and-price + ), + "default_optimizer" => GLPK.Optimizer # GLPK for the master & the subproblems +); + +# In BlockDecomposition, an axis is an index set of subproblems. +# Let `M_axis` be the index set of machines; it defines an axis along which we can implement the +# desired decomposition. + +@axis(M_axis, M); + +# In this example, the axis `M_axis` defines one knapsack subproblem for each machine. +# For instance, the first machine index is 1 and is of type `BlockDecomposition.AxisId`: + +M_axis[1] + +typeof(M_axis[1]) + +# Jobs are not involved in the decomposition, set `J` of jobs thus stays as a classic +# range. + +# The model takes the form: + +model = BlockModel(coluna); + +# You can write `BlockModel(coluna; direct_model = true)` to pass names of variables +# and constraints to Coluna. + +@variable(model, x[m in M_axis, j in J], Bin); +@constraint(model, cov[j in J], sum(x[m, j] for m in M_axis) >= 1); +@constraint(model, knp[m in M_axis], sum(w[m, j] * x[m, j] for j in J) <= Q[m]); +@objective(model, Min, sum(c[m, j] * x[m, j] for m in M_axis, j in J)); + +# This is the same model as above except that we use a `BlockModel` instead of a `Model` and +# `M_axis` as the set of machines instead of `M`. +# Therefore, BlockDecomposition will know which variables and constraints are involved in subproblems +# because one of their indices is a `BlockDecomposition.AxisId`. + +# You then apply a Dantzig-Wolfe decomposition along `M_axis`: + +@dantzig_wolfe_decomposition(model, decomposition, M_axis) + +# where `decomposition` is a variable that contains information about the decomposition. + +decomposition + +# Once the decomposition is defined, you can retrieve the master and the subproblems to give +# additional information to the solver. + +master = getmaster(decomposition) +subproblems = getsubproblems(decomposition) + +# The multiplicity of a subproblem is the number of times that the same independent block +# shaped by the subproblem appears in the model. This multiplicity also specifies the number of +# solutions to the subproblem that can appear in the solution to the original problem. + +# In this GAP instance, the upper multiplicity is $1$ because every subproblem is different, +# *i.e.*, every machine is different and used at most once. + +# The lower multiplicity is $0$ because a machine may stay unused. +# The multiplicity specifications take the form: + +specify!.(subproblems, lower_multiplicity = 0, upper_multiplicity = 1) +getsubproblems(decomposition) + +# The model is now fully defined. To solve it, you need to call: + +optimize!(model) + +# You can find more information about the output of the column generation algorithm [ColumnGeneration](@ref). + +# Finally, you can retrieve the solution to the original formulation with JuMP methods. +# For example, if we want to know if job 3 is assigned to machine 1: + +value(x[1,3]) + diff --git a/previews/PR1107/start/start/index.html b/previews/PR1107/start/start/index.html new file mode 100644 index 000000000..913727f20 --- /dev/null +++ b/previews/PR1107/start/start/index.html @@ -0,0 +1,111 @@ + +Column generation · Coluna.jl

Column generation with the Generalized Assignment Problem

This quick start guide introduces the main features of Coluna through the example of the Generalized Assignment Problem.

Classic model solved with MIP solver

Consider a set of machines M and a set of jobs J. A machine $m$ has a resource capacity $Q_m$ . A job $j$ assigned to a machine $m$ has a cost $c_{mj}$ and consumes $w_{mj}$ resource units of the machine $m$. The goal is to minimize the sum of job costs while assigning each job to a machine and not exceeding the capacity of each machine.

Let $x_{mj}$ equal to one if job $j$ is assigned to machine $m$; $0$ otherwise. The problem has the original formulation:

\[\begin{alignedat}{4} +\text{[GAP]} \equiv \min \mathrlap{\sum_{m \in M}\sum_{j \in J} c_{mj} x_{mj}} \\ +\text{s.t.} && \sum_{m \in M} x_{mj} &= 1 \quad& j \in J \\ +&& \sum_{j \in J} w_{mj} x_{mj} &\leq Q_m  \quad \quad& m \in M \\ +&& x_{mj} &\in \{0,1\} &m \in M,\; j \in J +\end{alignedat}\]

Let us consider the following instance.

M = 1:3;
+J = 1:15;
+c = [12.7 22.5 8.9 20.8 13.6 12.4 24.8 19.1 11.5 17.4 24.7 6.8 21.7 14.3 10.5; 19.1 24.8 24.4 23.6 16.1 20.6 15.0 9.5 7.9 11.3 22.6 8.0 21.5 14.7 23.2;  18.6 14.1 22.7 9.9 24.2 24.5 20.8 12.9 17.7 11.9 18.7 10.1 9.1 8.9 7.7; 13.1 16.2 16.8 16.7 9.0 16.9 17.9 12.1 17.5 22.0 19.9 14.6 18.2 19.6 24.2];
+w = [61 70 57 82 51 74 98 64 86 80 69 79 60 76 78; 50 57 61 83 81 79 63 99 82 59 83 91 59 99 91;91 81 66 63 59 81 87 90 65 55 57 68 92 91 86; 62 79 73 60 75 66 68 99 69 60 56 100 67 68 54];
+Q = [1020 1460 1530];

We write the model with JuMP, a domain-specific modeling language for mathematical optimization embedded in Julia. We optimize with GLPK. If you are not familiar with the JuMP package, you may want to check its documentation.

using JuMP, GLPK;

A JuMP model for the original formulation is:

model = Model(GLPK.Optimizer)
+@variable(model, x[m in M, j in J], Bin);
+@constraint(model, cov[j in J], sum(x[m, j] for m in M) >= 1);
+@constraint(model, knp[m in M], sum(w[m, j] * x[m, j] for j in J) <= Q[m]);
+@objective(model, Min, sum(c[m, j] * x[m, j] for m in M, j in J));

We optimize the instance and retrieve the objective value.

optimize!(model);
+objective_value(model)
166.5

Try column generation easily with Coluna and BlockDecomposition

This model has a block structure: each knapsack constraint defines an independent block and the set-partitioning constraints couple these independent blocks. By applying the Dantzig-Wolfe reformulation, each knapsack constraint forms a tractable subproblem and the set-partitioning constraints are handled in a master problem.

To write the model, you need JuMP and BlockDecomposition. The latter is an extension built on top of JuMP to model Dantzig-Wolfe and Benders decompositions. You will find more documentation about BlockDecomposition in the Decomposition & reformulation To optimize the problem, you need Coluna and a Julia package that provides a MIP solver such as GLPK.

Since we have already loaded JuMP and GLPK, we just need:

using BlockDecomposition, Coluna;

Next, you instantiate the solver and define the algorithm that you use to optimize the problem. In this case, the algorithm is a classic branch-and-price provided by Coluna.

coluna = optimizer_with_attributes(
+    Coluna.Optimizer,
+    "params" => Coluna.Params(
+        solver = Coluna.Algorithm.TreeSearchAlgorithm() # default branch-cut-and-price
+    ),
+    "default_optimizer" => GLPK.Optimizer # GLPK for the master & the subproblems
+);

In BlockDecomposition, an axis is an index set of subproblems. Let M_axis be the index set of machines; it defines an axis along which we can implement the desired decomposition.

@axis(M_axis, M);

In this example, the axis M_axis defines one knapsack subproblem for each machine. For instance, the first machine index is 1 and is of type BlockDecomposition.AxisId:

M_axis[1]
+
+typeof(M_axis[1])
BlockDecomposition.AxisId{:M_axis, Int64}

Jobs are not involved in the decomposition, set J of jobs thus stays as a classic range.

The model takes the form:

model = BlockModel(coluna);

You can write BlockModel(coluna; direct_model = true) to pass names of variables and constraints to Coluna.

@variable(model, x[m in M_axis, j in J], Bin);
+@constraint(model, cov[j in J], sum(x[m, j] for m in M_axis) >= 1);
+@constraint(model, knp[m in M_axis], sum(w[m, j] * x[m, j] for j in J) <= Q[m]);
+@objective(model, Min, sum(c[m, j] * x[m, j] for m in M_axis, j in J));

This is the same model as above except that we use a BlockModel instead of a Model and M_axis as the set of machines instead of M. Therefore, BlockDecomposition will know which variables and constraints are involved in subproblems because one of their indices is a BlockDecomposition.AxisId.

You then apply a Dantzig-Wolfe decomposition along M_axis:

@dantzig_wolfe_decomposition(model, decomposition, M_axis)

where decomposition is a variable that contains information about the decomposition.

decomposition
Root - Annotation(BlockDecomposition.Master, BlockDecomposition.DantzigWolfe, lm = 1.0, um = 1.0, id = 2) with 3 subproblems :
+	 2 => Annotation(BlockDecomposition.DwPricingSp, BlockDecomposition.DantzigWolfe, lm = 1.0, um = 1.0, id = 4) 
+	 3 => Annotation(BlockDecomposition.DwPricingSp, BlockDecomposition.DantzigWolfe, lm = 1.0, um = 1.0, id = 5) 
+	 1 => Annotation(BlockDecomposition.DwPricingSp, BlockDecomposition.DantzigWolfe, lm = 1.0, um = 1.0, id = 3) 
+

Once the decomposition is defined, you can retrieve the master and the subproblems to give additional information to the solver.

master = getmaster(decomposition)
+subproblems = getsubproblems(decomposition)
3-element Vector{BlockDecomposition.SubproblemForm}:
+ Subproblem formulation for M_axis = 1 contains :	 1.0 <= multiplicity <= 1.0
+
+ Subproblem formulation for M_axis = 2 contains :	 1.0 <= multiplicity <= 1.0
+
+ Subproblem formulation for M_axis = 3 contains :	 1.0 <= multiplicity <= 1.0
+

The multiplicity of a subproblem is the number of times that the same independent block shaped by the subproblem appears in the model. This multiplicity also specifies the number of solutions to the subproblem that can appear in the solution to the original problem.

In this GAP instance, the upper multiplicity is $1$ because every subproblem is different, i.e., every machine is different and used at most once.

The lower multiplicity is $0$ because a machine may stay unused. The multiplicity specifications take the form:

specify!.(subproblems, lower_multiplicity = 0, upper_multiplicity = 1)
+getsubproblems(decomposition)
3-element Vector{BlockDecomposition.SubproblemForm}:
+ Subproblem formulation for M_axis = 1 contains :	 0.0 <= multiplicity <= 1.0
+
+ Subproblem formulation for M_axis = 2 contains :	 0.0 <= multiplicity <= 1.0
+
+ Subproblem formulation for M_axis = 3 contains :	 0.0 <= multiplicity <= 1.0
+

The model is now fully defined. To solve it, you need to call:

optimize!(model)
Coluna
+Version 0.7.0 | https://github.com/atoptima/Coluna.jl
+***************************************************************************************
+**** B&B tree root node
+**** Local DB = -Inf, global bounds: [ -Inf , Inf ], time = 0.00 sec.
+***************************************************************************************
+  <st= 1> <it=  1> <et= 0.00> <mst= 0.00> <sp= 0.00> <cols= 3> <al= 0.00> <DB=-199514.2000> <mlp=100000.0000> <PB=Inf>
+  <st= 1> <it=  2> <et= 0.00> <mst= 0.00> <sp= 0.00> <cols= 3> <al= 0.00> <DB=-99999.8000> <mlp=50148.8000> <PB=Inf>
+  <st= 1> <it=  3> <et= 0.00> <mst= 0.00> <sp= 0.00> <cols= 3> <al= 0.00> <DB= -453.9000> <mlp=  220.2000> <PB=220.2000>
+  <st= 1> <it=  4> <et= 0.00> <mst= 0.00> <sp= 0.00> <cols= 3> <al= 0.00> <DB= -411.4000> <mlp=  220.2000> <PB=220.2000>
+  <st= 1> <it=  5> <et= 0.00> <mst= 0.00> <sp= 0.00> <cols= 3> <al= 0.00> <DB= -350.0000> <mlp=  220.2000> <PB=220.2000>
+  <st= 1> <it=  6> <et= 0.00> <mst= 0.00> <sp= 0.00> <cols= 3> <al= 0.00> <DB= -314.8000> <mlp=  220.2000> <PB=220.2000>
+  <st= 1> <it=  7> <et= 0.00> <mst= 0.00> <sp= 0.00> <cols= 3> <al= 0.00> <DB= -299.3000> <mlp=  220.2000> <PB=220.2000>
+  <st= 1> <it=  8> <et= 0.00> <mst= 0.00> <sp= 0.00> <cols= 3> <al= 0.00> <DB= -285.0000> <mlp=  220.2000> <PB=220.2000>
+  <st= 1> <it=  9> <et= 0.00> <mst= 0.00> <sp= 0.00> <cols= 3> <al= 0.00> <DB= -155.5667> <mlp=  220.2000> <PB=220.2000>
+  <st= 1> <it= 10> <et= 0.00> <mst= 0.00> <sp= 0.00> <cols= 3> <al= 0.00> <DB= -139.9400> <mlp=  220.2000> <PB=220.2000>
+  <st= 1> <it= 11> <et= 0.01> <mst= 0.00> <sp= 0.00> <cols= 3> <al= 0.00> <DB=  -63.5455> <mlp=  220.2000> <PB=220.2000>
+  <st= 1> <it= 12> <et= 0.01> <mst= 0.00> <sp= 0.00> <cols= 3> <al= 0.00> <DB=  -51.8000> <mlp=  220.2000> <PB=220.2000>
+  <st= 1> <it= 13> <et= 0.01> <mst= 0.00> <sp= 0.00> <cols= 3> <al= 0.00> <DB=   61.1900> <mlp=  208.7500> <PB=220.2000>
+  <st= 1> <it= 14> <et= 0.01> <mst= 0.00> <sp= 0.00> <cols= 3> <al= 0.00> <DB=  -13.0964> <mlp=  207.9821> <PB=220.2000>
+  <st= 1> <it= 15> <et= 0.01> <mst= 0.00> <sp= 0.00> <cols= 3> <al= 0.00> <DB=   57.6200> <mlp=  206.7200> <PB=220.2000>
+  <st= 1> <it= 16> <et= 0.01> <mst= 0.00> <sp= 0.00> <cols= 3> <al= 0.00> <DB=  149.3588> <mlp=  204.8441> <PB=220.2000>
+  <st= 1> <it= 17> <et= 0.01> <mst= 0.00> <sp= 0.00> <cols= 3> <al= 0.00> <DB=   95.3995> <mlp=  202.4207> <PB=220.2000>
+  <st= 1> <it= 18> <et= 0.01> <mst= 0.00> <sp= 0.00> <cols= 3> <al= 0.00> <DB=  107.0038> <mlp=  196.0509> <PB=220.2000>
+  <st= 1> <it= 19> <et= 0.01> <mst= 0.00> <sp= 0.00> <cols= 3> <al= 0.00> <DB=   78.5000> <mlp=  188.6667> <PB=220.2000>
+  <st= 1> <it= 20> <et= 0.01> <mst= 0.00> <sp= 0.00> <cols= 3> <al= 0.00> <DB=  110.5091> <mlp=  186.5545> <PB=220.2000>
+  <st= 1> <it= 21> <et= 0.01> <mst= 0.00> <sp= 0.00> <cols= 3> <al= 0.00> <DB=  150.2941> <mlp=  185.3540> <PB=220.2000>
+  <st= 1> <it= 22> <et= 0.01> <mst= 0.00> <sp= 0.00> <cols= 3> <al= 0.00> <DB=  113.6444> <mlp=  180.6000> <PB=180.6000>
+  <st= 1> <it= 23> <et= 0.01> <mst= 0.00> <sp= 0.00> <cols= 3> <al= 0.00> <DB=  121.1800> <mlp=  180.6000> <PB=180.6000>
+  <st= 1> <it= 24> <et= 0.01> <mst= 0.00> <sp= 0.00> <cols= 3> <al= 0.00> <DB=  121.5385> <mlp=  177.6385> <PB=180.6000>
+  <st= 1> <it= 25> <et= 0.01> <mst= 0.00> <sp= 0.00> <cols= 3> <al= 0.00> <DB=  135.5560> <mlp=  176.6960> <PB=180.6000>
+  <st= 1> <it= 26> <et= 0.01> <mst= 0.00> <sp= 0.00> <cols= 3> <al= 0.00> <DB=  124.8000> <mlp=  175.4000> <PB=180.6000>
+  <st= 1> <it= 27> <et= 0.01> <mst= 0.00> <sp= 0.00> <cols= 3> <al= 0.00> <DB=  140.3645> <mlp=  175.3548> <PB=180.6000>
+  <st= 1> <it= 28> <et= 0.01> <mst= 0.00> <sp= 0.00> <cols= 3> <al= 0.00> <DB=  140.6211> <mlp=  174.9184> <PB=180.6000>
+  <st= 1> <it= 29> <et= 0.01> <mst= 0.00> <sp= 0.00> <cols= 3> <al= 0.00> <DB=  131.2406> <mlp=  173.4125> <PB=180.6000>
+  <st= 1> <it= 30> <et= 0.01> <mst= 0.00> <sp= 0.00> <cols= 3> <al= 0.00> <DB=  146.2667> <mlp=  173.0521> <PB=180.6000>
+  <st= 1> <it= 31> <et= 0.01> <mst= 0.00> <sp= 0.00> <cols= 3> <al= 0.00> <DB=  152.7125> <mlp=  173.0521> <PB=180.6000>
+  <st= 1> <it= 32> <et= 0.01> <mst= 0.00> <sp= 0.00> <cols= 3> <al= 0.00> <DB=  155.1333> <mlp=  172.6444> <PB=180.6000>
+  <st= 1> <it= 33> <et= 0.02> <mst= 0.00> <sp= 0.00> <cols= 3> <al= 0.00> <DB=  156.4722> <mlp=  172.6444> <PB=180.6000>
+  <st= 1> <it= 34> <et= 0.02> <mst= 0.00> <sp= 0.00> <cols= 3> <al= 0.00> <DB=  159.0324> <mlp=  171.1000> <PB=171.1000>
+  <st= 1> <it= 35> <et= 0.02> <mst= 0.00> <sp= 0.00> <cols= 3> <al= 0.00> <DB=  163.5000> <mlp=  167.7000> <PB=167.7000>
+  <st= 1> <it= 36> <et= 0.02> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  164.4000> <mlp=  167.7000> <PB=167.7000>
+  <st= 1> <it= 37> <et= 0.02> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  165.9000> <mlp=  167.7000> <PB=167.7000>
+  <st= 1> <it= 38> <et= 0.02> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  166.5000> <mlp=  167.7000> <PB=167.7000>
+  <st= 1> <it= 39> <et= 0.02> <mst= 0.00> <sp= 0.00> <cols= 2> <al= 0.00> <DB=  165.1000> <mlp=  167.7000> <PB=167.7000>
+  <st= 1> <it= 40> <et= 0.02> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  164.5000> <mlp=  167.7000> <PB=167.7000>
+  <st= 1> <it= 41> <et= 0.02> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  166.3286> <mlp=  167.7000> <PB=167.7000>
+  <st= 1> <it= 42> <et= 0.02> <mst= 0.00> <sp= 0.00> <cols= 1> <al= 0.00> <DB=  166.5000> <mlp=  167.7000> <PB=167.7000>
+  <st= 1> <it= 43> <et= 0.02> <mst= 0.00> <sp= 0.00> <cols= 0> <al= 0.00> <DB=  166.5000> <mlp=  166.5000> <PB=166.5000>
+ ──────────────────────────────────────────────────────────────────────────
+                                  Time                    Allocations
+                         ───────────────────────   ────────────────────────
+    Tot / % measured:         3.04s /  21.2%            275MiB /  32.9%
+
+ Section         ncalls     time    %tot     avg     alloc    %tot      avg
+ ──────────────────────────────────────────────────────────────────────────
+ Coluna               2    646ms  100.0%   323ms   90.3MiB  100.0%  45.2MiB
+   SolveLpForm       91   45.6ms    7.1%   501μs   10.7MiB   11.8%   120KiB
+ ──────────────────────────────────────────────────────────────────────────
+[ Info: Terminated
+[ Info: Primal bound: 166.5
+[ Info: Dual bound: 166.5

You can find more information about the output of the column generation algorithm ColumnGeneration.

Finally, you can retrieve the solution to the original formulation with JuMP methods. For example, if we want to know if job 3 is assigned to machine 1:

value(x[1,3])
1.0

This page was generated using Literate.jl.

diff --git a/previews/PR1107/start/tree2.dot b/previews/PR1107/start/tree2.dot new file mode 100644 index 000000000..b99ce3e97 --- /dev/null +++ b/previews/PR1107/start/tree2.dot @@ -0,0 +1,6 @@ +## dot -Tpdf thisfile > thisfile.pdf + +digraph Branching_Tree { + edge[fontname = "Courier", fontsize = 10]; + n1 [label= "N_1 (1 s) \n[440.3950 , Inf]"]; +}