def set_data(self, data):
        """Set the data dict with the data series required
        for the generator.
        
        Inputs:
            data: dict - with keys matching those requested by
                get_data_types. 
        """

        txmultigeneratormultisite.TxMultiGeneratorMultiSite.set_data(
            self, data)

        self.data = data[self.config['data_name']]
        self.site_to_data = {}

        if len(self.config['data_map_name']) > 0:
            map_data = data[self.config['data_map_name']]
            for i in range(0, map_data.shape[0]):
                self.site_to_data[map_data[i, 0]] = map_data[i, 1]
            if len(self.params_to_site) == 0:
                # We have a data map, but no params to site, so assume
                # all data are used and map 1:1 to this.
                self.params_to_site = map_data[:, 0]

        elif len(self.params_to_site) > 0:
            # No data map is provided, but params_to_site is. If the
            # lengths agree, map the site index list to the data 1:1.

            if not (len(self.params_to_site) == self.data.shape[1]):
                raise mureilexception.ConfigException(
                    'In model ' + self.config['section'] +
                    ', no data map is provided, the data is width ' +
                    str(self.data.shape[1]) +
                    ' and the provided params_to_site list is ' +
                    str(len(self.params_to_site)) +
                    ' so no automatic mapping is possible.', {})

            for i in range(len(self.params_to_site)):
                self.site_to_data[self.params_to_site[i]] = i

        else:
            # No list of sites is provided. Just map to ordinal numbers.
            self.params_to_site = range(self.data.shape[1])
            for i in range(0, self.data.shape[1]):
                self.site_to_data[i] = i

        # Check that all of the values in site_to_data are within the
        # self.data matrix.

        max_data = self.data.shape[1]
        for data_index in self.site_to_data.itervalues():
            if (data_index < 0) or (data_index >= max_data):
                raise mureilexception.ConfigException(
                    'data_index ' + str(data_index) +
                    ' was requested by the model in section ' +
                    self.config['section'] +
                    ' but the maximum index in the data array is ' +
                    str(max_data), {})
    def set_startup_state(self, startup_data):
        """Set the startup state from the data provided. Sets 
        self.startup_state from this.
        
        Inputs:
            startup_data: An array of generators * 4:
                [[site_index, capacity, build_date, decommissioning_period],
                ...]
        """

        # Check if the startup data is empty. If so, just return.
        if len(startup_data) == 0:
            return

        # Find out which build periods are covered.
        startup_data = numpy.array(startup_data)
        if not (len(startup_data.shape) == 2):
            raise mureilexception.ConfigException(
                'startup data array for module ' + self.config['section'] +
                ' is not rectangular.', {})

        if not (startup_data.shape[1] == 4):
            raise mureilexception.ConfigException(
                'startup data array for module ' + self.config['section'] +
                ' shape ' + str(startup_data.shape) +
                ' but (n, 4) is required.', {})

        self.extra_periods = map(
            int, (list(set(startup_data[:, 2].tolist() + self.extra_periods))))
        self.extra_periods.sort()

        # And insert each existing generator into the starting state.
        cap_list = self.startup_state['capacity']
        hist_list = self.startup_state['history']

        for i in range(startup_data.shape[0]):
            site_index = int(startup_data[i, 0])
            new_cap = startup_data[i, 1]
            period = int(startup_data[i, 2])
            decomm_date = int(startup_data[i, 3])

            new_entry = (new_cap, period, decomm_date)
            if decomm_date < self.run_periods[0]:
                logger.warning(
                    'Model in section ' + self.config['section'] +
                    ' adds startup capacity decommissioned at end of ' +
                    decomm_date + ' but the first run period is ' +
                    self.run_periods[0] +
                    ' so it has been removed from the startup state.')
                if site_index not in hist_list:
                    hist_list[site_index] = []
                hist_list[site_index].append(new_entry)
            else:
                new_entry = (new_cap, period, decomm_date)

                if site_index not in cap_list:
                    cap_list[site_index] = []
                cap_list[site_index].append(new_entry)
示例#3
0
    def do_iteration(self):
        if (not self.is_configured):
            msg = 'do_iteration requested, but geneticalgorithm is not configured'
            logger.critical(msg)
            raise mureilexception.ConfigException(msg, {})

        self.iteration_count += 1
        self.population.mutate()
        self.pop_score()
        if self.iteration_count % 1 == 0:
            try:
                gene = iter(self.population.genes).next()
                b_score = gene.score
                bestgene = gene
            except StopIteration:
                b_score = Inf
                bestgene = None

            for gene in self.population.genes:
                if gene.score > b_score:
                    bestgene = gene
                    b_score = gene.score
            logger.debug('b_score = %f', b_score)

        self.best_gene_data.append(
            [bestgene.values[:], bestgene.score, self.iteration_count])
        self.population.lemming()
        self.population.breed()
        self.decloner()
        logger.debug('iteration: %d', self.iteration_count)

        return None
    def calculate_cost(self, state_handle, period, site_indices):
        """Calculate the cost of adding new transmission connections to the network,
        from the list of active sites provided.
        
        Inputs:
            state_handle: The state_handle, as returned by get_startup_state.
            site_indices: A list of sites requiring transmission connections, 
                which may include duplicates.

        Outputs:
            cost: The cost in $M for building the new transmission.
        """
        curr_conf = self.period_configs[period]

        uniq_sites = list(set(site_indices))

        cost = 0.0
        for site in uniq_sites:
            try:
                cost += self.dist_map[site] * curr_conf['cost_per_km']
            except KeyError:
                raise mureilexception.ConfigException(
                    'Site ' + str(site) + ' is not in transmission map.', {})

        return cost
    def run(self, extra_data=None):
        start_time = time.time()
        logger.critical('Run started at %s', time.ctime())

        if (not self.is_configured):
            msg = 'run requested, but txmultimastersimple is not configured'
            logger.critical(msg)
            raise mureilexception.ConfigException(msg, {})

        try:
            self.algorithm.prepare_run()
            for i in range(self.config['iterations']):
                self.algorithm.do_iteration()
                if ((self.config['output_frequency'] > 0)
                        and ((i % self.config['output_frequency']) == 0)):
                    logger.info('Interim results at iteration %d', i)
                    self.output_results(iteration=i)

        except mureilexception.AlgorithmException:
            # Insert here something special to do if debugging
            # such an exception is required.
            # self.finalise will be called by the caller
            raise

        logger.critical('Run time: %.2f seconds', (time.time() - start_time))

        results = self.output_results(iteration=self.config['iterations'],
                                      final=True)

        return results
    def complete_configuration_pre_expand(self):
        """Complete the configuration prior to expanding the
        period configs. 
        
        This implementation checks that the lifetime_yrs is a multiple
        of time_period_yrs, and sets the startup state and params_to_site from the
        configuration strings.
        """
        
        time_period_yrs = self.config['time_period_yrs']
        lifetime_yrs = self.config['lifetime_yrs']
        error = None
        if isinstance(lifetime_yrs, dict):
            for value in lifetime_yrs.itervalues():
                div = value / time_period_yrs
                if not (float(int(div)) == div):
                    error = value
        else:
            div = lifetime_yrs / time_period_yrs
            if not (float(int(div)) == div):
                error = lifetime_yrs
        
        if error is not None:
            msg = ('In section ' + self.config['section'] + ', lifetime_yrs = ' +
                str(error) + ' which is required to be a multiple of time_period_yrs of ' +
                str(time_period_yrs))
            raise mureilexception.ConfigException(msg, {})

        # Set the startup state and the params to site from the configuration strings.
        if self.config['startup_data_string'] is not None:
            self.set_startup_state(self.config['startup_data_string'])
        
        if len(self.config['params_to_site_data_string']) > 0:
            self.params_to_site = self.config['params_to_site_data_string']
示例#7
0
    def solve_multiple_steps(self, market, multi_demand, multi_generation):
        """Solve the LP in the market object, for quantity values in a matrix for
        demand and generation, which correspond to the bids and offer nodes when
        the market object was created by build_optimisation.
        
        Inputs:
            market: a market optimisation created by build_optimisation
            multi_demand: a matrix of quantities bid, corresponding to bid nodes
            multi_generation: a matrix of quantities offered, corresponding to offer nodes
            
        Outputs:
            results: if success == True, a dict containing the following, or None:
                scheduled_bids: a matrix of scheduled bids, corresponding to multi_demand
                scheduled_offers: a matrix of scheduled offers, corresponding to multi_generation
            solutions: The solutions object, for debug use.

        Exceptions:
            will raise SolverException if either the solver failed to reach an optimal
                solution, or the solver was not run due to rejection by the 
                reject_outright_proportion check on total demand and total supply.
        """
        logger.debug('multi demand')
        logger.debug(multi_demand)
        logger.debug('multi generation')
        logger.debug(multi_generation)
        solutions = []

        if multi_demand.size[1] != multi_generation.size[1]:
            msg = ('multi_demand.size[0] = ' + str(multi_demand.size[0]) +
                   ', multi_demand.size[1] = ' + str(multi_demand.size[1]) +
                   ', multi_generation.size[0] = ' +
                   str(multi_generation.size[0]) +
                   ', multi_generation.size[1] = ' +
                   str(multi_generation.size[1]))
            raise mureilexception.ConfigException(msg, {})

        for j in range(multi_generation.size[1]):
            # Check here that total demand isn't heaps more than total supply
            tot_d = np.sum(multi_demand[:, j])
            tot_g = np.sum(multi_generation[:, j])
            if (tot_d / tot_g) > self.config['reject_outright_proportion']:
                msg = 'Reject outright ' + str(tot_d / tot_g)
                raise mureilexception.SolverException(msg,
                                                      {'prop': tot_d / tot_g})
            self.update_program(market, multi_demand[:, j],
                                multi_generation[:, j])
            this_sol = self.solve(market)
            solutions.append(this_sol)

        results = {}
        schedules = cvx.matrix([s['x'].T for s in solutions]).T
        results['scheduled_bids'] = self.scheduled_bids(market, schedules)
        results['scheduled_offers'] = self.scheduled_offers(market, schedules)
        return results, solutions
    def complete_configuration(self):
        # Check the pump_round_trip is <= 1
        if self.config['pump_round_trip'] > 1:
            msg = (
                'BasicPumpedHydro requires pump_round_trip to be less than 1. '
                + ' Value = {:.3f}'.format(self.config['pump_round_trip']))
            logger.critical(msg)
            raise mureilexception.ConfigException(msg, {})

        # Pre-calculate these for improved speed
        # Instead of calculating explicitly the water that's pumped up, calculate
        # the amount of electricity that's stored.
        # Adjust here for the timestep - elec_res is in units of MW-timesteps.
        # so 1 MWh = (60/timestep) MW-timesteps
        self.elec_res = (1 / self.config['timestep_hrs']) * (
            float(self.config['starting_level']) /
            float(self.config['water_factor']))
        self.elec_cap = (1 / self.config['timestep_hrs']) * (float(
            self.config['dam_capacity']) / float(self.config['water_factor']))
        self.pump_round_trip_recip = 1 / self.config['pump_round_trip']
        self.is_configured = True
示例#9
0
    def complete_configuration_pre_expand(self):
        """Complete the configuration by setting the param-site map and pre-calculating the
        fuel cost in $m/mwh.
        """

        txmultigeneratormultisite.TxMultiGeneratorMultiSite.complete_configuration_pre_expand(self)
        
        if isinstance(self.config['site_index'], dict):
            msg = ('In model ' + self.config['model'] + 
                ', the site_index parameter must not vary with time.')
            raise mureilexception.ConfigException(msg, {})
            
        self.params_to_site = numpy.array([self.config['site_index']])
        
        fuel_price = self.config['fuel_price_mwh']
        if isinstance(fuel_price, dict):
            self.config['fuel_price_mwh_m'] = fpm = {}
            for key, value in fuel_price:
                fpm[key] = value / 1e6
        else:
            self.config['fuel_price_mwh_m'] = fuel_price / 1e6
示例#10
0
    def complete_configuration_post_expand(self):
        """Read in the grid information and site->node information.
        """

        self.grid = grid_data_loader.Grid()
        self.grid.load(self.config['grid_input_dir'],
                       self.config['grid_filenames'],
                       self.config['grid_remove_slack_nodes'])

        self.nodes = []
        for node in self.grid.nodes:
            self.nodes.append(node['name'])

        # and the site->node map - just a simple CSV read - would be neater
        # to integrate this into the data system as it refers to generator indices.

        import csv
        # both site_to_node and site_connection_cost are dicts on site_index
        self.site_to_node = {}
        self.site_connection_cost = {}

        with open(self.config['site_filename'], 'rU') as n:
            reader = csv.reader(n)
            for row in reader:
                if reader.line_num == 1:
                    continue
                else:
                    site_index = int(row[0])
                    node_name = row[1]
                    if node_name not in self.nodes:
                        msg = (
                            'When reading in transmission configuration, node '
                            + node_name +
                            ' is mapped to a site index, but is not in grid.')
                        raise mureilexception.ConfigException(msg, {})

                    self.site_to_node[site_index] = node_name
                    self.site_connection_cost[site_index] = float(row[2])
示例#11
0
    def run(self, extra_data=None):
        start_time = time.time()
        logger.critical('Run started at %s', time.ctime())

        if (not self.is_configured):
            msg = 'run requested, but GeTxMultiMaster is not configured'
            logger.critical(msg)
            raise mureilexception.ConfigException(msg, {})

        # Read in the json data for generator capacity
        period_lists, startup_lists, demand_settings = self.load_js(extra_data)

        # Build up the results expected by the GE demo web code from the
        # results.
        all_years_out = {}

        # Compute an annual total for generation
        output_multiplier = (self.global_config['variable_cost_mult'] /
                             float(self.global_config['time_period_yrs']))

        cuml_cost = 0.0

        results = self.calc_list_cost(period_lists, startup_lists,
                                      demand_settings)

        for i in range(len(self.run_periods)):
            period = self.run_periods[i]

            all_years_out[str(period)] = period_out = {}

            this_res = results['periods'][period]

            # Output, in MWh
            period_out['output'] = output_section = {}

            # Cost, in $M
            period_out['cost'] = cost_section = {}

            # Total carbon emissions
            period_out['co2_tonnes'] = 0.0

            ts_demand = this_res['generators']['demand']['other']['ts_demand']

            period_out['demand'] = '{:.2f}'.format(
                abs(sum(ts_demand)) * self.global_config['timestep_hrs'] *
                output_multiplier)

            this_period_cost = 0.0
            this_period_carbon = 0.0

            for gen_type in this_res['generators']:
                this_data = this_res['generators'][gen_type]

                # Total output, in MWh per annum
                output_section[gen_type] = '{:.2f}'.format(
                    this_data['total_supply_period'] /
                    self.global_config['time_period_yrs'])

                # Total cost, per decade
                cost_section[gen_type] = this_data['cost']
                this_period_cost += this_data['cost']
                # or as a string:
                #                cost_section[gen_type] = '{:.2f}'.format(this_data['cost'])

                # Total carbon, per decade
                this_period_carbon += sum(this_data['carbon_emissions_period'])

            # Total cumulative cost, with discounting
            # This assumes the costs are all incurred at the beginning of
            # each period (a simplification)
            period_out['period_cost'] = this_period_cost
            cuml_cost += this_period_cost / (
                (1 + (self.config['discount_rate'] / 100))
                **(float(self.global_config['time_period_yrs']) * i))
            period_out['discounted_cumulative_cost'] = cuml_cost

            period_out['reliability'] = this_res['generators'][
                'missed_supply']['other']['reliability']
            period_out['co2_tonnes'] = this_period_carbon

        return all_years_out
示例#12
0
    def load_js(self, json_data):
        """ Input: JSON data structure with info on generators and demand management
                   at different time periods.
            Output: None
            Reads in the data and computes the params for each time period.
        """

        generators = json.loads(json_data)['selections']['generators']

        ## Only coal, gas, wind and solar are handled, and only one of each.
        ## hydro is awaiting a rainfall-based model.

        self.total_params = {}
        self.inc_params = {}

        gen_total_table = {}
        gen_inc_table = {}
        year_list = self.config['year_list']

        gen_type_list = ['coal', 'gas', 'wind', 'solar']
        gen_param_counts = {}

        # Initialise the tables of capacity
        for gen_type in gen_type_list:
            gen_param_counts[gen_type] = self.gen_list[
                gen_type].get_param_count()
            gen_total_table[gen_type] = numpy.zeros(
                (len(self.config['year_list']), gen_param_counts[gen_type]))
            gen_inc_table[gen_type] = numpy.zeros(
                (len(self.config['year_list']), gen_param_counts[gen_type]))

        # Fill in the tables of capacity
        for gen in generators:
            gen_type = gen['type']
            if gen_type not in gen_type_list:
                msg = 'Generator ' + str(gen_type) + ' ignored'
                logger.warning(msg)
            else:
                this_total_table = gen_total_table[gen_type]
                this_inc_table = gen_inc_table[gen_type]

                loc_index = self.find_loc_index(gen)
                if (loc_index >= gen_param_counts[gen_type]):
                    msg = ('Generator ' + gen['id'] + ' looked up index as ' +
                           str(loc_index) + ' but the ' + gen_type +
                           ' has data for ' + str(gen_param_counts[gen_type]) +
                           ' sites.')
                    raise mureilexception.ConfigException(msg, {})

                # build date could be specified as earlier, so capex is not paid.
                build_index = numpy.where(
                    numpy.array(year_list) == str(gen['decade']))
                if len(build_index[0] > 0):
                    build_index = build_index[0][0]
                else:
                    build_index = -1

                decommission_index = numpy.where(
                    numpy.array(year_list) == str(gen['decomission']))
                if len(decommission_index[0] > 0):
                    decommission_index = decommission_index[0][0]
                else:
                    decommission_index = len(year_list) - 1

                # accumulate new capacity in the incremental list
                if build_index >= 0:
                    this_inc_table[build_index][loc_index] += gen['capacity']

                # and add the new capacity to the total across all years until decommissioning
                start_fill = build_index
                if (build_index == -1):
                    start_fill = 0
                for i in range(start_fill, decommission_index + 1):
                    this_total_table[i][loc_index] += gen['capacity']

        # Convert the tables of capacity to params for the sim
        for i in range(0, len(year_list)):
            this_total_params = numpy.zeros(self.param_count)
            this_inc_params = numpy.zeros(self.param_count)

            for gen_type in ['coal', 'wind', 'solar', 'gas']:
                param_ptr = self.gen_params[gen_type]
                if (param_ptr[0] < param_ptr[1]) and (gen_type
                                                      in gen_total_table):
                    this_total_params[param_ptr[0]:param_ptr[1]] = (
                        gen_total_table[gen_type][i])
                    this_inc_params[param_ptr[0]:param_ptr[1]] = (
                        gen_inc_table[gen_type][i])

            self.total_params[str(year_list[i])] = this_total_params
            self.inc_params[str(year_list[i])] = this_inc_params

        self.demand_settings = json.loads(json_data)['selections']['demand']
示例#13
0
 def gene_test_undef(self, dummy):
     raise mureilexception.ConfigException(
         'gene_test_callback not set in geneticalgorithm.py',
         __name__ + '.gene_test_undef', [])
示例#14
0
    def set_config(self, full_config, extra_data):

        # Master explicitly does not copy in the global variables. It is too confusing
        # to combine those with flags, defaults and values defined in the config files.
        self.load_initial_config(full_config['Master'])

        # Get the global variables
        mureilbuilder.check_section_exists(full_config, self.config['global'])
        if 'model' not in full_config[self.config['global']]:
            full_config[self.config['global']][
                'model'] = 'tools.globalconfig.GlobalBase'
        self.global_calc = mureilbuilder.create_instance(
            full_config, None, self.config['global'],
            mureilbase.ConfigurableInterface)
        self.global_config = self.global_calc.get_config()

        # Now check the dispatch_order, to get a list of the generators
        for gen in self.config['dispatch_order']:
            self.config_spec += [(gen, None, None)]

        self.update_from_config_spec()
        self.check_config()

        self.dispatch_order = self.config['dispatch_order']

        # Set up the data class and get the data, and compute the global parameters
        self.data = mureilbuilder.create_instance(
            full_config, self.global_config, self.config['data'],
            mureilbase.DataSinglePassInterface)
        self.global_calc.update_config(
            {'data_ts_length': self.data.get_ts_length()})
        self.global_calc.post_data_global_calcs()
        self.global_config = self.global_calc.get_config()

        # Instantiate the transmission model
        if self.config['transmission'] in full_config:
            self.transmission = mureilbuilder.create_instance(
                full_config, self.global_config, self.config['transmission'],
                configurablebase.ConfigurableMultiBase,
                self.config['run_periods'])
            mureilbuilder.supply_single_pass_data(self.transmission, self.data,
                                                  self.config['transmission'])
        else:
            self.transmission = None

        # Instantiate the generator objects, set their data
        self.gen_list = {}

        for i in range(len(self.dispatch_order)):
            gen_type = self.dispatch_order[i]

            # Build the generator instances
            gen = mureilbuilder.create_instance(
                full_config, self.global_config, self.config[gen_type],
                txmultigeneratorbase.TxMultiGeneratorBase,
                self.config['run_periods'])
            self.gen_list[gen_type] = gen

            # Supply data as requested by the generator
            mureilbuilder.supply_single_pass_data(gen, self.data, gen_type)

        # Check that run_periods increases by time_period_yrs
        self.run_periods = self.config['run_periods']
        if len(self.run_periods) > 1:
            run_period_diffs = numpy.diff(self.run_periods)
            if (not (min(run_period_diffs)
                     == self.global_config['time_period_yrs'])
                    or not (max(run_period_diffs)
                            == self.global_config['time_period_yrs'])):
                raise mureilexception.ConfigException(
                    'run_periods must be separated by time_period_yrs', {})

        self.period_count = len(self.run_periods)
        self.is_configured = True
示例#15
0
    def set_config(self, full_config, extra_data):

        # Master explicitly does not copy in the global variables. It is too confusing
        # to combine those with flags, defaults and values defined in the config files.
        self.load_initial_config(full_config['Master'])

        # Get the global variables
        mureilbuilder.check_section_exists(full_config, self.config['global'])
        if 'model' not in full_config[self.config['global']]:
            full_config[self.config['global']][
                'model'] = 'tools.globalconfig.GlobalBase'
        self.global_calc = mureilbuilder.create_instance(
            full_config, None, self.config['global'],
            mureilbase.ConfigurableInterface)
        self.global_config = self.global_calc.get_config()

        # Now check the dispatch_order, to get a list of the generators
        for gen in self.config['dispatch_order']:
            self.config_spec += [(gen, None, None)]

        self.update_from_config_spec()
        self.check_config()

        self.dispatch_order = self.config['dispatch_order']

        # Set up the data class and get the data, and compute the global parameters
        self.data = mureilbuilder.create_instance(
            full_config, self.global_config, self.config['data'],
            mureilbase.DataSinglePassInterface)
        self.global_calc.update_config(
            {'data_ts_length': self.data.get_ts_length()})
        self.global_calc.post_data_global_calcs()
        self.global_config = self.global_calc.get_config()

        # Instantiate the generator objects, set their data, determine their param requirements
        param_count = 0
        self.gen_list = {}
        self.gen_params = {}
        start_values_min = []
        start_values_max = []

        for i in range(len(self.dispatch_order)):
            gen_type = self.dispatch_order[i]

            # Build the generator instances
            gen = mureilbuilder.create_instance(
                full_config, self.global_config, self.config[gen_type],
                singlepassgenerator.SinglePassGeneratorBase)
            self.gen_list[gen_type] = gen

            # Supply data as requested by the generator
            mureilbuilder.supply_single_pass_data(gen, self.data, gen_type)

            # Determine how many parameters this generator requires and
            # allocate the slots in the params list
            params_req = gen.get_param_count()
            if (params_req == 0):
                self.gen_params[gen_type] = (0, 0)
            else:
                self.gen_params[gen_type] = (param_count,
                                             param_count + params_req)
                (starts_min, starts_max) = gen.get_param_starts()

                if len(starts_min) == 0:
                    start_values_min += (
                        (np.ones(params_req) *
                         self.global_config['min_param_val']).tolist())
                else:
                    start_values_min += starts_min

                if len(starts_max) == 0:
                    start_values_max += (
                        (np.ones(params_req) *
                         self.global_config['max_param_val']).tolist())
                else:
                    start_values_max += starts_max

            param_count += params_req

        self.param_count = param_count

        # Check if 'extra_data' has been provided, as a full gene to start at.
        # extra_data needs to be a dict with entry 'start_gene' that is a list
        # of integer values the same length as param_count.
        if extra_data is not None:
            if 'start_gene' in extra_data:
                if not (len(start_values_min) == param_count):
                    msg = (
                        'extra_data of start_gene passed to simplemureilmaster. '
                        + 'Length expected = {:d}, found = {:d}'.format(
                            param_count, len(start_values_min)))
                    raise mureilexception.ConfigException(msg, {})
                else:
                    start_values_min = extra_data['start_gene']
                    start_values_max = extra_data['start_gene']

        # Instantiate the genetic algorithm
        mureilbuilder.check_section_exists(full_config,
                                           self.config['algorithm'])
        algorithm_config = full_config[self.config['algorithm']]
        algorithm_config['min_len'] = algorithm_config['max_len'] = param_count
        algorithm_config['start_values_min'] = start_values_min
        algorithm_config['start_values_max'] = start_values_max
        algorithm_config['gene_test_callback'] = self.gene_test
        self.algorithm = mureilbuilder.create_instance(
            full_config, self.global_config, self.config['algorithm'],
            mureilbase.ConfigurableInterface)

        self.is_configured = True
示例#16
0
 def get_timeseries(self, ts_name):
     try:
         return self.data[ts_name]
     except:
         msg = 'Timeseries ' + str(ts_name) + ' requested, but not available.'
         raise mureilexception.ConfigException(msg, {})
示例#17
0
    def complete_configuration(self):
        self.data = {}

        for list_type in [
                'ts_float_list', 'ts_int_list', 'other_float_list',
                'other_int_list'
        ]:

            for series_name in self.config[list_type]:
                infile = self.config['dir'] + self.config[series_name +
                                                          '_file']

                try:
                    f = nc.NetCDFFile(infile)
                except:
                    msg = ('File ' + infile + ' for data series ' +
                           series_name + ' was not opened.')
                    raise mureilexception.ConfigException(msg, {})

                try:
                    vbl = f.variables[self.config[series_name + '_vbl']]
                except:
                    msg = ('Variable ' + self.config[series_name + '_vbl'] +
                           ' not found in file ' + infile)
                    raise mureilexception.ConfigException(msg, {})

                dims = len(vbl.shape)

                if (dims == 1):
                    temp = vbl[:]
                elif (dims == 2):
                    temp = vbl[:, :]
                else:
                    msg = 'Data series ' + series_name + ' has more than 2 dimensions, so is not handled.'
                    raise mureilexception.ConfigException(msg, {})

                if 'float' in list_type:
                    if not mureiltypes.check_ndarray_float(temp, True):
                        self.data[series_name] = numpy.array(temp, dtype=float)
                    else:
                        self.data[series_name] = temp
                else:
                    if not mureiltypes.check_ndarray_int(temp, True):
                        self.data[series_name] = numpy.array(temp, dtype=int)
                    else:
                        self.data[series_name] = temp

        for list_type in ['ts_csv_list']:
            for series_name in self.config[list_type]:
                infile = self.config['dir'] + self.config[series_name +
                                                          '_file']
                temp = []

                try:
                    with open(infile, 'rU') as n:
                        reader = csv.reader(n)
                        for row in reader:
                            if reader.line_num == 1:
                                self.data[series_name + '_hdr'] = row[1:]
                            else:
                                if reader.line_num == 2:
                                    temp = [map(float, (row[1:]))]
                                else:
                                    temp.append(map(float, (row[1:])))

                    self.data[series_name] = numpy.array(temp, dtype=float)
                    if not mureiltypes.check_ndarray_float(temp, True):
                        self.data[series_name] = numpy.array(temp, dtype=float)
                    else:
                        self.data[series_name] = temp

                except:
                    msg = ('File ' + infile + ' for data series ' +
                           series_name +
                           ' was not opened or had an error in reading.')
                    raise mureilexception.ConfigException(msg, {})

        # Now apply the NaN filter to the ts lists, but note that the integer
        # ones are not identified as nan.
        all_ts = self.config['ts_float_list'] + self.config[
            'ts_int_list'] + self.config['ts_csv_list']

        if len(all_ts) == 0:
            self.ts_length = 0
            logger.warning('No timeseries data defined')
        else:
            self.ts_length = self.data[all_ts[0]].shape[0]

            # Start with an array of 'False'
            nan_acc = (numpy.ones(self.ts_length) == 0)

            # Accumulate 'True' entries in nan_acc where NaN found in timeseries
            for ts_name in all_ts:
                ts_nan = numpy.isnan(self.data[ts_name])
                if ts_nan.ndim > 1:
                    ts_nan = ts_nan.any(1)

                # Check all the timeseries are the same length
                if not (len(ts_nan) == self.ts_length):
                    msg = ('Data series ' + ts_name +
                           ' is length {:d}, not matching {:d} of '.format(
                               len(ts_nan), self.ts_length) + all_ts[0])
                    raise mureilexception.ConfigException(msg, {})

                nan_acc = numpy.logical_or(nan_acc, ts_nan)

            # Clean up the timeseries using slices
            nan_acc = numpy.logical_not(nan_acc)
            for ts_name in all_ts:
                if self.data[ts_name].ndim == 1:
                    self.data[ts_name] = self.data[ts_name][nan_acc]
                else:
                    self.data[ts_name] = self.data[ts_name][nan_acc, :]

            self.ts_length = self.data[all_ts[0]].shape[0]

        self.is_configured = True

        return None
示例#18
0
 def get_ts_length(self):
     try:
         return self.ts_length
     except:
         msg = 'Data ts length requested, but ts_length not available'
         raise mureilexception.ConfigException(msg, {})
示例#19
0
    def run(self, extra_data):
        if (not self.is_configured):
            msg = 'run requested, but GeMureilMaster is not configured'
            logger.critical(msg)
            raise mureilexception.ConfigException(msg, {})

        # Read in the json data for generator capacity
        self.load_js(extra_data)

        all_years_out = {}

        # Compute an annual total for generation
        output_multiplier = (self.global_config['variable_cost_mult'] /
                             float(self.global_config['time_period_yrs']))

        cuml_cost = 0.0

        for year_index in range(len(self.config['year_list'])):

            ## MG - this is a hack. The config should be set with all
            ## of the values at the start, and then be passed the year,
            ## not have them updated each time. This is ok here as it's
            ## only evaluated once anyway.

            results = self.evaluate_results(year_index)

            year = self.config['year_list'][year_index]

            # print results['gen_desc']

            all_years_out[str(year)] = year_out = {}

            # Output, in MWh
            year_out['output'] = output_section = {}

            # Cost, in $M
            year_out['cost'] = cost_section = {}

            # Total carbon emissions
            year_out['co2_tonnes'] = 0.0

            # Total demand, in MWh per annum
            for generator_type, value in results['other'].iteritems():
                if value is not None:
                    if 'ts_demand' in value:
                        year_out['demand'] = '{:.2f}'.format(
                            abs(sum(value['ts_demand'])) *
                            self.global_config['timestep_hrs'] *
                            output_multiplier)

            # Total output, in MWh per annum
            for gen_type, vals in results['output'].iteritems():
                output_section[gen_type] = '{:.2f}'.format(
                    sum(vals) * self.global_config['timestep_hrs'] *
                    output_multiplier)

            # Total cost, per decade
            this_period_cost = 0.0
            for gen_type, value in results['cost'].iteritems():
                cost_section[gen_type] = value
                this_period_cost += value
# or as a string:
#                cost_section[generator_type] = '{:.2f}'.format(value)

# Total cumulative cost, with discounting
# This assumes the costs are all incurred at the beginning of
# each period (a simplification)
            year_out['period_cost'] = this_period_cost
            cuml_cost += this_period_cost / (
                (1 + (self.config['discount_rate'] / 100))
                **(float(self.global_config['time_period_yrs']) * year_index))
            year_out['discounted_cumulative_cost'] = cuml_cost

            for gen_type, value in results['other'].iteritems():
                if value is not None:
                    if 'reliability' in value:
                        year_out['reliability'] = value['reliability']
                    if 'carbon' in value:
                        year_out['co2_tonnes'] += value['carbon']

            if 'reliability' not in year_out:
                year_out['reliability'] = 100

        return all_years_out
    def set_config(self, full_config, extra_data):

        # Master explicitly does not copy in the global variables. It is too confusing
        # to combine those with flags, defaults and values defined in the config files.
        self.load_initial_config(full_config['Master'])

        # Get the global variables
        mureilbuilder.check_section_exists(full_config, self.config['global'])
        if 'model' not in full_config[self.config['global']]:
            full_config[self.config['global']][
                'model'] = 'tools.globalconfig.GlobalBase'
        self.global_calc = mureilbuilder.create_instance(
            full_config, None, self.config['global'],
            mureilbase.ConfigurableInterface)
        self.global_config = self.global_calc.get_config()

        # Now check the dispatch_order, to get a list of the generators
        for gen in self.config['dispatch_order']:
            self.config_spec += [(gen, None, None)]

        self.update_from_config_spec()
        self.check_config()

        self.dispatch_order = self.config['dispatch_order']

        # Set up the data class and get the data, and compute the global parameters
        self.data = mureilbuilder.create_instance(
            full_config, self.global_config, self.config['data'],
            mureilbase.DataSinglePassInterface)
        self.global_calc.update_config(
            {'data_ts_length': self.data.get_ts_length()})
        self.global_calc.post_data_global_calcs()
        self.global_config = self.global_calc.get_config()

        # Instantiate the transmission model
        if self.config['transmission'] in full_config:
            self.transmission = mureilbuilder.create_instance(
                full_config, self.global_config, self.config['transmission'],
                configurablebase.ConfigurableMultiBase,
                self.config['run_periods'])
            mureilbuilder.supply_single_pass_data(self.transmission, self.data,
                                                  self.config['transmission'])
        else:
            self.transmission = None

        # Instantiate the generator objects, set their data, determine their param requirements
        param_count = 0
        self.gen_list = {}
        self.gen_params = {}

        run_period_len = len(self.config['run_periods'])
        start_values_min = numpy.array([[]]).reshape(run_period_len, 0)
        start_values_max = numpy.array([[]]).reshape(run_period_len, 0)

        for i in range(len(self.dispatch_order)):
            gen_type = self.dispatch_order[i]

            # Build the generator instances
            gen = mureilbuilder.create_instance(
                full_config, self.global_config, self.config[gen_type],
                txmultigeneratorbase.TxMultiGeneratorBase,
                self.config['run_periods'])
            self.gen_list[gen_type] = gen

            # Supply data as requested by the generator
            mureilbuilder.supply_single_pass_data(gen, self.data, gen_type)

            # Determine how many parameters this generator requires and
            # allocate the slots in the params list
            params_req = gen.get_param_count()
            if (params_req == 0):
                self.gen_params[gen_type] = (0, 0)
            else:
                self.gen_params[gen_type] = (param_count,
                                             param_count + params_req)

                start_values_min, start_values_max = mureilbuilder.add_param_starts(
                    gen.get_param_starts(), params_req, self.global_config,
                    run_period_len, start_values_min, start_values_max)

            param_count += params_req

        start_values_min = start_values_min.reshape(run_period_len *
                                                    param_count)
        start_values_max = start_values_max.reshape(run_period_len *
                                                    param_count)

        self.param_count = param_count
        # Check that run_periods increases by time_period_yrs
        self.run_periods = self.config['run_periods']
        if len(self.run_periods) > 1:
            run_period_diffs = numpy.diff(self.run_periods)
            if (not (min(run_period_diffs)
                     == self.global_config['time_period_yrs'])
                    or not (max(run_period_diffs)
                            == self.global_config['time_period_yrs'])):
                raise mureilexception.ConfigException(
                    'run_periods must be separated by time_period_yrs', {})

        self.period_count = len(self.run_periods)
        self.total_param_count = param_count * self.period_count

        # Check if 'extra_data' has been provided, as a full gene to start at.
        # extra_data needs to be a dict with entry 'start_gene' that is a list
        # of integer values the same length as param_count.
        if extra_data is not None:
            if 'start_gene' in extra_data:
                if not (len(start_values_min) == self.total_param_count):
                    msg = (
                        'extra_data of start_gene passed to txmultimastersimple. '
                        + 'Length expected = {:d}, found = {:d}'.format(
                            self.total_param_count, len(start_values_min)))
                    raise mureilexception.ConfigException(msg, {})

                start_values_min = extra_data['start_gene']
                start_values_max = extra_data['start_gene']

        # Instantiate the genetic algorithm
        mureilbuilder.check_section_exists(full_config,
                                           self.config['algorithm'])
        algorithm_config = full_config[self.config['algorithm']]
        algorithm_config['min_len'] = algorithm_config[
            'max_len'] = self.total_param_count
        algorithm_config['start_values_min'] = start_values_min
        algorithm_config['start_values_max'] = start_values_max
        algorithm_config['gene_test_callback'] = self.gene_test
        self.algorithm = mureilbuilder.create_instance(
            full_config, self.global_config, self.config['algorithm'],
            mureilbase.ConfigurableInterface)

        self.is_configured = True
示例#21
0
    def set_config(self, full_config, extra_data):

        # Master explicitly does not copy in the global variables. It is too confusing
        # to combine those with flags, defaults and values defined in the config files.
        self.load_initial_config(full_config['Master'])

        # Get the global variables
        mureilbuilder.check_section_exists(full_config, self.config['global'])
        if 'model' not in full_config[self.config['global']]:
            full_config[self.config['global']][
                'model'] = 'tools.globalconfig.GlobalBase'
        self.global_calc = mureilbuilder.create_instance(
            full_config, None, self.config['global'],
            mureilbase.ConfigurableInterface)
        self.global_config = self.global_calc.get_config()

        # Now check the list of the generators
        for gen in self.config['generators']:
            self.config_spec += [(gen, None, None)]

        self.update_from_config_spec()
        self.check_config()

        self.generators = self.config['generators']

        # Set up the data class and get the data, and compute the global parameters
        self.data = mureilbuilder.create_instance(
            full_config, self.global_config, self.config['data'],
            mureilbase.DataSinglePassInterface)
        self.global_calc.update_config(
            {'data_ts_length': self.data.get_ts_length()})
        self.global_calc.post_data_global_calcs()
        self.global_config = self.global_calc.get_config()

        ## The master here takes 'global' variables, if needed and available, to get at the carbon_price_m,
        ## variable_cost_mult and time_scale_up_mult values, which it then expands to all time periods,
        ## for use later.
        for param_name in [
                'carbon_price_m', 'variable_cost_mult', 'time_scale_up_mult'
        ]:
            if (param_name not in self.config) and (param_name
                                                    in self.global_config):
                self.config[param_name] = self.global_config[param_name]
        self.config_spec += [('carbon_price_m', float, None),
                             ('variable_cost_mult', float, None),
                             ('time_scale_up_mult', float, None)]
        self.check_config()
        self.expand_config(self.config['run_periods'])

        # Now instantiate the demand model
        self.demand = mureilbuilder.create_instance(
            full_config, self.global_config, self.config['demand'],
            configurablebase.ConfigurableMultiBase, self.config['run_periods'])

        # Supply data to the demand model
        mureilbuilder.supply_single_pass_data(self.demand, self.data, 'demand')

        # And instantiate the transmission model
        self.transmission = mureilbuilder.create_instance(
            full_config, self.global_config, self.config['transmission'],
            configurablebase.ConfigurableMultiBase, self.config['run_periods'])

        mureilbuilder.check_subclass(
            self.transmission, interfacesflowmaster.InterfaceTransmission)

        # Instantiate the generator objects, set their data, determine their param requirements,
        # and separate by dispatch type.
        param_count = 0

        # gen_list, gen_params are indexed by position in self.generators
        self.gen_list = [None] * len(self.generators)
        self.gen_params = [None] * len(self.generators)

        self.semisch_list = []
        self.instant_list = []
        self.ramp_list = []

        run_period_len = len(self.config['run_periods'])
        start_values_min = numpy.array([[]]).reshape(run_period_len, 0)
        start_values_max = numpy.array([[]]).reshape(run_period_len, 0)

        for i in range(len(self.generators)):
            gen_type = self.generators[i]

            # Build the generator instances
            gen = mureilbuilder.create_instance(
                full_config, self.global_config, self.config[gen_type],
                txmultigeneratorbase.TxMultiGeneratorBase,
                self.config['run_periods'])

            self.gen_list[i] = gen

            gen_details = gen.get_details()
            gen_dispatch = gen_details['dispatch']

            if (gen_dispatch == 'semischeduled'):
                self.semisch_list.append(i)
                mureilbuilder.check_subclass(
                    gen, interfacesflowmaster.InterfaceSemiScheduledDispatch)
            elif (gen_dispatch == 'instant'):
                self.instant_list.append(i)
                mureilbuilder.check_subclass(
                    gen, interfacesflowmaster.InterfaceInstantDispatch)
            elif (gen_dispatch == 'ramp'):
                self.ramp_list.append(i)
                mureilbuilder.check_subclass(
                    gen, interfacesflowmaster.InterfaceRampDispatch)
                msg = ("Generator " + gen_type +
                       " has dispatch type ramp, which is not yet implemented")
                raise mureilexception.ConfigException(msg, {})
            else:
                msg = ("Generator " + gen_type + " has dispatch type " +
                       gen_dispatch +
                       " which is not one of semischeduled, instant or ramp.")
                raise mureilexception.ConfigException(msg, {})

            # Supply data as requested by the generator
            mureilbuilder.supply_single_pass_data(gen, self.data, gen_type)

            # Determine how many parameters this generator requires and
            # allocate the slots in the params list
            params_req = gen.get_param_count()
            if (params_req == 0):
                self.gen_params[i] = (0, 0)
            else:
                self.gen_params[i] = (param_count, param_count + params_req)

                start_values_min, start_values_max = mureilbuilder.add_param_starts(
                    gen.get_param_starts(), params_req, self.global_config,
                    run_period_len, start_values_min, start_values_max)

            param_count += params_req

        start_values_min = start_values_min.reshape(run_period_len *
                                                    param_count)
        start_values_max = start_values_max.reshape(run_period_len *
                                                    param_count)

        self.param_count = param_count
        # Check that run_periods increases by time_period_yrs
        self.run_periods = self.config['run_periods']
        if len(self.run_periods) > 1:
            run_period_diffs = numpy.diff(self.run_periods)
            if (not (min(run_period_diffs)
                     == self.global_config['time_period_yrs'])
                    or not (max(run_period_diffs)
                            == self.global_config['time_period_yrs'])):
                raise mureilexception.ConfigException(
                    'run_periods must be separated by time_period_yrs', {})

        self.period_count = len(self.run_periods)
        self.total_param_count = param_count * self.period_count

        # Check if 'extra_data' has been provided, as a full gene to start at.
        # extra_data needs to be a dict with entry 'start_gene' that is a list
        # of integer values the same length as param_count.
        if extra_data is not None:
            if 'start_gene' in extra_data:
                if not (len(start_values_min) == self.total_param_count):
                    msg = (
                        'extra_data of start_gene passed to txmultimasterflow. '
                        + 'Length expected = {:d}, found = {:d}'.format(
                            self.total_param_count, len(start_values_min)))
                    raise mureilexception.ConfigException(msg, {})

                start_values_min = extra_data['start_gene']
                start_values_max = extra_data['start_gene']

        # Instantiate the market solver
        self.market_solver = mureilbuilder.create_instance(
            full_config, self.global_config, self.config['market_solver'],
            configurablebase.ConfigurableMultiBase, self.config['run_periods'])

        # Instantiate the genetic algorithm
        mureilbuilder.check_section_exists(full_config,
                                           self.config['algorithm'])
        algorithm_config = full_config[self.config['algorithm']]
        algorithm_config['min_len'] = algorithm_config[
            'max_len'] = self.total_param_count
        algorithm_config['start_values_min'] = start_values_min
        algorithm_config['start_values_max'] = start_values_max
        algorithm_config['gene_test_callback'] = self.gene_test
        self.algorithm = mureilbuilder.create_instance(
            full_config, self.global_config, self.config['algorithm'],
            mureilbase.ConfigurableInterface)

        self.is_configured = True