Example #1
0
def generate_simple_sets(model_run):
    """
    Generate basic sets for a given pre-processed ``model_run``.

    Parameters
    ----------
    model_run : AttrDict

    """
    sets = AttrDict()

    flat_techs = model_run.techs.as_dict(flat=True)
    flat_locations = model_run.locations.as_dict(flat=True)

    sets.resources = set(
        flatten_list(v for k, v in flat_techs.items() if '.carrier' in k))

    sets.carriers = sets.resources - set(['resource'])

    sets.carrier_tiers = set(
        key.split('.carrier_')[1] for key in flat_techs.keys()
        if '.carrier_' in key)

    sets.costs = set(
        k.split('costs.')[-1].split('.')[0] for k in flat_locations.keys()
        if '.costs.' in k)

    sets.locs = set(model_run.locations.keys())

    sets.techs_non_transmission = set(k for k, v in model_run.techs.items()
                                      if v.inheritance[-1] != 'transmission')

    sets.techs_transmission_names = set(k for k, v in model_run.techs.items()
                                        if v.inheritance[-1] == 'transmission')

    # This builds the "tech:loc" expansion of transmission technologies
    techs_transmission = set()
    for loc_name, loc_config in model_run.locations.items():
        for link_name, link_config in loc_config.get('links', {}).items():
            for tech_name in link_config.techs:
                techs_transmission.add('{}:{}'.format(tech_name, link_name))
    sets.techs_transmission = techs_transmission

    sets.techs = sets.techs_non_transmission | sets.techs_transmission_names

    # this extracts location coordinate information
    coordinates = set(
        k.split('.')[-1] for k in flat_locations.keys()
        if '.coordinates.' in k)

    if coordinates:
        sets.coordinates = coordinates

    # `timesteps` set is built from the results of timeseries_data processing
    sets.timesteps = list(model_run.timesteps.astype(str))
    model_run.del_key('timesteps')

    # `techlists` are strings with comma-separated techs used for grouping in
    # some model-wide constraints
    sets.techlists = set()
    for k in model_run.model.get_key('group_share', {}).keys():
        sets.techlists.add(k)

    return sets
Example #2
0
def generate_simple_sets(model_run):
    """
    Generate basic sets for a given pre-processed ``model_run``.

    Parameters
    ----------
    model_run : AttrDict

    """
    sets = AttrDict()

    flat_techs = model_run.techs.as_dict(flat=True)
    flat_locations = model_run.locations.as_dict(flat=True)

    sets.resources = set(
        flatten_list(v for k, v in flat_techs.items() if '.carrier' in k))

    sets.carriers = sets.resources - set(['resource'])

    sets.carrier_tiers = set(
        key.split('.carrier_')[1] for key in flat_techs.keys()
        if '.carrier_' in key)

    sets.costs = set(
        k.split('costs.')[-1].split('.')[0] for k in flat_locations.keys()
        if '.costs.' in k)

    sets.locs = set(model_run.locations.keys())

    sets.techs_non_transmission = set()
    tech_groups = [
        'demand', 'supply', 'supply_plus', 'conversion', 'conversion_plus',
        'storage'
    ]
    for tech_group in tech_groups:
        sets['techs_{}'.format(tech_group)] = set(
            k for k, v in model_run.techs.items()
            if v.inheritance[-1] == tech_group)
        sets.techs_non_transmission.update(sets['techs_{}'.format(tech_group)])

    sets.techs_transmission_names = set(k for k, v in model_run.techs.items()
                                        if v.inheritance[-1] == 'transmission')

    # This builds the "tech:loc" expansion of transmission technologies
    techs_transmission = set()
    for loc_name, loc_config in model_run.locations.items():
        for link_name, link_config in loc_config.get('links', {}).items():
            for tech_name in link_config.techs:
                techs_transmission.add('{}:{}'.format(tech_name, link_name))
    sets.techs_transmission = techs_transmission

    sets.techs = sets.techs_non_transmission | sets.techs_transmission_names

    # this extracts location coordinate information
    coordinates = set(
        k.split('.')[-1] for k in flat_locations.keys()
        if '.coordinates.' in k)

    if coordinates:
        sets.coordinates = coordinates

    # `timesteps` set is built from the results of timeseries_data processing
    sets.timesteps = list(model_run.timesteps.astype(str))
    model_run.del_key('timesteps')

    # `techlists` are strings with comma-separated techs used for grouping in
    # some model-wide constraints
    sets.techlists = set()
    for k in model_run.model.get_key('group_share', {}).keys():
        sets.techlists.add(k)

    # `constraint_groups` are the group names per constraint that is defined
    # at a group level

    sets.group_constraints = set()
    group_constraints = AttrDict({
        name: data
        for name, data in model_run['group_constraints'].items()
        if data.get("exists", True)
    })
    if len(group_constraints.keys()) > 0:
        sets.group_constraints.update(
            i.split('.')[1] for i in group_constraints.as_dict_flat().keys()
            if i.split('.')[1] not in ['techs', 'locs'])
        for constr in sets.group_constraints:
            sets['group_names_' + constr] = set(
                k for k, v in group_constraints.items() if constr in v.keys())

    return sets
Example #3
0
def process_techs(config_model):

    default_palette_cycler = itertools.cycle(range(len(_DEFAULT_PALETTE)))

    result = AttrDict()
    errors = []
    debug_comments = AttrDict()

    for tech_id, tech_config in config_model.techs.items():

        # If a tech specifies ``exists: false``, we skip it entirely
        if not tech_config.get('exists', True):
            continue

        tech_result = AttrDict()

        # Add inheritance chain
        tech_result.inheritance = get_parents(tech_id, config_model)

        # CHECK: A tech's parent must lead to one of the built-in tech_groups
        builtin_tech_groups = checks.defaults_model.tech_groups.keys()
        if tech_result.inheritance[-1] not in builtin_tech_groups:
            errors.append(
                'tech {} must inherit from a built-in tech group'.format(tech_id)
            )

        # Process inheritance
        tech_result.essentials = AttrDict()
        tech_result.constraints = AttrDict()
        for parent in reversed(tech_result.inheritance):
            # Does the parent group have model-wide settings?
            parent_essentials = config_model.tech_groups[parent].essentials
            parent_systemwide_constraints = util.get_systemwide_constraints(
                config_model.tech_groups[parent]
            )
            for k in parent_essentials.as_dict_flat():
                debug_comments.set_key(
                    '{}.essentials.{}'.format(tech_id, k),
                    'From parent tech_group `{}`'.format(parent)
                )
            tech_result.essentials.union(parent_essentials, allow_override=True)
            tech_result.constraints.union(parent_systemwide_constraints, allow_override=True)

        # Add this tech's essentials and constraints, overwriting any essentials from parents
        tech_result.essentials.union(tech_config.essentials, allow_override=True)
        tech_result.constraints.union(
            util.get_systemwide_constraints(tech_config), allow_override=True
        )

        # Add allowed_constraints and required_constraints from base tech
        keys_to_add = ['required_constraints', 'allowed_constraints', 'allowed_costs']
        for k in keys_to_add:
            tech_result[k] = config_model.tech_groups[tech_result.inheritance[-1]].get(k, [])

        # CHECK: If necessary, populate carrier_in and carrier_out in essentials, but
        # also break on missing carrier data
        if 'carrier_in' not in tech_result.essentials:
            if tech_result.inheritance[-1] in ['supply', 'supply_plus']:
                tech_result.essentials.carrier_in = 'resource'
            elif tech_result.inheritance[-1] in ['demand', 'transmission',
                                                 'storage']:
                try:
                    tech_result.essentials.carrier_in = \
                        tech_result.essentials.carrier
                    debug_comments.set_key(
                        '{}.essentials.carrier_in'.format(tech_id),
                        'Set from essentials.carrier'
                    )
                except KeyError:
                    errors.append(
                        '`carrier` or `carrier_in` must be '
                        'defined for {}'.format(tech_id)
                    )
            else:
                errors.append(
                    '`carrier_in` must be defined for {}'.format(tech_id)
                )

        if 'carrier_out' not in tech_result.essentials:
            if tech_result.inheritance[-1] == 'demand':
                tech_result.essentials.carrier_out = 'resource'
            elif tech_result.inheritance[-1] in ['supply', 'supply_plus',
                                                 'transmission', 'storage']:
                try:
                    tech_result.essentials.carrier_out = \
                        tech_result.essentials.carrier
                except KeyError:
                    errors.append(
                        '`carrier` or `carrier_out` must be '
                        'defined for {}'.format(tech_id)
                    )
            else:
                errors.append(
                    '`carrier_out` must be defined for {}'.format(tech_id)
                )
        # Deal with primary carrier in/out for conversion_plus techs
        if tech_result.inheritance[-1] == 'conversion_plus':
            for direction in ['_in', '_out']:
                carriers = set(util.flatten_list([
                    v for k, v in tech_result.essentials.items()
                    if k.startswith('carrier' + direction)
                ]))
                primary_carrier = tech_result.essentials.get(
                    'primary_carrier' + direction, None
                )
                if primary_carrier is None and len(carriers) == 1:
                    tech_result.essentials['primary_carrier' + direction] = carriers.pop()
                elif primary_carrier is None and len(carriers) > 1:
                    errors.append(
                        'Primary_carrier{0} must be assigned for tech `{1}` as '
                        'there are multiple carriers{0}'.format(direction, tech_id)
                    )
                elif primary_carrier not in carriers:
                    errors.append(
                        'Primary_carrier{0} `{1}` not one of the available carriers'
                        '{0} for `{2}`'.format(direction, primary_carrier, tech_id)
                    )

        # If necessary, pick a color for the tech, cycling through
        # the hardcoded default palette
        if not tech_result.essentials.get_key('color', None):
            color = _DEFAULT_PALETTE[next(default_palette_cycler)]
            tech_result.essentials.color = color
            debug_comments.set_key(
                '{}.essentials.color'.format(tech_id),
                'From Calliope default palette')
        result[tech_id] = tech_result

    return result, debug_comments, errors
Example #4
0
def _check_tech(model_run, tech_id, tech_config, loc_id, warnings, errors, comments):
    """
    Checks individual tech/tech groups at specific locations.
    NOTE: Updates `warnings` and `errors` lists in-place.
    """
    if tech_id not in model_run.techs:
        warnings.append(
            'Tech {} was removed by setting ``exists: False`` - not checking '
            'the consistency of its constraints at location {}.'.format(tech_id, loc_id)
        )
        return warnings, errors

    required = model_run.techs[tech_id].required_constraints
    allowed = model_run.techs[tech_id].allowed_constraints
    allowed_costs = model_run.techs[tech_id].allowed_costs
    all_defaults = list(defaults.default_tech.constraints.keys())

    # Error if required constraints are not defined
    for r in required:
        # If it's a string, it must be defined
        single_ok = isinstance(r, str) and r in tech_config.constraints
        # If it's a list of strings, one of them must be defined
        multiple_ok = (
            isinstance(r, list) and
            any([i in tech_config.constraints for i in r])
        )
        if not single_ok and not multiple_ok:
            errors.append(
                '`{}` at `{}` fails to define '
                'all required constraints: {}'.format(tech_id, loc_id, required)
            )
            # print('{} -- {}-{}: {}, {}'.format(r, loc_id, tech_id, single_ok, multiple_ok))

    # If the technology is supply_plus, check if it has storage_cap_max. If yes, it needs charge rate
    if model_run.techs[tech_id].essentials.parent == 'supply_plus':
        if (any(['storage_cap_' in k for k in tech_config.constraints.keys()])
            and 'charge_rate' not in tech_config.constraints.keys()):
            errors.append(
                '`{}` at `{}` fails to define '
                'charge_rate, but is using storage'.format(tech_id, loc_id, required)
            )
    # If a technology is defined by units (i.e. integer decision variable), it must define energy_cap_per_unit
    if (any(['units_' in k for k in tech_config.constraints.keys()])
        and 'energy_cap_per_unit' not in tech_config.constraints.keys()):
        errors.append(
            '`{}` at `{}` fails to define energy_cap_per_unit when specifying '
            'technology in units_max/min/equals'.format(tech_id, loc_id, required)
        )

    # If a technology is defined by units & is a storage tech, it must define storage_cap_per_unit
    if (any(['units_' in k for k in tech_config.constraints.keys()])
            and model_run.techs[tech_id].essentials.parent in ['storage', 'supply_plus']
            and any(['storage' in k for k in tech_config.constraints.keys()])
            and 'storage_cap_per_unit' not in tech_config.constraints.keys()):
        errors.append(
            '`{}` at `{}` fails to define storage_cap_per_unit when specifying '
            'technology in units_max/min/equals'.format(tech_id, loc_id, required)
        )

    # If a technology is defines force_resource but is not in loc_techs_finite_resource
    if ('force_resource' in tech_config.constraints.keys() and
            loc_id + '::' + tech_id not in model_run.sets.loc_techs_finite_resource):

        warnings.append(
            '`{}` at `{}` defines force_resource but not a finite resource, so '
            'force_resource will not be applied'.format(tech_id, loc_id)
        )

    # Flatten required list and gather remaining unallowed constraints
    required_f = flatten_list(required)
    remaining = set(tech_config.constraints) - set(required_f) - set(allowed)

    # Error if something is defined that's not allowed, but is in defaults
    # Warn if something is defined that's not allowed, but is not in defaults
    # (it could be a misspelling)
    for k in remaining:
        if k in all_defaults:
            errors.append(
                '`{}` at `{}` defines non-allowed '
                'constraint `{}`'.format(tech_id, loc_id, k)
            )
        else:
            warnings.append(
                '`{}` at `{}` defines unrecognised '
                'constraint `{}` - possibly a misspelling?'.format(tech_id, loc_id, k)
            )

    # Error if an `export` statement does not match the given carrier_outs
    if 'export_carrier' in tech_config.constraints:
        essentials = model_run.techs[tech_id].essentials
        export = tech_config.constraints.export_carrier
        if (export and export not in [essentials.get_key(k, '')
                for k in ['carrier_out', 'carrier_out_2', 'carrier_out_3']]):
            errors.append(
                '`{}` at `{}` is attempting to export a carrier '
                'not given as an output carrier: `{}`'.format(tech_id, loc_id, export)
            )

    # Error if non-allowed costs are defined
    for cost_class in tech_config.get_key('costs', {}):
        for k in tech_config.costs[cost_class]:
            if k not in allowed_costs:
                errors.append(
                    '`{}` at `{}` defines non-allowed '
                    '{} cost: `{}`'.format(tech_id, loc_id, cost_class, k)
                )

    return warnings, errors
Example #5
0
def generate_simple_sets(model_run):
    """
    Generate basic sets for a given pre-processed ``model_run``.

    Parameters
    ----------
    model_run : AttrDict

    """
    sets = AttrDict()

    flat_techs = model_run.techs.as_dict(flat=True)
    flat_locations = model_run.locations.as_dict(flat=True)

    sets.resources = set(flatten_list(
        v for k, v in flat_techs.items()
        if '.carrier' in k
    ))

    sets.carriers = sets.resources - set(['resource'])

    sets.carrier_tiers = set(
        key.split('.carrier_')[1]
        for key in flat_techs.keys()
        if '.carrier_' in key
    )

    sets.costs = set(
        k.split('costs.')[-1].split('.')[0]
        for k in flat_locations.keys()
        if '.costs.' in k)

    sets.locs = set(model_run.locations.keys())

    sets.techs_non_transmission = set(
        k for k, v in model_run.techs.items()
        if v.inheritance[-1] != 'transmission')

    sets.techs_transmission_names = set(
        k for k, v in model_run.techs.items()
        if v.inheritance[-1] == 'transmission')

    # This builds the "tech:loc" expansion of transmission technologies
    techs_transmission = set()
    for loc_name, loc_config in model_run.locations.items():
        for link_name, link_config in loc_config.get('links', {}).items():
            for tech_name in link_config.techs:
                techs_transmission.add('{}:{}'.format(tech_name, link_name))
    sets.techs_transmission = techs_transmission

    sets.techs = sets.techs_non_transmission | sets.techs_transmission_names

    # this extracts location coordinate information
    coordinates = set(
        k.split('.')[-1] for k in flat_locations.keys()
        if '.coordinates.' in k)

    if coordinates:
        sets.coordinates = coordinates

    # `timesteps` set is built from the results of timeseries_data processing
    sets.timesteps = list(model_run.timesteps.astype(str))
    model_run.del_key('timesteps')

    # `techlists` are strings with comma-separated techs used for grouping in
    # some model-wide constraints
    sets.techlists = set()
    for k in model_run.model.get_key('group_share', {}).keys():
        sets.techlists.add(k)

    return sets
Example #6
0
def process_techs(config_model):

    default_palette_cycler = itertools.cycle(range(len(_DEFAULT_PALETTE)))

    result = AttrDict()
    errors = []
    debug_comments = AttrDict()

    for tech_id, tech_config in config_model.techs.items():

        # If a tech specifies ``exists: false``, we skip it entirely
        if not tech_config.get('exists', True):
            continue

        tech_result = AttrDict()

        # Add inheritance chain
        tech_result.inheritance = get_parents(tech_id, config_model)

        # CHECK: A tech's parent must lead to one of the built-in tech_groups
        builtin_tech_groups = checks.defaults_model.tech_groups.keys()
        if tech_result.inheritance[-1] not in builtin_tech_groups:
            errors.append(
                'tech {} must inherit from a built-in tech group'.format(tech_id)
            )

        # Process inheritance
        tech_result.essentials = AttrDict()
        tech_result.constraints = AttrDict()
        for parent in reversed(tech_result.inheritance):
            # Does the parent group have model-wide settings?
            parent_essentials = config_model.tech_groups[parent].essentials
            parent_systemwide_constraints = util.get_systemwide_constraints(
                config_model.tech_groups[parent]
            )
            for k in parent_essentials.as_dict_flat():
                debug_comments.set_key(
                    '{}.essentials.{}'.format(tech_id, k),
                    'From parent tech_group `{}`'.format(parent)
                )
            tech_result.essentials.union(parent_essentials, allow_override=True)
            tech_result.constraints.union(parent_systemwide_constraints, allow_override=True)

        # Add this tech's essentials and constraints, overwriting any essentials from parents
        tech_result.essentials.union(tech_config.essentials, allow_override=True)
        tech_result.constraints.union(
            util.get_systemwide_constraints(tech_config), allow_override=True
        )

        # Add allowed_constraints and required_constraints from base tech
        keys_to_add = ['required_constraints', 'allowed_constraints', 'allowed_costs']
        for k in keys_to_add:
            tech_result[k] = config_model.tech_groups[tech_result.inheritance[-1]].get(k, [])

        # CHECK: If necessary, populate carrier_in and carrier_out in essentials, but
        # also break on missing carrier data
        if 'carrier_in' not in tech_result.essentials:
            if tech_result.inheritance[-1] in ['supply', 'supply_plus']:
                tech_result.essentials.carrier_in = 'resource'
            elif tech_result.inheritance[-1] in ['demand', 'transmission',
                                                 'storage']:
                try:
                    tech_result.essentials.carrier_in = \
                        tech_result.essentials.carrier
                    debug_comments.set_key(
                        '{}.essentials.carrier_in'.format(tech_id),
                        'Set from essentials.carrier'
                    )
                except KeyError:
                    errors.append(
                        '`carrier` or `carrier_in` must be '
                        'defined for {}'.format(tech_id)
                    )
            else:
                errors.append(
                    '`carrier_in` must be defined for {}'.format(tech_id)
                )

        if 'carrier_out' not in tech_result.essentials:
            if tech_result.inheritance[-1] == 'demand':
                tech_result.essentials.carrier_out = 'resource'
            elif tech_result.inheritance[-1] in ['supply', 'supply_plus',
                                                 'transmission', 'storage']:
                try:
                    tech_result.essentials.carrier_out = \
                        tech_result.essentials.carrier
                except KeyError:
                    errors.append(
                        '`carrier` or `carrier_out` must be '
                        'defined for {}'.format(tech_id)
                    )
            else:
                errors.append(
                    '`carrier_out` must be defined for {}'.format(tech_id)
                )
        # Deal with primary carrier in/out for conversion_plus techs
        if tech_result.inheritance[-1] == 'conversion_plus':
            for direction in ['_in', '_out']:
                carriers = set(util.flatten_list([
                    v for k, v in tech_result.essentials.items()
                    if k.startswith('carrier' + direction)
                ]))
                primary_carrier = tech_result.essentials.get(
                    'primary_carrier' + direction, None
                )
                if primary_carrier is None and len(carriers) == 1:
                    tech_result.essentials['primary_carrier' + direction] = carriers.pop()
                elif primary_carrier is None and len(carriers) > 1:
                    errors.append(
                        'Primary_carrier{0} must be assigned for tech `{1}` as '
                        'there are multiple carriers{0}'.format(direction, tech_id)
                    )
                elif primary_carrier not in carriers:
                    errors.append(
                        'Primary_carrier{0} `{1}` not one of the available carriers'
                        '{0} for `{2}`'.format(direction, primary_carrier, tech_id)
                    )

        # If necessary, pick a color for the tech, cycling through
        # the hardcoded default palette
        if not tech_result.essentials.get_key('color', None):
            color = _DEFAULT_PALETTE[next(default_palette_cycler)]
            tech_result.essentials.color = color
            debug_comments.set_key(
                '{}.essentials.color'.format(tech_id),
                'From Calliope default palette')
        result[tech_id] = tech_result

    return result, debug_comments, errors