def create_sced_instance(data_provider:DataProvider,
                         current_state:SimulationState,
                         options,
                         sced_horizon,
                         forecast_error_method = ForecastErrorMethod.PRESCIENT
                         ):
    ''' Create a deterministic economic dispatch instance, given current forecasts and commitments.
    '''
    assert current_state is not None

    sced_md = data_provider.get_initial_actuals_model(options, sced_horizon, current_state.minutes_per_step)

    # Set initial state
    _copy_initial_state_into_model(options, current_state, sced_md)

    ################################################################################
    # initialize the demand and renewables data, based on the forecast error model #
    ################################################################################

    if forecast_error_method is ForecastErrorMethod.PRESCIENT:
        # Warning: This method can see into the future!
        for forecastable, sced_data in get_forecastables(sced_md):
            future = current_state.get_future_actuals(forecastable)
            for t in range(sced_horizon):
                sced_data[t] = future[t]

    else:  # persistent forecast error:
        # Go through each time series that can be forecasted
        for forecastable, sced_data in get_forecastables(sced_md):
            forecast = current_state.get_forecasts(forecastable)
            # the first value is, by definition, the actual.
            sced_data[0] = current_state.get_current_actuals(forecastable)

            # Find how much the first forecast was off from the actual, as a fraction of 
            # the forecast. For all subsequent times, adjust the forecast by the same fraction.
            if forecast[0] == 0.0:
                forecast_error_ratio = 0.0
            else:
                forecast_error_ratio = sced_data[0] / forecast[0]

            for t in range(1, sced_horizon):
                sced_data[t] = forecast[t] * forecast_error_ratio

    _ensure_reserve_factor_honored(options, sced_md, range(sced_horizon))
    _ensure_contingencies_monitored(options, sced_md)

    # Set generator commitments & future state
    for g, g_dict in sced_md.elements(element_type='generator', generator_type='thermal'):
        # Start by preparing an empty array of the correct size for each generator
        fixed_commitment = [None]*sced_horizon
        g_dict['fixed_commitment'] = _time_series_dict(fixed_commitment)

        # Now fill it in with data
        for t in range(sced_horizon):
            fixed_commitment[t] = current_state.get_generator_commitment(g,t)

        # Look as far into the future as we can for future startups / shutdowns
        last_commitment = fixed_commitment[-1]
        for t in range(sced_horizon, current_state.timestep_count):
            this_commitment = current_state.get_generator_commitment(g,t)
            if (this_commitment - last_commitment) > 0.5:
                # future startup
                future_status_time_steps = ( t - sced_horizon + 1 )
                break
            elif (last_commitment - this_commitment) > 0.5:
                # future shutdown
                future_status_time_steps = -( t - sced_horizon + 1 )
                break
        else: # no break
            future_status_time_steps = 0
        g_dict['future_status'] = (current_state.minutes_per_step/60.) * future_status_time_steps

    if not options.no_startup_shutdown_curves:
        minutes_per_step = current_state.minutes_per_step
        for g, g_dict in sced_md.elements(element_type='generator', generator_type='thermal'):
            if 'startup_curve' in g_dict:
                continue
            ramp_up_rate_sced = g_dict['ramp_up_60min'] * minutes_per_step/60.
            # this rarely happens, e.g., synchronous condenser
            if ramp_up_rate_sced == 0:
                continue
            if 'startup_capacity' not in g_dict:
                sced_startup_capacity = _calculate_sced_startup_shutdown_capacity_from_none(
                                            g_dict['p_min'], ramp_up_rate_sced)
            else:
                sced_startup_capacity = _calculate_sced_startup_shutdown_capacity_from_existing(
                                            g_dict['startup_capacity'], g_dict['p_min'], minutes_per_step)

            g_dict['startup_curve'] = [ sced_startup_capacity - i*ramp_up_rate_sced \
                                        for i in range(1,int(math.ceil(sced_startup_capacity/ramp_up_rate_sced))) ]

        for g, g_dict in sced_md.elements(element_type='generator', generator_type='thermal'):
            if 'shutdown_curve' in g_dict:
                continue

            ramp_down_rate_sced = g_dict['ramp_down_60min'] * minutes_per_step/60.
            # this rarely happens, e.g., synchronous condenser
            if ramp_down_rate_sced == 0:
                continue
            # compute a new shutdown curve if we go from "on" to "off"
            if g_dict['initial_status'] > 0 and g_dict['fixed_commitment']['values'][0] == 0:
                power_t0 = g_dict['initial_p_output']
                # if we end up using a historical curve, it's important
                # for the time-horizons to match, particularly since this
                # function is also used to create long-horizon look-ahead
                # SCEDs for the unit commitment process
                create_sced_instance.shutdown_curves[g, minutes_per_step] = \
                        [ power_t0 - i*ramp_down_rate_sced for i in range(1,int(math.ceil(power_t0/ramp_down_rate_sced))) ]

            if (g,minutes_per_step) in create_sced_instance.shutdown_curves:
                g_dict['shutdown_curve'] = create_sced_instance.shutdown_curves[g,minutes_per_step]
            else:
                if 'shutdown_capacity' not in g_dict:
                    sced_shutdown_capacity = _calculate_sced_startup_shutdown_capacity_from_none(
                                                g_dict['p_min'], ramp_down_rate_sced)
                else:
                    sced_shutdown_capacity = _calculate_sced_startup_shutdown_capacity_from_existing(
                                                g_dict['shutdown_capacity'], g_dict['p_min'], minutes_per_step)

                g_dict['shutdown_curve'] = [ sced_shutdown_capacity - i*ramp_down_rate_sced \
                                             for i in range(1,int(math.ceil(sced_shutdown_capacity/ramp_down_rate_sced))) ]

    if not options.enforce_sced_shutdown_ramprate:
        for g, g_dict in sced_md.elements(element_type='generator', generator_type='thermal'):
            # make sure the generator can immediately turn off
            g_dict['shutdown_capacity'] = max(g_dict['shutdown_capacity'], (60./current_state.minutes_per_step)*g_dict['initial_p_output'] + 1.)

    return sced_md
Example #2
0
    def generate_stack_graph(options, daily_stats: DailyStats):

        md_dict = ModelData.empty_model_data_dict()

        system = md_dict['system']

        # put just the HH:MM in the graph
        system['time_keys'] = [
            str(opstats.timestamp.time())[0:5]
            for opstats in daily_stats.operations_stats()
        ]

        system['reserve_requirement'] = _time_series_dict([
            opstats.reserve_requirement
            for opstats in daily_stats.operations_stats()
        ])
        system['reserve_shortfall'] = _time_series_dict([
            opstats.reserve_shortfall
            for opstats in daily_stats.operations_stats()
        ])

        elements = md_dict['elements']

        elements['load'] = {
            'system_load': {
                'p_load':
                _time_series_dict([
                    opstats.total_demand
                    for opstats in daily_stats.operations_stats()
                ])
            }
        }
        elements['bus'] = {
            'system_load_shed': {
                'p_balance_violation':
                _time_series_dict([
                    opstats.load_shedding
                    for opstats in daily_stats.operations_stats()
                ])
            },
            'system_over_generation': {
                'p_balance_violation':
                _time_series_dict([
                    -opstats.over_generation
                    for opstats in daily_stats.operations_stats()
                ])
            }
        }

        ## load in generation, storage
        generator_fuels = {}
        thermal_quickstart = {}
        thermal_dispatch = {}
        thermal_headroom = {}
        thermal_states = {}
        renewables_dispatch = {}
        renewables_curtailment = {}
        virtual_dispatch = {}
        storage_input_dispatch = {}
        storage_output_dispatch = {}
        storage_types = {}
        for opstats in daily_stats.operations_stats():
            _collect_time_assert_equal(opstats.generator_fuels,
                                       generator_fuels)
            _collect_time_assert_equal(opstats.quick_start_capable,
                                       thermal_quickstart)
            _collect_time_assert_equal(opstats.storage_types, storage_types)

            _collect_time(opstats.observed_thermal_dispatch_levels,
                          thermal_dispatch)
            _collect_time(opstats.observed_thermal_headroom_levels,
                          thermal_headroom)
            _collect_time(opstats.observed_thermal_states, thermal_states)

            _collect_time(opstats.observed_renewables_levels,
                          renewables_dispatch)
            _collect_time(opstats.observed_renewables_curtailment,
                          renewables_curtailment)

            _collect_time(opstats.observed_virtual_dispatch_levels,
                          virtual_dispatch)

            _collect_time(opstats.storage_input_dispatch_levels,
                          storage_input_dispatch)
            _collect_time(opstats.storage_output_dispatch_levels,
                          storage_output_dispatch)

        # load generation
        gen_dict = {}
        for g, fuel in generator_fuels.items():
            gen_dict[g] = {
                'fuel': fuel,
                'generator_type': 'renewable'
            }  # will get re-set below for thermal units
        for g, quickstart in thermal_quickstart.items():
            gen_dict[g]['fast_start'] = quickstart
            gen_dict[g]['generator_type'] = 'thermal'
        for g in virtual_dispatch:
            gen_dict[g]['generator_type'] = 'virtual'

        _add_timeseries_attribute_to_egret_dict(gen_dict, thermal_dispatch,
                                                'pg')
        _add_timeseries_attribute_to_egret_dict(gen_dict, thermal_headroom,
                                                'headroom')
        _add_timeseries_attribute_to_egret_dict(gen_dict, thermal_states,
                                                'commitment')

        _add_timeseries_attribute_to_egret_dict(gen_dict, renewables_dispatch,
                                                'pg')
        _add_timeseries_attribute_to_egret_dict(gen_dict,
                                                renewables_curtailment,
                                                'curtailment')

        _add_timeseries_attribute_to_egret_dict(gen_dict, virtual_dispatch,
                                                'pg')

        for g_dict in gen_dict.values():
            if g_dict['generator_type'] == 'renewable':
                pg = g_dict['pg']['values']
                curtailment = g_dict['curtailment']['values']
                g_dict['p_max'] = _time_series_dict(
                    [pg_val + c_val for pg_val, c_val in zip(pg, curtailment)])

        elements['generator'] = gen_dict

        # load storage
        storage_dict = {}
        for s, stype in storage_types.items():
            storage_dict[s] = {'fuel': stype}
        _add_timeseries_attribute_to_egret_dict(storage_dict,
                                                storage_input_dispatch,
                                                'p_charge')
        _add_timeseries_attribute_to_egret_dict(storage_dict,
                                                storage_output_dispatch,
                                                'p_discharge')

        elements['storage'] = storage_dict

        figure_path = os.path.join(
            options.output_directory, "plots",
            "stackgraph_" + str(daily_stats.date) + ".png")

        graphutils.generate_stack_graph(ModelData(md_dict),
                                        bar_width=1,
                                        x_tick_frequency=4 *
                                        (60 // options.sced_frequency_minutes),
                                        title=str(daily_stats.date),
                                        save_fig=figure_path)
Example #3
0
def _add_timeseries_attribute_to_egret_dict(egret_dict, attribute_dict,
                                            egret_attribute_name):
    for g, vals in attribute_dict.items():
        egret_dict[g][egret_attribute_name] = _time_series_dict(vals)
Example #4
0
def create_sced_instance(data_provider:DataProvider,
                         current_state:SimulationState,
                         options,
                         sced_horizon,
                         forecast_error_method = ForecastErrorMethod.PRESCIENT
                         ):
    ''' Create an hourly deterministic economic dispatch instance, given current forecasts and commitments.
    '''
    assert current_state != None

    sced_md = data_provider.get_initial_model(options, sced_horizon, current_state.minutes_per_step)

    # Set initial state
    _copy_initial_state_into_model(options, current_state, sced_md)

    ################################################################################
    # initialize the demand and renewables data, based on the forecast error model #
    ################################################################################

    if forecast_error_method is ForecastErrorMethod.PRESCIENT:
        # Warning: This method can see into the future!
        future_actuals = current_state.get_future_actuals()
        sced_forecastables, = get_forecastables(sced_md)
        for future,sced_data in zip(future_actuals, sced_actuals):
            for t in range(sced_horizon):
                sced_data[t] = future[t]

    else:  # persistent forecast error:
        current_actuals = current_state.get_current_actuals()
        forecasts = current_state.get_forecasts()
        sced_forecastables = get_forecastables(sced_md)
        # Go through each time series that can be forecasted
        for current_actual, forecast, (sced_data,) in zip(current_actuals, forecasts, sced_forecastables):
            # the first value is, by definition, the actual.
            sced_data[0] = current_actual

            # Find how much the first forecast was off from the actual, as a fraction of 
            # the forecast. For all subsequent times, adjust the forecast by the same fraction.
            current_forecast = forecast[0]
            if current_forecast == 0.0:
                forecast_error_ratio = 0.0
            else:
                forecast_error_ratio = current_actual / forecast[0]

            for t in range(1, sced_horizon):
                sced_data[t] = forecast[t] * forecast_error_ratio

    _ensure_reserve_factor_honored(options, sced_md, range(sced_horizon))

    ## TODO: propogate relax_t0_ramping_initial_day into this function
    ## if relaxing initial ramping, we need to relax it in the first SCED as well
    assert options.relax_t0_ramping_initial_day is False

    # Set generator commitments
    for g, g_dict in sced_md.elements(element_type='generator', generator_type='thermal'):
        # Start by preparing an empty array of the correct size for each generator
        fixed_commitment = [None]*sced_horizon
        g_dict['fixed_commitment'] = _time_series_dict(fixed_commitment)

        # Now fill it in with data
        for t in range(sced_horizon):
            fixed_commitment[t] = current_state.get_generator_commitment(g,t)

    return sced_md