def process_request(self, fleet_request): """ Request for timestep ts :param fleet_request: an instance of FleetRequest :return fleet_response: an instance of FleetResponse """ fleet_response = FleetResponse() fleet_response.ts = fleet_request.ts_req fleet_response.P_service = fleet_request.P_req return fleet_response
def forecast(self, fleet_requests): """ Request for current time step :param requests: list of FleetRequest instances :return res: list of FleetResponse instances """ fleet_responses = [] for fleet_request in fleet_requests: fleet_response = FleetResponse() fleet_responses.append(fleet_response) return fleet_responses
def process_request(self, fleet_request): """ The expectation that configuration will have at least the following items :param fleet_request: an instance of FleetRequest :return res: an instance of FleetResponse """ res = FleetResponse() # Dummy values for testing res.P_togrid = 100 res.P_service = 100 res.Q_service = 100 res.Q_togrid = 100 return res
def run(self, P_req=[0], Q_req=[0],ts=datetime.utcnow(), del_t=timedelta(hours=1)): ''' This function takes the real and reactive power requested, date, time, and time step and calculates the updated fleet variables as well as a FleetResponse object based on what the simulated fleet is able to provide. It does this by dividing up the requests and sending each device in the fleet its own request. If some of the fleet is unable to supply the requested power, then the remainder is devided umung the remaining devices. :param P_req: requested real power, Q_req: requested reactive power ts:datetime opject, del_t: timedelta object :return fleet_response: an instance of FleetResponse ''' np = numpy.ones(self.num_of_devices,int) nq = numpy.ones(self.num_of_devices,int) p_none = 0 q_none = 0 if P_req==None: P_req = 0 p_req = 0 p_none = 1 else: p_req = P_req/self.num_of_devices if Q_req==None: Q_req = 0 q_req = 0 q_none = 1 else: q_req = Q_req/self.num_of_devices p_tot = 0.0 q_tot = 0.0 dt = del_t.total_seconds() / 3600.0 self.t = self.t + dt p_FW = numpy.zeros(self.num_of_devices,float) q_VV = numpy.zeros(self.num_of_devices,float) response = FleetResponse() response.ts = ts last_P = numpy.zeros(self.num_of_devices,int) last_Q = numpy.zeros(self.num_of_devices,int) soc_update = copy.copy(self.soc) if self.model_type == 'CRM': pdc_update = self.pdc ibat_update = self.ibat v1_update = self.v1 v2_update = self.v2 vbat_update = self.vbat for i in range(self.num_of_devices): last_P[i] = self.P_service[i] last_Q[i] = self.Q_service[i] self.P_service[i] = 0 self.Q_service[i] = 0 TOL = 0.000001 # tolerance one_pass = 1 #guarantees at least one pass through the control loop while ((p_tot < P_req-TOL or p_tot > P_req+TOL) or (q_tot < Q_req-TOL or q_tot > Q_req+TOL)) and sum(np)!=0 and sum(nq)!=0 or one_pass == 1: #continue looping through devices until the power needs are met or all devices are at their limits one_pass = 0 # distribute the requested power equally among the devices that are not at their limits p_req = (P_req - p_tot)/sum(np) q_req = (Q_req - q_tot)/sum(nq) for i in range(self.num_of_devices): if self.model_type == 'ERM': soc_update[i] = self.run_soc_update(p_req,q_req,np,nq,last_P,last_Q,i,dt) if self.model_type == 'CRM': [soc_update[i],pdc_update[i],ibat_update[i],vbat_update[i],v1_update[i],v2_update[i]] = \ self.run_soc_update(p_req,q_req,np,nq,last_P,last_Q,i,dt) # at the end of looping through the devices, add up their power to determine if the request has been met p_tot = sum(self.P_service) q_tot = sum(self.Q_service) # after all the power needs have been placed and met, then make adjustments based on autonomous operation settings if (self.FW21_Enabled == True or self.VV11_Enabled == True) and self.is_autonomous == True: for i in range(self.num_of_devices) : p_req = self.P_service[i] q_req = self.Q_service[i] # determin if VV11 or FW21 change the p_req or q_req if self.FW21_Enabled == True: p_mod = self.frequency_watt(p_req,ts,self.location[i]) if self.VV11_Enabled == True: if q_none == 1: q_mod = self.volt_var(ts,self.location[i]) if self.model_type == 'ERM': soc_update[i] = self.run_soc_update(p_req=(p_mod-p_req),q_req=(q_mod-q_req),np=numpy.ones(self.num_of_devices),nq=numpy.ones(self.num_of_devices),last_P=last_P,last_Q=last_Q,i=i,dt=dt) if self.model_type == 'CRM': [soc_update[i],pdc_update[i],ibat_update[i],vbat_update[i],v1_update[i],v2_update[i]] = \ self.run_soc_update(p_req=(p_mod-p_req),q_req=(q_mod-q_req),np=numpy.ones(self.num_of_devices),nq=numpy.ones(self.num_of_devices),last_P=last_P,last_Q=last_Q,i=i,dt=dt) else : if self.model_type == 'ERM': soc_update[i] = self.run_soc_update(p_req=(p_req),q_req=(q_req),np=numpy.ones(self.num_of_devices),nq=numpy.ones(self.num_of_devices),last_P=last_P,last_Q=last_Q,i=i,dt=dt) if self.model_type == 'CRM': [soc_update[i],pdc_update[i],ibat_update[i],vbat_update[i],v1_update[i],v2_update[i]] = \ self.run_soc_update(p_req=p_req,q_req=q_req,np=numpy.ones(self.num_of_devices),nq=numpy.ones(self.num_of_devices),last_P=last_P,last_Q=last_Q,i=i,dt=dt) p_tot = sum(self.P_service) q_tot = sum(self.Q_service) # update SoC self.soc = soc_update # update SoH if self.model_type == 'ERM': self.soh = self.soh - 100*dt*abs(self.P_service)/((1+1/self.energy_efficiency)*self.cycle_life*self.energy_capacity) if self.model_type == 'CRM': self.v1 = v1_update self.v2 = v2_update self.voc_update() self.ibat = ibat_update self.vbat = (self.v1 + self.v2 + self.voc + self.ibat*self.r0) *self.n_cells self.soh = self.soh - 100*dt*abs(self.ibat)/((1+1/self.coulombic_efficiency)*self.cycle_life*self.charge_capacity) # once the power request has been met, or all devices are at their limits, return the response variables response.P_service = p_tot response.Q_service = q_tot response.soc = numpy.average(self.soc) response.E = numpy.average(self.soc) * self.energy_capacity / 100.0 return response
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
def Run_Fleet(self, ts, sim_step, P_req, Q_req, return_forecast, WP): # this module aggregates the PV information from a large pool of PV/invertre system # Updates prepares report for system operator based on PV fleet # Breaks down the operation command from system operator to subfleet and device level if P_req == None: P_req = [] if Q_req == None: Q_req = [] def Grid_Param(): from grid_info import GridInfo a = GridInfo('Grid_Info_DATA_2.csv') V = a.get_voltage('XX') f = a.get_frequency('XX') return f, V # pull information about the PV subfleet P_rated = self.p_rated P_min = self.p_min S_max = self.s_max Qmax_Plus = self.qmax_plus Qmax_minus = self.qmax_minus P_up = self.p_ramp_down P_down = self.p_ramp_down Q_up = self.q_ramp_down Q_down = self.q_ramp_down # Create Battery equivalent model (BEM) variables for the subfleets and PV fleet # SubFleetA_NP_C='NA' # SubFleetA_NP_P_grid_max=P_rated*self.SubFleet_NumberOfUnits #Minimum real power for services # SubFleetA_NP_Q_grid_max=Qmax_Plus*self.SubFleet_NumberOfUnits#Maximum reactive power for services # SubFleetA_NP_P_grid_min=P_min*self.SubFleet_NumberOfUnits#Minimum reactive power for services # SubFleetA_NP_Q_grid_min=Qmax_minus*self.SubFleet_NumberOfUnits#Ramp rate real power up # SubFleetA_NP_P_service_max=SubFleetA_NP_P_grid_max-SubFleetA_NP_P_grid_min#Ramp rate real power down # SubFleetA_NP_Q_service_max=SubFleetA_NP_Q_grid_max-SubFleetA_NP_Q_grid_min#Ramp rate reactive power up # SubFleetA_NP_P_service_min=0#Ramp rate reactive power down # SubFleetA_NP_Q_service_min=0 SubFleetA_NP_P_up = P_up SubFleetA_NP_Q_up = Q_up SubFleetA_NP_P_down = P_down SubFleetA_NP_Q_down = Q_down # SubFleetA_NP_e_in='NA' SubFleetA_NP_e_out = [] SubFleetA_NP_S_rating = S_max * self.SubFleet_NumberOfUnits Fleet_PV_NP_C = 'NA' # Fleet_PV_NP_P_grid_max=SubFleetA_NP_P_grid_max#Minimum real power for services # Fleet_PV_NP_Q_grid_max=SubFleetA_NP_Q_grid_max#Maximum reactive power for services # Fleet_PV_NP_P_grid_min=SubFleetA_NP_P_grid_min#Minimum reactive power for services # Fleet_PV_NP_Q_grid_min=SubFleetA_NP_Q_grid_min#Ramp rate real power up # Fleet_PV_NP_P_service_max=SubFleetA_NP_P_service_max#Ramp rate real power down # Fleet_PV_NP_Q_service_max=SubFleetA_NP_Q_service_max#Ramp rate reactive power up # Fleet_PV_NP_P_service_min=0#Ramp rate reactive power down # Fleet_PV_NP_Q_service_min=0 Fleet_PV_NP_P_up = SubFleetA_NP_P_up Fleet_PV_NP_Q_up = SubFleetA_NP_Q_up Fleet_PV_NP_P_down = SubFleetA_NP_P_down Fleet_PV_NP_Q_down = SubFleetA_NP_Q_down Fleet_PV_NP_e_in = 'NA' # Fleet_PV_NP_e_out=[] FleetA_NP_S_rating = SubFleetA_NP_S_rating # break down the command for fleet to command for device Command_to_Device = self.Aggregator_Command( P_req, Q_req, self.SubFleet_NumberOfUnits) # Grid parameters required for autonomous mode of operation #[Time_,P_rec,Q_rec,P_req_,Q_req_,P_MPP,G_rec,T_rec]\ [P_grid,Q_grid,P_service,Q_service,E_t0,c,P_output,Q_output,P_grid_max,Q_grid_max,\ P_grid_min,Q_grid_min,P_service_max,Q_service_max,P_service_min,Q_service_min,del_t_hold,\ t_restore,SP,N_req,Effeciency,sim_step]=self.Device_PV(ts,sim_step,Command_to_Device,return_forecast,WP) #print(P_Output) SubFleetA_P_grid = P_grid * self.SubFleet_NumberOfUnits / 1000 SubFleetA_Q_grid = Q_grid * self.SubFleet_NumberOfUnits / 1000 SubFleetA_P_service = P_service * self.SubFleet_NumberOfUnits / 1000 SubFleetA_Q_service = Q_service * self.SubFleet_NumberOfUnits / 1000 SubFleetA_E_t0 = E_t0 SubFleetA_c = c SubFleetA_P_output = [ x * self.SubFleet_NumberOfUnits / 1000 for x in P_output ] SubFleetA_Q_output = [ x * self.SubFleet_NumberOfUnits / 1000 for x in Q_output ] SubFleetA_P_grid_max = [ x * self.SubFleet_NumberOfUnits / 1000 for x in P_grid_max ] SubFleetA_Q_grid_max = [ x * self.SubFleet_NumberOfUnits / 1000 for x in Q_grid_max ] SubFleetA_P_grid_min = [ x * self.SubFleet_NumberOfUnits / 1000 for x in P_grid_min ] SubFleetA_Q_grid_min = [ x * self.SubFleet_NumberOfUnits / 1000 for x in Q_grid_min ] SubFleetA_P_service_max = [ x * self.SubFleet_NumberOfUnits / 1000 for x in P_service_max ] SubFleetA_Q_service_max = [ x * self.SubFleet_NumberOfUnits / 1000 for x in Q_service_max ] SubFleetA_P_service_min = [ x * self.SubFleet_NumberOfUnits / 1000 for x in P_service_min ] SubFleetA_Q_service_min = [ x * self.SubFleet_NumberOfUnits / 1000 for x in Q_service_min ] SubFleetA_del_t_hold = del_t_hold SubFleetA_t_restore = t_restore SubFleetA_SP = SP SubFleetA_N_req = N_req SubFleetA_NP_e_out = Effeciency # Calculate information about Subfleets # Calculate information about Device Fleet: PV # Calculate information about Subfleets Fleet_PV_P_grid = SubFleetA_P_grid Fleet_PV_Q_grid = SubFleetA_Q_grid Fleet_PV_P_service = SubFleetA_P_service Fleet_PV_Q_service = SubFleetA_Q_service Fleet_PV_E_t0 = SubFleetA_E_t0 # Fleet_PV_c=SubFleetA_c # # Fleet_PV_P_output=SubFleetA_P_output # Fleet_PV_Q_output=SubFleetA_Q_output Fleet_PV_P_grid_max = SubFleetA_P_grid_max Fleet_PV_Q_grid_max = SubFleetA_Q_grid_max Fleet_PV_P_grid_min = SubFleetA_P_grid_min Fleet_PV_Q_grid_min = SubFleetA_Q_grid_min Fleet_PV_P_service_max = SubFleetA_P_service_max Fleet_PV_Q_service_max = SubFleetA_Q_service_max Fleet_PV_P_service_min = SubFleetA_P_service_min Fleet_PV_Q_service_min = SubFleetA_Q_service_min Fleet_PV_del_t_hold = SubFleetA_del_t_hold Fleet_PV_t_restore = SubFleetA_t_restore # Fleet_PV_SP=SubFleetA_SP # Fleet_PV_N_req=SubFleetA_N_req response = FleetResponse() response.ts = ts response.C = Fleet_PV_NP_C response.dT_hold_limit = Fleet_PV_del_t_hold response.E = Fleet_PV_E_t0 response.Eff_charge = Fleet_PV_NP_e_in response.Eff_discharge = SubFleetA_NP_e_out response.P_dot_down = Fleet_PV_NP_P_down response.P_dot_up = Fleet_PV_NP_P_up response.P_service = Fleet_PV_P_service response.P_service_max = Fleet_PV_P_service_max response.P_service_min = Fleet_PV_P_service_min response.P_togrid = Fleet_PV_P_grid response.P_togrid_max = Fleet_PV_P_grid_max[0] response.P_togrid_min = Fleet_PV_P_grid_min[0] response.Q_dot_down = Fleet_PV_NP_Q_down response.Q_dot_up = Fleet_PV_NP_Q_up response.Q_service = Fleet_PV_Q_service response.Q_service_max = Fleet_PV_Q_service_max response.Q_service_min = Fleet_PV_Q_service_min response.Q_togrid = Fleet_PV_Q_grid response.Q_togrid_max = Fleet_PV_Q_grid_max[0] response.Q_togrid_min = Fleet_PV_Q_grid_min[0] response.S_Rating = FleetA_NP_S_rating response.T_restore = Fleet_PV_t_restore response.P_base = Fleet_PV_P_grid_max[0] response.sim_step = sim_step response.Strike_price = 0 response.SOC_cost = 'na' self.energy_impacts += abs( response.P_service) * (int(sim_step.total_seconds()) / 3600) return response
def fc_model(self, ts, sim_step, Preq, forecast=False, start_time=None): """ :param ts: Request created for current time-step: datetime :param sim_step: Request for simulation time-step: timedelta object :param Preq: Request for current real power request :param forecast: Returns fleet response forecast :param start_time: Request for current real power request :return resp: Fleet response object """ if self.P_tank_ideal <= self.min_charge: pass else: # Create base power profile if isinstance(self.p, ndarray): self.__p_base = sum([ self.p[j] * (self.__inc + 1)**(21 - j) for j in range(22) ]) else: self.__p_base = self.p # Ptogrid: Pbase for None or zero requests since FC is a source if Preq is None or float(Preq) == 0.0: Preq = self.__p_base # Ptogrid: Preq+Pbase for +ve or -ve requests else: Preq += self.__p_base # Compute the power generated by the fleet self.__fc_p_calc(Preq) if self.FW21_Enabled and self.is_autonomous: # all in kW Preq, self.f = self.frequency_watt(p_pre=self.__p_tot_ideal, p_avl=self.fc_Pmax_fleet, p_min=self.fc_Pmin_fleet, ts=ts, start_time=start_time) self.__p_tot_ideal = Preq # Update SOC self.__soc_calc() self.__inc += 1 # Response # Power injected is positive resp = FleetResponse() resp.ts = ts resp.sim_step = sim_step resp.C = 0 resp.dT_hold_limit = 0 resp.E = self.soc_ideal resp.Eff_charge = 1.0 resp.Eff_discharge = self.__eta_ds resp.P_dot_down = 0 resp.P_dot_up = 0 resp.P_togrid = self.__p_tot_ideal resp.P_togrid_max = self.fc_Pmax_fleet resp.P_togrid_min = self.fc_Pmin_fleet resp.P_service = resp.P_togrid - self.__p_base resp.P_service_max = self.fc_Pmax_fleet - self.__p_base resp.P_service_min = self.__p_base - self.fc_Pmin_fleet resp.Q_dot_down = 0 resp.Q_dot_up = 0 resp.Q_service = 0 resp.Q_service_max = 0 resp.Q_service_min = 0 resp.Q_togrid = 0 resp.Q_togrid_max = 0 resp.Q_togrid_min = 0 resp.T_restore = 0 resp.P_base = self.__p_base resp.Q_base = 0 resp.Strike_price = 0 resp.SOC_cost = 0 # Impact metrics if not forecast: self.metrics.append([ str(ts), str(self.__Vr), str(self.__Vr_age), str(self.__ne), str(self.__ne_age), str(self.soc_ideal * 1e2), str(self.soc_age * 1e2), str(self.lka_h2), str(self.__eta_ds * 1e2) ]) # Print Soc every 5 secs. if self.__inc % 5000 == 0: print("Soc:%4.2f%%" % (resp.E * 1e2)) return resp
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 # run through fleet once as a forecast just to get initial conditions number = 0 # run through all the devices servsum = 0 servmax = 0 servmax2 = 0 NumDevicesToCall = 0 p_togrid = 0 p_service = 0 p_base = 0 service_max = 0 service_min = 0 # laststep = step - 1 # decision making about which RF to call on for service, check if available at last step, if so then # check for SoC > self.minSOC and Soc < self.maxSOC P_request_perRF = P_req/max(NumDevicesToCall,1) #divide the fleet request by the number of devices that can be called upon for n in range(self.numRF): if P_request_perRF < 0 and self.IsAvailableAdd[n] > 0: NumDevicesToCall += 1 elif P_request_perRF > 0 and self.IsAvailableShed[n] > 0: NumDevicesToCall += 1 P_request_perRF = P_req/max(NumDevicesToCall,1) #divide the fleet request by the number of devices that can be called upon ################################# for rfdg in self.RFs: #loop through all RFs TsetLast = self.TsetInitial[number] TairLastB = self.TairInitialB[number] TfoodLastB = self.TfoodInitialB[number] TcaseLastB = self.TcaseInitialB[number] TairLast = self.TairInitial[number] TfoodLast = self.TfoodInitial[number] TcaseLast = self.TcaseInitial[number] if P_req<0 and self.IsAvailableAdd[number] > 0 : # For step >=1, can use last step temperature... response = rfdg.FRIDGE(TairLastB, TfoodLastB, TcaseLastB, TairLast, TfoodLast, TcaseLast, TsetLast, self.Tamb[number][self.step], self.R_case[number], self.R_food[number], self.R_infil[number], self.C_case[number], self.C_food[number], self.C_air[number], P_request_perRF, dt, self.elementOnB[number], self.elementOn[number], self.lockonB[number], self.lockoffB[number], self.lockon[number], self.lockoff[number], self.cycle_off_base[number], self.cycle_on_base[number], self.cycle_off_grid[number], self.cycle_on_grid[number]) P_req = P_req - response.Eservice if P_req >= 0: # all the service request has been met P_req = 0 elif P_req>0 and self.IsAvailableShed[number] > 0 : # For step >=1, can use last step temperature... response = rfdg.FRIDGE(TairLastB, TfoodLastB, TcaseLastB, TairLast, TfoodLast, TcaseLast, TsetLast, self.Tamb[number][self.step], self.R_case[number], self.R_food[number], self.R_infil[number], self.C_case[number], self.C_food[number], self.C_air[number], P_request_perRF, dt, self.elementOnB[number], self.elementOn[number], self.lockonB[number], self.lockoffB[number], self.lockon[number], self.lockoff[number], self.cycle_off_base[number], self.cycle_on_base[number], self.cycle_off_grid[number], self.cycle_on_grid[number]) P_req = P_req - response.Eservice if P_req <= 0: # all the service request has been met P_req = 0 # break else: response = rfdg.FRIDGE(TairLastB, TfoodLastB, TcaseLastB, TairLast, TfoodLast, TcaseLast, TsetLast, self.Tamb[number][self.step], self.R_case[number], self.R_food[number], self.R_infil[number], self.C_case[number], self.C_food[number], self.C_air[number], 0, dt, self.elementOnB[number], self.elementOn[number], self.lockonB[number], self.lockoffB[number], self.lockon[number], self.lockoff[number], self.cycle_off_base[number], self.cycle_on_base[number], self.cycle_off_grid[number], self.cycle_on_grid[number]) # assign returned parameters to associated lists to be recorded self.TsetInitial[number] = response.Tset self.TairInitialB[number] = response.TairB self.TfoodInitialB[number] = response.TfoodB self.TcaseInitialB[number] = response.TcaseB self.elementOnB[number] = response.ElementOnB self.TairInitial[number] = response.Tair self.TfoodInitial[number] = response.Tfood self.TcaseInitial[number] = response.Tcase self.elementOn[number] = response.ElementOn self.lockonB[number] = response.lockonB self.lockoffB[number] = response.lockoffB self.lockon[number] = response.lockon self.lockoff[number] = response.lockoff self.cycle_off_base[number] = response.cycle_off_base self.cycle_on_base[number] = response.cycle_on_base self.cycle_off_grid[number] = response.cycle_off_grid self.cycle_on_grid[number] = response.cycle_on_grid 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 # resp.P_togrid_max += response.PusedMax # resp.P_togrid_min += response.PusedMin p_service += response.Eservice #Eused/10 #Eservice p_base += response.Pbase # FleetResponse.sim_step = response.sim_step number += 1 # go to next device if P_req>=0: service_max += response.AvailableCapacityShed # NOTE THIS ASSUMES THE MAX SERVICE IS LOAD SHED else: service_min += response.AvailableCapacityAdd # NOTE THIS ASSUMES THE MIN SERVICE IS LOAD ADD self.step += 1 # Output Fleet Response resp = FleetResponse() # Positive Prequest means reducing power consumption resp.ts = ts resp.sim_step = timedelta(seconds=dt) resp.P_service = [] resp.P_service_max = [] # positive only? 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.P_dot_up = resp.P_togrid_max / ServiceRequest.Timestep.seconds resp.P_service = p_service resp.P_service_max = service_max # positive decrease loads resp.P_service_min = service_min # negative increase loasd resp.P_base = -p_base resp.P_togrid = -p_togrid resp.P_togrid_max = -(p_base-service_max) resp.P_togrid_min = -(p_base-service_min) resp.E = 0 resp.C = 0 # response.SOC/(self.numRF) 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.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_step, 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 numberr in range(self.numRF-1): if self.TairInitial[numberr]>=self.TsetInitial[numberr] + 3: #assume 2F, self.deadband self.unmet_hours += 1*self.sim_step/3600.0 self.ave_TairB = np.average(self.TairInitialB) self.ave_Tair = np.average(self.TairInitial) self.SOCb_metric = np.average(self.SOCb) self.SOC_metric = np.average(self.SOC) self.unmet_hours = self.unmet_hours/self.numRF 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