Skip to content

Commit fd4fb22

Browse files
committed
Remove PACs from code and documentation
Closes #596.
1 parent 542baff commit fd4fb22

9 files changed

Lines changed: 38 additions & 189 deletions

File tree

docs/glossary.md

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@
22

33
**Activity:** The flow of input/s or output/s of a *Process* that are limited by its capacity. For
44
example, a 500MW power station can output 500MWh per hour of electrical power, or a 50MW
5-
electrolyser consumes up to 50MWh per hour of electrical power to produce hydrogen. The
6-
*Primary Activity Commodity* specifies which output/s or input/s are linked to the *Process*
7-
capacity.
5+
electrolyser consumes up to 50MWh per hour of electrical power to produce hydrogen.
86

97
**Agent:** A decision-making entity in the system. An *Agent* is responsible for serving a
108
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.
2725
**Calibration:** The act of ensuring that the model represents the system being modelled in a
2826
historical base year.
2927

30-
**Capacity:** The maximum output (or input) of an *Asset*, as measured by units of the *Primary
31-
Activity Commodity*.
28+
**Capacity:** The maximum output (or input) of an *Asset*.
3229

33-
**Capital Cost:** The overnight capital cost of a process, measured in units of the *Primary
34-
Activity Commodity* divided by CAP2ACT. CAP2ACT is a factor that converts 1 unit of capacity to
35-
maximum activity of the primary activity commodity/ies per year. For example, if capacity is
36-
measured in GW and activity is measured in PJ, CAP2ACT for the process is 31.536 because 1 GW of
37-
capacity can produce 31.536 PJ energy output in a year.
30+
**Capital Cost:** The overnight capital cost of a process.
3831

3932
<!-- markdownlint-disable-next-line MD033 -->
4033
**Commodity:** A substance (e.g. CO<sub>2</sub>) 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
7972

8073
**Output Commodity/ies:** The commodities that flow out of a *Process*.
8174

82-
**Primary Activity Commodity (PAC):** The PACs specify which output/s are linked to the *Process*
83-
capacity. The combined output of all PACs cannot exceed the *Asset's* capacity. A user can define
84-
which output/s are PACs. Most, but not all *Process*es will have only one PAC.
85-
8675
**Process:** A blueprint of an available *Process* that converts input commodities to output
8776
commodities. *Process*es have economic attributes of capital cost, fixed operating cost per unit
8877
capacity, non-fuel variable operating cost per unit activity, and risk discount rate. They have
@@ -114,9 +103,8 @@ Levelised Cost of X, etc.
114103
a model does not represent seasons or within-day (diurnal) variation). A typical model will have
115104
several diurnal time slices, and several seasonal time slices.
116105

117-
**Utilisation:** The percentage of an *Asset*s capacity that is actually used to produce *Primary
118-
Activity Commodities*. Must be between 0 and 1, and can be measured at time slice, season, or year
119-
level.
106+
**Utilisation:** The percentage of an *Asset*'s capacity that is actually used to produce its
107+
commodities. Must be between 0 and 1, and can be measured at time slice, season, or year level.
120108

121109
**Variable Operating Cost:** The variable operating cost charged per unit of input or output of the
122-
*Primary Activity Commodity* of the *Process*.
110+
*Process*.

schemas/input/process_flows.yaml

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,6 @@ description: |
33
Commodity flows can vary by region and year. For each process, there must be entries covering all
44
the years and regions in which the process operates.
55
6-
One (and only one) commodity flow for each region/year must be designated as the primary activity
7-
commodity (PAC).
8-
96
fields:
107
- name: process_id
118
type: string
@@ -36,9 +33,3 @@ fields:
3633
type: number
3734
title: The cost per unit flow
3835
description: Optional. If present, must be >0.
39-
- name: is_pac
40-
type: boolean
41-
title: Whether this commodity flow is a primary activity commodity (PAC)
42-
description: |
43-
One and only one commodity flow can be a PAC for a given combination of region and milestone
44-
year

schemas/input/process_parameters.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ fields:
2727
title: Annual operating cost per unit capacity
2828
- name: variable_operating_cost
2929
type: number
30-
title: Annual variable operating cost per unit activity, for PACs **only**
30+
title: Annual variable operating cost per unit activity
3131
- name: lifetime
3232
type: integer
3333
title: Lifetime in years of an asset created from this process
@@ -38,5 +38,5 @@ fields:
3838
description: Must be positive. A warning will be issued if this number is >1.
3939
- name: capacity_to_activity
4040
type: number
41-
title: Factor for calculating the maximum PAC consumption/production over a year.
41+
title: Factor for calculating the maximum consumption/production over a year.
4242
description: Must be >=0

src/asset.rs

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ impl Asset {
8686

8787
/// Get the energy limits for this asset in a particular time slice
8888
///
89-
/// This is an absolute max and min on the PAC energy produced/consumed in that time slice.
89+
/// This is an absolute max and min on the energy produced/consumed in that time slice.
9090
pub fn get_energy_limits(&self, time_slice: &TimeSliceID) -> RangeInclusive<f64> {
9191
let limits = self
9292
.process
@@ -104,7 +104,7 @@ impl Asset {
104104
(max_act * limits.start())..=(max_act * limits.end())
105105
}
106106

107-
/// Maximum activity for this asset (PAC energy produced/consumed per year)
107+
/// Maximum activity for this asset (energy produced/consumed per year)
108108
pub fn maximum_activity(&self) -> f64 {
109109
self.capacity * self.process_parameter.capacity_to_activity
110110
}
@@ -126,12 +126,6 @@ impl Asset {
126126
pub fn iter_flows(&self) -> impl Iterator<Item = &ProcessFlow> {
127127
self.get_flows_map().values()
128128
}
129-
130-
/// Iterate over the asset's Primary Activity Commodity flows
131-
pub fn iter_pacs(&self) -> impl Iterator<Item = &ProcessFlow> {
132-
self.process
133-
.iter_pacs(&self.region_id, self.commission_year)
134-
}
135129
}
136130

137131
/// A wrapper around [`Asset`] for storing references in maps.

src/input/process.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,6 @@ mod tests {
308308
coeff: -10.0,
309309
kind: FlowType::Fixed,
310310
cost: 1.0,
311-
is_pac: false,
312311
}},
313312
)])
314313
}
@@ -322,7 +321,6 @@ mod tests {
322321
coeff: 10.0,
323322
kind: FlowType::Fixed,
324323
cost: 1.0,
325-
is_pac: false,
326324
}},
327325
)])
328326
}
@@ -383,7 +381,6 @@ mod tests {
383381
coeff: 10.0,
384382
kind: FlowType::Fixed,
385383
cost: 1.0,
386-
is_pac: false,
387384
}},
388385
)]),
389386
)])

src/input/process/availability.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ impl ProcessAvailabilityRaw {
3939

4040
/// Calculate fraction of annual energy as availability multiplied by time slice length.
4141
///
42-
/// The resulting limits are max/min PAC energy produced/consumed in each timeslice per
43-
/// cap2act units of capacity
42+
/// The resulting limits are max/min energy produced/consumed in each timeslice per
43+
/// `capacity_to_activity` units of capacity.
4444
fn to_bounds(&self, ts_length: f64) -> RangeInclusive<f64> {
4545
let value = self.value * ts_length;
4646
match self.limit_type {

src/input/process/flow.rs

Lines changed: 16 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Code for reading process flows file
22
use super::super::*;
3-
use crate::commodity::{CommodityID, CommodityMap};
3+
use crate::commodity::CommodityMap;
44
use crate::id::IDCollection;
55
use crate::process::{FlowType, Process, ProcessFlow, ProcessFlowsMap, ProcessID};
66
use crate::region::parse_region_str;
@@ -25,7 +25,6 @@ struct ProcessFlowRaw {
2525
#[serde(rename = "type")]
2626
kind: FlowType,
2727
cost: Option<f64>,
28-
is_pac: bool,
2928
}
3029

3130
impl ProcessFlowRaw {
@@ -112,7 +111,6 @@ where
112111
coeff: record.coeff,
113112
kind: record.kind,
114113
cost: record.cost.unwrap_or(0.0),
115-
is_pac: record.is_pac,
116114
};
117115

118116
// Insert flow into the map
@@ -134,63 +132,26 @@ where
134132
}
135133
}
136134

137-
// Validate flows and sort flows so PACs are at the start
138135
for (process_id, map) in map.iter_mut() {
139136
let process = processes.get(process_id).unwrap();
140137
validate_process_flows_map(process, map)?;
141-
sort_flows(map);
142138
}
143139

144140
Ok(map)
145141
}
146142

147-
/// Sort flows so PACs come first
148-
fn sort_flows(map: &mut ProcessFlowsMap) {
149-
for map in map.values_mut() {
150-
map.sort_by(|_, a, _, b| b.is_pac.cmp(&a.is_pac));
151-
}
152-
}
153-
154143
/// Validate flows for a process
155144
fn validate_process_flows_map(process: &Process, map: &ProcessFlowsMap) -> Result<()> {
156145
let process_id = process.id.clone();
157-
let reference_years = &process.years;
158-
let reference_regions = &process.regions;
159-
for year in reference_years.iter() {
160-
for region in reference_regions {
146+
for year in process.years.iter() {
147+
for region in process.regions.iter() {
161148
// Check that the process has flows for this region/year
162-
let flow_map = map.get(&(region.clone(), *year)).with_context(|| {
163-
format!("Missing entry for process {process_id} in {region}/{year}")
164-
})?;
165-
166-
// Validate flows for this process/region/year
167-
validate_flow_map(flow_map).with_context(|| {
168-
format!("Invalid flows for process {process_id} in {region}/{year}")
169-
})?;
170-
}
171-
}
172-
Ok(())
173-
}
174-
175-
/// Validate a vector of flows for a process in a given region/year
176-
fn validate_flow_map(flow_map: &IndexMap<CommodityID, ProcessFlow>) -> Result<()> {
177-
// PACs must be either all inputs or all outputs
178-
let mut flow_sign: Option<bool> = None; // False for inputs, true for outputs
179-
for flow in flow_map.values().filter(|flow| flow.is_pac) {
180-
// Check that flow sign is consistent
181-
let current_flow_sign = flow.coeff > 0.0;
182-
if let Some(flow_sign) = flow_sign {
183149
ensure!(
184-
current_flow_sign == flow_sign,
185-
"PACs are a mix of inputs and outputs",
150+
map.contains_key(&(region.clone(), *year)),
151+
"Missing entry for process {process_id} in {region}/{year}"
186152
);
187153
}
188-
flow_sign = Some(current_flow_sign);
189154
}
190-
191-
// Check that at least one PAC is defined
192-
ensure!(flow_sign.is_some(), "No PACs defined");
193-
194155
Ok(())
195156
}
196157

@@ -199,15 +160,10 @@ mod tests {
199160
use super::*;
200161
use crate::commodity::{Commodity, CommodityLevyMap, CommodityType, DemandMap};
201162
use crate::time_slice::TimeSliceLevel;
202-
use indexmap::indexmap;
203-
use rstest::{fixture, rstest};
204163

205-
fn create_process_flow_raw(
206-
coeff: f64,
207-
kind: FlowType,
208-
cost: Option<f64>,
209-
is_pac: bool,
210-
) -> ProcessFlowRaw {
164+
use rstest::fixture;
165+
166+
fn create_process_flow_raw(coeff: f64, kind: FlowType, cost: Option<f64>) -> ProcessFlowRaw {
211167
ProcessFlowRaw {
212168
process_id: "process".into(),
213169
commodity_id: "commodity".into(),
@@ -216,45 +172,34 @@ mod tests {
216172
coeff,
217173
kind,
218174
cost,
219-
is_pac,
220175
}
221176
}
222177

223178
#[test]
224179
fn test_validate_flow_raw() {
225180
// Valid
226-
let valid = create_process_flow_raw(1.0, FlowType::Fixed, Some(0.0), true);
181+
let valid = create_process_flow_raw(1.0, FlowType::Fixed, Some(0.0));
227182
assert!(valid.validate().is_ok());
228183

229184
// Invalid: Bad flow value
230-
let invalid = create_process_flow_raw(0.0, FlowType::Fixed, Some(0.0), true);
185+
let invalid = create_process_flow_raw(0.0, FlowType::Fixed, Some(0.0));
231186
assert!(invalid.validate().is_err());
232-
let invalid = create_process_flow_raw(f64::NAN, FlowType::Fixed, Some(0.0), true);
187+
let invalid = create_process_flow_raw(f64::NAN, FlowType::Fixed, Some(0.0));
233188
assert!(invalid.validate().is_err());
234-
let invalid = create_process_flow_raw(f64::INFINITY, FlowType::Fixed, Some(0.0), true);
189+
let invalid = create_process_flow_raw(f64::INFINITY, FlowType::Fixed, Some(0.0));
235190
assert!(invalid.validate().is_err());
236-
let invalid = create_process_flow_raw(f64::NEG_INFINITY, FlowType::Fixed, Some(0.0), true);
191+
let invalid = create_process_flow_raw(f64::NEG_INFINITY, FlowType::Fixed, Some(0.0));
237192
assert!(invalid.validate().is_err());
238193

239194
// Invalid: Bad flow cost value
240-
let invalid = create_process_flow_raw(1.0, FlowType::Fixed, Some(f64::NAN), true);
195+
let invalid = create_process_flow_raw(1.0, FlowType::Fixed, Some(f64::NAN));
241196
assert!(invalid.validate().is_err());
242-
let invalid = create_process_flow_raw(1.0, FlowType::Fixed, Some(f64::NEG_INFINITY), true);
197+
let invalid = create_process_flow_raw(1.0, FlowType::Fixed, Some(f64::NEG_INFINITY));
243198
assert!(invalid.validate().is_err());
244-
let invalid = create_process_flow_raw(1.0, FlowType::Fixed, Some(f64::INFINITY), true);
199+
let invalid = create_process_flow_raw(1.0, FlowType::Fixed, Some(f64::INFINITY));
245200
assert!(invalid.validate().is_err());
246201
}
247202

248-
fn create_process_flow(commodity: Rc<Commodity>, coeff: f64, is_pac: bool) -> ProcessFlow {
249-
ProcessFlow {
250-
commodity,
251-
coeff,
252-
kind: FlowType::Fixed,
253-
cost: 0.0,
254-
is_pac,
255-
}
256-
}
257-
258203
#[fixture]
259204
fn commodity1() -> Commodity {
260205
Commodity {
@@ -278,44 +223,4 @@ mod tests {
278223
levies: CommodityLevyMap::default(),
279224
}
280225
}
281-
282-
#[rstest]
283-
fn test_validate_flow_map_valid_single(commodity1: Commodity, commodity2: Commodity) {
284-
// Valid: Single PAC
285-
let flows = indexmap! {
286-
commodity1.id.clone() => create_process_flow(commodity1.into(), 1.0, true),
287-
commodity2.id.clone() => create_process_flow(commodity2.into(), 1.0, false),
288-
};
289-
assert!(validate_flow_map(&flows).is_ok());
290-
}
291-
292-
#[rstest]
293-
fn test_validate_flow_map_valid_multiple(commodity1: Commodity, commodity2: Commodity) {
294-
// Valid: Multiple PACs
295-
let flows = indexmap! {
296-
commodity1.id.clone() => create_process_flow(commodity1.into(), 1.0, true),
297-
commodity2.id.clone() => create_process_flow(commodity2.into(), 1.0, true),
298-
};
299-
assert!(validate_flow_map(&flows).is_ok());
300-
}
301-
302-
#[rstest]
303-
fn test_validate_flow_map_invalid_no_pacs(commodity1: Commodity, commodity2: Commodity) {
304-
// Invalid: No PACs
305-
let flows = indexmap! {
306-
commodity1.id.clone() => create_process_flow(commodity1.into(), 1.0, false),
307-
commodity2.id.clone() => create_process_flow(commodity2.into(), 1.0, false),
308-
};
309-
assert!(validate_flow_map(&flows).is_err());
310-
}
311-
312-
#[rstest]
313-
fn test_validate_flow_map(commodity1: Commodity, commodity2: Commodity) {
314-
// Invalid: Mixed PAC flow types
315-
let flows = indexmap! {
316-
commodity1.id.clone() => create_process_flow(commodity1.into(), 1.0, true),
317-
commodity2.id.clone() => create_process_flow(commodity2.into(), -1.0, true),
318-
};
319-
assert!(validate_flow_map(&flows).is_err());
320-
}
321226
}

0 commit comments

Comments
 (0)