diff --git a/Cargo.lock b/Cargo.lock index 9dc651f69..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", @@ -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" @@ -327,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" @@ -698,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" @@ -721,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]] @@ -756,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" @@ -785,6 +804,7 @@ dependencies = [ "clap", "clap-markdown", "csv", + "derive_more", "fern", "float-cmp", "highs", @@ -885,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", @@ -913,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", @@ -941,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", ] @@ -959,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" @@ -1146,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", @@ -1437,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" @@ -1495,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", @@ -1607,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 7c1fc951f..ba7f2a61c 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; @@ -14,6 +15,7 @@ pub mod region; pub mod settings; pub mod simulation; pub mod time_slice; +pub mod units; pub mod year; #[cfg(test)] diff --git a/src/metrics.rs b/src/metrics.rs new file mode 100644 index 000000000..b4f8e075c --- /dev/null +++ b/src/metrics.rs @@ -0,0 +1,65 @@ +#![allow(missing_docs)] + +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); + } + if discount_rate == Dimensionless(0.0) { + return Dimensionless(1.0 / lifetime.0 as f64); + } + let factor = (Dimensionless(1.0) + discount_rate).powi(lifetime.0 as i32); + (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, + 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; + 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, +) -> MoneyPerYear { + 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, + lifetime: IYear, + discount_rate: Dimensionless, + 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); + 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 new file mode 100644 index 000000000..f5d18e0d1 --- /dev/null +++ b/src/units.rs @@ -0,0 +1,158 @@ +//! This module defines various unit types and their conversions. + +/// 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 { + 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 Dimensionless { + /// Raise the dimensionless quantity to the power of an integer. + pub fn powi(self, rhs: i32) -> Self { + Dimensionless::from(self.0.powi(rhs)) + } +} + +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) => { + /// 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 + } + } + + 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) + } + } + }; +} + +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<$Rhs> for $Out { + type Output = $Lhs; + fn mul(self, by: $Rhs) -> $Lhs { + <$Lhs>::from(self.0 * by.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) + } + } + }; +} + +// Base quantities +unit_struct!(Money); +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!(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)] +pub struct IYear(pub u32);