示例#1
0
def create_mock_containers_with_results(ureg):
    mock_containers = create_mock_bldg_containers(ureg)
    prev_sim_year = 2015
    kwh_year=ureg.kW*ureg.h/ureg.year

    energy_demand_res = EnergyDemandSimulationResults(
                                tot_dhw_demand=10000 * kwh_year,
                                tot_heating_demand=180000 * kwh_year,
                                tot_electricity_demand=80000 * kwh_year,
                                tot_cooling_demand=0 * kwh_year,
                                total_floor_area=250*ureg.m**2)

    for c in mock_containers.values():
        c.get_bldg_model().site.simulation_year = prev_sim_year
        c.set_energy_demand_sim_res(energy_demand_res)
        c.set_retrofit_log(RetrofitLog())

    retrofit_log_fid3 = RetrofitLog()
    retrofit_log_fid3.log_retrofit_measure(3, BuildingElement.WALL, None, prev_sim_year, None, None, None, None,
                                           "old_wall_constr", "new_wall_constr")
    mock_containers[3].set_retrofit_log(retrofit_log_fid3)


    energy_demand_low = EnergyDemandSimulationResults(
                                tot_dhw_demand=5000 * kwh_year,
                                tot_heating_demand=60000 * kwh_year,
                                tot_electricity_demand=5000 * kwh_year,
                                tot_cooling_demand=0 * kwh_year,
                                total_floor_area=250*ureg.m**2)

    mock_containers[4].set_energy_demand_sim_res(energy_demand_low)

    return mock_containers
示例#2
0
def test_retrofitted_in():
    ureg = cesarp.common.init_unit_registry()
    myLogger = RetrofitLog()
    # empty log
    assert myLogger.was_construction_retrofitted_in(2022) == False
    myLogger.log_retrofit_measure(
        bldg_fid=22,
        bldg_element=BuildingElement.ROOF,
        retrofitted_area=100 * ureg.m**2,
        year_of_retrofit=2020,
        retrofit_target="SIA380_MIN",
        costs=200 * ureg.CHF / ureg.m**2,
        non_renewable_pen=0.02 * ureg.MJ * ureg.Oileq / ureg.m**2,
        co2_emission=3 * ureg.kg * ureg.CO2eq / ureg.m**2,
        old_construction_name="bad_construction",
        new_construction_name="good_construction",
    )

    assert myLogger.was_construction_retrofitted_in(2022) == False
    assert myLogger.was_construction_retrofitted_in(2020) == True
    assert myLogger.was_construction_retrofitted_in(year=2022,
                                                    bldg_fid=22) == False
    assert myLogger.was_construction_retrofitted_in(year=2020,
                                                    bldg_fid=22) == True
    assert myLogger.was_construction_retrofitted_in(year=2020,
                                                    bldg_fid=33) == False
    def __init__(self,
                 ureg: pint.UnitRegistry,
                 custom_config: Dict[str, Any] = {}):
        """
        Initialize. See set_year_of_retrofit() and set_bldgs_elems_to_retrofit() for further
        configuration options.

        :param ureg: pint unit registry
        """
        self._ureg = ureg
        self._year_of_retrofit: Optional[int] = None
        self._bldg_elems_to_retrofit = self._retrofittable_bldg_elems
        self._logger = logging.getLogger(__name__)
        self._cfg = cesarp.common.load_config_for_package(
            _default_config_file, __package__, custom_config)

        # can be changed after initialization if needed...
        self.construction_retrofitter: ConstructionRetrofitterProtocol = GraphDBFacade(
            self._ureg, custom_config).get_graph_construction_retrofitter()
        # can be changed after initialization if needed...
        self.costs: ConstructionRetrofitCostProtocol = ConstructionRetrofitCosts(
            ureg=ureg)
        self.emissions: ConstructionRetrofitEmbodiedEmissionsProtocol = RetrofitEmbodiedEmissions(
            ureg)
        # log is not written to disk or anything, it is intended that the caller gets the log after all retrofit
        # operations are done
        self.retrofit_log = RetrofitLog()

        # methods must return the area in m2 (as float, not as pint.Quantity)
        self.area_calc_methods = {
            BuildingElement.WALL:
            area_calculator.calc_wall_area_without_window_glass_area,
            BuildingElement.ROOF: area_calculator.calc_roof_area,
            BuildingElement.GROUNDFLOOR: area_calculator.calc_groundfloor_area,
        }
        self.area_calc_win_glass_method = area_calculator.calc_window_glass_area
        self.area_calc_win_frame_method = area_calculator.calc_window_frame_area
示例#4
0
class ConstrRetrofitterMock:
    retrofit_log = RetrofitLog()
    def set_year_of_retrofit(self, year):
        pass

    def set_bldg_elems_to_retrofit(self, bldg_elems):
        pass

    def retrofit_bldg_construction(self,
                                   bldg_fid: int,
                                   bldg_construction: BuildingConstruction,
                                   bldg_shape_detailed: BldgShapeDetailed):
        pass

    def reset_retrofit_log(self):
        pass
    def run(self) -> pd.DataFrame:
        """
        Do run the project with retrofitting.
        It does create the retrofit scenarios for each of retrofit periods you passed in the weather files dictionary in the initilization.

        :return: annual results for all retrofit scenarios.
        :rtype: pd.DataFrame
        """
        retrofit_period_years = self._retrofitter.get_retrofit_periods()

        # create base scenario
        self._current_sim_period_year = retrofit_period_years[0]
        base_weather_file = self._weather_per_period[self._current_sim_period_year]
        base_scenario_config = {
            "MANAGER": {"SINGLE_SITE": {"ACTIVE": True, "WEATHER_FILE": base_weather_file}, "SITE_PER_CH_COMMUNITY": {"ACTIVE": False}},
            "SITE": {"SIMULATION_YEAR": self._current_sim_period_year},
        }
        self._proj_mgr.create_scenario(str(self._current_sim_period_year), base_scenario_config)
        prev_sz_name = str(self._current_sim_period_year)
        for bldg_c in self._proj_mgr.get_sim_mgr_for(prev_sz_name).bldg_containers.values():
            # initialize empty retrofit logs because in retrofit method log is uesed to lookup for previous retrofits...
            bldg_c.set_retrofit_log(RetrofitLog())

        # retrofit needs energy demand from previous simulation to calculate emissions, thus run simulation for
        # before going to the next retrofit period
        self._proj_mgr.run_not_simulated_scenarios()
        # create and run scenario for each retrofit period
        for sim_year in retrofit_period_years[1:]:
            sz_name = str(sim_year)
            self._current_sim_period_year = sim_year
            self._proj_mgr.derive_scenario(prev_sz_name, sz_name, self._change_sim_year_for_model)
            self._retrofitter.retrofit_site(
                sim_year,
                self._proj_mgr.get_sim_mgr_for(sz_name).bldg_containers,
                self._proj_mgr.get_sim_mgr_for(prev_sz_name).bldg_containers,
            )
            self._proj_mgr.run_not_simulated_scenarios()
            prev_sz_name = sz_name

        self._retrofitter.get_retrofit_log().save(str(self._proj_base_path / Path(RETROFIT_LOG_NAME)))
        return self._proj_mgr.collect_all_scenario_summaries(
            metadata_descr_project_summary=f"Retrofit sceanarios created with {__name__}. Simulation year and weather defined in configuarion is not used! "
            f"following weather files were used: {self._weather_per_period}. For other "
            f"details please have a look in the metadata of each simulation period"
        )
示例#6
0
def test_basic_log():
    ureg = cesarp.common.init_unit_registry()
    myLogger = RetrofitLog()
    myLogger.log_retrofit_measure(
        bldg_fid=33,
        bldg_element=BuildingElement.WINDOW,
        retrofitted_area=200 * ureg.m**2,
        year_of_retrofit=2020,
        retrofit_target="SIA380_MIN",
        costs=200 * ureg.CHF / ureg.m**2,
        non_renewable_pen=0.02 * ureg.MJ * ureg.Oileq / ureg.m**2,
        co2_emission=3 * ureg.kg * ureg.CO2eq / ureg.m**2,
        old_construction_name="bad_construction",
        new_construction_name="good_construction",
    )
    assert (len(myLogger.my_log_entries) == 1)
    assert (len(myLogger.convert_to_df()) == 1)
    the_log = "test_retrofit_log.csv"
    myLogger.save(the_log)
    assert os.path.isfile(the_log)
    os.remove(the_log)
示例#7
0
 def __init__(self,
              ureg: pint.UnitRegistry,
              custom_config: Dict[str, Any] = {}):
     """
     :param ureg:
     :param year_of_retrofit: must match one of cesarp.energy_strategy config "TIME_PERIODS"
     :param custom_config:
     """
     self._cfg = cesarp.common.load_config_for_package(
         _default_config_file, __package__, custom_config)
     self._op_emissions_calculator = OperationalEmissionsAndCosts(
         ureg, custom_config)
     self._energy_target = EnergyTargetLookup(
         self._cfg["ENERGY_TARGETS_LOOKUP_FILE"], ureg)
     self._supported_bldg_types = [BldgType.MFH, BldgType.SFH]
     self._retrofit_rates_accessor: RetrofitRates = RetrofitRates(
         custom_config)
     self._per_elem_construction_retrofitter = BuildingElementsRetrofitter(
         ureg)
     self._retrofit_categories_last_run: Optional[Dict[
         BldgType, List[RetrofitCategoryDetails]]] = None
     self._all_buildings_retrofit_log: RetrofitLog = RetrofitLog()
     self._logger = logging.getLogger(__name__)
示例#8
0
class EnergyPerspective2050BldgElementsRetrofitter:
    """
    Implements constructional retrofit as outlined in the Master Thesis of Jonas Landolt.
    For Details about the retrofit strategy please check chapter 5.1.6 in CESAR-Tool_Documentation.pdf

    This class contains the logic to select the buildings for which a retrofit measure should be applied and in case
    partial retrofit is activated (configuration parameter DO_PARTIAL_RETROFIT) also which building elements shall be retrofitted,
    e.g. only Roof and Wall or Windows only. In case partial retrofit is not on, all buildings which get a retrofit
    are fully retrofitted, meaning Roof, Wall, Window, Groundfloor are retrofitted.
    To carry out the retrofits the cesarp.retrofit.BuildingElementsRetrofitter is used.

    Retrofit strategy only applied so far for residential buildings!
    Emission and costs for retrofit can only be calculated for rectangualr footprint shapes, as the area of the groundfloor and
    roof area are required and calculating the area of polygon area is not yet implemented (e.g. one could use shaply or another lib to do that).
    In case of non-rectangular footprint shape, retrofit emission and costs for roof and groundfloor are set to None in RetrofitLog

    The percentages of full and partial retrofit depending on building age and retrofit year/period are configurable
    through input files and are located in the cesarp.energy_strategy, as they relate to the energy strategy.

    Taking any retrofit shares that could not be "fulfilled" in one retrofit period to the next is not yet implemented,
    but was in the Matlab version. See gitlab Issue #109.

    If the site does not have buildings for a certain building type and age class used in the retrofit rate definition,
    that retrofit rate get's ignored. For example, if the site defines no single family home (SFH) buildings with
    year_of_construction in the range of 2001-2005, the assigned retrofit rates for age class 2001-2005 are ignored.
    """
    def __init__(self,
                 ureg: pint.UnitRegistry,
                 custom_config: Dict[str, Any] = {}):
        """
        :param ureg:
        :param year_of_retrofit: must match one of cesarp.energy_strategy config "TIME_PERIODS"
        :param custom_config:
        """
        self._cfg = cesarp.common.load_config_for_package(
            _default_config_file, __package__, custom_config)
        self._op_emissions_calculator = OperationalEmissionsAndCosts(
            ureg, custom_config)
        self._energy_target = EnergyTargetLookup(
            self._cfg["ENERGY_TARGETS_LOOKUP_FILE"], ureg)
        self._supported_bldg_types = [BldgType.MFH, BldgType.SFH]
        self._retrofit_rates_accessor: RetrofitRates = RetrofitRates(
            custom_config)
        self._per_elem_construction_retrofitter = BuildingElementsRetrofitter(
            ureg)
        self._retrofit_categories_last_run: Optional[Dict[
            BldgType, List[RetrofitCategoryDetails]]] = None
        self._all_buildings_retrofit_log: RetrofitLog = RetrofitLog()
        self._logger = logging.getLogger(__name__)

    def retrofit_site(
        self,
        year_of_retrofit: int,
        bldg_containers_current: Dict[int, BuildingContainer],
        bldg_containers_prev_period: Dict[int, BuildingContainer],
    ) -> RetrofitLog:
        """
        Retrofit building construction.
        Buildings meeting one of the following criteria do not get retrofitted:
        - Operational Emissions below target
        - Building got retrofitted

        As for the operational emissions calculation, dhw and heating energy carrier from current building model are
        used, make sure that if your retrofit strategy involves system retrofit to perform the system retrofit before
        calling this method.

        :param bldg_containers_current: dictionary with key fid, value the building container of the bldg for the
                                        current retrofit period, no simulation results expected
        :param bldg_containers_prev_period: dictionary with key fid, value the building container of the bldg for the
                                            previos retrofit period, should include simulation results and retrofit log
        :return: retrofit log for including retrofit measures for all buildings
        """
        retrofit_categories_by_bt = self._define_nr_of_bldgs_to_retrofit(
            year_of_retrofit, bldg_containers_current.values())

        self._all_buildings_retrofit_log = RetrofitLog()

        self._per_elem_construction_retrofitter.set_year_of_retrofit(
            year_of_retrofit)

        for fid, container_current in bldg_containers_current.items():
            self._per_elem_construction_retrofitter.reset_retrofit_log()
            model_current = container_current.get_bldg_model()

            if (model_current.bldg_construction.installation_characteristics.
                    e_carrier_dhw is not None
                    and model_current.bldg_construction.
                    installation_characteristics.e_carrier_heating
                    is not None):
                container_prev_sim = bldg_containers_prev_period[fid]

                energy_demand_last_period = container_prev_sim.get_energy_demand_sim_res(
                )
                emission_target_reached = self._check_emissions_below_target(
                    energy_demand_last_period,
                    model_current.bldg_construction.
                    installation_characteristics.e_carrier_dhw,
                    model_current.bldg_construction.
                    installation_characteristics.e_carrier_heating,
                    model_current.site.simulation_year,
                )
                retrofitted_in_last_period = self._check_retrofitted_in_last_period(
                    container_prev_sim.get_bldg_model().site.simulation_year,
                    container_prev_sim.get_retrofit_log())

                if not emission_target_reached and not retrofitted_in_last_period:
                    for ret_category in retrofit_categories_by_bt[
                            model_current.bldg_type]:
                        if ret_category.constr_ac.isInClass(
                                model_current.year_of_construction):
                            if not ret_category.is_target_nr_of_bldgs_reached(
                            ):
                                self._do_retrofit(
                                    model_current,
                                    ret_category.bldg_elems_to_retrofit)
                                ret_category.increment_retrofitted_cnt()
                                break

                if container_current.has_retrofit_log():
                    container_current.get_retrofit_log().append_log(
                        self._per_elem_construction_retrofitter.retrofit_log)
                else:
                    container_current.set_retrofit_log(
                        self._per_elem_construction_retrofitter.retrofit_log)

                self._all_buildings_retrofit_log.append_log(
                    self._per_elem_construction_retrofitter.retrofit_log)
            else:
                self._logger.warn(
                    f"energy carrier for DHW and Heating not available for {fid}, thus that building was NOT considered for retrofit"
                )

        return self._all_buildings_retrofit_log

    def get_retrofit_periods(self):
        return self._retrofit_rates_accessor.time_periods

    def get_retrofit_log(self):
        return self._all_buildings_retrofit_log

    def reset_retrofit_log(self):
        return self._per_elem_construction_retrofitter.reset_retrofit_log()

    def _define_nr_of_bldgs_to_retrofit(
        self, year_of_retrofit: int,
        bldg_containers: Iterable[BuildingContainer]
    ) -> Dict[BldgType, List[RetrofitCategoryDetails]]:
        """
        :param year_of_retrofit: year of this retrofit period
        :param nr_of_bldgs_on_site:
        :return: Dict[Building Type, Dict[Construction Age Bucket, List[Tuple(List[Building Elements to Retrofit], nr of bldgs to retrofit, nr of bldgs retrofitted)]]]
                Building Type: one of the self._supported_bldg_tpyes
                Construction Age Bucket: age classes as used for the retrofit rate definition in cesarp.energy_strategy.RetrofitRates.
                                                Note: do not match with age class used for constructional archetypes
                Building Elements to Retrofit: Building elements to be retrofitted, for full retrofit this
                                                includes all building elements, thus only one Tuple will be
                                                in the List. If partial retrofit is active, the list may
                                                contain up to 15 entries, for each possible combination of building elements.
                nr of bldgs to retrofit: number of buildings, according to retrofit rate and total buildings of that
                                            type and with it's construction year in the age class, that should get a
                                            retrofit for the listed building elements
                nr of bldgs retrofitte: always 0 when returned by this function, can be used to track how many
                                        buildings got that retrofit measure
        """
        retrofit_categories_by_bt = {}
        age_classes_used_for_ret_rates = self._retrofit_rates_accessor.get_retrofit_rates_age_classes(
        )
        nr_of_bldgs_on_site = self._get_nr_of_bldg_per_bldg_type_and_ac(
            bldg_containers, age_classes_used_for_ret_rates)
        for bt in self._supported_bldg_types:
            if self._cfg["DO_PARTIAL_RETROFIT"]:
                partial_retrofit_rates = self._retrofit_rates_accessor.get_partial_retrofit_rates_per_age_class(
                    sim_year=year_of_retrofit, bldg_type=bt)
                ret_cats = []
                for ac, rate_per_bldg_elems in partial_retrofit_rates.items():
                    for (bldg_elems, ret_rate) in rate_per_bldg_elems:
                        target_num_bldgs = int(
                            round(ret_rate * nr_of_bldgs_on_site[bt][ac],
                                  ndigits=0))
                        ret_cats.append(
                            RetrofitCategoryDetails(
                                bldg_type=bt,
                                constr_ac=ac,
                                bldg_elems_to_retrofit=bldg_elems,
                                target_nr_of_bldgs_to_retrofit=target_num_bldgs,
                            ))
                retrofit_categories_by_bt[bt] = ret_cats

            else:
                retrofit_rates = self._retrofit_rates_accessor.get_full_retrofit_rate_per_age_class(
                    sim_year=year_of_retrofit, bldg_type=bt)
                full_retrofit_elems = [
                    BuildingElement.ROOF,
                    BuildingElement.WINDOW,
                    BuildingElement.WALL,
                    BuildingElement.GROUNDFLOOR,
                ]
                ret_per_ac = []
                for ac, ret_rate in retrofit_rates.items():
                    target_num_bldgs = int(
                        round(ret_rate * nr_of_bldgs_on_site[bt][ac],
                              ndigits=0))
                    ret_per_ac.append(
                        RetrofitCategoryDetails(
                            bldg_type=bt,
                            constr_ac=ac,
                            bldg_elems_to_retrofit=full_retrofit_elems,
                            target_nr_of_bldgs_to_retrofit=target_num_bldgs,
                        ))
                retrofit_categories_by_bt[bt] = ret_per_ac
        self._retrofit_categories_last_run = retrofit_categories_by_bt
        return retrofit_categories_by_bt

    def _get_nr_of_bldg_per_bldg_type_and_ac(
            self, bldg_containers: Iterable[BuildingContainer],
            age_buckets: List[AgeClass]
    ) -> Dict[BldgType, Dict[AgeClass, int]]:

        nr_of_bldgs = {
            bt: {ac: 0
                 for ac in age_buckets}
            for bt in self._supported_bldg_types
        }
        for bldg_c in bldg_containers:
            model = bldg_c.get_bldg_model()
            for bucket in age_buckets:
                if bucket.isInClass(model.year_of_construction):
                    if model.bldg_type not in self._supported_bldg_types:
                        raise Exception(
                            f"{__file__} does not support bldg type {model.bldg_type} of bldg fid {model.fid}"
                        )
                    nr_of_bldgs[model.bldg_type][bucket] += 1
                    break

        return nr_of_bldgs

    def _do_retrofit(self, model: BuildingModel,
                     elems_to_retrofit: List[BuildingElement]) -> None:
        self._per_elem_construction_retrofitter.set_bldg_elems_to_retrofit(
            elems_to_retrofit)
        self._per_elem_construction_retrofitter.retrofit_bldg_construction(
            model.fid, model.bldg_construction, model.bldg_shape)

    def _check_emissions_below_target(
        self,
        energy_demand: EnergyDemandSimulationResults,
        dhw_carrier: EnergySource,
        heating_carrier: EnergySource,
        year_for_emission_calc,
    ):
        """
        Returns True if operational costs and emissions are below the required SIA target
        Translated from Matlab CESAR script constret_decision.m
        """
        op_emissions_costs = self._op_emissions_calculator.get_operational_emissions_and_costs(
            specific_dhw_demand=energy_demand.specific_dhw_demand,
            total_dhw_demand=energy_demand.tot_dhw_demand,
            dhw_carrier=dhw_carrier,
            specific_heating_demand=energy_demand.specific_heating_demand,
            total_heating_demand=energy_demand.tot_dhw_demand,
            heating_carrier=heating_carrier,
            specific_electricity_demand=energy_demand.
            specific_electricity_demand,
            total_electricity_demand=energy_demand.tot_electricity_demand,
            sim_year=year_for_emission_calc,
        )
        bldg_op_pen = op_emissions_costs.total_pen.to(
            self._energy_target.pen_unit)
        bldg_op_co2 = op_emissions_costs.total_co2_emission.to(
            self._energy_target.co2_unit)
        if bldg_op_pen.m <= self._energy_target.get_resi_op_pen_target(
                new_bldg=False
        ).m and bldg_op_co2.m <= self._energy_target.get_resi_op_co2_target(
                new_bldg=False).m:
            return True
        return False

    def _check_retrofitted_in_last_period(self, last_period_year: int,
                                          last_sim_retrofit: RetrofitLog):
        # TODO should it really only look at the last year? (see issue 109 on gitlab)
        return last_sim_retrofit.was_construction_retrofitted_in(
            last_period_year)
示例#9
0
 def _check_retrofitted_in_last_period(self, last_period_year: int,
                                       last_sim_retrofit: RetrofitLog):
     # TODO should it really only look at the last year? (see issue 109 on gitlab)
     return last_sim_retrofit.was_construction_retrofitted_in(
         last_period_year)
示例#10
0
    def retrofit_site(
        self,
        year_of_retrofit: int,
        bldg_containers_current: Dict[int, BuildingContainer],
        bldg_containers_prev_period: Dict[int, BuildingContainer],
    ) -> RetrofitLog:
        """
        Retrofit building construction.
        Buildings meeting one of the following criteria do not get retrofitted:
        - Operational Emissions below target
        - Building got retrofitted

        As for the operational emissions calculation, dhw and heating energy carrier from current building model are
        used, make sure that if your retrofit strategy involves system retrofit to perform the system retrofit before
        calling this method.

        :param bldg_containers_current: dictionary with key fid, value the building container of the bldg for the
                                        current retrofit period, no simulation results expected
        :param bldg_containers_prev_period: dictionary with key fid, value the building container of the bldg for the
                                            previos retrofit period, should include simulation results and retrofit log
        :return: retrofit log for including retrofit measures for all buildings
        """
        retrofit_categories_by_bt = self._define_nr_of_bldgs_to_retrofit(
            year_of_retrofit, bldg_containers_current.values())

        self._all_buildings_retrofit_log = RetrofitLog()

        self._per_elem_construction_retrofitter.set_year_of_retrofit(
            year_of_retrofit)

        for fid, container_current in bldg_containers_current.items():
            self._per_elem_construction_retrofitter.reset_retrofit_log()
            model_current = container_current.get_bldg_model()

            if (model_current.bldg_construction.installation_characteristics.
                    e_carrier_dhw is not None
                    and model_current.bldg_construction.
                    installation_characteristics.e_carrier_heating
                    is not None):
                container_prev_sim = bldg_containers_prev_period[fid]

                energy_demand_last_period = container_prev_sim.get_energy_demand_sim_res(
                )
                emission_target_reached = self._check_emissions_below_target(
                    energy_demand_last_period,
                    model_current.bldg_construction.
                    installation_characteristics.e_carrier_dhw,
                    model_current.bldg_construction.
                    installation_characteristics.e_carrier_heating,
                    model_current.site.simulation_year,
                )
                retrofitted_in_last_period = self._check_retrofitted_in_last_period(
                    container_prev_sim.get_bldg_model().site.simulation_year,
                    container_prev_sim.get_retrofit_log())

                if not emission_target_reached and not retrofitted_in_last_period:
                    for ret_category in retrofit_categories_by_bt[
                            model_current.bldg_type]:
                        if ret_category.constr_ac.isInClass(
                                model_current.year_of_construction):
                            if not ret_category.is_target_nr_of_bldgs_reached(
                            ):
                                self._do_retrofit(
                                    model_current,
                                    ret_category.bldg_elems_to_retrofit)
                                ret_category.increment_retrofitted_cnt()
                                break

                if container_current.has_retrofit_log():
                    container_current.get_retrofit_log().append_log(
                        self._per_elem_construction_retrofitter.retrofit_log)
                else:
                    container_current.set_retrofit_log(
                        self._per_elem_construction_retrofitter.retrofit_log)

                self._all_buildings_retrofit_log.append_log(
                    self._per_elem_construction_retrofitter.retrofit_log)
            else:
                self._logger.warn(
                    f"energy carrier for DHW and Heating not available for {fid}, thus that building was NOT considered for retrofit"
                )

        return self._all_buildings_retrofit_log
class BuildingElementsRetrofitter:
    """
    Handles retrofit for all or part of the building elements.
    In case of window retrofitting the infiltration rate is changed to the value set in configuration
    (INFILRATION_RATE_AFTER_WINDOW_RETROFIT), the actual window construction used for retrofitting has no
    influence on that value.

    Depending on the retrofit target (initialization parameter retrofit_target) using the
    cesarp.retrofit.ConstructionRetrofitter the retrofited consruction is assigend. Furthermore, costs for retrofit
    and embodied emissions (non-renewable PEN & CO2) are calculated and saved in the retrofit log.

    All retrofit measures are logged, which is accessible as member "retrofit_log" as instance of
    cesarp.retrofit.RetrofitLog class. It includes the embodied costs and emissions. Depending on your needs,
    you can reset the log by calling reset_retrofit_log() before calling retrofit_bldg_construction(....) and
    retrieving the log right afterwards, which gives you the log only for that one building. Otherwise, log entries
    for the different buildings are appended (log entries contain building fid).
    """

    _retrofittable_bldg_elems = [
        BuildingElement.ROOF,
        BuildingElement.WALL,
        BuildingElement.GROUNDFLOOR,
        BuildingElement.WINDOW,
    ]

    def __init__(self,
                 ureg: pint.UnitRegistry,
                 custom_config: Dict[str, Any] = {}):
        """
        Initialize. See set_year_of_retrofit() and set_bldgs_elems_to_retrofit() for further
        configuration options.

        :param ureg: pint unit registry
        """
        self._ureg = ureg
        self._year_of_retrofit: Optional[int] = None
        self._bldg_elems_to_retrofit = self._retrofittable_bldg_elems
        self._logger = logging.getLogger(__name__)
        self._cfg = cesarp.common.load_config_for_package(
            _default_config_file, __package__, custom_config)

        # can be changed after initialization if needed...
        self.construction_retrofitter: ConstructionRetrofitterProtocol = GraphDBFacade(
            self._ureg, custom_config).get_graph_construction_retrofitter()
        # can be changed after initialization if needed...
        self.costs: ConstructionRetrofitCostProtocol = ConstructionRetrofitCosts(
            ureg=ureg)
        self.emissions: ConstructionRetrofitEmbodiedEmissionsProtocol = RetrofitEmbodiedEmissions(
            ureg)
        # log is not written to disk or anything, it is intended that the caller gets the log after all retrofit
        # operations are done
        self.retrofit_log = RetrofitLog()

        # methods must return the area in m2 (as float, not as pint.Quantity)
        self.area_calc_methods = {
            BuildingElement.WALL:
            area_calculator.calc_wall_area_without_window_glass_area,
            BuildingElement.ROOF: area_calculator.calc_roof_area,
            BuildingElement.GROUNDFLOOR: area_calculator.calc_groundfloor_area,
        }
        self.area_calc_win_glass_method = area_calculator.calc_window_glass_area
        self.area_calc_win_frame_method = area_calculator.calc_window_frame_area

    def reset_retrofit_log(self):
        self.retrofit_log = RetrofitLog()

    def save_retrofit_log(self, filepath: str):
        self.retrofit_log.save(filepath)

    def get_retrofit_log_as_df(self) -> pd.DataFrame:
        return self.retrofit_log.convert_to_df()

    def set_year_of_retrofit(self, year_of_retrofit: int):
        self._year_of_retrofit = year_of_retrofit

    def set_bldg_elems_to_retrofit(
            self, bldg_elems_to_retrofit: List[BuildingElement]):
        self._bldg_elems_to_retrofit = bldg_elems_to_retrofit

    def retrofit_bldg_construction(self, bldg_fid: int,
                                   bldg_construction: BuildingConstruction,
                                   bldg_shape_detailed: BldgShapeDetailed):
        """
        Modifies building construction in place according to set parameters in constructor.

        :param bldg_fid: currently only used for logging
        :param bldg_construction: bldg construction model object
        :return: nothing, bldg_construction is modified in-place
        """
        for bldg_elem in self._bldg_elems_to_retrofit:
            current_construction = bldg_construction.get_construction_for_bldg_elem(
                bldg_elem)

            retrofitted_construction: Union[Construction, WindowConstruction]
            try:
                if bldg_elem == BuildingElement.WINDOW:
                    retrofitted_construction = self.construction_retrofitter.get_retrofitted_window(
                        current_construction)
                    bldg_construction.infiltration_rate = self._ureg(
                        self._cfg["INFILRATION_RATE_AFTER_WINDOW_RETROFIT"])
                else:
                    retrofitted_construction = self.construction_retrofitter.get_retrofitted_construction(
                        current_construction)
            except KeyError:
                self._logger.warning(
                    f"bldg_fid {bldg_fid}: no retrofit construction found for bldg elem {bldg_elem} "
                    f"with current construction {current_construction.name}. no retrofit is applied."
                )
                continue

            bldg_construction.set_construction_for_bldg_elem(
                bldg_elem, retrofitted_construction)

            try:
                (area_for_elem, co2_emission, costs,
                 pen) = self._get_emission_and_costs(bldg_elem,
                                                     bldg_shape_detailed,
                                                     retrofitted_construction)
            except CesarGeometryException as cge:
                self._logger.warning(
                    f"no emission and costs calculated for fid {bldg_fid}, element {bldg_elem.name}",
                    exc_info=cge)
                area_for_elem, co2_emission, costs, pen = None, None, None, None

            self.retrofit_log.log_retrofit_measure(
                bldg_fid=bldg_fid,
                bldg_element=bldg_elem,
                retrofitted_area=area_for_elem,
                year_of_retrofit=self._year_of_retrofit,
                retrofit_target=self.construction_retrofitter.
                get_retrofit_target_info(),
                costs=costs,
                non_renewable_pen=pen,
                co2_emission=co2_emission,
                old_construction_name=current_construction.name,
                new_construction_name=retrofitted_construction.name,
            )

    def _get_emission_and_costs(self, bldg_elem, bldg_shape_detailed,
                                retrofitted_constr):
        """
        Calculates retrofit emission and costs for the given building element and building shape.
        """
        if bldg_elem == BuildingElement.WINDOW:
            # glass and frame area for all windows of building
            win_glass_area = self.area_calc_win_glass_method(
                bldg_shape_detailed) * self._ureg.m**2
            win_frame_area = self.area_calc_win_frame_method(
                bldg_shape_detailed) * self._ureg.m**2
            costs = self.costs.get_costs_for_window_retrofit(
                retrofitted_constr) * win_glass_area
            co2_emission = (
                self.emissions.get_win_ret_glass_emb_co2(retrofitted_constr) *
                win_glass_area +
                self.emissions.get_win_ret_frame_emb_co2(retrofitted_constr) *
                win_frame_area)
            pen = (self.emissions.get_win_ret_glass_emb_non_renewable_pen(
                retrofitted_constr) * win_glass_area +
                   self.emissions.get_win_ret_frame_emb_non_renewable_pen(
                       retrofitted_constr) * win_frame_area)
            elem_area = win_glass_area + win_frame_area
        else:
            # area e.g. for all walls of building
            elem_area = self.area_calc_methods[bldg_elem](
                bldg_shape_detailed) * self._ureg.m**2
            costs = self.costs.get_costs_for_construction_retrofit(
                retrofitted_constr) * elem_area
            co2_emission = self.emissions.get_constr_ret_emb_co2(
                retrofitted_constr) * elem_area
            pen = self.emissions.get_constr_ret_emb_non_renewable_pen(
                retrofitted_constr) * elem_area
        return elem_area, co2_emission, costs, pen
 def reset_retrofit_log(self):
     self.retrofit_log = RetrofitLog()