def make_supplier(self): # Add supplier supplier = Neighbor() supplier.name = 'BPA' supplier.description = 'The Bonneville Power Administration as electricity supplier to the City of Richland, WA' supplier.lossFactor = self.supplier_loss_factor supplier.maximumPower = 200800 # [avg.kW, twice the average COR load] supplier.minimumPower = 0.0 # [avg.kW, will not export] # Add supplier model supplierModel = BulkSupplier_dc() supplierModel.name = 'BPAModel' #supplierModel.demandThreshold = 0.75 * supplier.maximumPower supplierModel.converged = False # Dynamically assigned supplierModel.convergenceThreshold = 0 # Not yet implemented supplierModel.effectiveImpedance = 0.0 # Not yet implemented supplierModel.friend = False # Separate business entity from COR supplierModel.transactive = False # Not a transactive neighbor # Add vertices # The first default vertex is, for now, based on the flat COR rate to # PNNL. The second vertex includes 2# losses at a maximum power that # is twice the average electric load for COR. This is helpful to # ensure that a unique price, power point will be found. In this # model the recipient pays the cost of energy losses. # The first vertex is based on BPA Jan HLH rate at zero power # importation. d1 = Vertex(0, 0, 0) # create first default vertex d1.marginalPrice = 0.04196 # HLH BPA rate Jan 2018 [$/kWh] d1.cost = 2000.0 # Const. price shift to COR customer rate [$/h] d1.power = 0.0 # [avg.kW] # The second default vertex represents imported and lost power at a power # value presumed to be the maximum deliverable power from BPA to COR. d2 = Vertex(0, 0, 0) # create second default vertex # COR pays for all sent power but receives an amount reduced by # losses. This creates a quadratic term in the production cost and # a slope to the marginal price curve. d2.marginalPrice = d1.marginalPrice / (1 - supplier.lossFactor ) # [$/kWh] # From the perspective of COR, it receives the power sent by BPA, # less losses. d2.power = (1 - supplier.lossFactor) * supplier.maximumPower # [avg.kW] # The production costs can be estimated by integrating the # marginal-price curve. d2.cost = d1.cost + d2.power * (d1.marginalPrice + 0.5 * (d2.marginalPrice - d1.marginalPrice) ) # [$/h] supplierModel.defaultVertices = [d1, d2] # COST PARAMTERS # A constant cost parameter is being used here to account for the # difference between wholesale BPA rates to COR and COR distribution # rates to customers like PNNL. A constant of $2,000/h steps the rates # from about 0.04 $/kWh to about 0.06 $/kWh. This may be refined later. # IMPORTANT: This shift has no affect on marginal pricing. supplierModel.costParameters[0] = 2000.0 # [$/h] # Cross-reference object & model supplierModel.object = supplier supplier.model = supplierModel # Meter bpaElectricityMeter = MeterPoint() # Instantiate an electricity meter bpaElectricityMeter.name = 'BpaElectricityMeter' bpaElectricityMeter.description = 'BPA electricity to COR' bpaElectricityMeter.measurementType = MeasurementType.PowerReal bpaElectricityMeter.measurementUnit = MeasurementUnit.kWh supplierModel.meterPoints = [bpaElectricityMeter] return supplier
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.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.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.transactive = True campus_model.inject(self, system_loss_topic=self.system_loss_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 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 test_update_dc_threshold(): print('Running BulkSupplier_dc.test_update_dc_threshold()') pf = 'pass' ## Basic configuration for tests: # Create a test object and initialize demand-realted properties test_obj = BulkSupplier_dc() test_obj.demandMonth = datetime.now().month # month(datetime) test_obj.demandThreshold = 1000 # Create a test market test_mkt = Market() # Create and store two time intervals dt = datetime.now() at = dt dur = timedelta(hours=1) # Hours(1) mkt = test_mkt mct = dt st = dt ti = [TimeInterval(at, dur, mkt, mct, st)] st = st + dur ti.append(TimeInterval(at, dur, mkt, mct, st)) test_mkt.timeIntervals = ti ## Test case when there is no MeterPoint object test_obj.demandThreshold = 1000 test_obj.demandMonth = datetime.now().month # month(datetime) test_obj.meterPoints = [] # MeterPoint.empty # Create and store a couple scheduled powers # iv(1) = IntervalValue(test_obj, ti[0], test_mkt, MeasurementType.ScheduledPower, 900) # iv(2) = IntervalValue(test_obj, ti[1], test_mkt, MeasurementType.ScheduledPower, 900) iv = [ IntervalValue(test_obj, ti[0], test_mkt, MeasurementType.ScheduledPower, 900), IntervalValue(test_obj, ti[1], test_mkt, MeasurementType.ScheduledPower, 900) ] test_obj.scheduledPowers = iv try: test_obj.update_dc_threshold(test_mkt) print('- the method ran without errors') except: pf = 'fail' print('- the method encountered errors when called') if test_obj.demandThreshold != 1000: pf = 'fail' print('- the method inferred the wrong demand threshold value') else: print('- the method properly kept the old demand threshold value with no meter') iv = [ IntervalValue(test_obj, ti[0], test_mkt, MeasurementType.ScheduledPower, 1100), IntervalValue(test_obj, ti[1], test_mkt, MeasurementType.ScheduledPower, 900) ] test_obj.scheduledPowers = iv try: test_obj.update_dc_threshold(test_mkt) print('- the method ran without errors when there is no meter') except: pf = 'fail' print('- the method encountered errors when there is no meter') if test_obj.demandThreshold != 1100: pf = 'fail' print('- the method did not update the inferred demand threshold value') else: print('- the method properly updated the demand threshold value with no meter') ## Test with an appropriate MeterPoint meter # Create and store a MeterPoint test object test_mtr = MeterPoint() test_mtr.measurementType = MeasurementType.AverageDemandkW test_mtr.currentMeasurement = 900 test_obj.meterPoints = [test_mtr] # Reconfigure the test object for this test: iv = [ IntervalValue(test_obj, ti[0], test_mkt, MeasurementType.ScheduledPower, 900), IntervalValue(test_obj, ti[1], test_mkt, MeasurementType.ScheduledPower, 900) ] test_obj.scheduledPowers = iv test_obj.demandThreshold = 1000 test_obj.demandMonth = datetime.now().month # Run the test. Confirm it runs. try: test_obj.update_dc_threshold(test_mkt) print('- the method ran without errors when there is a meter') except: pf = 'fail' print('- the method encountered errors when there is a meter') # Check that the old threshold is correctly retained. if test_obj.demandThreshold != 1000: pf = 'fail' print('- the method failed to keep the correct demand threshold value when there is a meter') else: print('- the method properly kept the old demand threshold value when there is a meter') # Reconfigure the test object with a lower current threshold iv = [ IntervalValue(test_obj, ti[0], test_mkt, MeasurementType.ScheduledPower, 900), IntervalValue(test_obj, ti[1], test_mkt, MeasurementType.ScheduledPower, 900) ] test_obj.scheduledPowers = iv test_obj.demandThreshold = 800 # Run the test. test_obj.update_dc_threshold(test_mkt) # Check that a new, higher demand threshold was set. if test_obj.demandThreshold != 900: pf = 'fail' print(['- the method failed to update the demand threshold value when there is a meter']) else: print('- the method properly updated the demand threshold value when there is a meter') ## Test rollover to new month # Configure the test object test_obj.demandMonth = dt + relativedelta.relativedelta(months=-1) # month(datetime - days(31)) # prior month test_obj.demandThreshold = 1000 test_obj.scheduledPowers[0].value = 900 test_obj.scheduledPowers[1].value = 900 test_obj.meterPoints = [] # MeterPoint.empty # Run the test test_obj.update_dc_threshold(test_mkt) # See if the demand threshold was reset at the new month. if test_obj.demandThreshold != 0.8 * 1000: pf = 'fail' print('- the method did not reduce the threshold properly in a new month') else: print('- the method reduced the threshold properly in a new month') # Success print('- the test ran to completion') print('Result: #s\n\n', pf)
def test_update_dc_threshold(): print('Running BulkSupplier_dc.test_update_dc_threshold()') # Basic configuration for tests: # Create a test object and initialize demand-related properties test_obj = BulkSupplier_dc() test_obj.demandMonth = datetime.now().month # month(datetime) test_obj.demandThreshold = 1000 # Create a test market test_mkt = Market() # Create and store two time intervals dt = datetime.now() at = dt dur = timedelta(hours=1) # Hours(1) mkt = test_mkt mct = dt st = dt ti = [TimeInterval(at, dur, mkt, mct, st)] st = st + dur ti.append(TimeInterval(at, dur, mkt, mct, st)) test_mkt.timeIntervals = ti # Test case when there is no MeterPoint object test_obj.demandThreshold = 1000 test_obj.demandMonth = datetime.now().month # month(datetime) test_obj.meterPoints = [] # MeterPoint.empty # Create and store a couple scheduled powers # iv(1) = IntervalValue(test_obj, ti[0], test_mkt, MeasurementType.ScheduledPower, 900) # iv(2) = IntervalValue(test_obj, ti[1], test_mkt, MeasurementType.ScheduledPower, 900) iv = [ IntervalValue(test_obj, ti[0], test_mkt, MeasurementType.ScheduledPower, 900), IntervalValue(test_obj, ti[1], test_mkt, MeasurementType.ScheduledPower, 900) ] test_obj.scheduledPowers = iv try: test_obj.update_dc_threshold(test_mkt) print('- the method ran without errors') except RuntimeWarning: print('- the method encountered errors when called') assert test_obj.demandThreshold == 1000, '- the method inferred the wrong demand threshold value' iv = [ IntervalValue(test_obj, ti[0], test_mkt, MeasurementType.ScheduledPower, 1100), IntervalValue(test_obj, ti[1], test_mkt, MeasurementType.ScheduledPower, 900) ] test_obj.scheduledPowers = iv try: test_obj.update_dc_threshold(test_mkt) print('- the method ran without errors when there is no meter') except RuntimeWarning: print('- the method encountered errors when there is no meter') assert test_obj.demandThreshold == 1100, '- the method did not update the inferred demand threshold value' # Test with an appropriate MeterPoint meter # Create and store a MeterPoint test object test_mtr = MeterPoint() test_mtr.measurementType = MeasurementType.AverageDemandkW test_mtr.currentMeasurement = 900 test_obj.meterPoints = [test_mtr] # Reconfigure the test object for this test: iv = [ IntervalValue(test_obj, ti[0], test_mkt, MeasurementType.ScheduledPower, 900), IntervalValue(test_obj, ti[1], test_mkt, MeasurementType.ScheduledPower, 900) ] test_obj.scheduledPowers = iv test_obj.demandThreshold = 1000 test_obj.demandMonth = datetime.now().month # Run the test. Confirm it runs. try: test_obj.update_dc_threshold(test_mkt) print('- the method ran without errors when there is a meter') except RuntimeWarning: print('- the method encountered errors when there is a meter') # Check that the old threshold is correctly retained. assert test_obj.demandThreshold == 1000, \ '- the method failed to keep the correct demand threshold value when there is a meter' # Reconfigure the test object with a lower current threshold iv = [ IntervalValue(test_obj, ti[0], test_mkt, MeasurementType.ScheduledPower, 900), IntervalValue(test_obj, ti[1], test_mkt, MeasurementType.ScheduledPower, 900)] test_obj.scheduledPowers = iv test_obj.demandThreshold = 800 # Run the test. test_obj.update_dc_threshold(test_mkt) # Check that a new, higher demand threshold was set. assert test_obj.demandThreshold == 900, \ '- the method failed to update the demand threshold value when there is a meter' # Test rollover to new month # Configure the test object last_month = dt.month - 1 if last_month == 0: last_month = 12 test_obj.demandMonth = last_month # month(datetime - days(31)) # prior month test_obj.demandThreshold = 1000 test_obj.scheduledPowers[0].value = 900 test_obj.scheduledPowers[1].value = 900 test_obj.meterPoints = [] # MeterPoint.empty test_obj.demandThresholdCoef = 0.8 # Run the test try: test_obj.update_dc_threshold(test_mkt) print(' - The method ran without errors') except RuntimeWarning: print(' - ERRORS ENCOUNTERED') # See if the demand threshold was reset at the new month. assert test_obj.demandThreshold == test_obj.demandThresholdCoef * 1000, \ '- the method did not reduce the threshold properly in a new month' # Success print('test_update_dc_threshold() ran to completion.\n')