Source code for citylearn.internal.kpi_energy

"""Energy, carbon, cost, comfort and grid-shape KPI records.

The facade supplies window/unit helpers; these functions own their family
calculations and return records without changing simulator state.
"""
from __future__ import annotations

from typing import TYPE_CHECKING

import numpy as np
import pandas as pd

from citylearn.cost_function import CostFunction

if TYPE_CHECKING:
    from citylearn.internal.kpi import CityLearnKPIService


[docs] def building_records(kpi: "CityLearnKPIService", building, t_start, t_end, control_condition, baseline_condition, comfort_band, evaluation_condition_cls, dynamics_building_cls): """Return legacy frame, extended rows and raw-cost relative benefit.""" extended_building_rows = [] building_days = kpi._building_days(building, t_start, t_end) if isinstance(building, dynamics_building_cls): building_control_condition = ( evaluation_condition_cls.WITH_STORAGE_AND_PARTIAL_LOAD_AND_PV if control_condition is None else control_condition ) building_baseline_condition = ( evaluation_condition_cls.WITHOUT_STORAGE_AND_PARTIAL_LOAD_BUT_WITH_PV if baseline_condition is None else baseline_condition ) else: building_control_condition = ( evaluation_condition_cls.WITH_STORAGE_AND_PV if control_condition is None else control_condition ) building_baseline_condition = ( evaluation_condition_cls.WITHOUT_STORAGE_BUT_WITH_PV if baseline_condition is None else baseline_condition ) indoor = kpi._thermal_values(building, building.indoor_dry_bulb_temperature, t_start, t_end) cooling_setpoint = kpi._thermal_values(building, building.indoor_dry_bulb_temperature_cooling_set_point, t_start, t_end) heating_setpoint = kpi._thermal_values(building, building.indoor_dry_bulb_temperature_heating_set_point, t_start, t_end) occupants = kpi._thermal_values(building, building.occupant_count, t_start, t_end) discomfort_kwargs = { 'indoor_dry_bulb_temperature': indoor, 'dry_bulb_temperature_cooling_set_point': cooling_setpoint, 'dry_bulb_temperature_heating_set_point': heating_setpoint, 'band': kpi._thermal_values(building, building.comfort_band, t_start, t_end) if comfort_band is None else comfort_band, 'occupant_count': occupants, } unmet, cold, hot, \ cold_minimum_delta, cold_maximum_delta, cold_average_delta, \ hot_minimum_delta, hot_maximum_delta, hot_average_delta = [v if v else [np.nan] for v in CostFunction.discomfort(**discomfort_kwargs)] expected_components, served_components = kpi._service_energy(building, t_start, t_end) expected_energy = expected_components.sum(axis=1) # Serving another demand cannot compensate a shortfall in this one. served_energy = np.minimum(expected_components, served_components).sum(axis=1) net_c_series = kpi._slice_window( kpi._energy_values(building, getattr(building, f'net_electricity_consumption{building_control_condition.value}')), t_start, t_end, ) net_b_series = kpi._slice_window( kpi._energy_values(building, getattr(building, f'net_electricity_consumption{building_baseline_condition.value}')), t_start, t_end, ) ec_c = kpi._positive_total(net_c_series) ec_b = kpi._positive_total(net_b_series) export_c = kpi._sum_finite(np.clip(-net_c_series, 0.0, None)) export_b = kpi._sum_finite(np.clip(-net_b_series, 0.0, None)) zne_c = kpi._net_total(net_c_series) zne_b = kpi._net_total(net_b_series) ce_c_series = kpi._slice_window( kpi._energy_values(building, getattr(building, f'net_electricity_consumption_emission{building_control_condition.value}')), t_start, t_end, ) ce_b_series = kpi._slice_window( kpi._energy_values(building, getattr(building, f'net_electricity_consumption_emission{building_baseline_condition.value}')), t_start, t_end, ) ce_c = kpi._positive_total(ce_c_series) ce_b = ( kpi._positive_total(ce_b_series) if np.sum(kpi._slice_window(building.carbon_intensity.carbon_intensity, t_start, t_end)) != 0 else 0.0 ) control_cost_series = kpi._slice_window( kpi._energy_values(building, getattr(building, f'net_electricity_consumption_cost{building_control_condition.value}')), t_start, t_end, ) baseline_cost_series = kpi._slice_window( kpi._energy_values(building, getattr(building, f'net_electricity_consumption_cost{building_baseline_condition.value}')), t_start, t_end, ) cost_c_legacy = kpi._positive_total(control_cost_series) cost_b_legacy = kpi._positive_total(baseline_cost_series) cost_c_raw = kpi._sum_finite(control_cost_series) cost_b_raw = kpi._sum_finite(baseline_cost_series) equity_benefit = kpi._equity_relative_benefit_percent(cost_c_raw, cost_b_raw) legacy_building_frame = pd.DataFrame([{ 'cost_function': 'electricity_consumption_total', 'value': kpi._safe_div(ec_c, ec_b), }, { 'cost_function': 'zero_net_energy', 'value': kpi._safe_div(zne_c, zne_b), }, { 'cost_function': 'carbon_emissions_total', 'value': kpi._safe_div(ce_c, ce_b), }, { 'cost_function': 'cost_total', 'value': kpi._safe_div(cost_c_legacy, cost_b_legacy), }, { 'cost_function': 'discomfort_proportion', 'value': unmet[-1], }, { 'cost_function': 'discomfort_cold_proportion', 'value': cold[-1], }, { 'cost_function': 'discomfort_hot_proportion', 'value': hot[-1], }, { 'cost_function': 'discomfort_cold_delta_minimum', 'value': cold_minimum_delta[-1], }, { 'cost_function': 'discomfort_cold_delta_maximum', 'value': cold_maximum_delta[-1], }, { 'cost_function': 'discomfort_cold_delta_average', 'value': cold_average_delta[-1], }, { 'cost_function': 'discomfort_hot_delta_minimum', 'value': hot_minimum_delta[-1], }, { 'cost_function': 'discomfort_hot_delta_maximum', 'value': hot_maximum_delta[-1], }, { 'cost_function': 'discomfort_hot_delta_average', 'value': hot_average_delta[-1], }, { 'cost_function': 'one_minus_thermal_resilience_proportion', 'value': kpi._cost_last( np.asarray( CostFunction.one_minus_thermal_resilience( power_outage=kpi._thermal_values(building, building.power_outage_signal, t_start, t_end), **discomfort_kwargs, ), dtype='float64', ), lambda x: x, ), }, { 'cost_function': 'power_outage_normalized_unserved_energy_total', 'value': kpi._cost_last( np.asarray( CostFunction.normalized_unserved_energy( expected_energy, served_energy, power_outage=kpi._thermal_values(building, building.power_outage_signal, t_start, t_end), ), dtype='float64', ), lambda x: x, ), }, { 'cost_function': 'annual_normalized_unserved_energy_total', 'value': kpi._cost_last( np.asarray(CostFunction.normalized_unserved_energy(expected_energy, served_energy), dtype='float64'), lambda x: x, ), }]) legacy_building_frame['name'] = building.name extended_building_rows.extend([ kpi._metric('electricity_consumption_control_total_kwh', ec_c, building.name, 'building'), kpi._metric('electricity_consumption_baseline_total_kwh', ec_b, building.name, 'building'), kpi._metric('electricity_consumption_delta_total_kwh', ec_c - ec_b, building.name, 'building'), kpi._metric('electricity_consumption_control_daily_average_kwh', kpi._daily_average(ec_c, building_days), building.name, 'building'), kpi._metric('electricity_consumption_baseline_daily_average_kwh', kpi._daily_average(ec_b, building_days), building.name, 'building'), kpi._metric('electricity_consumption_delta_daily_average_kwh', kpi._daily_average(ec_c - ec_b, building_days), building.name, 'building'), kpi._metric('electricity_export_control_total_kwh', export_c, building.name, 'building'), kpi._metric('electricity_export_baseline_total_kwh', export_b, building.name, 'building'), kpi._metric('electricity_export_delta_total_kwh', export_c - export_b, building.name, 'building'), kpi._metric('electricity_export_control_daily_average_kwh', kpi._daily_average(export_c, building_days), building.name, 'building'), kpi._metric('electricity_export_baseline_daily_average_kwh', kpi._daily_average(export_b, building_days), building.name, 'building'), kpi._metric('electricity_export_delta_daily_average_kwh', kpi._daily_average(export_c - export_b, building_days), building.name, 'building'), kpi._metric('zero_net_energy_control_total_kwh', zne_c, building.name, 'building'), kpi._metric('zero_net_energy_baseline_total_kwh', zne_b, building.name, 'building'), kpi._metric('zero_net_energy_delta_total_kwh', zne_c - zne_b, building.name, 'building'), kpi._metric('zero_net_energy_control_daily_average_kwh', kpi._daily_average(zne_c, building_days), building.name, 'building'), kpi._metric('zero_net_energy_baseline_daily_average_kwh', kpi._daily_average(zne_b, building_days), building.name, 'building'), kpi._metric('zero_net_energy_delta_daily_average_kwh', kpi._daily_average(zne_c - zne_b, building_days), building.name, 'building'), kpi._metric('carbon_emissions_control_total_kgco2', ce_c, building.name, 'building'), kpi._metric('carbon_emissions_baseline_total_kgco2', ce_b, building.name, 'building'), kpi._metric('carbon_emissions_delta_total_kgco2', ce_c - ce_b, building.name, 'building'), kpi._metric('carbon_emissions_control_daily_average_kgco2', kpi._daily_average(ce_c, building_days), building.name, 'building'), kpi._metric('carbon_emissions_baseline_daily_average_kgco2', kpi._daily_average(ce_b, building_days), building.name, 'building'), kpi._metric('carbon_emissions_delta_daily_average_kgco2', kpi._daily_average(ce_c - ce_b, building_days), building.name, 'building'), kpi._metric('cost_control_total_eur', cost_c_raw, building.name, 'building'), kpi._metric('cost_baseline_total_eur', cost_b_raw, building.name, 'building'), kpi._metric('cost_delta_total_eur', cost_c_raw - cost_b_raw, building.name, 'building'), kpi._metric('cost_control_daily_average_eur', kpi._daily_average(cost_c_raw, building_days), building.name, 'building'), kpi._metric('cost_baseline_daily_average_eur', kpi._daily_average(cost_b_raw, building_days), building.name, 'building'), kpi._metric('cost_delta_daily_average_eur', kpi._daily_average(cost_c_raw - cost_b_raw, building_days), building.name, 'building'), kpi._metric('equity_relative_benefit_percent', equity_benefit, building.name, 'building'), ]) return legacy_building_frame, extended_building_rows, equity_benefit
[docs] def district_records(kpi: "CityLearnKPIService", legacy_building, control_condition, baseline_condition, evaluation_condition_cls, simulated_days): """Preserve district condition selection and legacy mean aggregation.""" env = kpi.env get_net_electricity_consumption = lambda x, c: kpi._energy_values(x, getattr(x, f'net_electricity_consumption{c.value}')) get_net_electricity_consumption_cost = lambda x, c: kpi._energy_values(x, getattr(x, f'net_electricity_consumption_cost{c.value}')) get_net_electricity_consumption_emission = lambda x, c: kpi._energy_values(x, getattr(x, f'net_electricity_consumption_emission{c.value}')) daily_steps = kpi._window_steps(24.0 * 3600.0, env.seconds_per_time_step) monthly_steps = kpi._window_steps(730.0 * 3600.0, env.seconds_per_time_step) env_control_condition = ( evaluation_condition_cls.WITH_STORAGE_AND_PARTIAL_LOAD_AND_PV if control_condition is None else control_condition ) env_baseline_condition = ( evaluation_condition_cls.WITHOUT_STORAGE_AND_PARTIAL_LOAD_BUT_WITH_PV if baseline_condition is None else baseline_condition ) net_c_env_series = np.array(get_net_electricity_consumption(env, env_control_condition), dtype='float64') net_b_env_series = np.array(get_net_electricity_consumption(env, env_baseline_condition), dtype='float64') ramp_c = kpi._ramping_final(net_c_env_series) ramp_b = kpi._ramping_final(net_b_env_series) dlf_daily_c = kpi._one_minus_load_factor_final(net_c_env_series, window=daily_steps) dlf_daily_b = kpi._one_minus_load_factor_final(net_b_env_series, window=daily_steps) dlf_monthly_c = kpi._one_minus_load_factor_final(net_c_env_series, window=monthly_steps) dlf_monthly_b = kpi._one_minus_load_factor_final(net_b_env_series, window=monthly_steps) peak_daily_c = kpi._peak_final(net_c_env_series, window=daily_steps) peak_daily_b = kpi._peak_final(net_b_env_series, window=daily_steps) peak_all_c = kpi._peak_final(net_c_env_series, window=env.time_steps) peak_all_b = kpi._peak_final(net_b_env_series, window=env.time_steps) legacy_district_base = pd.DataFrame([{ 'cost_function': 'ramping_average', 'value': kpi._safe_div(ramp_c, ramp_b), }, { 'cost_function': 'daily_one_minus_load_factor_average', 'value': kpi._safe_div(dlf_daily_c, dlf_daily_b), }, { 'cost_function': 'monthly_one_minus_load_factor_average', 'value': kpi._safe_div(dlf_monthly_c, dlf_monthly_b), }, { 'cost_function': 'daily_peak_average', 'value': kpi._safe_div(peak_daily_c, peak_daily_b), }, { 'cost_function': 'all_time_peak_average', 'value': kpi._safe_div(peak_all_c, peak_all_b), }]) legacy_district = pd.concat([legacy_district_base, legacy_building], ignore_index=True, sort=False) legacy_district = legacy_district.groupby(['cost_function'])[['value']].mean().reset_index() legacy_district['name'] = 'District' legacy_district['level'] = 'district' legacy_cost_functions = pd.concat([legacy_district, legacy_building], ignore_index=True, sort=False) # Extended KPI district-level ec_c_env = kpi._positive_total(net_c_env_series) ec_b_env = kpi._positive_total(net_b_env_series) export_c_env = kpi._sum_finite(np.clip(-net_c_env_series, 0.0, None)) export_b_env = kpi._sum_finite(np.clip(-net_b_env_series, 0.0, None)) zne_c_env = kpi._net_total(net_c_env_series) zne_b_env = kpi._net_total(net_b_env_series) ce_c_env = kpi._positive_total(get_net_electricity_consumption_emission(env, env_control_condition)) ce_b_env = kpi._positive_total(get_net_electricity_consumption_emission(env, env_baseline_condition)) env_control_cost_series = get_net_electricity_consumption_cost(env, env_control_condition) env_baseline_cost_series = get_net_electricity_consumption_cost(env, env_baseline_condition) cost_c_env_raw = kpi._sum_finite(env_control_cost_series) cost_b_env_raw = kpi._sum_finite(env_baseline_cost_series) extended_district_rows = [ kpi._metric('electricity_consumption_control_total_kwh', ec_c_env, 'District', 'district'), kpi._metric('electricity_consumption_baseline_total_kwh', ec_b_env, 'District', 'district'), kpi._metric('electricity_consumption_delta_total_kwh', ec_c_env - ec_b_env, 'District', 'district'), kpi._metric('electricity_consumption_control_daily_average_kwh', kpi._daily_average(ec_c_env, simulated_days), 'District', 'district'), kpi._metric('electricity_consumption_baseline_daily_average_kwh', kpi._daily_average(ec_b_env, simulated_days), 'District', 'district'), kpi._metric('electricity_consumption_delta_daily_average_kwh', kpi._daily_average(ec_c_env - ec_b_env, simulated_days), 'District', 'district'), kpi._metric('electricity_export_control_total_kwh', export_c_env, 'District', 'district'), kpi._metric('electricity_export_baseline_total_kwh', export_b_env, 'District', 'district'), kpi._metric('electricity_export_delta_total_kwh', export_c_env - export_b_env, 'District', 'district'), kpi._metric('electricity_export_control_daily_average_kwh', kpi._daily_average(export_c_env, simulated_days), 'District', 'district'), kpi._metric('electricity_export_baseline_daily_average_kwh', kpi._daily_average(export_b_env, simulated_days), 'District', 'district'), kpi._metric('electricity_export_delta_daily_average_kwh', kpi._daily_average(export_c_env - export_b_env, simulated_days), 'District', 'district'), kpi._metric('zero_net_energy_control_total_kwh', zne_c_env, 'District', 'district'), kpi._metric('zero_net_energy_baseline_total_kwh', zne_b_env, 'District', 'district'), kpi._metric('zero_net_energy_delta_total_kwh', zne_c_env - zne_b_env, 'District', 'district'), kpi._metric('zero_net_energy_control_daily_average_kwh', kpi._daily_average(zne_c_env, simulated_days), 'District', 'district'), kpi._metric('zero_net_energy_baseline_daily_average_kwh', kpi._daily_average(zne_b_env, simulated_days), 'District', 'district'), kpi._metric('zero_net_energy_delta_daily_average_kwh', kpi._daily_average(zne_c_env - zne_b_env, simulated_days), 'District', 'district'), kpi._metric('carbon_emissions_control_total_kgco2', ce_c_env, 'District', 'district'), kpi._metric('carbon_emissions_baseline_total_kgco2', ce_b_env, 'District', 'district'), kpi._metric('carbon_emissions_delta_total_kgco2', ce_c_env - ce_b_env, 'District', 'district'), kpi._metric('carbon_emissions_control_daily_average_kgco2', kpi._daily_average(ce_c_env, simulated_days), 'District', 'district'), kpi._metric('carbon_emissions_baseline_daily_average_kgco2', kpi._daily_average(ce_b_env, simulated_days), 'District', 'district'), kpi._metric('carbon_emissions_delta_daily_average_kgco2', kpi._daily_average(ce_c_env - ce_b_env, simulated_days), 'District', 'district'), kpi._metric('cost_control_total_eur', cost_c_env_raw, 'District', 'district'), kpi._metric('cost_baseline_total_eur', cost_b_env_raw, 'District', 'district'), kpi._metric('cost_delta_total_eur', cost_c_env_raw - cost_b_env_raw, 'District', 'district'), kpi._metric('cost_control_daily_average_eur', kpi._daily_average(cost_c_env_raw, simulated_days), 'District', 'district'), kpi._metric('cost_baseline_daily_average_eur', kpi._daily_average(cost_b_env_raw, simulated_days), 'District', 'district'), kpi._metric('cost_delta_daily_average_eur', kpi._daily_average(cost_c_env_raw - cost_b_env_raw, simulated_days), 'District', 'district'), ] return legacy_cost_functions, extended_district_rows