diff --git a/docs/make.jl b/docs/make.jl
index bd77d2c94c..c7e7a054f4 100644
--- a/docs/make.jl
+++ b/docs/make.jl
@@ -58,6 +58,7 @@ makedocs(
"Lossless nodal DC power flows" => joinpath("advanced_concepts", "Lossless_DC_power_flow.md"),
"Representative days with seasonal storages" => joinpath("advanced_concepts", "representative_days_w_seasonal_storage.md"),
"Imposing renewable energy targets" => joinpath("advanced_concepts", "cumulated_flow_restrictions.md"),
+ "Modelling to generate alternatives" => joinpath("advanced_concepts", "mga.md"),
],
"Library" => "library.md",
],
diff --git a/docs/src/advanced_concepts/decomposition.md b/docs/src/advanced_concepts/decomposition.md
index 75380b5920..07a15bf857 100644
--- a/docs/src/advanced_concepts/decomposition.md
+++ b/docs/src/advanced_concepts/decomposition.md
@@ -49,13 +49,13 @@ on that variable, one can add an output object with the variable name prepended
Finally, if any constraint duals or reduced_cost values are requested via a report, calculate_duals is set to true and the final fixed LP solve is triggered.
## Using Decomposition
-The decomposition framework creates a master problem where the investment variables are optimised. The decomposition framework is invoked when a model object with the parameter [model\_type](@ref) set to `:spineopt_operations` is found and a second model object with `model_type` set to `:spineopt_master`. Once these conditions are met, all investment decisions in the model are automatically decomposed and optimised in the master problem. This behaviour may change in the future to allow some investment decisions to be optimised in the operations problem and some optimised in the master problem as desired.
+The decomposition framework creates a master problem where the investment variables are optimised. The decomposition framework is invoked when a model object with the parameter [model\_type](@ref) set to `:spineopt_standard` is found and a second model object with `model_type` set to `:spineopt_benders_master`. Once these conditions are met, all investment decisions in the model are automatically decomposed and optimised in the master problem. This behaviour may change in the future to allow some investment decisions to be optimised in the operations problem and some optimised in the master problem as desired.
**Steps to involke decomposition in an investments problem**
Assuming one has set up a conventional investments problem as described in [Investment Optimization](@ref) the following additional steps are required to utilise the decomposition framework:
- Create a new [model](@ref) object to representent the benders master problem
- - Set the [model\_type](@ref) parameter for the master problem model to `spineopt_master`.
- - Set the [model\_type](@ref) parameter for the existing conventional, operations problem model to `spineopt_operations`.
+ - Set the [model\_type](@ref) parameter for the master problem model to `spineopt_benders_master`.
+ - Set the [model\_type](@ref) parameter for the existing conventional, operations problem model to `spineopt_standard`.
- Specify the master problem [model](@ref) parameter, `max_gap` - This determines the master problem convergence criterion for the relative benders gap. A value of 0.05 will represent a relative benders gap of 5%.
- Specify the master problem [model](@ref) parameter `max_iterations` - This determines the master problem convergence criterion for the number of iterations. A value of 10 could be appropriate but this is highly dependent on the size and nature of the problem
- Specify appropriate `model_report` relationships to determine which reports are written for which model
diff --git a/docs/src/advanced_concepts/mga.md b/docs/src/advanced_concepts/mga.md
new file mode 100644
index 0000000000..25208c8069
--- /dev/null
+++ b/docs/src/advanced_concepts/mga.md
@@ -0,0 +1,13 @@
+# [Modelling to generate alternatives](@id mga-advanced)
+
+Through modelling to generate alternatives (short MGA), near-optimal solutions can be explored under certain conditions.
+The idea is that an orginal problem is solved, and subsequently solved again under the condition that the realization of variables should be maximally different from the previous iteration(s), while keeping the objective function within a certain threshold (defined by [max\_mga\_slack](@ref)).
+
+In SpineOpt, we choose [units\_invested\_available](@ref), [connections\_invested\_available](@ref), and [storages\_invested\_available](@ref) as variables that can be considered for the maximum-difference-problem. The implementation is based on [Modelling to generate alternatives: A technique to explore uncertainty in energy-environment-economy models](https://doi.org/10.1016/j.apenergy.2017.03.065).
+
+## How to set up an MGA problem
+- [model](@ref): In order to explore an MGA model, you will need one model of type [spineopt\_mga](@ref model_type_list). You should also define the number of iterations ([max\_mga\_iterations](@ref), and the maximum allowed deviation from the original objective function ([max\_mga\_slack](@ref)).
+- at least one investment candidate of type [unit](@ref), [connection](@ref), or [node](@ref). For more details on how to set up an investment problem please see: [Investment Optimization](@ref).
+- To include the investment decisions in the MGA difference maximization, the parameter [units\_invested\_mga](@ref), [connections\_invested\_mga](@ref), or [storages\_invested\_mga](@ref) need to be set to true, respectively.
+- The original MGA formulation is non-convex (maximization problem of an absolute function), but has been linearized through big M method. For this purpose, one should always make sure that [units\_invested\_big\_m\_mga](@ref), [connections\_invested\_big\_m\_mga](@ref), or [storages\_invested\_big\_m\_mga](@ref), respectively, is sufficently large to always be larger the the maximum possible difference per MGA iteration. (Typically the number of candidates could suffice.)
+- As [output](@ref)s are used to intermediately store solutions from different MGA runs, it is important that `units_invested`, `connections_invested`, or `storages_invested`, respectively, are defined as [output](@ref) objects in your database.
diff --git a/docs/src/concept_reference/connections_invested_big_m_mga.md b/docs/src/concept_reference/connections_invested_big_m_mga.md
new file mode 100644
index 0000000000..0084f53a21
--- /dev/null
+++ b/docs/src/concept_reference/connections_invested_big_m_mga.md
@@ -0,0 +1,3 @@
+The [connections\_invested\_big\_m\_mga](@ref) parameter is used in combination with the MGA algorithm (see [mga-advanced](@ref)).
+It defines an upper bound on the maximum difference between any MGA iteration. The big M should be chosen always sufficiently large. (Typically, a value equivalent to
+[candidate\_connections](@ref) could suffice.)
diff --git a/docs/src/concept_reference/connections_invested_mga.md b/docs/src/concept_reference/connections_invested_mga.md
new file mode 100644
index 0000000000..41ed35927c
--- /dev/null
+++ b/docs/src/concept_reference/connections_invested_mga.md
@@ -0,0 +1,2 @@
+The [connections\_invested\_mga](@ref) is a boolean parameter that can be used in combination with the MGA algorithm (see [mga-advanced](@ref)). As soon as
+the value of [connections\_invested\_mga](@ref) is set to `true`, investment decisions in this connection, or group of connections, will be included in the MGA algorithm.
diff --git a/docs/src/concept_reference/max_iterations.md b/docs/src/concept_reference/max_iterations.md
index fefc8fd162..0a843b212b 100644
--- a/docs/src/concept_reference/max_iterations.md
+++ b/docs/src/concept_reference/max_iterations.md
@@ -1 +1 @@
-When the [model](@ref) in question is of type `:spineopt_master`, this determines the maximum number of Benders iterations.
\ No newline at end of file
+When the [model](@ref) in question is of type `:spineopt_benders_master`, this determines the maximum number of Benders iterations.
\ No newline at end of file
diff --git a/docs/src/concept_reference/max_mga_iterations.md b/docs/src/concept_reference/max_mga_iterations.md
new file mode 100644
index 0000000000..2cd681c347
--- /dev/null
+++ b/docs/src/concept_reference/max_mga_iterations.md
@@ -0,0 +1 @@
+In the MGA algorithm the original problem is reoptimized (see also [mga-advanced](@ref)), and finds near-optimal solutions. The parameter [max\_mga\_iterations](@ref) defines how many MGA iterations will be performed, i.e. how many near-optimal solutions will be generated.
diff --git a/docs/src/concept_reference/max_mga_slack.md b/docs/src/concept_reference/max_mga_slack.md
new file mode 100644
index 0000000000..424ed2e474
--- /dev/null
+++ b/docs/src/concept_reference/max_mga_slack.md
@@ -0,0 +1 @@
+In the MGA algorithm the original problem is reoptimized (see also [mga-advanced](@ref)), and finds near-optimal solutions. The parameter [max\_mga\_slack](@ref) defines how far from the optimum the new solutions can maximally be (e.g. a value of 0.05 would alow for a 5% increase of the orginal objective value).
diff --git a/docs/src/concept_reference/mga_diff_relative.md b/docs/src/concept_reference/mga_diff_relative.md
new file mode 100644
index 0000000000..714519a65b
--- /dev/null
+++ b/docs/src/concept_reference/mga_diff_relative.md
@@ -0,0 +1 @@
+Currently, the MGA algorithm (see [mga-advanced](@ref)) only supports absolute differences between MGA variables (e.g. absolute differences between [units\_invested\_available](@ref) etc.). Hence, the default for this parameter is `false` and should not be changed for now.
diff --git a/docs/src/concept_reference/model_type.md b/docs/src/concept_reference/model_type.md
index 135a501428..f9b0ba0f09 100644
--- a/docs/src/concept_reference/model_type.md
+++ b/docs/src/concept_reference/model_type.md
@@ -1,3 +1,3 @@
-This parameter is used, generally, to control [model](@ref) dependent functionality and specify [model](@ref)-level parameters for different [model](@ref)s. Currently, the main use is to identify the [model](@ref) objects that represent the master and operational sub problems within a decomposed investment problem structure. To trigger the decomposed structure, a [model](@ref) object with `model_type`=`:spineopt_master` must exist and another with `model_type`=`:spineopt_operations` must also be present. To deactivate the decomposition functionality, the `model_type` of the master problem can be set to `:spineopt_other`.
+This parameter is used, generally, to control [model](@ref) dependent functionality and specify [model](@ref)-level parameters for different [model](@ref)s. Currently, the main use is to identify the [model](@ref) objects that represent the master and operational sub problems within a decomposed investment problem structure. To trigger the decomposed structure, a [model](@ref) object with `model_type`=`:spineopt_benders_master` must exist and another with `model_type`=`:spineopt_standard` must also be present. To deactivate the decomposition functionality, the `model_type` of the master problem can be set to `:spineopt_other`.
-See also [Decomposition](@ref).
\ No newline at end of file
+See also [Decomposition](@ref).
diff --git a/docs/src/concept_reference/storages_invested_big_m_mga.md b/docs/src/concept_reference/storages_invested_big_m_mga.md
new file mode 100644
index 0000000000..efac0f850f
--- /dev/null
+++ b/docs/src/concept_reference/storages_invested_big_m_mga.md
@@ -0,0 +1,3 @@
+The [storages\_invested\_big\_m\_mga](@ref) parameter is used in combination with the MGA algorithm (see [mga-advanced](@ref)).
+It defines an upper bound on the maximum difference between any MGA iteration. The big M should be chosen always sufficiently large. (Typically, a value equivalent to
+[candidate\_storages](@ref) could suffice.)
diff --git a/docs/src/concept_reference/storages_invested_mga.md b/docs/src/concept_reference/storages_invested_mga.md
new file mode 100644
index 0000000000..76de89a5b0
--- /dev/null
+++ b/docs/src/concept_reference/storages_invested_mga.md
@@ -0,0 +1,2 @@
+The [storages\_invested\_mga](@ref) is a boolean parameter that can be used in combination with the MGA algorithm (see [mga-advanced](@ref)). As soon as
+the value of [storages\_invested\_mga](@ref) is set to `true`, investment decisions in this connection, or group of storages, will be included in the MGA algorithm.
diff --git a/docs/src/concept_reference/units_invested_big_m_mga.md b/docs/src/concept_reference/units_invested_big_m_mga.md
new file mode 100644
index 0000000000..a57372c591
--- /dev/null
+++ b/docs/src/concept_reference/units_invested_big_m_mga.md
@@ -0,0 +1,3 @@
+The [units\_invested\_big\_m\_mga](@ref) parameter is used in combination with the MGA algorithm (see [mga-advanced](@ref)).
+It defines an upper bound on the maximum difference between any MGA iteration. The big M should be chosen always sufficiently large. (Typically, a value equivalent to
+[candidate\_units](@ref) could suffice.)
diff --git a/docs/src/concept_reference/units_invested_mga.md b/docs/src/concept_reference/units_invested_mga.md
new file mode 100644
index 0000000000..7bd53460bb
--- /dev/null
+++ b/docs/src/concept_reference/units_invested_mga.md
@@ -0,0 +1,2 @@
+The [units\_invested\_mga](@ref) is a boolean parameter that can be used in combination with the MGA algorithm (see [mga-advanced](@ref)). As soon as
+the value of [units\_invested\_mga](@ref) is set to `true`, investment decisions in this connection, or group of units, will be included in the MGA algorithm.
diff --git a/docs/src/concept_reference/unit_constraint.md b/docs/src/concept_reference/user_constraint.md
similarity index 100%
rename from docs/src/concept_reference/unit_constraint.md
rename to docs/src/concept_reference/user_constraint.md
diff --git a/src/SpineOpt.jl b/src/SpineOpt.jl
index 71c243793e..88d4955432 100644
--- a/src/SpineOpt.jl
+++ b/src/SpineOpt.jl
@@ -26,6 +26,7 @@ using SpineInterface
using JSON
using Printf
using Requires
+using JuMP
import Dates: CompoundPeriod
import LinearAlgebra: BLAS.gemm, LAPACK.getri!, LAPACK.getrf!
@@ -40,151 +41,138 @@ include("run_spineopt.jl")
include("util/docs_utils.jl")
include("data_structure/migration.jl")
-_lazy_include_file_paths = [
- "run_spineopt_sp.jl",
- "run_spineopt_mp.jl",
- "util/misc.jl",
- "util/postprocess_results.jl",
- "util/write_information_files.jl",
- "data_structure/benders_data.jl",
- "data_structure/temporal_structure.jl",
- "data_structure/stochastic_structure.jl",
- "data_structure/preprocess_data_structure.jl",
- "data_structure/check_data_structure.jl",
- "variables/variable_common.jl",
- "variables/variable_unit_flow.jl",
- "variables/variable_unit_flow_op.jl",
- "variables/variable_connection_flow.jl",
- "variables/variable_connection_intact_flow.jl",
- "variables/variable_connections_invested.jl",
- "variables/variable_connections_invested_available.jl",
- "variables/variable_connections_decommissioned.jl",
- "variables/variable_storages_invested.jl",
- "variables/variable_storages_invested_available.jl",
- "variables/variable_storages_decommissioned.jl",
- "variables/variable_node_state.jl",
- "variables/variable_units_on.jl",
- "variables/variable_units_invested.jl",
- "variables/variable_units_invested_available.jl",
- "variables/variable_units_mothballed.jl",
- "variables/variable_units_available.jl",
- "variables/variable_units_started_up.jl",
- "variables/variable_units_shut_down.jl",
- "variables/variable_node_slack_pos.jl",
- "variables/variable_node_slack_neg.jl",
- "variables/variable_node_injection.jl",
- "variables/variable_nonspin_units_started_up.jl",
- "variables/variable_start_up_unit_flow.jl",
- "variables/variable_ramp_up_unit_flow.jl",
- "variables/variable_nonspin_ramp_up_unit_flow.jl",
- "variables/variable_shut_down_unit_flow.jl",
- "variables/variable_ramp_down_unit_flow.jl",
- "variables/variable_nonspin_ramp_down_unit_flow.jl",
- "variables/variable_nonspin_units_shut_down.jl",
- "variables/variable_node_pressure.jl",
- "variables/variable_node_voltage_angle.jl",
- "variables/variable_binary_gas_connection_flow.jl",
- "variables/variable_mp_objective_lowerbound.jl",
- "objective/set_objective.jl",
- "objective/set_mp_objective.jl",
- "objective/variable_om_costs.jl",
- "objective/fixed_om_costs.jl",
- "objective/taxes.jl",
- "objective/start_up_costs.jl",
- "objective/shut_down_costs.jl",
- "objective/fuel_costs.jl",
- "objective/unit_investment_costs.jl",
- "objective/connection_investment_costs.jl",
- "objective/storage_investment_costs.jl",
- "objective/objective_penalties.jl",
- "objective/total_costs.jl",
- "objective/renewable_curtailment_costs.jl",
- "objective/connection_flow_costs.jl",
- "objective/res_proc_costs.jl",
- "objective/ramp_costs.jl",
- "objective/units_on_costs.jl",
- "constraints/constraint_common.jl",
- "constraints/constraint_total_cumulated_unit_flow_bounds.jl",
- "constraints/constraint_unit_flow_capacity.jl",
- "constraints/constraint_unit_flow_capacity_w_ramps.jl",
- "constraints/constraint_operating_point_bounds.jl",
- "constraints/constraint_operating_point_sum.jl",
- "constraints/constraint_nodal_balance.jl",
- "constraints/constraint_node_injection.jl",
- "constraints/constraint_node_state_capacity.jl",
- "constraints/constraint_cyclic_node_state.jl",
- "constraints/constraint_ratio_unit_flow.jl",
- "constraints/constraint_ratio_out_in_connection_flow.jl",
- "constraints/constraint_ratio_out_in_connection_intact_flow.jl",
- "constraints/constraint_connection_flow_capacity.jl",
- "constraints/constraint_connection_intact_flow_capacity.jl",
- "constraints/constraint_connection_flow_intact_flow.jl",
- "constraints/constraint_connection_intact_flow_ptdf.jl",
- "constraints/constraint_candidate_connection_flow_ub.jl",
- "constraints/constraint_candidate_connection_flow_lb.jl",
- "constraints/constraint_connection_flow_lodf.jl",
- "constraints/constraint_connections_invested_available.jl",
- "constraints/constraint_connections_invested_transition.jl",
- "constraints/constraint_connection_lifetime.jl",
- "constraints/constraint_storages_invested_available.jl",
- "constraints/constraint_storages_invested_transition.jl",
- "constraints/constraint_storage_lifetime.jl",
- "constraints/constraint_units_on.jl",
- "constraints/constraint_units_available.jl",
- "constraints/constraint_minimum_operating_point.jl",
- "constraints/constraint_min_up_time.jl",
- "constraints/constraint_min_down_time.jl",
- "constraints/constraint_unit_state_transition.jl",
- "constraints/constraint_user_constraint.jl",
- "constraints/constraint_units_invested_available.jl",
- "constraints/constraint_units_invested_transition.jl",
- "constraints/constraint_unit_lifetime.jl",
- "constraints/constraint_split_ramps.jl",
- "constraints/constraint_unit_pw_heat_rate.jl",
- "constraints/constraint_ramp_up.jl",
- "constraints/constraint_max_start_up_ramp.jl",
- "constraints/constraint_min_start_up_ramp.jl",
- "constraints/constraint_max_nonspin_ramp_up.jl",
- "constraints/constraint_min_nonspin_ramp_up.jl",
- "constraints/constraint_ramp_down.jl",
- "constraints/constraint_max_shut_down_ramp.jl",
- "constraints/constraint_min_shut_down_ramp.jl",
- "constraints/constraint_max_nonspin_ramp_down.jl",
- "constraints/constraint_min_nonspin_ramp_down.jl",
- "constraints/constraint_res_minimum_node_state.jl",
- "constraints/constraint_fix_node_pressure_point.jl",
- "constraints/constraint_compression_ratio.jl",
- "constraints/constraint_storage_line_pack.jl",
- "constraints/constraint_connection_flow_gas_capacity.jl",
- "constraints/constraint_max_node_pressure.jl",
- "constraints/constraint_min_node_pressure.jl",
- "constraints/constraint_max_node_voltage_angle.jl",
- "constraints/constraint_min_node_voltage_angle.jl",
- "constraints/constraint_node_voltage_angle.jl",
- "constraints/constraint_connection_unitary_gas_flow.jl",
- "constraints/constraint_mp_any_invested_cuts.jl",
-]
+include("run_spineopt_standard.jl")
+include("run_spineopt_benders.jl")
+include("run_spineopt_mga.jl")
+include("util/misc.jl")
+include("util/postprocess_results.jl")
+include("util/write_information_files.jl")
+include("data_structure/benders_data.jl")
+include("data_structure/mga_data.jl")
+include("data_structure/temporal_structure.jl")
+include("data_structure/stochastic_structure.jl")
+include("data_structure/preprocess_data_structure.jl")
+include("data_structure/check_data_structure.jl")
+include("variables/variable_common.jl")
+include("variables/variable_unit_flow.jl")
+include("variables/variable_unit_flow_op.jl")
+include("variables/variable_connection_flow.jl")
+include("variables/variable_connection_intact_flow.jl")
+include("variables/variable_connections_invested.jl")
+include("variables/variable_connections_invested_available.jl")
+include("variables/variable_connections_decommissioned.jl")
+include("variables/variable_storages_invested.jl")
+include("variables/variable_storages_invested_available.jl")
+include("variables/variable_storages_decommissioned.jl")
+include("variables/variable_node_state.jl")
+include("variables/variable_units_on.jl")
+include("variables/variable_units_invested.jl")
+include("variables/variable_units_invested_available.jl")
+include("variables/variable_units_mothballed.jl")
+include("variables/variable_units_available.jl")
+include("variables/variable_units_started_up.jl")
+include("variables/variable_units_shut_down.jl")
+include("variables/variable_node_slack_pos.jl")
+include("variables/variable_node_slack_neg.jl")
+include("variables/variable_node_injection.jl")
+include("variables/variable_nonspin_units_started_up.jl")
+include("variables/variable_start_up_unit_flow.jl")
+include("variables/variable_ramp_up_unit_flow.jl")
+include("variables/variable_nonspin_ramp_up_unit_flow.jl")
+include("variables/variable_shut_down_unit_flow.jl")
+include("variables/variable_ramp_down_unit_flow.jl")
+include("variables/variable_nonspin_ramp_down_unit_flow.jl")
+include("variables/variable_nonspin_units_shut_down.jl")
+include("variables/variable_node_pressure.jl")
+include("variables/variable_node_voltage_angle.jl")
+include("variables/variable_binary_gas_connection_flow.jl")
+include("variables/variable_mp_objective_lowerbound.jl")
+include("objective/set_objective.jl")
+include("objective/set_mp_objective.jl")
+include("objective/variable_om_costs.jl")
+include("objective/fixed_om_costs.jl")
+include("objective/taxes.jl")
+include("objective/start_up_costs.jl")
+include("objective/shut_down_costs.jl")
+include("objective/fuel_costs.jl")
+include("objective/unit_investment_costs.jl")
+include("objective/connection_investment_costs.jl")
+include("objective/storage_investment_costs.jl")
+include("objective/objective_penalties.jl")
+include("objective/total_costs.jl")
+include("objective/renewable_curtailment_costs.jl")
+include("objective/connection_flow_costs.jl")
+include("objective/res_proc_costs.jl")
+include("objective/ramp_costs.jl")
+include("objective/units_on_costs.jl")
+include("constraints/constraint_common.jl")
+include("constraints/constraint_total_cumulated_unit_flow_bounds.jl")
+include("constraints/constraint_unit_flow_capacity.jl")
+include("constraints/constraint_unit_flow_capacity_w_ramps.jl")
+include("constraints/constraint_operating_point_bounds.jl")
+include("constraints/constraint_operating_point_sum.jl")
+include("constraints/constraint_nodal_balance.jl")
+include("constraints/constraint_node_injection.jl")
+include("constraints/constraint_node_state_capacity.jl")
+include("constraints/constraint_cyclic_node_state.jl")
+include("constraints/constraint_ratio_unit_flow.jl")
+include("constraints/constraint_ratio_out_in_connection_flow.jl")
+include("constraints/constraint_ratio_out_in_connection_intact_flow.jl")
+include("constraints/constraint_connection_flow_capacity.jl")
+include("constraints/constraint_connection_intact_flow_capacity.jl")
+include("constraints/constraint_connection_flow_intact_flow.jl")
+include("constraints/constraint_connection_intact_flow_ptdf.jl")
+include("constraints/constraint_candidate_connection_flow_ub.jl")
+include("constraints/constraint_candidate_connection_flow_lb.jl")
+include("constraints/constraint_connection_flow_lodf.jl")
+include("constraints/constraint_connections_invested_available.jl")
+include("constraints/constraint_connections_invested_transition.jl")
+include("constraints/constraint_connection_lifetime.jl")
+include("constraints/constraint_storages_invested_available.jl")
+include("constraints/constraint_storages_invested_transition.jl")
+include("constraints/constraint_storage_lifetime.jl")
+include("constraints/constraint_units_on.jl")
+include("constraints/constraint_units_available.jl")
+include("constraints/constraint_minimum_operating_point.jl")
+include("constraints/constraint_min_up_time.jl")
+include("constraints/constraint_min_down_time.jl")
+include("constraints/constraint_unit_state_transition.jl")
+include("constraints/constraint_user_constraint.jl")
+include("constraints/constraint_units_invested_available.jl")
+include("constraints/constraint_units_invested_transition.jl")
+include("constraints/constraint_unit_lifetime.jl")
+include("constraints/constraint_split_ramps.jl")
+include("constraints/constraint_unit_pw_heat_rate.jl")
+include("constraints/constraint_ramp_up.jl")
+include("constraints/constraint_max_start_up_ramp.jl")
+include("constraints/constraint_min_start_up_ramp.jl")
+include("constraints/constraint_max_nonspin_ramp_up.jl")
+include("constraints/constraint_min_nonspin_ramp_up.jl")
+include("constraints/constraint_ramp_down.jl")
+include("constraints/constraint_max_shut_down_ramp.jl")
+include("constraints/constraint_min_shut_down_ramp.jl")
+include("constraints/constraint_max_nonspin_ramp_down.jl")
+include("constraints/constraint_min_nonspin_ramp_down.jl")
+include("constraints/constraint_res_minimum_node_state.jl")
+include("constraints/constraint_fix_node_pressure_point.jl")
+include("constraints/constraint_compression_ratio.jl")
+include("constraints/constraint_storage_line_pack.jl")
+include("constraints/constraint_connection_flow_gas_capacity.jl")
+include("constraints/constraint_max_node_pressure.jl")
+include("constraints/constraint_min_node_pressure.jl")
+include("constraints/constraint_max_node_voltage_angle.jl")
+include("constraints/constraint_min_node_voltage_angle.jl")
+include("constraints/constraint_node_voltage_angle.jl")
+include("constraints/constraint_connection_unitary_gas_flow.jl")
+include("constraints/constraint_mp_any_invested_cuts.jl")
-function __init__()
- @require JuMP="4076af6c-e467-56ae-b986-b466b2749572" begin
- export unit_flow_indices
- export unit_flow_op_indices
- export connection_flow_indices
- export node_state_indices
- export units_on_indices
- export units_invested_available_indices
- using .JuMP
- for file_path in _lazy_include_file_paths
- include(file_path)
- end
- @require Revise="295af30f-e4ad-537b-8983-00126c2a3abe" begin
- import .Revise
- for file_path in _lazy_include_file_paths
- Revise.track(@__MODULE__, joinpath(@__DIR__, file_path))
- end
- end
- end
-end
+
+export unit_flow_indices
+export unit_flow_op_indices
+export connection_flow_indices
+export node_state_indices
+export units_on_indices
+export units_invested_available_indices
const _template = JSON.parsefile(joinpath(@__DIR__, "..", "templates", "spineopt_template.json"))
diff --git a/src/data_structure/check_data_structure.jl b/src/data_structure/check_data_structure.jl
index 8c5c7f0e02..737d26a974 100644
--- a/src/data_structure/check_data_structure.jl
+++ b/src/data_structure/check_data_structure.jl
@@ -83,7 +83,7 @@ end
Check if at least one `node` is defined.
"""
function check_node_object()
- for m in model(model_type=:spineopt_operations)
+ for m in model(model_type=:spineopt_standard)
_check(
!isempty(node()),
"`node` object not found - you need at least one `node` to run a SpineOpt Operations Model",
@@ -99,7 +99,7 @@ Check that each `node` has at least one `temporal_block` connected to it in each
function check_model__node__temporal_block()
errors = [
(m, n)
- for m in model(model_type=:spineopt_operations) for n in node()
+ for m in model(model_type=:spineopt_standard) for n in node()
if isempty(intersect(node__temporal_block(node=n), model__temporal_block(model=m))) && n == members(n)
]
_check(
@@ -109,7 +109,7 @@ function check_model__node__temporal_block()
)
error_group = [
(m, n)
- for m in model(model_type=:spineopt_operations) for n in node()
+ for m in model(model_type=:spineopt_standard) for n in node()
if any(isempty, intersect(node__temporal_block(node=members(n)), model__temporal_block(model=m)))
]
_check(
@@ -120,7 +120,7 @@ function check_model__node__temporal_block()
)
warnings = [
(m, n)
- for m in model(model_type=:spineopt_operations) for n in node()
+ for m in model(model_type=:spineopt_standard) for n in node()
if isempty(intersect(node__temporal_block(node=n), model__temporal_block(model=m))) && n != members(n)
]
_check_warn(
@@ -141,18 +141,19 @@ This is deduced from the `model__stochastic_structure` and `node__stochastic_str
function check_model__node__stochastic_structure()
errors = [
(m, n)
- for m in model(model_type=:spineopt_operations) for n in node()
+ for m in model(model_type=:spineopt_standard) for n in node()
if length(intersect(node__stochastic_structure(node=n), model__stochastic_structure(model=m))) != 1 &&
n == members(n)
]
errors_group = [
(m, n)
- for m in model(model_type=:spineopt_operations) for n in node()
- if length(intersect(node__stochastic_structure(node=members(n)), model__stochastic_structure(model=m))) != 1
+ for m in model(model_type=:spineopt_standard) for n in node()
+ for n_mem in members(n)
+ if length(intersect(node__stochastic_structure(node=n_mem), model__stochastic_structure(model=m))) != 1
]
warnings = [
(m, n)
- for m in model(model_type=:spineopt_operations) for n in node()
+ for m in model(model_type=:spineopt_standard) for n in node()
if length(intersect(node__stochastic_structure(node=n), model__stochastic_structure(model=m))) != 1 &&
n != members(n)
]
@@ -186,8 +187,9 @@ This is deduced from the `model__stochastic_strucutre` and `units_on__stochastic
function check_model__unit__stochastic_structure()
errors = [
(m, u)
- for m in model(model_type=:spineopt_operations) for u in unit()
- if length(intersect(units_on__stochastic_structure(unit=u), model__stochastic_structure(model=m))) != 1
+ for m in model(model_type=:spineopt_standard) for u in unit()
+ for u_mem in members(u)
+ if length(intersect(units_on__stochastic_structure(unit=u_mem), model__stochastic_structure(model=m))) != 1
]
_check(
isempty(errors),
diff --git a/src/data_structure/mga_data.jl b/src/data_structure/mga_data.jl
new file mode 100644
index 0000000000..afb82587fa
--- /dev/null
+++ b/src/data_structure/mga_data.jl
@@ -0,0 +1,220 @@
+#############################################################################
+# Copyright (C) 2017 - 2021 Spine Project
+#
+# This file is part of SpineOpt.
+#
+# SpineOpt is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# SpineOpt is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program. If not, see .
+#############################################################################
+
+function units_invested_mga_indices()
+ unique(
+ [
+ (unit=ug,)
+ for ug in unit(units_invested_mga=true)])
+end
+
+function units_invested_mga_indices(mga_iteration)
+ unique(
+ [
+ (unit=ug, mga_iteration=mga_it)
+ for ug in unit(units_invested_mga=true)
+ for mga_it in mga_iteration])
+end
+
+function connections_invested_mga_indices()
+ unique(
+ [
+ (connection=cg,)
+ for cg in connection(connections_invested_mga=true)])
+end
+
+function connections_invested_mga_indices(mga_iteration)
+ unique(
+ [
+ (connection=cg, mga_iteration=mga_it)
+ for cg in connection(connections_invested_mga=true)
+ for mga_it in mga_iteration])
+end
+
+function storages_invested_mga_indices()
+ unique(
+ [
+ (node=ng, )
+ for ng in node(storages_invested_mga=true)])
+end
+
+function storages_invested_mga_indices(mga_iteration)
+ unique(
+ [
+ (node=ng, mga_iteration=mga_it)
+ for ng in node(storages_invested_mga=true)
+ for mga_it in mga_iteration])
+end
+
+function set_objective_mga_iteration!(m;iteration=nothing)
+ instance = m.ext[:instance]
+ if !mga_diff_relative(model=instance) #FIXME: define also for relative diffs in the future
+ _set_objective_mga_iteration!(
+ m,
+ :units_invested,
+ units_invested_available_indices,
+ unit_stochastic_scenario_weight,
+ units_invested_mga_indices,
+ units_invested_big_m_mga,
+ iteration
+ )
+ _set_objective_mga_iteration!(
+ m,
+ :connections_invested,
+ connections_invested_available_indices,
+ connection_stochastic_scenario_weight,
+ connections_invested_mga_indices,
+ connections_invested_big_m_mga,
+ iteration
+ )
+ _set_objective_mga_iteration!(
+ m,
+ :storages_invested,
+ storages_invested_available_indices,
+ node_stochastic_scenario_weight,
+ storages_invested_mga_indices,
+ storages_invested_big_m_mga,
+ iteration
+ )
+ @fetch mga_aux_diff, mga_objective = m.ext[:variables]
+ ub_objective = get!(m.ext[:constraints],:mga_objective_ub,Dict())
+ ub_objective[iteration] = @constraint(
+ m,
+ mga_objective[(model = m.ext[:instance],t=current_window(m))]
+ <= sum(
+ mga_aux_diff[ind...]
+ for ind in vcat(
+ [storages_invested_mga_indices(iteration),
+ connections_invested_mga_indices(iteration),
+ units_invested_mga_indices(iteration)]
+ )
+ )
+ )
+ for (con_key, cons) in m.ext[:constraints]
+ for (inds, con) in cons
+ set_name(con, string(con_key, inds))
+ end
+ end
+ end
+end
+
+function _set_objective_mga_iteration!(
+ m::Model,
+ variable_name::Symbol,
+ variable_indices_function::Function,
+ scenario_weight_function::Function,
+ mga_indices::Function,
+ mga_variable_bigM::Parameter,
+ mga_current_iteration::Object,
+ )
+ if !isempty(mga_indices())
+ t0 = _analysis_time(m).ref.x
+ @fetch units_invested = m.ext[:variables]
+ mga_results = m.ext[:outputs]
+ d_aux = get!(m.ext[:variables], :mga_aux_diff, Dict())
+ d_bin = get!(m.ext[:variables],:mga_aux_binary, Dict())
+ for ind in mga_indices(mga_current_iteration)
+ d_aux[ind] = @variable(m, base_name = _base_name(:mga_aux_diff,ind), lower_bound = 0)
+ d_bin[ind] = @variable(m, base_name = _base_name(:mga_aux_binary,ind), binary=true)
+ end
+ @fetch mga_aux_diff, mga_aux_binary, mga_objective = m.ext[:variables]
+ mga_results = m.ext[:outputs]
+ variable = m.ext[:variables][variable_name]
+ #FIXME: don't create new dict everytime, but get existing one
+ d_diff_ub1 = get!(m.ext[:constraints],:mga_diff_ub1,Dict())
+ d_diff_ub2 = get!(m.ext[:constraints],:mga_diff_ub2,Dict())
+ d_diff_lb1 = get!(m.ext[:constraints],:mga_diff_lb1,Dict())
+ d_diff_lb2 = get!(m.ext[:constraints],:mga_diff_lb2,Dict())
+ for ind in mga_indices()
+ d_diff_ub1[(ind...,mga_current_iteration...)] = @constraint(
+ m,
+ mga_aux_diff[((ind...,mga_iteration=mga_current_iteration))]
+ <=
+ sum(
+ + (
+ variable[_ind]
+ - mga_results[variable_name][(_drop_key(_ind,:t)..., mga_iteration=mga_current_iteration)][t0][_ind.t.start.x]
+ )
+ * scenario_weight_function(m; _drop_key(_ind,:t)...) #fix me, can also be only node or so
+ for _ind in variable_indices_function(m; ind...)
+ )
+ + mga_variable_bigM(;ind...)*mga_aux_binary[(ind...,mga_iteration=mga_current_iteration)])
+ d_diff_ub2[(ind...,mga_current_iteration...)]= @constraint(
+ m,
+ mga_aux_diff[((ind...,mga_iteration=mga_current_iteration))]
+ <=
+ sum(
+ - (variable[_ind]
+ - mga_results[variable_name][(_drop_key(_ind,:t)..., mga_iteration=mga_current_iteration)][t0][_ind.t.start.x])
+ * scenario_weight_function(m; _drop_key(_ind,:t)...)
+ for _ind in variable_indices_function(m; ind...)
+ )
+ + mga_variable_bigM(;ind...)*(1-mga_aux_binary[(ind...,mga_iteration=mga_current_iteration)])
+ )
+ d_diff_lb1[(ind...,mga_current_iteration...)] = @constraint(
+ m,
+ mga_aux_diff[((ind...,mga_iteration=mga_current_iteration))]
+ >=
+ sum(
+ (variable[_ind]
+ - mga_results[variable_name][(_drop_key(_ind,:t)..., mga_iteration=mga_current_iteration)][t0][_ind.t.start.x])
+ * scenario_weight_function(m; _drop_key(_ind,:t)...)
+ for _ind in variable_indices_function(m; ind...)
+ )
+ )
+ d_diff_lb2[(ind...,mga_current_iteration...)] = @constraint(
+ m,
+ mga_aux_diff[((ind...,mga_iteration=mga_current_iteration))]
+ >=
+ sum(
+ - (variable[_ind]
+ - mga_results[variable_name][(_drop_key(_ind,:t)..., mga_iteration=mga_current_iteration)][t0][_ind.t.start.x])
+ * scenario_weight_function(m; _drop_key(_ind,:t)...)
+ for _ind in variable_indices_function(m; ind...)
+ )
+ )
+ end
+ end
+end
+
+function add_mga_objective_constraint!(m::Model)
+ instance = m.ext[:instance]
+ m.ext[:constraints][:mga_slack_constraint] = Dict(m.ext[:instance] =>
+ @constraint(m, total_costs(m, end_(last(time_slice(m)))) <= (1+max_mga_slack(model=instance)) * objective_value_mga(model=instance))
+ )
+end
+
+function save_mga_objective_values!(m::Model)
+ ind = (model=m.ext[:instance], t=current_window(m))
+ for name in [:mga_objective,]#:mga_aux_diff]
+ for ind in keys(m.ext[:variables][name])
+ m.ext[:values][name] = Dict(ind => value(m.ext[:variables][name][ind]))
+ end
+ end
+end
+
+function set_mga_objective!(m)
+ m.ext[:variables][:mga_objective] = Dict(
+ (model = m.ext[:instance],t=current_window(m)) => @variable(m, base_name = _base_name(:mga_objective,(model = m.ext[:instance],t=current_window(m))), lower_bound=0)
+ )
+ @objective(m,
+ Max,
+ m.ext[:variables][:mga_objective][(model = m.ext[:instance],t=current_window(m))]
+ )
+end
diff --git a/src/data_structure/migration.jl b/src/data_structure/migration.jl
index f8bc995f1c..82ea173bd1 100644
--- a/src/data_structure/migration.jl
+++ b/src/data_structure/migration.jl
@@ -29,8 +29,9 @@
include("versions/rename_unit_constraint_to_user_constraint.jl")
include("versions/move_connection_flow_cost.jl")
+include("versions/rename_model_types.jl")
-_upgrade_functions = [rename_unit_constraint_to_user_constraint, move_connection_flow_cost]
+_upgrade_functions = [rename_unit_constraint_to_user_constraint, move_connection_flow_cost,rename_model_types]
"""
current_version()
@@ -61,7 +62,7 @@ function _run_migration(url, version, log_level)
upgrade_fn(url, log_level) || return false
run_request(
url,
- "import_data",
+ "import_data",
(
Dict("object_parameters" => [("settings", "version", version + 1)]),
"Update SpineOpt data structure to $(version + 1)"
diff --git a/src/data_structure/preprocess_data_structure.jl b/src/data_structure/preprocess_data_structure.jl
index bdd86e529d..896e8bd286 100644
--- a/src/data_structure/preprocess_data_structure.jl
+++ b/src/data_structure/preprocess_data_structure.jl
@@ -388,7 +388,7 @@ function generate_lodf()
for conn_cont in connection(has_ptdf=true)
)
for (conn_mon, lodf_trial) in ((conn_mon, lodf_fn(conn_mon)) for conn_mon in connection(has_ptdf=true))
- if conn_cont !== conn_mon && !isapprox(lodf_trial, 0; atol=tolerance)
+ if conn_cont !== conn_mon && !isapprox(lodf_trial, 0; atol=tolerance)
)
lodf_rel_cls = RelationshipClass(
:lodf_connection__connection, [:connection, :connection], keys(lodf_values), lodf_values
@@ -413,8 +413,8 @@ function generate_network_components()
generate_ptdf()
generate_lodf()
# the below needs the parameters write_ptdf_file and write_lodf_file - we can uncomment when we update the template perhaps?
- # write_ptdf_file(model=first(model(model_type=:spineopt_operations))) == Symbol(:true) && write_ptdfs()
- # write_lodf_file(model=first(model(model_type=:spineopt_operations))) == Symbol(:true) && write_lodfs()
+ # write_ptdf_file(model=first(model(model_type=:spineopt_standard))) == Symbol(:true) && write_ptdfs()
+ # write_lodf_file(model=first(model(model_type=:spineopt_standard))) == Symbol(:true) && write_lodfs()
end
"""
diff --git a/src/data_structure/versions/rename_model_types.jl b/src/data_structure/versions/rename_model_types.jl
new file mode 100644
index 0000000000..77121f57ef
--- /dev/null
+++ b/src/data_structure/versions/rename_model_types.jl
@@ -0,0 +1,71 @@
+#############################################################################
+# Copyright (C) 2017 - 2021 Spine Project
+#
+# This file is part of SpineOpt.
+#
+# SpineOpt is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# SpineOpt is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program. If not, see .
+#############################################################################
+
+"""
+ rename_model_types(db_url)
+
+Renaming `spineopt_master` to `spineopt_benders_master`, and `spineopt_operations` to `spineopt_standard`
+"""
+
+function rename_model_types(db_url, log_level)
+ @log log_level 0 "Renaming `spineopt_master` to `spineopt_benders_master`, and `spineopt_operations` to `spineopt_standard`"
+ data = run_request(
+ db_url, "query", ("object_parameter_value_sq", "parameter_value_list_sq")
+ )
+
+ pvals = data["object_parameter_value_sq"]
+ plists = data["parameter_value_list_sq"]
+ model_type_vals = [x for x in pvals if x["parameter_name"] == "model_type"]
+ model_type_list = [x for x in plists if x["name"] == "model_type_list"]
+ # Prepare new_data
+ new_data = Dict()
+ new_data[:object_parameter_values] = new_pvals = []
+ new_data[:parameter_value_lists] = new_plists = []
+ new_data[:object_parameters] = [
+ x for x in template()["object_parameters"] if x[2] == "model_type"
+ ]
+ ###
+ for pval in model_type_vals
+ model_id = pval["object_id"]
+ if pval["value"] == "spineopt_master"
+ new_pval = ["model", pval["object_name"], "model_type", "spineopt_benders_master"]
+ push!(new_pvals, new_pval)
+ elseif pval["value"] == "spineopt_operations"
+ new_pval = ["model", pval["object_name"], "model_type", "spineopt_standard"]
+ push!(new_pvals, new_pval)
+ end
+ end
+
+ for plist in model_type_list
+ if plist["value"] == "spineopt_master"
+ new_plist = ["model_type_list", "spineopt_benders_master"]
+ push!(new_plists, new_plist)
+ elseif plist["value"] == "spineopt_operations"
+ new_plist = ["model_type_list", "spineopt_standard"]
+ push!(new_plists, new_plist)
+ else
+ new_plist = ["model_type_list", plist["value"]]
+ push!(new_plists, new_plist)
+ end
+ end
+
+ # Add new data
+ run_request(db_url, "import_data", (new_data, ""))
+ true
+end
diff --git a/src/objective/set_objective.jl b/src/objective/set_objective.jl
index 1aa6dea923..fd7867af6d 100644
--- a/src/objective/set_objective.jl
+++ b/src/objective/set_objective.jl
@@ -27,11 +27,15 @@ Unless defined otherwise this expression executed until the last time_slice
"""
# TODO: Rethink this concept; Should we really evaluate until the very last time_slice,
# if multiple temporal_block end at different points in time
-function set_objective!(m::Model)
- total_discounted_costs = total_costs(m, end_(last(time_slice(m))))
- if !iszero(total_discounted_costs)
- @objective(m, Min, total_discounted_costs)
+function set_objective!(m::Model;alternative_objective=m -> nothing)
+ if alternative_objective(m) == nothing
+ total_discounted_costs = total_costs(m, end_(last(time_slice(m))))
+ if !iszero(total_discounted_costs)
+ @objective(m, Min, total_discounted_costs)
+ else
+ @warn "zero objective"
+ end
else
- @warn "zero objective"
+ alternative_objective(m)
end
end
diff --git a/src/run_spineopt.jl b/src/run_spineopt.jl
index c801191b66..79016437ae 100644
--- a/src/run_spineopt.jl
+++ b/src/run_spineopt.jl
@@ -127,7 +127,7 @@ function run_spineopt(
$missing_items
"""
end
- end
+ end
rerun_spineopt(
url_out;
@@ -138,7 +138,7 @@ function run_spineopt(
update_constraints=update_constraints,
log_level=log_level,
optimize=optimize,
- use_direct_model=use_direct_model
+ use_direct_model=use_direct_model #FIXME: make sure that this works with solvers, possibly adapt union? + allow for conflicts if direct model is used
)
end
@@ -151,30 +151,36 @@ function rerun_spineopt(
update_constraints=m -> nothing,
log_level=3,
optimize=true,
- use_direct_model=false
+ use_direct_model=false,
+ alternative_objective=m -> nothing,
)
- @eval using JuMP
- m = Base.invokelatest(create_model, :spineopt_operations, mip_solver, lp_solver, use_direct_model)
- mp = Base.invokelatest(create_model, :spineopt_master, mip_solver, lp_solver, use_direct_model)
- Base.invokelatest(
- rerun_spineopt!,
+ m = create_model(:spineopt_standard, mip_solver, lp_solver, use_direct_model)
+ mp = create_model(:spineopt_benders_master, mip_solver, lp_solver, use_direct_model)
+ m_mga = create_model(:spineopt_mga, mip_solver, lp_solver, use_direct_model)
+
+ rerun_spineopt!(
m,
mp,
+ m_mga,
url_out;
add_user_variables=add_user_variables,
add_constraints=add_constraints,
update_constraints=update_constraints,
log_level=log_level,
- optimize=optimize
+ optimize=optimize,
+ alternative_objective=alternative_objective
)
end
-rerun_spineopt!(::Nothing, mp, url_out; kwargs...) = error("No model of type `spineopt_operations` defined")
+rerun_spineopt!(::Nothing, mp, ::Nothing, url_out; kwargs...) = error("Model of type `spineopt_benders_master` requires the existence of a subproblem model of type `spineopt_standard`")
+rerun_spineopt!(::Nothing, mp, m_mga; kwargs...) = error("Currently the combination of Benders and mga is supported. Please make sure that you don't have a `model_type=:spineopt_benders_master` together with another model of type `:spineopt_mga`")
+rerun_spineopt!(m, ::Nothing, m_mga; kwargs...) = error("Currently the combination of models with type `spineopt_standard` and `spineopt_mga` is supported.")
+rerun_spineopt!(::Nothing, ::Nothing, ::Nothing; kwargs...) = error("At least one model object has to exist, with at least one of type `spineopt_standard` (or `spineopt_mga`)")
"""
A JuMP `Model` for SpineOpt.
"""
-function create_model(model_type, mip_solver, lp_solver, use_direct_model=false)
+function create_model(model_type, mip_solver, lp_solver, use_direct_model=false)
isempty(model(model_type=model_type)) && return nothing
instance = first(model(model_type=model_type))
mip_solver = _mip_solver(instance, mip_solver)
@@ -301,7 +307,7 @@ function _output_value_by_entity(by_entity, overwrite_results_on_rolling, output
end
-function objective_terms(m)
+function objective_terms(m) #FIXME: this should just be benders definind the objective function themselves, not haking into run_spineopt
# if we have a decomposed structure, master problem costs (investments) should not be included
invest_terms = [:unit_investment_costs, :connection_investment_costs, :storage_investment_costs]
op_terms = [
@@ -318,13 +324,13 @@ function objective_terms(m)
:ramp_costs,
:units_on_costs,
]
- if model_type(model=m.ext[:instance]) == :spineopt_operations
+ if (model_type(model=m.ext[:instance]) ==:spineopt_standard || model_type(model=m.ext[:instance]) ==:spineopt_mga)
if m.ext[:is_subproblem]
op_terms
else
[op_terms; invest_terms]
end
- elseif model_type(model=m.ext[:instance]) == :spineopt_master
+ elseif model_type(model=m.ext[:instance]) == :spineopt_benders_master
invest_terms
end
end
diff --git a/src/run_spineopt_mp.jl b/src/run_spineopt_benders.jl
similarity index 98%
rename from src/run_spineopt_mp.jl
rename to src/run_spineopt_benders.jl
index 68a49b663b..3913921fcd 100644
--- a/src/run_spineopt_mp.jl
+++ b/src/run_spineopt_benders.jl
@@ -20,12 +20,14 @@
function rerun_spineopt!(
m::Model,
mp::Model,
+ ::Nothing,
url_out::Union{String,Nothing};
add_user_variables=m -> nothing,
add_constraints=m -> nothing,
update_constraints=m -> nothing,
log_level=3,
- optimize=true
+ optimize=true,
+ alternative_objective=m -> nothing,
)
m.ext[:is_sub_problem] = true
@timelog log_level 2 "Preprocessing data structure..." preprocess_data_structure(; log_level=log_level)
@@ -41,7 +43,7 @@ function rerun_spineopt!(
max_benders_iterations = max_iterations(model=mp.ext[:instance])
j = 1
while optimize
- @log log_level 0 "Starting Benders iteration $j"
+ @log log_level 0 "Starting Benders iteration $j"
optimize_model!(mp; log_level=log_level) || break
@timelog log_level 2 "Processing master problem solution" process_master_problem_solution(mp)
k = 1
diff --git a/src/run_spineopt_mga.jl b/src/run_spineopt_mga.jl
new file mode 100644
index 0000000000..cad88f73e3
--- /dev/null
+++ b/src/run_spineopt_mga.jl
@@ -0,0 +1,61 @@
+function rerun_spineopt!(
+ ::Nothing,
+ ::Nothing,
+ m_mga::Model,
+ url_out::Union{String,Nothing};
+ add_user_variables=m_mga -> nothing,
+ add_constraints=m_mga -> nothing,
+ update_constraints=m_mga -> nothing,
+ log_level=3,
+ optimize=true,
+ alternative_objective=m_mga -> nothing,
+ )
+ outputs = Dict()
+ mga_iterations = 0
+ max_mga_iteration = max_mga_iterations(model=m_mga.ext[:instance])
+ name_mga_it = :mga_iteration
+ mga_iteration = SpineOpt.ObjectClass(name_mga_it, [])
+ @eval begin
+ mga_iteration = $mga_iteration
+ end
+ @timelog log_level 2 "Preprocessing data structure..." preprocess_data_structure(; log_level=log_level)
+ @timelog log_level 2 "Checking data structure..." check_data_structure(; log_level=log_level)
+ @timelog log_level 2 "Creating temporal structure..." generate_temporal_structure!(m_mga)
+ @timelog log_level 2 "Creating stochastic structure..." generate_stochastic_structure!(m_mga)
+ init_model!(m_mga; add_user_variables=add_user_variables, add_constraints=add_constraints, log_level=log_level,alternative_objective=alternative_objective)
+ init_outputs!(m_mga)
+ k = 1
+ while optimize
+ @log log_level 1 "Window $k: $(current_window(m_mga))"
+ optimize_model!(
+ m_mga;
+ log_level=log_level,
+ iterations=mga_iterations
+ ) || break
+ @timelog log_level 2 "Fixing non-anticipativity values..." fix_non_anticipativity_values!(m_mga)
+ if @timelog log_level 2 "Rolling temporal structure...\n" !roll_temporal_structure!(m_mga)
+ @timelog log_level 2 " ... Rolling complete\n" break
+ end
+ update_model!(m_mga; update_constraints=update_constraints, log_level=log_level)
+ k += 1
+ end
+ m_mga
+ name_mga_obj = :objective_value_mga
+ model.parameter_values[m_mga.ext[:instance]][name_mga_obj] = parameter_value(objective_value(m_mga))
+ @eval begin
+ $(name_mga_obj) = $(Parameter(name_mga_obj, [model]))
+ end
+ mga_iterations += 1
+ add_mga_objective_constraint!(m_mga)
+ set_mga_objective!(m_mga)
+ while mga_iterations <= max_mga_iteration
+ set_objective_mga_iteration!(m_mga;iteration=mga_iteration()[end])
+ optimize_model!(m_mga;
+ log_level=log_level,
+ iterations=mga_iterations) || break
+ save_mga_objective_values!(m_mga)
+ mga_iterations += 1
+ end
+ write_report(m_mga, url_out)
+ m_mga
+end
diff --git a/src/run_spineopt_sp.jl b/src/run_spineopt_standard.jl
similarity index 95%
rename from src/run_spineopt_sp.jl
rename to src/run_spineopt_standard.jl
index 7a88d9f833..868f38e282 100644
--- a/src/run_spineopt_sp.jl
+++ b/src/run_spineopt_standard.jl
@@ -20,9 +20,11 @@
function rerun_spineopt!(
m::Model,
::Nothing,
+ ::Nothing,
url_out::Union{String,Nothing};
add_user_variables=m -> nothing,
add_constraints=m -> nothing,
+ alternative_objective=m -> nothing,
update_constraints=m -> nothing,
log_level=3,
optimize=true
@@ -31,7 +33,7 @@ function rerun_spineopt!(
@timelog log_level 2 "Checking data structure..." check_data_structure(; log_level=log_level)
@timelog log_level 2 "Creating temporal structure..." generate_temporal_structure!(m)
@timelog log_level 2 "Creating stochastic structure..." generate_stochastic_structure!(m)
- init_model!(m; add_user_variables=add_user_variables, add_constraints=add_constraints, log_level=log_level)
+ init_model!(m; add_user_variables=add_user_variables, add_constraints=add_constraints, log_level=log_level,alternative_objective=alternative_objective)
init_outputs!(m)
k = 1
calculate_duals = any(startswith(lowercase(name), r"bound_|constraint_") for name in String.(keys(m.ext[:outputs])))
@@ -256,7 +258,7 @@ end
"""
Initialize the given model for SpineOpt: add variables, fix the necessary variables, add constraints and set objective.
"""
-function init_model!(m; add_user_variables=m -> nothing, add_constraints=m -> nothing, log_level=3)
+function init_model!(m; add_user_variables=m -> nothing, add_constraints=m -> nothing, log_level=3,alternative_objective=m -> nothing)
@timelog log_level 2 "Adding variables...\n" add_variables!(
m; add_user_variables=add_user_variables, log_level=log_level
)
@@ -264,14 +266,14 @@ function init_model!(m; add_user_variables=m -> nothing, add_constraints=m -> no
@timelog log_level 2 "Adding constraints...\n" add_constraints!(
m; add_constraints=add_constraints, log_level=log_level
)
- @timelog log_level 2 "Setting objective..." set_objective!(m)
+ @timelog log_level 2 "Setting objective..." set_objective!(m;alternative_objective=alternative_objective)
end
"""
Optimize the given model.
If an optimal solution is found, save results and return `true`, otherwise return `false`.
"""
-function optimize_model!(m::Model; log_level=3, calculate_duals=false)
+function optimize_model!(m::Model; log_level=3, calculate_duals=false, iterations=nothing)
write_mps_file(model=m.ext[:instance]) == :write_mps_always && write_to_file(m, "model_diagnostics.mps")
# NOTE: The above results in a lot of Warning: Variable connection_flow[...] is mentioned in BOUNDS,
# but is not mentioned in the COLUMNS section.
@@ -287,8 +289,8 @@ function optimize_model!(m::Model; log_level=3, calculate_duals=false)
@timelog log_level 0 "Optimizing final LP of $(m.ext[:instance]) to obtain duals..." optimize!(m)
end
@log log_level 1 "Optimal solution found, objective function value: $(objective_value(m))"
- @timelog log_level 2 "Saving $(m.ext[:instance]) results..." save_model_results!(m)
- if calculate_duals
+ @timelog log_level 2 "Saving $(m.ext[:instance]) results..." save_model_results!(m,iterations=iterations)
+ if calculate_duals
save_marginal_values!(m)
save_bound_marginal_values!(m)
if lp_solver != mip_solver
@@ -383,9 +385,21 @@ function _value_by_time_stamp_aggregated(by_time_slice_non_aggr, ::Nothing)
Dict(start(t) => v for (t, v) in by_time_slice_non_aggr)
end
-function _save_output!(m, out, value_or_param)
+function _save_output!(m, out, value_or_param; iterations=nothing)
by_entity_non_aggr = _value_by_entity_non_aggregated(m, value_or_param)
for (entity, by_analysis_time_non_aggr) in by_entity_non_aggr
+ if !isnothing(iterations)
+ new_mga_name = Symbol(string("mga_it_", iterations)) ##TODO: fixme! Needs to be done, befooooore we execute solve, as we need to set objective for this solve
+ if mga_iteration(new_mga_name) == nothing
+ new_mga_i = Object(new_mga_name)
+ add_object!(mga_iteration, new_mga_i)
+ else
+ new_mga_i = mga_iteration(new_mga_name)
+ end
+ new_val = (values(entity)...,new_mga_i)
+ new_key = (keys(entity)...,:mga_iteration)
+ entity = NamedTuple{new_key}(new_val)
+ end
for (analysis_time, by_time_slice_non_aggr) in by_analysis_time_non_aggr
t_highest_resolution!(by_time_slice_non_aggr)
output_time_slices_ = output_time_slices(m, output=out)
@@ -399,19 +413,19 @@ function _save_output!(m, out, value_or_param)
end
true
end
-_save_output!(m, out, ::Nothing) = false
+_save_output!(m, out, ::Nothing; iterations=iterations) = false
"""
Save the outputs of a model into a dictionary.
"""
-function save_outputs!(m)
+function save_outputs!(m; iterations=nothing)
for r in model__report(model=m.ext[:instance]), out in report__output(report=r)
value = get(m.ext[:values], out.name, nothing)
- if _save_output!(m, out, value)
+ if _save_output!(m, out, value;iterations=iterations)
continue
end
param = parameter(out.name, @__MODULE__)
- if _save_output!(m, out, param)
+ if _save_output!(m, out, param;iterations=iterations)
continue
end
@warn "can't find any values for '$(out.name)'"
@@ -421,10 +435,10 @@ end
"""
Save a model results: first postprocess results, then save variables and objective values, and finally save outputs
"""
-function save_model_results!(m)
+function save_model_results!(m; iterations=nothing)
save_variable_values!(m)
save_objective_values!(m)
- save_outputs!(m)
+ save_outputs!(m; iterations=iterations)
end
"""
diff --git a/src/variables/variable_connections_invested_available.jl b/src/variables/variable_connections_invested_available.jl
index 8c486a99f2..6f3ab84684 100644
--- a/src/variables/variable_connections_invested_available.jl
+++ b/src/variables/variable_connections_invested_available.jl
@@ -30,7 +30,8 @@ function connections_invested_available_indices(
t=anything,
temporal_block=anything,
)
- [
+ connection = members(connection)
+ unique([
(connection=conn, stochastic_scenario=s, t=t) for (conn, tb) in connection__investment_temporal_block(
connection=connection,
temporal_block=temporal_block,
@@ -42,7 +43,7 @@ function connections_invested_available_indices(
temporal_block=tb,
t=t,
)
- ]
+ ])
end
"""
diff --git a/src/variables/variable_storages_invested_available.jl b/src/variables/variable_storages_invested_available.jl
index 76b7a1b93d..15ed22e251 100644
--- a/src/variables/variable_storages_invested_available.jl
+++ b/src/variables/variable_storages_invested_available.jl
@@ -30,7 +30,8 @@ function storages_invested_available_indices(
t=anything,
temporal_block=anything,
)
- [
+ node=members(node)
+ unique([
(node=n, stochastic_scenario=s, t=t)
for (n, tb) in node__investment_temporal_block(node=node, temporal_block=temporal_block, _compact=false)
for (n, s, t) in node_investment_stochastic_time_indices(
@@ -40,7 +41,7 @@ function storages_invested_available_indices(
temporal_block=tb,
t=t,
)
- ]
+ ])
end
"""
diff --git a/src/variables/variable_units_invested_available.jl b/src/variables/variable_units_invested_available.jl
index e64b6e1dec..fb3755545a 100644
--- a/src/variables/variable_units_invested_available.jl
+++ b/src/variables/variable_units_invested_available.jl
@@ -30,7 +30,8 @@ function units_invested_available_indices(
t=anything,
temporal_block=anything,
)
- [
+ unit = members(unit)
+ unique([
(unit=u, stochastic_scenario=s, t=t)
for (u, tb) in unit__investment_temporal_block(unit=unit, temporal_block=temporal_block, _compact=false)
for (u, s, t) in unit_investment_stochastic_time_indices(
@@ -40,7 +41,7 @@ function units_invested_available_indices(
temporal_block=tb,
t=t,
)
- ]
+ ])
end
"""
diff --git a/templates/archetypes/unit_commitment_restrictions.json b/templates/archetypes/unit_commitment_restrictions.json
index 2609dfec03..22cd57742c 100644
--- a/templates/archetypes/unit_commitment_restrictions.json
+++ b/templates/archetypes/unit_commitment_restrictions.json
@@ -29,8 +29,9 @@
["constraint_sense_list", ">="],
["duration_unit_list", "hour"],
["duration_unit_list", "minute"],
- ["model_type_list", "spineopt_master"],
- ["model_type_list", "spineopt_operations"],
+ ["model_type_list", "spineopt_benders_master"],
+ ["model_type_list", "spineopt_standard"],
+ ["model_type_list", "spineopt_standard"],
["model_type_list", "spineopt_other"],
["node_opf_type_list", "node_opf_type_normal"],
["node_opf_type_list", "node_opf_type_reference"],
@@ -189,4 +190,4 @@
["object_activity_control", "temporal_block", "is_active", false],
["object_activity_control", "unit", "is_active", false]
]
-}
\ No newline at end of file
+}
diff --git a/templates/archetypes/units_ramping_and_non-spinning_reserve_restrictions.json b/templates/archetypes/units_ramping_and_non-spinning_reserve_restrictions.json
index bb1c2bff0c..b684e3bdde 100644
--- a/templates/archetypes/units_ramping_and_non-spinning_reserve_restrictions.json
+++ b/templates/archetypes/units_ramping_and_non-spinning_reserve_restrictions.json
@@ -28,8 +28,8 @@
["constraint_sense_list", ">="],
["duration_unit_list", "hour"],
["duration_unit_list", "minute"],
- ["model_type_list", "spineopt_master"],
- ["model_type_list", "spineopt_operations"],
+ ["model_type_list", "spineopt_benders_master"],
+ ["model_type_list", "spineopt_standard"],
["model_type_list", "spineopt_other"],
["node_opf_type_list", "node_opf_type_normal"],
["node_opf_type_list", "node_opf_type_reference"],
diff --git a/templates/archetypes/units_ramping_and_spinning_reserve_restrictions.json b/templates/archetypes/units_ramping_and_spinning_reserve_restrictions.json
index e2e7650f3f..26f9bbc859 100644
--- a/templates/archetypes/units_ramping_and_spinning_reserve_restrictions.json
+++ b/templates/archetypes/units_ramping_and_spinning_reserve_restrictions.json
@@ -28,8 +28,8 @@
["constraint_sense_list", ">="],
["duration_unit_list", "hour"],
["duration_unit_list", "minute"],
- ["model_type_list", "spineopt_master"],
- ["model_type_list", "spineopt_operations"],
+ ["model_type_list", "spineopt_benders_master"],
+ ["model_type_list", "spineopt_standard"],
["model_type_list", "spineopt_other"],
["node_opf_type_list", "node_opf_type_normal"],
["node_opf_type_list", "node_opf_type_reference"],
diff --git a/templates/archetypes/units_ramping_restrictions.json b/templates/archetypes/units_ramping_restrictions.json
index f08da28afc..714653a3ba 100644
--- a/templates/archetypes/units_ramping_restrictions.json
+++ b/templates/archetypes/units_ramping_restrictions.json
@@ -28,8 +28,8 @@
["constraint_sense_list", ">="],
["duration_unit_list", "hour"],
["duration_unit_list", "minute"],
- ["model_type_list", "spineopt_master"],
- ["model_type_list", "spineopt_operations"],
+ ["model_type_list", "spineopt_benders_master"],
+ ["model_type_list", "spineopt_standard"],
["model_type_list", "spineopt_other"],
["node_opf_type_list", "node_opf_type_normal"],
["node_opf_type_list", "node_opf_type_reference"],
diff --git a/templates/models/basic_model_template.json b/templates/models/basic_model_template.json
index 6dc07bb55b..3255943f0f 100644
--- a/templates/models/basic_model_template.json
+++ b/templates/models/basic_model_template.json
@@ -67,8 +67,8 @@
["constraint_sense_list", ">="],
["duration_unit_list", "hour"],
["duration_unit_list", "minute"],
- ["model_type_list", "spineopt_master"],
- ["model_type_list", "spineopt_operations"],
+ ["model_type_list", "spineopt_benders_master"],
+ ["model_type_list", "spineopt_standard"],
["model_type_list", "spineopt_other"],
["node_opf_type_list", "node_opf_type_normal"],
["node_opf_type_list", "node_opf_type_reference"],
@@ -113,7 +113,7 @@
["model", "max_iterations", 10.0, null, "Specifies the maximum number of iterations for the model. Currently only used for the master problem within a decomposed structure"],
["model", "model_end", {"data": "2000-01-02T00:00:00", "type": "date_time"}, null, "Defines the last timestamp to be modelled. Rolling optimization terminates after passing this point."],
["model", "model_start", {"data": "2000-01-01T00:00:00", "type": "date_time"}, null, "Defines the first timestamp to be modelled. Relative `temporal_blocks` refer to this value for their start and end."],
- ["model", "model_type", "spineopt_operations", "model_type_list", "Used to identify model objects as relating to the master problem or operational sub problems (default)"],
+ ["model", "model_type", "spineopt_standard", "model_type_list", "Used to identify model objects as relating to the master problem or operational sub problems (default)"],
["model", "roll_forward", null, null, "Defines how much the model moves ahead in time between solves in a rolling optimization. Without this parameter, everything is solved in as a single optimization."],
["model", "write_lodf_file", false, "boolean_value_list", "A boolean flag for whether the LODF values should be written to a results file."],
["model", "write_mps_file", null, "write_mps_file_list", "A selector for writing an .mps file of the model."],
diff --git a/templates/spineopt_template.json b/templates/spineopt_template.json
index 34510f8148..4b669af709 100644
--- a/templates/spineopt_template.json
+++ b/templates/spineopt_template.json
@@ -69,9 +69,10 @@
["constraint_sense_list", ">="],
["duration_unit_list", "hour"],
["duration_unit_list", "minute"],
- ["model_type_list", "spineopt_master"],
- ["model_type_list", "spineopt_operations"],
+ ["model_type_list", "spineopt_benders_master"],
+ ["model_type_list", "spineopt_standard"],
["model_type_list", "spineopt_other"],
+ ["model_type_list", "spineopt_mga"],
["node_opf_type_list", "node_opf_type_normal"],
["node_opf_type_list", "node_opf_type_reference"],
["unit_investment_variable_type_list", "unit_investment_variable_type_continuous"],
@@ -94,7 +95,7 @@
["db_mip_solver_list", "Gurobi.jl"],
["db_mip_solver_list", "Juniper.jl"],
["db_mip_solver_list", "MosekTools.jl"],
- ["db_mip_solver_list", "SCIP.jl"],
+ ["db_mip_solver_list", "SCIP.jl"],
["db_lp_solver_list", "KNITRO.jl"],
["db_lp_solver_list", "CDCS.jl"],
["db_lp_solver_list", "CDDLib.jl"],
@@ -142,18 +143,23 @@
["connection", "graph_view_position", null, null, "An optional setting for tweaking the position of the different elements when drawing them via Spine Toolbox Graph View."],
["connection", "is_active", true, "boolean_value_list", "If false, the object is excluded from the model if the tool filter object activity control is specified"],
["connection", "has_binary_gas_flow", false, "boolean_value_list", "This parameter needs to be set to `true` in order to represent bidirectional pressure drive gas transfer."],
+ ["connection", "connections_invested_big_m_mga", 100, null, "big_m_mga should be chosen as small as possible but sufficiently large. For units_invested_mga an appropriate big_m_mga would be twice the candidate connections."],
+ ["connection", "connections_invested_mga", false, "boolean_value_list", "Defines whether a certain variable (here: connections_invested) will be considered in the maximal-differences of the mga objective"],
["model", "duration_unit", "hour", "duration_unit_list", "Defines the base temporal unit of the `model`. Currently supported values are either an `hour` or a `minute`."],
["model", "is_active", true, "boolean_value_list", "If false, the object is excluded from the model if the tool filter object activity control is specified"],
["model", "max_gap", 0.05, null, "Specifies the maximum optimality gap for the model. Currently only used for the master problem within a decomposed structure"],
["model", "max_iterations", 10.0, null, "Specifies the maximum number of iterations for the model. Currently only used for the master problem within a decomposed structure"],
+ ["model", "max_mga_iterations", 10.0, null, "Define the number of mga iterations, i.e. how many alternative solutions will be generated."],
+ ["model", "max_mga_slack", 0.05, null, "Defines the maximum slack by which the alternative solution may differ from the original solution (e.g. 5% more than initial objective function value)"],
+ ["model", "mga_diff_relative", false, null, "If set to true, the elementwise differences are expressed relative to each other."],
["model", "model_end", {"type": "date_time", "data": "2000-01-02T00:00:00"}, null, "Defines the last timestamp to be modelled. Rolling optimization terminates after passing this point."],
["model", "model_start", {"type": "date_time", "data": "2000-01-01T00:00:00"}, null, "Defines the first timestamp to be modelled. Relative `temporal_blocks` refer to this value for their start and end."],
- ["model", "model_type", "spineopt_operations", "model_type_list", "Used to identify model objects as relating to the master problem or operational sub problems (default)"],
+ ["model", "model_type", "spineopt_standard", "model_type_list", "Used to identify model objects as relating to the master problem or operational sub problems (default)"],
["model", "roll_forward", null, null, "Defines how much the model moves ahead in time between solves in a rolling optimization. Without this parameter, everything is solved in as a single optimization."],
["model", "write_lodf_file", false, "boolean_value_list", "A boolean flag for whether the LODF values should be written to a results file."],
["model", "write_mps_file", null, "write_mps_file_list", "A selector for writing an .mps file of the model."],
["model", "write_ptdf_file", false, "boolean_value_list", "A boolean flag for whether the LODF values should be written to a results file."],
- ["model","big_m", 1000000, null, "Sufficiently large number used for linearization bilinear terms, e.g. to enforce bidirectional flow for gas pipielines"],
+ ["model","big_m", 1000000, null, "Sufficiently large number used for linearization bilinear terms, e.g. to enforce bidirectional flow for gas pipielines"],
["model", "db_lp_solver", "Clp.jl", "db_lp_solver_list", "Solver for MIP problems. Solver package must be added and pre-configured in Julia. Overrides lp_solver RunSpineOpt kwarg"],
["model", "db_mip_solver", "Cbc.jl", "db_mip_solver_list", "Solver for MIP problems. Solver package must be added and pre-configured in Julia. Overrides mip_solver RunSpineOpt kwarg"],
["model", "db_mip_solver_options", {"type": "map", "index_type": "str", "data": [["HiGHS.jl", {"type": "map", "index_type": "str", "data": [["presolve", "on"], ["mip_rel_gap", 0.01], ["threads", 8.0]]}], ["Cbc.jl", {"type": "map", "index_type": "str", "data": [["ratioGap", 0.01], ["logLevel", 0.0]]}], ["CPLEX.jl", {"type": "map", "index_type": "str", "data": [["CPX_PARAM_EPGAP", 0.01]]}]]}, null, "Map parameter containing MIP solver option name option value pairs for MIP. See solver documentation for supported solver options"],
@@ -194,11 +200,13 @@
["node", "min_node_pressure", null, null, "Minimum allowed gas pressure at `node`."],
["node", "max_voltage_angle", null, null, "Maximum allowed voltage angle at `node`."],
["node", "min_voltage_angle", null, null, "Minimum allowed voltage angle at `node`. "],
+ ["node", "storages_invested_big_m_mga", 100, null, "big_m_mga should be chosen as small as possible but sufficiently large. For units_invested_mga an appropriate big_m_mga would be twice the candidate storages."],
+ ["node", "storages_invested_mga", false, "boolean_value_list", "Defines whether a certain variable (here: storages_invested) will be considered in the maximal-differences of the mga objective"],
["output", "is_active", true, "boolean_value_list", "If false, the object is excluded from the model if the tool filter object activity control is specified"],
["output", "output_resolution", null, null, "Temporal resolution of the output variables associated with this `output`."],
["report", "is_active", true, "boolean_value_list", "If false, the object is excluded from the model if the tool filter object activity control is specified"],
["report", "output_db_url", null, null, "Database url for SpineOpt output."],
- ["settings", "version", 3, null, "Current version of the SpineOpt data structure. Modify it at your own risk (but please don't)."],
+ ["settings", "version", 4, null, "Current version of the SpineOpt data structure. Modify it at your own risk (but please don't)."],
["stochastic_scenario", "is_active", true, "boolean_value_list", "If false, the object is excluded from the model if the tool filter object activity control is specified"],
["stochastic_structure", "is_active", true, "boolean_value_list", "If false, the object is excluded from the model if the tool filter object activity control is specified"],
["temporal_block", "block_end", null, null, "The end time for the `temporal_block`. Can be given either as a `DateTime` for a static end point, or as a `Duration` for an end point relative to the start of the current optimization."],
@@ -227,6 +235,8 @@
["unit", "unit_investment_variable_type", "unit_investment_variable_type_continuous", "unit_investment_variable_type_list", "Determines whether investment variable is integer or continuous."],
["unit", "units_on_non_anticipativity_time", null, null, "Period of time where the value of the `units_on` variable has to be fixed to the result from the previous window."],
["unit", "units_on_cost", null, null, "Objective function coefficient on `units_on`. An idling cost, for example"],
+ ["unit", "units_invested_big_m_mga", 100, null, "big_m_mga should be chosen as small as possible but sufficiently large. For units_invested_mga an appropriate big_m_mga would be twice the candidate units."],
+ ["unit", "units_invested_mga", false, "boolean_value_list", "Defines whether a certain variable (here: units_invested) will be considered in the maximal-differences of the mga objective"],
["user_constraint", "constraint_sense", "==", "constraint_sense_list", "A selector for the sense of the `user_constraint`."],
["user_constraint", "is_active", true, "boolean_value_list", "If false, the object is excluded from the model if the tool filter object activity control is specified"],
["user_constraint", "right_hand_side", 0.0, null, "The right-hand side, constant term in a `user_constraint`. Can be time-dependent and used e.g. for complicated efficiency approximations."]
@@ -374,6 +384,7 @@
["output","connections_invested_available", null],
["output","connections_invested", null],
["output","mp_objective_lowerbound", null],
+ ["output","mga_objective", null],
["output","node_injection", null],
["output","node_pressure", null],
["output","node_slack_neg", null],
diff --git a/test/constraints/constraint_connection.jl b/test/constraints/constraint_connection.jl
index 9ccd0ed768..ad9e21c395 100644
--- a/test/constraints/constraint_connection.jl
+++ b/test/constraints/constraint_connection.jl
@@ -67,7 +67,7 @@
["model", "instance", "model_start", Dict("type" => "date_time", "data" => "2000-01-01T00:00:00")],
["model", "instance", "model_end", Dict("type" => "date_time", "data" => "2000-01-01T02:00:00")],
["model", "instance", "duration_unit", "hour"],
- ["model", "instance", "model_type", "spineopt_operations"],
+ ["model", "instance", "model_type", "spineopt_standard"],
["model", "master", "model_start", Dict("type" => "date_time", "data" => "2000-01-01T00:00:00")],
["model", "master", "model_end", Dict("type" => "date_time", "data" => "2000-01-01T02:00:00")],
["model", "master", "duration_unit", "hour"],
@@ -122,7 +122,7 @@
relationship_parameter_values =
[["connection__node__node", ["connection_ca", "node_c","node_a"], "fixed_pressure_constant_1", fixed_pressure_constant_1_[("connection_ca", "node_c","node_a")]]]
SpineInterface.import_data(
- url_in;
+ url_in;
object_parameter_values=object_parameter_values, relationship_parameter_values=relationship_parameter_values, relationships=relationships
)
m = run_spineopt(url_in; log_level=0, optimize=false)
@@ -175,7 +175,7 @@
["connection__node__node", ["connection_ca", "node_c","node_a"], "fixed_pressure_constant_0", fixed_pressure_constant_0_[("connection_ca", "node_c","node_a")]]
]
SpineInterface.import_data(url_in; object_parameter_values=object_parameter_values, relationship_parameter_values=relationship_parameter_values, relationships=relationships)
-
+
m = run_spineopt(url_in; log_level=0, optimize=false)
var_connection_flow = m.ext[:variables][:connection_flow]
var_binary_flow = m.ext[:variables][:binary_gas_connection_flow]
@@ -233,7 +233,7 @@
]
relationship_parameter_values = [
[
- "connection__node__node",
+ "connection__node__node",
["connection_ca", "node_c","node_a"],
"fixed_pressure_constant_1",
fixed_pr_constant_1_[("connection_ca", "node_c","node_a")]
@@ -291,7 +291,7 @@
]
_load_test_data(url_in, test_data)
SpineInterface.import_data(url_in; object_parameter_values=object_parameter_values, relationship_parameter_values=relationship_parameter_values, relationships=relationships)
-
+
m = run_spineopt(url_in; log_level=0, optimize=false)
var_connection_flow = m.ext[:variables][:connection_flow]
var_voltage_angle = m.ext[:variables][:node_voltage_angle]
@@ -348,7 +348,7 @@
relationships=relationships,
relationship_parameter_values=relationship_parameter_values,
)
-
+
m = run_spineopt(url_in; log_level=0, optimize=false)
var_connection_flow = m.ext[:variables][:connection_flow]
var_connections_invested_available = m.ext[:variables][:connections_invested_available]
@@ -402,7 +402,7 @@
["connection", "connection_ca", "connection_reactance", conn_x],
["connection", "connection_ca", "connection_resistance", conn_r],
["commodity", "electricity", "commodity_physics", "commodity_physics_ptdf"],
- ["node", "node_a", "node_opf_type", "node_opf_type_reference"],
+ ["node", "node_a", "node_opf_type", "node_opf_type_reference"],
]
relationship_parameter_values = [
["connection__node__node", ["connection_ab", "node_b", "node_a"], "fix_ratio_out_in_connection_flow", 1.0],
@@ -519,7 +519,7 @@
object_parameter_values=object_parameter_values,
relationship_parameter_values=relationship_parameter_values,
)
-
+
m = run_spineopt(url_in; log_level=0, optimize=false)
var_connection_flow = m.ext[:variables][:connection_flow]
constraint = m.ext[:constraints][:connection_flow_lodf]
@@ -610,7 +610,7 @@
object_parameter_values=object_parameter_values,
relationship_parameter_values=relationship_parameter_values,
)
-
+
m = run_spineopt(url_in; log_level=0, optimize=false)
var_connection_flow = m.ext[:variables][:connection_flow]
constraint = m.ext[:constraints][Symbol(ratio)]
@@ -662,7 +662,7 @@
["connection__investment_stochastic_structure", ["connection_ab", "stochastic"]],
]
SpineInterface.import_data(url_in; relationships=relationships, object_parameter_values=object_parameter_values)
-
+
m = run_spineopt(url_in; log_level=0, optimize=false)
var_connections_invested_available = m.ext[:variables][:connections_invested_available]
var_connections_invested = m.ext[:variables][:connections_invested]
@@ -697,7 +697,8 @@
candidate_connections = 4
object_parameter_values = [
["connection", "connection_ab", "candidate_connections", candidate_connections],
- ["model", "master", "model_type", "spineopt_master"],
+ ["model", "master", "model_type", "spineopt_benders_master"],
+ ["model", "instance", "model_type", "spineopt_standard"],
]
relationships = [
["connection__investment_temporal_block", ["connection_ab", "hourly"]],
@@ -706,7 +707,7 @@
["connection__investment_stochastic_structure", ["connection_ab", "stochastic"]],
]
SpineInterface.import_data(url_in; relationships=relationships, object_parameter_values=object_parameter_values)
-
+
m, mp = run_spineopt(url_in; log_level=0, optimize=false)
var_connections_invested_available = m.ext[:variables][:connections_invested_available]
var_connections_invested = m.ext[:variables][:connections_invested]
@@ -780,7 +781,7 @@
["connection__investment_stochastic_structure", ["connection_ab", "stochastic"]],
]
SpineInterface.import_data(url_in; relationships=relationships, object_parameter_values=object_parameter_values)
-
+
m = run_spineopt(url_in; log_level=0, optimize=false)
var_connections_invested_available = m.ext[:variables][:connections_invested_available]
var_connections_invested = m.ext[:variables][:connections_invested]
@@ -828,7 +829,8 @@
["connection", "connection_ab", "connection_investment_lifetime", connection_investment_lifetime],
["model", "instance", "model_end", model_end],
["model", "master", "model_end", model_end],
- ["model", "master", "model_type", "spineopt_master"],
+ ["model", "master", "model_type", "spineopt_benders_master"],
+ ["model", "instance", "model_type", "spineopt_standard"],
]
relationships = [
["connection__investment_temporal_block", ["connection_ab", "hourly"]],
@@ -837,7 +839,7 @@
["connection__investment_stochastic_structure", ["connection_ab", "investments_deterministic"]],
]
SpineInterface.import_data(url_in; relationships=relationships, object_parameter_values=object_parameter_values)
-
+
m, mp = run_spineopt(url_in; log_level=0, optimize=false)
var_connections_invested_available = m.ext[:variables][:connections_invested_available]
var_connections_invested = m.ext[:variables][:connections_invested]
@@ -914,7 +916,7 @@
["connection__investment_stochastic_structure", ["connection_ab", "stochastic"]],
]
SpineInterface.import_data(url_in; relationships=relationships, object_parameter_values=object_parameter_values)
-
+
m = run_spineopt(url_in; log_level=0, optimize=false)
var_connections_invested_available = m.ext[:variables][:connections_invested_available]
constraint = m.ext[:constraints][:connections_invested_available]
@@ -935,7 +937,8 @@
candidate_connections = 7
object_parameter_values = [
["connection", "connection_ab", "candidate_connections", candidate_connections],
- ["model", "master", "model_type", "spineopt_master"],
+ ["model", "master", "model_type", "spineopt_benders_master"],
+ ["model", "instance", "model_type", "spineopt_standard"],
]
relationships = [
["connection__investment_temporal_block", ["connection_ab", "hourly"]],
@@ -944,7 +947,7 @@
["connection__investment_stochastic_structure", ["connection_ab", "stochastic"]],
]
SpineInterface.import_data(url_in; relationships=relationships, object_parameter_values=object_parameter_values)
-
+
m, mp = run_spineopt(url_in; log_level=0, optimize=false)
var_connections_invested_available = m.ext[:variables][:connections_invested_available]
constraint = m.ext[:constraints][:connections_invested_available]
@@ -1016,7 +1019,7 @@
object_parameter_values=object_parameter_values,
relationship_parameter_values=relationship_parameter_values,
)
-
+
m = run_spineopt(url_in; log_level=0, optimize=false)
var_unit_flow = m.ext[:variables][:unit_flow]
var_units_on = m.ext[:variables][:units_on]
@@ -1107,7 +1110,7 @@
object_parameter_values=object_parameter_values,
relationship_parameter_values=relationship_parameter_values,
)
-
+
m = run_spineopt(url_in; log_level=0, optimize=false)
constraint = m.ext[:constraints][:connection_flow_intact_flow]
var_connection_flow = m.ext[:variables][:connection_flow]
@@ -1220,7 +1223,7 @@
object_parameter_values=object_parameter_values,
relationship_parameter_values=relationship_parameter_values,
)
-
+
m = run_spineopt(url_in; log_level=0, optimize=false)
constraint = m.ext[:constraints][:candidate_connection_flow_lb]
var_connection_flow = m.ext[:variables][:connection_flow]
@@ -1338,7 +1341,7 @@
object_parameter_values=object_parameter_values,
relationship_parameter_values=relationship_parameter_values,
)
-
+
m = run_spineopt(url_in; log_level=0, optimize=false)
constraint = m.ext[:constraints][:ratio_out_in_connection_intact_flow]
var_connection_intact_flow = m.ext[:variables][:connection_intact_flow]
@@ -1492,7 +1495,7 @@
object_parameter_values=object_parameter_values,
relationship_parameter_values=relationship_parameter_values,
)
-
+
m = run_spineopt(url_in; log_level=0, optimize=false)
constraint = m.ext[:constraints][:candidate_connection_flow_ub]
var_connection_intact_flow = m.ext[:variables][:connection_intact_flow]
diff --git a/test/constraints/constraint_node.jl b/test/constraints/constraint_node.jl
index 666b018758..8b021cb5d1 100644
--- a/test/constraints/constraint_node.jl
+++ b/test/constraints/constraint_node.jl
@@ -73,7 +73,7 @@
["model", "instance", "model_start", Dict("type" => "date_time", "data" => "2000-01-01T00:00:00")],
["model", "instance", "model_end", Dict("type" => "date_time", "data" => "2000-01-01T02:00:00")],
["model", "instance", "duration_unit", "hour"],
- ["model", "instance", "model_type", "spineopt_operations"],
+ ["model", "instance", "model_type", "spineopt_standard"],
["model", "master", "model_start", Dict("type" => "date_time", "data" => "2000-01-01T00:00:00")],
["model", "master", "model_end", Dict("type" => "date_time", "data" => "2000-01-01T02:00:00")],
["model", "master", "duration_unit", "hour"],
@@ -117,15 +117,15 @@
var_conn_flow = var_connection_flow[conn_key...]
expected_con = @build_constraint(var_n_inj + var_conn_flow + var_n_sl_pos - var_n_sl_neg == 0)
con = constraint[node_key...]
- observed_con = constraint_object(con)
+ observed_con = constraint_object(con)
# node_b
n = node(:node_b)
scenarios = (stochastic_scenario(:parent), stochastic_scenario(:child))
time_slices = time_slice(m; temporal_block=temporal_block(:hourly))
@testset for (s, t) in zip(scenarios, time_slices)
var_n_inj = var_node_injection[n, s, t]
- var_conn_flows = (
- - var_connection_flow[connection(:connection_bc), node(:node_b), direction(:from_node), s, t]
+ var_conn_flows = (
+ - var_connection_flow[connection(:connection_bc), node(:node_b), direction(:from_node), s, t]
)
expected_con = @build_constraint(var_n_inj + var_conn_flows == 0)
con = constraint[n, s, t]
@@ -141,9 +141,9 @@
["node", "node_a", "node_slack_penalty", 0.5],
["node", "node_group_bc", "balance_type", "balance_type_group"]
]
-
+
SpineInterface.import_data(url_in; object_parameter_values=object_parameter_values)
-
+
m = run_spineopt(url_in; log_level=0, optimize=false)
var_node_injection = m.ext[:variables][:node_injection]
var_connection_flow = m.ext[:variables][:connection_flow]
@@ -164,7 +164,7 @@
expected_con = @build_constraint(var_n_inj + var_conn_flow + var_n_sl_pos - var_n_sl_neg == 0)
con = constraint[node_key...]
observed_con = constraint_object(con)
-
+
@test _is_constraint_equal(observed_con, expected_con)
# node_group_bc
n = node(:node_group_bc)
@@ -172,10 +172,10 @@
time_slices = time_slice(m; temporal_block=temporal_block(:hourly))
@testset for (s, t) in zip(scenarios, time_slices)
var_n_inj = var_node_injection[node(:node_group_bc), s, t]
- var_conn_flows = (
+ var_conn_flows = (
- var_connection_flow[connection(:connection_ca), node(:node_c), direction(:from_node), s, t]
)
-
+
expected_con = @build_constraint(var_n_inj + var_conn_flows == 0)
con = constraint[node(:node_group_bc), s, t]
observed_con = constraint_object(con)
@@ -221,7 +221,7 @@
object_parameter_values=object_parameter_values,
relationship_parameter_values=relationship_parameter_values,
)
-
+
m = run_spineopt(url_in; log_level=0, optimize=false)
var_node_injection = m.ext[:variables][:node_injection]
var_unit_flow = m.ext[:variables][:unit_flow]
@@ -315,7 +315,7 @@
["node", "node_c", "has_state", true],
]
SpineInterface.import_data(url_in; object_parameter_values=object_parameter_values)
-
+
m = run_spineopt(url_in; log_level=0, optimize=false)
var_node_state = m.ext[:variables][:node_state]
constraint = m.ext[:constraints][:node_state_capacity]
@@ -350,7 +350,7 @@
["node__temporal_block", ["node_c", "hourly"],"cyclic_condition", cyc_cond[("node_c", "hourly")]],
]
SpineInterface.import_data(url_in; object_parameter_values=object_parameter_values, relationship_parameter_values=relationship_parameter_values)
-
+
m = run_spineopt(url_in; log_level=0, optimize=false)
var_node_state = m.ext[:variables][:node_state]
constraint = m.ext[:constraints][:cyclic_node_state]
@@ -397,7 +397,7 @@
relationship_parameter_values=relationship_parameter_values,
relationships=relationships,
)
-
+
m = run_spineopt(url_in; log_level=0, optimize=false)
var_node_pressure = m.ext[:variables][:node_pressure]
var_node_state = m.ext[:variables][:node_state]
@@ -449,7 +449,7 @@
object_parameter_values=object_parameter_values,
relationship_parameter_values=relationship_parameter_values,
)
-
+
m = run_spineopt(url_in; log_level=0, optimize=false)
var_node_pressure = m.ext[:variables][:node_pressure]
constraint = m.ext[:constraints][:compression_ratio]
@@ -485,7 +485,7 @@
url_in;
object_parameter_values=object_parameter_values
)
-
+
m = run_spineopt(url_in; log_level=0, optimize=false)
var_node_pressure = m.ext[:variables][:node_pressure]
constraint = m.ext[:constraints][:min_node_pressure]
@@ -517,7 +517,7 @@
url_in;
object_parameter_values=object_parameter_values
)
-
+
m = run_spineopt(url_in; log_level=0, optimize=false)
var_node_pressure = m.ext[:variables][:node_pressure]
constraint = m.ext[:constraints][:max_node_pressure]
@@ -549,7 +549,7 @@
url_in;
object_parameter_values=object_parameter_values
)
-
+
m = run_spineopt(url_in; log_level=0, optimize=false)
var_node_voltage_angle = m.ext[:variables][:node_voltage_angle]
constraint = m.ext[:constraints][:min_node_voltage_angle]
@@ -581,7 +581,7 @@
url_in;
object_parameter_values=object_parameter_values
)
-
+
m = run_spineopt(url_in; log_level=0, optimize=false)
var_node_voltage_angle = m.ext[:variables][:node_voltage_angle]
constraint = m.ext[:constraints][:max_node_voltage_angle]
@@ -615,7 +615,7 @@
["node__investment_stochastic_structure", ["node_c", "stochastic"]],
]
SpineInterface.import_data(url_in; relationships=relationships, object_parameter_values=object_parameter_values)
-
+
m = run_spineopt(url_in; log_level=0, optimize=false)
var_node_state = m.ext[:variables][:node_state]
var_storages_invested_available = m.ext[:variables][:storages_invested_available]
@@ -651,7 +651,7 @@
["node__investment_stochastic_structure", ["node_c", "stochastic"]],
]
SpineInterface.import_data(url_in; relationships=relationships, object_parameter_values=object_parameter_values)
-
+
m = run_spineopt(url_in; log_level=0, optimize=false)
var_storages_invested_available = m.ext[:variables][:storages_invested_available]
constraint = m.ext[:constraints][:storages_invested_available]
@@ -675,7 +675,8 @@
["node", "node_c", "candidate_storages", candidate_storages],
["node", "node_c", "node_state_cap", node_capacity],
["node", "node_b", "has_state", true],
- ["model", "master", "model_type", "spineopt_master"],
+ ["model", "master", "model_type", "spineopt_benders_master"],
+ ["model", "instance", "model_type", "spineopt_standard"],
]
relationships = [
["node__investment_temporal_block", ["node_c", "hourly"]],
@@ -684,7 +685,7 @@
["node__investment_stochastic_structure", ["node_c", "investments_deterministic"]],
]
SpineInterface.import_data(url_in; relationships=relationships, object_parameter_values=object_parameter_values)
-
+
m, mp = run_spineopt(url_in; log_level=0, optimize=false)
var_storages_invested_available = m.ext[:variables][:storages_invested_available]
constraint = m.ext[:constraints][:storages_invested_available]
@@ -727,7 +728,7 @@
["node__investment_stochastic_structure", ["node_c", "stochastic"]],
]
SpineInterface.import_data(url_in; relationships=relationships, object_parameter_values=object_parameter_values)
-
+
m = run_spineopt(url_in; log_level=0, optimize=false)
var_storages_invested_available = m.ext[:variables][:storages_invested_available]
var_storages_invested = m.ext[:variables][:storages_invested]
@@ -761,7 +762,8 @@
["node", "node_c", "candidate_storages", candidate_storages],
["node", "node_c", "node_state_cap", node_capacity],
["node", "node_b", "has_state", true],
- ["model", "master", "model_type", "spineopt_master"],
+ ["model", "master", "model_type", "spineopt_benders_master"],
+ ["model", "instance", "model_type", "spineopt_standard"],
]
relationships = [
["node__investment_temporal_block", ["node_c", "hourly"]],
@@ -770,7 +772,7 @@
["node__investment_stochastic_structure", ["node_c", "investments_deterministic"]],
]
SpineInterface.import_data(url_in; relationships=relationships, object_parameter_values=object_parameter_values)
-
+
m, mp = run_spineopt(url_in; log_level=0, optimize=false)
var_storages_invested_available = m.ext[:variables][:storages_invested_available]
var_storages_invested = m.ext[:variables][:storages_invested]
@@ -839,7 +841,7 @@
["node__investment_stochastic_structure", ["node_c", "stochastic"]],
]
SpineInterface.import_data(url_in; relationships=relationships, object_parameter_values=object_parameter_values)
-
+
m = run_spineopt(url_in; log_level=0, optimize=false)
var_storages_invested_available = m.ext[:variables][:storages_invested_available]
var_storages_invested = m.ext[:variables][:storages_invested]
@@ -889,7 +891,8 @@
["node", "node_c", "storage_investment_lifetime", storage_investment_lifetime],
["model", "instance", "model_end", model_end],
["model", "master", "model_end", model_end],
- ["model", "master", "model_type", "spineopt_master"],
+ ["model", "master", "model_type", "spineopt_benders_master"],
+ ["model", "instance", "model_type", "spineopt_standard"],
]
relationships = [
["node__investment_temporal_block", ["node_c", "hourly"]],
@@ -898,7 +901,7 @@
["node__investment_stochastic_structure", ["node_c", "investments_deterministic"]],
]
SpineInterface.import_data(url_in; relationships=relationships, object_parameter_values=object_parameter_values)
-
+
m, mp = run_spineopt(url_in; log_level=0, optimize=false)
var_storages_invested_available = m.ext[:variables][:storages_invested_available]
var_storages_invested = m.ext[:variables][:storages_invested]
@@ -965,4 +968,4 @@
end
end
end
-end
\ No newline at end of file
+end
diff --git a/test/constraints/constraint_unit.jl b/test/constraints/constraint_unit.jl
index e1e3e53223..bd5836fe6e 100644
--- a/test/constraints/constraint_unit.jl
+++ b/test/constraints/constraint_unit.jl
@@ -62,7 +62,7 @@
["model", "instance", "model_start", Dict("type" => "date_time", "data" => "2000-01-01T00:00:00")],
["model", "instance", "model_end", Dict("type" => "date_time", "data" => "2000-01-01T02:00:00")],
["model", "instance", "duration_unit", "hour"],
- ["model", "instance", "model_type", "spineopt_operations"],
+ ["model", "instance", "model_type", "spineopt_standard"],
["model", "master", "model_start", Dict("type" => "date_time", "data" => "2000-01-01T00:00:00")],
["model", "master", "model_end", Dict("type" => "date_time", "data" => "2000-01-01T02:00:00")],
["model", "master", "duration_unit", "hour"],
@@ -654,7 +654,8 @@
candidate_units = 7
object_parameter_values = [
["unit", "unit_ab", "candidate_units", candidate_units],
- ["model", "master", "model_type", "spineopt_master"],
+ ["model", "master", "model_type", "spineopt_benders_master"],
+ ["model", "instance", "model_type", "spineopt_standard"],
]
relationships = [
["unit__investment_temporal_block", ["unit_ab", "hourly"]],
@@ -732,7 +733,8 @@
candidate_units = 4
object_parameter_values = [
["unit", "unit_ab", "candidate_units", candidate_units],
- ["model", "master", "model_type", "spineopt_master"],
+ ["model", "master", "model_type", "spineopt_benders_master"],
+ ["model", "instance", "model_type", "spineopt_standard"],
]
relationships = [
["unit__investment_temporal_block", ["unit_ab", "hourly"]],
@@ -854,7 +856,8 @@
["unit", "unit_ab", "unit_investment_lifetime", unit_investment_lifetime],
["model", "instance", "model_end", model_end],
["model", "master", "model_end", model_end],
- ["model", "master", "model_type", "spineopt_master"],
+ ["model", "master", "model_type", "spineopt_benders_master"],
+ ["model", "instance", "model_type", "spineopt_standard"],
]
relationships = [
["unit__investment_temporal_block", ["unit_ab", "hourly"]],
diff --git a/test/constraints/constraint_user_constraint.jl b/test/constraints/constraint_user_constraint.jl
index e84319b2f6..e1a8797ce7 100644
--- a/test/constraints/constraint_user_constraint.jl
+++ b/test/constraints/constraint_user_constraint.jl
@@ -76,7 +76,7 @@
["model", "instance", "model_start", Dict("type" => "date_time", "data" => "2000-01-01T00:00:00")],
["model", "instance", "model_end", Dict("type" => "date_time", "data" => "2000-01-01T04:00:00")],
["model", "instance", "duration_unit", "hour"],
- ["model", "instance", "model_type", "spineopt_operations"],
+ ["model", "instance", "model_type", "spineopt_standard"],
["node", "node_c", "has_state", true],
["node", "node_c", "node_state_cap", 100],
["node", "node_c", "candidate_storages", 2],
diff --git a/test/data_structure/algorithm_mga_structure.jl b/test/data_structure/algorithm_mga_structure.jl
new file mode 100644
index 0000000000..dd84b85192
--- /dev/null
+++ b/test/data_structure/algorithm_mga_structure.jl
@@ -0,0 +1,463 @@
+#############################################################################
+# Copyright (C) 2017 - 2018 Spine Project
+#
+# This file is part of SpineOpt.
+#
+# SpineOpt is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# SpineOpt is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program. If not, see .
+#############################################################################
+
+# TODO: fix_units_on, fix_unit_flow
+
+@testset "algorithm strucutre" begin
+ url_in = "sqlite://"
+ test_data = Dict(
+ :objects => [
+ ["model", "instance"],
+ ["temporal_block", "hourly"],
+ ["temporal_block", "investments_hourly"],
+ ["temporal_block", "two_hourly"],
+ ["stochastic_structure", "deterministic"],
+ ["stochastic_structure", "investments_deterministic"],
+ ["stochastic_structure", "stochastic"],
+ ["unit", "unit_ab"],
+ ["unit", "unit_bc"],
+ ["unit", "unit_group_abbc"],
+ ["node", "node_a"],
+ ["node", "node_b"],
+ ["node", "node_c"],
+ ["node", "node_group_bc"],
+ ["connection", "connection_ab"],
+ ["connection", "connection_bc"],
+ ["connection", "connection_group_abbc"],
+ ["stochastic_scenario", "parent"],
+ ["stochastic_scenario", "child"],
+ #FIXME: maybe nicer way rahter than outputs?
+ ["output","units_invested"],
+ ["output","connections_invested"],
+ ["output","storages_invested"],
+ ["output","total_costs"],
+ ["report", "report_a"]
+ ],
+ :object_groups => [
+ ["node", "node_group_bc", "node_b"],
+ ["node", "node_group_bc", "node_c"],
+ ["connection", "connection_group_abbc", "connection_ab"],
+ ["connection", "connection_group_abbc", "connection_bc"],
+ ["unit", "unit_group_abbc", "unit_ab"],
+ ["unit", "unit_group_abbc", "unit_bc"],
+ ],
+ :relationships => [
+ ["model__temporal_block", ["instance", "hourly"]],
+ ["model__temporal_block", ["instance", "two_hourly"]],
+ ["model__default_investment_temporal_block", ["instance", "two_hourly"]],
+ ["model__stochastic_structure", ["instance", "deterministic"]],
+ ["model__stochastic_structure", ["instance", "stochastic"]],
+ ["model__default_investment_stochastic_structure", ["instance", "deterministic"]],
+ ["connection__from_node", ["connection_ab", "node_a"]],
+ ["connection__to_node", ["connection_ab", "node_b"]],
+ ["connection__from_node", ["connection_bc", "node_b"]],
+ ["connection__to_node", ["connection_bc", "node_c"]],
+ ["node__temporal_block", ["node_a", "hourly"]],
+ ["node__temporal_block", ["node_b", "two_hourly"]],
+ ["node__temporal_block", ["node_c", "hourly"]],
+ ["node__stochastic_structure", ["node_a", "stochastic"]],
+ ["node__stochastic_structure", ["node_b", "deterministic"]],
+ ["node__stochastic_structure", ["node_c", "stochastic"]],
+ ["stochastic_structure__stochastic_scenario", ["deterministic", "parent"]],
+ ["stochastic_structure__stochastic_scenario", ["stochastic", "parent"]],
+ ["stochastic_structure__stochastic_scenario", ["stochastic", "child"]],
+ ["stochastic_structure__stochastic_scenario", ["investments_deterministic", "parent"]],
+ ["parent_stochastic_scenario__child_stochastic_scenario", ["parent", "child"]],
+ ["units_on__temporal_block", ["unit_ab", "hourly"]],
+ ["units_on__temporal_block", ["unit_bc", "hourly"]],
+ ["units_on__stochastic_structure", ["unit_ab", "stochastic"]],
+ ["units_on__stochastic_structure", ["unit_bc", "stochastic"]],
+ ["unit__from_node", ["unit_ab", "node_a"]],
+ ["unit__from_node", ["unit_bc", "node_b"]],
+ ["unit__to_node", ["unit_ab", "node_b"]],
+ ["unit__to_node", ["unit_bc", "node_c"]],
+ ["report__output",["report_a", "units_invested"]],
+ ["report__output",["report_a","connections_invested"]],
+ ["report__output",["report_a","storages_invested"]],
+ ["report__output",["report_a","total_costs"]],
+ ["model__report",["instance","report_a"]],
+ ["unit__node__node", ["unit_ab", "node_a", "node_b"]],
+ ["connection__node__node", ["connection_ab", "node_a", "node_b"]],
+ ["unit__node__node", ["unit_ab", "node_b", "node_a"]],
+ ["connection__node__node", ["connection_ab", "node_b", "node_a"]],
+ ["unit__node__node", ["unit_bc", "node_b", "node_c"]],
+ ["connection__node__node", ["connection_bc", "node_b", "node_c"]],
+ ["unit__node__node", ["unit_bc", "node_c", "node_b"]],
+ ["connection__node__node", ["connection_bc", "node_c", "node_b"]],
+ ],
+ :object_parameter_values => [
+ ["model", "instance", "model_start", Dict("type" => "date_time", "data" => "2000-01-01T00:00:00")],
+ ["model", "instance", "model_end", Dict("type" => "date_time", "data" => "2000-01-01T02:00:00")],
+ ["model", "instance", "duration_unit", "hour"],
+ ["model", "instance", "model_type", "spineopt_mga"],
+ ["temporal_block", "hourly", "resolution", Dict("type" => "duration", "data" => "1h")],
+ ["temporal_block", "two_hourly", "resolution", Dict("type" => "duration", "data" => "2h")],
+ ],
+ :relationship_parameter_values => [
+ [
+ "stochastic_structure__stochastic_scenario",
+ ["stochastic", "parent"],
+ "stochastic_scenario_end",
+ Dict("type" => "duration", "data" => "1h")
+ ],
+ ["connection__node__node", ["connection_ab", "node_b", "node_a"], "fix_ratio_out_in_connection_flow", 1.0],
+ ["connection__node__node", ["connection_ab", "node_a", "node_b"], "fix_ratio_out_in_connection_flow", 1.0],
+ ["connection__node__node", ["connection_bc", "node_c", "node_b"], "fix_ratio_out_in_connection_flow", 1.0],
+ ["connection__node__node", ["connection_bc", "node_b", "node_c"], "fix_ratio_out_in_connection_flow", 1.0],
+ ["unit__node__node", ["unit_ab", "node_b", "node_a"], "fix_ratio_out_in_unit_flow", 1.0],
+ ["unit__node__node", ["unit_ab", "node_a", "node_b"], "fix_ratio_out_in_unit_flow", 1.0],
+ ["unit__node__node", ["unit_bc", "node_c", "node_b"], "fix_ratio_out_in_unit_flow", 1.0],
+ ["unit__node__node", ["unit_bc", "node_b", "node_c"], "fix_ratio_out_in_unit_flow", 1.0],
+
+ ],
+ )
+ @testset "test mga algorithm" begin
+ _load_test_data(url_in, test_data)
+ candidate_units = 1
+ candidate_connections = 1
+ candidate_storages = 1
+ units_invested_big_m_mga = storages_invested_big_m_mga = connections_invested_big_m_mga = 5
+ fuel_cost = 5
+ mga_slack = 0.05
+ object_parameter_values = [
+ ["unit", "unit_ab", "candidate_units", candidate_units],
+ ["unit", "unit_bc", "candidate_units", candidate_units],
+ ["unit", "unit_ab", "number_of_units", 0],
+ ["unit", "unit_bc", "number_of_units", 0],
+ ["unit", "unit_group_abbc", "units_invested_mga", true],
+ ["unit", "unit_group_abbc", "units_invested_big_m_mga",units_invested_big_m_mga],
+ ["unit", "unit_group_abbc", "units_invested__mga_weight",1],
+ ["unit", "unit_ab", "unit_investment_cost",1],
+ ["connection", "connection_ab", "candidate_connections", candidate_connections],
+ ["connection", "connection_bc", "candidate_connections", candidate_connections],
+ ["connection", "connection_group_abbc", "connections_invested_mga", true],
+ ["connection", "connection_group_abbc", "connections_invested_big_m_mga",connections_invested_big_m_mga],
+ ["connection", "connection_group_abbc", "connections_invested_mga_weight",1],
+ ["node", "node_b", "candidate_storages", candidate_storages],
+ ["node", "node_c", "candidate_storages", candidate_storages],
+ ["node", "node_a", "balance_type", :balance_type_none],
+ ["node", "node_b", "has_state", true],
+ ["node", "node_c", "has_state", true],
+ ["node", "node_b", "fix_node_state",0],
+ ["node", "node_c", "fix_node_state",0],
+ ["node", "node_b", "node_state_cap", 0],
+ ["node", "node_c", "node_state_cap", 0],
+ ["node", "node_group_bc", "storages_invested_mga", true],
+ ["node", "node_group_bc","storages_invested_big_m_mga",storages_invested_big_m_mga],
+ ["node", "node_group_bc","storages_invested_mga_weight",1],
+ ["model", "instance", "model_type", "spineopt_mga"],
+ ["model", "instance", "mga_diff_relative", false],
+ ["model", "instance", "max_mga_slack", mga_slack],
+ ["model", "instance", "max_mga_iterations", 2],
+ # ["node", "node_a", "demand",1],
+ ["node", "node_b", "demand",1],
+ ["node", "node_c", "demand",1],
+ ]
+ relationship_parameter_values = [
+ ["unit__to_node", ["unit_ab", "node_b"], "unit_capacity", 5],
+ ["unit__to_node", ["unit_ab", "node_b"], "fuel_cost", fuel_cost],
+ ["unit__to_node", ["unit_bc", "node_c"], "unit_capacity", 5],
+ ["connection__to_node", ["connection_ab","node_b"], "connection_capacity",5],
+ ["connection__to_node", ["connection_bc","node_c"], "connection_capacity",5]
+ ]
+ SpineInterface.import_data(url_in; object_parameter_values=object_parameter_values, relationship_parameter_values=relationship_parameter_values)
+ m=run_spineopt(url_in; log_level=1)
+ var_units_invested = m.ext[:variables][:units_invested]
+ var_unit_flow = m.ext[:variables][:unit_flow]
+ var_connections_invested = m.ext[:variables][:connections_invested]
+ var_storages_invested = m.ext[:variables][:storages_invested]
+ var_mga_aux_diff = m.ext[:variables][:mga_aux_diff]
+ var_mga_aux_binary = m.ext[:variables][:mga_aux_binary]
+ var_mga_aux_objective = m.ext[:variables][:mga_objective]
+ mga_results = m.ext[:outputs]
+ t0 = SpineOpt._analysis_time(m)
+ @testset "test mga_diff_ub1" begin
+ constraint = m.ext[:constraints][:mga_diff_ub1]
+ @test length(constraint) == 6
+ scenarios = (stochastic_scenario(:parent), )
+ time_slices = time_slice(m; temporal_block=temporal_block(:two_hourly))
+ mga_current_iteration = SpineOpt.mga_iteration()[end-1]
+ @testset for (s, t) in zip(scenarios, time_slices)
+ key = (unit=unit(:unit_group_abbc),mga_iteration=mga_current_iteration)
+ key1 = (unit(:unit_ab), s, t)
+ key2 = (unit(:unit_bc), s, t)
+ var_u_inv_1 = var_units_invested[key1...]
+ var_u_inv_2 = var_units_invested[key2...]
+ prev_mga_results_1 = mga_results[:units_invested][(unit=unit(:unit_ab), stochastic_scenario=s, mga_iteration=mga_current_iteration)][t0.ref.x][t.start.x]
+ prev_mga_results_2 = mga_results[:units_invested][(unit=unit(:unit_bc), stochastic_scenario=s, mga_iteration=mga_current_iteration)][t0.ref.x][t.start.x]
+ expected_con = @build_constraint(
+ var_mga_aux_diff[key]
+ <= (var_u_inv_1 - prev_mga_results_1 + var_u_inv_2 - prev_mga_results_2)
+ + units_invested_big_m_mga*var_mga_aux_binary[key])
+ con = constraint[key...]
+ observed_con = constraint_object(con)
+ @test _is_constraint_equal(observed_con, expected_con)
+ end
+ @testset for (s, t) in zip(scenarios, time_slices)
+ key = (connection=connection(:connection_group_abbc),mga_iteration=mga_current_iteration)
+ key1 = (connection(:connection_ab), s, t)
+ key2 = (connection(:connection_bc), s, t)
+ var_u_inv_1 = var_connections_invested[key1...]
+ var_u_inv_2 = var_connections_invested[key2...]
+ prev_mga_results_1 = mga_results[:connections_invested][(connection=connection(:connection_ab), stochastic_scenario=s, mga_iteration=mga_current_iteration)][t0.ref.x][t.start.x]
+ prev_mga_results_2 = mga_results[:connections_invested][(connection=connection(:connection_bc), stochastic_scenario=s, mga_iteration=mga_current_iteration)][t0.ref.x][t.start.x]
+ expected_con = @build_constraint(
+ var_mga_aux_diff[key]
+ <= (var_u_inv_1 - prev_mga_results_1 + var_u_inv_2 - prev_mga_results_2)
+ + connections_invested_big_m_mga*var_mga_aux_binary[key])
+ con = constraint[key...]
+ observed_con = constraint_object(con)
+ @test _is_constraint_equal(observed_con, expected_con)
+ end
+ @testset for (s, t) in zip(scenarios, time_slices)
+ key = (node=node(:node_group_bc),mga_iteration=mga_current_iteration)
+ key1 = (node(:node_b), s, t)
+ key2 = (node(:node_c), s, t)
+ var_u_inv_1 = var_storages_invested[key1...]
+ var_u_inv_2 = var_storages_invested[key2...]
+ prev_mga_results_1 = mga_results[:storages_invested][(node=node(:node_b), stochastic_scenario=s, mga_iteration=mga_current_iteration)][t0.ref.x][t.start.x]
+ prev_mga_results_2 = mga_results[:storages_invested][(node=node(:node_c), stochastic_scenario=s, mga_iteration=mga_current_iteration)][t0.ref.x][t.start.x]
+ expected_con = @build_constraint(
+ var_mga_aux_diff[key]
+ <= (var_u_inv_1 - prev_mga_results_1 + var_u_inv_2 - prev_mga_results_2)
+ + storages_invested_big_m_mga*var_mga_aux_binary[key])
+ con = constraint[key...]
+ observed_con = constraint_object(con)
+ @test _is_constraint_equal(observed_con, expected_con)
+ end
+ #FIXME: add for connection and node
+ end
+ @testset "test mga_diff_ub2" begin
+ constraint = m.ext[:constraints][:mga_diff_ub2]
+ @test length(constraint) == 6
+ scenarios = (stochastic_scenario(:parent), )
+ time_slices = time_slice(m; temporal_block=temporal_block(:two_hourly))
+ mga_current_iteration = SpineOpt.mga_iteration()[end-1]
+ @testset for (s, t) in zip(scenarios, time_slices)
+ key = (unit=unit(:unit_group_abbc),mga_iteration=mga_current_iteration)
+ key1 = (unit(:unit_ab), s, t)
+ key2 = (unit(:unit_bc), s, t)
+ var_u_inv_1 = var_units_invested[key1...]
+ var_u_inv_2 = var_units_invested[key2...]
+ t0 = SpineOpt._analysis_time(m)
+ prev_mga_results_1 = mga_results[:units_invested][(unit=unit(:unit_ab), stochastic_scenario=s, mga_iteration=mga_current_iteration)][t0.ref.x][t.start.x]
+ prev_mga_results_2 = mga_results[:units_invested][(unit=unit(:unit_bc), stochastic_scenario=s, mga_iteration=mga_current_iteration)][t0.ref.x][t.start.x]
+ expected_con = @build_constraint(
+ var_mga_aux_diff[key]
+ <= -(var_u_inv_1 - prev_mga_results_1 + var_u_inv_2 - prev_mga_results_2)
+ + units_invested_big_m_mga*(1-var_mga_aux_binary[key]))
+ con = constraint[key...]
+ observed_con = constraint_object(con)
+ @test _is_constraint_equal(observed_con, expected_con)
+ end
+ @testset for (s, t) in zip(scenarios, time_slices)
+ key = (connection=connection(:connection_group_abbc),mga_iteration=mga_current_iteration)
+ key1 = (connection(:connection_ab), s, t)
+ key2 = (connection(:connection_bc), s, t)
+ var_u_inv_1 = var_connections_invested[key1...]
+ var_u_inv_2 = var_connections_invested[key2...]
+ t0 = SpineOpt._analysis_time(m)
+ prev_mga_results_1 = mga_results[:connections_invested][(connection=connection(:connection_ab), stochastic_scenario=s, mga_iteration=mga_current_iteration)][t0.ref.x][t.start.x]
+ prev_mga_results_2 = mga_results[:connections_invested][(connection=connection(:connection_bc), stochastic_scenario=s, mga_iteration=mga_current_iteration)][t0.ref.x][t.start.x]
+ expected_con = @build_constraint(
+ var_mga_aux_diff[key]
+ <= -(var_u_inv_1 - prev_mga_results_1 + var_u_inv_2 - prev_mga_results_2)
+ + connections_invested_big_m_mga*(1-var_mga_aux_binary[key]))
+ con = constraint[key...]
+ observed_con = constraint_object(con)
+ @test _is_constraint_equal(observed_con, expected_con)
+ end
+ @testset for (s, t) in zip(scenarios, time_slices)
+ key = (node=node(:node_group_bc),mga_iteration=mga_current_iteration)
+ key1 = (node(:node_b), s, t)
+ key2 = (node(:node_c), s, t)
+ var_u_inv_1 = var_storages_invested[key1...]
+ var_u_inv_2 = var_storages_invested[key2...]
+ t0 = SpineOpt._analysis_time(m)
+ prev_mga_results_1 = mga_results[:storages_invested][(node=node(:node_b), stochastic_scenario=s, mga_iteration=mga_current_iteration)][t0.ref.x][t.start.x]
+ prev_mga_results_2 = mga_results[:storages_invested][(node=node(:node_c), stochastic_scenario=s, mga_iteration=mga_current_iteration)][t0.ref.x][t.start.x]
+ expected_con = @build_constraint(
+ var_mga_aux_diff[key]
+ <= -(var_u_inv_1 - prev_mga_results_1 + var_u_inv_2 - prev_mga_results_2)
+ + storages_invested_big_m_mga*(1-var_mga_aux_binary[key]))
+ con = constraint[key...]
+ observed_con = constraint_object(con)
+ @test _is_constraint_equal(observed_con, expected_con)
+ end
+ #FIXME: add for connection and node
+ end
+ @testset "test mga_diff_lb1" begin
+ constraint = m.ext[:constraints][:mga_diff_lb1]
+ @test length(constraint) == 6
+ scenarios = (stochastic_scenario(:parent), )
+ time_slices = time_slice(m; temporal_block=temporal_block(:two_hourly))
+ mga_current_iteration = SpineOpt.mga_iteration()[end-1]
+ @testset for (s, t) in zip(scenarios, time_slices)
+ key = (unit=unit(:unit_group_abbc),mga_iteration=mga_current_iteration)
+ key1 = (unit(:unit_ab), s, t)
+ key2 = (unit(:unit_bc), s, t)
+ var_u_inv_1 = var_units_invested[key1...]
+ var_u_inv_2 = var_units_invested[key2...]
+ t0 = SpineOpt._analysis_time(m)
+ prev_mga_results_1 = mga_results[:units_invested][(unit=unit(:unit_ab), stochastic_scenario=s, mga_iteration=mga_current_iteration)][t0.ref.x][t.start.x]
+ prev_mga_results_2 = mga_results[:units_invested][(unit=unit(:unit_bc), stochastic_scenario=s, mga_iteration=mga_current_iteration)][t0.ref.x][t.start.x]
+ expected_con = @build_constraint(
+ var_mga_aux_diff[key]
+ >= (var_u_inv_1 - prev_mga_results_1 + var_u_inv_2 - prev_mga_results_2))
+ con = constraint[key...]
+ observed_con = constraint_object(con)
+ @test _is_constraint_equal(observed_con, expected_con)
+ end
+ @testset for (s, t) in zip(scenarios, time_slices)
+ key = (connection=connection(:connection_group_abbc),mga_iteration=mga_current_iteration)
+ key1 = (connection(:connection_ab), s, t)
+ key2 = (connection(:connection_bc), s, t)
+ var_u_inv_1 = var_connections_invested[key1...]
+ var_u_inv_2 = var_connections_invested[key2...]
+ t0 = SpineOpt._analysis_time(m)
+ prev_mga_results_1 = mga_results[:connections_invested][(connection=connection(:connection_ab), stochastic_scenario=s, mga_iteration=mga_current_iteration)][t0.ref.x][t.start.x]
+ prev_mga_results_2 = mga_results[:connections_invested][(connection=connection(:connection_bc), stochastic_scenario=s, mga_iteration=mga_current_iteration)][t0.ref.x][t.start.x]
+ expected_con = @build_constraint(
+ var_mga_aux_diff[key]
+ >= (var_u_inv_1 - prev_mga_results_1 + var_u_inv_2 - prev_mga_results_2))
+ con = constraint[key...]
+ observed_con = constraint_object(con)
+ @test _is_constraint_equal(observed_con, expected_con)
+ end
+ @testset for (s, t) in zip(scenarios, time_slices)
+ key = (node=node(:node_group_bc),mga_iteration=mga_current_iteration)
+ key1 = (node(:node_b), s, t)
+ key2 = (node(:node_c), s, t)
+ var_u_inv_1 = var_storages_invested[key1...]
+ var_u_inv_2 = var_storages_invested[key2...]
+ t0 = SpineOpt._analysis_time(m)
+ prev_mga_results_1 = mga_results[:storages_invested][(node=node(:node_b), stochastic_scenario=s, mga_iteration=mga_current_iteration)][t0.ref.x][t.start.x]
+ prev_mga_results_2 = mga_results[:storages_invested][(node=node(:node_c), stochastic_scenario=s, mga_iteration=mga_current_iteration)][t0.ref.x][t.start.x]
+ expected_con = @build_constraint(
+ var_mga_aux_diff[key]
+ >= (var_u_inv_1 - prev_mga_results_1 + var_u_inv_2 - prev_mga_results_2))
+ con = constraint[key...]
+ observed_con = constraint_object(con)
+ @test _is_constraint_equal(observed_con, expected_con)
+ end
+ #FIXME: add for connection and node
+ end
+ @testset "test mga_diff_lb2" begin
+ constraint = m.ext[:constraints][:mga_diff_lb2]
+ @test length(constraint) == 6
+ scenarios = (stochastic_scenario(:parent), )
+ time_slices = time_slice(m; temporal_block=temporal_block(:two_hourly))
+ mga_current_iteration = SpineOpt.mga_iteration()[end-1]
+ @testset for (s, t) in zip(scenarios, time_slices)
+ key = (unit=unit(:unit_group_abbc),mga_iteration=mga_current_iteration)
+ key1 = (unit(:unit_ab), s, t)
+ key2 = (unit(:unit_bc), s, t)
+ var_u_inv_1 = var_units_invested[key1...]
+ var_u_inv_2 = var_units_invested[key2...]
+ t0 = SpineOpt._analysis_time(m)
+ prev_mga_results_1 = mga_results[:units_invested][(unit=unit(:unit_ab), stochastic_scenario=s, mga_iteration=mga_current_iteration)][t0.ref.x][t.start.x]
+ prev_mga_results_2 = mga_results[:units_invested][(unit=unit(:unit_bc), stochastic_scenario=s, mga_iteration=mga_current_iteration)][t0.ref.x][t.start.x]
+ expected_con = @build_constraint(
+ var_mga_aux_diff[key]
+ >= -(var_u_inv_1 - prev_mga_results_1 + var_u_inv_2 - prev_mga_results_2))
+ con = constraint[key...]
+ observed_con = constraint_object(con)
+ @test _is_constraint_equal(observed_con, expected_con)
+ end
+ @testset for (s, t) in zip(scenarios, time_slices)
+ key = (connection=connection(:connection_group_abbc),mga_iteration=mga_current_iteration)
+ key1 = (connection(:connection_ab), s, t)
+ key2 = (connection(:connection_bc), s, t)
+ var_u_inv_1 = var_connections_invested[key1...]
+ var_u_inv_2 = var_connections_invested[key2...]
+ t0 = SpineOpt._analysis_time(m)
+ prev_mga_results_1 = mga_results[:connections_invested][(connection=connection(:connection_ab), stochastic_scenario=s, mga_iteration=mga_current_iteration)][t0.ref.x][t.start.x]
+ prev_mga_results_2 = mga_results[:connections_invested][(connection=connection(:connection_bc), stochastic_scenario=s, mga_iteration=mga_current_iteration)][t0.ref.x][t.start.x]
+ expected_con = @build_constraint(
+ var_mga_aux_diff[key]
+ >= -(var_u_inv_1 - prev_mga_results_1 + var_u_inv_2 - prev_mga_results_2))
+ con = constraint[key...]
+ observed_con = constraint_object(con)
+ @test _is_constraint_equal(observed_con, expected_con)
+ end
+ @testset for (s, t) in zip(scenarios, time_slices)
+ key = (node=node(:node_group_bc),mga_iteration=mga_current_iteration)
+ key1 = (node(:node_b), s, t)
+ key2 = (node(:node_c), s, t)
+ var_u_inv_1 = var_storages_invested[key1...]
+ var_u_inv_2 = var_storages_invested[key2...]
+ t0 = SpineOpt._analysis_time(m)
+ prev_mga_results_1 = mga_results[:storages_invested][(node=node(:node_b), stochastic_scenario=s, mga_iteration=mga_current_iteration)][t0.ref.x][t.start.x]
+ prev_mga_results_2 = mga_results[:storages_invested][(node=node(:node_c), stochastic_scenario=s, mga_iteration=mga_current_iteration)][t0.ref.x][t.start.x]
+ expected_con = @build_constraint(
+ var_mga_aux_diff[key]
+ >= -(var_u_inv_1 - prev_mga_results_1 + var_u_inv_2 - prev_mga_results_2))
+ con = constraint[key...]
+ observed_con = constraint_object(con)
+ @test _is_constraint_equal(observed_con, expected_con)
+ end
+ #FIXME: add for connection and node
+ end
+ @testset "test mga_slack_constraint" begin
+ constraint = m.ext[:constraints][:mga_slack_constraint]
+ @test length(constraint) == 1
+ scenarios = (stochastic_scenario(:parent), )
+ time_slices = time_slice(m; temporal_block=temporal_block(:two_hourly))
+ mga_first_iteration = SpineOpt.mga_iteration()[1]
+ mga_current_iteration = SpineOpt.mga_iteration()[end-1]
+ first_obj_result = m.ext[:outputs][:total_costs][(model=model(:instance),mga_iteration = mga_first_iteration)]
+ @testset for (s, t) in zip(scenarios, time_slices)
+ key1 = (unit(:unit_ab), s, t)
+ key2 = (unit(:unit_ab), node(:node_b), direction(:to_node), s, t)
+ var_u_inv_1 = var_units_invested[key1...]
+ var_u_inv_2 = var_unit_flow[key2...]
+ expected_con = @build_constraint(
+ var_u_inv_2*2*fuel_cost + var_u_inv_1
+ <= first_obj_result[t0.ref.x][t.start.x]*(1+mga_slack))
+ con = constraint[model(:instance)]
+ observed_con = constraint_object(con)
+ @test _is_constraint_equal(observed_con, expected_con)
+ end
+ #FIXME: add for connection and node
+ end
+ @testset "test mga_objective_ub" begin
+ constraint = m.ext[:constraints][:mga_objective_ub]
+ @test length(constraint) == 2
+ scenarios = (stochastic_scenario(:parent), )
+ t = SpineOpt.current_window(m)
+ var_mga_objective = m.ext[:variables][:mga_objective]
+ mga_current_iteration = SpineOpt.mga_iteration()[end-1]
+ key1 = (unit=unit(:unit_group_abbc),mga_iteration=mga_current_iteration)
+ key2 = (connection=connection(:connection_group_abbc),mga_iteration=mga_current_iteration)
+ key3 = (node=node(:node_group_bc),mga_iteration=mga_current_iteration)
+ key4 = (model = model(:instance),t=t)
+ mga_aux_diff_1 = var_mga_aux_diff[key1]
+ mga_aux_diff_2 = var_mga_aux_diff[key2]
+ mga_aux_diff_3 = var_mga_aux_diff[key3]
+ var_mga_objective1 = var_mga_objective[key4]
+ expected_con = @build_constraint(
+ var_mga_objective1
+ <=
+ mga_aux_diff_1 + mga_aux_diff_2 + mga_aux_diff_3)
+ con = constraint[mga_current_iteration]
+ observed_con = constraint_object(con)
+ @test _is_constraint_equal(observed_con, expected_con)
+ end
+ end
+end
diff --git a/test/data_structure/preprocess_data_structure.jl b/test/data_structure/preprocess_data_structure.jl
index 97c93c5f19..e84af5a360 100644
--- a/test/data_structure/preprocess_data_structure.jl
+++ b/test/data_structure/preprocess_data_structure.jl
@@ -146,7 +146,7 @@ end
["model", "instance", "model_start", Dict("type" => "date_time", "data" => "2000-01-01T00:00:00")],
["model", "instance", "model_end", Dict("type" => "date_time", "data" => "2000-01-01T02:00:00")],
["model", "instance", "duration_unit", "hour"],
- ["model", "instance", "model_type", "spineopt_operations"],
+ ["model", "instance", "model_type", "spineopt_standard"],
["model", "master", "model_start", Dict("type" => "date_time", "data" => "2000-01-01T00:00:00")],
["model", "master", "model_end", Dict("type" => "date_time", "data" => "2000-01-01T02:00:00")],
["model", "master", "duration_unit", "hour"],
diff --git a/test/data_structure/temporal_structure.jl b/test/data_structure/temporal_structure.jl
index 0d661a8c36..15c5a2522e 100644
--- a/test/data_structure/temporal_structure.jl
+++ b/test/data_structure/temporal_structure.jl
@@ -23,7 +23,7 @@ function _is_time_slice_set_equal(ts_a, ts_b)
length(ts_a) == length(ts_b) && all(_is_time_slice_equal(a, b) for (a, b) in zip(sort(ts_a), sort(ts_b)))
end
-function _model(model_type=:spineopt_operations)
+function _model(model_type=:spineopt_standard)
m = Model()
m.ext[:instance] = first(model(model_type=model_type))
m
@@ -82,15 +82,15 @@ end
m_start = model_start(model=m.ext[:instance])
for t in SpineOpt.time_slice(m, temporal_block=temporal_block(:block_a))
t_end = end_(t)
- if t_end <= m_start + Hour(4)
+ if t_end <= m_start + Hour(4)
@test SpineOpt.representative_time_slice(m, t) == rep_blk1_ts[1]
- elseif t_end <= m_start + Hour(8)
+ elseif t_end <= m_start + Hour(8)
@test SpineOpt.representative_time_slice(m, t) == t
- elseif t_end <= m_start + Hour(12)
+ elseif t_end <= m_start + Hour(12)
@test SpineOpt.representative_time_slice(m, t) == rep_blk2_ts[1]
- elseif t_end <= m_start + Hour(16)
+ elseif t_end <= m_start + Hour(16)
@test SpineOpt.representative_time_slice(m, t) == rep_blk2_ts[2]
- elseif t_end <= m_start + Hour(20)
+ elseif t_end <= m_start + Hour(20)
@test SpineOpt.representative_time_slice(m, t) == rep_blk2_ts[3]
else
@test SpineOpt.representative_time_slice(m, t) == t
diff --git a/test/run_spineopt.jl b/test/run_spineopt.jl
index a52952bd4d..ff3ac26f35 100644
--- a/test/run_spineopt.jl
+++ b/test/run_spineopt.jl
@@ -461,10 +461,10 @@ end
object_parameter_values = [
["node", "node_b", "demand", demand],
- ["model", "instance", "model_type", "spineopt_operations"],
+ ["model", "instance", "model_type", "spineopt_standard"],
["model", "instance", "db_mip_solver_options", mip_solver_options],
["model", "instance", "db_lp_solver_options", lp_solver_options],
- ["model", "master_instance", "model_type", "spineopt_master"],
+ ["model", "master_instance", "model_type", "spineopt_benders_master"],
["model", "master_instance", "db_mip_solver_options", mip_solver_options_master],
["model", "master_instance", "db_lp_solver_options", lp_solver_options_master],
["model", "master_instance", "db_mip_solver", "Cbc.jl"],
diff --git a/test/runtests.jl b/test/runtests.jl
index d79cc50e79..c6fbbfc190 100644
--- a/test/runtests.jl
+++ b/test/runtests.jl
@@ -40,7 +40,7 @@ import SpineOpt:
node_stochastic_time_indices,
unit_stochastic_time_indices,
node_investment_dynamic_time_indices,
- rerun_spineopt_mp
+ rerun_spineopt!
# Test code uses legacy syntax for `import_data`, so interpret here.
SpineInterface.import_data(db_url::String; kwargs...) = SpineInterface.import_data(db_url, Dict(kwargs...), "testing")
@@ -87,9 +87,11 @@ end
@testset begin
include("data_structure/check_data_structure.jl")
+ # include("data_structure/migration.jl") #FIXME: we should have this in the future
include("data_structure/preprocess_data_structure.jl")
include("data_structure/temporal_structure.jl")
include("data_structure/stochastic_structure.jl")
+ include("data_structure/algorithm_mga_structure.jl")
include("constraints/constraint_unit.jl")
include("constraints/constraint_node.jl")
include("constraints/constraint_connection.jl")