Skip to content
Open
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
7 changes: 6 additions & 1 deletion docs/file_formats/generate_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,12 @@
"Processes": ["processes", "process_*"],
}
OUTPUT_FILE_ORDER = {
"Main CSV output files": ["assets", "commodity_flows", "commodity_prices"]
"Main CSV output files": [
"assets",
"asset_capacities",
"commodity_flows",
"commodity_prices",
]
}

sys.path.append(str(FILE_FORMAT_DOCS_DIR))
Expand Down
30 changes: 30 additions & 0 deletions schemas/output/asset_capacities.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
$schema: https://specs.frictionlessdata.io/schemas/table-schema.json
description: Capacity of each asset at each milestone year during the simulation.

fields:
- name: asset_id
type: string
description: The ID of this asset
notes: |
Filled for non-divisible assets. Empty for divisible assets, which use `group_id` instead. See
the `assets.csv` output file for further information about assets.
- name: group_id
type: string
description: The group ID of this asset group
notes: |
Filled for divisible assets. Empty for non-divisible assets, which use `asset_id` instead. See
the `assets.csv` output file for further information about groups.
- name: year
type: integer
description: The milestone year for this capacity entry
- name: capacity
type: number
description: The total capacity of the asset in this year
notes: |
For divisible assets, this is the total capacity across all active child assets
(i.e. unit size multiplied by the number of active units).
- name: num_units
type: integer
description: The number of active units making up the asset in this year
notes: |
For non-divisible assets, this is always blank.
27 changes: 9 additions & 18 deletions schemas/output/assets.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,15 @@ fields:
type: string
description: The ID number of this asset
notes: |
Every commissioned asset in the system is automatically assigned a numerical ID. This includes
assets supplied by the user in the `assets.csv` input file as well as those selected by agents
during the simulation.
Every commissioned non-divisible asset in the system is automatically assigned a numerical ID.
This includes assets supplied by the user in the `assets.csv` input file as well as those
selected by agents during the simulation. For divisible assets, this field will be empty and
`group_id` will be filled instead.
- name: group_id
type: string
description: The group ID number of this asset, if any
notes: |
Numerical identifier used for divisible assets.
- name: process_id
type: string
description: The ID of this asset's associated process
Expand All @@ -18,21 +24,6 @@ fields:
- name: agent_id
type: string
description: The agent to which this asset belongs
- name: group_id
type: string
description: The group ID number of this asset, if any
notes: |
Assets that have been created by dividing a parent asset are identified together by their group
id. If an asset does not come from a divided asset, then this field will be empty.
- name: commission_year
type: integer
description: The year in which the asset was commissioned
- name: decommission_year
type: integer
description: The year in which the asset was decommissioned
notes: |
This field will be empty if the asset was not decommissioned during the lifetime of the
simulation
- name: capacity
type: number
description: The capacity of the asset
154 changes: 1 addition & 153 deletions src/asset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,6 @@ pub struct AssetGroupID(u32);
///
/// `Future` and `Candidate` assets can be converted to `Commissioned` assets by calling
/// the `commission` method (or via pool operations that commission future/selected assets).
///
/// `Commissioned` assets can be decommissioned by calling `decommission`.
#[derive(Clone, Debug, PartialEq, strum::Display)]
pub enum AssetState {
/// The asset has been commissioned
Expand All @@ -86,15 +84,6 @@ pub enum AssetState {
/// All divided assets have a parent, which tracks the total capacity across the children.
parent: Option<AssetRef>,
},
/// The asset has been decommissioned
Decommissioned {
/// The ID of the asset
id: AssetID,
/// The ID of the agent that owned the asset
agent_id: AgentID,
/// The year the asset was decommissioned
decommission_year: u32,
},
/// The asset is planned for commissioning in the future
Future {
/// The ID of the agent that will own the asset
Expand Down Expand Up @@ -686,16 +675,6 @@ impl Asset {
self.commission_year
}

/// Get the decommission year for this asset
pub fn decommission_year(&self) -> Option<u32> {
match &self.state {
AssetState::Decommissioned {
decommission_year, ..
} => Some(*decommission_year),
_ => None,
}
}

/// Get the region ID for this asset
pub fn region_id(&self) -> &RegionID {
&self.region_id
Expand All @@ -714,9 +693,7 @@ impl Asset {
/// Get the ID for this asset
pub fn id(&self) -> Option<AssetID> {
match &self.state {
AssetState::Commissioned { id, .. } | AssetState::Decommissioned { id, .. } => {
Some(*id)
}
AssetState::Commissioned { id, .. } => Some(*id),
_ => None,
}
}
Expand Down Expand Up @@ -763,7 +740,6 @@ impl Asset {
pub fn agent_id(&self) -> Option<&AgentID> {
match &self.state {
AssetState::Commissioned { agent_id, .. }
| AssetState::Decommissioned { agent_id, .. }
| AssetState::Future { agent_id }
| AssetState::Selected { agent_id }
| AssetState::Parent { agent_id, .. } => Some(agent_id),
Expand Down Expand Up @@ -834,37 +810,6 @@ impl Asset {
.set(AssetCapacity::Discrete(n_units - 1, unit_size));
}

/// Decommission this asset
fn decommission(&mut self, decommission_year: u32, reason: &str) {
let (id, agent_id, parent) = match &self.state {
AssetState::Commissioned {
id,
agent_id,
parent,
..
} => (*id, agent_id.clone(), parent),
_ => panic!("Cannot decommission an asset that hasn't been commissioned"),
};
debug!(
"Decommissioning '{}' asset (ID: {}) for agent '{}' (reason: {})",
self.process_id(),
id,
agent_id,
reason
);

// If this is a child asset, we need to decrease the parent's capacity appropriately
if let Some(parent) = parent {
parent.decrement_unit_count();
}

self.state = AssetState::Decommissioned {
id,
agent_id,
decommission_year: decommission_year.min(self.max_decommission_year()),
};
}

/// Commission the asset.
///
/// Only assets with an [`AssetState`] of `Future` or `Selected` can be commissioned. If the
Expand Down Expand Up @@ -1556,95 +1501,6 @@ mod tests {
assert_eq!(asset2.id(), Some(AssetID(2)));
}

#[rstest]
#[case::commission_during_process_lifetime(2024, 2024)]
#[case::decommission_after_process_lifetime_ends(2026, 2025)]
fn asset_decommission(
#[case] requested_decommission_year: u32,
#[case] expected_decommission_year: u32,
process: Process,
) {
// Test successful commissioning of Future asset
let process_rc = Rc::new(process);
let mut asset = Asset::new_future(
"agent1".into(),
Rc::clone(&process_rc),
"GBR".into(),
Capacity(1.0),
2020,
)
.unwrap();
asset.commission(AssetID(1), None, "");
assert!(asset.is_commissioned());
assert_eq!(asset.id(), Some(AssetID(1)));

// Test successful decommissioning
asset.decommission(requested_decommission_year, "");
assert!(!asset.is_commissioned());
assert_eq!(asset.decommission_year(), Some(expected_decommission_year));
}

#[rstest]
#[case::decommission_after_predefined_max_year(2026, 2025, Some(2025))]
#[case::decommission_before_predefined_max_year(2024, 2024, Some(2025))]
#[case::decommission_during_process_lifetime_end_no_max_year(2024, 2024, None)]
#[case::decommission_after_process_lifetime_end_no_max_year(2026, 2025, None)]
fn asset_decommission_with_max_decommission_year_predefined(
#[case] requested_decommission_year: u32,
#[case] expected_decommission_year: u32,
#[case] max_decommission_year: Option<u32>,
process: Process,
) {
// Test successful commissioning of Future asset
let process_rc = Rc::new(process);
let mut asset = Asset::new_future_with_max_decommission(
"agent1".into(),
Rc::clone(&process_rc),
"GBR".into(),
Capacity(1.0),
2020,
max_decommission_year,
)
.unwrap();
asset.commission(AssetID(1), None, "");
assert!(asset.is_commissioned());
assert_eq!(asset.id(), Some(AssetID(1)));

// Test successful decommissioning
asset.decommission(requested_decommission_year, "");
assert!(!asset.is_commissioned());
assert_eq!(asset.decommission_year(), Some(expected_decommission_year));
}

#[rstest]
fn asset_decommission_divisible(asset_divisible: Asset) {
let asset = AssetRef::from(asset_divisible);
let original_capacity = asset.capacity();

// Commission children
let mut children = Vec::new();
let mut next_id = 0;
asset.into_for_each_child(&mut 0, |parent, mut child| {
child
.make_mut()
.commission(AssetID(next_id), parent.cloned(), "");
next_id += 1;
children.push(child);
});

let parent = children[0].parent().unwrap().clone();
assert_eq!(parent.capacity(), original_capacity);
children[0].make_mut().decommission(2020, "");

let AssetCapacity::Discrete(original_units, original_unit_size) = original_capacity else {
panic!("Capacity type should be discrete");
};
assert_eq!(
parent.capacity(),
AssetCapacity::Discrete(original_units - 1, original_unit_size)
);
}

#[rstest]
#[should_panic(expected = "Assets with state Candidate cannot be commissioned")]
fn commission_wrong_states(process: Process) {
Expand All @@ -1653,14 +1509,6 @@ mod tests {
asset.commission(AssetID(1), None, "");
}

#[rstest]
#[should_panic(expected = "Cannot decommission an asset that hasn't been commissioned")]
fn decommission_wrong_state(process: Process) {
let mut asset =
Asset::new_candidate(process.into(), "GBR".into(), Capacity(1.0), 2020).unwrap();
asset.decommission(2025, "");
}

#[test]
fn commission_year_before_time_horizon() {
let processes_patch = FilePatch::new("processes.csv")
Expand Down
Loading
Loading