From 4b9787554ca8b9dec7bf3c38457fc12caca472a1 Mon Sep 17 00:00:00 2001 From: Tom Bland Date: Mon, 14 Apr 2025 20:03:37 +0100 Subject: [PATCH 1/7] Rough idea for strict units --- Cargo.lock | 22 +++++++++ src/lib.rs | 1 + src/metrics.rs | 45 +++++++++++++++++++ src/units.rs | 120 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 188 insertions(+) create mode 100644 src/metrics.rs create mode 100644 src/units.rs diff --git a/Cargo.lock b/Cargo.lock index 9dc651f69..ee0dda9d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -291,6 +291,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -785,6 +791,7 @@ dependencies = [ "clap", "clap-markdown", "csv", + "derive_more", "fern", "float-cmp", "highs", @@ -1047,6 +1054,15 @@ dependencies = [ "semver", ] +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "1.0.7" @@ -1078,6 +1094,12 @@ version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" + [[package]] name = "serde" version = "1.0.219" diff --git a/src/lib.rs b/src/lib.rs index 7c1fc951f..c8d76ed45 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,7 @@ pub mod commodity; pub mod id; pub mod input; pub mod log; +pub mod metrics; pub mod model; pub mod output; pub mod process; diff --git a/src/metrics.rs b/src/metrics.rs new file mode 100644 index 000000000..5d8e9ef4e --- /dev/null +++ b/src/metrics.rs @@ -0,0 +1,45 @@ +#![allow(missing_docs)] + +use crate::units::*; + +pub fn capital_recovery_factor(lifetime: IYear, discount_rate: Dimensionless) -> Dimensionless { + if lifetime == IYear(0) { + return Dimensionless(0.0); + } + if discount_rate == Dimensionless(0.0) { + return Dimensionless(1.0 / lifetime.0 as f64); + } + let factor = (Dimensionless(1.0) + discount_rate).pow(lifetime); + (discount_rate * factor) / (factor - Dimensionless(1.0)) +} + +pub fn annual_capital_cost( + capital_cost: MoneyPerCapacity, + capacity: Capacity, + lifetime: IYear, + discount_rate: Dimensionless, +) -> MoneyPerYear { + let crf = capital_recovery_factor(lifetime, discount_rate); + let total_capital_cost = capital_cost * capacity * crf; + let annual_capital_cost = total_capital_cost * crf; + annual_capital_cost * PerYear(1.0) // this is an annualized quantity, so we return it as such +} + +pub fn annual_fixed_operating_cost( + fixed_operating_cost: MoneyPerYearPerCapacity, + capacity: Capacity, +) -> MoneyPerYear { + fixed_operating_cost * capacity +} + +pub fn annual_fixed_costs( + capital_cost: MoneyPerCapacity, + capacity: Capacity, + lifetime: IYear, + discount_rate: Dimensionless, + fixed_operating_cost: MoneyPerYearPerCapacity, +) -> MoneyPerYear { + let annual_capital_cost = annual_capital_cost(capital_cost, capacity, lifetime, discount_rate); + let annual_fixed_operating_cost = annual_fixed_operating_cost(fixed_operating_cost, capacity); + annual_capital_cost + annual_fixed_operating_cost +} diff --git a/src/units.rs b/src/units.rs new file mode 100644 index 000000000..fca3615e3 --- /dev/null +++ b/src/units.rs @@ -0,0 +1,120 @@ +#![allow(missing_docs)] + +use derive_more::{Add, Sub}; + +macro_rules! unit_struct { + ($name:ident) => { + #[derive(Debug, Clone, Copy, PartialEq, Add, Sub)] + pub struct $name(pub f64); + + impl $name { + pub fn from(val: f64) -> Self { + Self(val) + } + + pub fn value(self) -> f64 { + self.0 + } + } + }; +} + +macro_rules! impl_mul { + ($Lhs:ty, $Rhs:ty, $Out:ty) => { + impl std::ops::Mul<$Rhs> for $Lhs { + type Output = $Out; + fn mul(self, rhs: $Rhs) -> $Out { + <$Out>::from(self.0 * rhs.0) + } + } + impl std::ops::Mul<$Lhs> for $Rhs { + type Output = $Out; + fn mul(self, lhs: $Lhs) -> $Out { + <$Out>::from(self.0 * lhs.0) + } + } + }; +} + +macro_rules! impl_div { + ($Lhs:ty, $Rhs:ty, $Out:ty) => { + impl std::ops::Div<$Rhs> for $Lhs { + type Output = $Out; + fn div(self, rhs: $Rhs) -> $Out { + <$Out>::from(self.0 / rhs.0) + } + } + }; +} + +unit_struct!(Dimensionless); +unit_struct!(Money); +unit_struct!(Year); +unit_struct!(Capacity); +unit_struct!(Commodity); + +#[derive(Debug, Clone, Copy, PartialEq, derive_more::Add, derive_more::Sub)] +pub struct IYear(pub u32); + +unit_struct!(CommodityPerYear); +unit_struct!(MoneyPerYear); +unit_struct!(MoneyPerCommodity); +unit_struct!(MoneyPerCapacity); +unit_struct!(CommodityPerYearPerCapacity); +unit_struct!(MoneyPerYearPerCapacity); +unit_struct!(MoneyPerCommodityPerYear); +unit_struct!(PerYear); + +impl_div!(Commodity, Year, CommodityPerYear); +impl_div!(Money, Year, MoneyPerYear); +impl_div!(Money, Commodity, MoneyPerCommodity); +impl_div!(CommodityPerYear, Capacity, CommodityPerYearPerCapacity); +impl_div!(MoneyPerYear, Capacity, MoneyPerYearPerCapacity); +impl_div!(MoneyPerCommodity, Year, MoneyPerCommodityPerYear); +impl_div!(Dimensionless, Year, PerYear); +impl_div!(Money, Capacity, MoneyPerCapacity); + +impl_mul!(Dimensionless, Year, Year); +impl_mul!(Dimensionless, Capacity, Capacity); +impl_mul!(Dimensionless, Commodity, Commodity); +impl_mul!(Dimensionless, Money, Money); +impl_mul!(MoneyPerCapacity, Capacity, Money); +impl_mul!(MoneyPerYearPerCapacity, Capacity, MoneyPerYear); +impl_mul!(Money, PerYear, MoneyPerYear); +impl_mul!(Year, PerYear, Dimensionless); + +impl IYear { + pub fn from(val: u32) -> Self { + Self(val) + } + + pub fn value(self) -> u32 { + self.0 + } + + pub fn to_year(self) -> Year { + Year::from(self.0 as f64) + } +} + +impl Dimensionless { + pub fn pow(self, rhs: IYear) -> Self { + Dimensionless::from(self.0.powi(rhs.0 as i32)) + } +} + +impl std::ops::Mul for Dimensionless { + type Output = Dimensionless; + + fn mul(self, rhs: Dimensionless) -> Self::Output { + Dimensionless::from(self.0 * rhs.0) + } +} + +impl std::ops::Div for Dimensionless { + type Output = Dimensionless; + + fn div(self, rhs: Dimensionless) -> Self::Output { + Dimensionless::from(self.0 / rhs.0) + } +} From 6df88bc80fdb9bc1228c81e5add007e7c902fa3f Mon Sep 17 00:00:00 2001 From: Tom Bland Date: Tue, 15 Apr 2025 14:32:02 +0100 Subject: [PATCH 2/7] Add Activity, Energy --- src/units.rs | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/units.rs b/src/units.rs index fca3615e3..a09458be0 100644 --- a/src/units.rs +++ b/src/units.rs @@ -48,35 +48,37 @@ macro_rules! impl_div { } unit_struct!(Dimensionless); + unit_struct!(Money); unit_struct!(Year); +unit_struct!(Energy); +unit_struct!(Activity); unit_struct!(Capacity); -unit_struct!(Commodity); #[derive(Debug, Clone, Copy, PartialEq, derive_more::Add, derive_more::Sub)] pub struct IYear(pub u32); -unit_struct!(CommodityPerYear); +unit_struct!(EnergyPerYear); unit_struct!(MoneyPerYear); -unit_struct!(MoneyPerCommodity); +unit_struct!(MoneyPerEnergy); unit_struct!(MoneyPerCapacity); -unit_struct!(CommodityPerYearPerCapacity); +unit_struct!(EnergyPerYearPerCapacity); unit_struct!(MoneyPerYearPerCapacity); -unit_struct!(MoneyPerCommodityPerYear); +unit_struct!(MoneyPerEnergyPerYear); unit_struct!(PerYear); -impl_div!(Commodity, Year, CommodityPerYear); +impl_div!(Energy, Year, EnergyPerYear); impl_div!(Money, Year, MoneyPerYear); -impl_div!(Money, Commodity, MoneyPerCommodity); -impl_div!(CommodityPerYear, Capacity, CommodityPerYearPerCapacity); +impl_div!(Money, Energy, MoneyPerEnergy); +impl_div!(EnergyPerYear, Capacity, EnergyPerYearPerCapacity); impl_div!(MoneyPerYear, Capacity, MoneyPerYearPerCapacity); -impl_div!(MoneyPerCommodity, Year, MoneyPerCommodityPerYear); +impl_div!(MoneyPerEnergy, Year, MoneyPerEnergyPerYear); impl_div!(Dimensionless, Year, PerYear); impl_div!(Money, Capacity, MoneyPerCapacity); impl_mul!(Dimensionless, Year, Year); impl_mul!(Dimensionless, Capacity, Capacity); -impl_mul!(Dimensionless, Commodity, Commodity); +impl_mul!(Dimensionless, Energy, Energy); impl_mul!(Dimensionless, Money, Money); impl_mul!(MoneyPerCapacity, Capacity, Money); impl_mul!(MoneyPerYearPerCapacity, Capacity, MoneyPerYear); From 10451d7605b3fc09bdc9ee77ef1b05f0e264a744 Mon Sep 17 00:00:00 2001 From: Tom Bland Date: Wed, 16 Apr 2025 13:22:31 +0100 Subject: [PATCH 3/7] Incorporate mul/div by Dimensionless into macro --- src/units.rs | 74 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 52 insertions(+), 22 deletions(-) diff --git a/src/units.rs b/src/units.rs index a09458be0..6ed3a4164 100644 --- a/src/units.rs +++ b/src/units.rs @@ -2,6 +2,37 @@ use derive_more::{Add, Sub}; +#[derive(Debug, Clone, Copy, PartialEq, Add, Sub)] +pub struct Dimensionless(pub f64); + +impl std::ops::Mul for Dimensionless { + type Output = Dimensionless; + + fn mul(self, rhs: Dimensionless) -> Self::Output { + Dimensionless::from(self.0 * rhs.0) + } +} + +impl std::ops::Div for Dimensionless { + type Output = Dimensionless; + + fn div(self, rhs: Dimensionless) -> Self::Output { + Dimensionless::from(self.0 / rhs.0) + } +} + +impl From for Dimensionless { + fn from(val: f64) -> Self { + Self(val) + } +} + +impl From for f64 { + fn from(val: Dimensionless) -> Self { + val.0 + } +} + macro_rules! unit_struct { ($name:ident) => { #[derive(Debug, Clone, Copy, PartialEq, Add, Sub)] @@ -16,6 +47,27 @@ macro_rules! unit_struct { self.0 } } + + impl std::ops::Mul for $name { + type Output = $name; + fn mul(self, rhs: Dimensionless) -> $name { + $name::from(self.0 * rhs.0) + } + } + + impl std::ops::Mul<$name> for Dimensionless { + type Output = $name; + fn mul(self, rhs: $name) -> $name { + $name::from(self.0 * rhs.0) + } + } + + impl std::ops::Div for $name { + type Output = $name; + fn div(self, rhs: Dimensionless) -> $name { + $name::from(self.0 / rhs.0) + } + } }; } @@ -47,8 +99,6 @@ macro_rules! impl_div { }; } -unit_struct!(Dimensionless); - unit_struct!(Money); unit_struct!(Year); unit_struct!(Energy); @@ -76,10 +126,6 @@ impl_div!(MoneyPerEnergy, Year, MoneyPerEnergyPerYear); impl_div!(Dimensionless, Year, PerYear); impl_div!(Money, Capacity, MoneyPerCapacity); -impl_mul!(Dimensionless, Year, Year); -impl_mul!(Dimensionless, Capacity, Capacity); -impl_mul!(Dimensionless, Energy, Energy); -impl_mul!(Dimensionless, Money, Money); impl_mul!(MoneyPerCapacity, Capacity, Money); impl_mul!(MoneyPerYearPerCapacity, Capacity, MoneyPerYear); impl_mul!(Money, PerYear, MoneyPerYear); @@ -104,19 +150,3 @@ impl Dimensionless { Dimensionless::from(self.0.powi(rhs.0 as i32)) } } - -impl std::ops::Mul for Dimensionless { - type Output = Dimensionless; - - fn mul(self, rhs: Dimensionless) -> Self::Output { - Dimensionless::from(self.0 * rhs.0) - } -} - -impl std::ops::Div for Dimensionless { - type Output = Dimensionless; - - fn div(self, rhs: Dimensionless) -> Self::Output { - Dimensionless::from(self.0 / rhs.0) - } -} From 041bcfd655eb7cacc4e74499a7a7f49cf25f5d93 Mon Sep 17 00:00:00 2001 From: Tom Bland Date: Wed, 16 Apr 2025 13:50:56 +0100 Subject: [PATCH 4/7] Tidy up a bit --- src/metrics.rs | 2 +- src/units.rs | 45 ++++++++++++++++++++------------------------- 2 files changed, 21 insertions(+), 26 deletions(-) diff --git a/src/metrics.rs b/src/metrics.rs index 5d8e9ef4e..42ff4fb51 100644 --- a/src/metrics.rs +++ b/src/metrics.rs @@ -9,7 +9,7 @@ pub fn capital_recovery_factor(lifetime: IYear, discount_rate: Dimensionless) -> if discount_rate == Dimensionless(0.0) { return Dimensionless(1.0 / lifetime.0 as f64); } - let factor = (Dimensionless(1.0) + discount_rate).pow(lifetime); + let factor = (Dimensionless(1.0) + discount_rate).powi(lifetime.0 as i32); (discount_rate * factor) / (factor - Dimensionless(1.0)) } diff --git a/src/units.rs b/src/units.rs index 6ed3a4164..bff195d61 100644 --- a/src/units.rs +++ b/src/units.rs @@ -1,8 +1,9 @@ #![allow(missing_docs)] -use derive_more::{Add, Sub}; +//! This module defines various unit types and their conversions. -#[derive(Debug, Clone, Copy, PartialEq, Add, Sub)] +/// Represents a dimensionless quantity. +#[derive(Debug, Clone, Copy, PartialEq, derive_more::Add, derive_more::Sub)] pub struct Dimensionless(pub f64); impl std::ops::Mul for Dimensionless { @@ -21,6 +22,12 @@ impl std::ops::Div for Dimensionless { } } +impl Dimensionless { + pub fn powi(self, rhs: i32) -> Self { + Dimensionless::from(self.0.powi(rhs)) + } +} + impl From for Dimensionless { fn from(val: f64) -> Self { Self(val) @@ -35,14 +42,17 @@ impl From for f64 { macro_rules! unit_struct { ($name:ident) => { - #[derive(Debug, Clone, Copy, PartialEq, Add, Sub)] + /// Represents a type of quantity. + #[derive(Debug, Clone, Copy, PartialEq, derive_more::Add, derive_more::Sub)] pub struct $name(pub f64); impl $name { + /// Creates a new instance of the unit type from a f64 value. pub fn from(val: f64) -> Self { Self(val) } + /// Returns the value of the unit type as a f64. pub fn value(self) -> f64 { self.0 } @@ -99,15 +109,14 @@ macro_rules! impl_div { }; } +// Base quantities unit_struct!(Money); unit_struct!(Year); unit_struct!(Energy); unit_struct!(Activity); unit_struct!(Capacity); -#[derive(Debug, Clone, Copy, PartialEq, derive_more::Add, derive_more::Sub)] -pub struct IYear(pub u32); - +// Derived quantities unit_struct!(EnergyPerYear); unit_struct!(MoneyPerYear); unit_struct!(MoneyPerEnergy); @@ -117,6 +126,7 @@ unit_struct!(MoneyPerYearPerCapacity); unit_struct!(MoneyPerEnergyPerYear); unit_struct!(PerYear); +// Division rules impl_div!(Energy, Year, EnergyPerYear); impl_div!(Money, Year, MoneyPerYear); impl_div!(Money, Energy, MoneyPerEnergy); @@ -126,27 +136,12 @@ impl_div!(MoneyPerEnergy, Year, MoneyPerEnergyPerYear); impl_div!(Dimensionless, Year, PerYear); impl_div!(Money, Capacity, MoneyPerCapacity); +// Multiplication rules impl_mul!(MoneyPerCapacity, Capacity, Money); impl_mul!(MoneyPerYearPerCapacity, Capacity, MoneyPerYear); impl_mul!(Money, PerYear, MoneyPerYear); impl_mul!(Year, PerYear, Dimensionless); -impl IYear { - pub fn from(val: u32) -> Self { - Self(val) - } - - pub fn value(self) -> u32 { - self.0 - } - - pub fn to_year(self) -> Year { - Year::from(self.0 as f64) - } -} - -impl Dimensionless { - pub fn pow(self, rhs: IYear) -> Self { - Dimensionless::from(self.0.powi(rhs.0 as i32)) - } -} +/// Represents a number of years as an integer. +#[derive(Debug, Clone, Copy, PartialEq, derive_more::Add, derive_more::Sub)] +pub struct IYear(pub u32); From 915e355308f5a3d4788127628359fd8393f6279c Mon Sep 17 00:00:00 2001 From: Tom Bland Date: Wed, 16 Apr 2025 14:16:50 +0100 Subject: [PATCH 5/7] Improve macros --- src/metrics.rs | 6 ++--- src/units.rs | 69 +++++++++++++++++++++++++++----------------------- 2 files changed, 40 insertions(+), 35 deletions(-) diff --git a/src/metrics.rs b/src/metrics.rs index 42ff4fb51..cd168bb18 100644 --- a/src/metrics.rs +++ b/src/metrics.rs @@ -22,11 +22,11 @@ pub fn annual_capital_cost( let crf = capital_recovery_factor(lifetime, discount_rate); let total_capital_cost = capital_cost * capacity * crf; let annual_capital_cost = total_capital_cost * crf; - annual_capital_cost * PerYear(1.0) // this is an annualized quantity, so we return it as such + MoneyPerYear(annual_capital_cost.0) // this is an annualized quantity, so we return it as such } pub fn annual_fixed_operating_cost( - fixed_operating_cost: MoneyPerYearPerCapacity, + fixed_operating_cost: MoneyPerCapacityPerYear, capacity: Capacity, ) -> MoneyPerYear { fixed_operating_cost * capacity @@ -37,7 +37,7 @@ pub fn annual_fixed_costs( capacity: Capacity, lifetime: IYear, discount_rate: Dimensionless, - fixed_operating_cost: MoneyPerYearPerCapacity, + fixed_operating_cost: MoneyPerCapacityPerYear, ) -> MoneyPerYear { let annual_capital_cost = annual_capital_cost(capital_cost, capacity, lifetime, discount_rate); let annual_fixed_operating_cost = annual_fixed_operating_cost(fixed_operating_cost, capacity); diff --git a/src/units.rs b/src/units.rs index bff195d61..6095d92ae 100644 --- a/src/units.rs +++ b/src/units.rs @@ -1,5 +1,3 @@ -#![allow(missing_docs)] - //! This module defines various unit types and their conversions. /// Represents a dimensionless quantity. @@ -23,6 +21,7 @@ impl std::ops::Div for Dimensionless { } impl Dimensionless { + /// Raise the dimensionless quantity to the power of an integer. pub fn powi(self, rhs: i32) -> Self { Dimensionless::from(self.0.powi(rhs)) } @@ -81,29 +80,40 @@ macro_rules! unit_struct { }; } -macro_rules! impl_mul { +macro_rules! impl_div { ($Lhs:ty, $Rhs:ty, $Out:ty) => { - impl std::ops::Mul<$Rhs> for $Lhs { + impl std::ops::Div<$Rhs> for $Lhs { type Output = $Out; - fn mul(self, rhs: $Rhs) -> $Out { - <$Out>::from(self.0 * rhs.0) + fn div(self, rhs: $Rhs) -> $Out { + <$Out>::from(self.0 / rhs.0) } } - impl std::ops::Mul<$Lhs> for $Rhs { - type Output = $Out; - fn mul(self, lhs: $Lhs) -> $Out { - <$Out>::from(self.0 * lhs.0) + + impl std::ops::Mul<$Rhs> for $Out { + type Output = $Lhs; + fn mul(self, by: $Rhs) -> $Lhs { + <$Lhs>::from(self.0 * by.0) } } - }; -} -macro_rules! impl_div { - ($Lhs:ty, $Rhs:ty, $Out:ty) => { - impl std::ops::Div<$Rhs> for $Lhs { - type Output = $Out; - fn div(self, rhs: $Rhs) -> $Out { - <$Out>::from(self.0 / rhs.0) + impl std::ops::Mul<$Lhs> for $Out { + type Output = $Rhs; + fn mul(self, by: $Lhs) -> $Rhs { + <$Rhs>::from(self.0 * by.0) + } + } + + impl std::ops::Mul<$Out> for $Rhs { + type Output = $Lhs; + fn mul(self, by: $Out) -> $Lhs { + <$Lhs>::from(self.0 * by.0) + } + } + + impl std::ops::Mul<$Out> for $Lhs { + type Output = $Rhs; + fn mul(self, by: $Out) -> $Rhs { + <$Rhs>::from(self.0 * by.0) } } }; @@ -111,36 +121,31 @@ macro_rules! impl_div { // Base quantities unit_struct!(Money); -unit_struct!(Year); unit_struct!(Energy); unit_struct!(Activity); unit_struct!(Capacity); +unit_struct!(Year); // Derived quantities unit_struct!(EnergyPerYear); unit_struct!(MoneyPerYear); unit_struct!(MoneyPerEnergy); unit_struct!(MoneyPerCapacity); -unit_struct!(EnergyPerYearPerCapacity); -unit_struct!(MoneyPerYearPerCapacity); +unit_struct!(EnergyPerCapacityPerYear); +unit_struct!(MoneyPerCapacityPerYear); unit_struct!(MoneyPerEnergyPerYear); -unit_struct!(PerYear); -// Division rules +// Simple relationships impl_div!(Energy, Year, EnergyPerYear); impl_div!(Money, Year, MoneyPerYear); impl_div!(Money, Energy, MoneyPerEnergy); -impl_div!(EnergyPerYear, Capacity, EnergyPerYearPerCapacity); -impl_div!(MoneyPerYear, Capacity, MoneyPerYearPerCapacity); -impl_div!(MoneyPerEnergy, Year, MoneyPerEnergyPerYear); -impl_div!(Dimensionless, Year, PerYear); impl_div!(Money, Capacity, MoneyPerCapacity); -// Multiplication rules -impl_mul!(MoneyPerCapacity, Capacity, Money); -impl_mul!(MoneyPerYearPerCapacity, Capacity, MoneyPerYear); -impl_mul!(Money, PerYear, MoneyPerYear); -impl_mul!(Year, PerYear, Dimensionless); +// Complex relationships +impl_div!(EnergyPerYear, Capacity, EnergyPerCapacityPerYear); +impl_div!(MoneyPerYear, Capacity, MoneyPerCapacityPerYear); +impl_div!(Money, EnergyPerYear, MoneyPerEnergyPerYear); +impl_div!(MoneyPerEnergy, Year, MoneyPerEnergyPerYear); /// Represents a number of years as an integer. #[derive(Debug, Clone, Copy, PartialEq, derive_more::Add, derive_more::Sub)] From a369d3675ea650098c601895ce67b3451844833f Mon Sep 17 00:00:00 2001 From: Tom Bland Date: Wed, 23 Apr 2025 13:55:25 +0100 Subject: [PATCH 6/7] Add annual_variable_cost calculation --- src/metrics.rs | 20 ++++++++++++++++++++ src/units.rs | 6 ++++++ 2 files changed, 26 insertions(+) diff --git a/src/metrics.rs b/src/metrics.rs index cd168bb18..b4f8e075c 100644 --- a/src/metrics.rs +++ b/src/metrics.rs @@ -2,6 +2,9 @@ use crate::units::*; +/// Calculates the capital recovery factor (CRF) for a given lifetime and discount rate. +/// +/// The CRF is used to annualize capital costs over the lifetime of an asset. pub fn capital_recovery_factor(lifetime: IYear, discount_rate: Dimensionless) -> Dimensionless { if lifetime == IYear(0) { return Dimensionless(0.0); @@ -13,6 +16,7 @@ pub fn capital_recovery_factor(lifetime: IYear, discount_rate: Dimensionless) -> (discount_rate * factor) / (factor - Dimensionless(1.0)) } +/// Calculates the annual capital cost for a technology pub fn annual_capital_cost( capital_cost: MoneyPerCapacity, capacity: Capacity, @@ -25,6 +29,7 @@ pub fn annual_capital_cost( MoneyPerYear(annual_capital_cost.0) // this is an annualized quantity, so we return it as such } +/// Calculates the annual fixed operating cost for a technology pub fn annual_fixed_operating_cost( fixed_operating_cost: MoneyPerCapacityPerYear, capacity: Capacity, @@ -32,6 +37,9 @@ pub fn annual_fixed_operating_cost( fixed_operating_cost * capacity } +/// Calculates the annual fixed costs for a technology +/// +/// This is the sum of the annual capital cost and the annual fixed operating cost. pub fn annual_fixed_costs( capital_cost: MoneyPerCapacity, capacity: Capacity, @@ -43,3 +51,15 @@ pub fn annual_fixed_costs( let annual_fixed_operating_cost = annual_fixed_operating_cost(fixed_operating_cost, capacity); annual_capital_cost + annual_fixed_operating_cost } + +/// Calculates the annual variable cost for a technology +pub fn annual_variable_cost( + variable_operating_cost: MoneyPerActivity, + capacity: Capacity, + cap2act: ActivityPerCapacity, + utilization: Dimensionless, +) -> MoneyPerYear { + let capacity_a = capacity * cap2act; + let annual_variable_cost = variable_operating_cost * capacity_a * utilization; + MoneyPerYear(annual_variable_cost.0) // this is an annualized quantity, so we return it as such +} diff --git a/src/units.rs b/src/units.rs index 6095d92ae..f5d18e0d1 100644 --- a/src/units.rs +++ b/src/units.rs @@ -134,18 +134,24 @@ unit_struct!(MoneyPerCapacity); unit_struct!(EnergyPerCapacityPerYear); unit_struct!(MoneyPerCapacityPerYear); unit_struct!(MoneyPerEnergyPerYear); +unit_struct!(MoneyPerActivity); +unit_struct!(MoneyPerActivityPerYear); +unit_struct!(ActivityPerCapacity); // Simple relationships impl_div!(Energy, Year, EnergyPerYear); impl_div!(Money, Year, MoneyPerYear); impl_div!(Money, Energy, MoneyPerEnergy); impl_div!(Money, Capacity, MoneyPerCapacity); +impl_div!(Money, Activity, MoneyPerActivity); +impl_div!(Activity, Capacity, ActivityPerCapacity); // Complex relationships impl_div!(EnergyPerYear, Capacity, EnergyPerCapacityPerYear); impl_div!(MoneyPerYear, Capacity, MoneyPerCapacityPerYear); impl_div!(Money, EnergyPerYear, MoneyPerEnergyPerYear); impl_div!(MoneyPerEnergy, Year, MoneyPerEnergyPerYear); +impl_div!(MoneyPerYear, Activity, MoneyPerActivityPerYear); /// Represents a number of years as an integer. #[derive(Debug, Clone, Copy, PartialEq, derive_more::Add, derive_more::Sub)] From 4220983a10127aa322a91fe9c9dd715b13e41314 Mon Sep 17 00:00:00 2001 From: Tom Bland <23723407+tsmbland@users.noreply.github.com> Date: Wed, 18 Jun 2025 14:31:28 +0100 Subject: [PATCH 7/7] Fix rebase errors --- Cargo.lock | 78 ++++++++++++++++++++++++++---------------------------- Cargo.toml | 1 + src/lib.rs | 1 + 3 files changed, 40 insertions(+), 40 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ee0dda9d1..1698d085c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -99,9 +99,9 @@ checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "backtrace" @@ -168,9 +168,9 @@ checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee" [[package]] name = "cc" -version = "1.2.26" +version = "1.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "956a5e21988b87f372569b66183b78babf23ebc2e744b733e4350a752c4dafac" +checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" dependencies = [ "jobserver", "libc", @@ -333,6 +333,19 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "derive_more" +version = "0.99.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -704,9 +717,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.172" +version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "libgit2-sys" @@ -727,7 +740,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-targets 0.53.0", + "windows-targets 0.53.2", ] [[package]] @@ -762,9 +775,9 @@ checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "minimal-lexical" @@ -892,9 +905,9 @@ dependencies = [ [[package]] name = "plist" -version = "1.7.1" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac26e981c03a6e53e0aee43c113e3202f5581d5360dae7bd2c70e800dd0451d" +checksum = "3d77244ce2d584cd84f6a15f86195b8c9b2a0dfbfd817c09e0464244091a58ed" dependencies = [ "base64", "indexmap", @@ -920,9 +933,9 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "prettyplease" -version = "0.2.33" +version = "0.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dee91521343f4c5c6a63edd65e54f31f5c92fe8978c40a4282f8372194c6a7d" +checksum = "6837b9e10d61f45f987d50808f83d1ee3d206c66acf650c3e4ae2e1f6ddedf55" dependencies = [ "proc-macro2", "syn", @@ -948,9 +961,9 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.32.0" +version = "0.37.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d3a6e5838b60e0e8fa7a43f22ade549a37d61f8bdbe636d0d7816191de969c2" +checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" dependencies = [ "memchr", ] @@ -966,9 +979,9 @@ dependencies = [ [[package]] name = "r-efi" -version = "5.2.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "regex" @@ -1054,15 +1067,6 @@ dependencies = [ "semver", ] -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] - [[package]] name = "rustix" version = "1.0.7" @@ -1094,12 +1098,6 @@ version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" -[[package]] -name = "semver" -version = "1.0.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" - [[package]] name = "serde" version = "1.0.219" @@ -1168,9 +1166,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.102" +version = "2.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6397daf94fa90f058bd0fd88429dd9e5738999cca8d701813c80723add80462" +checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8" dependencies = [ "proc-macro2", "quote", @@ -1459,9 +1457,9 @@ dependencies = [ [[package]] name = "windows-link" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-result" @@ -1517,9 +1515,9 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.0" +version = "0.53.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" dependencies = [ "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", @@ -1629,9 +1627,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" -version = "0.7.10" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" +checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" dependencies = [ "memchr", ] diff --git a/Cargo.toml b/Cargo.toml index 2bf9ee480..d458db9ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,7 @@ indexmap = "2.9.0" human-panic = "2.0.2" clap-markdown = "0.1.5" platform-info = "2.0.5" +derive_more = "0.99" [dev-dependencies] regex = "1.11.1" diff --git a/src/lib.rs b/src/lib.rs index c8d76ed45..ba7f2a61c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,6 +15,7 @@ pub mod region; pub mod settings; pub mod simulation; pub mod time_slice; +pub mod units; pub mod year; #[cfg(test)]