diff --git a/k8s/job.jsonnet b/k8s/job.jsonnet index 4ee9a67..cbcf376 100644 --- a/k8s/job.jsonnet +++ b/k8s/job.jsonnet @@ -45,10 +45,32 @@ local job(name, args_excl_output) = { 'extended_boiler_upgrade_scheme', '--intervention', 'heat_pump_campaign', - '--campaign-target-heat-pump-awareness', - '0.8', - '--heat-pump-awareness-campaign-date', - '2028-01-01', + '--campaign-target-heat-pump-awareness-date', + '2028-01-01:0.5', + '--campaign-target-heat-pump-awareness-date', + '2034-01-01:0.75', + '--intervention', + 'gas_oil_boiler_ban', + '--gas-oil-boiler-ban-date', + '2035-01-01', + '--gas-oil-boiler-ban-announce-date', + '2025-01-01', + '--heat-pump-awareness', + '0.25', + '--price-gbp-per-kwh-gas', + '0.0682', + '--price-gbp-per-kwh-electricity', + '0.182', + '--heat-pump-installer-count', + '10000000000' + ]), + job('01b-%s-max-policy-extended-bus' % std.extVar('SHORT_SHA'), [ + '--intervention', + 'extended_boiler_upgrade_scheme', + '--intervention', + 'heat_pump_campaign', + '--campaign-target-heat-pump-awareness-date', + '2028-01-01:0.75', '--intervention', 'gas_oil_boiler_ban', '--gas-oil-boiler-ban-date', diff --git a/simulation/__main__.py b/simulation/__main__.py index ff19543..b2c4685 100644 --- a/simulation/__main__.py +++ b/simulation/__main__.py @@ -179,15 +179,11 @@ def check_string_is_isoformat_datetime(string) -> str: parser.add_argument("--price-gbp-per-kwh-oil", type=float, default=0.068) parser.add_argument( - "--heat-pump-awareness-campaign-date", - default=datetime.datetime(2028, 1, 1), - type=convert_to_datetime, - ) - - parser.add_argument( - "--campaign-target-heat-pump-awareness", - default=0.8, - type=float_between_0_and_1, + "--campaign-target-heat-pump-awareness-date", + action="append", + type=map_string_to_datetime_float_tuple, + help="A factor by which heat pump awareness will increase by a specified date.", + metavar="YYYY-MM-DD:heat_pump_awareness", ) return parser.parse_args(args) @@ -199,9 +195,20 @@ def validate_args(args): f"Boiler ban announcement date must be on or before ban date, got gas_oil_boiler_ban_date:{args.gas_oil_boiler_ban_date}, gas_oil_boiler_ban_announce_date:{args.gas_oil_boiler_ban_announce_date}" ) - if args.campaign_target_heat_pump_awareness < args.heat_pump_awareness: + # Check that target awareness inputs increase over the model horizon + campaigns = sorted(args.campaign_target_heat_pump_awareness_date) + _, awareness_factors = zip(*campaigns) + awareness_factors = list(awareness_factors) + awareness_factors.insert(0, args.heat_pump_awareness) + increasing_awareness = all( + [ + awareness_factors[i - 1] < awareness_factors[i] + for i in range(1, len(awareness_factors)) + ] + ) + if not increasing_awareness: raise ValueError( - f"Campaign target awareness must be greater than or equal to the population heat pump awareness, got campaign_target_heat_pump_awareness:{args.campaign_target_heat_pump_awareness}, heat_pump_awareness:{args.heat_pump_awareness}" + f"Campaign target awareness must be greater than or equal to the population heat pump awareness, got campaign_target_heat_pump_awareness:{args.campaign_target_heat_pump_awareness_date}, heat_pump_awareness:{args.heat_pump_awareness}" ) @@ -243,8 +250,7 @@ def validate_args(args): args.heat_pump_installer_count, args.heat_pump_installer_annual_growth_rate, ENGLAND_WALES_ANNUAL_NEW_BUILDS if args.include_new_builds else None, - args.campaign_target_heat_pump_awareness, - args.heat_pump_awareness_campaign_date, + args.campaign_target_heat_pump_awareness_date, ) with smart_open.open(args.history_file, "w") as file: diff --git a/simulation/agents.py b/simulation/agents.py index ea9b631..aece247 100644 --- a/simulation/agents.py +++ b/simulation/agents.py @@ -633,7 +633,6 @@ def proba_of_becoming_heat_pump_aware_required_to_reach_campaign_target( def update_heat_pump_awareness(self, model) -> None: if ( InterventionType.HEAT_PUMP_CAMPAIGN in model.interventions - and model.current_datetime >= model.heat_pump_awareness_campaign_date and model.heat_pump_awareness_at_timestep < model.campaign_target_heat_pump_awareness and not self.is_heat_pump_aware diff --git a/simulation/model.py b/simulation/model.py index 7a5c4e3..29ccb3c 100644 --- a/simulation/model.py +++ b/simulation/model.py @@ -47,8 +47,9 @@ def __init__( heat_pump_installer_annual_growth_rate: float, annual_new_builds: Optional[Dict[int, int]], heat_pump_awareness: float, - campaign_target_heat_pump_awareness: float, - heat_pump_awareness_campaign_date: datetime.datetime, + heat_pump_awareness_campaign_schedule: Optional[ + List[Tuple[datetime.datetime, float]] + ], population_heat_pump_awareness: List[bool], ): self.start_datetime = start_datetime @@ -79,8 +80,11 @@ def __init__( self.heat_pump_installations_at_current_step = 0 self.annual_new_builds = annual_new_builds self.heat_pump_awareness = heat_pump_awareness - self.campaign_target_heat_pump_awareness = campaign_target_heat_pump_awareness - self.heat_pump_awareness_campaign_date = heat_pump_awareness_campaign_date + self.heat_pump_awareness_campaign_schedule = ( + sorted(heat_pump_awareness_campaign_schedule) + if heat_pump_awareness_campaign_schedule + else None + ) self.population_heat_pump_awareness = population_heat_pump_awareness self.num_households_heat_pump_aware = sum(population_heat_pump_awareness) @@ -211,6 +215,24 @@ def heat_pump_awareness_at_timestep(self) -> float: + self.num_households_switching_to_heat_pump_aware ) / self.household_count + @property + def campaign_target_heat_pump_awareness(self) -> float: + + if self.heat_pump_awareness_campaign_schedule: + + step_dates, awareness_factors = zip( + *self.heat_pump_awareness_campaign_schedule + ) + + index = bisect(step_dates, self.current_datetime) + current_date_precedes_first_campaign_date = index == 0 + + if current_date_precedes_first_campaign_date: + return self.heat_pump_awareness + return awareness_factors[index - 1] + + return self.heat_pump_awareness + def increment_timestep(self): self.current_datetime += self.step_interval self.boiler_upgrade_scheme_cumulative_spend_gbp += ( @@ -282,8 +304,9 @@ def create_and_run_simulation( heat_pump_installer_count: int, heat_pump_installer_annual_growth_rate: float, annual_new_builds: Dict[int, int], - campaign_target_heat_pump_awareness: float, - heat_pump_awareness_campaign_date: datetime.datetime, + heat_pump_awareness_campaign_schedule: Optional[ + List[Tuple[datetime.datetime, float]] + ], ): population_heat_pump_awareness = [ @@ -308,8 +331,7 @@ def create_and_run_simulation( heat_pump_installer_annual_growth_rate=heat_pump_installer_annual_growth_rate, annual_new_builds=annual_new_builds, heat_pump_awareness=heat_pump_awareness, - campaign_target_heat_pump_awareness=campaign_target_heat_pump_awareness, - heat_pump_awareness_campaign_date=heat_pump_awareness_campaign_date, + heat_pump_awareness_campaign_schedule=heat_pump_awareness_campaign_schedule, population_heat_pump_awareness=population_heat_pump_awareness, ) diff --git a/simulation/tests/common.py b/simulation/tests/common.py index a27ae6c..1fcc198 100644 --- a/simulation/tests/common.py +++ b/simulation/tests/common.py @@ -58,8 +58,7 @@ def model_factory(**model_attributes): "heat_pump_installer_annual_growth_rate": 0, "annual_new_builds": None, "heat_pump_awareness": 0.5, - "campaign_target_heat_pump_awareness": 0.8, - "heat_pump_awareness_campaign_date": datetime.datetime(2028, 1, 1), + "heat_pump_awareness_campaign_schedule": None, "population_heat_pump_awareness": [], } diff --git a/simulation/tests/test_agents.py b/simulation/tests/test_agents.py index 56cc183..216cc05 100644 --- a/simulation/tests/test_agents.py +++ b/simulation/tests/test_agents.py @@ -705,8 +705,10 @@ def test_heat_pump_awareness_updated_after_successful_campaign( start_datetime=datetime.datetime(2025, 1, 1), step_interval=relativedelta(months=1), interventions=[InterventionType.HEAT_PUMP_CAMPAIGN], - heat_pump_awareness_campaign_date=datetime.datetime(2025, 3, 1), - campaign_target_heat_pump_awareness=1.0, + heat_pump_awareness=0.0, + heat_pump_awareness_campaign_schedule=[ + (datetime.datetime(2025, 3, 1), 1.0) + ], ) agent = household_factory(is_heat_pump_aware=False) model.add_agents([agent]) @@ -751,9 +753,10 @@ def test_heat_pump_awareness_does_not_increase_when_campaign_target_is_same_as_c start_datetime=datetime.datetime(2025, 1, 1), step_interval=relativedelta(months=1), interventions=[InterventionType.HEAT_PUMP_CAMPAIGN], - heat_pump_awareness_campaign_date=datetime.datetime(2025, 2, 1), + heat_pump_awareness_campaign_schedule=[ + (datetime.datetime(2025, 2, 1), 0.0) + ], heat_pump_awareness=0.0, - campaign_target_heat_pump_awareness=0.0, population_heat_pump_awareness=[False], ) agent = household_factory(is_heat_pump_aware=False) diff --git a/simulation/tests/test_main.py b/simulation/tests/test_main.py index b3e0067..464c28e 100644 --- a/simulation/tests/test_main.py +++ b/simulation/tests/test_main.py @@ -299,8 +299,8 @@ def test_campaign_target_less_than_heat_pump_awareness_raises_value_error( args = parse_args( [ *mandatory_local_args, - "--campaign-target-heat-pump-awareness", - "0.1", + "--campaign-target-heat-pump-awareness-date", + "2024-03-01:0.1", "--heat-pump-awareness", "0.5", ] diff --git a/simulation/tests/test_model.py b/simulation/tests/test_model.py index a50e54c..5bdb9d4 100644 --- a/simulation/tests/test_model.py +++ b/simulation/tests/test_model.py @@ -245,6 +245,36 @@ def test_model_installs_heat_pumps_in_existing_builds_when_there_is_capacity(sel assert 0 < capacity_new_build < capacity_existing_build assert model.has_heat_pump_installation_capacity + def test_heat_pump_awareness_campaign_is_intial_awareness_if_no_campaign_schedule_passed( + self, + ): + + model = model_factory(heat_pump_awareness_campaign_schedule=None) + + assert model.campaign_target_heat_pump_awareness == model.heat_pump_awareness + + def test_heat_pump_awareness_changes_when_crosses_campaign_schedule_date( + self, + ): + + model = model_factory( + start_datetime=datetime.datetime(2024, 2, 1), + heat_pump_awareness=0.25, + heat_pump_awareness_campaign_schedule=[ + (datetime.datetime(2024, 2, 2), 0.5), + (datetime.datetime(2024, 2, 3), 0.7), + ], + step_interval=datetime.timedelta(minutes=1440), + ) + + assert model.campaign_target_heat_pump_awareness == 0.25 + + model.increment_timestep() + assert model.campaign_target_heat_pump_awareness == 0.5 + + model.increment_timestep() + assert model.campaign_target_heat_pump_awareness == 0.7 + class test_household_agents: @@ -344,9 +374,10 @@ def test_all_household_agents_become_heat_pump_aware_with_100_per_cent_campaign_ start_datetime=datetime.datetime(2025, 1, 1), step_interval=relativedelta(months=1), interventions=[InterventionType.HEAT_PUMP_CAMPAIGN], - heat_pump_awareness_campaign_date=datetime.datetime(2025, 2, 1), heat_pump_awareness=0.0, - campaign_target_heat_pump_awareness=campaign_target_heat_pump_awareness, + heat_pump_awareness_campaign_schedule=[ + (datetime.datetime(2025, 2, 1), campaign_target_heat_pump_awareness) + ], population_heat_pump_awareness=population_heat_pump_awareness, ) model.add_agents([household_agents])