From cff993ce932b3216fa0e51153cb37e1e6379b979 Mon Sep 17 00:00:00 2001 From: Kyle Skolfield Date: Tue, 3 Dec 2024 11:46:17 -0700 Subject: [PATCH 1/8] minor testing updates --- gtep/config_options.py | 2 +- gtep/driver_config_work.py | 27 +- gtep/gtep_model.py | 380 ++++++++++++++--------------- gtep/tests/unit/test_gtep_model.py | 10 + 4 files changed, 211 insertions(+), 208 deletions(-) diff --git a/gtep/config_options.py b/gtep/config_options.py index 82272ff..c5c97c6 100644 --- a/gtep/config_options.py +++ b/gtep/config_options.py @@ -21,7 +21,7 @@ def _get_model_config(): CONFIG = ConfigBlock("GTEPModelConfig") CONFIG.declare( - "include_planning", + "include_investment", ConfigValue( default=True, domain=Bool, diff --git a/gtep/driver_config_work.py b/gtep/driver_config_work.py index 76d703d..33ee9aa 100644 --- a/gtep/driver_config_work.py +++ b/gtep/driver_config_work.py @@ -19,15 +19,12 @@ for k, v in mod_object.config.items(): print(f"k: {k}", f"v: {v}") - -exit() - +mod_object.config["include_investment"] = False mod_object.create_model() ic(mod_object) +exit() - -quit() TransformationFactory("gdp.bound_pretransformation").apply_to(mod_object.model) TransformationFactory("gdp.bigm").apply_to(mod_object.model) # opt = SolverFactory("gurobi") @@ -36,18 +33,18 @@ # # mod_object.results = opt.solve(mod_object.model, tee=True) mod_object.results = opt.solve(mod_object.model) -sol_object = ExpansionPlanningSolution() -sol_object.load_from_model(mod_object) -sol_object.dump_json("./gtep_solution.json") +# sol_object = ExpansionPlanningSolution() +# sol_object.load_from_model(mod_object) +# sol_object.dump_json("./gtep_solution.json") -sol_object.import_data_object(data_object) +# sol_object.import_data_object(data_object) -# sol_object.read_json("./gtep_lots_of_buses_solution.json") # "./gtep/data/WECC_USAEE" -# sol_object.read_json("./gtep_11bus_solution.json") # "./gtep/data/WECC_Reduced_USAEE" -# sol_object.read_json("./gtep_solution.json") -# sol_object.read_json("./updated_gtep_solution_test.json") -# sol_object.read_json("./gtep_wiggles.json") -sol_object.plot_levels(save_dir="./plots/") +# # sol_object.read_json("./gtep_lots_of_buses_solution.json") # "./gtep/data/WECC_USAEE" +# # sol_object.read_json("./gtep_11bus_solution.json") # "./gtep/data/WECC_Reduced_USAEE" +# # sol_object.read_json("./gtep_solution.json") +# # sol_object.read_json("./updated_gtep_solution_test.json") +# # sol_object.read_json("./gtep_wiggles.json") +# sol_object.plot_levels(save_dir="./plots/") # save_numerical_results = False # if save_numerical_results: diff --git a/gtep/gtep_model.py b/gtep/gtep_model.py index 4f4c8e9..5898e1b 100644 --- a/gtep/gtep_model.py +++ b/gtep/gtep_model.py @@ -599,11 +599,6 @@ def delta_bus_angle_rule(disj): domain=Reals, bounds=delta_bus_angle_bounds, rule=delta_bus_angle_rule ) - ## FIXME - # @disj.Constraint() - # def max_delta_bus_angle(disj): - # return abs(disj.deltaBusAngle) <= math.pi/6 - if m.config["flow_model"] == "DC": @disj.Constraint() @@ -1807,219 +1802,220 @@ def model_create_investment_stages(m, stages): # for t_1 in m.stages # if t_1 <= stage # ) + if m.config["include_investment"]: - # Linking generator investment status constraints - @m.Constraint(m.stages, m.thermalGenerators) - def gen_stats_link(m, stage, gen): - return ( - m.investmentStage[stage] - .genOperational[gen] - .indicator_var.get_associated_binary() - == m.investmentStage[stage - 1] - .genOperational[gen] - .indicator_var.get_associated_binary() - + m.investmentStage[stage - 1] - .genInstalled[gen] - .indicator_var.get_associated_binary() - - m.investmentStage[stage - 1] - .genRetired[gen] - .indicator_var.get_associated_binary() - if stage != 1 - else Constraint.Skip - ) + # Linking generator investment status constraints + @m.Constraint(m.stages, m.thermalGenerators) + def gen_stats_link(m, stage, gen): + return ( + m.investmentStage[stage] + .genOperational[gen] + .indicator_var.get_associated_binary() + == m.investmentStage[stage - 1] + .genOperational[gen] + .indicator_var.get_associated_binary() + + m.investmentStage[stage - 1] + .genInstalled[gen] + .indicator_var.get_associated_binary() + - m.investmentStage[stage - 1] + .genRetired[gen] + .indicator_var.get_associated_binary() + if stage != 1 + else Constraint.Skip + ) - # Renewable generation (in MW) retirement relationships - if len(m.stages) > 1: + # Renewable generation (in MW) retirement relationships + if len(m.stages) > 1: + + @m.Constraint(m.stages, m.renewableGenerators) + def renewable_retirement(m, stage, gen): + return sum( + m.investmentStage[t_2].renewableInstalled[gen] + for t_2 in m.stages + if t_2 <= stage - m.lifetimes[gen] + ) <= sum( + m.investmentStage[t_1].renewableRetired[gen] + + m.investmentStage[t_1].renewableExtended[gen] + for t_1 in m.stages + if t_1 <= stage + ) + # Total renewable generation (in MW) operational at a given stage + # is equal to what was operational and/or installed in the previous stage + # less what was retired in the previous stage @m.Constraint(m.stages, m.renewableGenerators) - def renewable_retirement(m, stage, gen): - return sum( - m.investmentStage[t_2].renewableInstalled[gen] - for t_2 in m.stages - if t_2 <= stage - m.lifetimes[gen] - ) <= sum( - m.investmentStage[t_1].renewableRetired[gen] - + m.investmentStage[t_1].renewableExtended[gen] - for t_1 in m.stages - if t_1 <= stage + def renewable_stats_link(m, stage, gen): + return ( + m.investmentStage[stage].renewableOperational[gen] + == m.investmentStage[stage - 1].renewableOperational[gen] + + m.investmentStage[stage - 1].renewableInstalled[gen] + - m.investmentStage[stage - 1].renewableRetired[gen] + if stage != 1 + else Constraint.Skip ) - # Total renewable generation (in MW) operational at a given stage - # is equal to what was operational and/or installed in the previous stage - # less what was retired in the previous stage - @m.Constraint(m.stages, m.renewableGenerators) - def renewable_stats_link(m, stage, gen): - return ( - m.investmentStage[stage].renewableOperational[gen] - == m.investmentStage[stage - 1].renewableOperational[gen] - + m.investmentStage[stage - 1].renewableInstalled[gen] - - m.investmentStage[stage - 1].renewableRetired[gen] - if stage != 1 - else Constraint.Skip - ) - - # If a gen is online at time t, it must have been online or installed at time t-1 - @m.LogicalConstraint(m.stages, m.thermalGenerators) - def consistent_operation(m, stage, gen): - return ( - m.investmentStage[stage] - .genOperational[gen] - .indicator_var.implies( - m.investmentStage[stage - 1].genOperational[gen].indicator_var - | m.investmentStage[stage - 1].genInstalled[gen].indicator_var + # If a gen is online at time t, it must have been online or installed at time t-1 + @m.LogicalConstraint(m.stages, m.thermalGenerators) + def consistent_operation(m, stage, gen): + return ( + m.investmentStage[stage] + .genOperational[gen] + .indicator_var.implies( + m.investmentStage[stage - 1].genOperational[gen].indicator_var + | m.investmentStage[stage - 1].genInstalled[gen].indicator_var + ) + if stage != 1 + else LogicalConstraint.Skip ) - if stage != 1 - else LogicalConstraint.Skip - ) - # If a gen is online at time t, it must be online, extended, or retired at time t+1 - @m.LogicalConstraint(m.stages, m.thermalGenerators) - def consistent_operation_future(m, stage, gen): - return ( - m.investmentStage[stage - 1] - .genOperational[gen] - .indicator_var.implies( - m.investmentStage[stage].genOperational[gen].indicator_var - | m.investmentStage[stage].genExtended[gen].indicator_var - | m.investmentStage[stage].genRetired[gen].indicator_var + # If a gen is online at time t, it must be online, extended, or retired at time t+1 + @m.LogicalConstraint(m.stages, m.thermalGenerators) + def consistent_operation_future(m, stage, gen): + return ( + m.investmentStage[stage - 1] + .genOperational[gen] + .indicator_var.implies( + m.investmentStage[stage].genOperational[gen].indicator_var + | m.investmentStage[stage].genExtended[gen].indicator_var + | m.investmentStage[stage].genRetired[gen].indicator_var + ) + if stage != 1 + else LogicalConstraint.Skip ) - if stage != 1 - else LogicalConstraint.Skip - ) - # Retirement in period t-1 implies disabled in period t - @m.LogicalConstraint(m.stages, m.thermalGenerators) - def full_retirement(m, stage, gen): - return ( - m.investmentStage[stage - 1] - .genRetired[gen] - .indicator_var.implies( - m.investmentStage[stage].genDisabled[gen].indicator_var + # Retirement in period t-1 implies disabled in period t + @m.LogicalConstraint(m.stages, m.thermalGenerators) + def full_retirement(m, stage, gen): + return ( + m.investmentStage[stage - 1] + .genRetired[gen] + .indicator_var.implies( + m.investmentStage[stage].genDisabled[gen].indicator_var + ) + if stage != 1 + else LogicalConstraint.Skip ) - if stage != 1 - else LogicalConstraint.Skip - ) - # If a gen is disabled at time t-1, it must stay disabled at time t - @m.LogicalConstraint(m.stages, m.thermalGenerators) - def consistent_disabled(m, stage, gen): - return ( - m.investmentStage[stage - 1] - .genDisabled[gen] - .indicator_var.implies( - m.investmentStage[stage].genDisabled[gen].indicator_var - | m.investmentStage[stage].genInstalled[gen].indicator_var + # If a gen is disabled at time t-1, it must stay disabled at time t + @m.LogicalConstraint(m.stages, m.thermalGenerators) + def consistent_disabled(m, stage, gen): + return ( + m.investmentStage[stage - 1] + .genDisabled[gen] + .indicator_var.implies( + m.investmentStage[stage].genDisabled[gen].indicator_var + | m.investmentStage[stage].genInstalled[gen].indicator_var + ) + if stage != 1 + else LogicalConstraint.Skip ) - if stage != 1 - else LogicalConstraint.Skip - ) - # If a gen is extended at time t-1, it must stay extended or be retired at time t - @m.LogicalConstraint(m.stages, m.thermalGenerators) - def consistent_extended(m, stage, gen): - return ( - m.investmentStage[stage - 1] - .genExtended[gen] - .indicator_var.implies( - m.investmentStage[stage].genExtended[gen].indicator_var - | m.investmentStage[stage].genRetired[gen].indicator_var + # If a gen is extended at time t-1, it must stay extended or be retired at time t + @m.LogicalConstraint(m.stages, m.thermalGenerators) + def consistent_extended(m, stage, gen): + return ( + m.investmentStage[stage - 1] + .genExtended[gen] + .indicator_var.implies( + m.investmentStage[stage].genExtended[gen].indicator_var + | m.investmentStage[stage].genRetired[gen].indicator_var + ) + if stage != 1 + else LogicalConstraint.Skip ) - if stage != 1 - else LogicalConstraint.Skip - ) - # Installation in period t-1 implies operational in period t - @m.LogicalConstraint(m.stages, m.thermalGenerators) - def full_investment(m, stage, gen): - return ( - m.investmentStage[stage - 1] - .genInstalled[gen] - .indicator_var.implies( - m.investmentStage[stage].genOperational[gen].indicator_var + # Installation in period t-1 implies operational in period t + @m.LogicalConstraint(m.stages, m.thermalGenerators) + def full_investment(m, stage, gen): + return ( + m.investmentStage[stage - 1] + .genInstalled[gen] + .indicator_var.implies( + m.investmentStage[stage].genOperational[gen].indicator_var + ) + if stage != 1 + else LogicalConstraint.Skip ) - if stage != 1 - else LogicalConstraint.Skip - ) - # If a branch is online at time t, it must have been online or installed at time t-1 - @m.LogicalConstraint(m.stages, m.transmission) - def consistent_branch_operation(m, stage, branch): - return ( - m.investmentStage[stage] - .branchOperational[branch] - .indicator_var.implies( - m.investmentStage[stage - 1].branchOperational[branch].indicator_var - | m.investmentStage[stage - 1].branchInstalled[branch].indicator_var + # If a branch is online at time t, it must have been online or installed at time t-1 + @m.LogicalConstraint(m.stages, m.transmission) + def consistent_branch_operation(m, stage, branch): + return ( + m.investmentStage[stage] + .branchOperational[branch] + .indicator_var.implies( + m.investmentStage[stage - 1].branchOperational[branch].indicator_var + | m.investmentStage[stage - 1].branchInstalled[branch].indicator_var + ) + if stage != 1 + else LogicalConstraint.Skip ) - if stage != 1 - else LogicalConstraint.Skip - ) - # If a branch is online at time t, it must be online, extended, or retired at time t+1 - @m.LogicalConstraint(m.stages, m.transmission) - def consistent_branch_operation_future(m, stage, branch): - return ( - m.investmentStage[stage - 1] - .branchOperational[branch] - .indicator_var.implies( - m.investmentStage[stage].branchOperational[branch].indicator_var - | m.investmentStage[stage].branchExtended[branch].indicator_var - | m.investmentStage[stage].branchRetired[branch].indicator_var + # If a branch is online at time t, it must be online, extended, or retired at time t+1 + @m.LogicalConstraint(m.stages, m.transmission) + def consistent_branch_operation_future(m, stage, branch): + return ( + m.investmentStage[stage - 1] + .branchOperational[branch] + .indicator_var.implies( + m.investmentStage[stage].branchOperational[branch].indicator_var + | m.investmentStage[stage].branchExtended[branch].indicator_var + | m.investmentStage[stage].branchRetired[branch].indicator_var + ) + if stage != 1 + else LogicalConstraint.Skip ) - if stage != 1 - else LogicalConstraint.Skip - ) - # Retirement in period t-1 implies disabled in period t - @m.LogicalConstraint(m.stages, m.transmission) - def full_branch_retirement(m, stage, branch): - return ( - m.investmentStage[stage - 1] - .branchRetired[branch] - .indicator_var.implies( - m.investmentStage[stage].branchDisabled[branch].indicator_var + # Retirement in period t-1 implies disabled in period t + @m.LogicalConstraint(m.stages, m.transmission) + def full_branch_retirement(m, stage, branch): + return ( + m.investmentStage[stage - 1] + .branchRetired[branch] + .indicator_var.implies( + m.investmentStage[stage].branchDisabled[branch].indicator_var + ) + if stage != 1 + else LogicalConstraint.Skip ) - if stage != 1 - else LogicalConstraint.Skip - ) - # If a branch is disabled at time t-1, it must stay disabled or be installed at time t - @m.LogicalConstraint(m.stages, m.transmission) - def consistent_branch_disabled(m, stage, branch): - return ( - m.investmentStage[stage - 1] - .branchDisabled[branch] - .indicator_var.implies( - m.investmentStage[stage].branchDisabled[branch].indicator_var - | m.investmentStage[stage].branchInstalled[branch].indicator_var + # If a branch is disabled at time t-1, it must stay disabled or be installed at time t + @m.LogicalConstraint(m.stages, m.transmission) + def consistent_branch_disabled(m, stage, branch): + return ( + m.investmentStage[stage - 1] + .branchDisabled[branch] + .indicator_var.implies( + m.investmentStage[stage].branchDisabled[branch].indicator_var + | m.investmentStage[stage].branchInstalled[branch].indicator_var + ) + if stage != 1 + else LogicalConstraint.Skip ) - if stage != 1 - else LogicalConstraint.Skip - ) - # If a branch is extended at time t-1, it must stay extended or be retired at time t - @m.LogicalConstraint(m.stages, m.transmission) - def consistent_branch_extended(m, stage, branch): - return ( - m.investmentStage[stage - 1] - .branchExtended[branch] - .indicator_var.implies( - m.investmentStage[stage].branchExtended[branch].indicator_var - | m.investmentStage[stage].branchRetired[branch].indicator_var + # If a branch is extended at time t-1, it must stay extended or be retired at time t + @m.LogicalConstraint(m.stages, m.transmission) + def consistent_branch_extended(m, stage, branch): + return ( + m.investmentStage[stage - 1] + .branchExtended[branch] + .indicator_var.implies( + m.investmentStage[stage].branchExtended[branch].indicator_var + | m.investmentStage[stage].branchRetired[branch].indicator_var + ) + if stage != 1 + else LogicalConstraint.Skip ) - if stage != 1 - else LogicalConstraint.Skip - ) - # Installation in period t-1 implies operational in period t - @m.LogicalConstraint(m.stages, m.transmission) - def full_branch_investment(m, stage, branch): - return ( - m.investmentStage[stage - 1] - .branchInstalled[branch] - .indicator_var.implies( - m.investmentStage[stage].branchOperational[branch].indicator_var + # Installation in period t-1 implies operational in period t + @m.LogicalConstraint(m.stages, m.transmission) + def full_branch_investment(m, stage, branch): + return ( + m.investmentStage[stage - 1] + .branchInstalled[branch] + .indicator_var.implies( + m.investmentStage[stage].branchOperational[branch].indicator_var + ) + if stage != 1 + else LogicalConstraint.Skip ) - if stage != 1 - else LogicalConstraint.Skip - ) diff --git a/gtep/tests/unit/test_gtep_model.py b/gtep/tests/unit/test_gtep_model.py index a454bdb..c21e6aa 100644 --- a/gtep/tests/unit/test_gtep_model.py +++ b/gtep/tests/unit/test_gtep_model.py @@ -113,3 +113,13 @@ def test_solve_bigm(self): self.assertEqual( str(u.get_units(modObject.model.total_cost_objective_rule.expr)), "USD" ) + + def test_no_investment(self): + md = read_debug_model() + modObject = ExpansionPlanningModel( + data=md, num_reps=1, len_reps=1, num_commit=1, num_dispatch=1 + ) + modObject.config["include_investment"] = False + modObject.create_model() + + pass From e09f49cebadfab77aa0ce168313a33fec311b32d Mon Sep 17 00:00:00 2001 From: Kyle Skolfield Date: Tue, 10 Dec 2024 13:45:17 -0700 Subject: [PATCH 2/8] some basic config flags --- gtep/config_options.py | 6 +- gtep/gtep_model.py | 493 ++++++++++++++++++++++------------------- 2 files changed, 265 insertions(+), 234 deletions(-) diff --git a/gtep/config_options.py b/gtep/config_options.py index c5c97c6..5250870 100644 --- a/gtep/config_options.py +++ b/gtep/config_options.py @@ -39,11 +39,11 @@ def _get_model_config(): ) CONFIG.declare( - "include_dispatch", + "include_redispatch", ConfigValue( default=True, domain=Bool, - description="Include economic dispatch formulation (i.e., OPF).", + description="Include economic redispatch formulation (i.e., >1 dispatch period per commitment period).", ), ) @@ -86,7 +86,7 @@ def _add_common_configs(CONFIG): CONFIG.declare( "scale_loads", ConfigValue( - default=False, + default=True, domain=Bool, description="Allow scaling of load values into future years; i.e., load scaling is represented in the model but not the data.", ), diff --git a/gtep/gtep_model.py b/gtep/gtep_model.py index 5898e1b..0781b37 100644 --- a/gtep/gtep_model.py +++ b/gtep/gtep_model.py @@ -361,26 +361,6 @@ def planning_reserve_requirement(b): ## NOTE: The following constraints can be split into rep_per and invest_stage components if desired - ## NOTE: Constraint (13) in the reference paper - # Minimum per-stage renewable generation requirement - @b.Constraint() - def renewable_generation_requirement(b): - renewableSurplusRepresentative = 0 - ## TODO: preprocess loads for the appropriate sum here - ed = 0 - for rep_per in b.representativePeriods: - for com_per in b.representativePeriod[rep_per].commitmentPeriods: - renewableSurplusRepresentative += ( - m.weights[rep_per] - * b.representativePeriod[rep_per] - .commitmentPeriod[com_per] - .renewableSurplusCommitment - ) - return ( - renewableSurplusRepresentative + b.quotaDeficit - >= m.renewableQuota[investment_stage] * ed - ) - # Operating costs for investment period @b.Expression() def operatingCostInvestment(b): @@ -460,6 +440,28 @@ def renewable_curtailment_cost(b): == m.investmentFactor[investment_stage] * renewableCurtailmentRep ) + ## NOTE: Constraint (13) in the reference paper + # Minimum per-stage renewable generation requirement + if m.config["include_investment"]: + + @b.Constraint() + def renewable_generation_requirement(b): + renewableSurplusRepresentative = 0 + ## TODO: preprocess loads for the appropriate sum here + ed = 0 + for rep_per in b.representativePeriods: + for com_per in b.representativePeriod[rep_per].commitmentPeriods: + renewableSurplusRepresentative += ( + m.weights[rep_per] + * b.representativePeriod[rep_per] + .commitmentPeriod[com_per] + .renewableSurplusCommitment + ) + return ( + renewableSurplusRepresentative + b.quotaDeficit + >= m.renewableQuota[investment_stage] * ed + ) + def add_dispatch_variables(b, dispatch_period): """Add dispatch-associated variables to representative period block.""" @@ -1056,11 +1058,10 @@ def commitment_period_rule(b, commitment_period): ## TODO: Redesign load scaling and allow nature of it as argument # Demand at each bus - temp_scale = 3 - temp_scale = 10 + if m.config["scale_loads"]: + temp_scale = 3 + temp_scale = 10 - scale_loads = True - if scale_loads: m.loads = { m.md.data["elements"]["load"][load_n]["bus"]: ( temp_scale @@ -1074,8 +1075,7 @@ def commitment_period_rule(b, commitment_period): ] for load_n in m.md.data["elements"]["load"] } - # Testing - # print(m.loads) + else: m.loads = { m.md.data["elements"]["load"][load_n]["bus"]: m.md.data["elements"]["load"][ @@ -1090,6 +1090,7 @@ def commitment_period_rule(b, commitment_period): b.dispatchPeriod[period].periodLength = Param(within=PositiveReals, default=1) add_dispatch_variables(b.dispatchPeriod[period], period) + ## TODO: if commitment is neglected but dispatch is still desired, pull something different here? or simply don't enforce linked commitment constraints? add_commitment_variables(b, commitment_period) add_commitment_constraints(b, commitment_period) @@ -1107,259 +1108,289 @@ def add_representative_period_variables(b, rep_per): def add_representative_period_constraints(b, rep_per): m = b.model() i_p = b.parent_block() + if m.config["include_commitment"]: - @b.LogicalConstraint(b.commitmentPeriods, m.thermalGenerators) - def consistent_commitment_shutdown(b, commitmentPeriod, thermalGen): - req_shutdown_periods = ceil( - 1 / float(m.md.data["elements"]["generator"][thermalGen]["ramp_down_rate"]) - ) - return ( - atmost( - req_shutdown_periods - 1, - [ - b.commitmentPeriod[commitmentPeriod - j - 1] + @b.LogicalConstraint(b.commitmentPeriods, m.thermalGenerators) + def consistent_commitment_shutdown(b, commitmentPeriod, thermalGen): + req_shutdown_periods = ceil( + 1 + / float( + m.md.data["elements"]["generator"][thermalGen]["ramp_down_rate"] + ) + ) + return ( + atmost( + req_shutdown_periods - 1, + [ + b.commitmentPeriod[commitmentPeriod - j - 1] + .genShutdown[thermalGen] + .indicator_var + for j in range(min(req_shutdown_periods, commitmentPeriod - 1)) + ], + ).land( + b.commitmentPeriod[commitmentPeriod - 1] .genShutdown[thermalGen] .indicator_var - for j in range(min(req_shutdown_periods, commitmentPeriod - 1)) - ], - ).land( - b.commitmentPeriod[commitmentPeriod - 1] - .genShutdown[thermalGen] - .indicator_var - ) - # | b.commitmentPeriod[commitmentPeriod-1].genOn.indicator_var) - .implies( - b.commitmentPeriod[commitmentPeriod] - .genShutdown[thermalGen] - .indicator_var - ) - if commitmentPeriod != 1 - else LogicalConstraint.Skip - ) - - @b.LogicalConstraint(b.commitmentPeriods, m.thermalGenerators) - def consistent_commitment_off_after_shutdown(b, commitmentPeriod, thermalGen): - req_shutdown_periods = ceil( - 1 / float(m.md.data["elements"]["generator"][thermalGen]["ramp_down_rate"]) - ) - return ( - atleast( - req_shutdown_periods, - [ - b.commitmentPeriod[commitmentPeriod - j - 1] + ) + # | b.commitmentPeriod[commitmentPeriod-1].genOn.indicator_var) + .implies( + b.commitmentPeriod[commitmentPeriod] .genShutdown[thermalGen] .indicator_var - for j in range(min(req_shutdown_periods, commitmentPeriod - 1)) - ], + ) + if commitmentPeriod != 1 + else LogicalConstraint.Skip ) - .land( - b.commitmentPeriod[commitmentPeriod - 1] - .genShutdown[thermalGen] - .indicator_var + + @b.LogicalConstraint(b.commitmentPeriods, m.thermalGenerators) + def consistent_commitment_off_after_shutdown(b, commitmentPeriod, thermalGen): + req_shutdown_periods = ceil( + 1 + / float( + m.md.data["elements"]["generator"][thermalGen]["ramp_down_rate"] + ) ) - .implies( - b.commitmentPeriod[commitmentPeriod].genOff[thermalGen].indicator_var + return ( + atleast( + req_shutdown_periods, + [ + b.commitmentPeriod[commitmentPeriod - j - 1] + .genShutdown[thermalGen] + .indicator_var + for j in range(min(req_shutdown_periods, commitmentPeriod - 1)) + ], + ) + .land( + b.commitmentPeriod[commitmentPeriod - 1] + .genShutdown[thermalGen] + .indicator_var + ) + .implies( + b.commitmentPeriod[commitmentPeriod] + .genOff[thermalGen] + .indicator_var + ) + if commitmentPeriod != 1 + else LogicalConstraint.Skip ) - if commitmentPeriod != 1 - else LogicalConstraint.Skip - ) - @b.LogicalConstraint(b.commitmentPeriods, m.thermalGenerators) - def consistent_commitment_startup(b, commitmentPeriod, thermalGen): - req_startup_periods = ceil( - 1 / float(m.md.data["elements"]["generator"][thermalGen]["ramp_up_rate"]) - ) - return ( - atmost( - req_startup_periods - 1, - [ - b.commitmentPeriod[commitmentPeriod - j - 1] + @b.LogicalConstraint(b.commitmentPeriods, m.thermalGenerators) + def consistent_commitment_startup(b, commitmentPeriod, thermalGen): + req_startup_periods = ceil( + 1 + / float(m.md.data["elements"]["generator"][thermalGen]["ramp_up_rate"]) + ) + return ( + atmost( + req_startup_periods - 1, + [ + b.commitmentPeriod[commitmentPeriod - j - 1] + .genStartup[thermalGen] + .indicator_var + for j in range(min(req_startup_periods, commitmentPeriod - 1)) + ], + ).land( + b.commitmentPeriod[commitmentPeriod - 1] .genStartup[thermalGen] .indicator_var - for j in range(min(req_startup_periods, commitmentPeriod - 1)) - ], - ).land( - b.commitmentPeriod[commitmentPeriod - 1] - .genStartup[thermalGen] - .indicator_var - ) - # | b.commitmentPeriod[commitmentPeriod-1].genOn.indicator_var) - .implies( - b.commitmentPeriod[commitmentPeriod] - .genStartup[thermalGen] - .indicator_var - ) - if commitmentPeriod != 1 - else LogicalConstraint.Skip - ) - - @b.LogicalConstraint(b.commitmentPeriods, m.thermalGenerators) - def consistent_commitment_on_after_startup(b, commitmentPeriod, thermalGen): - req_startup_periods = ceil( - 1 / float(m.md.data["elements"]["generator"][thermalGen]["ramp_up_rate"]) - ) - return ( - atleast( - req_startup_periods, - [ - b.commitmentPeriod[commitmentPeriod - j - 1] + ) + # | b.commitmentPeriod[commitmentPeriod-1].genOn.indicator_var) + .implies( + b.commitmentPeriod[commitmentPeriod] .genStartup[thermalGen] .indicator_var - for j in range(min(req_startup_periods, commitmentPeriod - 1)) - ], - ) - .land( - b.commitmentPeriod[commitmentPeriod - 1] - .genStartup[thermalGen] - .indicator_var - ) - .implies( - b.commitmentPeriod[commitmentPeriod].genOn[thermalGen].indicator_var + ) + if commitmentPeriod != 1 + else LogicalConstraint.Skip ) - if commitmentPeriod != 1 - else LogicalConstraint.Skip - ) - @b.LogicalConstraint(b.commitmentPeriods, m.thermalGenerators) - def consistent_commitment_uptime(b, commitmentPeriod, thermalGen): - return ( - atmost( - int(m.md.data["elements"]["generator"][thermalGen]["min_up_time"]) - 1, - [ - b.commitmentPeriod[commitmentPeriod - j - 1] - .genOn[thermalGen] - .indicator_var - for j in range( - min( - int( - m.md.data["elements"]["generator"][thermalGen][ - "min_up_time" - ] - ), - commitmentPeriod - 1, - ) - ) - ], - ) - .land( - b.commitmentPeriod[commitmentPeriod - 1].genOn[thermalGen].indicator_var - ) - .implies( - b.commitmentPeriod[commitmentPeriod].genOn[thermalGen].indicator_var + @b.LogicalConstraint(b.commitmentPeriods, m.thermalGenerators) + def consistent_commitment_on_after_startup(b, commitmentPeriod, thermalGen): + req_startup_periods = ceil( + 1 + / float(m.md.data["elements"]["generator"][thermalGen]["ramp_up_rate"]) ) - if commitmentPeriod - != 1 # int(m.md.data["elements"]["generator"][thermalGen]["min_up_time"])+1 - else LogicalConstraint.Skip - ) - - @b.LogicalConstraint(b.commitmentPeriods, m.thermalGenerators) - def consistent_commitment_shutdown_after_uptime(b, commitmentPeriod, thermalGen): - return ( - ( + return ( atleast( - int(m.md.data["elements"]["generator"][thermalGen]["min_up_time"]), + req_startup_periods, [ b.commitmentPeriod[commitmentPeriod - j - 1] - .genOn[thermalGen] + .genStartup[thermalGen] .indicator_var - for j in range( - min( - int( - m.md.data["elements"]["generator"][thermalGen][ - "min_up_time" - ] - ), - commitmentPeriod - 1, - ) - ) + for j in range(min(req_startup_periods, commitmentPeriod - 1)) ], - ).land( + ) + .land( b.commitmentPeriod[commitmentPeriod - 1] - .genOn[thermalGen] + .genStartup[thermalGen] .indicator_var ) - ).implies( - b.commitmentPeriod[commitmentPeriod].genOn[thermalGen].indicator_var - | b.commitmentPeriod[commitmentPeriod] - .genShutdown[thermalGen] - .indicator_var + .implies( + b.commitmentPeriod[commitmentPeriod].genOn[thermalGen].indicator_var + ) + if commitmentPeriod != 1 + else LogicalConstraint.Skip ) - if commitmentPeriod != 1 - else LogicalConstraint.Skip - ) - @b.LogicalConstraint(b.commitmentPeriods, m.thermalGenerators) - def consistent_commitment_downtime(b, commitmentPeriod, thermalGen): - return ( - ( + @b.LogicalConstraint(b.commitmentPeriods, m.thermalGenerators) + def consistent_commitment_uptime(b, commitmentPeriod, thermalGen): + return ( atmost( - int(m.md.data["elements"]["generator"][thermalGen]["min_down_time"]) + int(m.md.data["elements"]["generator"][thermalGen]["min_up_time"]) - 1, [ b.commitmentPeriod[commitmentPeriod - j - 1] - .genOff[thermalGen] + .genOn[thermalGen] .indicator_var for j in range( min( int( m.md.data["elements"]["generator"][thermalGen][ - "min_down_time" + "min_up_time" ] ), commitmentPeriod - 1, ) ) ], - ).land( + ) + .land( b.commitmentPeriod[commitmentPeriod - 1] - .genOff[thermalGen] + .genOn[thermalGen] .indicator_var ) - ).implies( - b.commitmentPeriod[commitmentPeriod].genOff[thermalGen].indicator_var + .implies( + b.commitmentPeriod[commitmentPeriod].genOn[thermalGen].indicator_var + ) + if commitmentPeriod + != 1 # int(m.md.data["elements"]["generator"][thermalGen]["min_up_time"])+1 + else LogicalConstraint.Skip ) - if commitmentPeriod - != 1 # >= int(m.md.data["elements"]["generator"][thermalGen]["min_down_time"])+1 - else LogicalConstraint.Skip - ) - @b.LogicalConstraint(b.commitmentPeriods, m.thermalGenerators) - def consistent_commitment_start_after_downtime(b, commitmentPeriod, thermalGen): - return ( - ( - atleast( - int( - m.md.data["elements"]["generator"][thermalGen]["min_down_time"] - ), - [ - b.commitmentPeriod[commitmentPeriod - j - 1] + @b.LogicalConstraint(b.commitmentPeriods, m.thermalGenerators) + def consistent_commitment_shutdown_after_uptime( + b, commitmentPeriod, thermalGen + ): + return ( + ( + atleast( + int( + m.md.data["elements"]["generator"][thermalGen][ + "min_up_time" + ] + ), + [ + b.commitmentPeriod[commitmentPeriod - j - 1] + .genOn[thermalGen] + .indicator_var + for j in range( + min( + int( + m.md.data["elements"]["generator"][thermalGen][ + "min_up_time" + ] + ), + commitmentPeriod - 1, + ) + ) + ], + ).land( + b.commitmentPeriod[commitmentPeriod - 1] + .genOn[thermalGen] + .indicator_var + ) + ).implies( + b.commitmentPeriod[commitmentPeriod].genOn[thermalGen].indicator_var + | b.commitmentPeriod[commitmentPeriod] + .genShutdown[thermalGen] + .indicator_var + ) + if commitmentPeriod != 1 + else LogicalConstraint.Skip + ) + + @b.LogicalConstraint(b.commitmentPeriods, m.thermalGenerators) + def consistent_commitment_downtime(b, commitmentPeriod, thermalGen): + return ( + ( + atmost( + int( + m.md.data["elements"]["generator"][thermalGen][ + "min_down_time" + ] + ) + - 1, + [ + b.commitmentPeriod[commitmentPeriod - j - 1] + .genOff[thermalGen] + .indicator_var + for j in range( + min( + int( + m.md.data["elements"]["generator"][thermalGen][ + "min_down_time" + ] + ), + commitmentPeriod - 1, + ) + ) + ], + ).land( + b.commitmentPeriod[commitmentPeriod - 1] .genOff[thermalGen] .indicator_var - for j in range( - min( - int( - m.md.data["elements"]["generator"][thermalGen][ - "min_down_time" - ] - ), - commitmentPeriod - 1, + ) + ).implies( + b.commitmentPeriod[commitmentPeriod] + .genOff[thermalGen] + .indicator_var + ) + if commitmentPeriod + != 1 # >= int(m.md.data["elements"]["generator"][thermalGen]["min_down_time"])+1 + else LogicalConstraint.Skip + ) + + @b.LogicalConstraint(b.commitmentPeriods, m.thermalGenerators) + def consistent_commitment_start_after_downtime(b, commitmentPeriod, thermalGen): + return ( + ( + atleast( + int( + m.md.data["elements"]["generator"][thermalGen][ + "min_down_time" + ] + ), + [ + b.commitmentPeriod[commitmentPeriod - j - 1] + .genOff[thermalGen] + .indicator_var + for j in range( + min( + int( + m.md.data["elements"]["generator"][thermalGen][ + "min_down_time" + ] + ), + commitmentPeriod - 1, + ) ) - ) - ], - ).land( - b.commitmentPeriod[commitmentPeriod - 1] + ], + ).land( + b.commitmentPeriod[commitmentPeriod - 1] + .genOff[thermalGen] + .indicator_var + ) + ).implies( + b.commitmentPeriod[commitmentPeriod] .genOff[thermalGen] .indicator_var + | b.commitmentPeriod[commitmentPeriod] + .genStartup[thermalGen] + .indicator_var ) - ).implies( - b.commitmentPeriod[commitmentPeriod].genOff[thermalGen].indicator_var - | b.commitmentPeriod[commitmentPeriod] - .genStartup[thermalGen] - .indicator_var + if commitmentPeriod != 1 + else LogicalConstraint.Skip ) - if commitmentPeriod != 1 - else LogicalConstraint.Skip - ) def representative_period_rule(b, representative_period): @@ -1372,12 +1403,12 @@ def representative_period_rule(b, representative_period): i_s = b.parent_block() b.currentPeriod = representative_period + if m.config["include_commitment"] or m.config["include_redispatch"]: + b.commitmentPeriods = RangeSet(m.numCommitmentPeriods[representative_period]) + b.commitmentPeriod = Block(b.commitmentPeriods, rule=commitment_period_rule) - b.commitmentPeriods = RangeSet(m.numCommitmentPeriods[representative_period]) - b.commitmentPeriod = Block(b.commitmentPeriods, rule=commitment_period_rule) - - add_representative_period_variables(b, representative_period) - add_representative_period_constraints(b, representative_period) + add_representative_period_variables(b, representative_period) + add_representative_period_constraints(b, representative_period) def investment_stage_rule(b, investment_stage): From 76c8ec1f890dd75223672f0a13156c6a923c39b9 Mon Sep 17 00:00:00 2001 From: Kyle Skolfield Date: Thu, 16 Jan 2025 15:44:43 -0700 Subject: [PATCH 3/8] model now reads config before building --- gtep/gtep_model.py | 43 +++++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/gtep/gtep_model.py b/gtep/gtep_model.py index 0781b37..91a74ce 100644 --- a/gtep/gtep_model.py +++ b/gtep/gtep_model.py @@ -20,7 +20,7 @@ from math import ceil -from config_options import _get_model_config +from config_options import _get_model_config, _add_common_configs # Define what a USD is for pyomo units purposes # This will be set to a base year and we will do NPV calculations @@ -83,11 +83,14 @@ def __init__( self.config = _get_model_config() self.timer = TicTocTimer() + _add_common_configs(self.config) + def create_model(self): """Create concrete Pyomo model object associated with the ExpansionPlanningModel""" self.timer.tic("Creating GTEP Model") m = ConcreteModel() + m.config = self.config m.rng = np.random.default_rng(seed=123186) ## TODO: checks for active/built/inactive/unbuilt/etc. gen @@ -1835,25 +1838,25 @@ def model_create_investment_stages(m, stages): # ) if m.config["include_investment"]: - # Linking generator investment status constraints - @m.Constraint(m.stages, m.thermalGenerators) - def gen_stats_link(m, stage, gen): - return ( - m.investmentStage[stage] - .genOperational[gen] - .indicator_var.get_associated_binary() - == m.investmentStage[stage - 1] - .genOperational[gen] - .indicator_var.get_associated_binary() - + m.investmentStage[stage - 1] - .genInstalled[gen] - .indicator_var.get_associated_binary() - - m.investmentStage[stage - 1] - .genRetired[gen] - .indicator_var.get_associated_binary() - if stage != 1 - else Constraint.Skip - ) + # # Linking generator investment status constraints + # @m.Constraint(m.stages, m.thermalGenerators) + # def gen_stats_link(m, stage, gen): + # return ( + # m.investmentStage[stage] + # .genOperational[gen] + # .indicator_var.get_associated_binary() + # == m.investmentStage[stage - 1] + # .genOperational[gen] + # .indicator_var.get_associated_binary() + # + m.investmentStage[stage - 1] + # .genInstalled[gen] + # .indicator_var.get_associated_binary() + # - m.investmentStage[stage - 1] + # .genRetired[gen] + # .indicator_var.get_associated_binary() + # if stage != 1 + # else Constraint.Skip + # ) # Renewable generation (in MW) retirement relationships if len(m.stages) > 1: From e7060bf585aa1c344933939f5e44f7a44d0d3deb Mon Sep 17 00:00:00 2001 From: Kyle Skolfield Date: Mon, 20 Jan 2025 10:52:46 -0700 Subject: [PATCH 4/8] tweaking config stuff --- gtep/config_options.py | 2 +- gtep/gtep_model.py | 35 +++++++++++++++++++++++++++++------ 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/gtep/config_options.py b/gtep/config_options.py index 5250870..09fbd6f 100644 --- a/gtep/config_options.py +++ b/gtep/config_options.py @@ -66,7 +66,7 @@ def _get_model_config(): CONFIG.declare( "time_period_dict", ConfigDict( - description="Time period dict, specified as \{(investment period #, length): \{(commitment period #, length): \{dispatch period #: length\}\}\}" + description="Time period dict, specified as \{(investment period #, length): \{(representative period #, length): \{(commitment period #, length): \{dispatch period #: length\}\}\}" ), ) diff --git a/gtep/gtep_model.py b/gtep/gtep_model.py index 0781b37..939a305 100644 --- a/gtep/gtep_model.py +++ b/gtep/gtep_model.py @@ -480,7 +480,7 @@ def thermal_generation_limits(b, thermalGen): domain=NonNegativeReals, bounds=thermal_generation_limits, initialize=0, - units=u.MW * u.hr, + units=u.MW, ) # Define bounds on renewable generator active generation @@ -492,7 +492,7 @@ def renewable_generation_limits(b, renewableGen): domain=NonNegativeReals, bounds=renewable_generation_limits, initialize=0, - units=u.MW * u.hr, + units=u.MW, ) # Define bounds on renewable generator curtailment @@ -504,7 +504,7 @@ def curtailment_limits(b, renewableGen): domain=NonNegativeReals, bounds=curtailment_limits, initialize=0, - units=u.MW * u.hr, + units=u.MW, ) # Per generator surplus @@ -522,7 +522,7 @@ def renewableCurtailmentCost(b, renewableGen): # Per generator cost @b.Expression(m.thermalGenerators) def generatorCost(b, gen): - return b.thermalGeneration[gen] * m.fuelCost[gen] + return b.thermalGeneration[gen] * m.fuelCost[gen] * b.di # Load shed per bus b.loadShed = Var(m.buses, domain=NonNegativeReals, initialize=0, units=u.MW * u.hr) @@ -560,7 +560,7 @@ def power_flow_limits(b, branch): domain=Reals, bounds=power_flow_limits, initialize=0, - units=u.MW * u.hr, + units=u.MW, ) @b.Disjunct(m.transmission) @@ -1109,7 +1109,8 @@ def add_representative_period_constraints(b, rep_per): m = b.model() i_p = b.parent_block() if m.config["include_commitment"]: - + ##FIXME this needs to be updated for variable length commitment periods + ## do this by (pre) processing the set of commitment periods for req_shutdown_periods @b.LogicalConstraint(b.commitmentPeriods, m.thermalGenerators) def consistent_commitment_shutdown(b, commitmentPeriod, thermalGen): req_shutdown_periods = ceil( @@ -1855,6 +1856,27 @@ def gen_stats_link(m, stage, gen): else Constraint.Skip ) + if len(m.stages) > 1: + ##FIXME Rewrite as logic + @m.Constraint(m.stages, m.thermalGenerators) + def gen_retirement(m, stage, gen): + return sum( + m.investmentStage[t_2] + .genInstalled[gen] + .indicator_var.get_associated_binary() + for t_2 in m.stages + if t_2 <= stage - m.lifetimes[gen] + ) <= sum( + m.investmentStage[t_1] + .genRetired[gen] + .indicator_var.get_associated_binary() + + m.investmentStage[t_1] + .genExtended[gen] + .indicator_var.get_associated_binary() + for t_1 in m.stages + if t_1 <= stage + ) + # Renewable generation (in MW) retirement relationships if len(m.stages) > 1: @@ -1928,6 +1950,7 @@ def full_retirement(m, stage, gen): ) # If a gen is disabled at time t-1, it must stay disabled at time t + ##FIXME Disabling is permanent. Re investment is a "new" unit. Remove the "or" @m.LogicalConstraint(m.stages, m.thermalGenerators) def consistent_disabled(m, stage, gen): return ( From 52ee78c7449c0571f18539b5b0f1de9109dd41d0 Mon Sep 17 00:00:00 2001 From: Kyle Skolfield Date: Mon, 20 Jan 2025 11:35:07 -0700 Subject: [PATCH 5/8] model updates --- gtep/gtep_model.py | 170 +++++++++++++++++++++++---------------------- 1 file changed, 87 insertions(+), 83 deletions(-) diff --git a/gtep/gtep_model.py b/gtep/gtep_model.py index c1b401b..7930338 100644 --- a/gtep/gtep_model.py +++ b/gtep/gtep_model.py @@ -525,7 +525,9 @@ def renewableCurtailmentCost(b, renewableGen): # Per generator cost @b.Expression(m.thermalGenerators) def generatorCost(b, gen): - return b.thermalGeneration[gen] * m.fuelCost[gen] * b.di + return b.thermalGeneration[gen] * m.fuelCost[gen] + + # * b.dispatchLength # Load shed per bus b.loadShed = Var(m.buses, domain=NonNegativeReals, initialize=0, units=u.MW * u.hr) @@ -1271,47 +1273,48 @@ def consistent_commitment_uptime(b, commitmentPeriod, thermalGen): else LogicalConstraint.Skip ) - @b.LogicalConstraint(b.commitmentPeriods, m.thermalGenerators) - def consistent_commitment_shutdown_after_uptime( - b, commitmentPeriod, thermalGen - ): - return ( - ( - atleast( - int( - m.md.data["elements"]["generator"][thermalGen][ - "min_up_time" - ] - ), - [ - b.commitmentPeriod[commitmentPeriod - j - 1] - .genOn[thermalGen] - .indicator_var - for j in range( - min( - int( - m.md.data["elements"]["generator"][thermalGen][ - "min_up_time" - ] - ), - commitmentPeriod - 1, - ) - ) - ], - ).land( - b.commitmentPeriod[commitmentPeriod - 1] - .genOn[thermalGen] - .indicator_var - ) - ).implies( - b.commitmentPeriod[commitmentPeriod].genOn[thermalGen].indicator_var - | b.commitmentPeriod[commitmentPeriod] - .genShutdown[thermalGen] - .indicator_var - ) - if commitmentPeriod != 1 - else LogicalConstraint.Skip - ) + ##FIXME: Is this constraint necessary? + # @b.LogicalConstraint(b.commitmentPeriods, m.thermalGenerators) + # def consistent_commitment_shutdown_after_uptime( + # b, commitmentPeriod, thermalGen + # ): + # return ( + # ( + # atleast( + # int( + # m.md.data["elements"]["generator"][thermalGen][ + # "min_up_time" + # ] + # ), + # [ + # b.commitmentPeriod[commitmentPeriod - j - 1] + # .genOn[thermalGen] + # .indicator_var + # for j in range( + # min( + # int( + # m.md.data["elements"]["generator"][thermalGen][ + # "min_up_time" + # ] + # ), + # commitmentPeriod - 1, + # ) + # ) + # ], + # ).land( + # b.commitmentPeriod[commitmentPeriod - 1] + # .genOn[thermalGen] + # .indicator_var + # ) + # ).implies( + # b.commitmentPeriod[commitmentPeriod].genOn[thermalGen].indicator_var + # | b.commitmentPeriod[commitmentPeriod] + # .genShutdown[thermalGen] + # .indicator_var + # ) + # if commitmentPeriod != 1 + # else LogicalConstraint.Skip + # ) @b.LogicalConstraint(b.commitmentPeriods, m.thermalGenerators) def consistent_commitment_downtime(b, commitmentPeriod, thermalGen): @@ -1354,47 +1357,48 @@ def consistent_commitment_downtime(b, commitmentPeriod, thermalGen): else LogicalConstraint.Skip ) - @b.LogicalConstraint(b.commitmentPeriods, m.thermalGenerators) - def consistent_commitment_start_after_downtime(b, commitmentPeriod, thermalGen): - return ( - ( - atleast( - int( - m.md.data["elements"]["generator"][thermalGen][ - "min_down_time" - ] - ), - [ - b.commitmentPeriod[commitmentPeriod - j - 1] - .genOff[thermalGen] - .indicator_var - for j in range( - min( - int( - m.md.data["elements"]["generator"][thermalGen][ - "min_down_time" - ] - ), - commitmentPeriod - 1, - ) - ) - ], - ).land( - b.commitmentPeriod[commitmentPeriod - 1] - .genOff[thermalGen] - .indicator_var - ) - ).implies( - b.commitmentPeriod[commitmentPeriod] - .genOff[thermalGen] - .indicator_var - | b.commitmentPeriod[commitmentPeriod] - .genStartup[thermalGen] - .indicator_var - ) - if commitmentPeriod != 1 - else LogicalConstraint.Skip - ) + ##FIXME: is this constraint necessary? + # @b.LogicalConstraint(b.commitmentPeriods, m.thermalGenerators) + # def consistent_commitment_start_after_downtime(b, commitmentPeriod, thermalGen): + # return ( + # ( + # atleast( + # int( + # m.md.data["elements"]["generator"][thermalGen][ + # "min_down_time" + # ] + # ), + # [ + # b.commitmentPeriod[commitmentPeriod - j - 1] + # .genOff[thermalGen] + # .indicator_var + # for j in range( + # min( + # int( + # m.md.data["elements"]["generator"][thermalGen][ + # "min_down_time" + # ] + # ), + # commitmentPeriod - 1, + # ) + # ) + # ], + # ).land( + # b.commitmentPeriod[commitmentPeriod - 1] + # .genOff[thermalGen] + # .indicator_var + # ) + # ).implies( + # b.commitmentPeriod[commitmentPeriod] + # .genOff[thermalGen] + # .indicator_var + # | b.commitmentPeriod[commitmentPeriod] + # .genStartup[thermalGen] + # .indicator_var + # ) + # if commitmentPeriod != 1 + # else LogicalConstraint.Skip + # ) def representative_period_rule(b, representative_period): From f9abb374c83c6f867122483695728fbf958ce32d Mon Sep 17 00:00:00 2001 From: Kyle Skolfield Date: Mon, 20 Jan 2025 17:39:43 -0700 Subject: [PATCH 6/8] fixme notes for immediate removal (separate pr) --- gtep/gtep_model.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/gtep/gtep_model.py b/gtep/gtep_model.py index 7930338..d5af180 100644 --- a/gtep/gtep_model.py +++ b/gtep/gtep_model.py @@ -808,6 +808,7 @@ def operating_limit_min(d, dispatchPeriod): ) # Maximum operating limits + ##FIXME: don't need this constraint @disj.Constraint(b.dispatchPeriods) def operating_limit_max(d, dispatchPeriod): return ( @@ -851,6 +852,8 @@ def max_spinning_reserve(disj, dispatchPeriod, generator): <= m.maxSpinningReserve[generator] * m.thermalCapacity[generator] ) + ##FIXME: add quick start reserve = 0 + @b.Disjunct(m.thermalGenerators) def genStartup(disj, generator): b = disj.parent_block() @@ -882,6 +885,7 @@ def ramp_up_limits(disj, dispatchPeriod, generator): m.rampUpRates[generator] * b.dispatchPeriod[dispatchPeriod].periodLength, ) + ##FIXME: I don't think this parenthesis is correct -- thermal capacity should go inside with the second term. Or do I need to make this two constraints? * m.thermalCapacity[generator] if dispatchPeriod != 1 else Constraint.Skip @@ -917,6 +921,7 @@ def ramp_down_limits(disj, dispatchPeriod, generator): m.rampDownRates[generator] * b.dispatchPeriod[dispatchPeriod].periodLength, ) + ##FIXME: I don't think this parenthesis is correct -- thermal capacity should go inside with the second term. Or do I need to make this two constraints? * m.thermalCapacity[generator] if dispatchPeriod != 1 else Constraint.Skip @@ -934,6 +939,7 @@ def operating_limit_max(disj, dispatchPeriod): # Maximum quickstart reserve constraint ## NOTE: maxQuickstartReserve is a percentage of thermalCapacity + ##FIXME: This isn't needed. instead we need to set spinning reserve to 0. @disj.Constraint(b.dispatchPeriods, m.thermalGenerators) def max_quickstart_reserve(disj, dispatchPeriod, generator): return ( From f96309232dfaa5066f36212a21994b0cd9c84757 Mon Sep 17 00:00:00 2001 From: Kyle Skolfield Date: Tue, 21 Jan 2025 16:57:24 -0700 Subject: [PATCH 7/8] time nest shapow --- gtep/config_options.py | 1 + gtep/config_time_nest.py | 7 +++++++ gtep/gtep_model.py | 4 ++++ 3 files changed, 12 insertions(+) create mode 100644 gtep/config_time_nest.py diff --git a/gtep/config_options.py b/gtep/config_options.py index 09fbd6f..9450fe5 100644 --- a/gtep/config_options.py +++ b/gtep/config_options.py @@ -124,6 +124,7 @@ def _add_investment_configs(CONFIG): description="Include transmission investment options", ), ) + CONFIG.declare("transmission_switching", ConfigValue(default=False, domain=Bool, description="Allow transmission switching during dispatch")) def _add_solver_configs(CONFIG): diff --git a/gtep/config_time_nest.py b/gtep/config_time_nest.py new file mode 100644 index 0000000..6b493ac --- /dev/null +++ b/gtep/config_time_nest.py @@ -0,0 +1,7 @@ +import pandas as pd + + + +class timeNest(object): + def __init__(self): + pass \ No newline at end of file diff --git a/gtep/gtep_model.py b/gtep/gtep_model.py index d5af180..00d30e3 100644 --- a/gtep/gtep_model.py +++ b/gtep/gtep_model.py @@ -587,6 +587,7 @@ def bus_angle_bounds(disj, bus): ) ] + ##FIXME: we need this for all buses all the time disj.busAngle = Var( disj.branch_buses, domain=Reals, initialize=0, bounds=bus_angle_bounds ) @@ -601,6 +602,7 @@ def delta_bus_angle_rule(disj): tb = m.transmission[branch]["to_bus"] return disj.busAngle[tb] - disj.busAngle[fb] + ##FIXME: we can just add this as a constraint rather than a variable, this is weird # @KyleSkolfield - I think this var is unused and commented it out, can we delete? disj.deltaBusAngle = Var( domain=Reals, bounds=delta_bus_angle_bounds, rule=delta_bus_angle_rule @@ -658,6 +660,8 @@ def must_use_active_branches(b, branch): ) ) + ##FIXME: this logic isn't true. remove when con fig fixes switching. + ##FIXME: replace with disabled/retired \implies not in use # JSC update - If a branch is not in use, it must be inactive. # Update this when switching is implemented @b.LogicalConstraint(m.transmission) From e55f87d530a5e10ad68d35290a20472900e38388 Mon Sep 17 00:00:00 2001 From: Kyle Skolfield Date: Wed, 22 Jan 2025 01:30:41 -0700 Subject: [PATCH 8/8] data structures begin --- gtep/config_time_nest.py | 7 ------- gtep/time_nest_data.py | 9 +++++++++ 2 files changed, 9 insertions(+), 7 deletions(-) delete mode 100644 gtep/config_time_nest.py create mode 100644 gtep/time_nest_data.py diff --git a/gtep/config_time_nest.py b/gtep/config_time_nest.py deleted file mode 100644 index 6b493ac..0000000 --- a/gtep/config_time_nest.py +++ /dev/null @@ -1,7 +0,0 @@ -import pandas as pd - - - -class timeNest(object): - def __init__(self): - pass \ No newline at end of file diff --git a/gtep/time_nest_data.py b/gtep/time_nest_data.py new file mode 100644 index 0000000..04e0221 --- /dev/null +++ b/gtep/time_nest_data.py @@ -0,0 +1,9 @@ +import awkward as ak + +# build helper wrapper thingy for awkward arrays +# how to interface with config? + # one thing it should do is send itself to config, most notably the time dict shenanigans + +class timeNestData(object): + def __init__(self): + pass \ No newline at end of file