def update_plots(self, i): """This function is called by Matplotlib for each animation frame Each time called, collect FNCS messages until the next time to plot has been reached. Then update the plot quantities and return the Line2D objects that have been updated for plotting. Check for new data outside the plotted vertical range, which triggers a full re-draw of the axes. On the last frame, finalize FNCS. Args: i (int): the animation frame number """ # print ('.', end='') # print ('frame', i, 'of', self.nsteps) bRedraw = False while self.time_granted < self.time_stop: # time in seconds # find the time value and index into the time (X) array self.time_granted = fncs.time_request(self.time_stop) events = fncs.get_events() self.root.update() idx = int(self.time_granted / self.yaml_delta) if idx <= self.idxlast: continue self.idxlast = idx h = float(self.time_granted / 3600.0) self.hrs.append(h) # find the newest Y values v0 = 0.0 v1 = 0.0 v2auc = 0.0 v2lmp = 0.0 v3 = 0.0 for topic in events: value = fncs.get_value(topic) if topic == 'power_A': v1 = 3.0 * float(value.strip('+ degFkW')) / 1000.0 elif topic == 'distribution_load': v3 = helpers.parse_kw(value) self.gld_load = v3 elif topic == 'vpos7': v0 = float(value.strip('+ degFkW')) / 133000.0 elif topic == 'clear_price': v2auc = float(value.strip('+ degFkW')) elif topic == 'LMP7': v2lmp = float(value.strip('+ degFkW')) elif topic == 'SUBSTATION7': v1 = float(value.strip('+ degFkW')) # already in kW # expand the Y axis limits if necessary, keeping a 10% padding around the range if v0 < self.y0min or v0 > self.y0max: self.y0min, self.y0max = self.expand_limits( v0, self.y0min, self.y0max) self.ax[0].set_ylim(self.y0min, self.y0max) bRedraw = True if v1 < self.y1min or v1 > self.y1max: self.y1min, self.y1max = self.expand_limits( v1, self.y1min, self.y1max) self.ax[1].set_ylim(self.y1min, self.y1max) bRedraw = True if v2auc > v2lmp: v2max = v2auc v2min = v2lmp else: v2max = v2lmp v2min = v2auc if v2min < self.y2min or v2max > self.y2max: self.y2min, self.y2max = self.expand_limits( v2min, self.y2min, self.y2max) self.y2min, self.y2max = self.expand_limits( v2max, self.y2min, self.y2max) self.ax[2].set_ylim(self.y2min, self.y2max) bRedraw = True if v3 < self.y3min or v3 > self.y3max: self.y3min, self.y3max = self.expand_limits( v3, self.y3min, self.y3max) self.ax[3].set_ylim(self.y3min, self.y3max) bRedraw = True # update the Y axis data to draw self.y0.append(v0) # Vpu self.y1.append(v1) # school kW self.y2auc.append(v2auc) # price self.y2lmp.append(v2lmp) # LMP self.y3fncs.append( v3) # this feeder load from FNCS (could be zero if no update) self.y3gld.append( self.gld_load) # most recent feeder load from FNCS self.ln0.set_data(self.hrs, self.y0) self.ln1.set_data(self.hrs, self.y1) self.ln2auc.set_data(self.hrs, self.y2auc) self.ln2lmp.set_data(self.hrs, self.y2lmp) self.ln3fncs.set_data(self.hrs, self.y3fncs) self.ln3gld.set_data(self.hrs, self.y3gld) if bRedraw: # print ('redrawing axes') self.fig.canvas.draw() if i >= (self.nsteps - 1): print('finalizing FNCS') fncs.finalize() print('FNCS active to False') self.bFNCSactive = False return self.ln0, self.ln1, self.ln2auc, self.ln2lmp, self.ln3fncs, self.ln3gld, print('not finalizing FNCS') return self.ln0, self.ln1, self.ln2auc, self.ln2lmp, self.ln3fncs, self.ln3gld, # in case we miss the last point
def pypower_loop (casefile, rootname): """ Public function to start PYPOWER solutions under control of FNCS The time step, maximum time, and other data must be set up in a JSON file. This function will run the case under FNCS, manage the FNCS message traffic, and shutdown FNCS upon completion. Five files are written: - *rootname.csv*; intermediate solution results during simulation - *rootname_m_dict.json*; metadata for post-processing - *bus_rootname_metrics.json*; bus metrics for GridLAB-D connections, upon completion - *gen_rootname_metrics.json*; bulk system generator metrics, upon completion - *sys_rootname_metrics.json*; bulk system-level metrics, upon completion Args: casefile (str): the configuring JSON file name, without extension rootname (str): the root filename for metrics output, without extension """ # if len(sys.argv) == 3: # rootname = sys.argv[1] # casefile = sys.argv[2] # else: # print ('usage: python fncsPYPOWER.py metrics_rootname casedata.json') # sys.exit() ppc = load_json_case (casefile) StartTime = ppc['StartTime'] tmax = int(ppc['Tmax']) period = int(ppc['Period']) dt = int(ppc['dt']) make_dictionary (ppc, rootname) bus_mp = open ("bus_" + rootname + "_metrics.json", "w") gen_mp = open ("gen_" + rootname + "_metrics.json", "w") sys_mp = open ("sys_" + rootname + "_metrics.json", "w") bus_meta = {'LMP_P':{'units':'USD/kwh','index':0},'LMP_Q':{'units':'USD/kvarh','index':1}, 'PD':{'units':'MW','index':2},'QD':{'units':'MVAR','index':3},'Vang':{'units':'deg','index':4}, 'Vmag':{'units':'pu','index':5},'Vmax':{'units':'pu','index':6},'Vmin':{'units':'pu','index':7}} gen_meta = {'Pgen':{'units':'MW','index':0},'Qgen':{'units':'MVAR','index':1},'LMP_P':{'units':'USD/kwh','index':2}} sys_meta = {'Ploss':{'units':'MW','index':0},'Converged':{'units':'true/false','index':1}} bus_metrics = {'Metadata':bus_meta,'StartTime':StartTime} gen_metrics = {'Metadata':gen_meta,'StartTime':StartTime} sys_metrics = {'Metadata':sys_meta,'StartTime':StartTime} gencost = ppc['gencost'] fncsBus = ppc['FNCS'] gen = ppc['gen'] ppopt_market = pp.ppoption(VERBOSE=0, OUT_ALL=0, PF_DC=ppc['opf_dc']) ppopt_regular = pp.ppoption(VERBOSE=0, OUT_ALL=0, PF_DC=ppc['pf_dc']) loads = np.loadtxt(ppc['CSVFile'], delimiter=',') for row in ppc['UnitsOut']: print ('unit ', row[0], 'off from', row[1], 'to', row[2], flush=True) for row in ppc['BranchesOut']: print ('branch', row[0], 'out from', row[1], 'to', row[2], flush=True) nloads = loads.shape[0] ts = 0 tnext_opf = -dt # initializing for metrics collection tnext_metrics = 0 loss_accum = 0 conv_accum = True n_accum = 0 bus_accum = {} gen_accum = {} for i in range (fncsBus.shape[0]): busnum = int(fncsBus[i,0]) bus_accum[str(busnum)] = [0,0,0,0,0,0,0,99999.0] for i in range (gen.shape[0]): gen_accum[str(i+1)] = [0,0,0] op = open (rootname + '.csv', 'w') print ('t[s],Converged,Pload,P7 (csv),Unresp (opf),P7 (rpf),Resp (opf),GLD Pub,BID?,P7 Min,V7,LMP_P7,LMP_Q7,Pgen1,Pgen2,Pgen3,Pgen4,Pdisp,Deg,c2,c1', file=op, flush=True) fncs.initialize() # transactive load components csv_load = 0 # from the file unresp = 0 # unresponsive load estimate from the auction agent resp = 0 # will be the responsive load as dispatched by OPF resp_deg = 0 # RESPONSIVE_DEG from FNCS resp_c1 = 0 # RESPONSIVE_C1 from FNCS resp_c2 = 0 # RESPONSIVE_C2 from FNCS resp_max = 0 # RESPONSIVE_MAX_MW from FNCS feeder_load = 0 # amplified feeder MW while ts <= tmax: # start by getting the latest inputs from GridLAB-D and the auction events = fncs.get_events() new_bid = False load_scale = float (fncsBus[0][2]) for topic in events: value = fncs.get_value(topic) if topic == 'UNRESPONSIVE_MW': unresp = load_scale * float(value) fncsBus[0][3] = unresp # to poke unresponsive estimate into the bus load slot new_bid = True elif topic == 'RESPONSIVE_MAX_MW': resp_max = load_scale * float(value) new_bid = True elif topic == 'RESPONSIVE_C2': resp_c2 = float(value) / load_scale new_bid = True elif topic == 'RESPONSIVE_C1': resp_c1 = float(value) new_bid = True elif topic == 'RESPONSIVE_DEG': resp_deg = int(value) new_bid = True else: gld_load = parse_mva (value) # actual value, may not match unresp + resp load feeder_load = float(gld_load[0]) * load_scale if new_bid == True: dummy = 2 # print('**Bid', ts, unresp, resp_max, resp_deg, resp_c2, resp_c1) # update the case for bids, outages and CSV loads idx = int ((ts + dt) / period) % nloads bus = ppc['bus'] gen = ppc['gen'] branch = ppc['branch'] gencost = ppc['gencost'] csv_load = loads[idx,0] bus[4,2] = loads[idx,1] bus[8,2] = loads[idx,2] # process the generator and branch outages for row in ppc['UnitsOut']: if ts >= row[1] and ts <= row[2]: gen[row[0],7] = 0 else: gen[row[0],7] = 1 for row in ppc['BranchesOut']: if ts >= row[1] and ts <= row[2]: branch[row[0],10] = 0 else: branch[row[0],10] = 1 if resp_deg == 2: gencost[4][3] = 3 gencost[4][4] = -resp_c2 gencost[4][5] = resp_c1 elif resp_deg == 1: gencost[4][3] = 2 gencost[4][4] = resp_c1 gencost[4][5] = 0.0 else: gencost[4][3] = 1 gencost[4][4] = 999.0 gencost[4][5] = 0.0 gencost[4][6] = 0.0 if ts >= tnext_opf: # expecting to solve opf one dt before the market clearing period ends, so GridLAB-D has time to use it # for OPF, the FNCS bus load is CSV + Unresponsive estimate, with Responsive separately dispatchable bus = ppc['bus'] gen = ppc['gen'] bus[6,2] = csv_load for row in ppc['FNCS']: unresp = float(row[3]) newidx = int(row[0]) - 1 if unresp >= feeder_load: bus[newidx,2] += unresp else: bus[newidx,2] += feeder_load gen[4][9] = -resp_max res = pp.runopf(ppc, ppopt_market) if res['success'] == False: conv_accum = False opf_bus = deepcopy (res['bus']) opf_gen = deepcopy (res['gen']) lmp = opf_bus[6,13] resp = -1.0 * opf_gen[4,1] fncs.publish('LMP_B7', 0.001 * lmp) # publishing $/kwh # print (' OPF', ts, csv_load, '{:.3f}'.format(unresp), '{:.3f}'.format(resp), # '{:.3f}'.format(feeder_load), '{:.3f}'.format(opf_bus[6,2]), # '{:.3f}'.format(opf_gen[0,1]), '{:.3f}'.format(opf_gen[1,1]), '{:.3f}'.format(opf_gen[2,1]), # '{:.3f}'.format(opf_gen[3,1]), '{:.3f}'.format(opf_gen[4,1]), '{:.3f}'.format(lmp)) # if unit 2 (the normal swing bus) is dispatched at max, change the swing bus to 9 if opf_gen[1,1] >= 191.0: ppc['bus'][1,1] = 2 ppc['bus'][8,1] = 3 print (' SWING Bus 9') else: ppc['bus'][1,1] = 3 ppc['bus'][8,1] = 1 print (' SWING Bus 2') tnext_opf += period # always update the electrical quantities with a regular power flow bus = ppc['bus'] gen = ppc['gen'] bus[6,13] = lmp gen[0,1] = opf_gen[0, 1] gen[1,1] = opf_gen[1, 1] gen[2,1] = opf_gen[2, 1] gen[3,1] = opf_gen[3, 1] # during regular power flow, we use the actual CSV + feeder load, ignore dispatchable load and use actual bus[6,2] = csv_load + feeder_load gen[4,1] = 0 # opf_gen[4, 1] gen[4,9] = 0 rpf = pp.runpf(ppc, ppopt_regular) if rpf[0]['success'] == False: conv_accum = False bus = rpf[0]['bus'] gen = rpf[0]['gen'] Pload = bus[:,2].sum() Pgen = gen[:,1].sum() Ploss = Pgen - Pload # update the metrics n_accum += 1 loss_accum += Ploss for i in range (fncsBus.shape[0]): busnum = int(fncsBus[i,0]) busidx = busnum - 1 row = bus[busidx].tolist() # LMP_P, LMP_Q, PD, QD, Vang, Vmag, Vmax, Vmin: row[11] and row[12] are Vmax and Vmin constraints PD = row[2] + resp # the ERCOT version shows how to track scaled_resp separately for each FNCS bus Vpu = row[7] bus_accum[str(busnum)][0] += row[13]*0.001 bus_accum[str(busnum)][1] += row[14]*0.001 bus_accum[str(busnum)][2] += PD bus_accum[str(busnum)][3] += row[3] bus_accum[str(busnum)][4] += row[8] bus_accum[str(busnum)][5] += Vpu if Vpu > bus_accum[str(busnum)][6]: bus_accum[str(busnum)][6] = Vpu if Vpu < bus_accum[str(busnum)][7]: bus_accum[str(busnum)][7] = Vpu for i in range (gen.shape[0]): row = gen[i].tolist() busidx = int(row[0] - 1) # Pgen, Qgen, LMP_P (includes the responsive load as dispatched by OPF) gen_accum[str(i+1)][0] += row[1] gen_accum[str(i+1)][1] += row[2] gen_accum[str(i+1)][2] += float(opf_bus[busidx,13])*0.001 # write the metrics if ts >= tnext_metrics: sys_metrics[str(ts)] = {rootname:[loss_accum / n_accum,conv_accum]} bus_metrics[str(ts)] = {} for i in range (fncsBus.shape[0]): busnum = int(fncsBus[i,0]) busidx = busnum - 1 row = bus[busidx].tolist() met = bus_accum[str(busnum)] bus_metrics[str(ts)][str(busnum)] = [met[0]/n_accum, met[1]/n_accum, met[2]/n_accum, met[3]/n_accum, met[4]/n_accum, met[5]/n_accum, met[6], met[7]] bus_accum[str(busnum)] = [0,0,0,0,0,0,0,99999.0] gen_metrics[str(ts)] = {} for i in range (gen.shape[0]): met = gen_accum[str(i+1)] gen_metrics[str(ts)][str(i+1)] = [met[0]/n_accum, met[1]/n_accum, met[2]/n_accum] gen_accum[str(i+1)] = [0,0,0] tnext_metrics += period n_accum = 0 loss_accum = 0 conv_accum = True volts = 1000.0 * bus[6,7] * bus[6,9] / sqrt(3.0) # VLN for GridLAB-D fncs.publish('three_phase_voltage_B7', volts) # CSV file output print (ts, res['success'], '{:.3f}'.format(Pload), # Pload '{:.3f}'.format(csv_load), # P7 (csv) '{:.3f}'.format(unresp), # GLD Unresp '{:.3f}'.format(bus[6,2]), # P7 (rpf) '{:.3f}'.format(resp), # Resp (opf) '{:.3f}'.format(feeder_load), # GLD Pub new_bid, '{:.3f}'.format(gen[4,9]), # P7 Min '{:.3f}'.format(bus[6,7]), # V7 '{:.3f}'.format(bus[6,13]), # LMP_P7 '{:.3f}'.format(bus[6,14]), # LMP_Q7 '{:.2f}'.format(gen[0,1]), # Pgen1 '{:.2f}'.format(gen[1,1]), # Pgen2 '{:.2f}'.format(gen[2,1]), # Pgen3 '{:.2f}'.format(gen[3,1]), # Pgen4 '{:.2f}'.format(res['gen'][4, 1]), # Pdisp '{:.4f}'.format(resp_deg), # degree '{:.8f}'.format(ppc['gencost'][4, 4]), # c2 '{:.8f}'.format(ppc['gencost'][4, 5]), # c1 sep=',', file=op, flush=True) # request the next time step, if necessary if ts >= tmax: print ('breaking out at',ts,flush=True) break ts = fncs.time_request(min(ts + dt, tmax)) # =================================== print ('writing metrics', flush=True) print (json.dumps(sys_metrics), file=sys_mp, flush=True) print (json.dumps(bus_metrics), file=bus_mp, flush=True) print (json.dumps(gen_metrics), file=gen_mp, flush=True) print ('closing files', flush=True) bus_mp.close() gen_mp.close() sys_mp.close() op.close() print ('finalizing FNCS', flush=True) fncs.finalize() if sys.platform != 'win32': usage = resource.getrusage(resource.RUSAGE_SELF) RESOURCES = [ ('ru_utime', 'User time'), ('ru_stime', 'System time'), ('ru_maxrss', 'Max. Resident Set Size'), ('ru_ixrss', 'Shared Memory Size'), ('ru_idrss', 'Unshared Memory Size'), ('ru_isrss', 'Stack Size'), ('ru_inblock', 'Block inputs'), ('ru_oublock', 'Block outputs')] print('Resource usage:') for name, desc in RESOURCES: print(' {:<25} ({:<10}) = {}'.format(desc, name, getattr(usage, name)))
def update_plots(self, i): print ('.', end='') # print ('frame', i, 'of', self.nsteps) bRedraw = False while self.time_granted < self.time_stop: # time in minutes self.time_granted = fncs.time_request(self.time_stop) events = fncs.get_events() self.root.update() idx = int (self.time_granted / self.yaml_delta) if idx <= self.idxlast: continue self.idxlast = idx v0 = 0.0 v1 = 0.0 v2 = 0.0 v3 = 0.0 for topic in events: value = fncs.get_value(topic) if topic == 'power_A': v1 = 3.0 * float (value.strip('+ degFkW')) / 1000.0 elif topic == 'distribution_load': v3 = simple_auction.parse_kw (value) elif topic == 'vpos7': v0 = float (value.strip('+ degFkW')) / 133000.0 elif topic == 'clear_price': v2 = float (value.strip('+ degFkW')) elif topic == 'LMP7': v2 = float (value.strip('+ degFkW')) elif topic == 'SUBSTATION7': v1 = float (value.strip('+ degFkW')) # already in kW retval = [self.ln0, self.ln1, self.ln2, self.ln3] h = float (self.time_granted / 60.0) self.hrs.append (h) if v0 < self.y0min or v0 > self.y0max: if v0 < self.y0min: self.y0min = v0 if v0 > self.y0max: self.y0max = v0 self.ax[0].set_ylim (self.y0min, self.y0max) bRedraw = True if v1 < self.y1min or v1 > self.y1max: if v1 < self.y1min: self.y1min = v1 if v1 > self.y1max: self.y1max = v1 self.ax[1].set_ylim (self.y1min, self.y1max) bRedraw = True if v2 < self.y2min or v2 > self.y2max: if v2 < self.y2min: self.y2min = v2 if v2 > self.y2max: self.y2max = v2 self.ax[2].set_ylim (self.y2min, self.y2max) bRedraw = True if v3 < self.y3min or v3 > self.y3max: if v3 < self.y3min: self.y3min = v3 if v3 > self.y3max: self.y3max = v3 self.ax[3].set_ylim (self.y3min, self.y3max) bRedraw = True self.y0.append (v0) # Vpu self.y1.append (v1) # school kW self.y2.append (v2) # price self.y3.append (v3) # feeder load self.ln0.set_data (self.hrs, self.y0) self.ln1.set_data (self.hrs, self.y1) self.ln2.set_data (self.hrs, self.y2) self.ln3.set_data (self.hrs, self.y3) if bRedraw: self.fig.canvas.draw() if i >= (self.nsteps - 1): fncs.finalize() self.bFNCSactive = False return retval
'seconds,OPFconverged,TotalLoad,TotalGen,SwingGen,LMP1,LMP8,gas1,coal1,nuc1,gas2,coal2,nuc2,gas3,coal3,gas4,gas5,coal5,gas7,coal7,wind1,wind3,wind4,wind6,wind7', sep=',', file=op, flush=True) print( 'seconds,PFConverged,TotalLoad,TotalGen,TotalLoss,SwingGen,v1,v2,v3,v4,v5,v6,v7,v8', sep=',', file=vp, flush=True) # MAIN LOOP starts here while ts <= tmax: # start by getting the latest inputs from GridLAB-D and the auction events = fncs.get_events() for topic in events: val = fncs.get_value(topic) if 'UNRESPONSIVE_MW_' in topic: busnum = int(topic[16:]) gld_load[busnum]['unresp'] = float(val) elif 'RESPONSIVE_MAX_MW_' in topic: busnum = int(topic[18:]) gld_load[busnum]['resp_max'] = float(val) elif 'RESPONSIVE_C2_' in topic: busnum = int(topic[14:]) gld_load[busnum]['c2'] = float(val) elif 'RESPONSIVE_C1_' in topic: busnum = int(topic[14:]) gld_load[busnum]['c1'] = float(val) elif 'RESPONSIVE_DEG_' in topic: busnum = int(topic[15:]) gld_load[busnum]['deg'] = int(val)
def precool_loop(nhours, metrics_root): time_stop = int(3600 * nhours) lp = open(metrics_root + "_agent_dict.json").read() dict = json.loads(lp) precool_meta = { 'temperature_deviation_min': { 'units': 'degF', 'index': 0 }, 'temperature_deviation_max': { 'units': 'degF', 'index': 1 }, 'temperature_deviation_avg': { 'units': 'degF', 'index': 2 } } StartTime = "2013-07-01 00:00:00 PST" precool_metrics = {'Metadata': precool_meta, 'StartTime': StartTime} dt = dict['dt'] mean = dict['mean'] stddev = dict['stddev'] period = dict['period'] k = dict['k_slope'] print('run till', time_stop, 'period', period, 'step', dt, 'mean', mean, 'stddev', stddev, 'k_slope', k) fncs.initialize() time_granted = 0 price = 0.11 # mean # time_next = dt voltages = {} temperatures = {} setpoints = {} # publish a new one only if changed lastchange = {} precooling_quiet = 4 * 3600 precooling_off = 25 * 3600 # never turns off precooling_status = {} lockout_period = 360 bSetDeadbands = True nPrecoolers = 0 while time_granted < time_stop: time_granted = fncs.time_request(time_stop) # time_next hour_of_day = 24.0 * ((float(time_granted) / 86400.0) % 1.0) events = fncs.get_events() for topic in events: value = fncs.get_value(topic) if topic == 'price': price = float(value) else: pair = topic.split('#') houseName = pair[0] if pair[1] == 'V1': voltages[houseName] = parse_fncs_magnitude(value) elif pair[1] == 'Tair': temperatures[houseName] = parse_fncs_magnitude(value) if bSetDeadbands: bSetDeadbands = False print('setting thermostat deadbands and heating setpoints at', time_granted) # set all of the house deadbands and initial setpoints for house, row in dict['houses'].items(): topic = house + '_thermostat_deadband' value = row['deadband'] fncs.publish(topic, value) setpoints[house] = 0.0 lastchange[house] = -lockout_period precooling_status[house] = False fncs.publish(house + '_heating_setpoint', 60.0) # update all of the house setpoints count_temp_dev = 0 sum_temp_dev = 0.0 min_temp_dev = 10000.0 max_temp_dev = 0.0 for house, row in dict['houses'].items(): # time-scheduled setpoints if hour_of_day >= row['day_start_hour'] and hour_of_day <= row[ 'day_end_hour']: value = row['day_set'] else: value = row['night_set'] # comfort metrics if house in temperatures: temp_dev = abs(temperatures[house] - value) if temp_dev < min_temp_dev: min_temp_dev = temp_dev if temp_dev > max_temp_dev: max_temp_dev = temp_dev sum_temp_dev += temp_dev count_temp_dev += 1 # time-of-day price response tdelta = (price - mean) * row['deadband'] / k / stddev value += tdelta # overvoltage checks if time_granted >= precooling_quiet and not precooling_status[ house]: if house in voltages: if voltages[house] > row['vthresh']: precooling_status[house] = True nPrecoolers += 1 elif time_granted >= precooling_off: precooling_status[house] = False # overvoltage response if precooling_status[house]: value += row['toffset'] if abs(value - setpoints[house]) > 0.1: if (time_granted - lastchange[house]) > lockout_period: topic = house + '_cooling_setpoint' fncs.publish(topic, value) setpoints[house] = value lastchange[house] = time_granted print('setting', house, 'to', value, 'at', time_granted, 'precooling', precooling_status[house]) if count_temp_dev < 1: count_temp_dev = 1 min_temp_dev = 0.0 precool_metrics[str(time_granted)] = [ min_temp_dev, max_temp_dev, sum_temp_dev / count_temp_dev ] time_next = time_granted + dt print(nPrecoolers, 'houses participated in precooling') print('writing metrics', flush=True) mp = open("precool_" + metrics_root + "_metrics.json", "w") print(json.dumps(precool_metrics), file=mp) mp.close() print('done', flush=True) print('finalizing FNCS', flush=True) fncs.finalize() if sys.platform != 'win32': usage = resource.getrusage(resource.RUSAGE_SELF) RESOURCES = [('ru_utime', 'User time'), ('ru_stime', 'System time'), ('ru_maxrss', 'Max. Resident Set Size'), ('ru_ixrss', 'Shared Memory Size'), ('ru_idrss', 'Unshared Memory Size'), ('ru_isrss', 'Stack Size'), ('ru_inblock', 'Block inputs'), ('ru_oublock', 'Block outputs')] print('Resource usage:') for name, desc in RESOURCES: print(' {:<25} ({:<10}) = {}'.format(desc, name, getattr(usage, name)))
def auction_loop (configfile, metrics_root, flag='WithMarket'): bWantMarket = True if flag == 'NoMarket': bWantMarket = False print ('Disabled the market', flush=True) time_stop = int (48 * 3600) # simulation time in seconds StartTime = '2013-07-01 00:00:00 -0800' time_fmt = '%Y-%m-%d %H:%M:%S %z' dt_now = datetime.strptime (StartTime, time_fmt) # ====== load the JSON dictionary; create the corresponding objects ========= lp = open (configfile).read() dict = json.loads(lp) market_key = list(dict['markets'].keys())[0] # TODO: only using the first market market_row = dict['markets'][market_key] unit = market_row['unit'] auction_meta = {'clearing_price':{'units':'USD','index':0},'clearing_type':{'units':'[0..5]=[Null,Fail,Price,Exact,Seller,Buyer]','index':1}} controller_meta = {'bid_price':{'units':'USD','index':0},'bid_quantity':{'units':unit,'index':1}} auction_metrics = {'Metadata':auction_meta,'StartTime':StartTime} controller_metrics = {'Metadata':controller_meta,'StartTime':StartTime} aucObj = simple_auction.simple_auction (market_row, market_key) dt = float(dict['dt']) period = aucObj.period topicMap = {} # to dispatch incoming FNCS messages; 0..5 for LMP, Feeder load, airtemp, mtr volts, hvac load, hvac state topicMap['LMP'] = [aucObj, 0] topicMap['refload'] = [aucObj, 1] hvacObjs = {} hvac_keys = list(dict['controllers'].keys()) for key in hvac_keys: row = dict['controllers'][key] hvacObjs[key] = simple_auction.hvac (row, key, aucObj) ctl = hvacObjs[key] topicMap[key + '#Tair'] = [ctl, 2] topicMap[key + '#V1'] = [ctl, 3] topicMap[key + '#Load'] = [ctl, 4] topicMap[key + '#On'] = [ctl, 5] # ==================== Time step looping under FNCS =========================== fncs.initialize() aucObj.initAuction() LMP = aucObj.mean refload = 0.0 bSetDefaults = True tnext_bid = period - 2 * dt #3 * dt # controllers calculate their final bids tnext_agg = period - 2 * dt # auction calculates and publishes aggregate bid tnext_opf = period - 1 * dt # PYPOWER executes OPF and publishes LMP (no action here) tnext_clear = period # clear the market with LMP tnext_adjust = period # + dt # controllers adjust setpoints based on their bid and clearing time_granted = 0 time_last = 0 while (time_granted < time_stop): time_granted = fncs.time_request(time_stop) time_delta = time_granted - time_last time_last = time_granted hour_of_day = 24.0 * ((float(time_granted) / 86400.0) % 1.0) # TODO - getting an overflow error when killing process - investigate whether that happens if simulation runs to completion # print (dt_now, time_delta, timedelta (seconds=time_delta)) dt_now = dt_now + timedelta (seconds=time_delta) day_of_week = dt_now.weekday() hour_of_day = dt_now.hour print (' ', time_last, time_granted, time_stop, time_delta, hour_of_day, day_of_week, flush=True) # update the data from FNCS messages events = fncs.get_events() for key in events: topic = key.decode() value = fncs.get_value(key).decode() row = topicMap[topic] if row[1] == 0: LMP = simple_auction.parse_fncs_magnitude (value) aucObj.set_lmp (LMP) elif row[1] == 1: refload = simple_auction.parse_kw (value) aucObj.set_refload (refload) elif row[1] == 2: row[0].set_air_temp (value) elif row[1] == 3: row[0].set_voltage (value) elif row[1] == 4: row[0].set_hvac_load (value) elif row[1] == 5: row[0].set_hvac_state (value) # set the time-of-day schedule for key, obj in hvacObjs.items(): if obj.change_basepoint (hour_of_day, day_of_week): fncs.publish (obj.name + '/cooling_setpoint', obj.basepoint) if bSetDefaults: for key, obj in hvacObjs.items(): fncs.publish (obj.name + '/bill_mode', 'HOURLY') fncs.publish (obj.name + '/monthly_fee', 0.0) fncs.publish (obj.name + '/thermostat_deadband', obj.deadband) fncs.publish (obj.name + '/heating_setpoint', 60.0) bSetDefaults = False if time_granted >= tnext_bid: print ('**', tnext_clear, flush=True) aucObj.clear_bids() time_key = str (int (tnext_clear)) controller_metrics [time_key] = {} for key, obj in hvacObjs.items(): bid = obj.formulate_bid () # bid is [price, quantity, on_state] if bWantMarket: aucObj.collect_bid (bid) controller_metrics[time_key][obj.name] = [bid[0], bid[1]] tnext_bid += period if time_granted >= tnext_agg: aucObj.aggregate_bids() fncs.publish ('unresponsive_mw', aucObj.agg_unresp) fncs.publish ('responsive_max_mw', aucObj.agg_resp_max) fncs.publish ('responsive_c2', aucObj.agg_c2) fncs.publish ('responsive_c1', aucObj.agg_c1) fncs.publish ('responsive_deg', aucObj.agg_deg) tnext_agg += period if time_granted >= tnext_clear: if bWantMarket: aucObj.clear_market() fncs.publish ('clear_price', aucObj.clearing_price) for key, obj in hvacObjs.items(): obj.inform_bid (aucObj.clearing_price) time_key = str (int (tnext_clear)) auction_metrics [time_key] = {aucObj.name:[aucObj.clearing_price, aucObj.clearing_type]} tnext_clear += period if time_granted >= tnext_adjust: if bWantMarket: for key, obj in hvacObjs.items(): fncs.publish (obj.name + '/price', aucObj.clearing_price) if obj.bid_accepted (): fncs.publish (obj.name + '/cooling_setpoint', obj.setpoint) tnext_adjust += period # ==================== Finalize the metrics output =========================== print ('writing metrics', flush=True) auction_op = open ('auction_' + metrics_root + '_metrics.json', 'w') controller_op = open ('controller_' + metrics_root + '_metrics.json', 'w') print (json.dumps(auction_metrics), file=auction_op) print (json.dumps(controller_metrics), file=controller_op) auction_op.close() controller_op.close() print ('finalizing FNCS', flush=True) fncs.finalize()
def update_plots(self, i): """This function is called by Matplotlib for each animation frame Each time called, collect FNCS messages until the next time to plot has been reached. Then update the plot quantities and return the Line2D objects that have been updated for plotting. Check for new data outside the plotted vertical range, which triggers a full re-draw of the axes. On the last frame, finalize FNCS. Args: i (int): the animation frame number """ print('.', end='') # print ('frame', i, 'of', self.nsteps) bRedraw = False while self.time_granted < self.time_stop: # time in minutes self.time_granted = fncs.time_request(self.time_stop) events = fncs.get_events() self.root.update() idx = int(self.time_granted / self.yaml_delta) if idx <= self.idxlast: continue self.idxlast = idx v0 = 0.0 v1 = 0.0 v2 = 0.0 v3 = 0.0 # for topic in events: # value = fncs.get_value(topic) # # if topic == 'vpos8': # # v1 = float (value.strip('+ degFkW')) / 199185.0 # if topic == 'vpos1': # v1 = float(value.strip('+ degFkW')) / 199185.0 # elif topic == 'distribution_load8': # v3 = helpers.parse_kw (value) # elif topic == 'vpos8': # v0 = float (value.strip('+ degFkW')) / 199185.0 # # elif topic == 'clear_price': # # v2 = float (value.strip('+ degFkW')) # elif topic == 'LMP8': # v2 = float (value.strip('+ degFkW')) # # elif topic == 'SUBSTATION7': # # v1 = float (value.strip('+ degFkW')) # already in kW for topic in events: value = fncs.get_value(topic) if topic == self.topicDict[self.plot0.title.get()]: v0 = self.CalculateValue(topic, value, self.plot0) elif topic == self.topicDict[self.plot1.title.get()]: v1 = self.CalculateValue(topic, value, self.plot1) elif topic == self.topicDict[self.plot2.title.get()]: v2 = self.CalculateValue(topic, value, self.plot2) elif topic == self.topicDict[self.plot3.title.get()]: v3 = self.CalculateValue(topic, value, self.plot3) retval = [ self.plot0.ln, self.plot1.ln, self.plot2.ln, self.plot3.ln ] h = float(self.time_granted / 3600.0) self.plot0.hrs.append(h) self.plot1.hrs.append(h) self.plot2.hrs.append(h) self.plot3.hrs.append(h) self.plot0.y.append(v0) # Vpu self.plot1.y.append(v1) # school kW self.plot2.y.append(v2) # price self.plot3.y.append(v3) # feeder load self.plot0.ln.set_data(self.plot0.hrs, self.plot0.y) self.plot1.ln.set_data(self.plot1.hrs, self.plot1.y) self.plot2.ln.set_data(self.plot2.hrs, self.plot2.y) self.plot3.ln.set_data(self.plot3.hrs, self.plot3.y) if len(self.plot0.y) == 2: diff = abs(self.plot0.y[0] - self.plot0.y[1]) if diff == 0: self.plot0.ymin = self.plot0.y[0] - 0.00001 self.plot0.ymax = self.plot0.y[0] + 0.00001 else: self.plot0.ymin = min(self.plot0.y) - 0.00001 * diff self.plot0.ymax = max(self.plot0.y) + 0.00001 * diff if self.plot0.ymin < 0: self.plot0.ymin = 0 self.plot0.ax.set_ylim(self.plot0.ymin, self.plot0.ymax) self.plot0.fig.canvas.draw() diff = abs(self.plot1.y[0] - self.plot1.y[1]) if diff == 0: self.plot1.ymin = self.plot1.y[0] - 0.00001 self.plot1.ymax = self.plot1.y[0] + 0.00001 else: self.plot1.ymin = min(self.plot1.y) - 0.00001 * diff self.plot1.ymax = max(self.plot1.y) + 0.00001 * diff if self.plot1.ymin < 0: self.plot1.ymin = 0 self.plot1.ax.set_ylim(self.plot1.ymin, self.plot1.ymax) self.plot1.fig.canvas.draw() diff = abs(self.plot2.y[0] - self.plot2.y[1]) if diff == 0: self.plot2.ymin = self.plot2.y[0] - 0.00001 self.plot2.ymax = self.plot2.y[0] + 0.00001 else: self.plot2.ymin = min(self.plot2.y) - 0.00001 * diff self.plot2.ymax = max(self.plot2.y) + 0.00001 * diff if self.plot2.ymin < 0: self.plot2.ymin = 0 self.plot2.ax.set_ylim(self.plot2.ymin, self.plot2.ymax) self.plot2.fig.canvas.draw() diff = abs(self.plot3.y[0] - self.plot3.y[1]) if diff == 0: self.plot3.ymin = self.plot3.y[0] - 0.00001 self.plot3.ymax = self.plot3.y[0] + 0.00001 else: self.plot3.ymin = min(self.plot3.y) - 0.00001 * diff self.plot3.ymax = max(self.plot3.y) + 0.00001 * diff if self.plot3.ymin < 0: self.plot3.ymin = 0 self.plot3.ax.set_ylim(self.plot3.ymin, self.plot3.ymax) self.plot3.fig.canvas.draw() else: if v0 < self.plot0.ymin or v0 > self.plot0.ymax: if v0 < self.plot0.ymin: self.plot0.ymin = v0 if v0 > self.plot0.ymax: self.plot0.ymax = v0 self.plot0.ax.set_ylim(self.plot0.ymin, self.plot0.ymax) self.plot0.fig.canvas.draw() if v1 < self.plot1.ymin or v1 > self.plot1.ymax: if v1 < self.plot1.ymin: self.plot1.ymin = v1 if v1 > self.plot1.ymax: self.plot1.ymax = v1 self.plot1.ax.set_ylim(self.plot1.ymin, self.plot1.ymax) self.plot1.fig.canvas.draw() if v2 < self.plot2.ymin or v2 > self.plot2.ymax: if v2 < self.plot2.ymin: self.plot2.ymin = v2 if v2 > self.plot2.ymax: self.plot2.ymax = v2 self.plot2.ax.set_ylim(self.plot2.ymin, self.plot2.ymax) self.plot2.fig.canvas.draw() if v3 < self.plot3.ymin or v3 > self.plot3.ymax: if v3 < self.plot3.ymin: self.plot3.ymin = v3 if v3 > self.plot3.ymax: self.plot3.ymax = v3 self.plot3.ax.set_ylim(self.plot3.ymin, self.plot3.ymax) self.plot3.fig.canvas.draw() # if bRedraw: # self.plot0.fig.canvas.draw() # self.plot1.fig.canvas.draw() # self.plot2.fig.canvas.draw() # self.plot3.fig.canvas.draw() if i >= (self.nsteps - 1): fncs.finalize() self.bFNCSactive = False return retval
def update_plots(self, i): print('.', end='') # print ('frame', i, 'of', self.nsteps) bRedraw = False while self.time_granted < self.time_stop: # time in minutes self.time_granted = fncs.time_request(self.time_stop) events = fncs.get_events() self.root.update() idx = int(self.time_granted / self.yaml_delta) if idx <= self.idxlast: continue self.idxlast = idx v0 = 0.0 v1 = 0.0 v2 = 0.0 v3 = 0.0 # for topic in events: # value = fncs.get_value(topic) # # if topic == 'vpos8': # # v1 = float (value.strip('+ degFkW')) / 199185.0 # if topic == 'vpos1': # v1 = float(value.strip('+ degFkW')) / 199185.0 # elif topic == 'distribution_load8': # v3 = simple_auction.parse_kw (value) # elif topic == 'vpos8': # v0 = float (value.strip('+ degFkW')) / 199185.0 # # elif topic == 'clear_price': # # v2 = float (value.strip('+ degFkW')) # elif topic == 'LMP8': # v2 = float (value.strip('+ degFkW')) # # elif topic == 'SUBSTATION7': # # v1 = float (value.strip('+ degFkW')) # already in kW for topic in events: value = fncs.get_value(topic) if topic == self.topicDict[self.plot0.title.get()]: v0 = self.CalculateValue(topic, value, self.plot0) elif topic == self.topicDict[self.plot1.title.get()]: v1 = self.CalculateValue(topic, value, self.plot1) elif topic == self.topicDict[self.plot2.title.get()]: v2 = self.CalculateValue(topic, value, self.plot2) elif topic == self.topicDict[self.plot3.title.get()]: v3 = self.CalculateValue(topic, value, self.plot3) retval = [self.plot0.ln, self.plot1.ln, self.plot2.ln, self.plot3.ln] h = float(self.time_granted / 3600.0) self.plot0.hrs.append(h) self.plot1.hrs.append(h) self.plot2.hrs.append(h) self.plot3.hrs.append(h) self.plot0.y.append(v0) # Vpu self.plot1.y.append(v1) # school kW self.plot2.y.append(v2) # price self.plot3.y.append(v3) # feeder load self.plot0.ln.set_data(self.plot0.hrs, self.plot0.y) self.plot1.ln.set_data(self.plot1.hrs, self.plot1.y) self.plot2.ln.set_data(self.plot2.hrs, self.plot2.y) self.plot3.ln.set_data(self.plot3.hrs, self.plot3.y) if len(self.plot0.y) == 2: diff = abs(self.plot0.y[0] - self.plot0.y[1]) if diff == 0: self.plot0.ymin = self.plot0.y[0] - 0.00001 self.plot0.ymax = self.plot0.y[0] + 0.00001 else: self.plot0.ymin = min(self.plot0.y) - 0.00001 * diff self.plot0.ymax = max(self.plot0.y) + 0.00001 * diff if self.plot0.ymin < 0: self.plot0.ymin = 0 self.plot0.ax.set_ylim(self.plot0.ymin, self.plot0.ymax) self.plot0.fig.canvas.draw() diff = abs(self.plot1.y[0] - self.plot1.y[1]) if diff == 0: self.plot1.ymin = self.plot1.y[0] - 0.00001 self.plot1.ymax = self.plot1.y[0] + 0.00001 else: self.plot1.ymin = min(self.plot1.y) - 0.00001 * diff self.plot1.ymax = max(self.plot1.y) + 0.00001 * diff if self.plot1.ymin < 0: self.plot1.ymin = 0 self.plot1.ax.set_ylim(self.plot1.ymin, self.plot1.ymax) self.plot1.fig.canvas.draw() diff = abs(self.plot2.y[0] - self.plot2.y[1]) if diff == 0: self.plot2.ymin = self.plot2.y[0] - 0.00001 self.plot2.ymax = self.plot2.y[0] + 0.00001 else: self.plot2.ymin = min(self.plot2.y) - 0.00001 * diff self.plot2.ymax = max(self.plot2.y) + 0.00001 * diff if self.plot2.ymin < 0: self.plot2.ymin = 0 self.plot2.ax.set_ylim(self.plot2.ymin, self.plot2.ymax) self.plot2.fig.canvas.draw() diff = abs(self.plot3.y[0] - self.plot3.y[1]) if diff == 0: self.plot3.ymin = self.plot3.y[0] - 0.00001 self.plot3.ymax = self.plot3.y[0] + 0.00001 else: self.plot3.ymin = min(self.plot3.y) - 0.00001 * diff self.plot3.ymax = max(self.plot3.y) + 0.00001 * diff if self.plot3.ymin < 0: self.plot3.ymin = 0 self.plot3.ax.set_ylim(self.plot3.ymin, self.plot3.ymax) self.plot3.fig.canvas.draw() else: if v0 < self.plot0.ymin or v0 > self.plot0.ymax: if v0 < self.plot0.ymin: self.plot0.ymin = v0 if v0 > self.plot0.ymax: self.plot0.ymax = v0 self.plot0.ax.set_ylim(self.plot0.ymin, self.plot0.ymax) self.plot0.fig.canvas.draw() if v1 < self.plot1.ymin or v1 > self.plot1.ymax: if v1 < self.plot1.ymin: self.plot1.ymin = v1 if v1 > self.plot1.ymax: self.plot1.ymax = v1 self.plot1.ax.set_ylim(self.plot1.ymin, self.plot1.ymax) self.plot1.fig.canvas.draw() if v2 < self.plot2.ymin or v2 > self.plot2.ymax: if v2 < self.plot2.ymin: self.plot2.ymin = v2 if v2 > self.plot2.ymax: self.plot2.ymax = v2 self.plot2.ax.set_ylim(self.plot2.ymin, self.plot2.ymax) self.plot2.fig.canvas.draw() if v3 < self.plot3.ymin or v3 > self.plot3.ymax: if v3 < self.plot3.ymin: self.plot3.ymin = v3 if v3 > self.plot3.ymax: self.plot3.ymax = v3 self.plot3.ax.set_ylim(self.plot3.ymin, self.plot3.ymax) self.plot3.fig.canvas.draw() # if bRedraw: # self.plot0.fig.canvas.draw() # self.plot1.fig.canvas.draw() # self.plot2.fig.canvas.draw() # self.plot3.fig.canvas.draw() if i >= (self.nsteps - 1): fncs.finalize() self.bFNCSactive = False return retval
def launch_all(self): print('launching all simulators') self.pids = [] for row in self.commands: procargs = row['args'] procenv = os.environ.copy() for var in row['env']: procenv[var[0]] = var[1] logfd = None if 'log' in row: logfd = open(row['log'], 'w') # print ('*******************************************************') # print (procargs, procenv) proc = subprocess.Popen(procargs, env=procenv, stdout=logfd) self.pids.append(proc) # print ('*******************************************************') print('launched', len(self.pids), 'simulators') # , self.pids) self.root.update() # print ('root update') fncs.initialize() print('FNCS initialized') time_granted = 0 nsteps = int(self.time_stop / self.yaml_delta) # plt.xlim(0.0, self.hour_stop) hrs = np.linspace(0.0, self.hour_stop, nsteps + 1) print('time_stop, hour_stop and nsteps =', self.time_stop, self.hour_stop, nsteps) idxlast = -1 x0 = np.empty(nsteps + 1) x1 = np.zeros(nsteps + 1) x2 = np.zeros(nsteps + 1) x3 = np.zeros(nsteps + 1) while time_granted < self.time_stop: time_granted = fncs.time_request(self.time_stop) events = fncs.get_events() idx = int(time_granted / self.yaml_delta) if idx <= idxlast: continue idxlast = idx bWantX0 = True # pu volts bWantX1 = True # school load bWantX2 = True # prices bWantX3 = True # total feeder load for key in events: tok = key.decode() if bWantX1 and tok == 'power_A': val = 3.0 * float( fncs.get_value(key).decode().strip( '+ degFkW')) / 1000.0 x1[idx] = val self.ax[1].plot(hrs[1:idx], x1[1:idx], color='red') bWantX1 = False elif bWantX3 and tok == 'distribution_load': val = simple_auction.parse_kw(fncs.get_value(key).decode()) x3[idx] = val self.ax[3].plot(hrs[1:idx], x3[1:idx], color='magenta') bWantX3 = False elif bWantX0 and tok == 'vpos7': val = float( fncs.get_value(key).decode().strip( '+ degFkW')) / 133000.0 x0[idx] = val self.ax[0].plot(hrs[1:idx], x0[1:idx], color='green') bWantX0 = False elif bWantX2 and tok == 'clear_price': val = float(fncs.get_value(key).decode().strip('+ degFkW')) x2[idx] = val self.ax[2].plot(hrs[1:idx], x2[1:idx], color='blue') bWantX2 = False elif bWantX2 and tok == 'LMP7': val = float(fncs.get_value(key).decode().strip('+ degFkW')) x2[idx] = val self.ax[2].plot(hrs[1:idx], x2[1:idx], color='blue') bWantX2 = False elif bWantX1 and tok == 'SUBSTATION7': val = float( fncs.get_value(key).decode().strip( '+ degFkW')) # already in kW x1[idx] = val self.ax[1].plot(hrs[1:idx], x1[1:idx], color='red') bWantX1 = False # print (time_granted, key.decode(), fncs.get_value(key).decode()) self.root.update() self.fig.canvas.draw() fncs.finalize()