Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mga fix/0.4.0 #681

Merged
merged 11 commits into from
Apr 12, 2024
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
non-zero in multi-stage GenX (#666)
- Added condition number scaling added to objective function (#667)
- Added versioned doc-pages for v0.3.6 and v0.4.0
- Add constraint in mga to compute total capacity in each zone from a given technology type (#681)
- New settings parameter MGAAnnualGeneration to switch between different MGA formulations (#681)

### Fixed
- Set MUST_RUN=1 for RealSystemExample/small_hydro plants (#517).
Expand Down
14 changes: 6 additions & 8 deletions docs/src/User_Guide/generate_alternatives.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,10 @@ GenX includes a modeling to generate alternatives (MGA) package that can be used

1. Add a `Resource_Type` column in all the resource `.csv` files denoting the type of each technology.
2. Add a `MGA` column in all the resource `.csv` files denoting the availability of the technology.
3. Set the `ModelingToGenerateAlternatives` flag in the `GenX_Settings.yml` file to 1.
4. Set the `ModelingtoGenerateAlternativeSlack` flag in the `GenX_Settings.yml` file to the desirable level of slack.
5. Create a `Rand_mga_objective_coefficients.csv` file to provide random objective function coefficients for each MGA iteration.
3. Set the `ModelingToGenerateAlternatives` flag in the `genx_settings.yml` file to 1.
4. Set the `ModelingtoGenerateAlternativeSlack` flag in the `genx_settings.yml` file to the desirable level of slack.
5. Set the `ModelingToGenerateAlternativesIterations` flag to half the total number of desired solutions, as each iteration provides 2 solutions.
6. Set the `MGAAnnualGeneration` flag in the `genx_settings.yml` file to the desired MGA formulation.
7. Solve the model using `Run.jl` file.

For each iteration, number of rows in the `Rand_mga_objective_coefficients`.csv file represents the number of distinct technology types while number of columns represent the number of model zones.

Solve the model using `Run.jl` file.

Results from the MGA algorithm would be saved in MGA_max and MGA_min folders in the `Example_Systems/` folder.
Results from the MGA algorithm would be saved in MGA_max and MGA_min folders in the case folder.
104 changes: 80 additions & 24 deletions src/additional_tools/modeling_to_generate_alternatives.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,35 @@

We have implemented an updated Modeling to Generate Alternatives (MGA) Algorithm proposed by [Berntsen and Trutnevyte (2017)](https://www.sciencedirect.com/science/article/pii/S0360544217304097) to generate a set of feasible, near cost-optimal technology portfolios. This algorithm was developed by [Brill Jr, E. D., 1979](https://pubsonline.informs.org/doi/abs/10.1287/mnsc.25.5.413) and introduced to energy system planning by [DeCarolia, J. F., 2011](https://www.sciencedirect.com/science/article/pii/S0140988310000721).

To create the MGA formulation, we replace the cost-minimizing objective function of GenX with a new objective function that creates multiple generation portfolios by zone. We further add a new budget constraint based on the optimal objective function value $f^*$ of the least-cost model and the user-specified value of slack $\delta$. After adding the slack constraint, the resulting MGA formulation is given as:
To create the MGA formulation, we replace the cost-minimizing objective function of GenX with a new objective function that creates multiple generation portfolios by zone. We further add a new budget constraint based on the optimal objective function value $f^*$ of the least-cost model and the user-specified value of slack $\delta$. After adding the slack constraint, the resulting MGA formulation is given as (`MGAAnnualGeneration = 0` in the genx_settings.yml file, or not set):

```math
\begin{aligned}
\text{max/min} \quad
&\sum_{z \in \mathcal{Z}}\sum_{r \in \mathcal{R}} \beta_{z,r}^{k}P_{z,r}\\
\text{s.t.} \quad
&P_{zr} = \sum_{y \in \mathcal{G}}\sum_{t \in \mathcal{T}} \omega_{t} \Theta_{y,t,z,r} \\
&P_{z,r} = \sum_{y \in \mathcal{G}}C_{y,z,r} \\
& f \leq f^* + \delta \\
&Ax = b
\end{aligned}
```

where, $\beta_{zr}$ is a random objective fucntion coefficient betwen $[0,100]$ for MGA iteration $k$. $\Theta_{y,t,z,r}$ is a generation of technology $y$ in zone $z$ in time period $t$ that belongs to a resource type $r$. We aggregate $\Theta_{y,t,z,r}$ into a new variable $P_{z,r}$ that represents total generation from technology type $r$ in a zone $z$. In the second constraint above, $\delta$ denote the increase in budget from the least-cost solution and $f$ represents the expression for the total system cost. The constraint $Ax = b$ represents all other constraints in the power system model. We then solve the formulation with minimization and maximization objective function to explore near optimal solution space.
where, $\beta_{z,r}$ is a random objective function coefficient betwen $[0,1]$ for MGA iteration $k$. We aggregate capacity into a new variable $P_{z,r}$ that represents total capacity from technology type $r$ in a zone $z$.

If the users set `MGAAnnualGeneration = 1` in the genx_settings.yml file, the MGA formulation is given as:
```math
\begin{aligned}
\text{max/min} \quad
&\sum_{z \in \mathcal{Z}}\sum_{r \in \mathcal{R}} \beta_{z,r}^{k}P_{z,r}\\
\text{s.t.} \quad
&P_{z,r} = \sum_{y \in \mathcal{G}}\sum_{t \in \mathcal{T}} \omega_{t} \Theta_{y,t,z,r} \\
& f \leq f^* + \delta \\
&Ax = b
\end{aligned}
```
where, $\beta_{z,r}$ is a random objective function coefficient betwen $[0,1]$ for MGA iteration $k$. $\Theta_{y,t,z,r}$ is a generation of technology $y$ in zone $z$ in time period $t$ that belongs to a resource type $r$. We aggregate $\Theta_{y,t,z,r}$ into a new variable $P_{z,r}$ that represents total generation from technology type $r$ in a zone $z$.

In the second constraint in both the above formulations, $\delta$ denote the increase in budget from the least-cost solution and $f$ represents the expression for the total system cost. The constraint $Ax = b$ represents all other constraints in the power system model. We then solve the formulation with minimization and maximization objective function to explore near optimal solution space.
"""
function mga(EP::Model, path::AbstractString, setup::Dict, inputs::Dict)
if setup["ModelingToGenerateAlternatives"] == 1
Expand All @@ -39,29 +54,10 @@ function mga(EP::Model, path::AbstractString, setup::Dict, inputs::Dict)
# Read slack parameter representing desired increase in budget from the least cost solution
slack = setup["ModelingtoGenerateAlternativeSlack"]

### Variables ###

@variable(EP, vSumvP[TechTypes = 1:length(TechTypes), z = 1:Z]>=0) # Variable denoting total generation from eligible technology of a given type

### End Variables ###

### Constraints ###

# Constraint to set budget for MGA iterations
@constraint(EP, budget, EP[:eObj]<=Least_System_Cost * (1 + slack))

# Constraint to compute total generation in each zone from a given Technology Type
function resource_in_zone_with_TechType(tt::Int64, z::Int64)
condition::BitVector = (resource_type_mga.(gen) .== TechTypes[tt]) .&
(zone_id.(gen) .== z)
return resource_id.(gen[condition])
end
@constraint(EP,
cGeneration[tt = 1:length(TechTypes), z = 1:Z],
vSumvP[tt,
z]==sum(EP[:vP][y, t] * inputs["omega"][t]
for y in resource_in_zone_with_TechType(tt, z), t in 1:T))

### End Constraints ###

### Create Results Directory for MGA iterations
Expand All @@ -87,7 +83,8 @@ function mga(EP::Model, path::AbstractString, setup::Dict, inputs::Dict)
### Maximization objective
@objective(EP,
Max,
sum(pRand[tt, z] * vSumvP[tt, z] for tt in 1:length(TechTypes), z in 1:Z))
sum(pRand[tt, z] * EP[:vMGA][tt, z]
for tt in 1:length(TechTypes), z in 1:Z))

# Solve Model Iteration
status = optimize!(EP)
Expand All @@ -101,7 +98,8 @@ function mga(EP::Model, path::AbstractString, setup::Dict, inputs::Dict)
### Minimization objective
@objective(EP,
Min,
sum(pRand[tt, z] * vSumvP[tt, z] for tt in 1:length(TechTypes), z in 1:Z))
sum(pRand[tt, z] * EP[:vMGA][tt, z]
for tt in 1:length(TechTypes), z in 1:Z))

# Solve Model Iteration
status = optimize!(EP)
Expand All @@ -117,3 +115,61 @@ function mga(EP::Model, path::AbstractString, setup::Dict, inputs::Dict)
### End MGA Iterations ###
end
end

@doc raw"""
mga!(EP::Model, inputs::Dict, setup::Dict)

This function reads the input data, collect the resources with MGA flag on and creates a set of unique technology types.
The function then adds a constraint to the model to compute total capacity in each zone from a given Technology Type.

If the user set `MGAAnnualGeneration = 0` in the genx_settings.yml file, the constraint has the following form:
```math
P_{z,r} = \sum_{y \in \mathcal{G}}C_{y,z,r}
```
where, the aggregated capacity $P_{z,r}$ represents total capacity from technology type $r$ in a zone $z$.

If the user set `MGAAnnualGeneration = 1` in the genx_settings.yml file, the constraint has the following form:
```math
P_{z,r} = \sum_{y \in \mathcal{G}}\sum_{t \in \mathcal{T}} \omega_{t} \Theta_{y,t,z,r}
```
where $\Theta_{y,t,z,r}$ is a generation of technology $y$ in zone $z$ in time period $t$ that belongs to a resource type $r$. $\Theta_{y,t,z,r}$ is aggregated into a new variable $P_{z,r}$ that represents total generation from technology type $r$ in a zone $z$.

# Arguments
- `EP::Model`: GenX model object
- `inputs::Dict`: Dictionary containing input data

# Returns
- This function updates the model object `EP` with the MGA variables and constraints in-place.
"""
function mga!(EP::Model, inputs::Dict, setup::Dict)
println("MGA Module")

T = inputs["T"] # Number of time steps (hours)
Z = inputs["Z"] # Number of zones
gen = inputs["RESOURCES"] # Resources data
# Read set of MGA variables
annual_gen = setup["MGAAnnualGeneration"] ### Choose setting in genx_settings.yaml: MGAAnnualGeneration: 1 = annual generation, otherwise, sum of capacity
# Create a set of unique technology types
resources_with_mga_on = gen[ids_with_mga(gen)]
TechTypes = unique(resource_type_mga.(resources_with_mga_on))

function resource_in_zone_same_TechType(tt::Int64, z::Int64)
condition::BitVector = (resource_type_mga.(gen) .== TechTypes[tt]) .&
(zone_id.(gen) .== z)
return resource_id.(gen[condition])
end
# Constraint to compute total generation in each zone from a given Technology Type
### Variables ###
@variable(EP, vMGA[TechTypes = 1:length(TechTypes), z = 1:Z]>=0)

### Constraint ###
if annual_gen == 1 # annual generation
@constraint(EP, cGeneration[tt = 1:length(TechTypes), z = 1:Z],
vMGA[tt,z]==sum(EP[:vP][y, t] * inputs["omega"][t]
lbonaldo marked this conversation as resolved.
Show resolved Hide resolved
for y in resource_in_zone_same_TechType(tt, z), t in 1:T))
else
@constraint(EP, cCapEquiv[tt = 1:length(TechTypes), z = 1:Z],
vMGA[tt,z]==sum(EP[:eTotalCap][y]
lbonaldo marked this conversation as resolved.
Show resolved Hide resolved
for y in resource_in_zone_same_TechType(tt, z)))
end
end
2 changes: 1 addition & 1 deletion src/case_runners/case_runner.jl
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ function run_genx_case_simple!(case::AbstractString, mysetup::Dict, optimizer::A
println(elapsed_time)
if mysetup["ModelingToGenerateAlternatives"] == 1
println("Starting Model to Generate Alternatives (MGA) Iterations")
mga(EP, case, mysetup, myinputs, outputs_path)
mga(EP, case, mysetup, myinputs)
end

if mysetup["MethodofMorris"] == 1
Expand Down
1 change: 1 addition & 0 deletions src/configure_settings/configure_settings.jl
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ function default_settings()
"TimeDomainReductionFolder" => "TDR_results",
"ModelingToGenerateAlternatives" => 0,
"ModelingtoGenerateAlternativeSlack" => 0.1,
"MGAAnnualGeneration" => 0,
"MultiStage" => 0,
"MethodofMorris" => 0,
"IncludeLossesInESR" => 0,
Expand Down
4 changes: 4 additions & 0 deletions src/model/generate_model.jl
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,10 @@ function generate_model(setup::Dict, inputs::Dict, OPTIMIZER::MOI.OptimizerWithA
maximum_capacity_requirement!(EP, inputs, setup)
end

if setup["ModelingToGenerateAlternatives"] == 1
mga!(EP, inputs, setup)
end

## Define the objective function
@objective(EP, Min, setup["ObjScale"]*EP[:eObj])

Expand Down
Loading