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