|
1 | 1 | //! Assets are instances of a process which are owned and invested in by agents. |
2 | | -use crate::agent::AgentID; |
| 2 | +use crate::agent::{AgentID, AgentMap}; |
3 | 3 | use crate::commodity::CommodityID; |
4 | 4 | use crate::process::{Process, ProcessFlow, ProcessID, ProcessParameter}; |
5 | 5 | use crate::region::RegionID; |
| 6 | +use crate::simulation::CommodityPrices; |
6 | 7 | use crate::time_slice::TimeSliceID; |
7 | | -use crate::units::{ |
8 | | - Activity, ActivityPerCapacity, Capacity, Dimensionless, MoneyPerActivity, MoneyPerFlow, |
9 | | -}; |
| 8 | +use crate::units::{Activity, ActivityPerCapacity, Capacity, Dimensionless, MoneyPerActivity}; |
10 | 9 | use anyhow::{Context, Result, ensure}; |
11 | 10 | use indexmap::IndexMap; |
12 | 11 | use itertools::{Itertools, chain}; |
@@ -272,23 +271,67 @@ impl Asset { |
272 | 271 | self.process_parameter.variable_operating_cost + flows_cost |
273 | 272 | } |
274 | 273 |
|
275 | | - /// Get the cost of input flows using the commodity prices in `input_prices` |
| 274 | + /// Get the total revenue from all flows for this asset, accounting for the parent agent's |
| 275 | + /// objective. |
| 276 | + /// |
| 277 | + /// We need to account for the agent's objective when calculating reduced costs, because if it |
| 278 | + /// is LCOX then we should exclude the primary output from the calculation. |
| 279 | + /// |
| 280 | + /// If a price is missing from `prices`, then it is assumed to be zero. |
| 281 | + /// |
| 282 | + /// # Panics |
| 283 | + /// |
| 284 | + /// Panics if this asset has no parent agent (i.e. it's a candidate). |
| 285 | + pub fn get_revenue_from_flows_for_objective( |
| 286 | + &self, |
| 287 | + agents: &AgentMap, |
| 288 | + prices: &CommodityPrices, |
| 289 | + year: u32, |
| 290 | + time_slice: &TimeSliceID, |
| 291 | + ) -> MoneyPerActivity { |
| 292 | + let exclude_commodity = self.primary_output().and_then(|flow| { |
| 293 | + let agent = &agents[self.agent_id().unwrap()]; |
| 294 | + let exclude_coi = |
| 295 | + agent.objectives[&year].exclude_primary_output_price_from_reduced_costs(); |
| 296 | + exclude_coi.then_some(&flow.commodity.id) |
| 297 | + }); |
| 298 | + |
| 299 | + self.get_revenue_from_flows_with_filter(prices, time_slice, |flow| { |
| 300 | + exclude_commodity.is_none_or(|commodity_id| commodity_id != &flow.commodity.id) |
| 301 | + }) |
| 302 | + } |
| 303 | + |
| 304 | + /// Get the cost of input flows using the commodity prices in `input_prices`. |
| 305 | + /// |
| 306 | + /// If a price is missing, there is assumed to be no cost. |
276 | 307 | pub fn get_input_cost_from_prices( |
277 | 308 | &self, |
278 | | - input_prices: &HashMap<(CommodityID, RegionID, TimeSliceID), MoneyPerFlow>, |
| 309 | + input_prices: &CommodityPrices, |
279 | 310 | time_slice: &TimeSliceID, |
280 | 311 | ) -> MoneyPerActivity { |
| 312 | + -self.get_revenue_from_flows_with_filter(input_prices, time_slice, ProcessFlow::is_input) |
| 313 | + } |
| 314 | + |
| 315 | + /// Get the total revenue from a subset of flows. |
| 316 | + /// |
| 317 | + /// Takes a function as an argument to filter the flows. If a price is missing, it is assumed to |
| 318 | + /// be zero. |
| 319 | + fn get_revenue_from_flows_with_filter<F>( |
| 320 | + &self, |
| 321 | + prices: &CommodityPrices, |
| 322 | + time_slice: &TimeSliceID, |
| 323 | + mut filter_for_flows: F, |
| 324 | + ) -> MoneyPerActivity |
| 325 | + where |
| 326 | + F: FnMut(&ProcessFlow) -> bool, |
| 327 | + { |
281 | 328 | self.iter_flows() |
282 | | - .filter_map(|flow| { |
283 | | - if !flow.is_input() { |
284 | | - return None; |
285 | | - } |
286 | | - let price = *input_prices.get(&( |
287 | | - flow.commodity.id.clone(), |
288 | | - self.region_id.clone(), |
289 | | - time_slice.clone(), |
290 | | - ))?; |
291 | | - Some(-flow.coeff * price) |
| 329 | + .filter(|flow| filter_for_flows(flow)) |
| 330 | + .map(|flow| { |
| 331 | + flow.coeff |
| 332 | + * prices |
| 333 | + .get(&flow.commodity.id, self.region_id(), time_slice) |
| 334 | + .unwrap_or_default() |
292 | 335 | }) |
293 | 336 | .sum() |
294 | 337 | } |
@@ -905,11 +948,8 @@ mod tests { |
905 | 948 | let asset = Asset::new_candidate(process, region_id.clone(), Capacity(1.0), 2020).unwrap(); |
906 | 949 |
|
907 | 950 | // Set input prices |
908 | | - let mut input_prices = HashMap::new(); |
909 | | - input_prices.insert( |
910 | | - (commodity_id.clone(), region_id.clone(), time_slice.clone()), |
911 | | - MoneyPerFlow(3.0), |
912 | | - ); |
| 951 | + let mut input_prices = CommodityPrices::default(); |
| 952 | + input_prices.insert(&commodity_id, ®ion_id, &time_slice, MoneyPerFlow(3.0)); |
913 | 953 |
|
914 | 954 | // Call function |
915 | 955 | let cost = asset.get_input_cost_from_prices(&input_prices, &time_slice); |
|
0 commit comments