def balance_storage_constraint_rule(backend_model, loc_tech, timestep): """ Balance carrier production and consumption of storage technologies, alongside any use of the stored volume. .. container:: scrolling-wrapper .. math:: \\boldsymbol{storage}(loc::tech, timestep) = \\boldsymbol{storage}(loc::tech, timestep_{previous}) \\times (1 - storage\\_loss(loc::tech, timestep))^{resolution(timestep)} - \\boldsymbol{carrier_{con}}(loc::tech::carrier, timestep) \\times \\eta_{energy}(loc::tech, timestep) - \\frac{\\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)}{\\eta_{energy}(loc::tech, timestep)} \\quad \\forall loc::tech \\in loc::techs_{storage}, \\forall timestep \\in timesteps """ model_data_dict = backend_model.__calliope_model_data["data"] run_config = backend_model.__calliope_run_config energy_eff = get_param(backend_model, "energy_eff", (loc_tech, timestep)) if po.value(energy_eff) == 0: carrier_prod = 0 else: loc_tech_carrier = model_data_dict["lookup_loc_techs"][loc_tech] carrier_prod = ( backend_model.carrier_prod[loc_tech_carrier, timestep] / energy_eff) carrier_con = backend_model.carrier_con[loc_tech_carrier, timestep] * energy_eff current_timestep = backend_model.timesteps.order_dict[timestep] if current_timestep == 0 and not run_config["cyclic_storage"]: storage_previous_step = ( get_param(backend_model, "storage_initial", loc_tech) * backend_model.storage_cap[loc_tech]) elif (hasattr(backend_model, "storage_inter_cluster") and model_data_dict["lookup_cluster_first_timestep"][timestep]): storage_previous_step = 0 else: if (hasattr(backend_model, "clusters") and model_data_dict["lookup_cluster_first_timestep"][timestep]): previous_step = model_data_dict["lookup_cluster_last_timestep"][ timestep] elif current_timestep == 0 and run_config["cyclic_storage"]: previous_step = backend_model.timesteps[-1] else: previous_step = get_previous_timestep(backend_model.timesteps, timestep) storage_loss = get_param(backend_model, "storage_loss", loc_tech) time_resolution = backend_model.timestep_resolution[previous_step] storage_previous_step = ( (1 - storage_loss)** time_resolution) * backend_model.storage[loc_tech, previous_step] return (backend_model.storage[loc_tech, timestep] == storage_previous_step - carrier_prod - carrier_con)
def ramping_constraint(backend_model, loc_tech_carrier, timestep, direction=0): """ Ramping rate constraints. Direction: 0 is up, 1 is down. .. container:: scrolling-wrapper .. math:: \\boldsymbol{max\\_ramping\\_rate}(loc::tech::carrier, timestep) = energy_{ramping}(loc::tech, timestep) \\times energy_{cap}(loc::tech) \\boldsymbol{diff}(loc::tech::carrier, timestep) = (carrier_{prod}(loc::tech::carrier, timestep) + carrier_{con}(loc::tech::carrier, timestep)) / timestep\\_resolution(timestep) - (carrier_{prod}(loc::tech::carrier, timestep-1) + carrier_{con}(loc::tech::carrier, timestep-1)) / timestep\\_resolution(timestep-1) """ # No constraint for first timestep # Pyomo returns the order 1-indexed, but we want 0-indexing if backend_model.timesteps.ord(timestep) - 1 == 0: return po.Constraint.NoConstraint else: previous_step = get_previous_timestep(backend_model.timesteps, timestep) time_res = backend_model.timestep_resolution[timestep] time_res_prev = backend_model.timestep_resolution[previous_step] loc_tech = loc_tech_carrier.rsplit("::", 1)[0] # Ramping rate (fraction of installed capacity per hour) ramping_rate = get_param(backend_model, "energy_ramping", (loc_tech, timestep)) try: prod_this = backend_model.carrier_prod[loc_tech_carrier, timestep] prod_prev = backend_model.carrier_prod[loc_tech_carrier, previous_step] except KeyError: prod_this = 0 prod_prev = 0 try: con_this = backend_model.carrier_con[loc_tech_carrier, timestep] con_prev = backend_model.carrier_con[loc_tech_carrier, previous_step] except KeyError: con_this = 0 con_prev = 0 diff = (prod_this + con_this) / time_res - (prod_prev + con_prev) / time_res_prev max_ramping_rate = ramping_rate * backend_model.energy_cap[loc_tech] if direction == 0: return diff <= max_ramping_rate else: return -1 * max_ramping_rate <= diff
def ramping_constraint(backend_model, loc_tech_carrier, timestep, direction=0): """ Ramping rate constraints. Direction: 0 is up, 1 is down. .. container:: scrolling-wrapper .. math:: \\boldsymbol{max\\_ramping\\_rate}(loc::tech::carrier, timestep) = energy_{ramping}(loc::tech, timestep) \\times energy_{cap}(loc::tech) \\boldsymbol{diff}(loc::tech::carrier, timestep) = (carrier_{prod}(loc::tech::carrier, timestep) + carrier_{con}(loc::tech::carrier, timestep)) / timestep\\_resolution(timestep) - (carrier_{prod}(loc::tech::carrier, timestep-1) + carrier_{con}(loc::tech::carrier, timestep-1)) / timestep\\_resolution(timestep-1) """ # No constraint for first timestep if backend_model.timesteps.order_dict[timestep] == 0: return po.Constraint.NoConstraint else: previous_step = get_previous_timestep(backend_model.timesteps, timestep) time_res = backend_model.timestep_resolution[timestep] time_res_prev = backend_model.timestep_resolution[previous_step] loc_tech = loc_tech_carrier.rsplit('::', 1)[0] # Ramping rate (fraction of installed capacity per hour) ramping_rate = get_param(backend_model, 'energy_ramping', (loc_tech, timestep)) try: prod_this = backend_model.carrier_prod[loc_tech_carrier, timestep] prod_prev = backend_model.carrier_prod[loc_tech_carrier, previous_step] except KeyError: prod_this = 0 prod_prev = 0 try: con_this = backend_model.carrier_con[loc_tech_carrier, timestep] con_prev = backend_model.carrier_con[loc_tech_carrier, previous_step] except KeyError: con_this = 0 con_prev = 0 diff = ( (prod_this + con_this) / time_res - (prod_prev + con_prev) / time_res_prev ) max_ramping_rate = ramping_rate * backend_model.energy_cap[loc_tech] if direction == 0: return diff <= max_ramping_rate else: return -1 * max_ramping_rate <= diff
def balance_storage_constraint_rule(backend_model, loc_tech, timestep): """ Balance carrier production and consumption of storage technologies, alongside any use of the stored volume. .. container:: scrolling-wrapper .. math:: \\boldsymbol{storage}(loc::tech, timestep) = \\boldsymbol{storage}(loc::tech, timestep_{previous}) \\times (1 - storage\\_loss(loc::tech, timestep))^{resolution(timestep)} - \\boldsymbol{carrier_{con}}(loc::tech::carrier, timestep) \\times \\eta_{energy}(loc::tech, timestep) - \\frac{\\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)}{\\eta_{energy}(loc::tech, timestep)} \\quad \\forall loc::tech \\in loc::techs_{storage}, \\forall timestep \\in timesteps """ model_data_dict = backend_model.__calliope_model_data__['data'] model_attrs = backend_model.__calliope_model_data__['attrs'] energy_eff = get_param(backend_model, 'energy_eff', (loc_tech, timestep)) if po.value(energy_eff) == 0: carrier_prod = 0 else: loc_tech_carrier = model_data_dict['lookup_loc_techs'][loc_tech] carrier_prod = backend_model.carrier_prod[loc_tech_carrier, timestep] / energy_eff carrier_con = backend_model.carrier_con[loc_tech_carrier, timestep] * energy_eff current_timestep = backend_model.timesteps.order_dict[timestep] if current_timestep == 0 and not model_attrs['run.cyclic_storage']: storage_previous_step = get_param(backend_model, 'storage_initial', loc_tech) elif (hasattr(backend_model, 'storage_inter_cluster') and model_data_dict['lookup_cluster_first_timestep'][timestep]): storage_previous_step = 0 else: if (hasattr(backend_model, 'clusters') and model_data_dict['lookup_cluster_first_timestep'][timestep]): previous_step = model_data_dict['lookup_cluster_last_timestep'][timestep] elif current_timestep == 0 and model_attrs['run.cyclic_storage']: previous_step = backend_model.timesteps[-1] else: previous_step = get_previous_timestep(backend_model.timesteps, timestep) storage_loss = get_param(backend_model, 'storage_loss', loc_tech) time_resolution = backend_model.timestep_resolution[previous_step] storage_previous_step = ( ((1 - storage_loss) ** time_resolution) * backend_model.storage[loc_tech, previous_step] ) return ( backend_model.storage[loc_tech, timestep] == storage_previous_step - carrier_prod - carrier_con )
def balance_storage_inter_cluster_rule(backend_model, loc_tech, datestep): """ When clustering days, to reduce the timeseries length, balance the daily stored energy across all days of the original timeseries. `Ref: DOI 10.1016/j.apenergy.2018.01.023 <https://doi.org/10.1016/j.apenergy.2018.01.023>`_ .. container:: scrolling-wrapper .. math:: \\boldsymbol{storage_{inter\\_cluster}}(loc::tech, datestep) = \\boldsymbol{storage_{inter\\_cluster}}(loc::tech, datestep_{previous}) \\times (1 - storage\\_loss(loc::tech, timestep))^{24} + \\boldsymbol{storage}(loc::tech, timestep_{final, cluster(datestep))}) \\quad \\forall loc::tech \\in loc::techs_{store}, \\forall datestep \\in datesteps Where :math:`timestep_{final, cluster(datestep_{previous}))}` is the final timestep of the cluster in the clustered timeseries corresponding to the previous day """ model_attrs = backend_model.__calliope_model_data__['attrs'] current_datestep = backend_model.datesteps.order_dict[datestep] if current_datestep == 0 and not model_attrs['run.cyclic_storage']: storage_previous_step = get_param(backend_model, 'storage_initial', loc_tech) storage_intra = 0 else: if current_datestep == 0 and model_attrs['run.cyclic_storage']: previous_step = backend_model.datesteps[-1] else: previous_step = get_previous_timestep(backend_model.datesteps, datestep) storage_loss = get_param(backend_model, 'storage_loss', loc_tech) storage_previous_step = ( ((1 - storage_loss) ** 24) * backend_model.storage_inter_cluster[loc_tech, previous_step] ) final_timestep = ( backend_model.__calliope_model_data__ ['data']['lookup_datestep_last_cluster_timestep'][previous_step] ) storage_intra = backend_model.storage[loc_tech, final_timestep] return ( backend_model.storage_inter_cluster[loc_tech, datestep] == storage_previous_step + storage_intra )
def balance_storage_constraint_rule(backend_model, loc_tech, timestep): """ Balance carrier production and consumption of storage technologies, alongside any use of the stored volume. .. container:: scrolling-wrapper .. math:: \\boldsymbol{storage}(loc::tech, timestep) = \\boldsymbol{storage}(loc::tech, timestep_{previous}) \\times (1 - storage\_loss(loc::tech, timestep))^{resolution(timestep)} - \\boldsymbol{carrier_{con}}(loc::tech::carrier, timestep) \\times \\eta_{energy}(loc::tech, timestep) - \\frac{\\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)}{\\eta_{energy}(loc::tech, timestep)} \\quad \\forall loc::tech \\in loc::techs_{storage}, \\forall timestep \\in timesteps """ model_data_dict = backend_model.__calliope_model_data__['data'] energy_eff = get_param(backend_model, 'energy_eff', (loc_tech, timestep)) if po.value(energy_eff) == 0: carrier_prod = 0 else: loc_tech_carrier = model_data_dict['lookup_loc_techs'][loc_tech] carrier_prod = backend_model.carrier_prod[loc_tech_carrier, timestep] / energy_eff carrier_con = backend_model.carrier_con[loc_tech_carrier, timestep] * energy_eff if backend_model.timesteps.order_dict[timestep] == 0: storage_previous_step = get_param(backend_model, 'storage_initial', loc_tech) else: storage_loss = get_param(backend_model, 'storage_loss', loc_tech) previous_step = get_previous_timestep(backend_model, timestep) time_resolution = backend_model.timestep_resolution[previous_step] storage_previous_step = ( ((1 - storage_loss) ** time_resolution) * backend_model.storage[loc_tech, previous_step] ) return ( backend_model.storage[loc_tech, timestep] == storage_previous_step - carrier_prod - carrier_con )
def _ramping_constraint_rule(backend_model, loc_tech_carrier, timestep, direction=0): """ Ramping rate constraints. Direction: 0 is up, 1 is down. """ # No constraint for first timestep if backend_model.timesteps.order_dict[timestep] == 0: return po.Constraint.NoConstraint else: previous_step = get_previous_timestep(backend_model, timestep) time_res = backend_model.timestep_resolution[timestep] time_res_prev = backend_model.timestep_resolution[previous_step] loc_tech = loc_tech_carrier.rsplit('::', 1)[0] # Ramping rate (fraction of installed capacity per hour) ramping_rate = get_param(backend_model, 'energy_ramping', (loc_tech, timestep)) try: prod_this = backend_model.carrier_prod[loc_tech_carrier, timestep] prod_prev = backend_model.carrier_prod[loc_tech_carrier, previous_step] except KeyError: prod_this = 0 prod_prev = 0 try: con_this = backend_model.carrier_con[loc_tech_carrier, timestep] con_prev = backend_model.carrier_con[loc_tech_carrier, previous_step] except KeyError: con_this = 0 con_prev = 0 diff = ( (prod_this + con_this) / time_res - (prod_prev + con_prev) / time_res_prev ) max_ramping_rate = ramping_rate * backend_model.energy_cap[loc_tech] if direction == 0: return diff <= max_ramping_rate else: return -1 * max_ramping_rate <= diff
def balance_storage_inter_constraint_rule(backend_model, node, tech, datestep): """ When clustering days, to reduce the timeseries length, balance the daily stored energy across all days of the original timeseries. `Ref: DOI 10.1016/j.apenergy.2018.01.023 <https://doi.org/10.1016/j.apenergy.2018.01.023>`_ .. container:: scrolling-wrapper .. math:: \\boldsymbol{storage_{inter\\_cluster}}(loc::tech, datestep) = \\boldsymbol{storage_{inter\\_cluster}}(loc::tech, datestep_{previous}) \\times (1 - storage\\_loss(loc::tech, timestep))^{24} + \\boldsymbol{storage}(loc::tech, timestep_{final, cluster(datestep))}) \\quad \\forall loc::tech \\in loc::techs_{store}, \\forall datestep \\in datesteps Where :math:`timestep_{final, cluster(datestep_{previous}))}` is the final timestep of the cluster in the clustered timeseries corresponding to the previous day """ run_config = backend_model.__calliope_run_config # Pyomo returns the order 1-indexed, but we want 0-indexing current_datestep = backend_model.datesteps.ord(datestep) - 1 if current_datestep == 0 and not run_config["cyclic_storage"]: storage_previous_step = get_param(backend_model, "storage_initial", (node, tech)) storage_intra = 0 else: if current_datestep == 0 and run_config["cyclic_storage"]: previous_step = backend_model.datesteps[-1] else: previous_step = get_previous_timestep(backend_model.datesteps, datestep) storage_loss = get_param(backend_model, "storage_loss", (node, tech)) storage_previous_step = ( (1 - storage_loss)** 24) * backend_model.storage_inter_cluster[node, tech, previous_step] final_timestep = backend_model.lookup_datestep_last_cluster_timestep[ previous_step].value storage_intra = backend_model.storage[node, tech, final_timestep] return (backend_model.storage_inter_cluster[node, tech, datestep] == storage_previous_step + storage_intra)
def balance_storage_inter_cluster_rule(backend_model, loc_tech, datestep): """ When clustering days, to reduce the timeseries length, balance the daily stored energy across all days of the original timeseries. `Ref: DOI 10.1016/j.apenergy.2018.01.023 <https://doi.org/10.1016/j.apenergy.2018.01.023>`_ .. container:: scrolling-wrapper .. math:: \\boldsymbol{storage_{inter\_cluster}}(loc::tech, datestep) = \\boldsymbol{storage_{inter\_cluster}}(loc::tech, datestep_{previous}) \\times (1 - storage\_loss(loc::tech, timestep))^{24} + \\boldsymbol{storage}(loc::tech, timestep_{final, cluster(datestep))}) \\quad \\forall loc::tech \\in loc::techs_{store}, \\forall datestep \\in datesteps Where :math:`timestep_{final, cluster(datestep_{previous}))}` is the final timestep of the cluster in the clustered timeseries corresponding to the previous day """ model_attrs = backend_model.__calliope_model_data__['attrs'] current_datestep = backend_model.datesteps.order_dict[datestep] if current_datestep == 0 and not model_attrs['run.cyclic_storage']: storage_previous_step = get_param(backend_model, 'storage_initial', loc_tech) storage_intra = 0 else: if current_datestep == 0 and model_attrs['run.cyclic_storage']: previous_step = backend_model.datesteps[-1] else: previous_step = get_previous_timestep(backend_model.datesteps, datestep) storage_loss = get_param(backend_model, 'storage_loss', loc_tech) storage_previous_step = ( ((1 - storage_loss)**24) * backend_model.storage_inter_cluster[loc_tech, previous_step]) final_timestep = ( backend_model.__calliope_model_data__['data'] ['lookup_datestep_last_cluster_timestep'][previous_step]) storage_intra = backend_model.storage[loc_tech, final_timestep] return (backend_model.storage_inter_cluster[loc_tech, datestep] == storage_previous_step + storage_intra)
def balance_supply_plus_constraint_rule(backend_model, loc_tech, timestep): """ Balance carrier production and resource consumption of supply_plus technologies alongside any use of resource storage. .. _system_balance_constraint: .. container:: scrolling-wrapper .. math:: \\boldsymbol{storage}(loc::tech, timestep) = \\boldsymbol{storage}(loc::tech, timestep_{previous}) \\times (1 - storage\\_loss(loc::tech, timestep))^{timestep\\_resolution(timestep)} + \\boldsymbol{resource_{con}}(loc::tech, timestep) \\times \\eta_{resource}(loc::tech, timestep) - \\frac{\\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)}{\\eta_{energy}(loc::tech, timestep) \\times \\eta_{parasitic}(loc::tech, timestep)} \\quad \\forall loc::tech \\in loc::techs_{supply^{+}}, \\forall timestep \\in timesteps If *no* storage is defined for the technology, this reduces to: .. container:: scrolling-wrapper .. math:: \\boldsymbol{resource_{con}}(loc::tech, timestep) \\times \\eta_{resource}(loc::tech, timestep) = \\frac{\\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)}{\\eta_{energy}(loc::tech, timestep) \\times \\eta_{parasitic}(loc::tech, timestep)} \\quad \\forall loc::tech \\in loc::techs_{supply^{+}}, \\forall timestep \\in timesteps """ model_data_dict = backend_model.__calliope_model_data["data"] run_config = backend_model.__calliope_run_config resource_eff = get_param(backend_model, "resource_eff", (loc_tech, timestep)) energy_eff = get_param(backend_model, "energy_eff", (loc_tech, timestep)) parasitic_eff = get_param(backend_model, "parasitic_eff", (loc_tech, timestep)) total_eff = energy_eff * parasitic_eff if po.value(total_eff) == 0: carrier_prod = 0 else: loc_tech_carrier = model_data_dict["lookup_loc_techs"][loc_tech] carrier_prod = ( backend_model.carrier_prod[loc_tech_carrier, timestep] / total_eff) # A) Case where no storage allowed if not loc_tech_is_in(backend_model, loc_tech, "loc_techs_store"): return (backend_model.resource_con[loc_tech, timestep] * resource_eff == carrier_prod) # B) Case where storage is allowed else: resource = backend_model.resource_con[loc_tech, timestep] * resource_eff current_timestep = backend_model.timesteps.order_dict[timestep] if current_timestep == 0 and not run_config["cyclic_storage"]: storage_previous_step = ( get_param(backend_model, "storage_initial", loc_tech) * backend_model.storage_cap[loc_tech]) elif (hasattr(backend_model, "storage_inter_cluster") and model_data_dict["lookup_cluster_first_timestep"][timestep]): storage_previous_step = 0 else: if (hasattr(backend_model, "clusters") and model_data_dict["lookup_cluster_first_timestep"][timestep] ): previous_step = model_data_dict[ "lookup_cluster_last_timestep"][timestep] elif current_timestep == 0 and run_config["cyclic_storage"]: previous_step = backend_model.timesteps[-1] else: previous_step = get_previous_timestep(backend_model.timesteps, timestep) storage_loss = get_param(backend_model, "storage_loss", loc_tech) time_resolution = backend_model.timestep_resolution[previous_step] storage_previous_step = ( (1 - storage_loss)** time_resolution) * backend_model.storage[loc_tech, previous_step] return (backend_model.storage[loc_tech, timestep] == storage_previous_step + resource - carrier_prod)
def balance_supply_plus_constraint_rule(backend_model, loc_tech, timestep): """ Balance carrier production and resource consumption of supply_plus technologies alongside any use of resource storage. .. _system_balance_constraint: .. container:: scrolling-wrapper .. math:: \\boldsymbol{storage}(loc::tech, timestep) = \\boldsymbol{storage}(loc::tech, timestep_{previous}) \\times (1 - storage\_loss(loc::tech, timestep))^{timestep\_resolution(timestep)} + \\boldsymbol{resource_{con}}(loc::tech, timestep) - \\frac{\\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)}{\\eta_{energy}(loc::tech, timestep) \\times \\eta_{parasitic}(loc::tech, timestep)} \\quad \\forall loc::tech \\in loc::techs_{supply^{+}}, \\forall timestep \\in timesteps If *no* storage is defined for the technology, this reduces to: .. container:: scrolling-wrapper .. math:: \\boldsymbol{resource_{con}}(loc::tech, timestep) = \\frac{\\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)}{\\eta_{energy}(loc::tech, timestep) \\times \\eta_{parasitic}(loc::tech, timestep)} \\quad \\forall loc::tech \\in loc::techs_{supply^{+}}, \\forall timestep \\in timesteps """ model_data_dict = backend_model.__calliope_model_data__['data'] energy_eff = get_param(backend_model, 'energy_eff', (loc_tech, timestep)) parasitic_eff = get_param(backend_model, 'parasitic_eff', (loc_tech, timestep)) total_eff = energy_eff * parasitic_eff if po.value(total_eff) == 0: carrier_prod = 0 else: loc_tech_carrier = model_data_dict['lookup_loc_techs'][loc_tech] carrier_prod = backend_model.carrier_prod[loc_tech_carrier, timestep] / total_eff # A) Case where no storage allowed if not loc_tech_is_in(backend_model, loc_tech, 'loc_techs_store'): return backend_model.resource_con[loc_tech, timestep] == carrier_prod # B) Case where storage is allowed else: resource = backend_model.resource_con[loc_tech, timestep] if backend_model.timesteps.order_dict[timestep] == 0: storage_previous_step = get_param(backend_model, 'storage_initial', loc_tech) else: storage_loss = get_param(backend_model, 'storage_loss', loc_tech) previous_step = get_previous_timestep(backend_model, timestep) time_resolution = backend_model.timestep_resolution[previous_step] storage_previous_step = ( ((1 - storage_loss) ** time_resolution) * backend_model.storage[loc_tech, previous_step] ) return ( backend_model.storage[loc_tech, timestep] == storage_previous_step + resource - carrier_prod )
def balance_supply_plus_constraint_rule(backend_model, loc_tech, timestep): """ Balance carrier production and resource consumption of supply_plus technologies alongside any use of resource storage. .. _system_balance_constraint: .. container:: scrolling-wrapper .. math:: \\boldsymbol{storage}(loc::tech, timestep) = \\boldsymbol{storage}(loc::tech, timestep_{previous}) \\times (1 - storage\\_loss(loc::tech, timestep))^{timestep\\_resolution(timestep)} + \\boldsymbol{resource_{con}}(loc::tech, timestep) \\times \\eta_{resource}(loc::tech, timestep) - \\frac{\\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)}{\\eta_{energy}(loc::tech, timestep) \\times \\eta_{parasitic}(loc::tech, timestep)} \\quad \\forall loc::tech \\in loc::techs_{supply^{+}}, \\forall timestep \\in timesteps If *no* storage is defined for the technology, this reduces to: .. container:: scrolling-wrapper .. math:: \\boldsymbol{resource_{con}}(loc::tech, timestep) \\times \\eta_{resource}(loc::tech, timestep) = \\frac{\\boldsymbol{carrier_{prod}}(loc::tech::carrier, timestep)}{\\eta_{energy}(loc::tech, timestep) \\times \\eta_{parasitic}(loc::tech, timestep)} \\quad \\forall loc::tech \\in loc::techs_{supply^{+}}, \\forall timestep \\in timesteps """ model_data_dict = backend_model.__calliope_model_data__['data'] model_attrs = backend_model.__calliope_model_data__['attrs'] resource_eff = get_param(backend_model, 'resource_eff', (loc_tech, timestep)) energy_eff = get_param(backend_model, 'energy_eff', (loc_tech, timestep)) parasitic_eff = get_param(backend_model, 'parasitic_eff', (loc_tech, timestep)) total_eff = energy_eff * parasitic_eff if po.value(total_eff) == 0: carrier_prod = 0 else: loc_tech_carrier = model_data_dict['lookup_loc_techs'][loc_tech] carrier_prod = backend_model.carrier_prod[loc_tech_carrier, timestep] / total_eff # A) Case where no storage allowed if not loc_tech_is_in(backend_model, loc_tech, 'loc_techs_store'): return backend_model.resource_con[loc_tech, timestep] * resource_eff == carrier_prod # B) Case where storage is allowed else: resource = backend_model.resource_con[loc_tech, timestep] * resource_eff current_timestep = backend_model.timesteps.order_dict[timestep] if current_timestep == 0 and not model_attrs['run.cyclic_storage']: storage_previous_step = get_param(backend_model, 'storage_initial', loc_tech) elif (hasattr(backend_model, 'storage_inter_cluster') and model_data_dict['lookup_cluster_first_timestep'][timestep]): storage_previous_step = 0 else: if (hasattr(backend_model, 'clusters') and model_data_dict['lookup_cluster_first_timestep'][timestep]): previous_step = model_data_dict['lookup_cluster_last_timestep'][timestep] elif current_timestep == 0 and model_attrs['run.cyclic_storage']: previous_step = backend_model.timesteps[-1] else: previous_step = get_previous_timestep(backend_model.timesteps, timestep) storage_loss = get_param(backend_model, 'storage_loss', loc_tech) time_resolution = backend_model.timestep_resolution[previous_step] storage_previous_step = ( ((1 - storage_loss) ** time_resolution) * backend_model.storage[loc_tech, previous_step] ) return ( backend_model.storage[loc_tech, timestep] == storage_previous_step + resource - carrier_prod )