def run(self, P_req=[0], Q_req=[0], initSoC=50, dt=1): P_req = P_req / self.num_of_devices Q_req = Q_req / self.num_of_devices # error checking if initSoC < self.min_soc or initSoC > self.max_soc: print('ERROR: initSoC out of range') return [[], []] elif P_req == []: print('ERROR: P_req vector must not be empty') return [[], []] else: self.t = self.t + dt response = FleetResponse() # pre-define output vectors SoC SoC = numpy.zeros(2) SoC[0] = initSoC # initialize SoC p_ach = 0 q_ach = 0 # Max ramp rate and aparent powerlimit checking if (P_req - self.P_injected) > self.max_ramp_up: p_ach = self.max_ramp_up elif (P_req - self.P_injected) < self.max_ramp_down: p_ach = self.max_ramp_down else: p_ach = P_req if (Q_req - self.Q_injected) > self.max_ramp_up: q_ach = self.max_ramp_up elif (Q_req - self.Q_injected) < self.max_ramp_down: q_ach = self.max_ramp_down else: q_ach = Q_req if p_ach < self.max_power_discharge: p_ach = self.max_power_discharge if p_ach > self.max_power_charge: p_ach = self.max_power_charge S_req = float(numpy.sqrt(p_ach**2 + q_ach**2)) if S_req > self.max_apparent_power: q_ach = float( numpy.sqrt(numpy.abs(self.max_apparent_power**2 - p_ach**2)) * numpy.sign(q_ach)) S_req = float(numpy.sqrt(p_ach**2 + q_ach**2)) if p_ach != 0.0: if float(numpy.abs(S_req / p_ach)) < self.min_pf: q_ach = float( numpy.sqrt( numpy.abs((p_ach / self.min_pf)**2 - p_ach**2)) * numpy.sign(q_ach)) # run function for ERM model type if self.model_type == 'ERM': response.P_injected_max = self.max_power_discharge response.Q_injected_max = float( numpy.sqrt(numpy.abs(self.max_apparent_power**2 - p_ach**2))) # Calculate SoC and Power Achieved Ppos = min(self.max_power_charge, max(p_ach, 0)) Pneg = max(self.max_power_discharge, min(p_ach, 0)) SoC[1] = SoC[0] + float(100) * dt * ( Pneg + (Ppos * self.energy_efficiency) + self.self_discharge_power) / self.energy_capacity if SoC[1] > self.max_soc: Ppos = (self.energy_capacity * (self.max_soc - SoC[0]) / (float(100) * dt) - self.self_discharge_power) / self.energy_efficiency SoC[1] = self.max_soc if SoC[0] == self.max_soc: Ppos = 0 if SoC[1] < self.min_soc: Pneg = self.energy_capacity * (self.min_soc - SoC[0]) / ( float(100) * dt) - self.self_discharge_power SoC[1] = self.min_soc if SoC[0] == self.min_soc: Pneg = 0 p_ach = (Ppos + Pneg) * self.num_of_devices q_ach = q_ach * self.num_of_devices response.P_injected = p_ach response.Q_injected = q_ach response.P_dot = p_ach - self.P_injected response.Q_dot = q_ach - self.Q_injected response.P_service = 0 response.Q_service = 0 response.P_service_max = 0 response.Q_service_max = 0 response.Loss_standby = self.self_discharge_power response.Eff_throughput = float( p_ach > 0) * self.energy_efficiency + float(p_ach > 0) self.soc = SoC[1] return response # run function for ERM model type elif self.model_type == 'CRM': # convert AC power p_ach to DC power pdc self.pdc = self.coeff_2 * (p_ach**2) + self.coeff_1 * ( p_ach) + self.coeff_0 # convert DC power pdc to DC current """ self.ibat = self.pdc *1000 / self.vbat self.pdc = self.ibat * ((self.v1 + self.v2 + self.voc + self.ibat*self.r0) *self.n_cells) *1000 0 = -self.pdc + (self.v1 + self.v2 + self.voc)*self.n_cells) *1000*self.ibat + self.r0 *self.n_cells*1000* (self.ibat**2) """ b = ((self.v1 + self.v2 + self.voc) * self.n_cells) a = self.r0 * self.n_cells c = -self.pdc * 1000 self.ibat = (-b + numpy.sqrt(b**2 - 4 * a * c)) / (2 * a) self.vbat = (self.v1 + self.v2 + self.voc + self.ibat * self.r0) * self.n_cells # calculate dynamic voltages self.v1 = self.v1 + dt * ((1 / (self.r1 * self.c1)) * self.v1 + (1 / (self.c1)) * self.ibat) self.v2 = self.v2 + dt * ((1 / (self.r2 * self.c2)) * self.v1 + (1 / (self.c2)) * self.ibat) response.P_injected_max = self.max_power_discharge response.Q_injected_max = float( numpy.sqrt(numpy.abs(self.max_apparent_power**2 - p_ach**2))) # Calculate SoC and Power Achieved Ipos = min(self.max_current_charge, max(self.ibat, 0)) Ineg = max(self.max_current_discharge, min(self.ibat, 0)) SoC[1] = SoC[0] + float(100) * dt * ( Ineg + (Ipos * self.coulombic_efficiency) + self.self_discharge_current) / self.charge_capacity if SoC[1] > self.max_soc: Ipos = (self.charge_capacity * (self.max_soc - SoC[0]) / (float(100) * dt) - self.self_discharge_current ) / self.coulombic_efficiency SoC[1] = self.max_soc if SoC[0] == self.max_soc: Ipos = 0 self.pdc = Ipos * self.vbat / 1000 if self.coeff_2 != 0: p_ach = (-self.coeff_1 + float( numpy.sqrt(self.coeff_1**2 - 4 * self.coeff_2 * (self.coeff_0 - self.pdc)))) / ( 2 * self.coeff_2) else: p_ach = (self.pdc - self.coeff_0) / self.coeff_1 if SoC[1] < self.min_soc: Ineg = self.charge_capacity * (self.min_soc - SoC[0]) / ( float(100) * dt) - self.self_discharge_current SoC[1] = self.min_soc if SoC[0] == self.min_soc: Ineg = 0 self.pdc = Ineg * self.vbat / 1000 if self.coeff_2 != 0: p_ach = (-self.coeff_1 + float( numpy.sqrt(self.coeff_1**2 - 4 * self.coeff_2 * (self.coeff_0 - self.pdc)))) / ( 2 * self.coeff_2) else: p_ach = (self.pdc - self.coeff_0) / self.coeff_1 self.ibat = Ipos + Ineg self.soc = SoC[1] self.voc_update() self.vbat = (self.v1 + self.v2 + self.voc + self.ibat * self.r0) * self.n_cells p_ach = p_ach * self.num_of_devices q_ach = q_ach * self.num_of_devices response.P_injected = p_ach response.Q_injected = q_ach response.P_dot = p_ach - self.P_injected response.Q_dot = q_ach - self.Q_injected response.P_service = 0 response.Q_service = 0 response.P_service_max = 0 response.Q_service_max = 0 response.Loss_standby = self.self_discharge_current * ( self.voc) response.E = (self.soc - self.min_soc) * self.charge_capacity * self.voc response.Eff_throughput = (response.E - self.es) / (p_ach * dt) self.es = response.E self.vbat = (self.v1 + self.v2 + self.voc + self.ibat * self.r0) * self.n_cells return response
def run(self, P_req, Q_req, initSOC, t, dt, ts): # ExecuteFleet(self, Steps, Timestep, P_request, Q_request, forecast): # run(self, P_req=[0], Q_req=[0], ts=datetime.utcnow(), del_t=timedelta(hours=1)): # Give the code the capability to respond to None requests if P_req == None: P_req = 0 if Q_req == None: Q_req = 0 P_togrid = 0 P_service = 0 P_base = 0 P_service_max = 0 self.P_request_perWH = P_req / self.numWH # this is only for the first step number = 0 # index for running through all the water heaters NumDevicesToCall = 0 P_service_max0 = 0 # decision making about which water heater to call on for service, check if available at last step, if so then # check for SoC > self.minSOC and Soc < self.maxSOC for n in range(self.numWH): if self.P_request_perWH < 0 and self.IsAvailableAdd[ n] > 0 and self.SOC[n] < self.maxSOC: NumDevicesToCall += 1 elif self.P_request_perWH > 0 and self.IsAvailableShed[ n] > 0 and self.SOC[n] > self.minSOC: NumDevicesToCall += 1 if P_req != None: self.P_request_perWH = P_req / max( NumDevicesToCall, 1 ) # divide the fleet request by the number of devices that can be called upon # create a .csv outputfile with each water heater's metrics # outputfilename = join(self.base_path,"WH_fleet_outputs.csv") # self.outputfile = open(outputfilename,"w") # self.outputfile.write("Timestep,") # create a .csv outputfile with P_service, P_togrid, and P_base # outputfilename = join(self.base_path,"WH_fleet_outputs.csv") ################################# for wh in self.whs: # loop through all water heaters if P_req == None: response = wh.execute( self.TtankInitial[number], self.TtankInitial_b[number], self.TsetInitial[number], self.Tamb[number][0], self.RHamb[number][0], self.Tmains[number][0], self.draw[number][0], 0, self.Type, self.dt, self.draw_fleet_ave[0], self.element_on_last) P_service = 0 if P_req < 0 and self.IsAvailableAdd[number] > 0: response = wh.execute( self.TtankInitial[number], self.TtankInitial_b[number], self.TsetInitial[number], self.Tamb[number][0], self.RHamb[number][0], self.Tmains[number][0], self.draw[number][0], P_req, self.Type, self.dt, self.draw_fleet_ave[0], self.element_on_last) P_req = P_req - response.Eservice P_service += response.Eservice elif P_req > 0 and self.IsAvailableShed[number] > 0: response = wh.execute( self.TtankInitial[number], self.TtankInitial_b[number], self.TsetInitial[number], self.Tamb[number][0], self.RHamb[number][0], self.Tmains[number][0], self.draw[number][0], P_req, self.Type, self.dt, self.draw_fleet_ave[0], self.element_on_last) P_req = P_req + response.Eservice P_service -= response.Eservice #print("P_req = {}, P_service = {}, Eservice = {}".format(P_req,P_service,response.Eservice)) else: response = wh.execute( self.TtankInitial[number], self.TtankInitial_b[number], self.TsetInitial[number], self.Tamb[number][0], self.RHamb[number][0], self.Tmains[number][0], self.draw[number][0], 0, self.Type, self.dt, self.draw_fleet_ave[0], self.element_on_last) # print('P_req = {}'.format(P_req)) # assign returned parameters to associated lists to be recorded self.element_on_last[number] = response.ElementOn self.TtankInitial[number] = response.Ttank self.TtankInitial_b[number] = response.Ttank_b self.SOC[number] = response.SOC self.SOCb[number] = response.SOC_b self.IsAvailableAdd[number] = response.IsAvailableAdd self.IsAvailableShed[number] = response.IsAvailableShed self.AvailableCapacityAdd[number] = response.AvailableCapacityAdd self.AvailableCapacityShed[number] = response.AvailableCapacityShed self.ServiceCallsAccepted[number] = response.ServiceCallsAccepted self.ServiceProvided[number] = response.Eservice ''' P_togrid -= response.Eused P_base -= response.Pbase if P_req <0 or P_req > 0: P_response = (P_togrid - P_base) else: P_response = 0 ''' P_togrid -= response.Eused P_base -= response.Pbase # self.outputfile.write(str(response.Ttank) +"," + str(self.TsetInitial[number]) + "," + str(response.Eused) + "," + str(response.PusedMax) + "," + str(response.Eloss) + "," + str(response.ElementOn) + "," + str(response.Eservice) + "," + str(response.SOC) + "," + str(response.AvailableCapacityAdd) + "," + str(response.AvailableCapacityShed) + "," + str(response.ServiceCallsAccepted) + "," + str(response.IsAvailableAdd) + "," + str(response.IsAvailableShed) + "," + str(self.draw[number][0]) + "," + str(response.Edel) + ",") # resp.sim_step = response.sim_step number += 1 # go to next device if P_req <= 0: P_service_max += response.AvailableCapacityShed # NOTE THIS ASSUMES THE MAX SERVICE IS LOAD SHED else: P_service_max0 += response.AvailableCapacityAdd P_service_max = -1.0 * P_service_max0 # self.outputfile.write("\n") self.step += 1 # To advance the step by step in the disturbance file # Output Fleet Response resp = FleetResponse() resp.P_service = [] resp.P_service_max = [] resp.P_service_min = [] resp.P_togrid = [] resp.P_togrid_max = [] resp.P_togrid_min = [] resp.P_forecast = [] resp.P_base = [] resp.E = [] resp.C = [] resp.ts = ts resp.sim_step = dt # resp.P_dot_up = resp.P_togrid_max / ServiceRequest.Timestep.seconds resp.P_service_max = P_service_max resp.P_service = P_service resp.P_base = P_base resp.P_togrid = P_togrid # if P_service != 0: # print("resp.P_base = {}".format(resp.P_base)) # print("Pbase = {}".format(resp.P_base)) # print("Ptogrid = {}".format(resp.P_togrid)) # Available Energy stored at the end of the most recent timestep # resp.E += response.Estored resp.E = 0 resp.C += response.SOC / (self.numWH) resp.Q_togrid = 'NA' resp.Q_service = 'NA' resp.Q_service_max = 'NA' resp.Q_service_min = 'NA' resp.Q_togrid_min = 'NA' resp.Q_togrid_max = 'NA' resp.Q_dot_up = 'NA' resp.Q_dot_down = 'NA' resp.P_dot_up = 0 resp.P_dot_down = 0 resp.Eff_charge = 1.0 # TODO: change this if we ever use a HPWH to use the HP efficiency resp.Eff_discharge = 1.0 # always equal to 1 for this device resp.P_dot = resp.P_togrid / dt resp.P_service_min = 0 resp.dT_hold_limit = 'NA' resp.T_restore = 'NA' resp.Strike_price = 'NA' resp.SOC_cost = 'NA' # TotalServiceProvidedPerTimeStep[step] = -1.0*self.P_service # per time step for all hvacs -1.0* """ Modify the power and SOC of the different subfeets according to the frequency droop regulation according to IEEE standard """ if self.FW21_Enabled and self.is_autonomous: power_ac = resp.P_service_max p_prev = resp.P_togrid power_fleet = self.frequency_watt(power_ac, p_prev, self.ts, self.location, self.db_UF_subfleet, self.db_OF_subfleet) power_fleet, SOC_step = self.update_soc_due_to_frequency_droop( initSOC, power_fleet, dt) self.time = t + dt self.ts = self.ts + timedelta(seconds=dt) # Restart time if it surpasses 24 hours if self.time > 24 * 3600: self.time = self.time - 24 * 3600 # Impact Metrics # Update the metrics ''' Ttotal = 0 SOCTotal = 0 self.cycle_basee = np.sum(self.cycle_off_base) self.cycle_basee += np.sum(self.cycle_on_base) self.cycle_grid = np.sum(self.cycle_off_grid) self.cycle_grid += np.sum(self.cycle_on_grid) ''' for number in range(self.numWH): if response.Ttank <= self.TsetInitial[ number] - 10: # assume 10F deadband (consistent with wh.py) self.unmet_hours += 1 * self.sim_step / 3600.0 if resp.P_base == 0 and resp.P_togrid == 0: self.ratio_P_togrid_P_base = 1.0 elif resp.P_base == 0 and resp.P_togrid != 0: self.ratio_P_togrid_P_base = 'NA' else: self.ratio_P_togrid_P_base = resp.P_togrid / (resp.P_base) self.energy_impacts += abs(resp.P_service) * (self.sim_step / 3600) return resp