Some questions about the post-solve process and no.commit module

27 views
Skip to first unread message

Chen Wang

unread,
Jan 9, 2024, 8:46:34 AM1/9/24
to Switch Model
Dear Prof. Matthias Fripp,
         Hi, Prof. Matthias, really sorry that I would bother you again with some questions. I know that you were very busy last week, so maybe you didn't have time to reply to me about my previous questions. But  I really want to know how to solve these questions and no one can help me but you. So,  please forgive me for asking you again. Well, my questions and problems are as bellow:
        The first question, which is also the most important question, is about the "DispatchTx" in the transmission.transport module. Last week, I have asked you the question about how to add codes in order to calculate the total MWh of power moved across all transmission lines during each period, and you have given me the solution codes. Even though with your codes, another error had happened, your advice about the "m.DIRECTIONAL_TX" had inspired me so much. I found another way to calculate that and it works. The relevant codes to calculate that are as follows:
def TxGenerationReceived(model, period):
    return sum(
        model.DispatchTx[zone_from, zone_to, tp] * model.tp_weight_in_year[tp]
        for (zone_from, zone_to) in model.DIRECTIONAL_TX
        for tp in model.TPS_IN_PERIOD[period]
    )

Thank you so much for your inspired advice again! These days, I have found another questions when I wanted to add my data about the transmission lines in the "transmission_lines.csv". I have a dataset of many transmission lines in China, and some of them have the same "trans_lz1" and "trans_lz2". However, the "trans_capital_cost_per_mw_km" of them are different, so I divided them into different groups and constructed the corresponding “DIRECTIONAL_TX”, "TX_CONNECTIONS_TO_ZONE",  and "trans_d_line1" in the transmission.transport.build module. The relevant codes about this are as below:
def init_DIRECTIONAL_TX1(model):
        tx_dir = []
        for tx in model.CROSS_PROVINCE_TRANS:
            tx_dir.append((model.trans_lz1[tx], model.trans_lz2[tx]))
            tx_dir.append((model.trans_lz2[tx], model.trans_lz1[tx]))
        return tx_dir
   
    def init_DIRECTIONAL_TX2(model):
        tx_dir = []
        for tx in model.CROSS_OTHER_UHVS:
            tx_dir.append((model.trans_lz1[tx], model.trans_lz2[tx]))
            tx_dir.append((model.trans_lz2[tx], model.trans_lz1[tx]))
        return tx_dir
   
    def init_DIRECTIONAL_TX3(model):
        tx_dir = []
        for tx in model.CROSS_REGIONAL_TRANS:
            tx_dir.append((model.trans_lz1[tx], model.trans_lz2[tx]))
            tx_dir.append((model.trans_lz2[tx], model.trans_lz1[tx]))
        return tx_dir
   
    mod.DIRECTIONAL_TX1 = Set(dimen=2, initialize=init_DIRECTIONAL_TX1)
    mod.DIRECTIONAL_TX2 = Set(dimen=2, initialize=init_DIRECTIONAL_TX2)
    mod.DIRECTIONAL_TX3 = Set(dimen=2, initialize=init_DIRECTIONAL_TX3)

    mod.TX_CONNECTIONS_TO_ZONE1 = Set(
        mod.LOAD_ZONES,
        dimen=1,
        initialize=lambda m, lz: [
            z for z in m.LOAD_ZONES if (z, lz) in m.DIRECTIONAL_TX1
        ],
    )

    mod.TX_CONNECTIONS_TO_ZONE2 = Set(
        mod.LOAD_ZONES,
        dimen=1,
        initialize=lambda m, lz: [
            z for z in m.LOAD_ZONES if (z, lz) in m.DIRECTIONAL_TX2
        ],
    )

    mod.TX_CONNECTIONS_TO_ZONE3 = Set(
        mod.LOAD_ZONES,
        dimen=1,
        initialize=lambda m, lz: [
            z for z in m.LOAD_ZONES if (z, lz) in m.DIRECTIONAL_TX3
        ],
    )


    def init_trans_d_line1(m, zone_from, zone_to):
        for tx in m.CROSS_PROVINCE_TRANS:
            if (m.trans_lz1[tx] == zone_from and m.trans_lz2[tx] == zone_to) or (
                m.trans_lz2[tx] == zone_from and m.trans_lz1[tx] == zone_to
            ):
                return tx

    def init_trans_d_line2(m, zone_from, zone_to):
        for tx in m.CROSS_OTHER_UHVS:
            if (m.trans_lz1[tx] == zone_from and m.trans_lz2[tx] == zone_to) or (
                m.trans_lz2[tx] == zone_from and m.trans_lz1[tx] == zone_to
            ):
                return tx

    def init_trans_d_line3(m, zone_from, zone_to):
        for tx in m.CROSS_REGIONAL_TRANS:
            if (m.trans_lz1[tx] == zone_from and m.trans_lz2[tx] == zone_to) or (
                m.trans_lz2[tx] == zone_from and m.trans_lz1[tx] == zone_to
            ):
                return tx
           
   
    mod.trans_d_line1 = Param(
        mod.DIRECTIONAL_TX1, within=mod.TRANSMISSION_LINES, initialize=init_trans_d_line1
    )

    mod.trans_d_line2 = Param(
        mod.DIRECTIONAL_TX2, within=mod.TRANSMISSION_LINES, initialize=init_trans_d_line2
    )

    mod.trans_d_line3 = Param(
        mod.DIRECTIONAL_TX3, within=mod.TRANSMISSION_LINES, initialize=init_trans_d_line3
    )

You can see that I have divided my transmission lines into 3 groups. In addition, I have contructed the corresponding "TRANS_TIMEPOINTS", "DispatchTx", "TxPowerSent" and "TxPowerReceived" for them. The relevant codes are as follows:
mod.TRANS_TIMEPOINTS1 = Set(
        dimen=3, initialize=lambda m: m.DIRECTIONAL_TX1 * m.TIMEPOINTS
    )
 
mod.TRANS_TIMEPOINTS2 = Set(
        dimen=3, initialize=lambda m: m.DIRECTIONAL_TX2 * m.TIMEPOINTS
    )

mod.TRANS_TIMEPOINTS3 = Set(
        dimen=3, initialize=lambda m: m.DIRECTIONAL_TX3 * m.TIMEPOINTS
    )

mod.DispatchTx1 = Var(mod.TRANS_TIMEPOINTS1, within=NonNegativeReals)

mod.DispatchTx2 = Var(mod.TRANS_TIMEPOINTS2, within=NonNegativeReals)

mod.DispatchTx3 = Var(mod.TRANS_TIMEPOINTS3, within=NonNegativeReals)

mod.TxPowerSent1 = Expression(
        mod.TRANS_TIMEPOINTS1,
        rule=lambda m, zone_from, zone_to, tp: (m.DispatchTx1[zone_from, zone_to, tp]),
    )
mod.TxPowerReceived1 = Expression(
        mod.TRANS_TIMEPOINTS1,
        rule=lambda m, zone_from, zone_to, tp: (
            m.DispatchTx1[zone_from, zone_to, tp]
            * m.trans_efficiency[m.trans_d_line1[zone_from, zone_to]]
        ),
    )

mod.TxPowerSent2 = Expression(
        mod.TRANS_TIMEPOINTS2,
        rule=lambda m, zone_from, zone_to, tp: (m.DispatchTx2[zone_from, zone_to, tp]),
    )
mod.TxPowerReceived2 = Expression(
        mod.TRANS_TIMEPOINTS2,
        rule=lambda m, zone_from, zone_to, tp: (
            m.DispatchTx2[zone_from, zone_to, tp]
            * m.trans_efficiency[m.trans_d_line2[zone_from, zone_to]]
        ),
    )

mod.TxPowerSent3 = Expression(
        mod.TRANS_TIMEPOINTS3,
        rule=lambda m, zone_from, zone_to, tp: (m.DispatchTx3[zone_from, zone_to, tp]),
    )
mod.TxPowerReceived3 = Expression(
        mod.TRANS_TIMEPOINTS3,
        rule=lambda m, zone_from, zone_to, tp: (
            m.DispatchTx3[zone_from, zone_to, tp]
            * m.trans_efficiency[m.trans_d_line3[zone_from, zone_to]]
        ),
    )

Because I have divided my transmission lines into 3 groups, so I should redefine the "TXPowerNet". The following codes are as follows:
def TXPowerNet_calculation(m, z, tp):
        return sum(
            m.TxPowerReceived1[zone_from, z, tp]
            for zone_from in m.TX_CONNECTIONS_TO_ZONE1[z]
        ) + sum(
            m.TxPowerReceived2[zone_from, z, tp]
            for zone_from in m.TX_CONNECTIONS_TO_ZONE2[z]
        ) + sum(
            m.TxPowerReceived3[zone_from, z, tp]
            for zone_from in m.TX_CONNECTIONS_TO_ZONE3[z]
        )  - sum(
            m.TxPowerSent1[z, zone_to, tp] for zone_to in m.TX_CONNECTIONS_TO_ZONE1[z]
        ) - sum(
            m.TxPowerSent2[z, zone_to, tp] for zone_to in m.TX_CONNECTIONS_TO_ZONE2[z]
        ) - sum(
            m.TxPowerSent3[z, zone_to, tp] for zone_to in m.TX_CONNECTIONS_TO_ZONE3[z]
        )
That's all about what I have done in the transmission.transport build and dispatch modules. Fortunately, it has worked successufully, but I wonder if what I have done is correct. I have run the SWITCH model twice, but the first time has used the transmission line data which is not divided, the second time has used the same transmission line data which has been divided into 3 gruops. It stands to reason that since the data about transmission lines is the same, the results obtained by the two model runs should be the same. But the results are different. So I have doubts about whether the changes I made are correct, and please instruct on me. Thank you so so much!
       The second question is about the post-solve process. Because I want to find how much MWh of power moved across all transmission lines during each period. So I want to make the SWITCH output a file about that. So I added the relevant codes about post-solve in the transmission.transport.dispatch module:
def post_solve(instance, outdir):

    import switch_model.reporting as reporting

    def get_row(m, p):
        row = (p,)
        row += (TxGenerationReceived[m, p],)
        return row
   
    reporting.write_table(
        instance,
        instance.Usage_Rate_PERIODS,
        output_file=os.path.join(outdir, "usage_rate_tx.csv"),
        headings=(
            "PERIOD",
            "TxGenerationReceivedMWh",
        ),
        values=get_row,
    )

But When I run the SWITCH, a new error has happened in the post-solve process:
Terminating early due to error:
    TypeError: 'function' object is not subscriptable 
I didn't know what is wrong with my codes, please guide me and I will be very appreciated for that! Thank you so much!
          The last question is about the no.commit module. In the previous e-mail, I have said that I want to constrain the output of baseload generators to with new codes in order to let the output to vary in a range but not maintain a constant and fixed(not changed) value in a whole period. And I added the following codes:
mod.Enforce_Dispatch_Upper_Limit_Baseload = Constraint(
        mod.BASELOAD_GEN_TPS,
        rule=lambda m, g, t: m.DispatchGen[g, t] == 0 or (m.DispatchUpperLimit[g, t] * 0.4 <= m.DispatchGen[g, t] <= m.DispatchUpperLimit[g, t]),
    )

And you have instructed me that the SWITCH uses the Pyomo framework, which only allows linear programs or mixed-integer linear programs, so “or” operator in my constraint can not work. In the meanwhile, you have talked to me that what I want is very similar to what the unit commitment module provides about  "gen_min_load_fraction". So I have followed your guidance and taken a preliminary study at switch_model.generators.core.commit.operate module. I have found that the switch_model.generators.core.commit.operate module had some constraints for a lot of indicators about the power generators, such min and max commitment, min load fraction, stratup and shut down capacity and so on. Well, after my preliminary study of the switch_model.generators.core.commit.operate module, I found that the relevant constriant about the minimum load of the generators is as follows:
mod.DispatchLowerLimit = Expression(
        mod.GEN_TPS,
        rule=lambda m, g, t: (m.CommitGen[g, t] * m.gen_min_load_fraction_TP[g, t]),
    )

    def DispatchUpperLimit_expr(m, g, t):
        if g in m.VARIABLE_GENS:
            return m.CommitGen[g, t] * m.gen_max_capacity_factor[g, t]
        else:
            return m.CommitGen[g, t]

    mod.DispatchUpperLimit = Expression(mod.GEN_TPS, rule=DispatchUpperLimit_expr)

    mod.Enforce_Dispatch_Lower_Limit = Constraint(
        mod.GEN_TPS,
        rule=lambda m, g, t: (m.DispatchLowerLimit[g, t] <= m.DispatchGen[g, t]),
    )
    mod.Enforce_Dispatch_Upper_Limit = Constraint(
        mod.GEN_TPS,
        rule=lambda m, g, t: (m.DispatchGen[g, t] <= m.DispatchUpperLimit[g, t]),
    )
So you can see that the DispatchGen is constrained between DispatchLowerLimit and DispatchUpperLimit, and the DispatchLowerLimit is not 0 but the minimum fraction of the generators available capacity. I would like to ask if the  DispatchGen can still be 0 with these 2 constraints? If not, does it mean that all built generators must all be online at no less than the minimum load? In other words, under these 2 constraints, can the model still endogenously decide that certain generators cannot be online thus making their dispacthgen equal to 0? I would have thought that this kind of constraints would mean that all built generators would have to be online and colud not fall below the minimum load of their cumulative capacity. I'm not sure if I'm on the right track, so I'll need you to guide me accordingly.
         That's all about my questions and problems. Really sorry that I have asked you so much questions in this letter. I know this will take you so much time, but I really want to know that reason and guidance about my questions. Really really thank you so much for your guidance and instructions! Really hope that you could have a nice day every day! Good luck! I am looking forward to your reply!
Yours sincerely, 
Wang Chen
Reply all
Reply to author
Forward
0 new messages