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
22 changes: 12 additions & 10 deletions src/asset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,16 +61,18 @@ impl Asset {
self.commission_year + self.process.parameter.lifetime
}

/// Get the activity limits for this asset in a particular time slice
pub fn get_activity_limits(&self, time_slice: &TimeSliceID) -> RangeInclusive<f64> {
let limits = self.process.activity_limits.get(time_slice).unwrap();
/// Get the energy limits for this asset in a particular time slice
///
/// This is an absolute max and min on the PAC energy produced/consumed in that time slice.
pub fn get_energy_limits(&self, time_slice: &TimeSliceID) -> RangeInclusive<f64> {
let limits = self.process.energy_limits.get(time_slice).unwrap();
let max_act = self.maximum_activity();

// Multiply the fractional capacity in self.process by this asset's actual capacity
(max_act * limits.start())..=(max_act * limits.end())
}

/// Maximum activity for this asset in a year
/// Maximum activity for this asset (PAC energy produced/consumed per year)
pub fn maximum_activity(&self) -> f64 {
self.capacity * self.process.parameter.capacity_to_activity
}
Expand Down Expand Up @@ -186,14 +188,14 @@ impl AssetPool {
mod tests {
use super::*;
use crate::commodity::{CommodityCostMap, CommodityType, DemandMap};
use crate::process::{ActivityLimitsMap, FlowType, Process, ProcessFlow, ProcessParameter};
use crate::process::{EnergyLimitsMap, FlowType, Process, ProcessFlow, ProcessParameter};
use crate::region::RegionSelection;
use crate::time_slice::TimeSliceLevel;
use itertools::{assert_equal, Itertools};
use std::iter;

#[test]
fn test_asset_get_activity_limits() {
fn test_asset_get_energy_limits() {
let time_slice = TimeSliceID {
season: "winter".into(),
time_of_day: "day".into(),
Expand Down Expand Up @@ -224,11 +226,11 @@ mod tests {
is_pac: true,
};
let fraction_limits = 1.0..=f64::INFINITY;
let activity_limits = iter::once((time_slice.clone(), fraction_limits)).collect();
let energy_limits = iter::once((time_slice.clone(), fraction_limits)).collect();
let process = Rc::new(Process {
id: "process1".into(),
description: "Description".into(),
activity_limits,
energy_limits,
flows: vec![flow.clone()],
parameter: process_param.clone(),
regions: RegionSelection::All,
Expand All @@ -242,7 +244,7 @@ mod tests {
commission_year: 2010,
};

assert_eq!(asset.get_activity_limits(&time_slice), 6.0..=f64::INFINITY);
assert_eq!(asset.get_energy_limits(&time_slice), 6.0..=f64::INFINITY);
}

fn create_asset_pool() -> AssetPool {
Expand All @@ -258,7 +260,7 @@ mod tests {
let process = Rc::new(Process {
id: "process1".into(),
description: "Description".into(),
activity_limits: ActivityLimitsMap::new(),
energy_limits: EnergyLimitsMap::new(),
flows: vec![],
parameter: process_param.clone(),
regions: RegionSelection::All,
Expand Down
6 changes: 3 additions & 3 deletions src/input/asset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ where
#[cfg(test)]
mod tests {
use super::*;
use crate::process::{ActivityLimitsMap, Process, ProcessParameter};
use crate::process::{EnergyLimitsMap, Process, ProcessParameter};
use crate::region::RegionSelection;
use itertools::assert_equal;
use std::iter;
Expand All @@ -114,7 +114,7 @@ mod tests {
let process = Rc::new(Process {
id: "process1".into(),
description: "Description".into(),
activity_limits: ActivityLimitsMap::new(),
energy_limits: EnergyLimitsMap::new(),
flows: vec![],
parameter: process_param.clone(),
regions: RegionSelection::All,
Expand Down Expand Up @@ -189,7 +189,7 @@ mod tests {
let process = Rc::new(Process {
id: "process1".into(),
description: "Description".into(),
activity_limits: ActivityLimitsMap::new(),
energy_limits: EnergyLimitsMap::new(),
flows: vec![],
parameter: process_param,
regions: RegionSelection::Some(["GBR".into()].into_iter().collect()),
Expand Down
14 changes: 7 additions & 7 deletions src/input/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
use super::*;
use crate::commodity::{Commodity, CommodityID, CommodityMap, CommodityType};
use crate::process::{
ActivityLimitsMap, Process, ProcessFlow, ProcessID, ProcessMap, ProcessParameter,
EnergyLimitsMap, Process, ProcessFlow, ProcessID, ProcessMap, ProcessParameter,
};
use crate::region::{RegionID, RegionSelection};
use crate::time_slice::TimeSliceInfo;
Expand Down Expand Up @@ -87,7 +87,7 @@ struct ValidationParams<'a> {
milestone_years: &'a [u32],
time_slice_info: &'a TimeSliceInfo,
parameters: &'a HashMap<ProcessID, ProcessParameter>,
availabilities: &'a HashMap<ProcessID, ActivityLimitsMap>,
availabilities: &'a HashMap<ProcessID, EnergyLimitsMap>,
}

/// Perform consistency checks for commodity flows.
Expand All @@ -98,7 +98,7 @@ fn validate_commodities(
milestone_years: &[u32],
time_slice_info: &TimeSliceInfo,
parameters: &HashMap<ProcessID, ProcessParameter>,
availabilities: &HashMap<ProcessID, ActivityLimitsMap>,
availabilities: &HashMap<ProcessID, EnergyLimitsMap>,
) -> anyhow::Result<()> {
let params = ValidationParams {
flows,
Expand Down Expand Up @@ -205,7 +205,7 @@ fn validate_svd_commodity(

fn create_process_map<I>(
descriptions: I,
mut availabilities: HashMap<ProcessID, ActivityLimitsMap>,
mut availabilities: HashMap<ProcessID, EnergyLimitsMap>,
mut flows: HashMap<ProcessID, Vec<ProcessFlow>>,
mut parameters: HashMap<ProcessID, ProcessParameter>,
mut regions: HashMap<ProcessID, RegionSelection>,
Expand All @@ -232,7 +232,7 @@ where
let process = Process {
id: id.clone(),
description: description.description,
activity_limits: availabilities,
energy_limits: availabilities,
flows,
parameter,
regions,
Expand All @@ -255,7 +255,7 @@ mod tests {

struct ProcessData {
descriptions: Vec<ProcessDescription>,
availabilities: HashMap<ProcessID, ActivityLimitsMap>,
availabilities: HashMap<ProcessID, EnergyLimitsMap>,
flows: HashMap<ProcessID, Vec<ProcessFlow>>,
parameters: HashMap<ProcessID, ProcessParameter>,
regions: HashMap<ProcessID, RegionSelection>,
Expand All @@ -278,7 +278,7 @@ mod tests {
let availabilities = ["process1", "process2"]
.into_iter()
.map(|id| {
let mut map = ActivityLimitsMap::new();
let mut map = EnergyLimitsMap::new();
map.insert(
TimeSliceID {
season: "winter".into(),
Expand Down
24 changes: 13 additions & 11 deletions src/input/process/availability.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Code for reading process availabilities CSV file
use super::super::*;
use crate::id::IDCollection;
use crate::process::{ActivityLimitsMap, ProcessID};
use crate::process::{EnergyLimitsMap, ProcessID};
use crate::time_slice::TimeSliceInfo;
use anyhow::{Context, Result};
use serde::Deserialize;
Expand Down Expand Up @@ -44,25 +44,25 @@ enum LimitType {
///
/// # Returns
///
/// A [`HashMap`] with process IDs as the keys and [`ActivityLimitsMap`]s as the values or an
/// A [`HashMap`] with process IDs as the keys and [`EnergyLimitsMap`]s as the values or an
/// error.
pub fn read_process_availabilities(
model_dir: &Path,
process_ids: &HashSet<ProcessID>,
time_slice_info: &TimeSliceInfo,
) -> Result<HashMap<ProcessID, ActivityLimitsMap>> {
) -> Result<HashMap<ProcessID, EnergyLimitsMap>> {
let file_path = model_dir.join(PROCESS_AVAILABILITIES_FILE_NAME);
let process_availabilities_csv = read_csv(&file_path)?;
read_process_availabilities_from_iter(process_availabilities_csv, process_ids, time_slice_info)
.with_context(|| input_err_msg(&file_path))
}

/// Process raw process availabilities input data into [`ActivityLimitsMap`]s
/// Process raw process availabilities input data into [`EnergyLimitsMap`]s
fn read_process_availabilities_from_iter<I>(
iter: I,
process_ids: &HashSet<ProcessID>,
time_slice_info: &TimeSliceInfo,
) -> Result<HashMap<ProcessID, ActivityLimitsMap>>
) -> Result<HashMap<ProcessID, EnergyLimitsMap>>
where
I: Iterator<Item = ProcessAvailabilityRaw>,
{
Expand All @@ -78,10 +78,12 @@ where

let ts_selection = time_slice_info.get_selection(&record.time_slice)?;

let map = map.entry(process_id).or_insert_with(ActivityLimitsMap::new);
let map = map.entry(process_id).or_insert_with(EnergyLimitsMap::new);

for (time_slice, ts_length) in time_slice_info.iter_selection(&ts_selection) {
// Calculate fraction of annual capacity as availability multiplied by time slice length
// Calculate fraction of annual energy as availability multiplied by time slice length
// The resulting limits are max/min PAC energy produced/consumed in each timeslice per
// cap2act units of capacity
let value = record.value * ts_length;
let bounds = match record.limit_type {
LimitType::LowerBound => value..=f64::INFINITY,
Expand All @@ -100,14 +102,14 @@ where
}
}

validate_capacity_maps(&map, time_slice_info)?;
validate_energy_limits_maps(&map, time_slice_info)?;

Ok(map)
}

/// Check that every capacity map has an entry for every time slice
fn validate_capacity_maps(
map: &HashMap<ProcessID, ActivityLimitsMap>,
/// Check that every energy limits map has an entry for every time slice
fn validate_energy_limits_maps(
map: &HashMap<ProcessID, EnergyLimitsMap>,
time_slice_info: &TimeSliceInfo,
) -> Result<()> {
for (process_id, map) in map.iter() {
Expand Down
2 changes: 1 addition & 1 deletion src/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ mod tests {
let process = Rc::new(Process {
id: process_id,
description: "Description".into(),
activity_limits: HashMap::new(),
energy_limits: HashMap::new(),
flows: vec![],
parameter: process_param.clone(),
regions: RegionSelection::All,
Expand Down
27 changes: 14 additions & 13 deletions src/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ pub struct Process {
pub id: ProcessID,
/// A human-readable description for the process (e.g. dry gas extraction)
pub description: String,
/// The activity limits for each time slice (as a fraction of maximum)
pub activity_limits: ActivityLimitsMap,
/// Commodity flows for this process
/// Limits on PAC energy consumption/production for each time slice (as a fraction of maximum)
pub energy_limits: EnergyLimitsMap,
/// Maximum annual commodity flows for this process
pub flows: Vec<ProcessFlow>,
/// Additional parameters for this process
pub parameter: ProcessParameter,
Expand All @@ -47,24 +47,25 @@ impl Process {
}
}

/// A map indicating activity limits for a [`Process`] throughout the year.
/// A map indicating relative PAC energy limits for a [`Process`] throughout the year.
///
/// The value is calculated as availability multiplied by time slice length. Note that it is a
/// **fraction** of activity for the year; to calculate **actual** activity for a given time slice
/// you need to know the maximum activity for the specific instance of a [`Process`] in use.
/// **fraction** of energy for the year; to calculate **actual** energy limits for a given time
/// slice you need to know the maximum activity (energy per year) for the specific instance of a
/// [`Process`] in use.
///
/// The limits are given as ranges, depending on the user-specified limit type and value for
/// availability.
pub type ActivityLimitsMap = HashMap<TimeSliceID, RangeInclusive<f64>>;
pub type EnergyLimitsMap = HashMap<TimeSliceID, RangeInclusive<f64>>;

/// Represents a commodity flow for a given process
/// Represents a maximum annual commodity flow for a given process
#[derive(PartialEq, Debug, Deserialize, Clone)]
pub struct ProcessFlow {
/// A unique identifier for the process
pub process_id: String,
/// The commodity produced or consumed by this flow
pub commodity: Rc<Commodity>,
/// Commodity flow quantity relative to other commodity flows.
/// Maximum annual commodity flow quantity relative to other commodity flows.
///
/// Positive value indicates flow out and negative value indicates flow in.
pub flow: f64,
Expand Down Expand Up @@ -102,16 +103,16 @@ pub struct ProcessParameter {
pub capital_cost: f64,
/// Annual operating cost per unit capacity
pub fixed_operating_cost: f64,
/// Variable operating cost per unit activity, for PACs **only**
/// Annual variable operating cost per unit activity, for PACs **only**
pub variable_operating_cost: f64,
/// Lifetime in years of an asset created from this process
pub lifetime: u32,
/// Process-specific discount rate
pub discount_rate: f64,
/// Factor for calculating the maximum PAC output over a year.
/// Factor for calculating the maximum PAC consumption/production over a year.
///
/// Used for converting one unit of capacity to maximum activity of the PAC per year. For
/// example, if capacity is measured in GW and activity is measured in PJ, the
/// Used for converting one unit of capacity to maximum energy of the PAC(s) per year. For
/// example, if capacity is measured in GW and energy is measured in PJ, the
/// capacity_to_activity for the process is 31.536 because 1 GW of capacity can produce 31.536
/// PJ energy output in a year.
pub capacity_to_activity: f64,
Expand Down
4 changes: 2 additions & 2 deletions src/simulation/optimisation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ fn calculate_cost_coefficient(
mod tests {
use super::*;
use crate::commodity::{Commodity, CommodityCost, CommodityCostMap, CommodityType, DemandMap};
use crate::process::{ActivityLimitsMap, FlowType, Process, ProcessParameter};
use crate::process::{EnergyLimitsMap, FlowType, Process, ProcessParameter};
use crate::region::RegionSelection;
use crate::time_slice::TimeSliceLevel;
use float_cmp::assert_approx_eq;
Expand Down Expand Up @@ -296,7 +296,7 @@ mod tests {
let process = Rc::new(Process {
id: "process1".into(),
description: "Description".into(),
activity_limits: ActivityLimitsMap::new(),
energy_limits: EnergyLimitsMap::new(),
flows: vec![flow.clone()],
parameter: process_param.clone(),
regions: RegionSelection::All,
Expand Down
2 changes: 1 addition & 1 deletion src/simulation/optimisation/constraints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ fn add_asset_capacity_constraints(
terms.push((var, 1.0));
}

let mut limits = asset.get_activity_limits(time_slice);
let mut limits = asset.get_energy_limits(time_slice);

// If it's an input flow, the q's will be negative, so we need to invert the limits
if is_input {
Expand Down