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
19 changes: 15 additions & 4 deletions benches/assets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ use muse2::process::{Process, ProcessID};
use muse2::simulation::candidate_assets_for_next_year;
use muse2::simulation::investment::{flatten_preset_demands_for_year, select_best_assets};
use muse2::simulation::market::{
collect_agent_addition_limits, get_asset_options, get_demand_portion_for_market,
get_responsible_agents,
collect_agent_limits, get_asset_options, get_demand_portion_for_market, get_responsible_agents,
};
use muse2::simulation::optimisation::DispatchRun;
use muse2::simulation::prices::{Prices, calculate_prices};
Expand Down Expand Up @@ -137,6 +136,7 @@ fn build_synthetic_processes(templates: &[Arc<Process>], n: usize) -> Vec<Arc<Pr
/// - `parallel`: uses the default Rayon global thread pool (all available cores).
/// - `sequential`: installs a single-thread Rayon pool so that `par_iter` degenerates to serial
/// execution, giving a fair like-for-like comparison with the overhead of parallelism removed.
#[allow(clippy::too_many_lines)]
fn criterion_benchmark(c: &mut Criterion) {
let (model, mut writer, _example_dir, _output_dir) = load_bench_model();
let (base_year_assets, existing_assets, candidates) = build_assets_for_investment_year(&model);
Expand Down Expand Up @@ -204,12 +204,21 @@ fn criterion_benchmark(c: &mut Criterion) {
model.parameters.capacity_limit_factor,
)
.collect();
let agent_addition_limits = collect_agent_addition_limits(
let agent_addition_limits = collect_agent_limits(
&agent,
region_id,
&commodity.id,
YEAR,
commodity_portion,
Process::agent_addition_limit,
);
let agent_total_limits = collect_agent_limits(
&agent,
region_id,
&commodity.id,
YEAR,
commodity_portion,
Process::agent_total_limit,
);

group.bench_with_input(
Expand All @@ -221,15 +230,17 @@ fn criterion_benchmark(c: &mut Criterion) {
(
opt_assets.clone(),
agent_addition_limits.clone(),
agent_total_limits.clone(),
demand.clone(),
)
},
|(opt_assets, agent_addition_limits, demand)| {
|(opt_assets, agent_addition_limits, agent_total_limits, demand)| {
let run = || {
select_best_assets(
black_box(&model),
opt_assets,
agent_addition_limits,
agent_total_limits,
black_box(commodity),
black_box(&agent),
black_box(region_id),
Expand Down
5 changes: 4 additions & 1 deletion src/input/process/investment_constraints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,10 @@ where
.addition_limit
.map(|limit| limit * Year(years_since_prev as f64));

let constraint = Arc::new(ProcessInvestmentConstraint { addition_limit });
let constraint = Arc::new(ProcessInvestmentConstraint {
addition_limit,
total_capacity_limit: record.total_capacity_limit,
});

try_insert(process_map, &(region.clone(), year), constraint.clone())?;
}
Expand Down
127 changes: 113 additions & 14 deletions src/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,14 @@ impl Process {
self.years.contains(&year)
}

/// Calculate an agent's share of the addition limit for this process in a region and year
/// based on commodity portion.
pub fn agent_addition_limit(
/// Calculate an agent's share of any limit for this process in a region and year based on
/// commodity portion.
fn agent_limit(
Comment thread
AdrianDAlessandro marked this conversation as resolved.
&self,
region_id: &RegionID,
commission_year: u32,
commodity_portion: Dimensionless,
get_limit: fn(&ProcessInvestmentConstraint) -> Option<Capacity>,
) -> Option<Capacity> {
assert!(
commodity_portion >= Dimensionless(0.0) && commodity_portion <= Dimensionless(1.0),
Expand All @@ -92,11 +93,39 @@ impl Process {

self.investment_constraints
.get(&(region_id.clone(), commission_year))
.and_then(|constraint| {
constraint
.get_addition_limit()
.map(|limit| limit * commodity_portion)
})
.and_then(|constraint| get_limit(constraint).map(|limit| limit * commodity_portion))
}

/// Calculate an agent's share of the addition limit for this process in a region and year
/// based on commodity portion.
pub fn agent_addition_limit(
&self,
region_id: &RegionID,
commission_year: u32,
commodity_portion: Dimensionless,
) -> Option<Capacity> {
self.agent_limit(
region_id,
commission_year,
commodity_portion,
ProcessInvestmentConstraint::get_addition_limit,
)
}

/// Calculate an agent's share of the total limit for this process in a region and year
/// based on commodity portion.
pub fn agent_total_limit(
&self,
region_id: &RegionID,
commission_year: u32,
commodity_portion: Dimensionless,
) -> Option<Capacity> {
self.agent_limit(
region_id,
commission_year,
commodity_portion,
ProcessInvestmentConstraint::get_total_limit,
)
}
}

Expand Down Expand Up @@ -527,17 +556,20 @@ pub struct ProcessInvestmentConstraint {
/// Addition constraint: Limit an agent can invest in the process, shared according to the
/// agent's proportion of the process's primary commodity demand
pub addition_limit: Option<Capacity>,
/// Total capacity limit for the process
pub total_capacity_limit: Option<Capacity>,
Comment thread
AdrianDAlessandro marked this conversation as resolved.
}

impl ProcessInvestmentConstraint {
/// Calculate the effective addition limit
///
/// For now, this just returns `addition_limit`, but in the future when we add growth
/// limits and total capacity limits, this will have more complex logic which will depend on the
/// current total capacity.
/// Get the addition limit
pub fn get_addition_limit(&self) -> Option<Capacity> {
self.addition_limit
}

/// Get the total capacity limit allowed
pub fn get_total_limit(&self) -> Option<Capacity> {
self.total_capacity_limit
}
}

#[cfg(test)]
Expand All @@ -546,7 +578,7 @@ mod tests {
use crate::commodity::{
CommodityConstraintsMap, CommodityLevyMap, CommodityType, DemandMap, PricingStrategy,
};
use crate::fixture::{assert_error, region_id, time_slice, time_slice_info2};
use crate::fixture::{assert_error, process, region_id, time_slice, time_slice_info2};
use crate::time_slice::TimeSliceLevel;
use crate::time_slice::TimeSliceSelection;
use float_cmp::assert_approx_eq;
Expand Down Expand Up @@ -1167,4 +1199,71 @@ mod tests {
"Availability limit for season winter clashes with time slice limits"
);
}

#[rstest]
#[case(Dimensionless(1.1))]
#[case(Dimensionless(-0.1))]
#[should_panic(expected = "commodity_portion must be between 0 and 1 inclusive")]
fn agent_limit_invalid_commodity_portion(
process: Process,
region_id: RegionID,
#[case] commodity_portion: Dimensionless,
) {
process.agent_limit(
&region_id,
2015,
commodity_portion,
ProcessInvestmentConstraint::get_addition_limit,
);
}

#[rstest]
fn agent_addition_limit_no_constraint(process: Process, region_id: RegionID) {
assert!(
process
.agent_addition_limit(&region_id, 2015, Dimensionless(1.0))
.is_none()
);
}

#[rstest]
fn agent_addition_limit_scaled_by_portion(mut process: Process, region_id: RegionID) {
process.investment_constraints.insert(
(region_id.clone(), 2015),
Arc::new(ProcessInvestmentConstraint {
addition_limit: Some(crate::units::Capacity(10.0)),
total_capacity_limit: Some(crate::units::Capacity(100.0)),
}),
);

let result = process
.agent_addition_limit(&region_id, 2015, Dimensionless(0.5))
.unwrap();
assert_eq!(result, Capacity(5.0));
}

#[rstest]
fn agent_total_limit_no_constraint(process: Process, region_id: RegionID) {
assert!(
process
.agent_total_limit(&region_id, 2015, Dimensionless(1.0))
.is_none()
);
}

#[rstest]
fn agent_total_limit_scaled_by_portion(mut process: Process, region_id: RegionID) {
process.investment_constraints.insert(
(region_id.clone(), 2015),
Arc::new(ProcessInvestmentConstraint {
addition_limit: Some(crate::units::Capacity(10.0)),
total_capacity_limit: Some(crate::units::Capacity(100.0)),
}),
);

let result = process
.agent_total_limit(&region_id, 2015, Dimensionless(0.5))
.unwrap();
assert_eq!(result, Capacity(50.0));
}
}
Loading
Loading