def test_update_vertices():
    # TEST_UPDATE_VERTICES() - test method update_vertices(), which for this
    # base class of LocalAssetModel does practically nothing and must be
    # redefined by child classes that represent flesible assets.
    print('Running LocalAssetModel.test_update_vertices()')
    pf = 'pass'

    #   Create a test Market object.
    test_market = Market

    #   Create and store a TimeInterval object.
    dt = datetime.now()  # datetime that may be used for most datetime arguments
    time_interval = TimeInterval(dt, timedelta(hours=1), test_market, dt, dt)
    test_market.timeIntervals = [time_interval]

    #   Create a test LocalAssetModel object.
    test_model = LocalAssetModel()

    #   Create and store a scheduled power IntervalValue in the active time interval.
    test_model.scheduledPowers = [
        IntervalValue(test_model, time_interval, test_market, MeasurementType.ScheduledPower, 50)]

    #   Create a LocalAsset object and its maximum and minimum powers.
    test_object = LocalAsset()
    test_object.maximumPower = 200
    test_object.minimumPower = 0

    #   Have the LocalAsset model and object cross reference one another.
    test_object.model = test_model
    test_model.object = test_object

    ## TEST 1
    print('- Test 1: Basic operation')

    test_model.update_vertices(test_market)
    print('  - the method ran without errors')

    if len(test_model.activeVertices) != 1:
        pf = 'fail'
        print('  - there is an unexpected number of active vertices')
    else:
        print('  - the expected number of active vertices was found')

    # Success.
    print('- the test ran to completion')
    print('\nResult: #s\n\n', pf)
Exemplo n.º 2
0
    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])
Exemplo n.º 3
0
    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)
Exemplo n.º 4
0
    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_calculate_reserve_margin():
    # TEST_LAM_CALCULATE_RESERVE_MARGIN() - a LocalAssetModel ("LAM") class
    # method NOTE: Reserve margins are introduced but not fully integrated into
    # code in early template versions.
    # CASES:
    # 1. uses hard maximum if no active vertices exist
    # 2. vertices exist
    # 2.1 uses maximum vertex power if it is less than hard power constraint
    # 2.2 uses hard constraint if it is less than maximum vertex power
    # 2.3 upper flex power is greater than scheduled power assigns correct
    # positive reserve margin
    # 2.4 upperflex power less than scheduled power assigns zero value to
    # reserve margin.

    print('Running LocalAssetModel.test_calculate_reserve_margin()')

    pf = 'pass'

    # Establish test market
    test_mkt = Market()

    # Establish test market with an active time interval
    # Note: modified 1/29/18 due to new TimeInterval constructor
    dt = datetime.now()
    at = dt
    # NOTE: def Hours() corrects behavior of Matlab hours().
    dur = timedelta(hours=1)
    mkt = test_mkt
    mct = dt
    # st = datetime(date)
    st = datetime.combine(date.today(), time())

    ti = TimeInterval(at, dur, mkt, mct, st)

    # Store time interval
    test_mkt.timeIntervals = [ti]

    # Establish a test object that is a LocalAsset with assigned maximum power
    test_object = LocalAsset()
    test_object.maximumPower = 100

    # Establish test object that is a LocalAssetModel
    test_model = LocalAssetModel()
    test_model.scheduledPowers = [
        IntervalValue(test_model, ti, test_mkt, MeasurementType.ScheduledPower, 0.0)]

    # Allow object and model to cross-reference one another.
    test_object.model = test_model
    test_model.object = test_object

    # Run the first test case.
    test_model.calculate_reserve_margin(test_mkt)
    print('- method ran without errors')

    if len(test_model.reserveMargins) != 1:
        raise Exception('- an unexpected number of results were stored')
    else:
        print('- one reserve margin was stored, as expected')

    if test_model.reserveMargins[0].value != test_object.maximumPower:
        pf = 'fail'
        raise Exception('- the method did not use the available maximum power')
    else:
        print('- the method used maximum power value, as expected')

    # create some vertices and store them
    iv = [
        IntervalValue(test_model, ti, test_mkt, MeasurementType.Vertex, Vertex(0, 0, -10)),
        IntervalValue(test_model, ti, test_mkt, MeasurementType.Vertex, Vertex(0, 0, 10))
    ]
    test_model.activeVertices = iv

    # run test with maximum power greater than maximum vertex
    test_object.maximumPower = 100
    test_model.calculate_reserve_margin(test_mkt)

    if test_model.reserveMargins[0].value != 10:
        pf = 'fail'
        raise Exception('- the method should have used vertex for comparison')
    else:
        print('- the method correctly chose to use the vertex power')

    # run test with maximum power less than maximum vertex
    test_object.maximumPower = 5
    test_model.calculate_reserve_margin(test_mkt)

    if test_model.reserveMargins[0].value != 5:
        pf = 'fail'
        raise Exception('- method should have used maximum power for comparison')
    else:
        print('- the method properly chose to use the maximum power')

    # run test with scheduled power greater than maximum vertex
    test_model.scheduledPowers[0].value = 20
    test_object.maximumPower = 500
    test_model.calculate_reserve_margin(test_mkt)

    if test_model.reserveMargins[0].value != 0:
        pf = 'fail'
        raise Exception('- method should have assigned zero for a neg. result')
    else:
        print('- the method properly assigned 0 for a negative result')

    # Success.
    print('- the test ran to completion')
    print('\nResult: #s\n\n', pf)
Exemplo n.º 6
0
def test_schedule():
    print('Running AbstractModel.test_schedule()')
    pf = 'pass'

    #   Create a test market test_mkt
    test_mkt = Market()

    #   Create a sample time interval ti
    dt = datetime.now()
    at = dt
    # NOTE: Function Hours() corrects behavior of Matlab hours().
    dur = timedelta(hours=1)
    mkt = test_mkt
    mct = dt
    # NOTE: Function Hours() corrects behavior of Matlab hours().
    st = datetime.combine(date.today(), time()) + timedelta(hours=20)
    ti = TimeInterval(at, dur, mkt, mct, st)

    #   Save the time interval
    test_mkt.timeIntervals = [ti]

    #   Assign a marginal price in the time interval
    test_mkt.check_marginal_prices()

    #   Create a Neighbor test object and give it a default maximum power value
    test_obj = Neighbor()
    test_obj.maximumPower = 100

    #   Create a corresponding NeighborModel
    test_mdl = NeighborModel()

    #   Make sure that the model and object cross-reference one another
    test_obj.model = test_mdl
    test_mdl.object = test_obj

    #   Run a test with a NeighborModel object
    print('- running test with a NeighborModel:')

    test_mdl.schedule(test_mkt)
    print('  - the method encountered no errors')

    if len(test_mdl.scheduledPowers) != 1:
        pf = 'fail'
        raise '  - the method did not store a scheduled power'
    else:
        print('  - the method calculated and stored a scheduled power')

    if len(test_mdl.reserveMargins) != 1:
        pf = 'fail'
        raise '  - the method did not store a reserve margin'
    else:
        print('  - the method stored a reserve margin')

    if len(test_mdl.activeVertices) != 1:
        pf = 'fail'
        raise '  - the method did not store an active vertex'
    else:
        print('  - the method stored an active vertex')

    # Run a test again with a LocalAssetModel object
    test_obj = LocalAsset()
    test_obj.maximumPower = 100
    test_mdl = LocalAssetModel()
    test_obj.model = test_mdl
    test_mdl.object = test_obj

    print('- running test with a LocalAssetModel:')

    test_mdl.schedule(test_mkt)
    print('  - the method encountered no errors')

    if len(test_mdl.scheduledPowers) != 1:
        pf = 'fail'
        raise '  - the method did not store a scheduled power'
    else:
        print('  - the method calculated and stored a scheduled power')

    if len(test_mdl.reserveMargins) != 1:
        pf = 'fail'
        raise '  - the method did not store a reserve margin'
    else:
        print('  - the method stored a reserve margin')

    if len(test_mdl.activeVertices) != 1:
        pf = 'fail'
        raise '  - the method did not store an active vertex'
    else:
        print('  - the method stored an active vertex')

    # Success
    print('- the test ran to completion')
    print('Result: %s', pf)
Exemplo n.º 7
0
def test_update_costs():
    print('Running AbstractModel.test_update_costs()')

    pf = 'pass'

    #   Create a test market test_mkt
    test_mkt = Market()

    #   Create a sample time interval ti
    dt = datetime.now()
    at = dt
    #   NOTE: Function Hours() corrects behavior of Matlab hours().
    dur = timedelta(hours=1)
    mkt = test_mkt
    mct = dt
    st = datetime.combine(date.today(), time()) + timedelta(hours=20)
    ti = TimeInterval(at, dur, mkt, mct, st)

    #   Save the time interval
    test_mkt.timeIntervals = [ti]

    #   Assign a marginal price in the time interval
    test_mkt.check_marginal_prices()

    #   Create a Neighbor test object and give it a default maximum power value
    test_obj = Neighbor()
    #     test_obj.maximumPower = 100

    #   Create a corresponding NeighborModel
    test_mdl = NeighborModel()

    #   Make sure that the model and object cross-reference one another
    test_obj.model = test_mdl
    test_mdl.object = test_obj

    test_mdl.scheduledPowers = [
        IntervalValue(test_mdl, ti, test_mkt, MeasurementType.ScheduledPower,
                      100)
    ]
    test_mdl.activeVertices = [
        IntervalValue(test_mdl, ti, test_mkt, MeasurementType.ActiveVertex,
                      Vertex(0.05, 0, 100))
    ]

    #   Run a test with a NeighborModel object
    print('- running test with a NeighborModel:')
    try:
        test_mdl.update_costs(test_mkt)
        print('  - the method encountered no errors')
    except:
        pf = 'fail'
        raise '  - the method did not run without errors'

    if len(test_mdl.productionCosts) != 1:
        pf = 'fail'
        raise '  - the method did not store a production cost'
    else:
        print('  - the method calculated and stored a production cost')

    if len(test_mdl.dualCosts) != 1:
        pf = 'fail'
        raise '  - the method did not store a dual cost'
    else:
        print('  - the method stored a dual cost')

    if test_mdl.totalProductionCost != sum(
        [x.value for x in test_mdl.productionCosts]):
        pf = 'fail'
        raise '  - the method did not store a total production cost'
    else:
        print('  - the method stored an total production cost')

    if test_mdl.totalDualCost != sum([x.value for x in test_mdl.dualCosts]):
        pf = 'fail'
        raise '  - the method did not store a total dual cost'
    else:
        print('  - the method stored an total dual cost')

    # Run a test again with a LocalAssetModel object
    test_obj = LocalAsset()
    #     test_obj.maximumPower = 100
    test_mdl = LocalAssetModel()
    test_obj.model = test_mdl
    test_mdl.object = test_obj

    test_mdl.scheduledPowers = [
        IntervalValue(test_mdl, ti, test_mkt, MeasurementType.ScheduledPower,
                      100)
    ]
    test_mdl.activeVertices = [
        IntervalValue(test_mdl, ti, test_mkt, MeasurementType.ActiveVertex,
                      Vertex(0.05, 0, 100))
    ]

    print('- running test with a LocalAssetModel:')

    try:
        test_mdl.update_costs(test_mkt)
        print('  - the method encountered no errors')
    except:
        pf = 'fail'
        raise '  - the method did not run without errors'

    if len(test_mdl.productionCosts) != 1:
        pf = 'fail'
        raise '  - the method did not store a production cost'
    else:
        print('  - the method calculated and stored a production cost')

    if len(test_mdl.dualCosts) != 1:
        pf = 'fail'
        raise '  - the method did not store a dual cost'
    else:
        print('  - the method stored a dual cost')

    if test_mdl.totalProductionCost != sum(
        [x.value for x in test_mdl.productionCosts]):
        pf = 'fail'
        raise '  - the method did not store a total production cost'
    else:
        print('  - the method stored an total production cost')

    if test_mdl.totalDualCost != sum([x.value for x in test_mdl.dualCosts]):
        pf = 'fail'
        raise '  - the method did not store a total dual cost'
    else:
        print('  - the method stored an total dual cost')

    # Success
    print('- the test ran to completion')
    print('Result: %s', pf)
Exemplo n.º 8
0
def test_sum_vertices():
    print('Running Market.test_sum_vertices()')
    pf = 'pass'

    # Create a test myTransactiveNode object.
    test_node = myTransactiveNode()

    # Create a test Market object.
    test_market = Market()

    # List the test market with the test_node.
    test_node.markets = test_market

    # Create and store a time interval to work with.
    dt = datetime.now()
    at = dt
    dur = timedelta(hours=1)
    mkt = test_market
    mct = dt
    st = dt
    time_interval = TimeInterval(at, dur, mkt, mct, st)
    test_market.timeIntervals = [time_interval]

    # Create test LocalAsset and LocalAssetModel objects
    test_asset = LocalAsset()
    test_asset_model = LocalAssetModel()

    # Add the test_asset to the test node list.
    test_node.localAssets = [test_asset]

    # Have the test asset and its model cross reference one another.
    test_asset.model = test_asset_model
    test_asset_model.object = test_asset

    # Create and store an active Vertex or two for the test asset
    test_vertex = [Vertex(0.2, 0, -110), Vertex(0.2, 0, -90)]
    interval_values = [
        IntervalValue(test_node, time_interval, test_market,
                      MeasurementType.ActiveVertex, test_vertex[0]),
        IntervalValue(test_node, time_interval, test_market,
                      MeasurementType.ActiveVertex, test_vertex[1])
    ]
    test_asset_model.activeVertices = [interval_values[0], interval_values[1]
                                       ]  # interval_value(1:2)

    # Create test Neighbor and NeighborModel objects.
    test_neighbor = Neighbor()
    test_neighbor_model = NeighborModel()

    # Add the test neighbor to the test node list.
    test_node.neighbors = [test_neighbor]

    # Have the test neighbor and its model cross reference one another.
    test_neighbor.model = test_neighbor_model
    test_neighbor.model.object = test_neighbor

    # Create and store an active Vertex or two for the test neighbor
    test_vertex.append(Vertex(0.1, 0, 0))
    test_vertex.append(Vertex(0.3, 0, 200))
    interval_values.append(
        IntervalValue(test_node, time_interval, test_market,
                      MeasurementType.ActiveVertex, test_vertex[2]))
    interval_values.append(
        IntervalValue(test_node, time_interval, test_market,
                      MeasurementType.ActiveVertex, test_vertex[3]))
    test_neighbor_model.activeVertices = [
        interval_values[2], interval_values[3]
    ]

    ## Case 1
    print('- Case 1: Basic case with interleaved vertices')

    # Run the test.
    try:
        vertices = test_market.sum_vertices(test_node, time_interval)
        print('  - the method ran without errors')
    except:
        pf = 'fail'
        print('  - the method had errors when called and stopped')

    if len(vertices) != 4:
        pf = 'fail'
        print('  - an unexpected number of vertices was returned')
    else:
        print('  - the expected number of vertices was returned')

    powers = [x.power for x in vertices]

    # if any(~ismember(single(powers), single([-110.0000, -10.0000, 10.0000, 110.0000])))
    if len([
            x for x in powers
            if round(x, 4) not in [-110.0000, -10.0000, 10.0000, 110.0000]
    ]) > 0:
        pf = 'fail'
        print('  - the vertex powers were not as expected')
    else:
        print('  - the vertex powers were as expected')

    marginal_prices = [round(x.marginalPrice, 4) for x in vertices]

    # if any(~ismember(single(marginal_prices), single([0.1000, 0.2000, 0.3000])))
    if len([
            x for x in marginal_prices
            if round(x, 4) not in [0.1000, 0.2000, 0.3000]
    ]) > 0:
        pf = 'fail'
        print('  - the vertex powers were not as expected')
    else:
        print('  - the vertex marginal prices were as expected')

    ## CASE 2: NEIGHBOR MODEL TO BE EXCLUDED
    # This case is needed when a demand or supply curve must be created for a
    # transactive Neighbor object. The active vertices of the target Neighbor
    # must be excluded, leaving a residual supply or demand curve against which
    # the Neighbor may plan.
    print('- Case 2: Exclude test Neighbor model')

    # Run the test.
    try:
        # [vertices] = test_market.sum_vertices(test_node, time_interval, test_neighbor_model)
        vertices = test_market.sum_vertices(test_node, time_interval,
                                            test_neighbor_model)
        print('  - the method ran without errors')
    except:
        pf = 'fail'
        print('  - the method encountered errors and stopped')

    if len(vertices) != 2:
        pf = 'fail'
        print('  - an unexpected number of vertices was returned')
    else:
        print('  - the expected number of vertices was returned')

    powers = [round(x.power, 4) for x in vertices]

    # if any(~ismember(single(powers), single([-110.0000, -90.0000])))
    if len([x for x in powers if x not in [-110.0000, -90.0000]]) > 0:
        pf = 'fail'
        print('  - the vertex powers were not as expected')
    else:
        print('  - the vertex powers were as expected')

    marginal_prices = [x.marginalPrice for x in vertices]

    # if any(~ismember(single(marginal_prices), single([0.2000])))
    if len([x for x in marginal_prices if round(x, 4) not in [0.2000]]) > 0:
        pf = 'fail'
        print('  - the vertex powers were not as expected')
    else:
        print('  - the vertex marginal prices were as expected')

    ## CASE 3: CONSTANT SHOULD NOT CREATE NEW NET VERTEX
    print('- Case 3: Include a constant vertex. No net vertex should be added')

    # Change the test asset to NOT have any flexibility. A constant should
    # not introduce a net vertex at a constant's marginal price. Marginal
    # price is NOT meaningful for an inelastic device.
    test_asset_model.activeVertices = [interval_values[0]]

    # Run the test.
    try:
        # [vertices] = test_market.sum_vertices(test_node, time_interval)
        vertices = test_market.sum_vertices(test_node, time_interval)
        print('  - the method ran without errors')
    except:
        pf = 'fail'
        print('  - the method encountered errors and stopped')

    #%[180907DJH: THIS TEST IS CORRECTED. THE NEIGHBOR HAS TWO VERTICES. ADDING
    #AN ASSET WITH ONE VERTEX (NO FLEXIBILITY) SHOULD NOT CHANGE THE NUMBER OF
    #ACTIVE VERTICES, SO THE CORRECTED TEST CONFIRMS TWO VERTICES. THE CODE HAS
    #BEEN CORRECTED ACCORDINGLY.]
    if len(vertices) != 2:
        pf = 'fail'
        print('  - an unexpected number of vertices was returned')
    else:
        print('  - the expected number of vertices was returned')

    powers = [x.power for x in vertices]

    # if any(~ismember(single(powers), single([-110.0000, 90])))
    if len([x for x in powers if round(x, 4) not in [-110.0000, 90]]) > 0:
        pf = 'fail'
        print('  - the vertex powers were not as expected')
    else:
        print('  - the vertex powers were as expected')

    marginal_prices = [x.marginalPrice for x in vertices]

    # if any(~ismember(single(marginal_prices), single([0.1000, 0.3000, Inf])))
    if len([
            x for x in marginal_prices if round(x, 4) not in
        [0.1000, 0.3000, float("inf")]
    ]) > 0:
        pf = 'fail'
        print('  - the vertex powers were not as expected')
    else:
        print('  - the vertex marginal prices were as expected')

    # CASE 4: More than two vertices at any marginal price
    print('- Case 4: More than two vertices at same marginal price')

    # Move the two active vertices of the test asset to be at the same
    # marginal price as one of the neighbor active vertices.
    test_vertex = [Vertex(0.1, 0, -110), Vertex(0.1, 0, -90)]
    interval_values = [
        IntervalValue(test_node, time_interval, test_market,
                      MeasurementType.ActiveVertex, test_vertex[0]),
        IntervalValue(test_node, time_interval, test_market,
                      MeasurementType.ActiveVertex, test_vertex[1])
    ]
    test_asset_model.activeVertices = [interval_values[0], interval_values[1]
                                       ]  # interval_value(1:2)

    # Run the test.
    try:
        vertices = test_market.sum_vertices(test_node, time_interval)
        print('  - the method ran without errors')
    except:
        pf = 'fail'
        print('  - the method encountered errors and stopped')

    if len(vertices) != 3:
        pf = 'fail'
        print('  - an unexpected number of vertices was returned')
    else:
        print('  - the expected number of vertices was returned')

    powers = [x.power for x in vertices]

    # if any(~ismember(single(powers), single([-110.0000, -90.0000, 110.0000])))
    if len([
            x for x in powers
            if round(x, 4) not in [-110.0000, -90.0000, 110.0000]
    ]) > 0:
        pf = 'fail'
        print('  - the vertex powers were not as expected')
    else:
        print('  - the vertex powers were as expected')

    marginal_prices = [x.marginalPrice for x in vertices]

    # if any(~ismember(single(marginal_prices), single([0.1000, 0.3000])))
    if len([x for x in marginal_prices if round(x, 4) not in [0.1000, 0.3000]
            ]) > 0:
        pf = 'fail'
        print('  - the vertex powers were not as expected')
    else:
        print('  - the vertex marginal prices were as expected')

    # Success
    print('- the test ran to completion')
    print('Result: #s\n\n', pf)