Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Frequently Used Policy Types #14

Closed
Ethan-Russell opened this issue Oct 7, 2022 · 23 comments
Closed

Implement Frequently Used Policy Types #14

Ethan-Russell opened this issue Oct 7, 2022 · 23 comments
Assignees

Comments

@Ethan-Russell
Copy link
Member

No description provided.

@Ethan-Russell Ethan-Russell added this to the Important Features milestone Oct 7, 2022
@sallyrobson
Copy link
Member

sallyrobson commented Feb 6, 2023

Frequently Used Policy Types

  • Clean Electricity Standard (CES) - Load serving entity must purchase a certain amount of clean energy credits. The number of credits for a type of generation depends on it's emissions relative to a benchmark.
  • Renewable Portfolio Standard (RPS) - Load serving entity pays a fine if they do not meet the set quantity of renewable energy. Renewable is defined in the policy but RPSs don't usually include low emission generation like CCS.
  • Production Tax Credit (PTC) - A $/MWh tax incentive for the generation of specific technology or under specific conditions.
  • Investment Tax Credit (ITC) - A tax incentive for capacity built of a specific type or under specific conditions, usually given as a percentage of investment cost.
  • Emissions Cap - Limit on certain type of emissions for a specified locations (usually CO2, CO2e or NOx)
  • Emissions Price - A price per ton of a certain type of emissions (usually CO2 or CO2e)
  • Voluntary Green Power Purchasing Agreements (VGPs) - Similar to a CES or RPS but an agreement instead of a policy. (not exactly a policy so could be represented differently)
  • Technology specific carveouts - A given percentage of load must be served with a given type of generation.
  • Carbon Storage Price/Tax Credit - A $/ton tax incentive for storing carbon, usually applied to CCUS and DAC.

Currently represented as policies which will now be implemented differently

  • Installed Reserve Margins
  • Coal calibration

@sallyrobson sallyrobson self-assigned this Feb 6, 2023
@sallyrobson
Copy link
Member

sallyrobson commented Feb 6, 2023

Clean Energy Standards

E4ST.m representation:

  1. The emission type is identified
  2. A credit is calculated/set for all emitting generation
  3. Credits are mapped to generators of that type in the specified area
  4. Adds a total output constraint (addTOC.m). For 'qty' this means calculating hourly requirement for load (total requirement/8760), checking that the max output from qualifying generators can meet the requirement, then applying TOC with cap as minimum.

E4ST.jl possible representation:

Mod inputs include:

  • area
  • qualifying gens (possibly with the option to specify credit levels here, otherwise will be calculated based on benchmark)
  • benchmark (optional with default to 0)
  • type of emission (optional with default to CO2)
  • % of load required to be clean, ByYear (this may differ within the total region so maybe a way to specify this for other filters)

Within mod:

  1. Create expression for sum of credits
  2. Add to the expression with credit level for each gentype
  3. Calculate cap level
  4. Write JuMP constraint where expression >= cap level

@sallyrobson
Copy link
Member

Note: Ethan has already written a GenerationCap mod which can be applied as an emissions cap. We might want to just use that or create a an actual Policy

@sallyrobson
Copy link
Member

@Ethan-Russell I'm thinking about how we want to store info for things like CESs and RPSs where we will have a cap level for each year. I think it would look clunky to have them all written out as key value pairs in the config file. We could specify a separate CSV but that's also going to mean a lot of CSVs. But I think that could be organized fairly easily so might not be a big deal. Any opinions?

@sallyrobson
Copy link
Member

Production Tax Credit

E4ST.m representation: The given $/MWh value is added to the general 'PTC' variable and then gencost is updated where 'PTC' is subtracted from the VOM and fuel cost (and other variable costs). Here the general 'PTC' doesn't just refer to actual PTC policies but just anything that is subtracted from gencost.

E4ST.jl possible representation:

Mod inputs include:

  • value of PTC ($/MWh)
  • gen filters it applies to (gentype, genfuel, age range, location, etc)
  • start and end year
  • fade out (optional) - can give specific years and their levels
  • maybe conditions? (I don't know if we can do this in a linear way but I'm thinking about the IRA PTC which fades out when a certain emissions criteria is met. Something like this could be written as an iteration modification)

Within Mod:

  1. get set of gen_idx based on filters
  2. get set of year_idx based on years
  3. create PerMWhGen obj expression that subtracts PTC value * egen for those idxs and then add to the objective function

@Ethan-Russell
Copy link
Member Author

Ethan-Russell commented Feb 7, 2023

Some thoughts on policy implementations, hopefully also addressing your comment above.

Policy type that takes in a table of policies.

That could look like:

struct CSVPolicy
    file::String
    table::DataFrame
end
function CSVPolicy(;file)
    table = E4ST.load_table(file)
    return CSVPolicy(file, table)
end
E4ST.fieldnames_for_yaml(::Type{::CSVPolicy}) = (:file,)
function E4ST.modify_data!(mod::CSVPolicy, config, data)
    for m in getmods(mod)
        modify_data!(m, config, data)
    end
end

The above is very loose and unfinished. Just an idea we could expand upon.

Policy Shortcuts

The idea here would be to create a fully-featured policy that requires a bunch of inputs, then make several convenience shortcut policy types that basically just use those policies. We could even do that with entire sets.

Inside config YAML:

mods:
  ira:
    type: InflationReductionAct
  rps_il:
    type: IllinoisRPS

Julia implementation:

struct IllinoisRPS <: Policy 
    pol::RPS
    function IllinoisRPS() # This would be the function called when parsing the config file
        pol = RPS(
            gentypes=["wind", "solar"],
            standard = Dict(
                "y2020"=>500000,
                "y2021"=>510000,
                "y2022"=>520000,
                # ...
            ),
        )
        return IllinoisRPS(pol)
    end
end
E4ST.fieldnames_for_yaml(::Type{::IllinoisRPS}) = (,)
function E4ST.modify_data!(mod::IllinoisRPS, config, data)
    modify_data!(mod.pol, config, data)
end



struct InflationReductionAct<: Policy
    pols::Vector{<:Policy}
    function InflationReductionAct()
        pols = [
            # insert policies here
        ]
        return InflationReductionAct(pols)
    end
end

@sallyrobson
Copy link
Member

Investment Tax Credit

E4ST.m representation: ITC value given as a percentage of CAP_COST. Credit level is multiplied by CAP_COST then added to the general 'ITC' and offer price is updated. The general 'ITC' is anything subtracted out from the offer price (the cost to add capacity) not just the policy ITC.

E4ST.jl possible representation:

Mod inputs include:

  • value of ITC (given as % of investment cost)
  • gen filters
  • start and end years
  • fade out (optional)

Within Mod:

  • get gen idxs - use start and end year to filter by year_on of the gen. ITC should be applied for the whole econ life of a gen that was originally built in the qualifying year.
  • (I think this can be indexed by all years because gens are filtered by start year so no gen_idx should exist for years before the start year but worth checking and it wouldn't hurt to not index years before the start of the policy)
  • create PerMWCap (or maybe PerMWStartCap) obj expression where ITC is multiplied by the capacity of the initial investment. Then subtract this from the objective function. It is important that ITC be multiplied by the initial/max capacity because it is a portion of the investment cost which is annualized in E4ST (accrual accounting). This means that cap cost and any discounts on it are paid out over the econ lifetime of the gen instead of as a lump sum in the first year. When doing the results processing for ITC we may want some result that shows the lump sum payment of ITC in a year because that more closely represents the real gov payment at the time (although many ITCs still have a more complicated payment plan, such as spreading it out over 5 years, which we don't attempt to model).

@sallyrobson
Copy link
Member

Emissions Price

E4ST.jl possible representation:

Mod inputs include:

  • gen filters, likely just area but might exclude certain gentypes
  • either single value and start + end years or input file with years and respective prices
  • emis type

Within Mod:

  1. get gen idxs
  2. get year idxs
  3. create expression that multiplied emis rate by price by egen and add to the objective function.

One option would be to create a general GenerationPrc mod like the GenerationCap mod where you specify a price and column name that it is multiplied by which is then multiplied by generation. This just makes things more modular and cleaner.

@sallyrobson
Copy link
Member

sallyrobson commented Mar 1, 2023

Building on the above idea about policy shortcuts and policies from CSVs:

There will be basic policy types (ie. PTC, ITC, CES, etc).

When you write a new actual policy, it may contain multiple of these basic policy types (ie. The inflation reduction act has multiple ITCs and PTCs). All values, filters, and other info required for the basic policy type can be stored in a csv for that actual policy, or even multiple policies but best to not mix too much. The function for the actual policy is to:

  1. read in the info from the file
  2. create individual policies using the basic policy types, getting the info in the file into the basic policy structs
  3. implement them. if the actual policy mod is getting called in setup (aka there is a modify_data! function for the mod) this could mean adding the mods to the config so they get called later to modify the model. if the actual policy mod is called somewhere later if could mean directly calling something like modify_model! for the basic policies.

Policy CSV example (work in progress)

policy name basic pol type filter 1 filter 2 more filters ... y2016 y2017 y2018 y2019 more years ...
example_ptc PTC gentype=>solar country=>narnia 2 3 4 NaN NaN

@Ethan-Russell
Copy link
Member Author

From our discussion today about Emissions Cap:

struct EmissionsCap
    gen_cons::GenerationConstraint
    other
    fields
    function EmissionsCap(other, fields)
        gen_cons = GenerationConstraint(other, fields)
        new(gen_cons, other, fields)
    end
end
fieldnames_for_yaml(::EmissionsCap) = (:other, :fields)

"""
    modify_model!(pol::EmissionsCap, config, data, model) -> 


"""
function modify_model!(pol::EmissionsCap, config, data, model)
    modify_model!(pol.gen_cons, config, data, model)
end
export modify_model!

@sallyrobson
Copy link
Member

Questions about RPSs?

  1. Does the standard apply to a certain percentage of the load demanded or to a percentage of the total generation.

@sallyrobson
Copy link
Member

sallyrobson commented Mar 20, 2023

Generation/Portfolio Standards (RPS/CES)

Matlab Representation:

(done in applyPS.m)
Inputs:

  • ps_val (I think standard level that needs to be met, given as a percent, qty or price)
  • ps_type (percent, qty, qty_max, price)
  • credit_type (rps, ces, custom)
  • mapGen - generators that can be used to fulfill
  • mapLoad - load that the standard is applied to
  • name
  • eopt - options, not sure what gets included here
  1. credit gets set for each generation type (not filtered by geographically qualifying gens)
  2. map generators whose generation will included
  3. apply credit to only included gens
  4. calculate coefficient that will be multiplied by generation in total output constraint
  5. calculate cap (max value for the constraint)
  6. add total output constraint with general formulation: sum(generator output * coeff) [< >] K

E4ST.jl representation

For now we will only define it as a percent of demanded load (although we could define it as a percentage of served load, but this complicates formulation). For the quantity definition, we can use the GenerationConstraint type.

Thinking of having a GenerationStandard or PortfolioStandard type which is created by the RPS/CES type

Inputs for GenerationStandard:

  • name
  • standard level/value, given by year
  • map of generators (gen_filters)
  • map of load (load_filters, load_bus_filters)
  • (not an input) the gen table would already have a column with the credit level of each gen, calculated in set up of RPS/CES

Basic idea:

  1. For the load region and for each year, calculate the total load and apply the standard value.
  2. Add column to gen table that is credit level for the policy based on gentype and whether the gen qualifies
  3. Set a constraint that sum(egen*credit) >= (load*standard level) for each year.

Simpler idea:
Use GenerationConstraint where the standard level*load is the min. Total load would be calculated in the CES and RPS setup and the column to multiply by would be the credit level also calculated in setup. However, since we want to apply the PS to more than just the target load, we can't just use the GenerationConstrain because we need to include eserv as a variable in the constraint.

Overlap challenge:

Policies from different regions sometimes overlap in what generators qualify to supply the clean energy they need. We don't want to double count these generators for both policies. We need a way to indicate that only a portion of generation from an overlapped gen is going to each policy. (We, for the most part, define RPS regions so they don't overlap. This is most relevant to VGPs where some of the generation can come from anywhere in the nation.)

There is an exception: Overlapping RPSs and CESs can have generators count for both (ie. generators in an RPS can get clean energy credits in a CES).
(TODO: rework this example to be more general) If there is an RPS and a CES in the same place, generation can count for both of those. Meaning that if there is a 90% CES and 70% RPS you could only need to meet the more binding policy and not somehow have over 100% clean energy. And if these policies overlap with a policy from another region then we would only want to count one of the policies with the outside policy.

Ideas:

  • System PS Wrapper: At some point in the code, have a function/mod that reconciles all of the portfolio standards. One way to reconcile them would be to look at how many PSs apply to that gen and then divide the credit rate by the total number of PSs. This would have to come after all other PS mods (and possibly other mods). Would want to apply it before any constraints are created because the credit value wouldn't get updated.
  • When any PS is calculating credits, it check how many other PSs already apply and divides the credits accordingly. If there are more than 2 at that point, it would assume that the credit has previously been divided and would calculate the new credit accordingly. This would all be done in setup data, before constraints are written.
  • To be able to reconcile this, the code needs to have access to the location filters of the policy so it needs to be able to see the mod. It also needs to be able to modify the credit level. And it has to be before the constraints are written.

Other notes:

  • We could (and possibly should) used energy served (expression) instead of demanded load (input). This would complicate the constraint but is more accurate to reality (I think). We would need to make sure that any modifications to eserv are made before the PS constraints are written because they wouldn't be updated after the constraint is made. We also wouldn't be able to use GenerationConstraint but would create a very similar function so it isn't much more work.
  • In general, any modifications to credit or other expressions in the constraint need to be done before the constraint is written.

@Ethan-Russell
Copy link
Member Author

Questions about RPSs?

  1. Does the standard apply to a certain percentage of the load demanded or to a percentage of the total generation.

I believe it should be a percentage of the served load in a region (which would be an expression). We could probably get away with using the demanded load.

@sallyrobson
Copy link
Member

Questions about RPSs?

  1. Does the standard apply to a certain percentage of the load demanded or to a percentage of the total generation.

I believe it should be a percentage of the served load in a region (which would be an expression). We could probably get away with using the demanded load.

We should ask @DanShawhan about this. Another option is to do a percentage of energy served (which is closer to total generation). This complicates the constraint because it would include battery charging, DAC, and any other added loads, but is possibly more realistic.

@sallyrobson
Copy link
Member

sallyrobson commented Mar 22, 2023

After talking with Dan about above question:

We should apply the constraint to target load - curtailment + DAC + net battery load + T&D losses. For now this would be eserv but when we figure out battery representation this might change.

Should we include transmission and distribution losses?
Yes, we should apply PS to all load including losses (T&D losses and storage losses).

Could we have the option to apply it to retail sales?
Retail sales = target load + DAC - curtailment
We could likely have an option in to config file for this. If we can't, then we can adjust our inputs as needed.

@DanShawhan
Copy link

DanShawhan commented Mar 23, 2023 via email

@e4st-dev e4st-dev deleted a comment from DanShawhan Mar 24, 2023
@sallyrobson
Copy link
Member

sallyrobson commented Mar 27, 2023

Generation Standards

Current best idea:

  1. In data setup, columns with original credit level for each gen will be set up in the gen table.
  2. As GSs are added to the gen table, also add the names of the policies to an array in data. This tracks them so they can be reconciled later.
  3. After all mods have been applied (any modifications to expressions have been made), apply constraints sum(load * GS level) <= sum(egen * credit). load here means target load - curtailment + DAC + net battery load + T&D losses
  4. Look at which generators receive credits from multiple policies within the list. (check when credit > 0 for multiple columns where names are in the list stored in data). Check whether any of the policies completely overlap (have the same location filters).
  5. When there are partially overlapping GSs that conflict* create super constraints that constrain the total amount of qualifying generation to be >= the total load required by both policies. This ensures that generators won't be double counted.

*Not all policies that overlap will conflict. An RPS and CES can overlap and generators are allowed to count for both. A state carveout and an RPS can overlap without conflicting (the state carveout is almost a sub constraint of the RPS). But two overlapping RPSs will conflict and VGP will conflict with RPSs and CESs.

@e4st-dev e4st-dev deleted a comment from DanShawhan Mar 27, 2023
@sallyrobson
Copy link
Member

Discussion 3/27/23:

  • Some states have a requirement that a certain portion of the qualifying gen has to come from inside the state. This could be set up as two different GSs, one that draws just from the state and one that draws from the larger region.

  • Matlab E4ST currently has super constraints to prevent overlap. So for any regions with overlap, you create an additional constraint to make sure that the total qualified load from both regions is met with generation from the whole (including overlapping area).

  • Things that qualify for a CES also qualify for an RPS. All RPS generation can qualify for a CES. We wouldn't need to apply a super constraint to this situation.

  • Voluntary Green Power Purchasing would be a situation where we need super constraints (because some of the generation can come from anywhere in the country).

@Ethan-Russell
Copy link
Member Author

Ethan-Russell commented Mar 28, 2023

mods:
  illinois_rps:
    type: RPS
    crediting:
      type: CreditByGentype
      gentypes:
        ngccccs: 0.5
        solar: 1.0
        geothermal: 0.9
  california_rps:
    type: GenerationStandard
    crediting:
      type: CreditByBenchmark
      benchark: 0.6
  texas_ces:
    type: GenerationStandard
    crediting:
      type: CrazyTexasCrediting      
  util_rps:
    type: GenerationStandard
    crediting:
      type: StandardRPSCrediting
  • Define abstract Crediting or whatever you want to name it
  • Add that abstract type to reload_types!
  • Make a constructor for Crediting, similar to Modification constructor (using discard_type)
  • Use the above constructor inside the GenerationStandard constructor.
# pseudocode
struct GenerationStandard{C} where {C<:Crediting} # Optional to parameterize
    crediting::C
    other
    fields
end

function GenerationStandard(; crediting, other, fields)
    c = Crediting(crediting)
    return GenerationStandard(c, other, fields)
end

# Crediting interface
get_credits(c::Crediting, gen::DataFrame) -> credits::Vector{Float64}

# OR, probably better
get_credits(c::Crediting, gen_row::DataFrameRow) -> credit::Float64

@sallyrobson
Copy link
Member

sallyrobson commented Mar 28, 2023

  • What should our standard set of technologies included in an RPS be?
  • Should ngccs be included and should it receive a partial credit (this is how it is done in current E4ST)?
  • should it include hydrogen and should hydrogen get a full credit?

@DanShawhan
Copy link

DanShawhan commented Mar 29, 2023 via email

@sallyrobson
Copy link
Member

  • Look into welfare transfers for policies. Is it type specific or policy specific? Is this an input in the config or a feature of the policy type.

@sallyrobson
Copy link
Member

In generating our existing state RPSs, I have come up against the issue of super constraints and overlapping generation regions. The main issue is that we specify different rps regions (midwest, ne, pjm, etc.) that different state RPSs can draw from. In matlab E4ST, we group all of the state policies in that region into one E4ST policy. The state targets are applied to the state load and then the constraint is applied on the total of those targets. To do this, we specify targets for each state.

With the way that generation standards are current written in E4ST.jl, there is only one set of targets for each policy and it is applied to all the load in the filtered region. This means that each state policy is treated as a unique E4ST policy and we can't currently group them like we do in matlab E4ST.

We could try to redesign GenerationStandard so that you could specify multiple sets of targets specific to a location in one policy. This might be useful for other policy representations beyond these state RPSs.

Or we can write the script for creating superconstraints. This is something we wanted to do at some point in the future anyways but thought we didn't need yet. We haven't fully resolved how to deal with policies that only partially overlap but it would be pretty straightforward to write super constraints for policies like this where the entire gen area is the same for the grouped states and the rps regions don't overlap with each other.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants