diff --git a/temoa/temoa_model/hybrid_loader.py b/temoa/temoa_model/hybrid_loader.py index e321dd049..babadae1c 100644 --- a/temoa/temoa_model/hybrid_loader.py +++ b/temoa/temoa_model/hybrid_loader.py @@ -101,6 +101,7 @@ def __init__(self, db_connection: Connection, config: TemoaConfig): self.viable_rt: ViableSet | None = None self.viable_rpit: ViableSet | None = None self.viable_rtt: ViableSet | None = None # to support scanning LinkedTech + self.viable_rtv_eol: ViableSet | None = None # to support scanning EndOfLifeOutput self.efficiency_values: list[tuple] = [] # container for loaded data @@ -175,6 +176,7 @@ def _build_efficiency_dataset( self.viable_rt = filts['rt'] self.viable_rpit = filts['rpit'] self.viable_rpto = filts['rpto'] + self.viable_rtv_eol = filts['rtv_eol'] self.viable_techs = filts['t'] self.viable_input_comms = filts['ic'] self.viable_vintages = filts['v'] @@ -977,10 +979,19 @@ def load_indexed_set(indexed_set: Set, index_value, element, element_validator=N # EmissionEmbodied if self.table_exists('EmissionEmbodied'): - raw = cur.execute( - 'SELECT region, emis_comm, tech, vintage, value ' - 'FROM main.EmissionEmbodied' - ).fetchall() + if mi: + qry = ( + 'SELECT region, emis_comm, tech, vintage, value FROM main.EmissionEmbodied' + ' WHERE vintage >= ? AND vintage <= ?' + ) + raw = cur.execute( + qry, + (mi.base_year, mi.last_demand_year), + ).fetchall() + else: + raw = cur.execute( + 'SELECT region, emis_comm, tech, vintage, value FROM main.EmissionEmbodied' + ).fetchall() load_element(M.EmissionEmbodied, raw, self.viable_rtv, (0, 2, 3)) # EmissionEndOfLife @@ -993,9 +1004,19 @@ def load_indexed_set(indexed_set: Set, index_value, element, element_validator=N # ConstructionInput if self.table_exists('ConstructionInput'): - raw = cur.execute( - 'SELECT region, input_comm, tech, vintage, value FROM main.ConstructionInput' - ).fetchall() + if mi: + qry = ( + 'SELECT region, input_comm, tech, vintage, value FROM main.ConstructionInput' + ' WHERE vintage >= ? AND vintage <= ?' + ) + raw = cur.execute( + qry, + (mi.base_year, mi.last_demand_year), + ).fetchall() + else: + raw = cur.execute( + 'SELECT region, input_comm, tech, vintage, value FROM main.ConstructionInput' + ).fetchall() load_element(M.ConstructionInput, raw, self.viable_rtv, (0, 2, 3)) # EndOfLifeOutput @@ -1003,7 +1024,7 @@ def load_indexed_set(indexed_set: Set, index_value, element, element_validator=N raw = cur.execute( 'SELECT region, tech, vintage, output_comm, value FROM main.EndOfLifeOutput' ).fetchall() - load_element(M.EndOfLifeOutput, raw, self.viable_rtv, (0, 1, 2)) + load_element(M.EndOfLifeOutput, raw, self.viable_rtv_eol, (0, 1, 2)) # LinkedTechs # Note: Both of the linked techs must be viable. As this is non period/vintage diff --git a/temoa/temoa_model/model_checking/commodity_graph.py b/temoa/temoa_model/model_checking/commodity_graph.py index b103f7a0e..17492e3d5 100644 --- a/temoa/temoa_model/model_checking/commodity_graph.py +++ b/temoa/temoa_model/model_checking/commodity_graph.py @@ -89,7 +89,7 @@ def generate_graph( } cap_edges = { (tech.ic, tech.name, tech.oc) for tech in network_data.available_techs[region, period] - if tech.name in ('Construction','EndOfLife') + if tech.name == tech.ic or tech.name == tech.oc } exc_edges = { (tech.ic, tech.name, tech.oc) for tech in network_data.available_techs[region, period] diff --git a/temoa/temoa_model/model_checking/commodity_network_manager.py b/temoa/temoa_model/model_checking/commodity_network_manager.py index bff1568be..e3b9657a9 100644 --- a/temoa/temoa_model/model_checking/commodity_network_manager.py +++ b/temoa/temoa_model/model_checking/commodity_network_manager.py @@ -117,6 +117,7 @@ def build_filters(self) -> dict[str, ViableSet]: valid_rpit = set() valid_rpto = set() valid_t = set() + valid_rtv_eol = set() valid_input_commodities = set() valid_output_commodities = set() valid_vintages = set() @@ -128,10 +129,17 @@ def build_filters(self) -> dict[str, ViableSet]: valid_rpit.add((tech.region, p, tech.ic, tech.name)) valid_rpto.add((tech.region, p, tech.name, tech.oc)) valid_t.add(tech.name) + valid_rtv_eol.add((tech.region, tech.ic, tech.vintage)) valid_input_commodities.add(tech.ic) valid_output_commodities.add(tech.oc) valid_vintages.add(tech.vintage) + for r, p, t, v in self.filtered_data.silent_rptv: + valid_rtv.add((r, t, v)) + valid_rt.add((r, t)) + valid_t.add(t) + valid_vintages.add(v) + filts = { 'ritvo': ViableSet( elements=valid_ritvo, exception_loc=0, exception_vals=ViableSet.REGION_REGEXES @@ -148,6 +156,9 @@ def build_filters(self) -> dict[str, ViableSet]: 'v': ViableSet(elements=valid_vintages), 'ic': ViableSet(elements=valid_input_commodities), 'oc': ViableSet(elements=valid_output_commodities), + 'rtv_eol': ViableSet( + elements=valid_rtv_eol, exception_loc=0, exception_vals=ViableSet.REGION_REGEXES + ), } return filts diff --git a/temoa/temoa_model/model_checking/network_model_data.py b/temoa/temoa_model/model_checking/network_model_data.py index dd36937ad..539c32bfb 100644 --- a/temoa/temoa_model/model_checking/network_model_data.py +++ b/temoa/temoa_model/model_checking/network_model_data.py @@ -65,6 +65,7 @@ def __init__(self, **kwargs): 'available_techs' ) self.available_linked_techs: set[LinkedTech] = kwargs.get('available_linked_techs', set()) + self.silent_rptv: set[str] = kwargs.get('silent_rptv', set()) # a catch-all for indicators for techs...growth potential # dev note: this is indexed by tech name, and is blind to vintage. The intended use is in the # network graph, which is also blind to vintage. So it is interpreted as "at least one" @@ -82,6 +83,7 @@ def clone(self) -> Self: all_commodities=self.physical_commodities.copy(), available_techs=self.available_techs.copy(), available_linked_techs=self.available_linked_techs.copy(), + silent_rptv=self.silent_rptv.copy(), ) @property @@ -179,11 +181,13 @@ def _build_from_db( # re-use some of the hybrid loader code in a clear way. Not too much overlap, though res = NetworkModelData() cur = con.cursor() + raw = cur.execute('SELECT tech FROM Technology WHERE unlim_cap==1').fetchall() + tech_uncap = {t[0] for t in raw} raw = cur.execute('SELECT tech FROM Technology WHERE retire==1').fetchall() tech_retire = {t[0] for t in raw} raw = cur.execute('SELECT DISTINCT region, tech, vintage FROM LifetimeSurvivalCurve').fetchall() tech_survival_curve = set(raw) - raw = cur.execute('SELECT period FROM TimePeriod').fetchall() + raw = cur.execute('SELECT period FROM TimePeriod WHERE flag == "f"').fetchall() periods = [p[0] for p in sorted(raw)] period_length = {periods[i]: periods[i+1] - periods[i] for i in range(len(periods)-1)} periods = periods[:-1] @@ -191,6 +195,7 @@ def _build_from_db( res.physical_commodities = {c[0] for c in raw} res.capacity_commodities = set() res.exchange_commodities = set() + res.silent_rptv = set() raw = cur.execute("SELECT Commodity.name FROM Commodity WHERE flag LIKE '%w%'").fetchall() waste_comms = {c[0] for c in raw} waste_dict = defaultdict(set) @@ -233,7 +238,7 @@ def _build_from_db( ' AND main.MyopicEfficiency.region = main.LifeTimeTech.region ' ' JOIN TimePeriod ' ' ON MyopicEfficiency.vintage = TimePeriod.period ' - # f' WHERE main.MyopicEfficiency.vintage <= {myopic_index.last_demand_year}' + f' WHERE main.MyopicEfficiency.vintage <= {myopic_index.last_demand_year}' ) raw = cur.execute(query).fetchall() # need to exclude the final year which is a non-demand year and should have no tech data @@ -241,11 +246,12 @@ def _build_from_db( # filter further if myopic if myopic_index: - periods = { + periods = [ p for p in periods if myopic_index.base_year <= p <= myopic_index.last_demand_year - } + ] techs = defaultdict(set) living_techs = set() # for screening the linked techs below + living_rtv = set() # filter out the dead ones... for element in raw: (r, ic, tech, v, oc, lifetime) = element @@ -267,45 +273,96 @@ def _build_from_db( else: techs[r, p].add(Tech(r, ic, tech, v, oc)) living_techs.add(tech) + living_rtv.add((r, tech, v)) if ic in source_comms: source_dict[r, p].add(ic) if oc in waste_comms: waste_dict[r, p].add(oc) - # End of life output - if any(( - p <= v+lifetime < p + period_length[p], # natural eol this period - tech in tech_retire and v < p <= v+lifetime - period_length[p], # allowed early retirement - (r, tech, v) in tech_survival_curve and v <= p <= v+lifetime - )): - try: - raw_eol = cur.execute( - 'SELECT region, tech, vintage, output_comm FROM EndOfLifeOutput ' - f' WHERE region == "{r}" AND tech == "{tech}" AND vintage == {v}' - ).fetchall() - - for _r, _tech, _v, _oc in raw_eol: - techs[_r, p].add(Tech(_r, _tech, 'EndOfLife', _v, _oc)) - source_dict[_r, p].add(_tech) - res.capacity_commodities.add(_tech) - living_techs.add(_tech) - if _oc in waste_comms: - waste_dict[_r, p].add(_oc) - except: - # EndOfLifeOutput table did not exist TODO remove this eventually - pass + # ExistingCapacity for checking + query = ( + 'SELECT region, tech, vintage, capacity FROM main.ExistingCapacity' + ) + raw = cur.execute(query).fetchall() + exs_cap = dict() + for r, tech, v, cap in raw: + exs_cap[r, tech, v] = cap + + # End of life output + query = ( + ' SELECT main.EndOfLifeOutput.region, EndOfLifeOutput.tech, EndOfLifeOutput.vintage, EndOfLifeOutput.output_comm, ' + f' coalesce(main.LifetimeProcess.lifetime, main.LifetimeTech.lifetime, {default_lifetime}) AS lifetime ' + ' FROM main.EndOfLifeOutput ' + ' LEFT JOIN main.LifetimeProcess ' + ' ON main.EndOfLifeOutput.tech = LifetimeProcess.tech ' + ' AND main.EndOfLifeOutput.vintage = LifetimeProcess.vintage ' + ' AND main.EndOfLifeOutput.region = LifetimeProcess.region ' + ' LEFT JOIN main.LifetimeTech ' + ' ON main.EndOfLifeOutput.tech = main.LifetimeTech.tech ' + ' AND main.EndOfLifeOutput.region = main.LifeTimeTech.region ' + ' JOIN TimePeriod ' + ' ON EndOfLifeOutput.vintage = TimePeriod.period ' + ) + raw = cur.execute(query).fetchall() + for (r, tech, v, oc, lifetime) in raw: + if tech in tech_uncap: + # No capacity to retire + continue + if exs_cap.get((r, tech, v), 0) <= 0: + continue + for p in periods: + if ( + (p == periods[0] and v + lifetime == p) # retires on start of horizon + or ( + (r, tech, v) in living_rtv and any(( + p <= v+lifetime < p + period_length[p], # natural eol this period + tech in tech_retire and v < p <= v+lifetime - period_length[p], # allowed early retirement + (r, tech, v) in tech_survival_curve and v <= p <= v+lifetime # survival curve retirement + )) + ) + ): + techs[r, p].add(Tech(r, tech, tech, v, oc)) + source_dict[r, p].add(tech) + res.capacity_commodities.add(tech) + if oc in waste_comms: + waste_dict[r, p].add(oc) + + # Emission end of life + query = ( + ' SELECT main.EmissionEndOfLife.region, EmissionEndOfLife.tech, EmissionEndOfLife.vintage, ' + f' coalesce(main.LifetimeProcess.lifetime, main.LifetimeTech.lifetime, {default_lifetime}) AS lifetime ' + ' FROM main.EmissionEndOfLife ' + ' LEFT JOIN main.LifetimeProcess ' + ' ON main.EmissionEndOfLife.tech = LifetimeProcess.tech ' + ' AND main.EmissionEndOfLife.vintage = LifetimeProcess.vintage ' + ' AND main.EmissionEndOfLife.region = LifetimeProcess.region ' + ' LEFT JOIN main.LifetimeTech ' + ' ON main.EmissionEndOfLife.tech = main.LifetimeTech.tech ' + ' AND main.EmissionEndOfLife.region = main.LifeTimeTech.region ' + ' JOIN TimePeriod ' + ' ON EmissionEndOfLife.vintage = TimePeriod.period ' + ) + raw = cur.execute(query).fetchall() + for (r, tech, v, lifetime) in raw: + if tech in tech_uncap: + # No capacity to retire + continue + if exs_cap.get((r, tech, v), 0) <= 0: + continue + if v + lifetime == periods[0]: + res.silent_rptv.add((r, periods[0], tech, v)) # Construction input - try: - raw = cur.execute('SELECT region, input_comm, tech, vintage FROM ConstructionInput').fetchall() - for r, ic, tech, v in raw: - techs[r, v].add(Tech(r, ic, 'Construction', v, tech)) - demand_dict[r, v].add(tech) - res.capacity_commodities.add(tech) - living_techs.add(tech) - except: - # ConstructionInput table did not exist TODO remove this eventually - pass + raw = cur.execute('SELECT region, input_comm, tech, vintage FROM ConstructionInput').fetchall() + for r, ic, tech, v in raw: + if tech in tech_uncap: + # No capacity to construct + continue + if v not in periods: + continue + techs[r, v].add(Tech(r, ic, tech, v, tech)) + demand_dict[r, v].add(tech) + res.capacity_commodities.add(tech) res.available_techs = techs res.demand_commodities = demand_dict diff --git a/temoa/temoa_model/table_data_puller.py b/temoa/temoa_model/table_data_puller.py index 9cd1889e6..58c9037be 100644 --- a/temoa/temoa_model/table_data_puller.py +++ b/temoa/temoa_model/table_data_puller.py @@ -99,7 +99,7 @@ def poll_capacity_results(M: TemoaModel, epsilon=1e-5) -> CapData: for r, t, v in M.V_NewCapacity.keys(): if v in M.time_optimize: val = value(M.V_NewCapacity[r, t, v]) - if abs(val) < epsilon: + if val < epsilon: continue new_cap = (r, t, v, val) built.append(new_cap) @@ -108,7 +108,7 @@ def poll_capacity_results(M: TemoaModel, epsilon=1e-5) -> CapData: net = [] for r, p, t, v in M.V_Capacity.keys(): val = value(M.V_Capacity[r, p, t, v]) - if abs(val) < epsilon: + if val < epsilon: continue new_net_cap = (r, p, t, v, val) net.append(new_net_cap) @@ -124,8 +124,8 @@ def poll_capacity_results(M: TemoaModel, epsilon=1e-5) -> CapData: if t in M.tech_retirement and v < p <= v + lifetime - value(M.PeriodLength[p]): early = value(M.V_RetiredCapacity[r, p, t, v]) eol -= early - early = 0 if abs(early) < epsilon else early - eol = 0 if abs(eol) < epsilon else eol + early = 0 if early < epsilon else early + eol = 0 if eol < epsilon else eol if early == 0 and eol == 0: continue new_retired_cap = (r, p, t, v, eol, early) @@ -150,7 +150,7 @@ def poll_flow_results(M: TemoaModel, epsilon=1e-5) -> dict[FI, dict[FlowType, fl for key in M.V_FlowIn.keys(): fi = FI(*key) flow = value(M.V_FlowIn[fi]) - if abs(flow) < epsilon: + if flow < epsilon: continue res[fi][FlowType.IN] = flow res[fi][FlowType.LOST] = (1 - temoa_rules.get_variable_efficiency(M, *key)) * flow @@ -159,7 +159,7 @@ def poll_flow_results(M: TemoaModel, epsilon=1e-5) -> dict[FI, dict[FlowType, fl for key in M.V_FlowOut.keys(): fi = FI(*key) flow = value(M.V_FlowOut[fi]) - if abs(flow) < epsilon: + if flow < epsilon: continue res[fi][FlowType.OUT] = flow @@ -172,7 +172,7 @@ def poll_flow_results(M: TemoaModel, epsilon=1e-5) -> dict[FI, dict[FlowType, fl for key in M.V_Curtailment.keys(): fi = FI(*key) val = value(M.V_Curtailment[fi]) - if abs(val) < epsilon: + if val < epsilon: continue res[fi][FlowType.CURTAIL] = val @@ -180,7 +180,7 @@ def poll_flow_results(M: TemoaModel, epsilon=1e-5) -> dict[FI, dict[FlowType, fl for key in M.V_Flex.keys(): fi = FI(*key) flow = value(M.V_Flex[fi]) - if abs(flow) < epsilon: + if flow < epsilon: continue res[fi][FlowType.FLEX] = flow res[fi][FlowType.OUT] -= flow @@ -200,7 +200,7 @@ def poll_flow_results(M: TemoaModel, epsilon=1e-5) -> dict[FI, dict[FlowType, fl distribution = value(M.SegFrac[p, s, d]) fi = FI(r, p, s, d, i, t, v, o) flow = value(M.V_FlowOutAnnual[r, p, i, t, v, o]) * distribution - if abs(flow) < epsilon: + if flow < epsilon: continue res[fi][FlowType.OUT] = flow res[fi][FlowType.IN] = flow / value(M.Efficiency[ritvo(fi)]) @@ -212,7 +212,7 @@ def poll_flow_results(M: TemoaModel, epsilon=1e-5) -> dict[FI, dict[FlowType, fl for d in M.time_of_day: fi = FI(r, p, s, d, i, t, v, o) flow = value(M.V_FlexAnnual[r, p, i, t, v, o]) * value(M.SegFrac[p, s, d]) - if abs(flow) < epsilon: + if flow < epsilon: continue res[fi][FlowType.FLEX] = flow res[fi][FlowType.OUT] -= flow @@ -260,7 +260,7 @@ def poll_storage_level_results(M: TemoaModel, epsilon=1e-5) -> dict[SLI, float]: continue state = value(M.V_StorageLevel[r, p, s, d, t, v]) / (value(M.SegFracPerSeason[p, s]) * value(M.DaysPerPeriod)) sli = SLI(r, p, s, d, t, v) - if abs(state) < epsilon: state = 0 # still want to know but decimals are ugly + if state < epsilon: state = 0 # still want to know but decimals are ugly res[sli] = state for r, p, s_seq, t, v in M.SeasonalStorageLevel_rpstv: @@ -272,7 +272,7 @@ def poll_storage_level_results(M: TemoaModel, epsilon=1e-5) -> dict[SLI, float]: for d in M.time_of_day: state = value(M.V_SeasonalStorageLevel[r, p, s_seq, t, v]) + value(M.V_StorageLevel[r, p, s, d, t, v]) * days_adjust sli = SLI(r, p, s_seq, d, t, v) - if abs(state) < epsilon: state = 0 # still want to know but decimals are ugly + if state < epsilon: state = 0 # still want to know but decimals are ugly res[sli] = state return res @@ -316,7 +316,7 @@ def poll_cost_results( for r, t, v in M.CostInvest.sparse_iterkeys(): # Returns only non-zero values # gather details... cap = value(M.V_NewCapacity[r, t, v]) - if abs(cap) < epsilon: + if cap < epsilon: continue loan_life = value(LLN[r, t, v]) loan_rate = value(M.LoanRate[r, t, v]) @@ -374,7 +374,7 @@ def poll_cost_results( for r, p, t, v in M.CostFixed.sparse_iterkeys(): cap = value(M.V_Capacity[r, p, t, v]) - if abs(cap) < epsilon: + if cap < epsilon: continue fixed_cost = value(M.CostFixed[r, p, t, v]) @@ -420,7 +420,7 @@ def poll_cost_results( for S_i in M.processInputs[r, p, t, v] for S_o in M.processOutputsByInput[r, p, t, v, S_i] ) - if abs(activity) < epsilon: + if activity < epsilon: continue var_cost = value(M.CostVariable[r, p, t, v]) diff --git a/temoa/temoa_model/temoa_initialize.py b/temoa/temoa_model/temoa_initialize.py index 163e9ee1b..81a472a48 100644 --- a/temoa/temoa_model/temoa_initialize.py +++ b/temoa/temoa_model/temoa_initialize.py @@ -263,7 +263,6 @@ def CheckEfficiencyIndices(M: 'TemoaModel'): # by checking by REGION and PERIOD... Each region/period is unique. c_physical = set(i for r, i, t, v, o in M.Efficiency.sparse_iterkeys()) c_physical = c_physical | set(i for r, i, t, v in M.ConstructionInput.sparse_iterkeys()) - techs = set(t for r, i, t, v, o in M.Efficiency.sparse_iterkeys()) c_outputs = set(o for r, i, t, v, o in M.Efficiency.sparse_iterkeys()) c_outputs = c_outputs | set(o for r, t, v, o in M.EndOfLifeOutput.sparse_iterkeys()) c_physical = c_physical | c_outputs @@ -279,6 +278,11 @@ def CheckEfficiencyIndices(M: 'TemoaModel'): f_msg = msg.format(', '.join(symdiff)) logger.error(f_msg) raise ValueError(f_msg) + + techs = set(t for r, i, t, v, o in M.Efficiency.sparse_iterkeys()) + techs = techs | set(t for r, t, v, o in M.EndOfLifeOutput.sparse_iterkeys()) + techs = techs | set(t for r, i, t, v in M.ConstructionInput.sparse_iterkeys()) + techs = techs | set(t for r, e, t, v in M.EmissionEndOfLife.sparse_iterkeys()) symdiff = techs.symmetric_difference(M.tech_all) if symdiff: @@ -356,6 +360,27 @@ def CheckEfficiencyVariable(M: 'TemoaModel'): ) +def CheckExistingCapacity(M: 'TemoaModel'): + """ + Check that all existing capacity vintages have a valid lifetime and are properly accounted for in the model. + """ + for r, t, v in M.ExistingCapacity.sparse_iterkeys(): + cap = value(M.ExistingCapacity[r, t, v]) + if t not in M.tech_all: + continue + if cap <= 0: + msg = f"Existing capacity {r, t, v} has non-positive capacity {cap}. This entry will be ignored." + logger.warning(msg) + life = value(M.LifetimeProcess[r, t, v]) + if (r, t, v) not in M.processPeriods and v + life > M.time_optimize.first(): + msg = ( + f"Existing capacity {r, t, v} with lifetime {life} and capacity {cap} should extend into " + "future periods but it not in process periods. Was it included in the Efficiency table?" + ) + logger.error(msg) + raise ValueError(msg) + + def CheckCapacityFactorProcess(M: 'TemoaModel'): count_rptv = dict() @@ -805,16 +830,6 @@ def CreateSparseDicts(M: 'TemoaModel'): # l_loan_life = value(M.LoanLifetimeProcess[l_process]) # if v + l_loan_life >= p: # M.processLoans[pindex] = True - - # Get all periods where the process can retire - if t not in M.tech_uncap and any(( - p <= v+l_lifetime < p + value(M.PeriodLength[p]), # natural eol this period - t in M.tech_retirement and v < p <= v+l_lifetime - value(M.PeriodLength[p]), # allowed early retirement - M.isSurvivalCurveProcess[r, t, v] and v <= p <= v+l_lifetime - )): - if (r, t, v) not in M.retirementPeriods: - M.retirementPeriods[r, t, v] = set() - M.retirementPeriods[r, t, v].add(p) # if tech is no longer active, don't include it if v + l_lifetime <= p: @@ -942,11 +957,44 @@ def CreateSparseDicts(M: 'TemoaModel'): # logger.error(f_msg) # raise ValueError(f_msg) + # Get all periods where processes can retire + unique_rtv = { + (r, t, v) for r, _i, t, v, _o in M.Efficiency.sparse_iterkeys() + } | set(M.ExistingCapacity.sparse_iterkeys()) + for r, t, v in unique_rtv: + if t in M.tech_uncap: + # No capacity to retire + continue + if t not in M.tech_all: + # Not an active technology so wont have a lifetime + # If it has an EOLoutput it will be in tech_all + continue + lifetime = value(M.LifetimeProcess[r, t, v]) + for p in M.time_optimize: + if ( + ( + p == M.time_optimize.first() + and v + lifetime == p + and value(M.ExistingCapacity[r, t, v]) > 0 + ) # retires on start of horizon + or ( + (r, t, v) in M.processPeriods and any(( + p <= v+lifetime < p + value(M.PeriodLength[p]), # natural eol this period + t in M.tech_retirement and v < p <= v+lifetime - value(M.PeriodLength[p]), # allowed early retirement + M.isSurvivalCurveProcess[r, t, v] and v <= p <= v+lifetime # survival curve retirement + )) + ) + ): + if (r, t, v) not in M.retirementPeriods: + M.retirementPeriods[r, t, v] = set() + M.retirementPeriods[r, t, v].add(p) + # Need this here for the commodity balance rpc set for r, i, t, v in M.ConstructionInput.sparse_iterkeys(): if (r, v, i) not in M.capacityConsumptionTechs: M.capacityConsumptionTechs[r, v, i] = set() M.capacityConsumptionTechs[r, v, i].add(t) + l_used_techs.add(t) for r, t, v, o in M.EndOfLifeOutput.sparse_iterkeys(): if (r, t, v) not in M.retirementPeriods: continue # might be running myopic @@ -955,13 +1003,16 @@ def CreateSparseDicts(M: 'TemoaModel'): if (r, p, o) not in M.retirementProductionProcesses: M.retirementProductionProcesses[r, p, o] = set() M.retirementProductionProcesses[r, p, o].add((t, v)) + l_used_techs.add(t) + for r, e, t, v in M.EmissionEndOfLife.sparse_iterkeys(): + if (r, t, v) not in M.retirementPeriods: + continue # might be running myopic + l_used_techs.add(t) + l_unused_techs = M.tech_all - l_used_techs if l_unused_techs: - msg = ( - "Notice: '{}' specified as technology, but it is not utilized in " - 'the Efficiency parameter.\n' - ) + msg = "Notice: '{}' specified as technology, but it is not used" for i in sorted(l_unused_techs): SE.write(msg.format(i)) @@ -1267,6 +1318,8 @@ def CreateSurvivalCurve(M: 'TemoaModel'): for (r, _, t, v, _) in M.Efficiency.sparse_iterkeys(): M.isSurvivalCurveProcess[r, t, v] = False # by default + for (r, t, v) in M.ExistingCapacity.sparse_iterkeys(): + M.isSurvivalCurveProcess[r, t, v] = False # by default # Collect rptv indices into (r, t, v): p dictionary for r, p, t, v in M.LifetimeSurvivalCurve.sparse_iterkeys(): @@ -1466,6 +1519,7 @@ def LifetimeProcessIndices(M: 'TemoaModel'): process indices that may be specified in the LifetimeProcess parameter. """ indices = set((r, t, v) for r, i, t, v, o in M.Efficiency.sparse_iterkeys()) + indices = indices | set(M.ExistingCapacity.sparse_iterkeys()) return indices diff --git a/temoa/temoa_model/temoa_model.py b/temoa/temoa_model/temoa_model.py index 81e554fb9..8ff609ade 100755 --- a/temoa/temoa_model/temoa_model.py +++ b/temoa/temoa_model/temoa_model.py @@ -384,6 +384,7 @@ def __init__(M, *args, **kwargs): # equations below. M.Create_SparseDicts = BuildAction(rule=CreateSparseDicts) M.initialize_Demands = BuildAction(rule=CreateDemands) + M.validate_ExistingCapacity = BuildAction(rule=CheckExistingCapacity) M.CapacityFactor_rpsdt = Set(dimen=5, initialize=CapacityFactorTechIndices) M.CapacityFactorTech = Param(M.CapacityFactor_rpsdt, default=1, validate=validate_0to1) diff --git a/tests/test_network_model_data.py b/tests/test_network_model_data.py index e9b4e7a19..ff06c6166 100644 --- a/tests/test_network_model_data.py +++ b/tests/test_network_model_data.py @@ -51,6 +51,7 @@ { 'name': 'basic', 'data': [ + [], # unlimited capacity techs [], # retirement techs [], # survival curve techs [ @@ -74,10 +75,10 @@ ('R1', 'p2', 't3', 2000, 'd1', 100), ('R1', 'p2', 't5', 2000, 'd2', 100), ], # techs - [ - (2020,), - (2025,), - ], # periods + [], # no existing capacity + [], # no eol output + [], # no emission end of life + [], # no construction input [], # no linked techs [], # no negative cost techs ], @@ -100,6 +101,7 @@ { 'name': 'bad linked tech', 'data': [ + [], # unlimited capacity techs [], # retirement techs [], # survival curve techs [ @@ -120,10 +122,10 @@ ('R1', 'p1', 'driven', 1990, 'd2', 100), ('R1', 's1', 't1', 2000, 'd1', 100), ], # techs - [ - (2020,), - (2025,), - ], # periods + [], # no existing capacity + [], # no eol output + [], # no emission end of life + [], # no construction input [('R1', 't4', 'nox', 'driven')], # t4 drives 'driven' with 'nox' emission [], # no negative cost techs ], @@ -148,6 +150,7 @@ # 'name': 'good linked tech', 'data': [ + [], # unlimited capacity techs [], # retirement techs [], # survival curve techs [ @@ -163,10 +166,10 @@ ('R1', 's2', 'driven', 1990, 'd2', 100), ('R1', 's1', 't1', 2000, 'd1', 100), ], # techs - [ - (2020,), - (2025,), - ], # periods + [], # no existing capacity + [], # no eol output + [], # no emission end of life + [], # no construction input [('R1', 't4', 'nox', 'driven')], # t4 drives 'driven' with 'nox' emission [], # no negative cost techs ], diff --git a/tests/testing_configs/config_materials.toml b/tests/testing_configs/config_materials.toml index 71f23bf7b..6898a28ed 100644 --- a/tests/testing_configs/config_materials.toml +++ b/tests/testing_configs/config_materials.toml @@ -1,6 +1,6 @@ # this config is used for testing in test_full_runs.py scenario = "test run" -scenario_mode = "perfect_foresight" +scenario_mode = "myopic" input_database = "tests/testing_outputs/materials.sqlite" output_database = "tests/testing_outputs/materials.sqlite" @@ -69,6 +69,8 @@ weight = "integer" # currently supported: [integer, normalized] [myopic] myopic_view = 2 # number of periods seen at one iteration +view_depth = 1 +step_size = 1 diff --git a/tests/testing_data/mediumville.sql b/tests/testing_data/mediumville.sql index 6739e0d03..33e4352c9 100644 --- a/tests/testing_data/mediumville.sql +++ b/tests/testing_data/mediumville.sql @@ -426,7 +426,6 @@ CREATE TABLE ExistingCapacity notes TEXT, PRIMARY KEY (region, tech, vintage) ); -INSERT INTO ExistingCapacity VALUES('A','EH',2020,200.0,'things',NULL); CREATE TABLE TechGroup ( group_name TEXT diff --git a/tests/testing_data/mediumville_sets.json b/tests/testing_data/mediumville_sets.json index aa5267582..23638a207 100644 --- a/tests/testing_data/mediumville_sets.json +++ b/tests/testing_data/mediumville_sets.json @@ -4225,9 +4225,7 @@ "tech_exchange": [ "FGF_pipe" ], - "tech_exist": [ - "EH" - ], + "tech_exist": [], "tech_flex": [ "EFL" ],