From 147a5de538744f8ca5703d07d540ac277a73ad33 Mon Sep 17 00:00:00 2001 From: Matt W Date: Tue, 9 Feb 2021 19:19:33 -0500 Subject: [PATCH 1/6] Add JuliaFormatter config file --- .JuliaFormatter.toml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .JuliaFormatter.toml diff --git a/.JuliaFormatter.toml b/.JuliaFormatter.toml new file mode 100644 index 00000000..df92e1b7 --- /dev/null +++ b/.JuliaFormatter.toml @@ -0,0 +1,7 @@ +# Configuration file for JuliaFormatter.jl, see: https://domluna.github.io/JuliaFormatter.jl/stable/config/ + +always_for_in = true +always_use_return = true +margin = 80 +remove_extra_newlines = true +short_to_long_function_def = true From ad063992d072271cbf68e179649e9dabe3836320 Mon Sep 17 00:00:00 2001 From: Matt W Date: Tue, 9 Feb 2021 19:45:49 -0500 Subject: [PATCH 2/6] Add macros for model based SIP interface --- .../interface/scratch_sheet.jl | 129 +++++++++--------- src/eago_semiinfinite/interface/sip_model.jl | 37 +++-- 2 files changed, 93 insertions(+), 73 deletions(-) diff --git a/src/eago_semiinfinite/interface/scratch_sheet.jl b/src/eago_semiinfinite/interface/scratch_sheet.jl index 190965e3..7486df7d 100644 --- a/src/eago_semiinfinite/interface/scratch_sheet.jl +++ b/src/eago_semiinfinite/interface/scratch_sheet.jl @@ -24,81 +24,84 @@ optimize!() using JuMP -import JuMP.object_dictionary - @enum(SIPCons, DECISION, UNCERTAIN, SEMIINFINITE, SIPNOTSET) -""" -""" -Base.@kwdef mutable struct SIPModel <: JuMP.AbstractModel - m::JuMP.Model = JuMP.Model() +Base.@kwdef mutable struct SIPData p::Dict{JuMP.VariableRef,Bool} = Dict{JuMP.VariableRef,Bool}() constr_type::Dict{JuMP.ConstraintRef, SIPCons} = Dict{JuMP.ConstraintRef, SIPCons}() nl_constraint_type::Dict{Int, SIPCons} = Dict{Int, SIPCons}() nl_expression_type::Dict{Int, SIPCons} = Dict{Int, SIPCons}() end -#= -One option is to fully extend JuMP... That requires alot of function definitions -and the only new functionality we actually want to add is classifying constraints -based on is it a decision variable or is it a -=# +function initialize_sip_data(m::JuMP.Model) + m.ext[:sip] = SIPData() +end + +function sip_optimizehook(m::JuMP.Model; kwargs...) + data = _getsipdata(m)::SIPData + if uncertain_variable_num(data) == 0.0 + ret = JuMP.optimize!(m::JuMP.Model, ignore_optimize_hook = true, kwargs...) + else + # ret = model_sip_solve(m) + end + return ret +end + +function enable_semiinfinite(m::JuMP.Model) + haskey(m.ext, :sip) && error("Model already has semiinfinite programs enabled") + initialize_sip_data(m) + JuMP.set_optimize_hook(m, sip_optimizehook) + return nothing +end + +function ModelWithSIP(args...; kwargs...) + m = JuMP.Model(args...; kwargs...) + enable_semiinfinite(m) + return m +end + +""" + @uncertain_variable + +Add an *anonymous* variable to `m::SIPModel` described by the keyword +arguments `kw_args` and returns the variable. + + @uncertain_variable(m, expr, args..., kw_args...) + +This macro simply denotes the variable as belonging to the set of uncertain +variables and then performs `@variable(m, expr, args..., kw_args...)` to +add the variable to the basic JuMP model for storage. All JuMP syntax is +supported. +""" +macro uncertain_variable(args...) + esc(quote + vi = @variable($(args...)) + $(args[1]).ext[:sip].p[vi] = true + vi + end) +end + +""" + @decision_variable + +Add an *anonymous* variable to `m::SIPModel` described by the keyword +arguments `kw_args` and returns the variable. + + @decision_variable(m, expr, args..., kw_args...) + +This macro simply denotes the variable as belonging to the set of decision +variables and then performs `@variable(m, expr, args..., kw_args...)` to +add the variable to the basic JuMP model for storage. All JuMP syntax is +supported. +""" macro decision_variable(args...) esc(quote - mSIP = $args[1] - inputs_2f = $(args[2:end]...) - @show mSIP - @show inputs_2f - #inputs = $(esc(args)) - #@show inputs - #@show typeof(inputs) vi = @variable($(args...)) - #model = $inputs[1] - #for vi in variable_indices - # model.p[vi] = false - #end - # return vi + $(args[1]).ext[:sip].p[vi] = false + vi end) end -mSIP = SIPModel() +mSIP = ModelWithSIP() +@uncertain_variable(mSIP, p) @decision_variable(mSIP, x) - -#= -using MathOptInterface - -m = Model() -@variable(m, 0 <= y <= 1) -@variable(m, x) -@variable(m, a, Bin) -@variable(m, q[i=1:2]) -@constraint(m, y^2 + y + x<= 0) -@constraint(m, [x, y-1, y-2] in SecondOrderCone()) -@NLconstraint(m, sin(x) + cos(y) <= 0.0) -@constraint(m, 2x - 1 ⟂ x) -@constraint(m, q in SOS2([3,5])) -@constraint(m, a => {x + y <= 1}) -@SDconstraint(m, [x 2x; 3x 4x] >= ones(2, 2)) - -A = [1 2; 3 4] -b = [5,6] -@constraint(m, con, A * x .== b) - - -list = list_of_constraint_types(m) - -cons0 = all_constraints(m, VariableRef, MOI.LessThan{Float64}) -cons1 = all_constraints(m, VariableRef, MOI.GreaterThan{Float64}) -cons2 = all_constraints(m, GenericQuadExpr{Float64,VariableRef}, MOI.LessThan{Float64}) -cons2 = all_constraints(m, GenericQuadExpr{Float64,VariableRef}, MOI.LessThan{Float64}) - -cons0_1 = cons0[1] -cons1_1 = cons1[1] -cons2_1 = cons2[1] -cons3_1 = cons2[1] - -out = constraint_object(cons2_1).func -l_terms = linear_terms(out) -q_terms = quad_terms(out) -#all_consts = all_constraints(m, list[1][1], list[1][1]) -=# diff --git a/src/eago_semiinfinite/interface/sip_model.jl b/src/eago_semiinfinite/interface/sip_model.jl index bd0a1d4d..3663b4f4 100644 --- a/src/eago_semiinfinite/interface/sip_model.jl +++ b/src/eago_semiinfinite/interface/sip_model.jl @@ -13,9 +13,6 @@ for a discussion of the roadmap here. @enum(SIPCons, DECISION, UNCERTAIN, SEMIINFINITE, SIPNOTSET) - -""" -""" Base.@kwdef mutable struct SIPData p::Dict{JuMP.VariableRef,Bool} = Dict{JuMP.VariableRef,Bool}() constr_type::Dict{JuMP.ConstraintRef, SIPCons} = Dict{JuMP.ConstraintRef, SIPCons}() @@ -58,16 +55,36 @@ arguments `kw_args` and returns the variable. @uncertain_variable(m, expr, args..., kw_args...) -This macro simply denotes the variable as belonging to the set of descision +This macro simply denotes the variable as belonging to the set of uncertain variables and then performs `@variable(m, expr, args..., kw_args...)` to add the variable to the basic JuMP model for storage. All JuMP syntax is supported. """ macro uncertain_variable(args...) - vi = @variable(args...) - model = esc(args[1]) - for vi in variable_indices - model.p[vi] = true - end - return vi + esc(quote + vi = @variable($(args...)) + $(args[1]).ext[:sip].p[vi] = true + vi + end) +end + +""" + @decision_variable + +Add an *anonymous* variable to `m::SIPModel` described by the keyword +arguments `kw_args` and returns the variable. + + @decision_variable(m, expr, args..., kw_args...) + +This macro simply denotes the variable as belonging to the set of decision +variables and then performs `@variable(m, expr, args..., kw_args...)` to +add the variable to the basic JuMP model for storage. All JuMP syntax is +supported. +""" +macro decision_variable(args...) + esc(quote + vi = @variable($(args...)) + $(args[1]).ext[:sip].p[vi] = false + vi + end) end From 7bc77ab3616dc94f3303b4904c19eae2868318ac Mon Sep 17 00:00:00 2001 From: Matt W Date: Tue, 9 Feb 2021 20:13:29 -0500 Subject: [PATCH 3/6] Work on JuMP like SIP interface --- src/eago_semiinfinite/interface/sip_model.jl | 49 +++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/src/eago_semiinfinite/interface/sip_model.jl b/src/eago_semiinfinite/interface/sip_model.jl index 3663b4f4..6d6a5d4c 100644 --- a/src/eago_semiinfinite/interface/sip_model.jl +++ b/src/eago_semiinfinite/interface/sip_model.jl @@ -12,13 +12,59 @@ for a discussion of the roadmap here. =# @enum(SIPCons, DECISION, UNCERTAIN, SEMIINFINITE, SIPNOTSET) +const MOIU_AUTO_CACHE = MOIU.CachingOptimizerMode.AUTOMATIC Base.@kwdef mutable struct SIPData p::Dict{JuMP.VariableRef,Bool} = Dict{JuMP.VariableRef,Bool}() constr_type::Dict{JuMP.ConstraintRef, SIPCons} = Dict{JuMP.ConstraintRef, SIPCons}() nl_constraint_type::Dict{Int, SIPCons} = Dict{Int, SIPCons}() nl_expression_type::Dict{Int, SIPCons} = Dict{Int, SIPCons}() + model_decision::Model = Model(caching_mode = MOIU_AUTO_CACHE) + model_uncertain::Model = Model((aching_mode = MOIU_AUTO_CACHE) + model_semiinfinite::Model = Model(caching_mode = MOIU_AUTO_CACHE) end +uncertain_variable_num(d::SIPData) = any(x -> x, d.p) + +function initialize_pure_models!(m::JuMP.Model) + if haskey(m.ext, :sip) + + # extract sip data and storage models + sip_data = _get_sip_data(m)::SIPData + m_dec = sip_data.model_decision + m_unc = sip_data.model_uncertain + m_sip = sip_data.model_semiinfinite + + # get index map + indx_map_dec = MOI.copy_to(backend(m_dec), backend(m), copy_names = true) + indx_map_unc = MOI.copy_to(backend(m_unc), backend(m), copy_names = true) + indx_map_sip = MOI.copy_to(backend(m_sip), backend(m), copy_names = true) + + # copy extension data + for (key, data) in m.ext + if key != :sip + m_dec.ext[key] = copy_extension_data(data, m_dec, m) + m_unc.ext[key] = copy_extension_data(data, m_unc, m) + m_sip.ext[key] = copy_extension_data(data, m_sip, m) + end + end + + # copy objects (TODO need to check on SIP, DEC, UNC) + reference_map = ReferenceMap(new_model, index_map) + for (name, value) in object_dictionary(model) + new_model[name] = getindex.(reference_map, value) + end + + if m.nlp_data !== nothing + # TODO copy NLP data + end + + for (vi, is_uncertain) in m.ext.p + model_semiinfinite + model_decision_ + end + return nothing +end + function initialize_sip_data(m::JuMP.Model) m.ext[:sip] = SIPData() @@ -29,7 +75,8 @@ function sip_optimizehook(m::JuMP.Model; kwargs...) if uncertain_variable_num(data) == 0.0 ret = JuMP.optimize!(m::JuMP.Model, ignore_optimize_hook = true, kwargs...) else - # ret = model_sip_solve(m) + initialize_pure_models!(m) + #ret = model_sip_solve(m) end return ret end From 85a38f31ed33bf386cc4168cf37c1b6d9f30749c Mon Sep 17 00:00:00 2001 From: Matt W Date: Wed, 10 Feb 2021 17:35:10 -0500 Subject: [PATCH 4/6] Work on populating SIP subproblems from JuMP --- .../interface/scratch_sheet.jl | 108 +++--------------- src/eago_semiinfinite/interface/sip_model.jl | 32 +++++- 2 files changed, 46 insertions(+), 94 deletions(-) diff --git a/src/eago_semiinfinite/interface/scratch_sheet.jl b/src/eago_semiinfinite/interface/scratch_sheet.jl index 7486df7d..baf5316d 100644 --- a/src/eago_semiinfinite/interface/scratch_sheet.jl +++ b/src/eago_semiinfinite/interface/scratch_sheet.jl @@ -1,107 +1,29 @@ #= =# - - - -#= -Basic idea behind syntax -mSIP = SIPModel() - -@decision_variable(mSIP, 0.0 <= x <= 1.0) -@decision_variable(mSIP, z, Bin) -@uncertain_variable(mSIP, p, 0.0 <= x <= 1.0) - - -@constraint(mSIP, ...) -@objective(mSIP, Min, ...) - -@NLconstraint(mSIP, ...) -@NLobjective(mSIP, Min, ...) - -optimize!() -=# - using JuMP -@enum(SIPCons, DECISION, UNCERTAIN, SEMIINFINITE, SIPNOTSET) +function pseudo_copy(m::JuMP.Model) -Base.@kwdef mutable struct SIPData - p::Dict{JuMP.VariableRef,Bool} = Dict{JuMP.VariableRef,Bool}() - constr_type::Dict{JuMP.ConstraintRef, SIPCons} = Dict{JuMP.ConstraintRef, SIPCons}() - nl_constraint_type::Dict{Int, SIPCons} = Dict{Int, SIPCons}() - nl_expression_type::Dict{Int, SIPCons} = Dict{Int, SIPCons}() -end + nlp_new = deepcopy(m.nlp_data) + nlp_new.evaluator = NLPEvaluator(m) + MOI.initialize(nlp_new.evaluator, Symbol[:ExprGraph]) -function initialize_sip_data(m::JuMP.Model) - m.ext[:sip] = SIPData() -end + obj_expr = MOI.objective_expr(nlp_new.evaluator) + con_expr = MOI.constraint_expr(nlp_new.evaluator, 1) -function sip_optimizehook(m::JuMP.Model; kwargs...) - data = _getsipdata(m)::SIPData - if uncertain_variable_num(data) == 0.0 - ret = JuMP.optimize!(m::JuMP.Model, ignore_optimize_hook = true, kwargs...) - else - # ret = model_sip_solve(m) - end - return ret -end + @show obj_expr + @show con_expr -function enable_semiinfinite(m::JuMP.Model) - haskey(m.ext, :sip) && error("Model already has semiinfinite programs enabled") - initialize_sip_data(m) - JuMP.set_optimize_hook(m, sip_optimizehook) return nothing end -function ModelWithSIP(args...; kwargs...) - m = JuMP.Model(args...; kwargs...) - enable_semiinfinite(m) - return m -end - -""" - @uncertain_variable - -Add an *anonymous* variable to `m::SIPModel` described by the keyword -arguments `kw_args` and returns the variable. - - @uncertain_variable(m, expr, args..., kw_args...) - -This macro simply denotes the variable as belonging to the set of uncertain -variables and then performs `@variable(m, expr, args..., kw_args...)` to -add the variable to the basic JuMP model for storage. All JuMP syntax is -supported. -""" -macro uncertain_variable(args...) - esc(quote - vi = @variable($(args...)) - $(args[1]).ext[:sip].p[vi] = true - vi - end) -end - -""" - @decision_variable +m = Model() +@variable(m, x[1:3]) +@variable(m, y) +@NLconstraint(m, x[1] + y^2 <= 0.0) +@NLobjective(m, Min, x[2]*x[3]*y^2) -Add an *anonymous* variable to `m::SIPModel` described by the keyword -arguments `kw_args` and returns the variable. - - @decision_variable(m, expr, args..., kw_args...) - -This macro simply denotes the variable as belonging to the set of decision -variables and then performs `@variable(m, expr, args..., kw_args...)` to -add the variable to the basic JuMP model for storage. All JuMP syntax is -supported. -""" -macro decision_variable(args...) - esc(quote - vi = @variable($(args...)) - $(args[1]).ext[:sip].p[vi] = false - vi - end) -end +nlp_data = m.nlp_data -mSIP = ModelWithSIP() -@uncertain_variable(mSIP, p) -@decision_variable(mSIP, x) +out = pseudo_copy(m) diff --git a/src/eago_semiinfinite/interface/sip_model.jl b/src/eago_semiinfinite/interface/sip_model.jl index 6d6a5d4c..d4cf1aff 100644 --- a/src/eago_semiinfinite/interface/sip_model.jl +++ b/src/eago_semiinfinite/interface/sip_model.jl @@ -25,6 +25,31 @@ Base.@kwdef mutable struct SIPData end uncertain_variable_num(d::SIPData) = any(x -> x, d.p) +function _copy_nlp_objective!(out::JuMP.Model, m::JuMP.Model, nlp_data::JuMP._NLPData) + if nlp_data.nlobj !== nothing + # TODO CHECK THAT THE FORMULATION IS PEACHY + obj_expr = MOI.objective_expr(nlp_new.evaluator) + # obj_expr_jref = substitute moi variables to jump refs... + @NLobjective(m.ext[:sip].model_decision, Min, obj_expr_jref) + end + return nothing +end + +function _copy_nlp_constraints!(m::JuMP.Model, nlp_data::JuMP._NLPData) + for i = 1:length(nlp_data.nlconstr) + con_expr = MOI.constraint_expr(nlp_data.evaluator, i) + end + return nothing +end + +""" + +Populates variables and constraints into one of three models based on classification: +1) store decision variables and constraints containing only decision variables in +`m.model_decision`, 2) store uncertain variables and constraints containing only +uncertain variables in `m.model_uncertain`, 3) store all variables and all +constraints containing both decision variables to `m.model_semiinfinite`. +""" function initialize_pure_models!(m::JuMP.Model) if haskey(m.ext, :sip) @@ -55,7 +80,12 @@ function initialize_pure_models!(m::JuMP.Model) end if m.nlp_data !== nothing - # TODO copy NLP data + nlp_data = deepcopy(m.nlp_data) + nlp_data.evaluator = NLPEvaluator(m) + MOI.initialize(nlp_data.evaluator, Symbol[:ExprGraph]) + copy_nlp_objective!(m, nlp_data) + copy_nlp_expressions!(m, nlp_data) + copy_nlp_constraints!(m, nlp_data) end for (vi, is_uncertain) in m.ext.p From 3649a3376e414d5022e2714f27cda83fb3c52422 Mon Sep 17 00:00:00 2001 From: Matt W Date: Fri, 12 Feb 2021 15:15:17 -0500 Subject: [PATCH 5/6] Work on SIPOptimizer --- src/eago_semiinfinite/interface/optimizer.jl | 68 ++++++++++++++++++++ src/eago_semiinfinite/interface/sip_model.jl | 3 +- 2 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 src/eago_semiinfinite/interface/optimizer.jl diff --git a/src/eago_semiinfinite/interface/optimizer.jl b/src/eago_semiinfinite/interface/optimizer.jl new file mode 100644 index 00000000..e1556f73 --- /dev/null +++ b/src/eago_semiinfinite/interface/optimizer.jl @@ -0,0 +1,68 @@ +Base.@kwdef mutable struct SIPOptimizer <: MOI.AbstractOptimizer + objective_sense::MOI.OptimizationSense = MOI.FEASIBLE_SENSE + time_limit::Float64 = 1000.0 + is_silent::Bool = false + x::Vector{Float64} = Float64[] + obj_bound::Float64 = 0.0 + obj_value::Float64 = 0.0 + termination_status::MOI.TerminationStatusCode = MOI.OPTIMIZE_NOT_CALLED + result_status_code::MOI.ResultStatusCode = MOI.UNKNOWN_RESULT_STATUS + run_time::Float64 = 0.0 +end + +function MOI.empty!(m::SIPOptimizer) +end +function MOI.is_empty(m::Optimizer) +end + +function MOI.set(m::SIPOptimizer, ::MOI.Silent, value) + m.is_silent = value + return nothing +end + +function MOI.set(m::SIPOptimizer, ::MOI.TimeLimitSec, value::Nothing) + m.time_limit = Inf + return nothing +end +function MOI.set(m::SIPOptimizer, ::MOI.TimeLimitSec, value::Float64) + m.time_limit = value + return nothing +end + +MOI.set(m::SIPOptimizer, p::MOI.RawParameter, value) = error("The SIPOptimizer does not have any raw parameters.") + +function MOI.get(m::SIPOptimizer, ::MOI.ListOfVariableIndices) +end +function MOI.get(m::SIPOptimizer, ::MOI.NumberOfVariables) +end + +function MOI.get(m::SIPOptimizer, ::MOI.ObjectiveValue) + +end +function MOI.get(m::SIPOptimizer, ::MOI.ObjectiveBound) + +end + +MOI.get(m::SIPOptimizer, p::MOI.RawParameter) = error("The SIPOptimizer does not have any raw parameters.") + + +function MOI.get(m::SIPOptimizer, ::MOI.RelativeGap) + abs(objective_bound() - objective_value())/objective_value() +end +MOI.get(m::SIPOptimizer, ::MOI.SolverName) = "EAGO: SIPOptimizer" +MOI.get(m::SIPOptimizer, ::MOI.TerminationStatus) = m._termination_status_code +MOI.get(m::SIPOptimizer, ::MOI.PrimalStatus) = m.result_status_code +MOI.get(m::SIPOptimizer, ::MOI.SolveTime) = m.run_time +MOI.get(m::SIPOptimizer, ::MOI.ResultCount) = (m.result_status_code === MOI.FEASIBLE_POINT) ? 1 : 0 +MOI.get(m::SIPOptimizer, ::MOI.TimeLimitSec) = m.time_limit + +MOI.supports(::SIPOptimizer, ::MOI.TimeLimitSec) = true +MOI.supports(::SIPOptimizer, ::MOI.ObjectiveSense) = true +function MOI.set(m::SIPOptimizer, ::MOI.ObjectiveSense, sense::MOI.OptimizationSense) + m.objective_sense = sense + return nothing +end + + +function MOI.optimize!(m::SIPOptimizer) +end diff --git a/src/eago_semiinfinite/interface/sip_model.jl b/src/eago_semiinfinite/interface/sip_model.jl index d4cf1aff..5a3d5712 100644 --- a/src/eago_semiinfinite/interface/sip_model.jl +++ b/src/eago_semiinfinite/interface/sip_model.jl @@ -106,7 +106,7 @@ function sip_optimizehook(m::JuMP.Model; kwargs...) ret = JuMP.optimize!(m::JuMP.Model, ignore_optimize_hook = true, kwargs...) else initialize_pure_models!(m) - #ret = model_sip_solve(m) + ret = sip_solve(data) end return ret end @@ -120,6 +120,7 @@ end function ModelWithSIP(args...; kwargs...) m = JuMP.Model(args...; kwargs...) + set_optimizer(SIPOptimizer) enable_semiinfinite(m) return m end From 7fd9613e41085d19d2f954bf1888f1d080dcbcb0 Mon Sep 17 00:00:00 2001 From: Matt W Date: Fri, 12 Feb 2021 15:23:44 -0500 Subject: [PATCH 6/6] Add tagbot and documentation workflow --- .github/workflows/TagBot.yml | 15 +++++++++++++++ .github/workflows/documentation.yml | 23 +++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 .github/workflows/TagBot.yml create mode 100644 .github/workflows/documentation.yml diff --git a/.github/workflows/TagBot.yml b/.github/workflows/TagBot.yml new file mode 100644 index 00000000..083baf95 --- /dev/null +++ b/.github/workflows/TagBot.yml @@ -0,0 +1,15 @@ +name: TagBot +on: + issue_comment: + types: + - created + workflow_dispatch: +jobs: + TagBot: + if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot' + runs-on: ubuntu-latest + steps: + - uses: JuliaRegistries/TagBot@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + ssh: ${{ secrets.DOCUMENTER_KEY }} diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml new file mode 100644 index 00000000..ed224493 --- /dev/null +++ b/.github/workflows/documentation.yml @@ -0,0 +1,23 @@ +name: Documentation +on: + push: + branches: [master] + tags: '*' + pull_request: + types: [opened, synchronize, reopened] +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: julia-actions/setup-julia@latest + with: + # Build documentation on Julia 1.0 + version: '1.0' + - name: Install dependencies + run: julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()' + - name: Build and deploy + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # For authentication with GitHub Actions token + DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} # For authentication with SSH deploy key + run: julia --project=docs/ docs/make.jl