Skip to content

Commit

Permalink
update with the latest main (#1138)
Browse files Browse the repository at this point in the history
  • Loading branch information
nnhjy authored Nov 14, 2024
2 parents a8382c5 + bb5c565 commit 01a82e3
Show file tree
Hide file tree
Showing 10 changed files with 187 additions and 32 deletions.
5 changes: 4 additions & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "SpineOpt"
uuid = "0d8fc150-4032-4b6e-9540-20efcb304861"
authors = ["Spine Project consortium <[email protected]>"]
version = "0.8.3"
version = "0.9.1"

[deps]
Arrow = "69666777-d1a9-59fb-9406-91d4454c9d45"
Expand All @@ -20,6 +20,9 @@ SpineInterface = "0cda1612-498a-11e9-3c92-77fa82595a4f"
TOML = "fa267f1f-6049-4f14-aa54-33bafae1ed76"

[compat]
Arrow = "2"
DataStructures = "0.18"
Distributions = "0.25"
HiGHS = "1"
JSON = "0.21"
JuMP = "^1.20"
Expand Down
5 changes: 3 additions & 2 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@ pages = [
"Introduction" => "index.md",
"Getting Started" => Any[
"Installation" => joinpath("getting_started", "installation.md"),
"Recommended workflow" => joinpath("getting_started", "recommended_workflow.md"),
"Verify installation" => joinpath("getting_started", "recommended_workflow.md"),
"Troubleshooting" => joinpath("getting_started", "troubleshooting.md"),
],
"Tutorials" => Any[
"Webinars and examples" => joinpath("tutorial", "webinars.md"),
"Webinars" => joinpath("tutorial", "webinars.md"),
"Simple system" => joinpath("tutorial", "simple_system.md"),
"Temporal resolution" => joinpath("tutorial", "temporal_resolution.md"),
"Stochastic structure" => joinpath("tutorial", "stochastic_system.md"),
Expand All @@ -49,6 +49,7 @@ pages = [
"Unit Commitment" => joinpath("tutorial", "unit_commitment.md"),
],
"How to" => [],
"Example gallery" => joinpath("gallery", "gallery.md"),
"Database structure" => Any[
"Basics of the data structure" => joinpath("concept_reference", "the_basics.md"),
"Archetypes" => joinpath("concept_reference", "archetypes.md"),
Expand Down
22 changes: 22 additions & 0 deletions docs/src/gallery/gallery.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Example gallery

## How to run the examples

To run the examples, we recommend to follow the instructions [here](https://spine-tools.github.io/SpineOpt.jl/latest/getting_started/recommended_workflow/) to set up the basic SpineToolbox workflow, loading the JSON example file in your input data store depending on the example you want to run. We recommend creating a new SpineToolbox project for each example.

## Examples

Each example has a link to the JSON file with the input data.

- [**Simple system**](https://github.com/spine-tools/SpineOpt.jl/blob/master/examples/simple_system.json): Example with two nodes and units, including the relationships among them for a temporal block with duration of one day.
- [**Stochastic model**](https://github.com/spine-tools/SpineOpt.jl/blob/master/examples/stochastic.json): Setup of stochastic structures for three forecast scenarios.
- [**Capacity planning**](https://github.com/spine-tools/SpineOpt.jl/blob/master/examples/capacity_planning.json): This example shows the definition of investment and operational temporal blocks in SpineOpt for one target year.
- **Multi-year investment examples**: The following examples have a setup for a multi-year investment case study (e.g., for pathway planning) in a five-year time horizon with investments every five years (e.g., at the beginning and end of the time horizon) with a 4-month operational temporal block duration. Here, we have different options in SpineOpt:
- [*Example without economic parameters calculation*](https://github.com/spine-tools/SpineOpt.jl/blob/master/examples/multi-year_investment_without_econ_params.json): No extra manipulation of the input data from SpineOpt (`use_economic_represention=false`). So, the user input data needs to account for the value of money over time according to discount rate and year.
- [*Example with economic parameters calculation without milestone years*](https://github.com/spine-tools/SpineOpt.jl/blob/master/examples/multi-year_investment_with_econ_params_without_milestones.json): SpineOpt internally-calculates the parameters for discounting investment and operation costs given a discount rate, discount year, and lifetime information (`use_economic_represention=true`).
- [*Example with economic parameters calculation with milestone years*](https://github.com/spine-tools/SpineOpt.jl/blob/master/examples/multi-year_investment_with_econ_params_with_milestones.json): Since solving five years of operation might be computational intensive, SpineOpt offers the option of having milestone years (`use_milestone_years=true`). In that case, only two years are solved in the model. Still, one represents the operation of the non-milestone years using a calculated weight, including the discounted operation costs.
- [**Reserves constraints**](https://github.com/spine-tools/SpineOpt.jl/blob/master/examples/reserves.json): Extension of the simple system including operating reserve constraints.
- [**Unit commitment constraints**](https://github.com/spine-tools/SpineOpt.jl/blob/master/examples/unit_commitment.json): Extension of the sinple system including unit commitment constraints.
- [**Rolling horizon**](https://github.com/spine-tools/SpineOpt.jl/blob/master/examples/rolling_horizon.json): This is an Example with a total time horizon of one week, an optimization window of one day, and rolling forward one day at a time.

Some archived examples are also in the spine tools repository, e.g. [case study A5](https://github.com/spine-tools/spine-cs-a5).
4 changes: 1 addition & 3 deletions docs/src/getting_started/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,7 @@ Pkg.activate("jenv") # activate the virtual environment in the current working d
To install SpineOpt, execute the following commands in the Julia REPL:
```julia
import Pkg # not needed if you are still in the same REPL session
Pkg.Registry.add("General")
Pkg.Registry.add(Pkg.RegistrySpec(url = "https://github.com/spine-tools/SpineJuliaRegistry")) # Add SpineJuliaRegistry as an available registry for your Julia
Pkg.add("SpineOpt") # Install SpineOpt from the SpineJuliaRegistry
Pkg.add("SpineOpt")
```

The SpineOpt package is now available and ready to use in your julia scripts. If you have installed SpineOpt in a virtual environment, don't forget to activate the virtual environment when you try to run your scripts.
Expand Down
2 changes: 1 addition & 1 deletion docs/src/getting_started/recommended_workflow.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# [Recommended Workflow](@id recommended_workflow)
# [Verify installation](@id recommended_workflow)

Now that we've installed Spine Toolbox and SpineOpt, let's make sure that everything truly works by running an example. We'll be using an existing example to ensure that any issues we may encounter at this point are related to the installation. If you indeed encounter any problems, check the [troubleshooting](@ref troubleshooting) section. On the other hand, if you are able to successfully complete this example, you can continue to a first hands on experience with the tutorials.

Expand Down
5 changes: 5 additions & 0 deletions docs/src/getting_started/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ Have you created a new project? File > New project
## I get an error that the 'model' object is not defined in the database
That tends to happen when you accidentally switched your input and output in the Run SpineOpt tool.

## Solver throws error `Name too long`
Variables and constraints names in SpineOpt sometimes can be longer than the maximum name length the solvers allow. If you get this error, we recommend using the solver parameters to run it ingnoring the names. For instance, Gurobi has the parameter `IgnoreNames` which allows to run without the names but returning the solution with the original names in the model.

To setup the solver parameters you can look at the [How-to section](@ref how-to-change-solver) guide change the solver and define its parameters.

## SpineOpt and SpineInterface are out of sync
Some of the development of SpineOpt depends on the development of SpineInterface and vice versa. At some points in time that can create an incompatibility between the two.

Expand Down
2 changes: 1 addition & 1 deletion docs/src/how_to/change_the_solver.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# How to change the solver
# [How to change the solver](@id how-to-change-solver)

If you want to change the solver for your optimization problem in SpineOpt, here is some guidance:
- You can change the solvers in your input datastore using the `db_lp_solver` and `db_mip_solver` parameter values of the `model` object.
Expand Down
8 changes: 3 additions & 5 deletions docs/src/tutorial/webinars.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
# Webinars and examples
# Webinars

[SpinOpt tutorial](https://www.youtube.com/watch?v=brf6rlzNDR0) covers the entire process from installing Spine Toolbox and SpineOpt, creating and running a model with these tools and manipulating databases.

[Spine Toolbox: Case study of Unit Commitment](https://www.youtube.com/watch?v=IYIEDyBsSvk) is another nice example by Alireza Zakariazadeh ([email protected]).

There are also some archived examples in the spine tools repository, e.g. [case study A5](https://github.com/spine-tools/spine-cs-a5).
[Spine Toolbox: Case study of Unit Commitment](https://www.youtube.com/watch?v=IYIEDyBsSvk) is another nice webinar by Alireza Zakariazadeh (`[email protected]`).

!!! tip
Looking for the Hydro tutorial? It is now part of the how to section.
Looking for the Hydro tutorial? It is now part of the how to section.
9 changes: 8 additions & 1 deletion src/objective/fixed_om_costs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,14 @@ function fixed_om_costs(m, t_range)
# an extended period of time with a weight >=1, e.g. a representative month represents 3 months.
* duration(t)
for (u, ng, d) in indices(unit_capacity; unit=indices(fom_cost))
for (u, _n, _d, s, t) in unit_flow_indices(m; unit=u, node=ng, direction=d, t=t_range);
for (u, s, t) in Iterators.flatten(
u in intersect(indices(candidate_units), members(u)) ?
(units_invested_available_indices(m; unit=u, t=t_range),) :
((
(u, s, t) for (u, _n, _d, s, t)
in unit_flow_indices(m; unit=u, node=ng, direction=d, t=t_range)
),)
);
init=0,
)
)
Expand Down
157 changes: 139 additions & 18 deletions test/objective/objective.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#############################################################################

@testset "objective" begin
function _test_objective_setup()
url_in = "sqlite://"
test_data = Dict(
:objects => [
Expand Down Expand Up @@ -66,8 +66,13 @@
Dict("type" => "duration", "data" => "1h"),
]],
)
@testset "fixed_om_costs" begin
_load_test_data(url_in, test_data)
_load_test_data(url_in, test_data)
url_in
end

function test_fom_cost_case1()
@testset "fom_cost case1" begin
url_in = _test_objective_setup()
unit_capacity = 100
fom_cost = 8
number_of_units = 2
Expand Down Expand Up @@ -104,8 +109,85 @@
observed_obj = objective_function(m)
@test observed_obj == expected_obj
end
@testset "fuel_costs" begin
_load_test_data(url_in, test_data)
end

function test_fom_cost_case2()
@testset "fom_cost case2" begin
url_in = _test_objective_setup()
unit_capacity = 100
fom_cost = 8
number_of_units = 0
# The template provides the parameter `number_of_units` with a default value of 1.
candidate_units = 3
object_parameter_values = [
["unit", "unit_ab", "fom_cost", fom_cost],
["unit", "unit_ab", "number_of_units", number_of_units],
["unit", "unit_ab", "candidate_units", candidate_units],
]
relationships = [
["unit__investment_temporal_block", ["unit_ab", "hourly"]],
["unit__investment_stochastic_structure", ["unit_ab", "stochastic"]],
]
relationship_parameter_values = [
["unit__to_node", ["unit_ab", "node_b"], "unit_capacity", unit_capacity],
]
SpineInterface.import_data(
url_in;
relationships=relationships,
object_parameter_values=object_parameter_values,
relationship_parameter_values=relationship_parameter_values
)
m = run_spineopt(url_in; log_level=0, optimize=false)
var_units_invested_available = m.ext[:spineopt].variables[:units_invested_available]

duration = length(time_slice(m; temporal_block=temporal_block(:two_hourly)))
scenarios = (stochastic_scenario(:parent), stochastic_scenario(:child))
time_slices = time_slice(m; temporal_block=temporal_block(:hourly))
expected_obj = fom_cost * unit_capacity * duration *
sum(
(number_of_units + var_units_invested_available[unit(:unit_ab), s, t])
for (s, t) in zip(scenarios, time_slices)
)
observed_obj = objective_function(m)
@test observed_obj == expected_obj
end
end

function test_fom_cost_case3()
@testset "fom_cost case3" begin
url_in = _test_objective_setup()
unit_capacity = 100
fom_cost = 8
number_of_units = 2
object_parameter_values = [
["unit", "unit_ab", "fom_cost", fom_cost],
["unit", "unit_ab", "number_of_units", number_of_units],
]
relationship_parameter_values = [
["unit__to_node", ["unit_ab", "node_b"], "unit_capacity", unit_capacity],
]
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)

duration = length(time_slice(m; temporal_block=temporal_block(:two_hourly)))
scenarios = (stochastic_scenario(:parent), stochastic_scenario(:child))
time_slices = time_slice(m; temporal_block=temporal_block(:hourly))
expected_obj = fom_cost * unit_capacity * duration *
sum(
number_of_units for (s, t) in zip(scenarios, time_slices)
)
observed_obj = objective_function(m)
@test observed_obj == expected_obj
end
end

function test_fuel_cost()
@testset "fuel_cost" begin
url_in = _test_objective_setup()
fuel_cost = 125
relationship_parameter_values = [["unit__to_node", ["unit_ab", "node_b"], "fuel_cost", fuel_cost]]
SpineInterface.import_data(url_in; relationship_parameter_values=relationship_parameter_values)
Expand All @@ -119,8 +201,11 @@
expected_obj = fuel_cost * sum(unit_flow[(key..., s, t)...] for (s, t) in zip(scenarios, time_slices))
@test observed_obj == expected_obj
end
@testset "investment_costs" begin
_load_test_data(url_in, test_data)
end

function test_unit_investment_cost()
@testset "unit_investment_cost" begin
url_in = _test_objective_setup()
unit_investment_cost = 1000
candidate_units = 3
object_parameter_values = [
Expand All @@ -142,8 +227,11 @@
)
@test observed_obj == expected_obj
end
end

function test_node_slack_penalty()
@testset "node_slack_penalty" begin
_load_test_data(url_in, test_data)
url_in = _test_objective_setup()
node_a_slack_penalty = 0.6
node_b_slack_penalty = 0.4
object_parameter_values = [
Expand Down Expand Up @@ -172,8 +260,11 @@
)
@test observed_obj == expected_obj
end
end

function test_user_constraint_slack_penalty()
@testset "user_constraint_slack_penalty" begin
_load_test_data(url_in, test_data)
url_in = _test_objective_setup()
uc_slack_penalty = 0.6
objects = [["user_constraint", "ucx"]]
relationships = [["node__user_constraint", ["node_a", "ucx"]]]
Expand All @@ -200,8 +291,11 @@
)
@test observed_obj == expected_obj
end
@testset "shut_down_costs" begin
_load_test_data(url_in, test_data)
end

function test_shut_down_cost()
@testset "shut_down_cost" begin
url_in = _test_objective_setup()
shut_down_cost = 180
object_parameter_values = [["unit", "unit_ab", "shut_down_cost", shut_down_cost]]
SpineInterface.import_data(url_in; object_parameter_values=object_parameter_values)
Expand All @@ -215,8 +309,11 @@
expected_obj = shut_down_cost * units_shut_down[unit(:unit_ab), s_parent, t2h]
@test observed_obj == expected_obj
end
@testset "start_up_costs" begin
_load_test_data(url_in, test_data)
end

function test_start_up_cost()
@testset "start_up_cost" begin
url_in = _test_objective_setup()
start_up_cost = 220
object_parameter_values = [["unit", "unit_ab", "start_up_cost", start_up_cost]]
SpineInterface.import_data(url_in; object_parameter_values=object_parameter_values)
Expand All @@ -230,8 +327,11 @@
expected_obj = start_up_cost * units_started_up[unit(:unit_ab), s_parent, t2h]
@test observed_obj == expected_obj
end
end

function test_vom_cost()
@testset "vom_cost" begin
_load_test_data(url_in, test_data)
url_in = _test_objective_setup()
vom_cost = 150
relationship_parameter_values = [["unit__to_node", ["unit_ab", "node_b"], "vom_cost", vom_cost]]
SpineInterface.import_data(url_in; relationship_parameter_values=relationship_parameter_values)
Expand All @@ -245,8 +345,11 @@
expected_obj = vom_cost * sum(unit_flow[(key..., s, t)...] for (s, t) in zip(scenarios, time_slices))
@test observed_obj == expected_obj
end
@testset "connection_flow_costs" begin
_load_test_data(url_in, test_data)
end

function test_connection_flow_cost()
@testset "connection_flow_cost" begin
url_in = _test_objective_setup()
connection_flow_cost = 185
objects = [["connection", "connection_ab"]]
relationships = [["connection__to_node", ["connection_ab", "node_b"]]]
Expand All @@ -271,8 +374,11 @@
)
@test observed_obj == expected_obj
end
@testset "units_on_costs" begin
_load_test_data(url_in, test_data)
end

function test_units_on_cost()
@testset "units_on_cost" begin
url_in = _test_objective_setup()
units_on_cost = 913
object_parameter_values = [["unit", "unit_ab", "units_on_cost", units_on_cost]]
SpineInterface.import_data(url_in; object_parameter_values=object_parameter_values)
Expand All @@ -285,3 +391,18 @@
@test observed_obj == expected_obj
end
end

@testset "objective" begin
test_fom_cost_case1()
test_fom_cost_case2()
test_fom_cost_case3()
test_fuel_cost()
test_unit_investment_cost()
test_node_slack_penalty()
test_user_constraint_slack_penalty()
test_shut_down_cost()
test_start_up_cost()
test_vom_cost()
test_connection_flow_cost()
test_units_on_cost()
end

0 comments on commit 01a82e3

Please sign in to comment.