示例#1
0
class Model:
    simulated = []  # this is not strictly neccessary

    # it uses a class variable to save
    # all simulated instances of a model
    # they get recorded at the end of the
    # Model.simulate() method

    def __init__(
            self,
            building_path="data/building_oib_16linie.xlsx",
            kWp=1,  # PV kWp
            battery_kWh=1):  # Battery kWh

        ###### Compononets #####
        # (Other classes and parts, that form the model)
        self.building = Building(path=building_path)
        self.HVAC = HVAC()

        self.PV = PV(csv="data/pv_1kWp.csv", kWp=1)
        self.PV.set_kWp(kWp)

        self.battery = Battery(kWh=battery_kWh)

        ###### Parameters #####
        self.cp_air = 0.34  # spez. Wärme kapazität Luft (Wh/m3K)

        self.price_grid = 0.19  # €/kWh
        self.price_feedin = 0.05  # €/kWh

        ###### Timeseries #####
        # load Usage characteristics
        self.Usage = pd.read_csv("data/usage_profiles.csv", encoding="cp1252")

        # load climate data
        self.TA = np.genfromtxt("data/climate.csv", delimiter=";")[1:, 1]
        # load solar gains
        self.QS = np.genfromtxt("data/Solar_gains.csv")  # W/m²

    def init_sim(self):
        # (re)load profiles from self.Usage
        #this is neccessary  if the PV model has changed inbetween simulations
        self.QI_winter = self.Usage["Qi Winter W/m²"].to_numpy()
        self.QI_summer = self.Usage["Qi Sommer W/m²"].to_numpy()

        self.ACH_V = self.Usage["Luftwechsel_Anlage_1_h"].to_numpy()
        self.ACH_I = self.Usage["Luftwechsel_Infiltration_1_h"].to_numpy()
        self.Qdhw = self.Usage["Warmwasserbedarf_W_m2"].to_numpy()
        self.ED_user = self.Usage["Nutzerstrom_W_m2"].to_numpy()

        # (re)load PV profiles
        #this is neccessary  if the PV model has changed inbetween simulations
        self.PV_prod = self.PV.TSD * 1000 / self.building.bgf  # everything is in Wh/m²
        self.PV_use = np.zeros(8760)
        self.PV_feedin = np.zeros(8760)
        self.PV_to_battery = np.zeros(8760)

        # initialize result arrays
        self.timestamp = pd.Series(
            np.arange('2020-01-01 00:00',
                      '2021-01-01 00:00',
                      dtype='datetime64[h]'))

        self.QV = np.zeros(8760)  # ventilation losses
        self.QT = np.zeros(8760)  # transmission losses
        self.QI = np.zeros(8760)  #  Internal losses
        self.Q_loss = np.zeros(8760)  # total losses without heating/cooling

        self.TI = np.zeros(8760)  # indoor temperature

        self.QH = np.zeros(8760)  # Heating demand Wh/m²
        self.QC = np.zeros(8760)  # Cooling demand Wh/m²

        # Energy demands
        self.ED_QH = np.zeros(8760)  # Electricity demand for heating Wh/m²
        self.ED_QC = np.zeros(8760)  # Electricity demand for cooling Wh/m²
        #self.ED_Qdhw = 0
        self.ED = np.zeros(8760)  # Electricity demand Wh/m²
        self.ED_grid = np.zeros(8760)

        self.Btt_to_ED = np.zeros(8760)

        ## initialize starting conditions
        self.TI[0] = self.HVAC.minimum_room_temperature

    def calc_QV(self, t):
        """Ventilation heat losses [W/m²BGF] at timestep t"""
        dT = self.TA[t - 1] - self.TI[t - 1]
        room_height = self.building.net_storey_height
        cp_air = self.cp_air
        # thermally effective air change
        eff_airchange = self.ACH_I[t] + self.ACH_V[
            t]  # * M.VentilationSystem.share_cs * rel_ACH_after_heat_recovery

        self.QV[t] = eff_airchange * room_height * cp_air * dT

    def calc_QT(self, t):
        """Transmission heat losses [W/m²BGF] at timestep t"""
        dT = self.TA[t - 1] - self.TI[t - 1]
        self.QT[t] = self.building.LT * dT

    def calc_QI(self, t):
        heat = self.timestamp[t].month in self.HVAC.heating_months
        cool = self.timestamp[t].month in self.HVAC.cooling_months
        if (heat and cool) or (
                not heat and
                not cool):  # wenn beides oder keinss von beiden, mittelwert
            self.QI[t] = (self.QI_winter[t] + self.QI_summer[t]) / 2
        elif heat:
            self.QI[t] = self.QI_winter[t]
        elif cool:
            self.QI[t] = self.QI_summer[t]
        else:
            raise NotImplementedError("Case not defined!")

    def handle_losses(self, t):
        # determine losses
        self.Q_loss[t] = (self.QT[t] + self.QV[t]) + self.QS[t] + self.QI[t]
        # determine indoor temperature after losses
        self.TI[t] = self.TI_after_Q(self.TI[t - 1], self.Q_loss[t],
                                     self.building.heat_capacity)

    def TI_after_Q(self, TI_before, Q, cp):
        """cp = spec. building heat_capacity"""
        return TI_before + Q / cp

    def is_heating_on(self, t, TI_new):
        if self.HVAC.heating_system == True:
            if self.timestamp[t].month in self.HVAC.heating_months:
                if TI_new < self.HVAC.minimum_room_temperature:
                    return True
        return False

    def is_cooling_on(self, t, TI_new):
        """
        Determines, whether all conditions are met to use cooling
        """
        c1 = self.HVAC.cooling_system == True
        c2 = self.timestamp[t].month in self.HVAC.cooling_months
        c3 = TI_new > self.HVAC.maximum_room_temperature
        return all(
            [c1, c2, c3]
        )  # returns True if all conditions are true, False otherwise. similarly, any(). You can stack this way more cleanly

    def minimum_Q(self, TI, set_min, set_max, cp):
        """calculates the minimum Q (positive or negative) to reach the setpoint targets"""
        if TI < set_min:
            return (set_min - TI) * cp
        if TI > set_max:
            return (set_max - TI) * cp
        else:
            return 0.

    def handle_heating(self, t):
        """Handles the use of a heating system, applies changes to self.QH, self.ED_QH, self.TI if neccessary"""
        TI = self.TI[t]
        if self.is_heating_on(t, TI):
            required_QH = self.minimum_Q(
                TI=TI,
                set_min=self.HVAC.minimum_room_temperature,
                set_max=self.HVAC.maximum_room_temperature,
                cp=self.building.heat_capacity)
            required_ED = required_QH / self.HVAC.HP_COP / self.HVAC.heating_eff
            available_power = self.HVAC.HP_heating_power
            self.ED_QH[t] = min(required_ED, available_power)
            self.QH[
                t] = self.ED_QH[t] * self.HVAC.HP_COP * self.HVAC.heating_eff
            self.TI[t] = self.TI_after_Q(TI, self.QH[t],
                                         self.building.heat_capacity)

    def handle_cooling(self, t):
        """Handles the use of a heating system, applies changes to self.QH, self.ED_QH, self.TI if neccessary"""
        TI = self.TI[t]
        if self.is_cooling_on(t, TI):
            required_QC = self.minimum_Q(
                TI=TI,
                set_min=self.HVAC.minimum_room_temperature,
                set_max=self.HVAC.maximum_room_temperature,
                cp=self.building.heat_capacity)
            required_ED = -required_QC / self.HVAC.HP_COP / self.HVAC.heating_eff
            available_power = self.HVAC.HP_heating_power
            self.ED_QC[t] = min(required_ED, available_power)
            self.QC[
                t] = -self.ED_QC[t] * self.HVAC.HP_COP * self.HVAC.heating_eff
            self.TI[t] = self.TI_after_Q(TI, self.QC[t],
                                         self.building.heat_capacity)

    def calc_ED(self, t):
        self.ED[t] = self.ED_QH[t] + self.ED_QC[t] + self.ED_user[t]

    def handle_PV(self, t):
        """allocates the PV to direct and  battery charge use"""
        #calculate the direct Use of PV
        self.PV_use[t] = min(self.PV_prod[t], self.ED[t])
        remain = self.PV_prod[t] - self.PV_use[t]

        #calculate the remaining PV to Battery
        self.PV_to_battery[t] = self.battery.charge(
            remain * self.building.bgf / 1000) * 1000 / self.building.bgf
        remain = remain - self.PV_to_battery[t]
        #calculate the remaining PV to Battery
        self.PV_feedin[t] = max(remain - self.ED[t], 0)

    def handle_grid(self, t):
        """calculate the remaining grid demand: Total Energy demand [ED] - PVuse - Battery_discharge"""
        self.ED_grid[t] = self.ED[t] - self.PV_use[t] - self.Btt_to_ED[t]

    def handle_battery(self, t):
        # Lower SoC by hourly losses
        self.battery.SoC = (1 - self.battery.discharge_per_hour) \
                           * self.battery.SoC

        # calculate remaining electricity demand not covered after PV use for time t
        remaining_ED = (self.ED[t] - self.PV_use[t]
                        ) * self.building.bgf / 1000  #kW not W/m²
        # conditions
        # if remaining energy demand > 0 AND battery.SoC > 0
        c1 = (remaining_ED > 0)
        c2 = (self.battery.SoC > 0)
        if all([c1, c2]):
            self.Btt_to_ED[t] = self.battery.discharge(remaining_ED)

    def calc_cost(self, years=20, verbose=True):
        """calculates the total cost of the system"""
        # calc investment
        self.investment_cost = self.building.differential_cost * self.building.bgf + self.PV.cost + self.battery.cost
        self.operational_cost = self.building.bgf * (
                                - self.PV_feedin.sum()/1000 * self.price_feedin \
                                + self.ED_grid.sum()/1000 * self.price_grid)

        self.total_cost = self.investment_cost + self.operational_cost * years

        if verbose:
            print(f"Investment cost:  {round(self.investment_cost):>20.2f} €")
            print(
                f"Operational cost: {round(self.operational_cost):>20.2f} €/annum"
            )
            print(
                f"Total cost after {years} years: {round(self.total_cost):>11,.2f} €"
            )

        return self.total_cost

    def simulate(self):

        self.init_sim()  # don't forget to intialize the first timestep = 0
        # with sensible starting values
        # like TI[0] = self.minimum_room_temperature

        for t in range(1, 8760):
            #### Verluste
            self.calc_QV(t)
            self.calc_QT(t)
            self.calc_QI(t)
            self.handle_losses(t)

            #### Heizung
            self.handle_heating(t)

            #### Kühlung
            self.handle_cooling(t)

            #calc total energy demand
            self.calc_ED(t)

            #allocate pv
            self.handle_PV(t)

            # discharge battery
            self.handle_battery(t)

            # handle grid
            self.handle_grid(t)

        self.calc_cost(verbose=False)

        Model.simulated.append(
            self)  # this adds the model result to the base class (optional)
        return True  # the simulate() method does not NEEd to return something
        # but it can be used to check if the simulation ran successfully

    def plot(self, show=True, start=1, end=8760, month=None):
        fig, ax = plt.subplots(2, 2)  #,figsize=(8,12)) #tight_layout=True)
        ax = ax.flatten()
        self.plot_heat_balance(fig, ax[0], start=start, end=end)
        self.plot_temperatures(fig, ax[1], start=start, end=end)
        self.plot_electricity_demand(fig, ax[2], start=start, end=end)
        self.plot_electricity_use(fig, ax=ax[3], start=start, end=end)

        if show:
            dummy = plt.figure()  # create a dummy figure
            new_manager = dummy.canvas.manager  # and use its manager to display "fig"
            new_manager.canvas.figure = fig
            fig.set_canvas(new_manager.canvas)
            fig.show()

    def plot_heat_balance(self,
                          fig=None,
                          ax=None,
                          start=1,
                          end=8760,
                          **kwargs):
        if (fig, ax) == (None, None):
            fig, ax = plt.subplots(1, 1)
        self.plot_arrays(
            ax=ax,
            start=start,
            end=end,
            arrays=[self.QT, self.QV, self.QS, self.QI, self.QH, self.QC],
            **kwargs)
        ax.legend([
            "Transmissionsverluste", "Lüftungsverluste", "Solare Gewinne",
            "Innere Lasten", "Heizwärmebdedarf", "Kühlbedarf"
        ])
        ax.set_title("Wärmebilanz")
        ax.set_ylabel("W/m²")
        plt.show()

    def plot_temperatures(self,
                          fig=None,
                          ax=None,
                          start=1,
                          end=8760,
                          **kwargs):
        if (fig, ax) == (None, None):
            fig, ax = plt.subplots(1, 1)
        self.plot_arrays(ax=ax,
                         start=start,
                         end=end,
                         arrays=[self.TI, self.TA],
                         **kwargs)
        ax.legend(["Innenraum", "Außenluft"])
        ax.set_title("Temperatur")
        ax.set_ylabel("Temperatur [°C]")
        plt.show()

    def plot_electricity_demand(self,
                                fig=None,
                                ax=None,
                                start=1,
                                end=8760,
                                **kwargs):
        # FigureCanvas(fig) # not needed in mpl >= 3.1
        if (fig, ax) == (None, None):
            fig, ax = plt.subplots(1, 1)
        self.plot_arrays(
            ax=ax,
            start=start,
            end=end,
            arrays=[self.PV_prod, self.ED_QH, self.ED_QC, self.ED_user],
            **kwargs)
        ax.set_title("Strom")
        ax.set_ylabel("W/m²")
        ax.legend(["PV", "WP Heizen", "WP Kühlen", "Nutzerstrom"])
        plt.show()

    def plot_electricity_use(self,
                             fig=None,
                             ax=None,
                             start=1,
                             end=8760,
                             **kwargs):
        """plots the electricity supply and use"""
        if (fig, ax) == (None, None):
            fig, ax = plt.subplots(1, 1)
        self.plot_arrays(ax=ax,
                         start=start,
                         end=end,
                         arrays=[
                             self.PV_use, self.Btt_to_ED, self.ED_grid,
                             self.PV_to_battery, self.PV_feedin
                         ],
                         **kwargs)
        ax.set_title("PV Nutzung")
        ax.set_ylabel("W/m²")
        ax.legend([
            'PV Eigenverbrauch', 'Batterie-Entladung', 'Netzstrom',
            'Batterie-Beladung', 'Einspeisung'
        ])
        plt.show()

    def plot_arrays(self, ax, start, end, arrays: list, **kwargs):
        for array in arrays:
            ax.plot(array[start:end], **kwargs)

    def __repr__(self):
        width = len(self.building.file)
        return f"""