diff --git a/docs/glossary.md b/docs/glossary.md
index 15ebc80d9..e0c31b3e4 100644
--- a/docs/glossary.md
+++ b/docs/glossary.md
@@ -2,9 +2,7 @@
**Activity:** The flow of input/s or output/s of a *Process* that are limited by its capacity. For
example, a 500MW power station can output 500MWh per hour of electrical power, or a 50MW
-electrolyser consumes up to 50MWh per hour of electrical power to produce hydrogen. The
-*Primary Activity Commodity* specifies which output/s or input/s are linked to the *Process*
-capacity.
+electrolyser consumes up to 50MWh per hour of electrical power to produce hydrogen.
**Agent:** A decision-making entity in the system. An *Agent* is responsible for serving a
user-specified portion of a *Commodity* demand or *Service Demand*. *Agents* invest in and operate
@@ -27,14 +25,9 @@ data, including **Process** stock and commodity consumption/production.
**Calibration:** The act of ensuring that the model represents the system being modelled in a
historical base year.
-**Capacity:** The maximum output (or input) of an *Asset*, as measured by units of the *Primary
-Activity Commodity*.
+**Capacity:** The maximum output (or input) of an *Asset*.
-**Capital Cost:** The overnight capital cost of a process, measured in units of the *Primary
-Activity Commodity* divided by CAP2ACT. CAP2ACT is a factor that converts 1 unit of capacity to
-maximum activity of the primary activity commodity/ies per year. For example, if capacity is
-measured in GW and activity is measured in PJ, CAP2ACT for the process is 31.536 because 1 GW of
-capacity can produce 31.536 PJ energy output in a year.
+**Capital Cost:** The overnight capital cost of a process.
**Commodity:** A substance (e.g. CO2) or form of energy (e.g. electricity) that can be
@@ -79,10 +72,6 @@ the next most expensive, etc, until demand is served. Also called “unit commit
**Output Commodity/ies:** The commodities that flow out of a *Process*.
-**Primary Activity Commodity (PAC):** The PACs specify which output/s are linked to the *Process*
-capacity. The combined output of all PACs cannot exceed the *Asset's* capacity. A user can define
-which output/s are PACs. Most, but not all *Process*es will have only one PAC.
-
**Process:** A blueprint of an available *Process* that converts input commodities to output
commodities. *Process*es have economic attributes of capital cost, fixed operating cost per unit
capacity, non-fuel variable operating cost per unit activity, and risk discount rate. They have
@@ -114,9 +103,8 @@ Levelised Cost of X, etc.
a model does not represent seasons or within-day (diurnal) variation). A typical model will have
several diurnal time slices, and several seasonal time slices.
-**Utilisation:** The percentage of an *Asset*s capacity that is actually used to produce *Primary
-Activity Commodities*. Must be between 0 and 1, and can be measured at time slice, season, or year
-level.
+**Utilisation:** The percentage of an *Asset*'s capacity that is actually used to produce its
+commodities. Must be between 0 and 1, and can be measured at time slice, season, or year level.
-**Variable Operating Cost:** The variable operating cost charged per unit of input or output of the
-*Primary Activity Commodity* of the *Process*.
+**Variable Operating Cost:** The variable operating cost charged per unit of activity of the
+ *Process*.
diff --git a/examples/simple/process_flows.csv b/examples/simple/process_flows.csv
index f1ae84f24..88c03c51a 100644
--- a/examples/simple/process_flows.csv
+++ b/examples/simple/process_flows.csv
@@ -1,15 +1,15 @@
-process_id,commodity_id,regions,years,coeff,type,cost,is_pac
-GASDRV,GASPRD,all,all,1.0,fixed,,true
-GASPRC,GASPRD,all,all,-1.05,fixed,,false
-GASPRC,GASNAT,all,all,1.0,fixed,,true
-WNDFRM,ELCTRI,all,all,1.0,fixed,,true
-GASCGT,GASNAT,all,all,-1.5,fixed,,false
-GASCGT,ELCTRI,all,all,1.0,fixed,,true
-RGASBR,GASNAT,all,all,-1.15,fixed,,false
-RGASBR,RSHEAT,all,all,1.0,fixed,,true
-RELCHP,ELCTRI,all,all,-0.33,fixed,,true
-RELCHP,RSHEAT,all,all,1.0,fixed,,false
-GASDRV,CO2EMT,all,all,5.113,fixed,,false
-GASPRC,CO2EMT,all,all,2.5565,fixed,,false
-GASCGT,CO2EMT,all,all,76.695,fixed,,false
-RGASBR,CO2EMT,all,all,58.7995,fixed,,false
+process_id,commodity_id,regions,years,coeff,type,cost
+GASDRV,GASPRD,all,all,1.0,fixed,
+GASPRC,GASPRD,all,all,-1.05,fixed,
+GASPRC,GASNAT,all,all,1.0,fixed,
+WNDFRM,ELCTRI,all,all,1.0,fixed,
+GASCGT,GASNAT,all,all,-1.5,fixed,
+GASCGT,ELCTRI,all,all,1.0,fixed,
+RGASBR,GASNAT,all,all,-1.15,fixed,
+RGASBR,RSHEAT,all,all,1.0,fixed,
+RELCHP,ELCTRI,all,all,-0.33,fixed,
+RELCHP,RSHEAT,all,all,1.0,fixed,
+GASDRV,CO2EMT,all,all,5.113,fixed,
+GASPRC,CO2EMT,all,all,2.5565,fixed,
+GASCGT,CO2EMT,all,all,76.695,fixed,
+RGASBR,CO2EMT,all,all,58.7995,fixed,
diff --git a/schemas/input/process_flows.yaml b/schemas/input/process_flows.yaml
index d75347af8..2a2ad5f18 100644
--- a/schemas/input/process_flows.yaml
+++ b/schemas/input/process_flows.yaml
@@ -5,8 +5,6 @@ notes:
- Commodity flows can vary by region and year.
- For each process, there must be entries covering all the years and regions in which the process
operates.
- - One (and only one) commodity flow for each region/year must be designated as the primary
- activity commodity (PAC).
fields:
- name: process_id
@@ -38,9 +36,3 @@ fields:
type: number
description: The cost per unit flow
notes: Optional. If present, must be >0.
- - name: is_pac
- type: boolean
- description: Whether this commodity flow is a primary activity commodity (PAC)
- notes: |
- One and only one commodity flow can be a PAC for a given combination of region and milestone
- year
diff --git a/schemas/input/process_parameters.yaml b/schemas/input/process_parameters.yaml
index ccf92578e..313a96dd1 100644
--- a/schemas/input/process_parameters.yaml
+++ b/schemas/input/process_parameters.yaml
@@ -27,7 +27,7 @@ fields:
description: Annual operating cost per unit capacity
- name: variable_operating_cost
type: number
- description: Annual variable operating cost per unit activity, for PACs **only**
+ description: Annual variable operating cost per unit activity
- name: lifetime
type: integer
description: Lifetime in years of an asset created from this process
@@ -38,5 +38,5 @@ fields:
notes: Must be positive. A warning will be issued if this number is >1.
- name: capacity_to_activity
type: number
- description: Factor for calculating the maximum PAC consumption/production over a year.
+ description: Factor for calculating the maximum consumption/production over a year.
notes: Must be >=0
diff --git a/src/asset.rs b/src/asset.rs
index 1befaf649..6a0187c67 100644
--- a/src/asset.rs
+++ b/src/asset.rs
@@ -85,13 +85,11 @@ impl Asset {
self.commission_year + self.process_parameter.lifetime
}
- /// 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 {
+ /// Get the activity limits for this asset in a particular time slice
+ pub fn get_activity_limits(&self, time_slice: &TimeSliceID) -> RangeInclusive {
let limits = self
.process
- .energy_limits
+ .activity_limits
.get(&(
self.region_id.clone(),
self.commission_year,
@@ -100,12 +98,11 @@ impl Asset {
.unwrap();
let max_act = self.maximum_activity();
- // Multiply the fractional energy limits by this asset's maximum activity to get energy
// limits in real units (which are user defined)
(max_act * limits.start())..=(max_act * limits.end())
}
- /// Maximum activity for this asset (PAC energy produced/consumed per year)
+ /// Maximum activity for this asset
pub fn maximum_activity(&self) -> f64 {
self.capacity * self.process_parameter.capacity_to_activity
}
@@ -127,12 +124,6 @@ impl Asset {
pub fn iter_flows(&self) -> impl Iterator- {
self.get_flows_map().values()
}
-
- /// Iterate over the asset's Primary Activity Commodity flows
- pub fn iter_pacs(&self) -> impl Iterator
- {
- self.process
- .iter_pacs(&self.region_id, self.commission_year)
- }
}
/// A wrapper around [`Asset`] for storing references in maps.
@@ -318,7 +309,7 @@ mod tests {
use super::*;
use crate::fixture::{assert_error, process};
use crate::process::{
- Process, ProcessEnergyLimitsMap, ProcessFlowsMap, ProcessParameter, ProcessParameterMap,
+ Process, ProcessActivityLimitsMap, ProcessFlowsMap, ProcessParameter, ProcessParameterMap,
};
use itertools::{assert_equal, Itertools};
use rstest::{fixture, rstest};
@@ -393,7 +384,7 @@ mod tests {
id: "process1".into(),
description: "Description".into(),
years: vec![2010, 2020],
- energy_limits: ProcessEnergyLimitsMap::new(),
+ activity_limits: ProcessActivityLimitsMap::new(),
flows: ProcessFlowsMap::new(),
parameters: process_parameter_map,
regions: HashSet::from(["GBR".into()]),
@@ -416,7 +407,7 @@ mod tests {
}
#[test]
- fn test_asset_get_energy_limits() {
+ fn test_asset_get_activity_limits() {
let time_slice = TimeSliceID {
season: "winter".into(),
time_of_day: "day".into(),
@@ -435,9 +426,9 @@ mod tests {
.map(|&year| (("GBR".into(), year), process_param.clone()))
.collect();
let fraction_limits = 1.0..=f64::INFINITY;
- let mut energy_limits = ProcessEnergyLimitsMap::new();
+ let mut activity_limits = ProcessActivityLimitsMap::new();
for year in [2010, 2020] {
- energy_limits.insert(
+ activity_limits.insert(
("GBR".into(), year, time_slice.clone()),
fraction_limits.clone(),
);
@@ -446,7 +437,7 @@ mod tests {
id: "process1".into(),
description: "Description".into(),
years: vec![2010, 2020],
- energy_limits,
+ activity_limits,
flows: ProcessFlowsMap::new(),
parameters: process_parameter_map,
regions: HashSet::from(["GBR".into()]),
@@ -460,7 +451,7 @@ mod tests {
)
.unwrap();
- assert_eq!(asset.get_energy_limits(&time_slice), 6.0..=f64::INFINITY);
+ assert_eq!(asset.get_activity_limits(&time_slice), 6.0..=f64::INFINITY);
}
#[rstest]
diff --git a/src/fixture.rs b/src/fixture.rs
index 1657b6ee8..81f44af72 100644
--- a/src/fixture.rs
+++ b/src/fixture.rs
@@ -7,7 +7,7 @@ use crate::agent::{
use crate::asset::{Asset, AssetPool};
use crate::commodity::{Commodity, CommodityID, CommodityLevyMap, CommodityType, DemandMap};
use crate::process::{
- Process, ProcessEnergyLimitsMap, ProcessFlowsMap, ProcessMap, ProcessParameter,
+ Process, ProcessActivityLimitsMap, ProcessFlowsMap, ProcessMap, ProcessParameter,
ProcessParameterMap,
};
use crate::region::RegionID;
@@ -114,7 +114,7 @@ pub fn process(
id: "process1".into(),
description: "Description".into(),
years: vec![2010, 2020],
- energy_limits: ProcessEnergyLimitsMap::new(),
+ activity_limits: ProcessActivityLimitsMap::new(),
flows: ProcessFlowsMap::new(),
parameters: process_parameter_map,
regions: region_ids,
diff --git a/src/input/agent/search_space.rs b/src/input/agent/search_space.rs
index 62cab44d8..6a10d0b34 100644
--- a/src/input/agent/search_space.rs
+++ b/src/input/agent/search_space.rs
@@ -157,7 +157,9 @@ where
mod tests {
use super::*;
use crate::fixture::{agents, assert_error, region_ids};
- use crate::process::{ProcessEnergyLimitsMap, ProcessFlowsMap, ProcessID, ProcessParameterMap};
+ use crate::process::{
+ ProcessActivityLimitsMap, ProcessFlowsMap, ProcessID, ProcessParameterMap,
+ };
use crate::region::RegionID;
use rstest::{fixture, rstest};
use std::iter;
@@ -171,7 +173,7 @@ mod tests {
id: id.clone(),
description: "Description".into(),
years: vec![2010, 2020],
- energy_limits: ProcessEnergyLimitsMap::new(),
+ activity_limits: ProcessActivityLimitsMap::new(),
flows: ProcessFlowsMap::new(),
parameters: ProcessParameterMap::new(),
regions: region_ids.clone(),
diff --git a/src/input/process.rs b/src/input/process.rs
index 227a05b72..e9419e2b3 100644
--- a/src/input/process.rs
+++ b/src/input/process.rs
@@ -2,7 +2,7 @@
use super::*;
use crate::commodity::{Commodity, CommodityID, CommodityMap, CommodityType};
use crate::process::{
- Process, ProcessEnergyLimitsMap, ProcessFlowsMap, ProcessID, ProcessMap, ProcessParameterMap,
+ Process, ProcessActivityLimitsMap, ProcessFlowsMap, ProcessID, ProcessMap, ProcessParameterMap,
};
use crate::region::{parse_region_str, RegionID};
use crate::time_slice::{TimeSliceInfo, TimeSliceSelection};
@@ -55,7 +55,7 @@ pub fn read_processes(
let mut processes = read_processes_file(model_dir, milestone_years, region_ids)?;
let process_ids = processes.keys().cloned().collect();
- let mut energy_limits =
+ let mut activity_limits =
read_process_availabilities(model_dir, &process_ids, &processes, time_slice_info)?;
let mut flows = read_process_flows(model_dir, &process_ids, &processes, commodities)?;
let mut parameters = read_process_parameters(model_dir, &process_ids, &processes)?;
@@ -64,7 +64,7 @@ pub fn read_processes(
validate_commodities(
commodities,
&flows,
- &energy_limits,
+ &activity_limits,
region_ids,
milestone_years,
time_slice_info,
@@ -72,7 +72,7 @@ pub fn read_processes(
// Add data to Process objects
for (id, process) in processes.iter_mut() {
- process.energy_limits = energy_limits
+ process.activity_limits = activity_limits
.remove(id)
.with_context(|| format!("Missing availabilities for process {id}"))?;
process.flows = flows
@@ -139,7 +139,7 @@ where
id: process_raw.id.clone(),
description: process_raw.description,
years,
- energy_limits: ProcessEnergyLimitsMap::new(),
+ activity_limits: ProcessActivityLimitsMap::new(),
flows: ProcessFlowsMap::new(),
parameters: ProcessParameterMap::new(),
regions,
@@ -158,7 +158,7 @@ where
fn validate_commodities(
commodities: &CommodityMap,
flows: &HashMap,
- availabilities: &HashMap,
+ availabilities: &HashMap,
region_ids: &HashSet,
milestone_years: &[u32],
time_slice_info: &TimeSliceInfo,
@@ -227,7 +227,7 @@ fn validate_svd_commodity(
time_slice_info: &TimeSliceInfo,
commodity: &Commodity,
flows: &HashMap,
- availabilities: &HashMap,
+ availabilities: &HashMap,
region_id: &RegionID,
year: &u32,
ts_selection: &TimeSliceSelection,
@@ -308,7 +308,6 @@ mod tests {
coeff: -10.0,
kind: FlowType::Fixed,
cost: 1.0,
- is_pac: false,
}},
)])
}
@@ -322,7 +321,6 @@ mod tests {
coeff: 10.0,
kind: FlowType::Fixed,
cost: 1.0,
- is_pac: false,
}},
)])
}
@@ -383,7 +381,6 @@ mod tests {
coeff: 10.0,
kind: FlowType::Fixed,
cost: 1.0,
- is_pac: false,
}},
)]),
)])
@@ -398,7 +395,7 @@ mod tests {
) {
let availabilities = HashMap::from_iter(vec![(
"process1".into(),
- ProcessEnergyLimitsMap::from_iter(vec![(
+ ProcessActivityLimitsMap::from_iter(vec![(
("GBR".into(), 2010, time_slice.clone()),
0.1..=0.9,
)]),
@@ -427,7 +424,7 @@ mod tests {
// Invalid scenario: no availability
let availabilities = HashMap::from_iter(vec![(
"process1".into(),
- ProcessEnergyLimitsMap::from_iter(vec![(
+ ProcessActivityLimitsMap::from_iter(vec![(
("GBR".into(), 2010, time_slice.clone()),
0.0..=0.0,
)]),
diff --git a/src/input/process/availability.rs b/src/input/process/availability.rs
index dd17079c1..b9a16b81c 100644
--- a/src/input/process/availability.rs
+++ b/src/input/process/availability.rs
@@ -1,7 +1,7 @@
//! Code for reading process availabilities CSV file
use super::super::*;
use crate::id::IDCollection;
-use crate::process::{Process, ProcessEnergyLimitsMap, ProcessID};
+use crate::process::{Process, ProcessActivityLimitsMap, ProcessID};
use crate::region::parse_region_str;
use crate::time_slice::TimeSliceInfo;
use crate::year::parse_year_str;
@@ -39,8 +39,8 @@ impl ProcessAvailabilityRaw {
/// 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
+ /// The resulting limits are max/min energy produced/consumed in each timeslice per
+ /// `capacity_to_activity` units of capacity.
fn to_bounds(&self, ts_length: f64) -> RangeInclusive {
let value = self.value * ts_length;
match self.limit_type {
@@ -75,14 +75,14 @@ enum LimitType {
///
/// # Returns
///
-/// A [`HashMap`] with process IDs as the keys and [`ProcessEnergyLimitsMap`]s as the values or an
+/// A [`HashMap`] with process IDs as the keys and [`ProcessActivityLimitsMap`]s as the values or an
/// error.
pub fn read_process_availabilities(
model_dir: &Path,
process_ids: &IndexSet,
processes: &HashMap,
time_slice_info: &TimeSliceInfo,
-) -> Result> {
+) -> Result> {
let file_path = model_dir.join(PROCESS_AVAILABILITIES_FILE_NAME);
let process_availabilities_csv = read_csv(&file_path)?;
read_process_availabilities_from_iter(
@@ -94,13 +94,13 @@ pub fn read_process_availabilities(
.with_context(|| input_err_msg(&file_path))
}
-/// Process raw process availabilities input data into [`ProcessEnergyLimitsMap`]s
+/// Process raw process availabilities input data into [`ProcessActivityLimitsMap`]s
fn read_process_availabilities_from_iter(
iter: I,
process_ids: &IndexSet,
processes: &HashMap,
time_slice_info: &TimeSliceInfo,
-) -> Result>
+) -> Result>
where
I: Iterator
- ,
{
@@ -130,10 +130,10 @@ where
// Get timeslices
let ts_selection = time_slice_info.get_selection(&record.time_slice)?;
- // Insert the energy limit into the map
+ // Insert the activity limit into the map
let entry = map
.entry(id.clone())
- .or_insert_with(ProcessEnergyLimitsMap::new);
+ .or_insert_with(ProcessActivityLimitsMap::new);
for (time_slice, ts_length) in ts_selection.iter(time_slice_info) {
let bounds = record.to_bounds(ts_length);
@@ -149,14 +149,14 @@ where
}
}
- validate_energy_limits_maps(&map, processes, time_slice_info)?;
+ validate_activity_limits_maps(&map, processes, time_slice_info)?;
Ok(map)
}
-/// Check that every energy limits covers every time slice, and all regions/years of the process
-fn validate_energy_limits_maps(
- map: &HashMap,
+/// Check that the activity limits cover every time slice and all regions/years of the process
+fn validate_activity_limits_maps(
+ map: &HashMap,
processes: &HashMap,
time_slice_info: &TimeSliceInfo,
) -> Result<()> {
diff --git a/src/input/process/flow.rs b/src/input/process/flow.rs
index 6e32539a9..21f934c31 100644
--- a/src/input/process/flow.rs
+++ b/src/input/process/flow.rs
@@ -1,6 +1,6 @@
//! Code for reading process flows file
use super::super::*;
-use crate::commodity::{CommodityID, CommodityMap};
+use crate::commodity::CommodityMap;
use crate::id::IDCollection;
use crate::process::{FlowType, Process, ProcessFlow, ProcessFlowsMap, ProcessID};
use crate::region::parse_region_str;
@@ -25,7 +25,6 @@ struct ProcessFlowRaw {
#[serde(rename = "type")]
kind: FlowType,
cost: Option,
- is_pac: bool,
}
impl ProcessFlowRaw {
@@ -112,7 +111,6 @@ where
coeff: record.coeff,
kind: record.kind,
cost: record.cost.unwrap_or(0.0),
- is_pac: record.is_pac,
};
// Insert flow into the map
@@ -134,63 +132,26 @@ where
}
}
- // Validate flows and sort flows so PACs are at the start
for (process_id, map) in map.iter_mut() {
let process = processes.get(process_id).unwrap();
validate_process_flows_map(process, map)?;
- sort_flows(map);
}
Ok(map)
}
-/// Sort flows so PACs come first
-fn sort_flows(map: &mut ProcessFlowsMap) {
- for map in map.values_mut() {
- map.sort_by(|_, a, _, b| b.is_pac.cmp(&a.is_pac));
- }
-}
-
/// Validate flows for a process
fn validate_process_flows_map(process: &Process, map: &ProcessFlowsMap) -> Result<()> {
let process_id = process.id.clone();
- let reference_years = &process.years;
- let reference_regions = &process.regions;
- for year in reference_years.iter() {
- for region in reference_regions {
+ for year in process.years.iter() {
+ for region in process.regions.iter() {
// Check that the process has flows for this region/year
- let flow_map = map.get(&(region.clone(), *year)).with_context(|| {
- format!("Missing entry for process {process_id} in {region}/{year}")
- })?;
-
- // Validate flows for this process/region/year
- validate_flow_map(flow_map).with_context(|| {
- format!("Invalid flows for process {process_id} in {region}/{year}")
- })?;
- }
- }
- Ok(())
-}
-
-/// Validate a vector of flows for a process in a given region/year
-fn validate_flow_map(flow_map: &IndexMap) -> Result<()> {
- // PACs must be either all inputs or all outputs
- let mut flow_sign: Option = None; // False for inputs, true for outputs
- for flow in flow_map.values().filter(|flow| flow.is_pac) {
- // Check that flow sign is consistent
- let current_flow_sign = flow.coeff > 0.0;
- if let Some(flow_sign) = flow_sign {
ensure!(
- current_flow_sign == flow_sign,
- "PACs are a mix of inputs and outputs",
+ map.contains_key(&(region.clone(), *year)),
+ "Missing entry for process {process_id} in {region}/{year}"
);
}
- flow_sign = Some(current_flow_sign);
}
-
- // Check that at least one PAC is defined
- ensure!(flow_sign.is_some(), "No PACs defined");
-
Ok(())
}
@@ -199,15 +160,10 @@ mod tests {
use super::*;
use crate::commodity::{Commodity, CommodityLevyMap, CommodityType, DemandMap};
use crate::time_slice::TimeSliceLevel;
- use indexmap::indexmap;
- use rstest::{fixture, rstest};
- fn create_process_flow_raw(
- coeff: f64,
- kind: FlowType,
- cost: Option,
- is_pac: bool,
- ) -> ProcessFlowRaw {
+ use rstest::fixture;
+
+ fn create_process_flow_raw(coeff: f64, kind: FlowType, cost: Option) -> ProcessFlowRaw {
ProcessFlowRaw {
process_id: "process".into(),
commodity_id: "commodity".into(),
@@ -216,45 +172,34 @@ mod tests {
coeff,
kind,
cost,
- is_pac,
}
}
#[test]
fn test_validate_flow_raw() {
// Valid
- let valid = create_process_flow_raw(1.0, FlowType::Fixed, Some(0.0), true);
+ let valid = create_process_flow_raw(1.0, FlowType::Fixed, Some(0.0));
assert!(valid.validate().is_ok());
// Invalid: Bad flow value
- let invalid = create_process_flow_raw(0.0, FlowType::Fixed, Some(0.0), true);
+ let invalid = create_process_flow_raw(0.0, FlowType::Fixed, Some(0.0));
assert!(invalid.validate().is_err());
- let invalid = create_process_flow_raw(f64::NAN, FlowType::Fixed, Some(0.0), true);
+ let invalid = create_process_flow_raw(f64::NAN, FlowType::Fixed, Some(0.0));
assert!(invalid.validate().is_err());
- let invalid = create_process_flow_raw(f64::INFINITY, FlowType::Fixed, Some(0.0), true);
+ let invalid = create_process_flow_raw(f64::INFINITY, FlowType::Fixed, Some(0.0));
assert!(invalid.validate().is_err());
- let invalid = create_process_flow_raw(f64::NEG_INFINITY, FlowType::Fixed, Some(0.0), true);
+ let invalid = create_process_flow_raw(f64::NEG_INFINITY, FlowType::Fixed, Some(0.0));
assert!(invalid.validate().is_err());
// Invalid: Bad flow cost value
- let invalid = create_process_flow_raw(1.0, FlowType::Fixed, Some(f64::NAN), true);
+ let invalid = create_process_flow_raw(1.0, FlowType::Fixed, Some(f64::NAN));
assert!(invalid.validate().is_err());
- let invalid = create_process_flow_raw(1.0, FlowType::Fixed, Some(f64::NEG_INFINITY), true);
+ let invalid = create_process_flow_raw(1.0, FlowType::Fixed, Some(f64::NEG_INFINITY));
assert!(invalid.validate().is_err());
- let invalid = create_process_flow_raw(1.0, FlowType::Fixed, Some(f64::INFINITY), true);
+ let invalid = create_process_flow_raw(1.0, FlowType::Fixed, Some(f64::INFINITY));
assert!(invalid.validate().is_err());
}
- fn create_process_flow(commodity: Rc, coeff: f64, is_pac: bool) -> ProcessFlow {
- ProcessFlow {
- commodity,
- coeff,
- kind: FlowType::Fixed,
- cost: 0.0,
- is_pac,
- }
- }
-
#[fixture]
fn commodity1() -> Commodity {
Commodity {
@@ -278,44 +223,4 @@ mod tests {
levies: CommodityLevyMap::default(),
}
}
-
- #[rstest]
- fn test_validate_flow_map_valid_single(commodity1: Commodity, commodity2: Commodity) {
- // Valid: Single PAC
- let flows = indexmap! {
- commodity1.id.clone() => create_process_flow(commodity1.into(), 1.0, true),
- commodity2.id.clone() => create_process_flow(commodity2.into(), 1.0, false),
- };
- assert!(validate_flow_map(&flows).is_ok());
- }
-
- #[rstest]
- fn test_validate_flow_map_valid_multiple(commodity1: Commodity, commodity2: Commodity) {
- // Valid: Multiple PACs
- let flows = indexmap! {
- commodity1.id.clone() => create_process_flow(commodity1.into(), 1.0, true),
- commodity2.id.clone() => create_process_flow(commodity2.into(), 1.0, true),
- };
- assert!(validate_flow_map(&flows).is_ok());
- }
-
- #[rstest]
- fn test_validate_flow_map_invalid_no_pacs(commodity1: Commodity, commodity2: Commodity) {
- // Invalid: No PACs
- let flows = indexmap! {
- commodity1.id.clone() => create_process_flow(commodity1.into(), 1.0, false),
- commodity2.id.clone() => create_process_flow(commodity2.into(), 1.0, false),
- };
- assert!(validate_flow_map(&flows).is_err());
- }
-
- #[rstest]
- fn test_validate_flow_map(commodity1: Commodity, commodity2: Commodity) {
- // Invalid: Mixed PAC flow types
- let flows = indexmap! {
- commodity1.id.clone() => create_process_flow(commodity1.into(), 1.0, true),
- commodity2.id.clone() => create_process_flow(commodity2.into(), -1.0, true),
- };
- assert!(validate_flow_map(&flows).is_err());
- }
}
diff --git a/src/process.rs b/src/process.rs
index cf1e99dc7..7da590a10 100644
--- a/src/process.rs
+++ b/src/process.rs
@@ -15,16 +15,11 @@ define_id_type! {ProcessID}
/// A map of [`Process`]es, keyed by process ID
pub type ProcessMap = IndexMap>;
-/// A map indicating relative PAC energy limits for a [`Process`] throughout the year.
+/// A map indicating activity 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 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 ProcessEnergyLimitsMap = HashMap<(RegionID, u32, TimeSliceID), RangeInclusive>;
+/// The value is calculated as availability multiplied by time slice length. The limits are given as
+/// ranges, depending on the user-specified limit type and value for availability.
+pub type ProcessActivityLimitsMap = HashMap<(RegionID, u32, TimeSliceID), RangeInclusive>;
/// A map of [`ProcessParameter`]s, keyed by region and year
pub type ProcessParameterMap = HashMap<(RegionID, u32), Rc>;
@@ -43,8 +38,8 @@ pub struct Process {
pub description: String,
/// The years in which this process is available for investment
pub years: Vec,
- /// Limits on PAC energy consumption/production for each time slice (as a fraction of maximum)
- pub energy_limits: ProcessEnergyLimitsMap,
+ /// Limits on activity for each time slice (as a fraction of maximum)
+ pub activity_limits: ProcessActivityLimitsMap,
/// Maximum annual commodity flows for this process
pub flows: ProcessFlowsMap,
/// Additional parameters for this process
@@ -66,15 +61,6 @@ impl Process {
.unwrap() // all regions and years are covered
.contains_key(commodity_id)
}
-
- /// Iterate over this process's Primary Activity Commodity flows
- pub fn iter_pacs(&self, region_id: &RegionID, year: u32) -> impl Iterator
- {
- self.flows
- .get(&(region_id.clone(), year))
- .unwrap()
- .values()
- .take_while(|flow| flow.is_pac)
- }
}
/// Represents a maximum annual commodity coeff for a given process
@@ -91,11 +77,8 @@ pub struct ProcessFlow {
/// Cost per unit flow.
///
/// For example, cost per unit of natural gas produced. The user can apply it to any specified
- /// flow, in contrast to [`ProcessParameter::variable_operating_cost`], which applies only to
- /// PAC flows.
+ /// flow.
pub cost: f64,
- /// Whether this flow represents a Primary Activity Commodity
- pub is_pac: bool,
}
impl ProcessFlow {
@@ -153,18 +136,17 @@ pub struct ProcessParameter {
pub capital_cost: f64,
/// Annual operating cost per unit capacity
pub fixed_operating_cost: f64,
- /// Annual variable operating cost per unit activity, for PACs **only**
+ /// Annual variable operating cost per unit activity
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 consumption/production over a year.
+ /// Factor for calculating the maximum consumption/production over a year.
///
- /// 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.
+ /// Used for converting one unit of capacity to maximum energy of asset 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,
}
@@ -327,7 +309,6 @@ mod tests {
coeff: 1.0,
kind: FlowType::Fixed,
cost: 5.0,
- is_pac: true,
}
}
@@ -354,7 +335,6 @@ mod tests {
coeff: 1.0,
kind: FlowType::Fixed,
cost: 5.0,
- is_pac: true,
}
}
@@ -381,7 +361,6 @@ mod tests {
coeff: 1.0,
kind: FlowType::Fixed,
cost: 5.0,
- is_pac: true,
}
}
@@ -396,7 +375,6 @@ mod tests {
coeff: 1.0,
kind: FlowType::Fixed,
cost: 0.0,
- is_pac: true,
};
assert_eq!(flow.get_levy(®ion_id, 2020, &time_slice), 0.0);
@@ -413,7 +391,6 @@ mod tests {
coeff: 1.0,
kind: FlowType::Fixed,
cost: 0.0,
- is_pac: true,
};
assert_eq!(flow.get_levy(®ion_id, 2020, &time_slice), 10.0);
@@ -430,7 +407,6 @@ mod tests {
coeff: 1.0,
kind: FlowType::Fixed,
cost: 0.0,
- is_pac: true,
};
assert_eq!(flow.get_levy(®ion_id, 2020, &time_slice), -5.0);
@@ -443,7 +419,6 @@ mod tests {
coeff: 1.0,
kind: FlowType::Fixed,
cost: 0.0,
- is_pac: true,
};
assert_eq!(flow.get_levy(&"USA".into(), 2020, &time_slice), 5.0);
@@ -460,7 +435,6 @@ mod tests {
coeff: 1.0,
kind: FlowType::Fixed,
cost: 0.0,
- is_pac: true,
};
assert_eq!(flow.get_levy(®ion_id, 2030, &time_slice), 7.0);
@@ -473,7 +447,6 @@ mod tests {
coeff: 1.0,
kind: FlowType::Fixed,
cost: 0.0,
- is_pac: true,
};
let different_time_slice = TimeSliceID {
@@ -495,7 +468,6 @@ mod tests {
coeff: 1.0, // Positive coefficient means production
kind: FlowType::Fixed,
cost: 0.0,
- is_pac: true,
};
assert_eq!(flow.get_levy(®ion_id, 2020, &time_slice), 0.0);
@@ -512,7 +484,6 @@ mod tests {
coeff: -1.0, // Negative coefficient means consumption
kind: FlowType::Fixed,
cost: 0.0,
- is_pac: true,
};
assert_eq!(flow.get_levy(®ion_id, 2020, &time_slice), 10.0);
@@ -529,7 +500,6 @@ mod tests {
coeff: 1.0, // Positive coefficient means production
kind: FlowType::Fixed,
cost: 0.0,
- is_pac: true,
};
assert_eq!(flow.get_levy(®ion_id, 2020, &time_slice), 10.0);
@@ -546,7 +516,6 @@ mod tests {
coeff: -1.0, // Negative coefficient means consumption
kind: FlowType::Fixed,
cost: 0.0,
- is_pac: true,
};
assert_eq!(flow.get_levy(®ion_id, 2020, &time_slice), 0.0);
diff --git a/src/simulation/optimisation/constraints.rs b/src/simulation/optimisation/constraints.rs
index 3db818ba4..da69fe885 100644
--- a/src/simulation/optimisation/constraints.rs
+++ b/src/simulation/optimisation/constraints.rs
@@ -103,8 +103,8 @@ fn add_commodity_balance_constraints(
/// Add asset-level capacity and availability constraints.
///
-/// For every asset at every time slice, the sum of the commodity flows for PACs must not exceed the
-/// capacity limits, which are a product of the annual capacity, time slice length and process
+/// For every asset at every time slice, the sum of the commodity flows for assets must not exceed
+/// the capacity limits, which are a product of the annual capacity, time slice length and process
/// availability.
///
/// See description in [the dispatch optimisation documentation][1].