Energy balance: Difference between revisions
(OpasnetUtils::combine) |
|||
(20 intermediate revisions by 2 users not shown) | |||
Line 1: | Line 1: | ||
<noinclude> | |||
[[op_fi:Energiatase]] | [[op_fi:Energiatase]] | ||
[[Category:Energy]] | [[Category:Energy]] | ||
Line 6: | Line 7: | ||
[[Category:Code under inspection]] | [[Category:Code under inspection]] | ||
{{method|moderator=Jouni|stub=Yes}} | {{method|moderator=Jouni|stub=Yes}} | ||
</noinclude> | |||
==Question== | ==Question== | ||
What is energy balance and how is it modelled? | |||
==Answer== | ==Answer== | ||
Summing up the amount of energy produced and subtracting the amount of energy consumed within a time period gives the energy balance. Since the electricity grid and district heat network lack significant storage mechanics, the balance has to be virtually zero over short periods. When considering the balance of a particular area (e.g. Helsinki), we can make the assumption that electricity can be imported and exported in international markets. The energy in the district heat network, however, has to be produced locally. This sets up the non-trivial problem of optimising production so that there are no significant deficits as well as minimising losses and maximising profits. This problem is solved (to some extent) by market forces in the real world. | |||
In Opasnet, there are two different ways to calculate energy balance. Our most recent energy balance model uses linear programming tools to solve an optimum for the activity of a given set of production units in simulated instances created by the main model. The main model is responsible for the decision making aspects, while the energy balance optimisation only functions as an approximation of real world market mechanics. This version was used in [[Helsinki energy decision 2015]]. | |||
The previous version was based on setting up a set of linear equations describing the inputs, outputs, and shares of different energy and plant processes. This approach is less flexible, because it does not use an optimising function and everything must be described as linear (or piecewise linear). However, this approach was successfully used in [[Energy balance in Kuopio]] and [[Energy balance in Suzhou]]. | |||
<gallery heights="500" widths="500"> | |||
File:District heat flow in Helsinki.png|Example of energy balance model: District heat flow in Helsinki. The scenarios are from the assessment [[Helsinki energy decision 2015]]. | |||
File:Flow of electric power in Helsinki.png|Example of energy balance model: Electric power heat flow in Helsinki. The scenarios are from the assessment [[Helsinki energy decision 2015]]. | |||
File:Incomes and costs of energy production in Helsinki.png|Example of energy balance model: Incomes and costs of energy production in Helsinki. The scenarios are from the assessment [[Helsinki energy decision 2015]]. | |||
</gallery> | |||
This code is an example how the energy balance model is used in a city case. The data comes from [[Helsinki energy decision 2015]]. | |||
<rcode name="answer" graphics="1"> | <rcode name="answer" graphics="1"> | ||
Line 23: | Line 33: | ||
library(ggplot2) | library(ggplot2) | ||
openv.setN(0) # use medians instead of whole sampled distributions | |||
# Download case-specific data, in this case from Helsinki. | |||
objects.latest("Op_en7237", code_name = "intermediates") # [[Helsinki energy decision 2015]] | |||
balance | # Download energy balance model and its parts: | ||
# EnergyConsumerDemandTotal | |||
# EnergyFlowHeat | |||
# EnergyFlowOther | |||
# EnergyNetworkDemand | |||
# EnergyNetworkOptim | |||
# fuelUse | |||
# EnergyNetworkCost | |||
objects.latest("Op_en5141", code_name = "EnergyNetworkOptim") # [[Energy balance]] | |||
oprint(summary(EvalOutput(EnergyNetworkOptim))) | |||
</rcode> | |||
==Rationale== | |||
=== Energy balance with linear programming === | |||
The linear programming problem is set up as follows. | |||
For each production unit: let x<sub>i</sub> be activity of the plant. Lets also have variables y<sub>j</sub> for deficits and excesses for each type of energy produced. | |||
The objective function is the function we are optimising. Each production unit has a unit profit per activity denoted by a<sub>i</sub> which is determined by the amount of different input commodities (e.g. coal) per amount of different output commodities (i.e. electricity and heat) and their market prices. Also, lets say we want to make sure that district heat demand is always met when possible and have a large penalty factor for each unit of heat demand not met (1 M€ in the model). In addition, it must be noted that excess district heat becomes wasted so it counts as loss. Let these deficit and excess related losses be denoted by b<sub>j</sub>. The whole objective function then becomes: sum(x<sub>i</sub>a<sub>i</sub>) + sum(y<sub>j</sub>b<sub>j</sub>). | |||
The values of variables are constrained by equalities and inequalities: the sum of production of a commodity is equal to its demand minus deficit plus excess, activity is constrained by the maximum capacity and all variables are non-negative by definition. | |||
This can be efficiently solved by computers for each given instance. Production wind-up and wind-down is ignored, since time continuity is not considered. As a consequence fuel limits (e.g. diminishing hydropower capacity) are not modelled completely either. | |||
:''Ovariables like EnergyNetworkOptim below are used in [[Helsinki energy decision 2015]]. [[Prices of fuels in heat production]] are used as direct inputs in the optimising. | |||
== | <rcode name="EnergyNetworkOptim" label="Initiate EnergyNetworkOptim(developers only)" embed=1> | ||
## This code is Op_en5141/EnergyNetworkOptim [[Energy balance]]. | |||
library(OpasnetUtils) | |||
=== | EnergyConsumerDemandTotal <- Ovariable("EnergyConsumerDemandTotal", | ||
dependencies = data.frame( | |||
Name = c( | |||
"EnergyConsumerDemand" | |||
) | |||
), | |||
formula = function(...) { | |||
energy <- oapply(EnergyConsumerDemand, NULL, sum, c("Renovation", "Efficiency")) | |||
energy <- energy * 1e-6 # W to MW | |||
return(energy) | |||
} | |||
) | |||
EnergyFlowHeat <- Ovariable("EnergyFlowHeat", | |||
dependencies = data.frame( | |||
Name = c( | |||
"EnergyConsumerDemandTotal", | |||
"fuelShares" | |||
) | |||
), | |||
formula = function(...) { | |||
EnergyFlowHeat <- EnergyConsumerDemandTotal[EnergyConsumerDemandTotal$Consumable %in% c("Heating", "Hot water"),] * fuelShares | |||
return(EnergyFlowHeat) | |||
} | |||
) | |||
< | EnergyFlowOther <- Ovariable("EnergyFlowOther", | ||
dependencies = data.frame( | |||
Name = c( | |||
"EnergyConsumerDemandTotal", | |||
"temperene", | |||
"nontemperene" | |||
) | |||
), | |||
formula = function(...) { | |||
EnergyFlowOther <- merge( | |||
EnergyConsumerDemandTotal[!EnergyConsumerDemandTotal$Consumable %in% c("Heating", "Hot water"),], | |||
unique(OpasnetUtils::combine(temperene, nontemperene)[,c("Consumable", "Fuel")]) | |||
) | |||
EnergyFlowOther@name <- EnergyConsumerDemandTotal@name | |||
return(EnergyFlowOther) | |||
} | |||
) | |||
EnergyNetworkDemand <- Ovariable("EnergyNetworkDemand", | |||
dependencies = data.frame( | |||
Name = c( | |||
"EnergyFlowHeat", | |||
"EnergyFlowOther", | |||
"EnergyConsumerDemandTotal" | |||
) | |||
), | |||
formula = function(...) { | |||
demand <- OpasnetUtils::combine( | |||
EnergyFlowHeat[EnergyFlowHeat$Fuel %in% c("Heat", "Electricity"), colnames(EnergyFlowHeat@output) != "Burner"], | |||
EnergyFlowOther[EnergyFlowOther$Fuel %in% c("Cooling", "Electricity"),] | |||
) | |||
# EnergyFlowOther should not have other flows | |||
#assign("EnergyFlowOther", NULL, .GlobalEnv) | |||
#assign("EnergyFlowHeat", EnergyFlowHeat[!EnergyFlowHeat$Fuel %in% c("Heat", "Electricity"),], .GlobalEnv) | |||
# Total consumer demand also unnecessary? | |||
#assign("EnergyConsumerDemandTotal", NULL, .GlobalEnv) | |||
NAindex <- sapply(lapply(lapply(demand@output, unique), is.na), sum) | |||
NAindex <- names(NAindex)[NAindex != 0] | |||
demand@output <- fillna(demand@output , NAindex) | |||
demand <- oapply(demand, NULL, sum, c("Heating", "Consumable"))#, "Renovation", "Efficiency")) # Assumes no NA | |||
return(demand) | |||
} | |||
) | |||
< | EnergyNetworkOptim <- Ovariable("EnergyNetworkOptim", | ||
dependencies = data.frame( | |||
< | Name = c( | ||
#"energy", # Energy supply and demand in the system | |||
#"nondynsupply", # Energy supply that is not optimised. | |||
#"requiredName", # Fuel name for the energy type that is balanced for inputs and outputs. | |||
#"fuelShares", # Shares of fuels for different heating types. | |||
"energyProcess", # Matrix showing inputs and outputs of the process of each plant. | |||
"fuelPrice", # Prices of fuels (by Time) | |||
#"timelylimit", # Maximum energy supply in the given conditions at a particular time point | |||
"plantParameters", # Capacities and costs of running each plant | |||
#"temperene", | |||
#"nontemperene", | |||
"EnergyNetworkDemand" | |||
), | |||
Ident = c( | |||
#NA, | |||
#NA, | |||
#NA, | |||
#NA, | |||
NA, | |||
"Op_en4151/fuelPrice", | |||
#NA, | |||
NA, | |||
#"Op_en5488/temperene", | |||
#"Op_en5488/nontemperene", | |||
NA | |||
) | |||
), | |||
formula = function(...) { | |||
require(lpSolve) | |||
require(plyr) | |||
require(reshape2) | |||
# Prices are given as €/MWh while plant activity is given as MW while optimisation is done | |||
# with time resolution = 1 day | |||
optim_time_period_adj <- 24 | |||
# List non-interating indices | |||
exclude <- c("Fuel", "Plant", "Parameter", "Heating", "Consumable", "Burner") | |||
# Calculate unitprofit for plant activity energy flows | |||
unitp <- fuelPrice * energyProcess * optim_time_period_adj | |||
unitp <- oapply(unitp, NULL, sum, c("Fuel", "Burner")) | |||
colnames(unitp@output)[colnames(unitp@output) == "Result"] <- "UnitPrice" | |||
# Reshape crucial variables to reduce merging difficulty | |||
ePmargs <- colnames(energyProcess@output)[energyProcess@marginal] | |||
energyProcess@output <- dcast( | |||
energyProcess@output, | |||
as.formula(paste(paste(ePmargs[ePmargs != "Fuel"], collapse = "+"), "~ Fuel")), | |||
value.var = paste(energyProcess@name, "Result", sep = ""), | |||
fill = NA | |||
) | |||
energyProcess@marginal <- colnames(energyProcess@output) %in% ePmargs | |||
pPmargs <- colnames(plantParameters@output)[plantParameters@marginal] | |||
plantParameters@output <- dcast( | |||
plantParameters@output, | |||
as.formula(paste(paste(pPmargs[pPmargs != "Parameter"], collapse = "+"), "~ Parameter")), | |||
value.var = paste(plantParameters@name, "Result", sep = ""), | |||
fill = NA | |||
) | |||
plantParameters@marginal <- colnames(plantParameters@output) %in% pPmargs | |||
EnergyNetworkDemand$Fuel <- paste(EnergyNetworkDemand$Fuel, "Demand", sep = "") | |||
ENDmargs <- colnames(EnergyNetworkDemand@output)[EnergyNetworkDemand@marginal] | |||
EnergyNetworkDemand@output <- dcast( | |||
EnergyNetworkDemand@output, | |||
as.formula(paste(paste(ENDmargs[ENDmargs != "Fuel"], collapse = "+"), "~ Fuel")), | |||
value.var = paste(EnergyNetworkDemand@name, "Result", sep = ""), | |||
fill = NA | |||
) | |||
EnergyNetworkDemand@marginal <- colnames(EnergyNetworkDemand@output) %in% ENDmargs | |||
fuelPrice$Fuel <- paste(fuelPrice$Fuel, "Price", sep = "") | |||
fPmargs <- colnames(fuelPrice@output)[fuelPrice@marginal] | |||
fuelPrice@output <- dcast( | |||
fuelPrice@output, | |||
as.formula(paste(paste(fPmargs[fPmargs != "Fuel"], collapse = "+"), "~ Fuel")), | |||
value.var = paste(fuelPrice@name, "Result", sep = ""), | |||
fill = 0 | |||
) | |||
fuelPrice@marginal <- colnames(fuelPrice@output) %in% fPmargs | |||
# Duplicates a couple of values, but provides better performance | |||
vars <- merge(EnergyNetworkDemand, plantParameters) | |||
vars <- merge(vars, energyProcess) | |||
vars <- merge(vars, fuelPrice) | |||
vars <- merge(vars, unitp) | |||
# Split into iterations | |||
#vars <- dlply(vars@output, colnames(vars@output)[vars@marginal & !colnames(vars@output) %in% exclude], I) | |||
#out <- data.frame() | |||
include <- union(ePmargs, pPmargs) | |||
include <- union(include, ENDmargs) | |||
include <- union(include, fPmargs) | |||
include <- include[!include %in% exclude] | |||
# Optimisation iteration function | |||
optf <- function(varsi) { | |||
# Acquire relevant sections of variables with respect to index iteration | |||
demandi <- unlist(varsi[1,grepl("Demand$", colnames(varsi))]) | |||
names(demandi) <- gsub("Demand$", "", names(demandi)) | |||
fuelPricei <- unlist(varsi[1,grepl("Price$", colnames(varsi))]) | |||
names(fuelPricei) <- gsub("Price$", "", names(fuelPricei)) | |||
fuelPricei <- fuelPricei[names(fuelPricei) != "Unit"] | |||
plants <- as.character(unique(varsi[["Plant"]])) # Used for ordering parameters | |||
if (nrow(varsi) != length(plants)) stop("Wrong subset given to energy production optimizer! Each row not unique with respect to Plant.") | |||
lower <- varsi[["Min"]] | |||
upper <- varsi[["Max"]] | |||
opcost <- varsi[["Operation cost"]] | |||
unitpi <- varsi[["UnitPrice"]] | |||
#commodities <- as.character(unique(energyProcessi$Fuel)) # Used for ordering parameters | |||
# Constraint matrix | |||
# Total number of variables: | |||
# * plant activity for each plant | |||
# * (commodity flow total) | |||
# * commodity deficit and excess with respect to demand | |||
# * opeartion costs | |||
nvars <- length(plants) + length(demandi) * 2 + 1 | |||
# Rows: | |||
# * demand rows and their constraints | |||
# * (commodity flow rows) | |||
# * plant activity constraints | |||
# * operation costs row | |||
nrows <- 3 * length(demandi) + 2 * length(plants) + 1 | |||
M <- matrix( | |||
0, | |||
nrows, | |||
nvars | |||
) | |||
sign <- rep(">=", nrows) | |||
constant <- rep(0, nrows) | |||
for (j in 1:length(demandi)) { | |||
# Total flow (production - consumption between all processes) + deficit = demand + excess | |||
# which translates to: | |||
# total_commodity_flow + deficit - excess == constant ("static" demand) | |||
< | M[j, length(plants) + j*2 + 1:2 - 2] <- c(1, -1) | ||
sign[j] <- "==" | |||
< | constant[j] <- demandi[j] | ||
# Plant flows by activity | |||
M[j, 1:length(plants)] <- varsi[[names(demandi)[j]]] | |||
} | |||
rowi <- length(demandi) | |||
diag(M[ | |||
(rowi + 1):(rowi + length(plants)), | |||
1:length(plants) | |||
]) <- 1 | |||
diag(M[ | |||
(rowi + length(plants) + 1):(rowi + 2 * length(plants)), | |||
1:length(plants) | |||
]) <- 1 | |||
constant[rowi + 1:length(plants)] <- lower | |||
constant[rowi + length(plants) + 1:length(plants)] <- upper | |||
sign[rowi + length(plants) + 1:length(plants)] <- "<=" | |||
rowi <- rowi + 2 * length(plants) | |||
# Excess and deficit must both be positive | |||
diag(M[ | |||
rowi + 1:(2 * length(demandi)), | |||
length(plants) + #length(commodities) + | |||
1:(2 * length(demandi)) | |||
]) <- 1 | |||
# Operating cost | |||
M[nrows, 1:length(plants)] <- opcost * optim_time_period_adj | |||
M[nrows, nvars] <- -1 | |||
sign[nrows] <- "==" | |||
# Objective function (profits - penalty) | |||
objective <- rep(0, nvars) | |||
# Commodity prices | |||
#objective[length(plants) + (1:length(commodities))[!is.na(fuelPricej)]] <- result(fuelPricei)[fuelPricej[!is.na(fuelPricej)]] | |||
# Penalize heat deficit heavily, | |||
# electricity deficit has no impact on profit assuming company sells to customers at the purchase price | |||
# cooling deficit has no effect | |||
if ("Heat" %in% names(demandi)) { | |||
objective[length(plants) + match("Heat", names(demandi)) * 2 - 1] <- -1e6 | |||
} | |||
# Electricity excess can be sold in the markets, | |||
# heat and cooling (and other?) excess, however, cannot, hence they need to be deducted from profits | |||
objective[ | |||
length(plants) + which(names(demandi) != "Electricity") * 2 | |||
] <- - fuelPricei[match(names(demandi)[names(demandi) != "Electricity"], names(fuelPricei))] * optim_time_period_adj | |||
# Operation cost | |||
objective[nvars] <- -1 | |||
# Unit profit | |||
objective[1:length(plants)] <- unitpi | |||
# Do actual linear programming | |||
temp <- lp( | |||
"max", | |||
objective, | |||
M, | |||
sign, | |||
constant | |||
) | |||
# Format results | |||
outi <- varsi[rep(1, nvars + 1), include, drop = FALSE] | |||
outi[["Process_variable_type"]] <- rep( | |||
c("Activity", "Flow", "Misc"), | |||
c(length(plants), nvars - length(plants) - 1, 2) | |||
) | |||
outi[["Process_variable_name"]] <- c( | |||
as.character(plants), | |||
paste(rep(names(demandi), each = 2), c("Deficit", "Excess"), sep = ""), | |||
"Operation cost", | |||
"Profit" | |||
) | |||
outi[["Result"]] <- c(temp$solution, temp$objval) | |||
return(outi) | |||
} | |||
#aggregate(out$Result[out$Process_variable_name == "Profit"], out[out$Process_variable_name == "Profit",colnames(out) %in% c("Time", "Temperature") | grepl("Policy", colnames(out))], mean) | |||
#aggregate(out$Result[out$Process_variable_name == "HeatDeficit"], out[out$Process_variable_name == "HeatDeficit", colnames(out) %in% c("Time", "Temperature") | grepl("Policy", colnames(out))], mean) | |||
#aggregate(out$Result[out$Process_variable_name == "Hanasaari"], out[out$Process_variable_name == "Hanasaari", colnames(out) %in% c("Time", "Temperature") | grepl("Policy", colnames(out))], mean) | |||
# Error handling | |||
optfsecure <- function(varsi) { | |||
ret <- tryCatch( | |||
optf(varsi), | |||
error = function(e) return(NULL) | |||
) | |||
if (!is.null(ret)) { | |||
return(ret) | |||
} else { | |||
warning(paste("EnergyNetworkOptim failed optimising a permutation with error:", geterrmessage())) | |||
return(data.frame()) | |||
} | |||
} | |||
out <- ddply(vars@output, colnames(vars@output)[vars@marginal & !colnames(vars@output) %in% exclude], optfsecure) | |||
out <- Ovariable(output = out, marginal = !grepl("Result$", colnames(out))) | |||
return(out) | |||
} | |||
) | |||
=== | fuelUse <- Ovariable("fuelUse", | ||
dependencies = data.frame( | |||
Name = c( | |||
"EnergyNetworkOptim", | |||
"EnergyNetworkDemand", | |||
"energyProcess", | |||
"EnergyFlowHeat", | |||
"temperdays" | |||
) | |||
), | |||
formula = function(...){ | |||
# Calculate flows resulting from plant activity | |||
EnergyFlow <- EnergyNetworkOptim[EnergyNetworkOptim$Process_variable_type == "Activity", colnames(EnergyNetworkOptim@output) != "Process_variable_type"] | |||
colnames(EnergyFlow@output)[colnames(EnergyFlow@output) == "Process_variable_name"] <- "Plant" | |||
EnergyFlow <- EnergyFlow * energyProcess | |||
# Realised consumption of centrally produced commodities (demand - deficit or prod - excess) | |||
# NOTE: centralized flows which consume electricity for example should be separated | |||
# from consumer demand and consumption, so prod - excess is not accurate | |||
deficit <- EnergyNetworkOptim[ | |||
grepl("Deficit$", EnergyNetworkOptim$Process_variable_name), | |||
colnames(EnergyNetworkOptim@output) != "Process_variable_type" | |||
] | |||
colnames(deficit@output)[colnames(deficit@output) == "Process_variable_name"] <- "Fuel" | |||
deficit$Fuel <- gsub("Deficit$", "", deficit$Fuel) | |||
# Assume that the power company purchases any electricity deficit from the markets in order to satisfy demand | |||
result(deficit)[deficit$Fuel == "Electricity"] <- 0 | |||
#excess <- EnergyNetworkOptim[ | |||
# grepl("Excess$", EnergyNetworkOptim$Process_variable_name), | |||
# colnames(EnergyNetworkOptim@output) != "Process_variable_type" | |||
#] | |||
#colnames(excess@output)[colnames(excess@output) == "Process_variable_name"] <- "Fuel" | |||
#excess$Fuel <- gsub("Excess$", "", excess$Fuel) | |||
#temp <- oapply(EnergyFlow, NULL, sum, c("Plant", "Burner")) | |||
#real <- oapply(EnergyFlow[EnergyFlow$Fuel %in% unique(excess$Fuel)], NULL, sum, c("Plant", "Burner")) | |||
#real <- real - excess | |||
real <- EnergyNetworkDemand - deficit | |||
# Combine | |||
real$Burner <- "None" | |||
real$Plant <- "Domestic" | |||
heating <- EnergyFlowHeat[!EnergyFlowHeat$Fuel %in% unique(deficit$Fuel),] | |||
* | heating <- oapply(heating, NULL, sum, "Consumable") | ||
heating$Plant <- "Domestic" | |||
heating$Heating <- NULL | |||
EnergyFlow <- OpasnetUtils::combine(0 - EnergyFlow, real, heating) | |||
EnergyFlow <- unkeep(EnergyFlow, sources = TRUE, prevresults = TRUE) | |||
#EnergyFlowTest <- oapply(EnergyFlow, c("Time", "Temperature", "Fuel"), sum) | |||
#ggplot(EnergyFlowTest@output, aes(x = Temperature, y = Result, group = Fuel, color = Fuel)) + geom_line() + facet_wrap( ~ Time) | |||
EnergyFlow <- EnergyFlow * temperdays * 3600 * 24 | |||
EnergyFlow <- oapply(EnergyFlow, cols = c("Temperature"), FUN = sum) | |||
return(EnergyFlow) | |||
} | |||
) | |||
=== | EnergyNetworkCost <- Ovariable("EnergyNetworkCost", | ||
dependencies = | |||
data.frame( | |||
Name = c( | |||
"plantParameters", | |||
"EnergyNetworkOptim", | |||
"temperdays" | |||
), | |||
Ident = c( | |||
NA, | |||
"Op_en5141/EnergyNetworkOptim", # [[Energy balance]] | |||
NA | |||
) | |||
), | |||
formula = function(...) { | |||
oper <- plantParameters[plantParameters@output$Parameter == "Max" , colnames(plantParameters@output) != "Parameter"] | |||
result(oper)[result(oper) != 0] <- 1 | |||
oper <- plantParameters * oper | |||
# Take the first year when a plant is operated and put all investment cost there. | |||
investment <- oper[oper@output$Parameter == "Investment cost" , colnames(oper@output) != "Parameter"] | |||
investment <- investment[result(investment) > 0 , ] | |||
investment <- investment[order(investment@output$Time) , ] | |||
investment <- investment[!duplicated(investment@output[investment@marginal & colnames(investment@output) != "Time"]) , ] | |||
investment <- unkeep(investment, sources = TRUE) | |||
#investment <- oapply(investment, cols = "Plant", FUN = sum) | |||
==== | maintenance <- oper[oper@output$Parameter == "Management cost" , colnames(oper@output) != "Parameter"] | ||
maintenance <- unkeep(maintenance, sources = TRUE) | |||
#maintenance <- oapply(maintenance, cols = c("Plant"), FUN = sum) | |||
operation <- EnergyNetworkOptim[EnergyNetworkOptim@output$Process_variable_name == "Operation cost" , ] | |||
operation <- operation * temperdays * 10 * 1E-6 # For 10-year periods, € -> M€ | |||
operation <- oapply(operation, cols = c("Temperature"), FUN = sum) | |||
operation <- unkeep(operation, cols = c("Process_variable_name", "Process_variable_type"), sources = TRUE, prevresults = TRUE) | |||
operation <- operation * Ovariable(output = data.frame(Plant = "Operation", Result = 1), marginal = c(TRUE, FALSE)) | |||
cost <- OpasnetUtils::combine(investment, maintenance, operation) | |||
marginals <- character() | |||
for(i in colnames(cost@output)[cost@marginal]) { | |||
if(any(is.na(cost@output[[i]]))) marginals <- c(marginals, i) | |||
} | |||
if(length(marginals) > 0) { | |||
cost@output <- fillna(cost@output, marginals) | |||
warning(paste("In combine had to fillna marginals", marginals, "\n")) | |||
} | |||
return(cost) | |||
} | |||
) | |||
objects.store(EnergyConsumerDemandTotal, EnergyFlowHeat, EnergyFlowOther, EnergyNetworkDemand, EnergyNetworkOptim, fuelUse, EnergyNetworkCost) | |||
cat("Ovariables EnergyConsumerDemandTotal, EnergyFlowHeat, EnergyFlowOther, EnergyNetworkDemand, EnergyNetworkOptim, EnergyNetworkCost and fuelUse stored.\n") | |||
</rcode> | |||
=== Fuel use and fuel shares in generic processes === | === Fuel use and fuel shares in generic processes === | ||
There is an alternative | There is an alternative way for calculating fuel use. It is based on the idea that for each heating type, there is a constant share of fuels used. For some heating types, this is generic and is shown on this page. For some others, the constant is case-specific and is determined on a case-specific page. | ||
The table below contains connections of heating types and fuel usage in generic situations. There may be case-specific differences, which must be handled separately. | The table below contains connections of heating types and fuel usage in generic situations. There may be case-specific differences, which must be handled separately. | ||
Line 198: | Line 548: | ||
Heating oil|Household|Light oil|1| | Heating oil|Household|Light oil|1| | ||
Coal|Household|Coal|1| | Coal|Household|Coal|1| | ||
Other sources|Household|Other sources|1| | Other sources|Household|Other sources|1| | ||
No energy source|Household|Other sources|1| | No energy source|Household|Other sources|1| | ||
Line 205: | Line 554: | ||
Solar heater/ collector|Grid|Electricity|0.1|Use only; life-cycle impacts omitted. | Solar heater/ collector|Grid|Electricity|0.1|Use only; life-cycle impacts omitted. | ||
Electricity|Grid|Electricity|1| | Electricity|Grid|Electricity|1| | ||
District|Undefined|Heat|1| | |||
</t2b> | </t2b> | ||
Line 220: | Line 570: | ||
</rcode> | </rcode> | ||
<rcode name="fuelUse" label="Initiate fuelUse (developers only)" embed=1> | <rcode name="fuelUse" label="Initiate fuelUse (developers only)" embed=1> | ||
Line 239: | Line 588: | ||
cat("Ovariable fuelUse stored.\n") | cat("Ovariable fuelUse stored.\n") | ||
</rcode> | </rcode> | ||
<noinclude> | |||
=== | === Old version with a set of linear equations === | ||
* Energy balances are described as input = output on a coarse level (called classes) where the structure is the same or similar to the OECD energy balance tables. If possible, this is described on the Energy balance method level and it is shared by all cities. | |||
* On more detailed (variable level in the matrix), the fraction of each variable of the total class are described separately. Fractions are city specific and they are described on city level in a separate table. | |||
* Based on the fraction table, detailed equations with variables are created. The format will be fraction * class total = variable. | |||
* The last fraction has zero degrees of freedom when the class total is given. However, it must have a variable and thus a row in the fraction table. The result for that variable is an empty cell (which results in NA). | |||
* Unlike in the previous version, all variables are given either as values or equations, and the user interface is not used for BAU. In contrast, user interface or decision table may be used to derive values for alternative scenarios. | |||
* To make this work, the city-specific fraction data must be defined as ovariable (so that it can be changed with a decision table), and also the energy balance method must be described asa ovariable. How are we going to make the two interplay, as we may want to have several cities? | |||
** Define one city ovariable and evaluate energy balance with that. The ovariable has a generic name. Then, define a new city ovariable with the same name and re-evaluate the energy balance ovariable; this must be done so that the two cities are appended rather than replaced. | |||
** city ovariables are appended first into a large fraction table, and then that is used to create the large energy balance matrix. {{defend|# |This is clearly better.|--[[User:Jouni|Jouni]] 17:09, 21 February 2013 (EET)}} | |||
* The city-specific ovariable may have Iter and other indices. A separate matrix is created and solved for each unique combination of indices. This makes it possible to have a very flexible approach. | |||
* We should check if the energy balance matrix (see Matti's Excel) has city-specific equations. If possible, energy transformations are described as generic equations on the energy balance method. | |||
* Structure of OECD Energy balance tables (data): | |||
** Fuel (given as observation columns in OECD table) | |||
** Activity (row in OECD table) | |||
** Description | |||
* Structure of the generic process table | |||
** Equation, | |||
** Col, | |||
** Result, | |||
** Description? {{attack|# |This does not join up in a coherent way.|--[[User:Jouni|Jouni]] 17:09, 21 February 2013 (EET)}} | |||
* Columns for fraction table | |||
** Class | |||
** Item | |||
** Result (fraction) | |||
** Indices as needed | |||
; Example table for making matrices from text format equations. CHPcapacity describes which of the piecewise linear equations should be used. Policy is a decision option that alters the outcome. Dummy is only for compatibility but it is not used. | |||
<t2b name="Equations" index="CHPcapacity,Policy,Equation" obs="Dummy" desc="Description" unit="GWh /a"> | |||
|Biofuel|CHP renewable = CHP peat|1|Biofuel policy contains half biofuels, half peat | |||
|BAU|CHP renewable = 89.24|1| | |||
||CHP peat + CHP renewable + CHP oil = CHP heat + CHP electricity|1| | |||
||CHP peat = 90-98*CHP oil|1| | |||
||CHP electricity = 0.689*CHP heat|1| | |||
CHP<1000||H heat = 0.08*CHP heat|1|Small heat plants reflect the total heat need | |||
CHP>1000||CHP heat + CHP electricity = 1000|1|But production capacity of CHP may be overwhelmed, decoupling CHP heat and H heat. | |||
||H biogas + H oil = H heat|1| | |||
||H oil = 18.973*H biogas|1| | |||
||Bought electricity + CHP electricity = Cons electricity|1| | |||
||CHP heat + H heat = Cons heat|1| | |||
||Cons electricity = 900-1100|1| | |||
||Cons heat = 900-1000|1| | |||
</t2b> | |||
; Example table to describe the details about nonlinear equations. | |||
<t2b name="Nonlinearity parameters" index="critVar,critIndex,rescol,critLocLow,critLocHigh" obs="critValue" unit="GWh /a"> | |||
Cons heat|CHPcapacity|Result|CHP<1000|CHP>1000|1080 | |||
</t2b> | |||
; This table is fetched if there are no nonlinearities. Therefore, there is no need to copy it to the case study page. | |||
<t2b name="No nonlinearities" index="critVar,critIndex,rescol,critLocLow,critLocHigh" obs="critValue" unit="GWh /a"> | |||
||||| | |||
</t2b> | |||
; This table is fetched if there are no modelled upstream variables that would affect the equations. | |||
<t2b name="No modelled upstream variables" index="energybalanceVars" obs="Result" unit="-"> | |||
| | |||
</t2b> | |||
</ | |||
Line 621: | Line 873: | ||
{{Helsinki energy decision 2015}} | {{Helsinki energy decision 2015}} | ||
* A previous model version written by Jouni. It used linear optimising but was not fully developed and was not actually used in any final assessment. [http://en.opasnet.org/en-opwiki/index.php?title=Energy_balance&oldid=38536#Calculations] | |||
* A new version of energy balance is using [https://stat.ethz.ch/R-manual/R-devel/library/stats/html/optim.html optim function]. See [[Energy balance in Helsinki]]. | * A new version of energy balance is using [https://stat.ethz.ch/R-manual/R-devel/library/stats/html/optim.html optim function]. See [[Energy balance in Helsinki]]. | ||
* [http://www.nzz.ch/meinung/kommentare/energiepolitik-bitte-wenden-1.18451262 Energiepolitik, bitte wenden!] Neue Zürcher Zeitung 27.12.2014 by Giorgio V. Müller | * [http://www.nzz.ch/meinung/kommentare/energiepolitik-bitte-wenden-1.18451262 Energiepolitik, bitte wenden!] Neue Zürcher Zeitung 27.12.2014 by Giorgio V. Müller | ||
Line 652: | Line 905: | ||
==Related files== | ==Related files== | ||
</noinclude> |
Latest revision as of 07:49, 10 April 2019
Moderator:Jouni (see all) |
This page is a stub. You may improve it into a full page. |
Upload data
|
Question
What is energy balance and how is it modelled?
Answer
Summing up the amount of energy produced and subtracting the amount of energy consumed within a time period gives the energy balance. Since the electricity grid and district heat network lack significant storage mechanics, the balance has to be virtually zero over short periods. When considering the balance of a particular area (e.g. Helsinki), we can make the assumption that electricity can be imported and exported in international markets. The energy in the district heat network, however, has to be produced locally. This sets up the non-trivial problem of optimising production so that there are no significant deficits as well as minimising losses and maximising profits. This problem is solved (to some extent) by market forces in the real world.
In Opasnet, there are two different ways to calculate energy balance. Our most recent energy balance model uses linear programming tools to solve an optimum for the activity of a given set of production units in simulated instances created by the main model. The main model is responsible for the decision making aspects, while the energy balance optimisation only functions as an approximation of real world market mechanics. This version was used in Helsinki energy decision 2015.
The previous version was based on setting up a set of linear equations describing the inputs, outputs, and shares of different energy and plant processes. This approach is less flexible, because it does not use an optimising function and everything must be described as linear (or piecewise linear). However, this approach was successfully used in Energy balance in Kuopio and Energy balance in Suzhou.
-
Example of energy balance model: District heat flow in Helsinki. The scenarios are from the assessment Helsinki energy decision 2015.
-
Example of energy balance model: Electric power heat flow in Helsinki. The scenarios are from the assessment Helsinki energy decision 2015.
-
Example of energy balance model: Incomes and costs of energy production in Helsinki. The scenarios are from the assessment Helsinki energy decision 2015.
This code is an example how the energy balance model is used in a city case. The data comes from Helsinki energy decision 2015.
Rationale
Energy balance with linear programming
The linear programming problem is set up as follows.
For each production unit: let xi be activity of the plant. Lets also have variables yj for deficits and excesses for each type of energy produced.
The objective function is the function we are optimising. Each production unit has a unit profit per activity denoted by ai which is determined by the amount of different input commodities (e.g. coal) per amount of different output commodities (i.e. electricity and heat) and their market prices. Also, lets say we want to make sure that district heat demand is always met when possible and have a large penalty factor for each unit of heat demand not met (1 M€ in the model). In addition, it must be noted that excess district heat becomes wasted so it counts as loss. Let these deficit and excess related losses be denoted by bj. The whole objective function then becomes: sum(xiai) + sum(yjbj).
The values of variables are constrained by equalities and inequalities: the sum of production of a commodity is equal to its demand minus deficit plus excess, activity is constrained by the maximum capacity and all variables are non-negative by definition. This can be efficiently solved by computers for each given instance. Production wind-up and wind-down is ignored, since time continuity is not considered. As a consequence fuel limits (e.g. diminishing hydropower capacity) are not modelled completely either.
- Ovariables like EnergyNetworkOptim below are used in Helsinki energy decision 2015. Prices of fuels in heat production are used as direct inputs in the optimising.
There is an alternative way for calculating fuel use. It is based on the idea that for each heating type, there is a constant share of fuels used. For some heating types, this is generic and is shown on this page. For some others, the constant is case-specific and is determined on a case-specific page.
The table below contains connections of heating types and fuel usage in generic situations. There may be case-specific differences, which must be handled separately.
Obs | Heating | Burner | Fuel | Fraction | Description |
---|---|---|---|---|---|
1 | Wood | Household | Wood | 1 | |
2 | Oil | Household | Light oil | 1 | |
3 | Gas | Household | Gas | 1 | |
4 | Heating oil | Household | Light oil | 1 | |
5 | Coal | Household | Coal | 1 | |
6 | Other sources | Household | Other sources | 1 | |
7 | No energy source | Household | Other sources | 1 | |
8 | Geothermal | Grid | Electricity | 0.3 | Geothermal does not sum up to 1 because more heat is produced than electricity consumed. |
9 | Centrifuge, hydro-extractor | Grid | Electricity | 0.3 | Not quite clear what this is but presumably a heat pump. |
10 | Solar heater/ collector | Grid | Electricity | 0.1 | Use only; life-cycle impacts omitted. |
11 | Electricity | Grid | Electricity | 1 | |
12 | District | Undefined | Heat | 1 |
Old version with a set of linear equations
- Energy balances are described as input = output on a coarse level (called classes) where the structure is the same or similar to the OECD energy balance tables. If possible, this is described on the Energy balance method level and it is shared by all cities.
- On more detailed (variable level in the matrix), the fraction of each variable of the total class are described separately. Fractions are city specific and they are described on city level in a separate table.
- Based on the fraction table, detailed equations with variables are created. The format will be fraction * class total = variable.
- The last fraction has zero degrees of freedom when the class total is given. However, it must have a variable and thus a row in the fraction table. The result for that variable is an empty cell (which results in NA).
- Unlike in the previous version, all variables are given either as values or equations, and the user interface is not used for BAU. In contrast, user interface or decision table may be used to derive values for alternative scenarios.
- To make this work, the city-specific fraction data must be defined as ovariable (so that it can be changed with a decision table), and also the energy balance method must be described asa ovariable. How are we going to make the two interplay, as we may want to have several cities?
- Define one city ovariable and evaluate energy balance with that. The ovariable has a generic name. Then, define a new city ovariable with the same name and re-evaluate the energy balance ovariable; this must be done so that the two cities are appended rather than replaced.
- city ovariables are appended first into a large fraction table, and then that is used to create the large energy balance matrix. ←--#: . This is clearly better. --Jouni 17:09, 21 February 2013 (EET) (type: truth; paradigms: science: defence)
- The city-specific ovariable may have Iter and other indices. A separate matrix is created and solved for each unique combination of indices. This makes it possible to have a very flexible approach.
- We should check if the energy balance matrix (see Matti's Excel) has city-specific equations. If possible, energy transformations are described as generic equations on the energy balance method.
- Structure of OECD Energy balance tables (data):
- Fuel (given as observation columns in OECD table)
- Activity (row in OECD table)
- Description
- Structure of the generic process table
- Columns for fraction table
- Class
- Item
- Result (fraction)
- Indices as needed
- Example table for making matrices from text format equations. CHPcapacity describes which of the piecewise linear equations should be used. Policy is a decision option that alters the outcome. Dummy is only for compatibility but it is not used.
Obs | CHPcapacity | Policy | Equation | Dummy | Description |
---|---|---|---|---|---|
1 | Biofuel | CHP renewable = CHP peat | 1 | Biofuel policy contains half biofuels, half peat | |
2 | BAU | CHP renewable = 89.24 | 1 | ||
3 | CHP peat + CHP renewable + CHP oil = CHP heat + CHP electricity | 1 | |||
4 | CHP peat = 90-98*CHP oil | 1 | |||
5 | CHP electricity = 0.689*CHP heat | 1 | |||
6 | CHP<1000 | H heat = 0.08*CHP heat | 1 | Small heat plants reflect the total heat need | |
7 | CHP>1000 | CHP heat + CHP electricity = 1000 | 1 | But production capacity of CHP may be overwhelmed, decoupling CHP heat and H heat. | |
8 | H biogas + H oil = H heat | 1 | |||
9 | H oil = 18.973*H biogas | 1 | |||
10 | Bought electricity + CHP electricity = Cons electricity | 1 | |||
11 | CHP heat + H heat = Cons heat | 1 | |||
12 | Cons electricity = 900-1100 | 1 | |||
13 | Cons heat = 900-1000 | 1 |
- Example table to describe the details about nonlinear equations.
Obs | critVar | critIndex | rescol | critLocLow | critLocHigh | critValue |
---|---|---|---|---|---|---|
1 | Cons heat | CHPcapacity | Result | CHP<1000 | CHP>1000 | 1080 |
- This table is fetched if there are no nonlinearities. Therefore, there is no need to copy it to the case study page.
Obs | critVar | critIndex | rescol | critLocLow | critLocHigh | critValue |
---|---|---|---|---|---|---|
1 |
- This table is fetched if there are no modelled upstream variables that would affect the equations.
Obs | energybalanceVars | Result |
---|---|---|
1 |
Stored objects below used by Energy balance in Kuopio.
- Model version that was used to run results for ISEE2013.
How to give uncertain parameters?
- In equations, the content is interpreted only inside solveMatrix. Therefore, the typical approach where all unique index combinations are run one at a time does not work.
- There should be an update in parameter interpretation for terms with one entry only. It can no longer be based on as.numeric, if distributions (=text) is allowed.
- If it starts with [a-z.] it is a variable name.
- If it starts with [0-9<\\-] it is a parameter value.
- Instead of params[[i]] and [[vars]] vectors, a data.frame will be created with Result as the params column.
- The data.frame is then interpreted with N = N. If parameters are probabilistic, Iter column will appear.
- When all parameters have been interpreted, check if Iter exists.
- If Iter exists, make a for loop for all values of Iter.
- Create a matrix from the parameters and solve.
- Rbind the result to a data.frame with Iter.
- Return the output.
- Old code with an input table with columns Equation, Col, Result, Description: [1]
See also
- A previous model version written by Jouni. It used linear optimising but was not fully developed and was not actually used in any final assessment. [2]
- A new version of energy balance is using optim function. See Energy balance in Helsinki.
- Energiepolitik, bitte wenden! Neue Zürcher Zeitung 27.12.2014 by Giorgio V. Müller
- TIME: Electric Cars Will Change the Way You Power Your Home. How the homes of the future will generate and store their own electricity, turning your house into a mini-power plant.
- Energy supply in Europe
- media:Health impacts of energy production.ppt (a lecture that also contains explanation of an energy balance using matrices)
- Energy balance in Kuopio Describes the production and consumption of energy in Kuopio.
- Energy balance in Basel
- Energy balance in Stuttgart Describes the production and consumption of energy in Stuttgart.
- Energy balance in Suzhou Describes the production and consumption of energy in Suzhou.
- A previous method to calculate energy balances. Includes also other pages:
- Energy transformations Describes the inputs and outputs of energy processes. Shows, which other things change when some input or output is changed.
- Market allocation factor
- File:Energy supply in Europe.xls
- Climate change policies in Kuopio Indicates, which items change when a policy changes.
- Energiatase
- Kasvihuonekaasupäästöt/Kuopio
- Päätösanalyysia_ja_riskinhallintaa.ppt
- Urgenche
- http://www.energia.fi/sites/default/files/polttoaine-energian_maarittaminen_taselaskennan_avulla.pdf
- Uusiutuvan energian riskit selvitetään
- Urgenche: Mesap Planet energy model
- OpasnetUtils/Drafts
References