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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .JuliaFormatter.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@

always_for_in = true
always_use_return = true
margin = 80
margin = 92
remove_extra_newlines = true
short_to_long_function_def = true
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ jobs:
- version: '1.9' # 1.9
os: ubuntu-latest
arch: x86
- version: 'nightly'
os: ubuntu-latest
arch: x64
# - version: 'nightly'
# os: ubuntu-latest
# arch: x64
steps:
- uses: actions/checkout@v3
- uses: julia-actions/setup-julia@v1
Expand Down
9 changes: 2 additions & 7 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,7 @@ pages = [
"Internal reference" => "reference/internal.md",
]

DocMeta.setdocmeta!(
TimeStruct,
:DocTestSetup,
:(using TimeStruct);
recursive = true,
)
DocMeta.setdocmeta!(TimeStruct, :DocTestSetup, :(using TimeStruct); recursive = true)

Documenter.makedocs(
sitename = "TimeStruct",
Expand All @@ -28,7 +23,7 @@ Documenter.makedocs(
assets = String[],
),
doctest = false,
#modules = [TimeStruct],
modules = [TimeStruct],
pages = pages,
)

Expand Down
8 changes: 3 additions & 5 deletions docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

Welcome to the documentation of the TimeStruct package!


## What is TimeStruct?

TimeStruct is a Julia package that supports the efficient development of optimization models with multi-horizon time modelling. The package is designed to be used in combination with the JuMP package for optimization modeling in Julia.
Expand All @@ -24,18 +23,20 @@ The main concept is a [`TimeStructure`](@ref) which is an abstract type that ena
## How to get started

The package is registered in the general registry and can be installed in standard fashion

```julia
] add TimeStruct
```

This documentation consists of a manual explaining concepts and giving examples as well as a complete
API reference.


## Cite

If you find TimeStruct useful in your work, we kindly request that you cite the
following:
```

@misc{TimeStruct.jl,
author = {Flatberg, Truls and Hellemo, Lars},
title = {{TimeStruct.jl: Flexible time structures in optimization modelling}},
Expand All @@ -47,9 +48,6 @@ following:
}
```


## Acknowledgements

This material is based upon work supported by the Research Council of Norway through the projects ZeroKyst (328721), MaritimeNH3 (328679) and CleanExport (308811).


50 changes: 28 additions & 22 deletions docs/src/manual/basic.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ using TimeStruct

## SimpleTimes

The basic time structure is [`SimpleTimes`](@ref) which represents a continuous period of time divided into individual time periods of varying duration.
The basic time structure is [`SimpleTimes`](@ref) which represents a continuous period of time divided into individual time periods of varying duration.
The length of each time period is obtained by the [`duration(t)`](@ref) function.

```@repl ts
Expand All @@ -16,46 +16,51 @@ durations = [duration(t) for t in periods]

![Illustration of SimpleTimes](./../figures/simple.png)

## Calendar based
## Calendar based

For some applications it is required to relate the time periods to actual calendar dates.
For some applications it is required to relate the time periods to actual calendar dates.
This is supported by the time structure [`CalendarTimes`](@ref) that alllows for creation and iteration of a calendar based sequence of periods in combination with calendar arithmetic.

The following example shows the creation of a time structure with 12 months starting from
the first of January 2024. The duration of each time period is given in hours by default, but it is possible to specify the time units to use by providing the period type to use:

```@repl ts
using Dates
year = CalendarTimes(DateTime(2024, 1, 1), 12, Month(1));
duration(first(year); dfunc = Dates.Day)
```

You can also make the time structure for a specific time zone as shown in the following
You can also make the time structure for a specific time zone as shown in the following
example with 3 days in the end of March with a transition to summer time on the second day:

```@repl ts
using TimeZones
periods = CalendarTimes(DateTime(2023, 3, 25), tz"Europe/Berlin", 3, Day(1));
duration.(periods)
```

## Representative periods
In some cases, a fine-scale representation for the operations of the infrastructure of the whole time horizon, is not feasible. A possible strategy is then to select one or more representative periods and use them to evaluate operational cost and feasibility. The time structure [`RepresentativePeriods`](@ref) consists of an ordered sequence of representative periods that represents a longer period of time. Each
representative period covers a specified share of the whole time period.

In some cases, a fine-scale representation for the operations of the infrastructure of the whole time horizon, is not feasible. A possible strategy is then to select one or more representative periods and use them to evaluate operational cost and feasibility. The time structure [`RepresentativePeriods`](@ref) consists of an ordered sequence of representative periods that represents a longer period of time. Each
representative period covers a specified share of the whole time period.

The following example shows an example with a year with daily resolution represented by two weeks
with a share of 0.7 and 0.3 respectively.
with a share of 0.7 and 0.3 respectively.

```@repl rp
using JuMP, TimeStruct

periods = RepresentativePeriods(
2,
365,
[0.7, 0.3],
2,
365,
[0.7, 0.3],
[SimpleTimes(7,1), SimpleTimes(7,1)]
);
```
The time periods can be iterated both for the whole time structure and individually by each representative period using the [`repr_periods`](@ref) function. This is illustrated here

The time periods can be iterated both for the whole time structure and individually by each representative period using the [`repr_periods`](@ref) function. This is illustrated here
when setting up a JuMP model with a separate constraint for each representative period:

```@repl rp
m = Model();
@variable(m, prod[periods] >= 0);
Expand All @@ -66,42 +71,43 @@ end

@constraint(m, sum(prod[t] * multiple(t) for t in periods) <= 1);
```

For each time period the [`multiple`](@ref) function returns how many times the given period
should be counted when aggregating to the whole [`RepresentativePeriods`](@ref) structure. This
will take into account both the duration and share of each representative period, thus
we have that:
we have that:

```@repl rp
sum(duration(t) * multiple(t) for t in periods)
duration(periods)
```

## Operational scenarios

Operations often face uncertain operating conditions. In energy systems modeling, a typical example is the availability of wind and solar power.
One method for accounting for this uncertainty is to have multiple operational scenarios that are used to evaluate the cost and feasibility of
Operations often face uncertain operating conditions. In energy systems modeling, a typical example is the availability of wind and solar power.
One method for accounting for this uncertainty is to have multiple operational scenarios that are used to evaluate the cost and feasibility of
operations, where each scenario has a given probability of occurring.

The time structure [`OperationalScenarios`](@ref) represents an unordered collection of
operational scenarios where each scenario has a separate time structure and an associated
The time structure [`OperationalScenarios`](@ref) represents an unordered collection of
operational scenarios where each scenario has a separate time structure and an associated
probability.

```@repl os
using TimeStruct, JuMP
scenarios = OperationalScenarios(
3,
[SimpleTimes(5,1), SimpleTimes(7,2), SimpleTimes(10,1)],
3,
[SimpleTimes(5,1), SimpleTimes(7,2), SimpleTimes(10,1)],
[0.3, 0.2, 0.5]
);
```

![Illustration of OperationalScenarios](./../figures/scenario.png)


Similar to representative periods, each period has a [`multiple`](@ref) that is defined
relative to the maximum duration for all scenarios. In addition, each time period
has a [`probability`](@ref)equal to the probability of its scenario. Thus we have that:

```@repl os
sum(duration(t) * probability(t) * multiple(t) for t in scenarios)
sum(duration(t) * probability(t) * multiple(t) for t in scenarios)
duration(scenarios)
```


14 changes: 8 additions & 6 deletions docs/src/manual/discount.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,29 @@

For multi-year investment optimization models it is common pratice to use an
objective that is discounted to get the net present value of the investment.
Since investment decisions usually are done on a strategic level, discount
factors are also calculated based on strategic periods.
Since investment decisions usually are done on a strategic level, discount
factors are also calculated based on strategic periods.

The discount factor for a time period `t` is found by the [`discount`](@ref)
function. There are two strategies for calculating the discount factor,
either all discounting is calculated based on the start of the strategic period
or it is based on finding an approximation of the average value over the
or it is based on finding an approximation of the average value over the
strategic period. The following example shows how these two types will
differ for a planning period of 50 years, consisting of 5 periods of
differ for a planning period of 50 years, consisting of 5 periods of
10 years:

```@repl ts
using TimeStruct
ts = TwoLevel(5, 10, SimpleTimes(1,1));
df_start = [discount(t, ts, 0.05; type = "start") for t in ts]
df_avg = [discount(t, ts, 0.05; type = "avg") for t in ts]
```
While it is often normal to assume investments at the start of each

While it is often normal to assume investments at the start of each
strategic period, it can be more correct to average the discount factor
for operational costs that are accrued throughout the strategic period.

To help setting up the objective function in a typical optimization problem,
there is a utility function [`objective_weight`](@ref) that returns
the weight to give a time period in the objective, considering both
discount factor, probability and possible multiplicity.
discount factor, probability and possible multiplicity.
22 changes: 14 additions & 8 deletions docs/src/manual/iteration.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## Basic iteration

All time structures are iterable over their operational time periods

```@repl ts
using TimeStruct

Expand All @@ -18,8 +19,9 @@ end

In many settings, e.g. tracking of storage, it is convenient to have
access to the previous time period. By using the custom iterator
[`withprev`](@ref) it is possible to return both the previous and
[`withprev`](@ref) it is possible to return both the previous and
current time period as a tuple when iterating:

```@repl ts
using TimeStruct
periods = SimpleTimes(5, 1);
Expand All @@ -37,15 +39,17 @@ there are several iterator wrappers that allows this kind of iteration pattern.


The [`chunk`](@ref) function iterates through a time structure returning
subsequences of length at most `n` starting at each time period.
subsequences of length at most `n` starting at each time period.

```@repl ts
periods = SimpleTimes(5,1)
collect(collect(ts) for ts in chunk(periods, 3))
```

This wrapper can be used for e.g. modelling of startup modelling with a minimum
uptime. The following example shows how this can be implemented as part of
a JuMP model:
uptime. The following example shows how this can be implemented as part of
a JuMP model:

```@ex
using JuMP, TimeStruct

Expand All @@ -57,10 +61,12 @@ m = Model()
for ts in chunk(periods, 3)
@constraint(m, sum(startup[t] for t in ts) <= 1)
end

```
Similarly, if modelling shutdown decision with a minimum uptime,
it is possible to reverse the original time periods and then
it is possible to reverse the original time periods and then
chunk:

```@ex
m = Model()
@variable(m, shutdown[periods], Bin)
Expand All @@ -74,14 +80,14 @@ end
Not all time structures can be reversed. Currently, it is only supported
for operational time structures and operational scenarios.


## Chunks based on duration

If working with a time structure that has varying duration for its time periods,
it can be more convenient with chunks based on their combined duration.

The [`chunk_duration`](@ref) function iterates through a time structure returning
subsequences of duration at least `dur` starting at each time period.
subsequences of duration at least `dur` starting at each time period.

```@repl ts
periods = SimpleTimes(5,[1, 2, 1, 1.5, 0.5, 2])
collect(collect(ts) for ts in chunk_duration(periods, 3))
Expand All @@ -91,7 +97,7 @@ collect(collect(ts) for ts in chunk_duration(periods, 3))

It is possible to use indices for operational time structures, either directly
using [`SimpleTimes`](@ref) or [`CalendarTimes`](@ref) or by accessing an
operational scenario.
operational scenario.

```@repl ts
periods = TwoLevel(3, 100, SimpleTimes(10,1));
Expand Down
27 changes: 15 additions & 12 deletions docs/src/manual/multi.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
# Multi-horizon time structures

## TwoLevel
## TwoLevel structure

The main motivation for the `TimeStruct` package is to support
multi-horizon optimization models. The time structure [`TwoLevel`](@ref) allows for a two level
approach, combining an ordered sequence of strategic periods with given duration and an associated operational time structure.
multi-horizon optimization models. The time structure [`TwoLevel`](@ref) allows for a two level
approach, combining an ordered sequence of strategic periods with given duration and an associated operational time structure.

```@repl ts
using TimeStruct
periods = TwoLevel(
[SimpleTimes(5,1), SimpleTimes(5,1), SimpleTimes(5,1)],
[SimpleTimes(5,1), SimpleTimes(5,1), SimpleTimes(5,1)],
);
```

![Illustration of TwoLevel](./../figures/twolevel.png)

The following example shows a typical usage of a [`TwoLevel`](@ref) strucure with investment
decisions on a strategic level and operational decision variables. It is possible to iterate
through each strategic period using the [`strat_periods`](@ref)function.
```@repl ts
The following example shows a typical usage of a [`TwoLevel`](@ref) strucure with investment
decisions on a strategic level and operational decision variables. It is possible to iterate
through each strategic period using the [`strat_periods`](@ref)function.

```@repl ts
using JuMP
m = Model();
@variable(m, invest[strat_periods(periods)] >= 0);
Expand All @@ -38,7 +40,7 @@ scen = OperationalScenarios([oper, oper, oper], [0.4, 0.5, 0.1]);
repr = RepresentativePeriods(2, 5, [0.5, 0.5], [oper, oper]);
repr_scen = RepresentativePeriods(2, 5, [0.5, 0.5], [scen, scen]);

periods = TwoLevel([scen, repr, repr_scen]);
periods = TwoLevel([scen, repr, repr_scen]);
```

![Complex TwoLevel](./../figures/two_complex.png)
Expand All @@ -55,13 +57,14 @@ that holds the number of operational periods per strategic period.

A typical use case is an investment problem where one uses years
to measure duration at the strategic level and hours/days on the operational level.
Below is an example with 3 strategic periods of duration 5, 5, and 10 years
respectively, while the operational time structure is given by
Below is an example with 3 strategic periods of duration 5, 5, and 10 years
respectively, while the operational time structure is given by
representative periods with duration in days. The `op_per_strat` is then set to 365.

```@repl ts
week = SimpleTimes(7,1);
repr = RepresentativePeriods(2, 365, [0.6, 0.4], [week, week]);
periods = TwoLevel(3, [5, 5, 10], [repr, repr, repr], 365.0);
```

## TwoLevelTree
## TwoLevelTree structure
Loading