Example #1
0
    def get_forecast_weatherservice(self, mkt):
        """
        Uses VOLTTRON DarkSky weather agent running on local or remote platform to
        get 24 hour forecast for weather data.
        :param mkt:
        :return:
        """
        weather_results = None
        weather_data = None
        try:
            result = self.parent.vip.rpc.call(self.weather_vip,
                                              "get_hourly_forecast",
                                              self.location,
                                              external_platform=self.remote_platform).get(timeout=15)
            weather_results = result[0]["weather_results"]

        except (gevent.Timeout, RemoteError) as ex:
            _log.warning("RPC call to {} failed for weather forecast: {}".format(self.weather_vip, ex))

        if weather_results is not None:
            try:
                weather_data = [[parser.parse(oat[0]).astimezone(self.localtz), oat[1][self.oat_point_name]] for oat in weather_results]
                weather_data = [[oat[0].replace(tzinfo=None), oat[1]] for oat in weather_data]
            except KeyError:
                if not self.predictedValues:
                    raise Exception("Measurement Point Name is not correct")

            # How do we deal with never getting weather information?  Exit?
            except Exception as ex:
                if not self.predictedValues:
                    raise Exception("Exception {} processing weather data.".format(ex))

        # Copy weather data to predictedValues
        if weather_data is not None:
            self.predictedValues = []
            for ti in mkt.timeIntervals:
                # Find item which has the same timestamp as ti.timeStamp
                start_time = ti.startTime.replace(minute=0)
                items = [x[1] for x in weather_data if x[0] == start_time]

                # Create interval value and add it to predicted values
                if items:
                    temp = items[0]
                    interval_value = IntervalValue(self, ti, mkt, MeasurementType.PredictedValue, temp)
                    self.predictedValues.append(interval_value)
        elif self.predictedValues:
            hour_gap = mkt.timeIntervals[0].startTime - self.predictedValues[0].timeInterval.startTime
            max_hour_gap = timedelta(hours=4)
            if hour_gap > max_hour_gap:
                self.predictedValues = []
                raise Exception('No weather data for time: {}'.format(utils.format_timestamp(mkt.timeIntervals[0].startTime)))
            else:
                predictedValues = []
                for i in range(1, len(mkt.timeIntervals)):
                    interval_value = IntervalValue(self, mkt.timeIntervals[i], mkt, MeasurementType.PredictedValue, self.predictedValues[i-1].value)
                    predictedValues.append(interval_value)
                self.predictedValues = predictedValues
        else:
            raise Exception(
                'No weather data for time: {}'.format(utils.format_timestamp(mkt.timeIntervals[0].startTime)))
    def update_information(ism, mkt):
        #   Gather active time intervals ti
        ti = mkt.timeIntervals

        #   index through active time intervals ti
        for i in range(len(ti)):  # for i = 1:length(ti)
            #       Get the start time for the indexed time interval
            st = ti(i).startTime

            #       Extract the starting time hour
            hr = st.hour

            #       Look up the value in a table. NOTE: tables may be used during
            #       development until active information services are developed.
            # Is the purpose of this one to read MODERATE weather temperature? YES
            T = readtable(ism.file)
            value = T(hr + 1, 1)

            #       Check whether the information exists in the indexed time interval
            # Question: what is ism? InformationServiceModel doesn't have 'values' as well as 'iv' properties.
            #   Suggestion: use long name as much as possible
            #   Need an example on what this one does. Really need a unit test here?
            #iv = findobj(ism.values, 'timeInterval', ti(i))
            iv = [x for x in ism.values if x.timeInterval.startTime == ti[i].startTime]  #
            iv = iv[0] if len(iv)>0 else None

            if iv is None:  # isempty(iv):
                # The value does not exist in the indexed time interval. Create it and store it.
                #iv = IntervalValue(ism, ti(i), mkt, 'Temperature', value)
                iv = IntervalValue(ism, ti[i], mkt, MeasurementType.Temperature, value)
                ism.values = [ism.values, iv]

            else:
                # The value exists in the indexed time interval. Simply reassign it
                iv.value = value
Example #3
0
    def update_dual_costs(self, mkt):
        # Update the dual cost for all active time intervals
        # (NOTE: Choosing not to separate this def from the base class because
        # cost might need to be handled differently and redefined in subclasses.)

        # Gather the active time intervals ti
        time_intervals = mkt.timeIntervals
        time_interval_values = [t.startTime for t in time_intervals]
        self.dualCosts = [
            x for x in self.dualCosts
            if x.timeInterval.startTime in time_interval_values
        ]

        # Index through the time intervals ti
        for i in range(1, len(time_intervals)):
            # Find the marginal price mp for the indexed time interval ti(i) in
            # the given market mkt
            mp = find_obj_by_ti(mkt.marginalPrices, time_intervals[i])
            mp = mp.value  # a marginal price [$/kWh]

            # Find the scheduled power sp for the asset in the indexed time interval ti(i)
            sp = find_obj_by_ti(self.scheduledPowers, time_intervals[i])
            sp = sp.value  # schedule power [avg.kW]

            # Find the production cost in the indexed time interval
            pc = find_obj_by_ti(self.productionCosts, time_intervals[i])
            pc = pc.value  # production cost [$]

            # Dual cost in the time interval is calculated as production cost,
            # minus the product of marginal price, scheduled power, and the
            # duration of the time interval.
            dur = time_intervals[i].duration.seconds // 3600
            dc = pc - (mp * sp * dur)  # a dual cost [$]

            # Check whether a dual cost exists in the indexed time interval
            iv = find_obj_by_ti(self.dualCosts, time_intervals[i])

            if iv is None:
                # No dual cost was found in the indexed time interval. Create an
                # interval value and assign it the calculated value.
                iv = IntervalValue(self, time_intervals[i], mkt,
                                   MeasurementType.DualCost, dc)

                # Append the new interval value to the list of active interval values
                self.dualCosts.append(iv)

            else:
                # The dual cost value was found to already exist in the indexed
                # time interval. Simply reassign it the new calculated value.
                iv.value = dc  # a dual cost [$]

        # Ensure that only active time intervals are in the list of dual costs adc
        #self.dualCosts = [x for x in self.dualCosts if x.timeInterval in ti]

        # Sum the total dual cost and save the value
        self.totalDualCost = sum([x.value for x in self.dualCosts])

        dc = [(x.timeInterval.name, x.value) for x in self.dualCosts]
        _log.debug("{} asset model dual costs are: {}".format(self.name, dc))
Example #4
0
    def update_vertices(self, mkt):
        # Create vertices to represent the asset's flexibility
        #
        # For the base local asset model, a single, inelastic power is needed.
        # There is no flexibility. The constant power may be represented by a
        # single (price, power) point (See struct Vertex).

        # Gather active time intervals
        ti = mkt.timeIntervals  # active TimeIntervals
        time_interval_values = [t.startTime for t in ti]
        self.activeVertices = [
            x for x in self.activeVertices
            if x.timeInterval.startTime in time_interval_values
        ]

        # Index through active time intervals ti
        for i in range(len(ti)):
            # Find the scheduled power for the indexed time interval
            # Extract the scheduled power value
            sp = find_obj_by_ti(self.scheduledPowers, ti[i])
            sp = sp.value  # avg. kW]

            # Create the vertex that can represent this (lack of) flexibility
            value = Vertex(float("inf"), 0.0, sp, True)

            # Check to see if the active vertex already exists for this indexed time interval.
            iv = find_obj_by_ti(self.activeVertices, ti[i])

            # If the active vertex does not exist, a new interval value must be
            # created and stored.
            if iv is None:
                # Create the interval value and place the active vertex in it
                iv = IntervalValue(self, ti[i], mkt,
                                   MeasurementType.ActiveVertex, value)

                # Append the interval value to the list of active vertices
                self.activeVertices.append(iv)

            else:
                # Otherwise, simply reassign the active vertex value to the
                # existing listed interval value. (NOTE that this base local
                # asset model unnecessarily reassigns constant values, but the
                # reassignment is allowed because it teaches how a more dynamic
                # assignment may be maintained.
                iv.value = value

        av = [(x.timeInterval.name, x.value.marginalPrice, x.value.power)
              for x in self.activeVertices]
        _log.debug("{} asset model active vertices are: {}".format(
            self.name, av))
Example #5
0
def test_view_marginal_prices():
    print('Running Market.test_view_marginal_prices()')
    pf = 'pass'

    # Establish a test market
    test_mkt = Market

    # Create and store three TimeIntervals
    dt = datetime
    at = dt
    dur = timedelta(hours=1)
    mkt = test_mkt
    mct = dt

    ti = []  # TimeInterval.empty

    st = dt
    ti[0] = TimeInterval(at, dur, mkt, mct, st)

    st = st + dur
    ti[1] = TimeInterval(at, dur, mkt, mct, st)

    st = st + dur
    ti[2] = TimeInterval(at, dur, mkt, mct, st)

    test_mkt.timeIntervals = ti

    ## Test using a Market object
    print('- using a Market object')

    iv = []  # IntervalValue.empty
    # Create and store three marginal price values
    iv[0] = IntervalValue(test_mkt, ti[2], test_mkt,
                          MeasurementType.MarginalPrice, 3)
    iv[1] = IntervalValue(test_mkt, ti[0], test_mkt,
                          MeasurementType.MarginalPrice, 1)
    iv[2] = IntervalValue(test_mkt, ti[1], test_mkt,
                          MeasurementType.MarginalPrice, 2)
    test_mkt.marginalPrices = iv

    try:
        test_mkt.view_marginal_prices()
        print('  - function ran without errors')
    except:
        raise ('  - function encountered errors and stopped')

    # Success
    print('- the test ran to completion')
    print('Result: #s\n\n', pf)
Example #6
0
    def update_vertices(self, mkt):
        if self.tcc_curves is None:
            super(TccModel, self).update_vertices(mkt)
        else:
            time_intervals = mkt.timeIntervals
            _log.debug("At {}, Tcc market has {} intervals".format(
                Timer.get_cur_time(), len(time_intervals)))

            # 1st mix-market doesn't have tcc_curves info => keep previous active vertices
            if self.tcc_curves[0] is None:
                first_interval_vertices = [
                    iv for iv in self.activeVertices
                    if iv.timeInterval.startTime == time_intervals[0].startTime
                ]
                self.activeVertices = first_interval_vertices

            # After 1st mix-market, we always have tcc_curves for 25 market intervals => clear all previous av
            else:
                self.activeVertices = []

            for i in range(len(time_intervals)):
                if self.tcc_curves[i] is None:
                    continue
                point1 = self.tcc_curves[i][0].tuppleize()
                q1 = -point1[0]
                p1 = point1[1]
                point2 = self.tcc_curves[i][1].tuppleize()
                q2 = -point2[0]
                p2 = point2[1]

                v1 = Vertex(p1, 0, q1)
                iv1 = IntervalValue(self, time_intervals[i], mkt,
                                    MeasurementType.ActiveVertex, v1)
                self.activeVertices.append(iv1)

                if q2 != q1:
                    v2 = Vertex(p2, 0, q2)
                    iv2 = IntervalValue(self, time_intervals[i], mkt,
                                        MeasurementType.ActiveVertex, v2)
                    self.activeVertices.append(iv2)

        av = [(x.timeInterval.name, x.value.marginalPrice, x.value.power)
              for x in self.activeVertices]
        _log.debug("TCC active vertices are: {}".format(av))
Example #7
0
    def schedule_engagement(self, mkt):
        # To assign engagement, or commitment, which
        # is relevant to some local assets (supports future capabilities).
        # NOTE: The assignment of engagement schedule, if used, may be assigned
        # during the scheduling of power, not separately as demonstrated here.
        # Commitment and engagement are closely aligned with the optimal
        # production costs of schedulable generators and utility def of
        # engagements (e.g., demand responses).

        # NOTE: Because this is a future capability, Implementers might choose to
        # simply return from the call until LocalAsset behaviors are found to need
        # commitment or engagement.

        # Gather the active time intervals ti
        time_intervals = mkt.timeIntervals  # active TimeIntervals
        time_interval_values = [t.startTime for t in time_intervals]
        self.engagementSchedule = [
            x for x in self.engagementSchedule
            if x.timeInterval.startTime in time_interval_values
        ]

        # Index through the active time intervals ti
        for i in range(len(time_intervals)):
            # Check whether an engagement schedule exists in the indexed time interval
            iv = find_obj_by_ti(self.engagementSchedule, time_intervals[i])

            # NOTE: this template currently assigns engagement value as true (i.e., engaged).
            val = True  # Asset is committed or engaged

            if iv is None:
                # No engagement schedule was found in the indexed time interval.
                # Create an interval value and assign its value.
                iv = IntervalValue(self, time_intervals[i], mkt,
                                   MeasurementType.EngagementValue,
                                   val)  # an IntervalValue

                # Append the interval value to the list of active interval values
                self.engagementSchedule.append(iv)

            else:
                # An engagement schedule was found in the indexed time interval.
                # Simpy reassign its value.
                iv.value = val  # [$]
Example #8
0
def test_view_net_curve():
    print('Running Market.test_view_net_curve()')
    pf = 'pass'

    # Establish a test market
    test_mkt = Market()

    # Create and store one TimeInterval
    dt = datetime(2018, 1, 1, 12, 0, 0)
    at = dt
    dur = timedelta(hours=1)
    mkt = test_mkt
    mct = dt
    st = dt
    ti = [TimeInterval(at, dur, mkt, mct, st)]

    test_mkt.timeIntervals = ti

    ## Test using a Market object
    print('- using a Market object')

    # Create and store three active vertices
    v = [Vertex(0.01, 0, -1), Vertex(0.02, 0, 1), Vertex(0.03, 0, 1)]
    iv = [
        IntervalValue(test_mkt, ti[0], test_mkt, MeasurementType.ActiveVertex,
                      v[2]),
        IntervalValue(test_mkt, ti[0], test_mkt, MeasurementType.ActiveVertex,
                      v[0]),
        IntervalValue(test_mkt, ti[0], test_mkt, MeasurementType.ActiveVertex,
                      v[1])
    ]
    test_mkt.activeVertices = [iv]

    test_mkt.view_net_curve(0)
    print('  - function ran without errors')

    # Success
    print('- the test ran to completion')
    print('Result: #s\n\n', pf)
Example #9
0
    def schedule_power(self, mkt):
        self.scheduledPowers = []
        time_intervals = mkt.timeIntervals
        for i in range(len(time_intervals)):
            value = self.defaultPower
            if self.quantities is not None and len(
                    self.quantities) > i and self.quantities[i] is not None:
                value = -self.quantities[i]
            iv = IntervalValue(self, time_intervals[i], mkt,
                               MeasurementType.ScheduledPower, value)
            self.scheduledPowers.append(iv)

        sp = [(x.timeInterval.name, x.value) for x in self.scheduledPowers]
        _log.debug("TCC scheduledPowers are: {}".format(sp))
Example #10
0
    def assign_system_vertices(self, mtn):
        # Collect active vertices from neighbor and asset models
        # and reassign them with aggregate system information for all active time intervals.
        #
        # ASSUMPTIONS:
        # - Active time intervals exist and are up-to-date
        # - Local convergence has occurred, meaning that power balance, marginal
        # price, and production costs have been adequately resolved from the
        # local agent's perspective
        # - The active vertices of local asset models exist and are up-to-date.
        # The vertices represent available power flexibility. The vertices
        # include meaningful, accurate production-cost information.
        # - There is agreement locally and in the network concerning the format
        # and content of transactive records
        # - Calls method mkt.sum_vertices in each time interval.
        #
        # INPUTS:
        # mtn - myTransactiveNode object
        #
        # OUTPUTS:
        # - Updates mkt.activeVertices - vertices that define the net system
        # balance and flexibility. The meaning of the vertex properties are
        # - marginalPrice: marginal price [$/kWh]
        # - cost: total production cost at the vertex [$]. (A locally
        #   meaningful blended electricity price is (total production cost /
        #   total production)).
        # - power: system net power at the vertex (The system "clears" where
        #   system net power is zero.)

        time_interval_values = [t.startTime for t in self.timeIntervals]
        # Delete any active vertices that are not in active time intervals. This
        # prevents time intervals from accumulating indefinitely.
        self.activeVertices = [x for x in self.activeVertices if x.timeInterval.startTime in time_interval_values]

        for ti in self.timeIntervals:
            # Find and delete existing aggregate active vertices in the indexed
            # time interval. These shall be recreated.
            self.activeVertices = [x for x in self.activeVertices if x.timeInterval.startTime != ti.startTime]

            # Call the utility method mkt.sum_vertices to recreate the
            # aggregate vertices in the indexed time interval. (This method is
            # separated out because it will be used by other methods.)
            s_vertices = self.sum_vertices(mtn, ti)

            # Create and store interval values for each new aggregate vertex v
            for sv in s_vertices:
                iv = IntervalValue(self, ti, self, MeasurementType.SystemVertex, sv)
                self.activeVertices.append(iv)
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)
Example #12
0
    def get_forecast_file(self, mkt):
        self.init_weather_data()
        # Copy weather data to predictedValues
        self.predictedValues = []
        for ti in mkt.timeIntervals:
            # Find item which has the same timestamp as ti.timeStamp
            start_time = ti.startTime.replace(minute=0)
            items = [x for x in self.weather_data if x['Timestamp'] == start_time]
            if len(items) == 0:
                trial_deltas = [-1, 1, -2, 2, -24, 24]
                for delta in trial_deltas:
                    items = [x for x in self.weather_data if x['Timestamp'] == (start_time - timedelta(hours=delta))]
                    if len(items) > 0:
                        break

                # None exist, raise exception
                if len(items) == 0:
                    raise Exception('No weather data for time: {}'.format(utils.format_timestamp(ti.startTime)))

            # Create interval value and add it to predicted values
            temp = items[0]['Value']
            interval_value = IntervalValue(self, ti, mkt, MeasurementType.PredictedValue, temp)
            self.predictedValues.append(interval_value)
Example #13
0
    def schedule_power(self, mkt):
        # Determine powers of an asset in active time
        # intervals. NOTE that this method may be redefined by subclasses if more
        # features are needed. NOTE that this method name is common for all asset
        # and neighbor models to facilitate its redefinition.
        #
        # PRESUMPTIONS:
        # - Active time intervals exist and have been updated
        # - Marginal prices exist and have been updated. NOTE: Marginal prices
        #   are not used for inelastic assets.
        # - Transition costs, if relevant, are applied during the scheduling
        #   of assets.
        # - An engagement schedule, if used, is applied during an asset's power
        #   scheduling.
        # - Scheduled power and engagement schedule must be self consistent at
        # the end of this method. That is, power should not be scheduled while
        # the asset is disengaged (uncommitted).
        #
        # INPUTS:
        # mkt - market object
        #
        # OUTPUTS:
        # - Updates self.scheduledPowers - the schedule of power consumed
        # - Updates self.engagementSchedule - an array that states whether the
        #   asset is engaged (committed) (true) or not (false) in the time interval

        # Gather the active time intervals ti
        time_intervals = mkt.timeIntervals
        time_interval_values = [t.startTime for t in time_intervals]
        self.scheduledPowers = [
            x for x in self.scheduledPowers
            if x.timeInterval.startTime in time_interval_values
        ]

        time_intervals.sort(key=lambda x: x.startTime)

        len_powers = len(self.default_powers)
        default_value = self.defaultPower

        # Index through the active time intervals ti
        for i in range(len(time_intervals)):
            # Check whether a scheduled power already exists for the indexed time interval
            iv = find_obj_by_ti(self.scheduledPowers, time_intervals[i])

            # Reassign default value if there is power value for this time interval
            if len_powers > i:
                default_value = self.default_powers[i] if self.default_powers[
                    i] is not None else self.defaultPower

            if iv is None:  # A scheduled power does not exist for the indexed time interval
                # Create an interval value and assign the default value
                iv = IntervalValue(self, time_intervals[i], mkt,
                                   MeasurementType.ScheduledPower,
                                   default_value)

                # Append the new scheduled power to the list of scheduled
                # powers for the active time intervals
                self.scheduledPowers.append(iv)

            else:
                # The scheduled power already exists for the indexed time
                # interval. Simply reassign its value
                iv.value = default_value  # [avg.kW]

        sp = [(x.timeInterval.name, x.value) for x in self.scheduledPowers]
        _log.debug("{} asset model scheduledPowers are: {}".format(
            self.name, sp))
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)
def test_update_production_costs():
    # TEST_UPDATE_PRODUCTION_COSTS() - test method update_production_costs()
    # that calculates production costs from active vertices and scheduled
    # powers.
    # NOTE: This test is virtually identical to the NeighborModel test of the
    # same name.
    print('Running LocalAssetModel.test_update_production_costs()')
    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 and store some active vertices IntervalValue objects in the
    #   active time interval.
    vertices = [
        Vertex(0.1, 1000, 0),
        Vertex(0.2, 1015, 100)
    ]
    interval_values = [
        IntervalValue(test_model, time_interval, test_market, MeasurementType.ActiveVertex, vertices[0]),
        IntervalValue(test_model, time_interval, test_market, MeasurementType.ActiveVertex, vertices[1])
    ]
    test_model.activeVertices = interval_values

    # TEST 1
    print('- Test 1: First calculation of a production cost')

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

    if len(test_model.productionCosts) != 1:
        pf = 'fail'
        print('  - the wrong number of production costs was created')
    else:
        print('  - the right number of production cost values was created')

    production_cost = test_model.productionCosts[0].value

    if float(production_cost) != float(1007.5):
        pf = 'fail'
        print('  - an unexpected production cost value was found')
    else:
        print('  - the expected production cost value was found')

    # TEST 2
    print('- Test 2: Reassignment of an existing production cost')

    #   Configure the test by modifying the scheduled power value.
    test_model.scheduledPowers[0].value = 150

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

    if len(test_model.productionCosts) != 1:
        pf = 'fail'
        print('  - the wrong number of productions was created')
    else:
        print('  - the right number of production cost values was created')

    production_cost = test_model.productionCosts[0].value

    if float(production_cost) != float(1015):
        pf = 'fail'
        print('  - an unexpected dual cost value was found')
    else:
        print('  - the expected dual cost value was found')

    # Success.
    print('- the test ran to completion')
    print('\nResult: #s\n\n', pf)
Example #16
0
def test_prod_cost_from_vertices():
    from local_asset_model import LocalAssetModel

    # TEST_PROD_COST_FROM_VERTICES - tests function prod_cost_from_vertices()
    print('Running test_prod_cost_from_vertices()')
    pf = 'pass'

    # Create a test object
    test_object = LocalAssetModel

    # Create a test market
    test_market = Market

    # Create several active vertices av
    av = [Vertex(0.02, 5, 0), Vertex(0.02, 7, 100), Vertex(0.025, 9.25, 200)]

    # Create a time interval
    dt = datetime.now()
    at = dt
    #   NOTE: Function Hours() corrects behavior of Matlab function hours().
    dur = timedelta(hours=1)
    mkt = test_market
    mct = dt
    st = dt
    ti = TimeInterval(at, dur, mkt, mct, st)

    # Create and store the activeVertices, which are IntervalValues
    test_object.activeVertices = [
        IntervalValue(test_object, ti, test_market,
                      MeasurementType.ActiveVertex, av[0]),
        IntervalValue(test_object, ti, test_market,
                      MeasurementType.ActiveVertex, av[1]),
        IntervalValue(test_object, ti, test_market,
                      MeasurementType.ActiveVertex, av[2])
    ]

    ## CASE: Various signed powers when there is more than one vertex
    test_powers = [-50, 0, 50, 150, 250]
    pc = []
    for p in test_powers:
        pc.append(prod_cost_from_vertices(test_object, ti, p))

    # pc(1) = 0: value is always 0 for power < 0
    # pc(2) = 5.0: assign cost from first vertex
    # pc(3) = 6.0: interpolate between vertices
    # pc(4) = 8.125: interpolate between vertices
    # pc(5) = 9.25: use last vertex cost if power > last vertex power

    #if ~all(pc == [0, 5.0, 6.0, 8.125, 9.25])
    expected = [0, 5.0, 6.0, 8.125, 9.25]
    if not all([pc[i] == expected[i] for i in range(len(pc))]):
        pf = 'fail'
        raise Exception('- the production cost was incorrectly calculated')
    else:
        print('- the production cost was correctly calculated')

    ## CASE: One vertex (inelastic case, a constant)
    test_object.activeVertices = [
        IntervalValue(test_object, ti, test_market,
                      MeasurementType.ActiveVertex, av[0])
    ]

    #pc[i] = prod_cost_from_vertices(test_object, ti, test_powers[i])
    pc = []
    for p in test_powers:
        pc.append(prod_cost_from_vertices(test_object, ti, p))

    expected = [0.0, 5.0, 5.0, 5.0, 5.0]
    #if ~all(pc == [0.0, 5.0, 5.0, 5.0, 5.0])
    if not all([pc[i] == expected[i] for i in range(len(pc))]):
        pf = 'fail'
        raise Exception(
            '- made an incorrect assignment when there is one vertex')
    else:
        print('- made a correct assignment when there is one vertex')

    ## CASE: No active vertices (error case):
    test_object.activeVertices = []

    #print('off', 'all')
    try:
        pc = prod_cost_from_vertices(test_object, ti, test_powers[4])
        pf = 'fail'
        #print('on', 'all')
        raise Exception(
            '- the function should have warned and continued when there were no active vertices'
        )
    except:
        print(
            '- the function returned gracefully when there were no active vertices'
        )
        #print('on', 'all')

    #   Success
    print('- the test function ran to completion')
    print('Result: #s\n\n', pf)
Example #17
0
    def update_production_costs(self, mkt):
        # Calculate the costs of generated energies.
        # (NOTE: Choosing not to separate this def from the base class because
        # cost might need to be handled differently and redefined in subclasses.)

        # Gather active time intervals ti
        time_intervals = mkt.timeIntervals
        time_interval_values = [t.startTime for t in time_intervals]
        self.productionCosts = [
            x for x in self.productionCosts
            if x.timeInterval.startTime in time_interval_values
        ]

        # Index through the active time interval ti
        for i in range(1, len(time_intervals)):
            # Get the scheduled power sp in the indexed time interval
            sp = find_obj_by_ti(self.scheduledPowers, time_intervals[i])
            sp = sp.value  # schedule power [avg.kW]

            # Call on def that calculates production cost pc based on the
            # vertices of the supply or demand curve
            # NOTE that this def is now stand-alone because it might be
            # generally useful for a number of models.
            pc = prod_cost_from_vertices(self, time_intervals[i],
                                         sp)  # interval production cost [$]

            # Check for a transition cost in the indexed time interval.
            # (NOTE: this differs from neighbor models, which do not posses the
            # concept of commitment and engagement. This is a good reason to keep
            # this method within its base class to allow for subtle differences.)
            tc = find_obj_by_ti(self.transitionCosts, time_intervals[i])

            if tc is None:
                tc = 0.0  # [$]
            else:
                tc = tc.value  # [$]

            # Add the transition cost to the production cost
            pc = pc + tc

            # Check to see if the production cost value has been defined for the
            # indexed time interval
            iv = find_obj_by_ti(self.productionCosts, time_intervals[i])

            if iv is None:
                # The production cost value has not been defined in the indexed
                # time interval. Create it and assign its value pc.
                iv = IntervalValue(self, time_intervals[i], mkt,
                                   MeasurementType.ProductionCost, pc)

                # Append the production cost to the list of active production
                # cost values
                self.productionCosts.append(iv)  # IntervalValues

            else:
                # The production cost value already exists in the indexed time
                # interval. Simply reassign its value.
                iv.value = pc  # interval production cost [$]

        # Ensure that only active time intervals are in the list of active
        # production costs apc
        #self.productionCosts = [x for x in self.productionCosts if x.timeInterval in time_intervals]

        # Sum the total production cost
        self.totalProductionCost = sum([x.value for x in self.productionCosts
                                        ])  # total production cost [$]

        pc = [(x.timeInterval.name, x.value) for x in self.productionCosts]
        _log.debug("{} asset model production costs are: {}".format(
            self.name, pc))
Example #18
0
    def assign_transition_costs(self, mkt):
        # Assign the cost of changeing
        # engagement state from the prior to the current time interval
        #
        # PRESUMPTIONS:
        # - Time intervals exist and have been updated
        # - The engagement schedule exists and has been updated. Contents are
        #   logical [true/false].
        # - Engagement costs have been accurately assigned for [disengagement,
        #   unchanged, engagement]
        #
        # INPUTS:
        # mkt - Market object
        #
        # USES:
        # - self.engagementCost - three costs that correspond to
        #   [disengagement, unchanged, engagement) transitions
        # - self.engagement_cost() - assigns appropriate cost from
        #   self.engagementCost property
        # - self.engagementSchedule - engagement states (true/false) for the asset
        #   in active time intervals
        #
        # OUTPUTS:
        # Assigns values to self.transition_costs

        # Gather active time intervals
        time_intervals = mkt.timeIntervals
        time_interval_values = [t.startTime for t in time_intervals]
        self.transitionCosts = [
            x for x in self.transitionCosts
            if x.timeInterval.startTime in time_interval_values
        ]

        # Ensure that ti is ordered by time interval start times
        time_intervals.sort(key=lambda x: x.startTime)

        # Index through all but the first time interval ti
        for i in range(len(time_intervals)):
            # Find the current engagement schedule ces in the current indexed
            # time interval ti(i)
            ces = [
                x for x in self.engagementSchedule
                if x.timeInterval.startTime == time_intervals[i].startTime
            ]

            # Extract its engagement state
            ces = ces[0].value  # logical (true/false)

            # Find the engagement schedule pes in the prior indexed time interval ti(i-1)
            pes = [
                x for x in self.engagementSchedule
                if x.timeInterval.startTime == time_intervals[i - 1].startTime
            ]

            # And extract its value
            pes = pes[0].value  # logical (true/false)

            # Calculate the state transition
            # - -1:Disengaging
            # -  0:Unchaged
            # -  1:Engaging
            dif = ces - pes

            # Assign the corresponding transition cost
            val = self.engagement_cost(dif)

            # Check whether a transition cost exists in the indexed time interval
            iv = find_obj_by_ti(self.transitionCosts, time_intervals[i])

            if iv is None:
                # No transition cost was found in the indexed time interval.
                # Create an interval value and assign its value.
                iv = IntervalValue(self, time_intervals[i], mkt,
                                   MeasurementType.TransitionCost, val)

                # Append the interval value to the list of active interval values
                self.transitionCosts.append(iv)

            else:
                # A transition cost was found in the indexed time interval.
                # Simpy reassign its value.
                iv.value = val  # [$]
Example #19
0
    def update_supply_demand(self, mtn):
        # For each time interval, sum the power that is generated, imported,
        # consumed, or exported for all modeled local resources, neighbors, and
        # local load.

        # Extract active time intervals
        time_intervals = self.timeIntervals  # active TimeIntervals

        time_interval_values = [t.startTime for t in time_intervals]
        # Delete netPowers not in active time intervals
        self.netPowers = [x for x in self.netPowers if x.timeInterval.startTime in time_interval_values]

        # Index through the active time intervals ti
        for i in range(1, len(time_intervals)):
            # Initialize total generation tg
            tg = 0.0  # [avg.kW]

            # Initialize total demand td
            td = 0.0  # [avg.kW]

            # Index through local asset models m.
            m = mtn.localAssets

            for k in range(len(m)):
                mo = find_obj_by_ti(m[k].model.scheduledPowers, time_intervals[i])

                # Extract and include the resource's scheduled power
                p = mo.value  # [avg.kW]

                if p > 0:  # Generation
                    # Add positive powers to total generation tg
                    tg = tg + p  # [avg.kW]

                else:  # Demand
                    # Add negative powers to total demand td
                    td = td + p  # [avg.kW]

            # Index through neighbors m
            m = mtn.neighbors

            for k in range(len(m)):
                # Find scheduled power for this neighbor in the indexed time interval
                mo = find_obj_by_ti(m[k].model.scheduledPowers, time_intervals[i])

                # Extract and include the neighbor's scheduled power
                p = mo.value  # [avg.kW]

                if p > 0:  # Generation
                    # Add positive power to total generation tg
                    tg = tg + p  # [avg.kW]

                else:  # Demand
                    # Add negative power to total demand td
                    td = td + p  # [avg.kW]

            # At this point, total generation and importation tg, and total
            # demand and exportation td have been calculated for the indexed
            # time interval ti[i]

            # Save the total generation in the indexed time interval

            # Check whether total generation exists for the indexed time interval
            iv = find_obj_by_ti(self.totalGeneration, time_intervals[i])

            if iv is None:
                # No total generation was found in the indexed time interval.
                # Create an interval value.
                iv = IntervalValue(self, time_intervals[i], self, MeasurementType.TotalGeneration, tg)  # an IntervalValue

                # Append the total generation to the list of total generations
                self.totalGeneration.append(iv)

            else:
                # Total generation exists in the indexed time interval. Simply
                # reassign its value.
                iv.value = tg

            # Calculate and save total demand for this time interval.
            # NOTE that this formulation includes both consumption and
            # exportation among total load.

            # Check whether total demand exists for the indexed time interval

            iv = find_obj_by_ti(self.totalDemand, time_intervals[i])
            if iv is None:
                # No total demand was found in the indexed time interval. Create
                # an interval value.
                iv = IntervalValue(self, time_intervals[i], self, MeasurementType.TotalDemand, td)  # an IntervalValue

                # Append the total demand to the list of total demands
                self.totalDemand.append(iv)

            else:
                # Total demand was found in the indexed time interval. Simply reassign it.
                iv.value = td

            # Update net power for the interval
            # Net power is the sum of total generation and total load.
            # By convention generation power is positive and consumption
            # is negative.

            # Check whether net power exists for the indexed time interval
            iv = find_obj_by_ti(self.netPowers, time_intervals[i])

            if iv is None:
                # Net power is not found in the indexed time interval. Create an interval value.
                iv = IntervalValue(self, time_intervals[i], self, MeasurementType.NetPower, tg + td)

                # Append the net power to the list of net powers
                self.netPowers.append(iv)
            else:
                # A net power was found in the indexed time interval. Simply reassign its value.
                iv.value = tg + td

        np = [(x.timeInterval.name, x.value) for x in self.netPowers]
        _log.debug("{} market netPowers are: {}".format(self.name, np))
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)
Example #21
0
    def check_marginal_prices(self):
        # Check that marginal prices exist for active time intervals. If they do
        # not exist for a time interval, choose from these alternatives that are
        # ordered from best to worst:
        # (1) initialize the marginal price from that of the preceding interval.
        # (2) use the default marginal price.
        # OUTPUTS:
        # populates list of active marginal prices (see class IntervalValue)

        ti = self.timeIntervals

        # Clean up the list of active marginal prices. Remove any active
        # marginal prices that are not in active time intervals.
        self.marginalPrices = [x for x in self.marginalPrices if x.timeInterval in ti]

        # Index through active time intervals ti
        for i in range(len(ti)):
            # Check to see if a marginal price exists in the active time interval
            iv = find_obj_by_ti(self.marginalPrices, ti[i])

            if iv is None:
                # No marginal price was found in the indexed time interval. Is
                # a marginal price defined in the preceding time interval?

                # Extract the starting time st of the currently indexed time interval
                st = ti[i].startTime

                # Calculate the starting time st of the previous time interval
                st = st - ti[i].duration

                # Find the prior active time interval pti that has this
                # calculated starting time
                pti = find_obj_by_st(self.timeIntervals, st)  # prior time interval

                # Initialize previous marginal price value pmp as an empty set
                pmp = None  # prior marginal prices

                if pti is not None:
                    # There is an active preceding time interval. Check whether
                    # there is an active marginal price in the previous time interval.
                    pmp = find_obj_by_ti(self.marginalPrices, pti)

                if pmp is None:

                    # No marginal price was found in the previous time interval
                    # either. Assign the marginal price from a default value.
                    value = self.defaultPrice  # [$/kWh]

                else:

                    # A marginal price value was found in the previous time
                    # interval. Use that marginal price.
                    value = pmp.value  # [$/kWh]

                # Create an interval value for the new marginal price in the
                # indexed time interval with either the default price or the
                # marginal price from the previous active time interval.
                iv = IntervalValue(self, ti[i], self, MeasurementType.MarginalPrice, value)

                # Append the marginal price value to the list of active marginal prices
                self.marginalPrices.append(iv)
Example #22
0
    def calculate_blended_prices(self):
        # Calculate the blended prices for active time intervals.
        #
        # The blended price is the averaged weighted price of all locally
        # generated and imported energies. A sum is made of all costs of
        # generated and imported energies, which are prices weighted by their
        # corresponding energy. This sum is divided by the total generated and
        # imported energy to get the average.
        #
        # The blended price does not include supply surplus and may therefore be
        # a preferred representation of price for local loads and friendly
        # neighbors, for which myTransactiveNode is not competitive and
        # profit-seeking.

        # Update and gather active time intervals ti. It's simpler to
        # recalculate the active time intervals than it is to check for
        # errors.

        self.check_intervals()
        ti = self.timeIntervals

        # Gather primal production costs of the time intervals.
        pc = self.productionCosts

        # Perform checks on interval primal production costs to ensure smooth
        # calculations. NOTE: This does not check the veracity of the
        # primal costs.

        # CASE 1: No primal production costs have been populated for the various
        # assets and neighbors. This results in termination of the
        # process.

        if pc is None or len(pc) == 0:  # isempty(pc)
            _log.warning('Primal costs have not yet been calculated.')
            return

        # CASE 2: There is at least one active time interval for which primal
        # costs have not been populated. This results in termination of the
        # process.

        elif len(ti) > len(pc):
            _log.warning('Missing primal costs for active time intervals.')
            return

        # CASE 3: There is at least one extra primal production cost that does
        # not refer to an active time interval. It will be removed.

        elif len(ti) < len(pc):
            _log.warning('Removing primal costs that are not among active time intervals.')
            self.productionCosts = [x for x in self.productionCosts if x.timeInterval in self.timeIntervals]

        for i in range(len(ti)):
            pc = find_obj_by_ti(self.productionCosts, ti[i])
            tg = find_obj_by_ti(self.totalGeneration, ti[i])
            bp = pc / tg

            self.blendedPrices1 = [x for x in self.blendedPrices1 if x != ti[i]]

            val = bp
            iv = IntervalValue(self, ti[i], self, MeasurementType.BlendedPrice, val)

            # Append the blended price to the list of interval values
            self.blendedPrices1.append(iv)
Example #23
0
def test_production():
    from local_asset_model import LocalAssetModel
    from market import Market

    print('Running test_production()')
    pf = 'pass'

    #   Create a test object
    test_object = LocalAssetModel()

    #   Create a test market
    test_market = Market()

    #   Create several active vertices av
    av = [
        Vertex(0.0200, 5.00, 0.0),
        Vertex(0.0200, 7.00, 100.0),
        Vertex(0.0250, 9.25, 200.0)
    ]

    # Create a time interval ti
    dt = datetime.now()
    at = dt
    #   NOTE: Function Hours() corrects the behavior of Matlab hours().
    dur = timedelta(hours=1)
    mkt = test_market
    mct = dt
    st = dt
    ti = TimeInterval(at, dur, mkt, mct, st)

    # Assign activeVertices, which are IntervalValues
    test_object.activeVertices = [
        IntervalValue(test_object, ti, test_market,
                      MeasurementType.ActiveVertex, av[0]),
        IntervalValue(test_object, ti, test_market,
                      MeasurementType.ActiveVertex, av[1]),
        IntervalValue(test_object, ti, test_market,
                      MeasurementType.ActiveVertex, av[2])
    ]

    ## CASE: Various marginal prices when there is more than one vertex
    test_prices = [-0.010, 0.000, 0.020, 0.0225, 0.030]

    p = [0] * len(test_prices)  #zeros(1, length(test_prices))
    for i in range(len(test_prices)):  #for i = 1:length(test_prices)
        p[i] = production(test_object, test_prices[i], ti)

    print('- the function ran without errors')

    # p(1) = 0: below first vertex
    # p(2) = 0: below first vertex
    # p(3) = 100: at first vertex, which has identical marginal price as second
    # p(4) = 150: interpolate between vertices
    # p(5) = 200: exceeds last vertex

    #if ~all(abs(p - [0, 0, 100, 150, 200]) < 0.001):
    expected = [0, 0, 100, 150, 200]
    if not all([p[i] - expected[i] < 0.001 for i in range(len(p))]):
        pf = 'fail'
        raise Exception('- the production cost was incorrectly calculated')
    else:
        print('- the production cost was correctly calculated')

    ## CASE: One vertex (inelastic case, a constant)
    test_object.activeVertices = [
        IntervalValue(test_object, ti, test_market,
                      MeasurementType.ActiveVertex, av[2])
    ]

    for i in range(5):
        p[i] = production(test_object, test_prices[i], ti)

    #if ~all(p == 200 * ones(1, length(p))):
    if not all(x == 200 for x in p):
        pf = 'fail'
        raise Exception(
            'the vertex power should be assigned when there is one vertex')
    else:
        print('- the correct power was assigned when there is one vertex')

    ## CASE: No active vertices (error case):
    test_object.activeVertices = []

    try:
        p = production(test_object, test_prices[4], ti)
        pf = 'fail'
        raise Exception(
            '- an error should have occurred with no active vertices')
    except:
        print('- with no vertices, system returned with warnings, as expected')

    #   Success
    print('- the test function ran to completion')
    print('Result: #s\n\n', pf)
Example #24
0
    def test_schedule_power(cls):
        print('Running test_schedule_power()')
        pf = 'pass'

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

        # Create and store a couple TimeInterval objects at a known date and
        # time.
        dt = datetime(2017, 11, 1, 12, 0, 0)  # Wednesday Nov. 1, 2017 at noon
        at = dt
        dur = timedelta(hours=1)
        mkt = test_mkt
        mct = dt
        st = dt
        test_intervals = [TimeInterval(at, dur, mkt, mct, st)]

        st = st + dur  # 1p on the same day
        test_intervals.append(TimeInterval(at, dur, mkt, mct, st))

        test_mkt.timeIntervals = test_intervals

        # Create a test TemperatureForecastModel object and give it some
        # temperature values in the test TimeIntervals.
        # test_forecast = TemperatureForecastModel
        # # The information type should be specified so the test object will
        # # correctly identivy it.
        # test_forecast.informationType = 'temperature'
        # # test_forecast.update_information(test_mkt)
        # test_forecast.predictedValues(1) = IntervalValue(test_forecast, test_intervals(1), test_mkt, 'Temperature', 20)  # Heating regime
        # test_forecast.predictedValues(2) = IntervalValue(test_forecast, test_intervals(2), test_mkt, 'Temperature', 100)  # Cooling regime
        # test_obj.informationServiceModels = {test_forecast}
        # Create a OpenLoopRichlandLoadPredictor test object.
        test_forecast = TemperatureForecastModel()
        test_forecast.informationType = 'temperature'
        test_forecast.predictedValues = [
            IntervalValue(test_forecast, test_intervals[0], test_mkt,
                          MeasurementType.Temperature, 20),
            # Heating regime
            IntervalValue(test_forecast, test_intervals[1], test_mkt,
                          MeasurementType.Temperature, 100)
            # Cooling regime
        ]
        test_obj = OpenLoopRichlandLoadPredictor(test_forecast)

        # Manually evaluate from the lookup table and the above categorical inputs
        # DOW = Wed. ==>
        Intercept1 = 146119
        Intercept2 = 18836
        Intercept3 = -124095
        Factor1 = -1375
        Factor2 = 1048
        Temperature1 = 20
        Temperature2 = 100

        LOAD = [
            -(Intercept1 + Intercept2 + Factor1 * Temperature1),
            -(Intercept1 + Intercept3 + Factor2 * Temperature2)
        ]

        try:
            test_obj.schedule_power(test_mkt)
            print('- the method ran without errors')
        except:
            pf = 'fail'
            _log.warning('- the method had errors when called')

        #if any(abs([test_obj.scheduledPowers(1: 2).value] - [LOAD])) > 5
        if any([
                abs(test_obj.scheduledPowers[i].value - LOAD[i]) > 5
                for i in range(len(test_obj.scheduledPowers))
        ]):
            pf = 'fail'
            _log.warning('- the calculated powers were not as expected')
        else:
            print('- the calculated powers were as expected')

        # Success
        print('- the test ran to completion')
        print('Result: #s\n\n', pf)
Example #25
0
    def calculate_reserve_margin(self, mkt):
        # Estimate available (spinning) reserve margin for this asset.
        #
        # NOTES:
        #   This method works with the simplest base classes that have constant
        #   power and therefore provide no spinning reserve. This method may be
        #   redefined by subclasses of the local asset model to add new features
        #   or capabilities.
        #   This calculation will be more meaningful and useful after resource
        #   commitments and uncertainty estimates become implemented. Until then,
        #   reserve margins may be tracked, even if they are not used.
        #
        # PRESUMPTIONS:
        # - Active time intervals exist and have been updated
        # - The asset's maximum power is a meaningful and accurate estimate of
        #   the maximum power level that can be achieved on short notice, i.e.,
        #   spinning reserve.
        #
        # INPUTS:
        # mkt - market object
        #
        # OUTPUTS:
        # Modifies self.reserveMargins - an array of estimated (spinning) reserve
        # margins in active time intervals

        # Gather the active time intervals ti
        time_intervals = mkt.timeIntervals  # active TimeIntervals
        time_interval_values = [t.startTime for t in time_intervals]
        self.reserveMargins = [
            x for x in self.reserveMargins
            if x.timeInterval.startTime in time_interval_values
        ]

        # Index through active time intervals ti
        for i in range(len(time_intervals)):
            # Calculate the reserve margin for the indexed interval. This is the
            # non-negative difference between the maximum asset power and the
            # scheduled power. In principle, generation may be increased or
            # demand decreased by this quantity to act as spinning reserve.

            # Find the scheduled power in the indexed time interval
            iv = find_obj_by_ti(self.scheduledPowers, time_intervals[i])

            # Calculate the reserve margin rm in the indexed time interval. The
            # reserve margin is the differnce between the maximum operational
            # power value in the interval and the scheduled power. The
            # operational maximum should be less than the object's hard physical
            # power constraint, so a check is in order.
            # start with the hard physical constraint.
            hard_const = self.object.maximumPower  # [avg.kW]

            # Calculate the operational maximum constraint, which is the highest
            # point on the supply/demand curve (i.e., the vertex) that represents
            # the residual flexibility of the asset in the time interval.
            op_const = find_objs_by_ti(self.activeVertices, time_intervals[i])

            if len(op_const) == 0:
                op_const = hard_const
            else:
                op_const = [x.value for x in op_const]  # active vertices
                op_const = max([x.power for x in op_const
                                ])  # operational max. power[avg.kW]

            # Check that the upper operational power constraint is less than or
            # equal to the object's hard physical constraint.
            soft_maximum = min(hard_const, op_const)  # [avg.kW]

            # And finally calculate the reserve margin.
            rm = max(0, soft_maximum - iv.value)  # reserve margin [avg. kW]

            # Check whether a reserve margin already exists for the indexed time interval
            iv = find_obj_by_ti(self.reserveMargins,
                                time_intervals[i])  # an IntervalValue

            if iv is None:
                # A reserve margin does not exist for the indexed time interval.
                # create it. (See IntervalValue class.)
                iv = IntervalValue(self, time_intervals[i], mkt,
                                   MeasurementType.ReserveMargin,
                                   rm)  # an IntervalValue

                # Append the new reserve margin interval value to the list of
                # reserve margins for the active time intervals
                self.reserveMargins.append(iv)

            else:
                # The reserve margin already exists for the indexed time
                # interval. Simply reassign its value.
                iv.value = rm  # reserve margin [avg.kW]
    def schedule_power(self, mkt):
        # Estimate stochastic generation from a solar
        # PV array as a function of time-of-day and a cloud-cover factor.
        # INPUTS:
        # obj - SolarPvResourceModel class object
        # tod - time of day
        # OUTPUTS:
        # p - calcalated maximum power production at this time of day
        # LOCAL:
        # h - hour (presumes 24-hour clock, local time)
        # *************************************************************************

        # Gather active time intervals
        tis = mkt.timeIntervals

        # Index through the active time intervals ti
        for ti in tis:
            # Production will be estimated from the time-of-day at the center of
            # the time interval.
            tod = ti.startTime + ti.duration / 2  # a datetime

            # extract a fractional representation of the hour-of-day
            h = tod.hour
            m = tod.minute
            h = h + m / 60  # TOD stated as fractional hours

            # Estimate solar generation as a sinusoidal function of daylight hours.
            if h < 5.5 or h > 17.5:
                # The time is outside the time of solar production. Set power to zero.
                p = 0.0  # [avg.kW]

            else:
                # A sinusoidal function is used to forecast solar generation
                # during the normally sunny part of a day.
                p = 0.5 * (1 + math.cos((h - 12) * 2.0 * math.pi / 12))
                p = self.object.maximumPower * p
                p = self.cloudFactor * p  # [avg.kW]

            # Check whether a scheduled power exists in the indexed time interval.
            iv = find_obj_by_ti(self.scheduledPowers, ti)
            if iv is None:
                # No scheduled power value is found in the indexed time interval.
                # Create and store one.
                iv = IntervalValue(self, ti, mkt,
                                   MeasurementType.ScheduledPower, p)
                # Append the scheduled power to the list of scheduled powers.
                self.scheduledPowers.append(iv)

            else:
                # A scheduled power already exists in the indexed time interval.
                # Simply reassign its value.
                iv.value = p  # [avg.kW]

            # Assign engagement schedule in the indexed time interval
            # NOTE: The assignment of engagement schedule, if used, will often be
            # assigned during the scheduling of power, not separately as
            # demonstrated here.

            # Check whether an engagement schedule exists in the indexed time interval
            iv = find_obj_by_ti(self.engagementSchedule, ti)

            # NOTE: this template assigns engagement value as true (i.e., engaged).
            val = True  # Asset is committed or engaged

            if iv is None:
                # No engagement schedule was found in the indexed time interval.
                # Create an interval value and assign its value.
                iv = IntervalValue(self, ti, mkt,
                                   MeasurementType.EngagementSchedule,
                                   val)  # an IntervalValue

                # Append the interval value to the list of active interval values
                self.engagementSchedule.append(iv)

            else:
                # An engagement schedule was found in the indexed time interval.
                # Simpy reassign its value.
                iv.value = val  # [$]

        # Remove any extra scheduled powers
        self.scheduledPowers = [
            x for x in self.scheduledPowers if x.timeInterval in tis
        ]

        # Remove any extra engagement schedule values
        self.engagementSchedule = [
            x for x in self.engagementSchedule if x.timeInterval in tis
        ]
Example #27
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)
def test_update_dual_costs():
    # TEST_UPDATE_DUAL_COSTS() - test method update_dual_costs() that creates
    # or revises the dual costs in active time intervals using active vertices,
    # scheduled powers, and marginal prices.
    # NOTE: This test is virtually identical to the NeighborModel test of the
    # same name.
    print('Running LocalAssetModel.test_update_dual_costs()')
    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 and store a marginal price IntervalValue object.
    test_market.marginalPrices = [
        IntervalValue(test_market, time_interval, test_market, MeasurementType.MarginalPrice, 0.1)]

    #   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, 100)]

    #   Create and store a production cost IntervalValue object in the active
    #   time interval.
    test_model.productionCosts = [
        IntervalValue(test_model, time_interval, test_market, MeasurementType.ProductionCost, 1000)]

    # TEST 1
    print('- Test 1: First calculation of a dual cost')

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

    if len(test_model.dualCosts) != 1:
        pf = 'fail'
        print('  - the wrong number of dual cost values was created')
    else:
        print('  - the right number of dual cost values was created')

    dual_cost = test_model.dualCosts[0].value

    if dual_cost != (1000 - 100 * 0.1):
        pf = 'fail'
        print('  - an unexpected dual cost value was found')
    else:
        print('  - the expected dual cost value was found')

    # TEST 2
    print('- Test 2: Reassignment of an existing dual cost')

    #   Configure the test by modifying the marginal price value.
    test_market.marginalPrices[0].value = 0.2

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

    if len(test_model.dualCosts) != 1:
        pf = 'fail'
        print('  - the wrong number of dual cost values was created')
    else:
        print('  - the right number of dual cost values was created')

    dual_cost = test_model.dualCosts[0].value

    if dual_cost != (1000 - 100 * 0.2):
        pf = 'fail'
        print('  - an unexpected dual cost value was found')
    else:
        print('  - the expected dual cost value was found')

    # Success.
    print('- the test ran to completion')
    print('\nResult: #s\n\n', pf)
Example #29
0
    def update_costs(self, mtn):
        # Sum the production and dual costs from all modeled local resources, local
        # loads, and neighbors, and then sum them for the entire duration of the
        # time horizon being calculated.
        #
        # PRESUMPTIONS:
        # - Dual costs have been created and updated for all active time
        # intervals for all neighbor objects
        # - Production costs have been created and updated for all active time
        # intervals for all asset objects
        #
        # INTPUTS:
        # mtn - my Transactive Node object
        #
        # OUTPUTS:
        # - Updates Market.productionCosts - an array of total production cost in
        # each active time interval
        # - Updates Market.totalProductionCost - the sum of production costs for
        # the entire future time horizon of active time intervals
        # - Updates Market.dualCosts - an array of dual cost for each active time
        # interval
        # - Updates Market.totalDualCost - the sum of all the dual costs for the
        # entire future time horizon of active time intervals

        # Call each LocalAssetModel to update its costs
        for la in mtn.localAssets:
            la.model.update_costs(self)

        # Call each NeighborModel to update its costs
        for n in mtn.neighbors:
            n.model.update_costs(self)

        for i in range (1, len(self.timeIntervals)):
            ti = self.timeIntervals[i]
            # Initialize the sum dual cost sdc in this time interval
            sdc = 0.0  # [$]

            # Initialize the sum production cost spc in this time interval
            spc = 0.0  # [$]

            for la in mtn.localAssets:
                iv = find_obj_by_ti(la.model.dualCosts, ti)
                sdc = sdc + iv.value  # sum dual cost [$]

                iv = find_obj_by_ti(la.model.productionCosts, ti)
                spc = spc + iv.value  # sum production cost [$]

            for n in mtn.neighbors:
                iv = find_obj_by_ti(n.model.dualCosts, ti)
                sdc = sdc + iv.value  # sum dual cost [$]

                iv = find_obj_by_ti(n.model.productionCosts, ti)
                spc = spc + iv.value  # sum production cost [$]

            # Check to see if a sum dual cost exists in the indexed time interval
            iv = find_obj_by_ti(self.dualCosts, ti)

            if iv is None:
                # No dual cost was found for the indexed time interval. Create
                # an IntervalValue and assign it the sum dual cost for the
                # indexed time interval
                iv = IntervalValue(self, ti, self, MeasurementType.DualCost, sdc)  # an IntervalValue

                # Append the dual cost to the list of interval dual costs
                self.dualCosts.append(iv)  # = [mkt.dualCosts, iv]  # IntervalValues

            else:
                # A sum dual cost value exists in the indexed time interval.
                # Simply reassign its value
                iv.value = sdc  # sum dual cost [$]

            # Check to see if a sum production cost exists in the indexed time interval
            iv = find_obj_by_ti(self.productionCosts, ti)

            if iv is None:
                # No sum production cost was found for the indexed time
                # interval. Create an IntervalValue and assign it the sum
                # prodution cost for the indexed time interval
                iv = IntervalValue(self, ti, self, MeasurementType.ProductionCost, spc)

                # Append the production cost to the list of interval production costs
                self.productionCosts.append(iv)

            else:
                # A sum production cost value exists in the indexed time
                # interval. Simply reassign its value
                iv.value = spc  # sum production cost [$]

        # Sum total dual cost for the entire time horizon
        self.totalDualCost = sum([x.value for x in self.dualCosts])  # [$]

        # Sum total primal cost for the entire time horizon
        self.totalProductionCost = sum([x.value for x in self.productionCosts])  # [$]
Example #30
0
def test_prod_cost_from_formula():
    from local_asset_model import LocalAssetModel
    from market import Market

    print('Running test_prod_cost_from_formula()')
    pf = 'pass'

    #   Create a test object
    test_object = LocalAssetModel()

    #   Create a test market
    test_market = Market()

    #   Create and store the object's cost parameters
    test_object.costParameters = [4, 3, 2]

    #   Create and store three hourly TimeIntervals
    #   Modified to use the TimeInterval constructor.
    dt = datetime.now()
    at = dt
    dur = timedelta(hours=1)
    mkt = test_market
    mct = dt

    st = dt
    ti = [TimeInterval(at, dur, mkt, mct, st)]

    st = st + dur
    ti.append(TimeInterval(at, dur, mkt, mct, st))

    st = st + dur
    ti.append(TimeInterval(at, dur, mkt, mct, st))

    test_market.timeIntervals = ti

    # Create and store three corresponding scheduled powers
    iv = [
        IntervalValue(test_object, ti[0], test_market,
                      MeasurementType.ScheduledPower, 100),
        IntervalValue(test_object, ti[1], test_market,
                      MeasurementType.ScheduledPower, 200),
        IntervalValue(test_object, ti[2], test_market,
                      MeasurementType.ScheduledPower, 300)
    ]
    test_object.scheduledPowers = iv

    #   Run the test
    pc = [0] * 3
    for i in range(3):
        pc[i] = prod_cost_from_formula(test_object, ti[i])

    # pc(1) = 4 + 3 * 100 + 0.5 * 2 * 100^2 = 10304
    # pc(2) = 4 + 3 * 200 + 0.5 * 2 * 200^2 = 40604
    # pc(3) = 4 + 3 * 300 + 0.5 * 2 * 300^2 = 90904

    #if all(pc ~=[10304, 40604, 90904])
    expected = [10304, 40604, 90904]
    if all([pc[i] != expected[i] for i in range(len(pc))]):
        pf = 'fail'
        raise Exception('- production cost was incorrectly calculated')
    else:
        print('- production cost was correctly calculated')

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