def schedule_run(self, cur_exp_time, cur_analysis_time, start_of_cycle=False): """ Run when first start or run at beginning of hour :return: """ # Balance market = self.markets[0] # Assume only 1 TNS market per node market.balance(self) prices = market.marginalPrices prices = prices[-25:] prices = [x.value for x in prices] _time = format_timestamp(Timer.get_cur_time()) self.vip.pubsub.publish(peer='pubsub', topic=self.price_topic, message={ 'prices': prices, 'current_time': _time }) self.campus.model.prep_transactive_signal(market, self) self.campus.model.send_transactive_signal( self, self.city_supply_topic, start_of_cycle=start_of_cycle) # Schedule to run next hour with start_of_cycle = True cur_exp_time = parser.parse(cur_exp_time) cur_analysis_time = parser.parse(cur_analysis_time) next_exp_time, next_analysis_time = self.get_next_exp_time( cur_exp_time, cur_analysis_time) self.core.schedule(next_exp_time, self.schedule_run, format_timestamp(next_exp_time), format_timestamp(next_analysis_time), start_of_cycle=True)
def new_demand_signal(self, peer, sender, bus, topic, headers, message): _log.debug("At {}, {} receives new demand records: {}".format( Timer.get_cur_time(), self.name, message)) demand_curves = message['curves'] # Should not do anything with start_of_cycle signal self.campus.model.receive_transactive_signal( self, demand_curves) # atm, only one campus self.balance_market(1)
def new_supply_signal(self, peer, sender, bus, topic, headers, message): _log.debug("At {}, {} receives new supply records: {}".format( Timer.get_cur_time(), self.name, message)) source = message['source'] supply_curves = message['curves'] start_of_cycle = message['start_of_cycle'] fail_to_converged = message['fail_to_converged'] self.city.model.receive_transactive_signal(self, supply_curves) if start_of_cycle: self.balance_market(1, start_of_cycle, fail_to_converged)
def new_demand_signal(self, peer, sender, bus, topic, headers, message): mtrs = self.campus.model.meterPoints if len(mtrs) > 0: bldg_meter = mtrs[0] power_unit = message[1] cur_power = float(message[0]["WholeBuildingPower"]) power_unit = power_unit.get("WholeBuildingPower", {}).get("units", "kW") if isinstance(power_unit, str) and power_unit.lower() == "watts": cur_power = cur_power / 1000.0 bldg_meter.set_meter_value(cur_power) if Timer.get_cur_time().minute >= 58: bldg_meter.update_avg()
def aggregate_callback(self, timestamp, market_name, buyer_seller, aggregate_demand): if buyer_seller == BUYER and market_name in self.market_names: # self.base_market_name in market_name: _log.debug("{}: at ts {} min of aggregate curve : {}".format( self.agent_name, timestamp, aggregate_demand.points[0])) _log.debug("{}: at ts {} max of aggregate curve : {}".format( self.agent_name, timestamp, aggregate_demand.points[-1])) _log.debug( "At {}: Report aggregate Market: {} buyer Curve: {}".format( Timer.get_cur_time(), market_name, aggregate_demand)) idx = int(market_name.split('_')[-1]) idx += 1 # quantity has 25 values while there are 24 future markets self.building_demand_curves[idx] = (aggregate_demand.points[0], aggregate_demand.points[-1])
def new_supply_signal(self, peer, sender, bus, topic, headers, message): _log.debug("At {}, {} receives new supply records: {}".format( Timer.get_cur_time(), self.name, message)) supply_curves = message['curves'] start_of_cycle = message['start_of_cycle'] self.campus.model.receive_transactive_signal(self, supply_curves) _log.debug("At {}, mixmarket state is {}, start_of_cycle {}".format( Timer.get_cur_time(), self.mix_market_running, start_of_cycle)) db_topic = "/".join([self.db_topic, self.name, "CampusSupply"]) message = supply_curves headers = {headers_mod.DATE: format_timestamp(Timer.get_cur_time())} self.vip.pubsub.publish("pubsub", db_topic, headers, message).get() if start_of_cycle: _log.debug("At {}, start of cycle. " "Mixmarket state before overriding is {}".format( Timer.get_cur_time(), self.mix_market_running)) # if self.simulation: # self.run_ep_sim(start_of_cycle) # else: self.start_mixmarket(start_of_cycle)
def balance_market(self, run_cnt): market = self.markets[0] # Assume only 1 TNS market per node market.signal_new_data = True market.balance(self) if market.converged: # Sum all the powers as will be needed by the net supply/demand curve. market.assign_system_vertices(self) # Check to see if it is ok to send signals self.campus.model.check_for_convergence(market) if not self.campus.model.converged: _log.debug("NeighborModel {} sends records to campus.".format( self.campus.model.name)) self.campus.model.prep_transactive_signal(market, self) self.campus.model.send_transactive_signal( self, self.city_supply_topic, start_of_cycle=False) else: # Schedule rerun balancing only if not in simulation mode if not self.simulation: dt = datetime.now() _log.debug( "{} ({}) did not send records due to check_for_convergence()" .format(self.name, dt)) # Schedule to rerun after 5 minutes if it is in the same hour and is the first reschedule next_run_dt = dt + self.reschedule_interval if dt.hour == next_run_dt.hour and run_cnt >= 1: _log.debug("{} reschedule to run at {}".format( self.name, next_run_dt)) self.core.schedule(next_run_dt, self.balance_market, run_cnt + 1) prices = market.marginalPrices # There is a case where the balancing happens at the end of the hour and continues to the next hour, which # creates 26 values. Get the last 25 values. prices = prices[-25:] prices = [x.value for x in prices] self.vip.pubsub.publish(peer='pubsub', topic=self.price_topic, message={ 'prices': prices, 'current_time': format_timestamp(Timer.get_cur_time()) }) else: _log.debug("Market balancing sub-problem failed.")
def new_demand_signal(self, peer, sender, bus, topic, headers, message): _log.debug("At {}, {} receives new demand records: {}".format( Timer.get_cur_time(), self.name, message)) building_name = message['source'] demand_curves = message['curves'] start_of_cycle = message['start_of_cycle'] fail_to_converged = message['fail_to_converged'] neighbors = [n for n in self.neighbors if n.name == building_name] if len(neighbors) == 1: neighbor = neighbors[0] neighbor.model.receive_transactive_signal(self, demand_curves) self.balance_market(1, start_of_cycle, fail_to_converged, neighbor) else: _log.error("{}: There are {} building(s) with name {}.".format( self.name, len(neighbors), building_name)) _log.error("Neighbors are: {}".format( [x.name for x in self.neighbors])) _log.error("Message is: {}".format(message)) _log.error( "Check value of 'name' key in the config file for building {}." .format(building_name))
def init_objects(self): # Add meter meter = MeterPoint() meter.measurementType = MeasurementType.PowerReal meter.name = 'CoRElectricMeter' meter.measurementUnit = MeasurementUnit.kWh self.meterPoints.append(meter) # Add weather forecast service weather_service = TemperatureForecastModel(self.config_path, self) self.informationServiceModels.append(weather_service) # Add inelastive asset # Source: https://www.ci.richland.wa.us/home/showdocument?id=1890 # Residential customers: 23,768 # Electricity sales in 2015: # Total: 100.0# 879,700,000 kWh 100,400 avg. kW) # Resident: 46.7 327,200,000 37,360 # Gen. Serv.: 38.1 392,300,000 44,790 # Industrial: 15.2 133,700,000 15,260 # Irrigation: 2.4 21,110,000 2,410 # Other: 0.6 5,278,000 603 # 2015 Res. rate: $0.0616/kWh # Avg. annual residential cust. use: 14,054 kWh # Winter peak, 160,100 kW (1.6 x average) # Summer peak, 180,400 kW (1.8 x average) # Annual power supply expenses: $35.5M # ************************************************************************* inelastive_load = LocalAsset() inelastive_load.name = 'InelasticLoad' inelastive_load.maximumPower = -50000 # Remember that a load is a negative power [kW] inelastive_load.minimumPower = -200000 # Assume twice the averag PNNL load [kW] # Add inelastive asset model inelastive_load_model = OpenLoopRichlandLoadPredictor(weather_service) inelastive_load_model.name = 'InelasticLoadModel' inelastive_load_model.defaultPower = -100420 # [kW] inelastive_load_model.defaultVertices = [ Vertex(float("inf"), 0.0, -100420.0) ] # Cross-reference asset & asset model inelastive_load_model.object = inelastive_load inelastive_load.model = inelastive_load_model # Add inelastic as city's asset self.localAssets.extend([inelastive_load]) # Add Market market = Market() market.name = 'dayAhead' market.commitment = False market.converged = False market.defaultPrice = 0.0428 # [$/kWh] market.dualityGapThreshold = self.duality_gap_threshold # [0.02 = 2#] market.initialMarketState = MarketState.Inactive market.marketOrder = 1 # This is first and only market market.intervalsToClear = 1 # Only one interval at a time market.futureHorizon = timedelta( hours=24) # Projects 24 hourly future intervals market.intervalDuration = timedelta( hours=1) # [h] Intervals are 1 h long market.marketClearingInterval = timedelta(hours=1) # [h] market.marketClearingTime = Timer.get_cur_time().replace( hour=0, minute=0, second=0, microsecond=0) # Aligns with top of hour market.nextMarketClearingTime = market.marketClearingTime + timedelta( hours=1) self.markets.append(market) self.campus = self.make_campus() supplier = self.make_supplier() # Add campus as city's neighbor self.neighbors.extend([self.campus, supplier])
def init_objects(self): # Add meter meter = MeterPoint() meter.measurementType = MeasurementType.PowerReal meter.name = 'CampusElectricityMeter' meter.measurementUnit = MeasurementUnit.kWh self.meterPoints.append(meter) # Add weather forecast service weather_service = TemperatureForecastModel(self.config_path, self) self.informationServiceModels.append(weather_service) # Add inelastive asset inelastive_load = LocalAsset() inelastive_load.name = 'InelasticBuildings' # Campus buildings that are not responsive inelastive_load.maximumPower = 0 # Remember that a load is a negative power [kW] inelastive_load.minimumPower = -2 * 8200 # Assume twice the average PNNL load [kW] # Add inelastive asset model inelastive_load_model = OpenLoopPnnlLoadPredictor(weather_service) inelastive_load_model.name = 'InelasticBuildingsModel' inelastive_load_model.engagementCost = [ 0, 0, 0 ] # Transition costs irrelevant inelastive_load_model.defaultPower = -6000 # [kW] inelastive_load_model.defaultVertices = [Vertex(0, 0, -6000.0, 1)] # Cross-reference asset & asset model inelastive_load_model.object = inelastive_load inelastive_load.model = inelastive_load_model # Add solar PV asset solar_pv = SolarPvResource() solar_pv.maximumPower = self.PV_max_kW # [avg.kW] solar_pv.minimumPower = 0.0 # [avg.kW] solar_pv.name = 'SolarPv' solar_pv.description = '120 kW solar PV site on the campus' # Add solar PV asset model solar_pv_model = SolarPvResourceModel() solar_pv_model.cloudFactor = 1.0 # dimensionless solar_pv_model.engagementCost = [0, 0, 0] solar_pv_model.name = 'SolarPvModel' solar_pv_model.defaultPower = 0.0 # [avg.kW] solar_pv_model.defaultVertices = [Vertex(0, 0, 30.0, True)] solar_pv_model.costParameters = [0, 0, 0] solar_pv_model.inject(self, power_topic=self.solar_topic) # Cross-reference asset & asset model solar_pv.model = solar_pv_model solar_pv_model.object = solar_pv # Add inelastive and solar_pv as campus' assets self.localAssets.extend([inelastive_load, solar_pv]) # Add Market market = Market() market.name = 'dayAhead' market.commitment = False market.converged = False market.defaultPrice = 0.04 # [$/kWh] market.dualityGapThreshold = self.duality_gap_threshold # [0.02 = 2#] market.initialMarketState = MarketState.Inactive market.marketOrder = 1 # This is first and only market market.intervalsToClear = 1 # Only one interval at a time market.futureHorizon = timedelta( hours=24) # Projects 24 hourly future intervals market.intervalDuration = timedelta( hours=1) # [h] Intervals are 1 h long market.marketClearingInterval = timedelta(hours=1) # [h] market.marketClearingTime = Timer.get_cur_time().replace( hour=0, minute=0, second=0, microsecond=0) # Aligns with top of hour market.nextMarketClearingTime = market.marketClearingTime + timedelta( hours=1) self.markets.append(market) # City object city = Neighbor() city.name = 'CoR' city.description = 'City of Richland (COR) electricity supplier node' city.maximumPower = 20000 # Remember loads have negative power [avg.kW] city.minimumPower = 0 # [avg.kW] city.lossFactor = self.city_loss_factor # City model city_model = NeighborModel() city_model.name = 'CoR_Model' city_model.location = self.name city_model.transactive = True city_model.defaultPower = 10000 # [avg.kW] city_model.defaultVertices = [ Vertex(0.046, 160, 0, True), Vertex(0.048, 160 + city.maximumPower * (0.046 + 0.5 * (0.048 - 0.046)), city.maximumPower, True) ] city_model.costParameters = [0, 0, 0] city_model.demand_threshold_coef = self.demand_threshold_coef city_model.demandThreshold = self.monthly_peak_power city_model.inject(self, system_loss_topic=self.system_loss_topic, dc_threshold_topic=self.dc_threshold_topic) # Cross-reference object & model city_model.object = city city.model = city_model self.city = city # Add city as campus' neighbor self.neighbors.append(city) # Add buildings for bldg_name in self.building_names: bldg_neighbor = self.make_bldg_neighbor(bldg_name) self.neighbors.append(bldg_neighbor)
def balance_market(self, run_cnt, start_of_cycle=False, fail_to_converged=False, fail_to_converged_neighbor=None): market = self.markets[0] # Assume only 1 TNS market per node market.signal_new_data = True market.balance(self) # Assume only 1 TNS market per node if market.converged: _log.debug("TNS market {} balanced successfully.".format( market.name)) # Sum all the powers as will be needed by the net supply/demand curve. market.assign_system_vertices(self) # Send only if either of the 2 conditions below occurs: # 1) Model balancing did not converge # 2) A new cycle (ie. begin of hour) for n in self.neighbors: # If the neighbor failed to converge (eg., building1 failed to converge) if n == fail_to_converged_neighbor and n is not None: n.model.prep_transactive_signal(market, self) topic = self.campus_demand_topic if n != self.city: topic = self.campus_supply_topic.format(n.name) n.model.send_transactive_signal(self, topic, start_of_cycle) _log.debug("NeighborModel {} sent records.".format( n.model.name)) else: # Always send signal downstream at the start of a new cyle if start_of_cycle: if n != self.city: n.model.prep_transactive_signal(market, self) topic = self.campus_supply_topic.format(n.name) n.model.send_transactive_signal( self, topic, start_of_cycle) _log.debug("NeighborModel {} sent records.".format( n.model.name)) else: _log.debug( "Not start of cycle. Check convergence for neighbor {}." .format(n.model.name)) n.model.check_for_convergence(market) if not n.model.converged: n.model.prep_transactive_signal(market, self) topic = self.campus_demand_topic if n != self.city: topic = self.campus_supply_topic.format(n.name) n.model.send_transactive_signal( self, topic, start_of_cycle) _log.debug("NeighborModel {} sent records.".format( n.model.name)) else: _log.debug( "{} ({}) did not send records due to check_for_convergence()." .format(n.model.name, self.name)) # Schedule rerun balancing if not in simulation mode if not self.simulation: # For start_of_cyle=True, the code above always send signal to neighbors so don't need to reschedule # Schedule rerun if any neighbor is not converged if not start_of_cycle: if not all([n.model.converged for n in self.neighbors]): dt = datetime.now() # Schedule to rerun after 5 minutes if it is in the same hour and is the first reschedule next_run_dt = dt + self.reschedule_interval if dt.hour == next_run_dt.hour and run_cnt >= 1: _log.debug("{} reschedule to run at {}".format( self.name, next_run_dt)) self.core.schedule(next_run_dt, self.balance_market, run_cnt + 1) prices = market.marginalPrices # There is a case where the balancing happens at the end of the hour and continues to the next hour, which # creates 26 values. Get the last 25 values. prices = prices[-25:] prices = [x.value for x in prices] self.vip.pubsub.publish(peer='pubsub', topic=self.price_topic, message={ 'prices': prices, 'current_time': format_timestamp(Timer.get_cur_time()) }) else: _log.debug("Market balancing sub-problem failed.") self.city.model.prep_transactive_signal(market, self) self.city.model.send_transactive_signal(self, self.campus_demand_topic, start_of_cycle)
def run_ep_sim(self, start_of_cycle): # Reset price array self.prices = [None for i in range(25)] # Save the 1st quantity as prior 2nd quantity cur_quantity = self.quantities[1] cur_curve = self.building_demand_curves[1] # Reset quantities and curves self.quantities = [None for i in range(25)] self.building_demand_curves = [None for i in range(25)] # If new cycle, set the 1st quantity to the corresponding quantity of previous hour if start_of_cycle: self.quantities[0] = cur_quantity self.building_demand_curves[0] = cur_curve # Balance market with previous known demands market = self.markets[0] # Assume only 1 TNS market per node market.signal_new_data = True market.balance(self) # Check if now is near the end of the hour, applicable only if not in simulation mode now = Timer.get_cur_time() near_end_of_hour = False if not self.simulation: near_end_of_hour = self.near_end_of_hour(now) if market.converged: # Get new prices (expected 25 values: current hour + next 24) prices = market.marginalPrices # There is a case where the balancing happens at the end of the hour and continues to the next hour, which # creates 26 values. Get the last 25 values. prices = prices[-25:] self.prices = [p.value for p in prices] # Signal to start mix market only if the previous market is done if not self.mix_market_running and not near_end_of_hour: self.mix_market_running = True # Read data from e+ output file self.quantities = [] self.prices = [] self.building_demand_curves = [] if self.cur_ep_line < len(self.ep_lines): for i in range(self.cur_ep_line, len(self.ep_lines)): line = self.ep_lines[i] if "mixmarket DEBUG: Quantities: " in line: self.quantities = eval(line[line.find('['):]) if "mixmarket DEBUG: Prices: " in line: self.prices = eval(line[line.find('['):]) if "mixmarket DEBUG: Curves: " in line: tmp = eval(line[line.find('['):]) for item in tmp: if item is None: self.building_demand_curves.append(item) else: p1 = Point(item[0][0], item[0][1]) p2 = Point(item[1][0], item[1][1]) self.building_demand_curves.append((p1, p2)) # Stop when have enough information (ie. all data responded by a single E+ simulation) if len(self.quantities) > 0 and len( self.prices) > 0 and len( self.building_demand_curves) > 0: self.cur_ep_line = i + 1 break self.elastive_load_model.set_tcc_curves( self.quantities, self.prices, self.building_demand_curves) self.balance_market(1)
def price_callback(self, timestamp, market_name, buyer_seller, price, quantity): _log.debug("{}: cleared price ({}, {}) for {} as {} at {}".format( Timer.get_cur_time(), price, quantity, market_name, buyer_seller, timestamp)) idx = int(market_name.split('_')[-1]) self.prices[ idx + 1] = price # price has 24 values, current hour price is excluded if price is None: raise "Market {} did not clear. Price is none.".format(market_name) idx += 1 # quantity has 25 values while there are 24 future markets if self.quantities[idx] is None: self.quantities[idx] = 0. if quantity is None: _log.error("Quantity is None. Set it to 0. Details below.") _log.debug("{}: ({}, {}) for {} as {} at {}".format( self.agent_name, price, quantity, market_name, buyer_seller, timestamp)) quantity = 0 self.quantities[idx] += quantity _log.debug("At {}, Quantity is {} and quantities are: {}".format( Timer.get_cur_time(), quantity, self.quantities)) if quantity is not None and quantity < 0: _log.error( "Quantity received from mixmarket is negative!!! {}".format( quantity)) # If all markets (ie. exclude 1st value) are done then update demands, otherwise do nothing mix_market_done = all( [False if q is None else True for q in self.quantities[1:]]) if mix_market_done: # Check if any quantity is greater than physical limit of the supply wire _log.debug("Quantity: {}".format(self.quantities)) if not all([ False if q > self.max_deliver_capacity else True for q in self.quantities[1:] ]): _log.error("One of quantity is greater than " "physical limit {}".format( self.max_deliver_capacity)) # Check demand curves exist all_curves_exist = all([ False if q is None else True for q in self.building_demand_curves[1:] ]) if not all_curves_exist: _log.error("Demand curves: {}".format( self.building_demand_curves)) raise "Mix market has all quantities but not all demand curves" # Update demand and balance market self.mix_market_running = False #self.elastive_load_model.default_powers = [-q if q is not None else None for q in self.quantities] curves_arr = [(c[0].tuppleize(), c[1].tuppleize()) if c is not None else None for c in self.building_demand_curves] _log.debug("Data at time {}:".format(Timer.get_cur_time())) _log.debug("Market intervals: {}".format( [x.name for x in self.markets[0].timeIntervals])) _log.debug("Quantities: {}".format(self.quantities)) _log.debug("Prices: {}".format(self.prices)) _log.debug("Curves: {}".format(curves_arr)) db_topic = "/".join([self.db_topic, self.name, "AggregateDemand"]) message = { "Timestamp": format_timestamp(timestamp), "Curves": self.building_demand_curves } headers = { headers_mod.DATE: format_timestamp(Timer.get_cur_time()) } self.vip.pubsub.publish("pubsub", db_topic, headers, message).get() db_topic = "/".join([self.db_topic, self.name, "Price"]) price_message = [] for i in range(len(self.markets[0].timeIntervals)): ts = self.markets[0].timeIntervals[i].name price = self.prices[i] quantity = self.quantities[i] price_message.append({ 'timeInterval': ts, 'price': price, 'quantity': quantity }) message = { "Timestamp": format_timestamp(timestamp), "Price": price_message } headers = { headers_mod.DATE: format_timestamp(Timer.get_cur_time()) } self.vip.pubsub.publish("pubsub", db_topic, headers, message).get() self.elastive_load_model.set_tcc_curves( self.quantities, self.prices, self.building_demand_curves) self.balance_market(1)
def init_objects(self): # Add meter # meter = MeterPoint() # meter.name = 'BuildingElectricMeter' # meter.measurementType = MeasurementType.PowerReal # meter.measurementUnit = MeasurementUnit.kWh # self.meterPoints.append(meter) # Add weather forecast service weather_service = TemperatureForecastModel(self.config_path, self) self.informationServiceModels.append(weather_service) # # Add inelastive asset # inelastive_load = LocalAsset() # inelastive_load.name = 'InelasticBldgLoad' # inelastive_load.maximumPower = 0 # Remember that a load is a negative power [kW] # inelastive_load.minimumPower = -200 # # # Add inelastive asset model # inelastive_load_model = LocalAssetModel() # inelastive_load_model.name = 'InelasticBuildingModel' # inelastive_load_model.defaultPower = -100 # [kW] # inelastive_load_model.defaultVertices = [Vertex(float("inf"), 0, -100, True)] # # # Cross-reference asset & asset model # inelastive_load_model.object = inelastive_load # inelastive_load.model = inelastive_load_model # Add elastive asset elastive_load = LocalAsset() elastive_load.name = 'TccLoad' elastive_load.maximumPower = 0 # Remember that a load is a negative power [kW] elastive_load.minimumPower = -self.max_deliver_capacity # Add inelastive asset model # self.elastive_load_model = LocalAssetModel() self.elastive_load_model = TccModel() self.elastive_load_model.name = 'TccModel' self.elastive_load_model.defaultPower = -0.5 * self.max_deliver_capacity # [kW] self.elastive_load_model.defaultVertices = [ Vertex(0.055, 0, -self.elastive_load_model.defaultPower, True), Vertex(0.06, 0, -self.elastive_load_model.defaultPower / 2, True) ] # Cross-reference asset & asset model self.elastive_load_model.object = elastive_load elastive_load.model = self.elastive_load_model # Add inelastive and elastive loads as building' assets self.localAssets.extend([elastive_load]) # Add Market market = Market() market.name = 'dayAhead' market.commitment = False market.converged = False market.defaultPrice = 0.0428 # [$/kWh] market.dualityGapThreshold = self.duality_gap_threshold # [0.02 = 2#] market.initialMarketState = MarketState.Inactive market.marketOrder = 1 # This is first and only market market.intervalsToClear = 1 # Only one interval at a time market.futureHorizon = timedelta( hours=24) # Projects 24 hourly future intervals market.intervalDuration = timedelta( hours=1) # [h] Intervals are 1 h long market.marketClearingInterval = timedelta(hours=1) # [h] market.marketClearingTime = Timer.get_cur_time().replace( hour=0, minute=0, second=0, microsecond=0) # Aligns with top of hour market.nextMarketClearingTime = market.marketClearingTime + timedelta( hours=1) self.markets.append(market) # Campus object campus = Neighbor() campus.name = 'PNNL_Campus' campus.description = 'PNNL_Campus' campus.maximumPower = self.max_deliver_capacity campus.minimumPower = 0. # [avg.kW] campus.lossFactor = self.campus_loss_factor # Campus model campus_model = NeighborModel() campus_model.name = 'PNNL_Campus_Model' campus_model.location = self.name campus_model.defaultVertices = [ Vertex(0.045, 25, 0, True), Vertex(0.048, 0, self.max_deliver_capacity, True) ] campus_model.transactive = True campus_model.demand_threshold_coef = self.demand_threshold_coef # campus_model.demandThreshold = self.demand_threshold_coef * self.monthly_peak_power campus_model.demandThreshold = self.monthly_peak_power campus_model.inject(self, system_loss_topic=self.system_loss_topic, dc_threshold_topic=self.dc_threshold_topic) # Avg building meter building_meter = MeterPoint() building_meter.name = self.name + ' ElectricMeter' building_meter.measurementType = MeasurementType.AverageDemandkW building_meter.measurementUnit = MeasurementUnit.kWh campus_model.meterPoints.append(building_meter) # Cross-reference object & model campus_model.object = campus campus.model = campus_model self.campus = campus # Add campus as building's neighbor self.neighbors.append(campus)
def start_mixmarket(self, start_of_cycle): # Reset price array self.prices = [None for i in range(25)] # Save the 1st quantity as prior 2nd quantity cur_quantity = self.quantities[1] cur_curve = self.building_demand_curves[1] # Reset quantities and curves self.quantities = [None for i in range(25)] self.building_demand_curves = [None for i in range(25)] # If new cycle, set the 1st quantity to the corresponding quantity of previous hour if start_of_cycle: self.quantities[0] = cur_quantity self.building_demand_curves[0] = cur_curve # Balance market with previous known demands market = self.markets[0] # Assume only 1 TNS market per node market.signal_new_data = True market.balance(self) # Check if now is near the end of the hour, applicable only if not in simulation mode now = Timer.get_cur_time() near_end_of_hour = False if not self.simulation: near_end_of_hour = self.near_end_of_hour(now) if market.converged: # Get new prices (expected 25 values: current hour + next 24) prices = market.marginalPrices # There is a case where the balancing happens at the end of the hour and continues to the next hour, which # creates 26 values. Get the last 25 values. prices = prices[-25:] self.prices = [p.value for p in prices] # Signal to start mix market only if the previous market is done if not self.mix_market_running and not near_end_of_hour: self.mix_market_running = True # Update weather information weather_service = None if len(self.informationServiceModels) > 0: weather_service = self.informationServiceModels[0] weather_service.update_information(market) _log.debug("prices are {}".format(self.prices)) if weather_service is None: self.vip.pubsub.publish(peer='pubsub', topic='mixmarket/start_new_cycle', message={ "prices": self.prices[-24:], "Date": format_timestamp(now) }) else: temps = [x.value for x in weather_service.predictedValues] temps = temps[-24:] _log.debug("temps are {}".format(temps)) self.vip.pubsub.publish(peer='pubsub', topic='mixmarket/start_new_cycle', message={ "prices": self.prices[-24:], "temp": temps, "Date": format_timestamp(now) })