Skip to content
Merged
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
2 changes: 2 additions & 0 deletions docs/release_notes/upcoming.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ ready to be released, carry out the following steps:
## New features

- Users can now optionally pass [custom options][highs-opts-docs] to the HiGHS solver ([#1276])
- Implement a new appraisal approach for the LCOX objective type ([#1319])

## Breaking changes

Expand All @@ -36,4 +37,5 @@ ready to be released, carry out the following steps:
[#1276]: https://github.com/EnergySystemsModellingLab/MUSE2/pull/1276
[#1281]: https://github.com/EnergySystemsModellingLab/MUSE2/pull/1281
[#1293]: https://github.com/EnergySystemsModellingLab/MUSE2/pull/1293
[#1319]: https://github.com/EnergySystemsModellingLab/MUSE2/pull/1319
[#1349]: https://github.com/EnergySystemsModellingLab/MUSE2/pull/1349
8 changes: 3 additions & 5 deletions src/fixture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use crate::simulation::investment::appraisal::{
use crate::time_slice::{TimeSliceID, TimeSliceInfo, TimeSliceLevel};
use crate::units::{
Activity, ActivityPerCapacity, Capacity, Dimensionless, Flow, MoneyPerActivity,
MoneyPerCapacity, MoneyPerCapacityPerYear, MoneyPerFlow, Year,
MoneyPerCapacity, MoneyPerCapacityPerYear, Year,
};
use anyhow::Result;
use indexmap::indexmap;
Expand Down Expand Up @@ -75,7 +75,7 @@ pub(crate) use patch_and_validate_simple;
/// Check whether validation succeeds for simple example with patches
macro_rules! assert_validate_ok_simple {
($file_patches:expr) => {
assert!(crate::fixture::patch_and_validate_simple!($file_patches).is_ok())
crate::fixture::patch_and_validate_simple!($file_patches).unwrap();
};
}
pub(crate) use assert_validate_ok_simple;
Expand Down Expand Up @@ -116,7 +116,7 @@ pub(crate) use patch_and_run_simple;
/// Check whether the simple example runs successfully after applying file patches
macro_rules! assert_patched_runs_ok_simple {
($file_patches:expr) => {
assert!(crate::fixture::patch_and_run_simple!($file_patches).is_ok())
crate::fixture::patch_and_run_simple!($file_patches).unwrap();
};
}
pub(crate) use assert_patched_runs_ok_simple;
Expand Down Expand Up @@ -408,10 +408,8 @@ pub fn appraisal_output(asset: Asset, time_slice: TimeSliceID) -> AppraisalOutpu
asset: AssetRef::from(asset),
capacity: AssetCapacity::Continuous(Capacity(42.0)),
coefficients: Rc::new(ObjectiveCoefficients {
capacity_coefficient: MoneyPerCapacity(2.14),
activity_coefficients,
market_costs,
unmet_demand_coefficient: MoneyPerFlow(10000.0),
}),
activity,
unmet_demand,
Expand Down
7 changes: 1 addition & 6 deletions src/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@ use crate::simulation::investment::appraisal::AppraisalOutput;
use crate::simulation::optimisation::{FlowMap, Solution};
use crate::simulation::prices::PriceMap;
use crate::time_slice::TimeSliceID;
use crate::units::{
Activity, Capacity, Flow, Money, MoneyPerActivity, MoneyPerCapacity, MoneyPerFlow,
};
use crate::units::{Activity, Capacity, Flow, Money, MoneyPerActivity, MoneyPerFlow};
use anyhow::{Context, Result, ensure};
use csv;
use indexmap::IndexMap;
Expand Down Expand Up @@ -261,7 +259,6 @@ struct AppraisalResultsRow {
process_id: ProcessID,
region_id: RegionID,
capacity: Capacity,
capacity_coefficient: MoneyPerCapacity,
metric: Option<f64>,
}

Expand Down Expand Up @@ -490,7 +487,6 @@ impl DebugDataWriter {
process_id: result.asset.process_id().clone(),
region_id: result.asset.region_id().clone(),
capacity: result.capacity.total_capacity(),
capacity_coefficient: result.coefficients.capacity_coefficient,
metric: result.metric.as_ref().map(|m| m.value()),
};
self.appraisal_results_writer.serialize(row)?;
Expand Down Expand Up @@ -1188,7 +1184,6 @@ mod tests {
process_id: asset.process_id().clone(),
region_id: asset.region_id().clone(),
capacity: Capacity(42.0),
capacity_coefficient: MoneyPerCapacity(2.14),
metric: Some(4.14),
};
let records: Vec<AppraisalResultsRow> =
Expand Down
6 changes: 3 additions & 3 deletions src/simulation/investment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -859,17 +859,17 @@ fn select_best_assets(
)?;

// Sort by investment priority and discard non-feasible options
sort_and_filter_appraisal_outputs(&mut outputs_for_opts);
let num_nonfeasible = sort_and_filter_appraisal_outputs(&mut outputs_for_opts);

// If none of the remaining options are feasible, we terminate the loop. We may still be
// able to meet the full demands with assets selected so far, so we continue anyway with a
// warning.
if outputs_for_opts.is_empty() {
warn!(
"Investment appraisal completed with unmet demand for commodity '{}', region '{}', \
year '{}', agent '{}'. No additional feasible investments were identified. \
year '{}', agent '{}'. {} non-feasible investments were not considered. \
This unmet demand may still be satisfied during the full system dispatch.",
&commodity.id, region_id, year, agent.id,
&commodity.id, region_id, year, agent.id, num_nonfeasible
);
break;
}
Expand Down
49 changes: 21 additions & 28 deletions src/simulation/investment/appraisal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ pub struct AppraisalOutput {
pub unmet_demand: DemandMap,
/// The comparison metric to compare investment decisions
pub metric: Option<Box<dyn MetricTrait>>,
/// Capacity and activity coefficients used in the appraisal
/// Activity coefficients and market costs used in the appraisal
pub coefficients: Rc<ObjectiveCoefficients>,
}

Expand Down Expand Up @@ -258,26 +258,19 @@ fn calculate_lcox(
coefficients: &Rc<ObjectiveCoefficients>,
demand: &DemandMap,
) -> Result<AppraisalOutput> {
let results = perform_optimisation(
model,
asset,
max_capacity,
commodity,
coefficients,
demand,
highs::Sense::Minimise,
)?;
let results =
perform_optimisation(model, asset, max_capacity, commodity, coefficients, demand)?;

let cost_index = lcox(
results.capacity.total_capacity(),
coefficients.capacity_coefficient,
max_capacity.total_capacity(),
Comment on lines +261 to +265

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At least for LCOX, if activity is zero, then lcox() will return None and so the value of the metric will also be None.

annual_fixed_cost(asset),
&results.activity,
&coefficients.market_costs,
);

Ok(AppraisalOutput::new(
asset.clone(),
results.capacity,
max_capacity,
results,
cost_index.map(LCOXMetric::new),
coefficients.clone(),
Expand All @@ -297,15 +290,8 @@ fn calculate_npv(
coefficients: &Rc<ObjectiveCoefficients>,
demand: &DemandMap,
) -> Result<AppraisalOutput> {
let results = perform_optimisation(
model,
asset,
max_capacity,
commodity,
coefficients,
demand,
highs::Sense::Maximise,
)?;
let results =
perform_optimisation(model, asset, max_capacity, commodity, coefficients, demand)?;

let annual_fixed_cost = annual_fixed_cost(asset);
Comment on lines +293 to 296
assert!(
Expand Down Expand Up @@ -373,13 +359,22 @@ fn compare_asset_fallback(asset1: &Asset, asset2: &Asset) -> Ordering {
/// with invalid metrics (e.g. `None`) as well as zero capacity. This avoids meaningless or `NaN`
/// appraisal metrics that could cause the program to panic, so the length of the returned vector
/// may be less than the input.
pub fn sort_and_filter_appraisal_outputs(outputs_for_opts: &mut Vec<AppraisalOutput>) {
outputs_for_opts.retain(AppraisalOutput::is_valid);
outputs_for_opts.sort_by(|output1, output2| match output1.compare_metric(output2) {
///
/// # Returns
///
/// Returns the number of non-feasible assets which were removed.
pub fn sort_and_filter_appraisal_outputs(outputs: &mut Vec<AppraisalOutput>) -> usize {
let old_len = outputs.len();
outputs.retain(AppraisalOutput::is_valid);
let num_nonfeasible = old_len - outputs.len();

outputs.sort_by(|output1, output2| match output1.compare_metric(output2) {
// If equal, we fall back on comparing asset properties
Ordering::Equal => compare_asset_fallback(&output1.asset, &output2.asset),
cmp => cmp,
});

num_nonfeasible
}

/// Counts the number of top appraisal outputs in a sorted slice that are indistinguishable
Expand All @@ -405,7 +400,7 @@ mod tests {
use crate::fixture::{agent_id, asset, process, region_id};
use crate::process::Process;
use crate::region::RegionID;
use crate::units::{Money, MoneyPerActivity, MoneyPerFlow};
use crate::units::{Money, MoneyPerActivity};
use float_cmp::assert_approx_eq;
use rstest::rstest;
use std::rc::Rc;
Expand Down Expand Up @@ -574,10 +569,8 @@ mod tests {

fn objective_coeffs() -> Rc<ObjectiveCoefficients> {
Rc::new(ObjectiveCoefficients {
capacity_coefficient: MoneyPerCapacity(0.0),
activity_coefficients: IndexMap::new(),
market_costs: IndexMap::new(),
unmet_demand_coefficient: MoneyPerFlow(0.0),
})
}

Expand Down
Loading
Loading