Add coal plant controller to Hycon#102
Conversation
|
Thanks for working on this @genevievestarke ! Heads up that #100 makes some fairly significant changes to the Hercules interface, so we may need to revamp this somewhat once that is merged. We've been doing some testing with it and I think it's close, so perhaps I'll try to wrap up some last tasks on that today and try to get it on by early next week so that you can (hopefully) move forward unimpeded. Still, the logic for the coal plant controller shouldn't change. I'm happy to talk you through #100 so that you know what's coming. My other thought: is the logic you're implementing specific to coal, or is it likely going to hold for other types of thermal units, too? |
|
I've now merged #100, and it looks like there is a merge conflict on the Hercules interface, which isn't surprising. I can try to resolve that if you'd like @genevievestarke ? |
|
Ok, I've merged in develop! It would be great to get your thoughts on the external signal handling, @misi9170! |
There was a problem hiding this comment.
Hi @genevievestarke , just going through and adding various comments/ideas but I'll work on that externally provided forced outage (I think it's what you already have, maybe we just workshop the name "plant_status" a bit!)
| # self.low_soc = low_soc | ||
| self.bid_curve = bid_curve | ||
| prices, powers = zip(*bid_curve) | ||
| self.bid_interpolator = interp1d(prices, powers, kind="quadratic", fill_value="extrapolate") |
There was a problem hiding this comment.
My understanding is that a bid curve is not a smooth function but something more step-like, for example:
- Willing to produce 200--400 MW for $20/MWh
- Willing to produce 400--500 MW for $25/MWh
So:
- if the plant clears both, it will produce 500MW at whatever the clearing price is
- if it's the marginal generator at the $25/MWh price, it will produce, say, 430 MW at $25/MWh (depending on how much the market needs to meet demand)
- if it's the marginal generator at the $20/MWh price, it will produce, say, 270 MW at $20/MWh (again, depending on demand)
- If the clearing price is below $20, it will produce zero power
At least, in an idealized case, ignoring any "make-whole" bids or hour-to-hour constraints, etc.
But it seems like fitting a quadratic will give it a smooth bid curve?
There was a problem hiding this comment.
One issue here, though, is that is the plant is the marginal generator, we're going to need a SCED signal to know at what level the plant should be operating.
There was a problem hiding this comment.
Presumably we'll just revert this file prior to merger? Fine to leave the print statements in for the time being if you're using them for debugging
| # # Coal plant parameters | ||
| # if self._has_coal_component: | ||
| # self.plant_parameters["coal_plant"] = { | ||
| # "capacity": h_dict["coal_plant"]["rated_capacity"], | ||
| # "min_stable_load": h_dict["coal_plant"]["min_stable_load_fraction"] * | ||
| # h_dict["coal_plant"]["rated_capacity"] | ||
| # } | ||
|
|
There was a problem hiding this comment.
Reminder to remove prior to merger
| # TODO: @Misha, is there a better way to do this with the new interface? | ||
| if "coal_power_reference" in h_dict["external_signals"]: | ||
| for c in h_dict["component_names"]: | ||
| if self.component_types[c] in hercules_thermal_types: | ||
| measurements[c]["power_reference"] = h_dict["external_signals"][ | ||
| "coal_power_reference" | ||
| ] |
There was a problem hiding this comment.
No, there isn't a better way, but I guess we need to determine whether this is necessary or not. Usually, we shouldn't need to provide an "external" power reference---the power setpoint is either determined by the controller itself (say, based on the bid curve) OR the power setpoint is passed down from a higher-level hybrid plant controller. In either case, the power setpoint for the coal component isn't set ahead of time and passed in.
There was a problem hiding this comment.
I'm now realizing we may need something like this to mimic a SCED signal if the plant is the marginal generator
| power_bids = self.bid_interpolator(day_ahead_lmp) | ||
| plant_status = measurements_dict[self.cname]["status_reference"] | ||
|
|
||
| external_power_reference = measurements_dict[self.cname]["power_reference"] / 1e3 |
There was a problem hiding this comment.
This is fine here, but this should be for cases where the power_reference is set by a higher-level hybrid plant controller, not by an external signal (I think). Happy to discuss more on that though
| # # print("Capacity:", self.plant_parameters[self.cname]["capacity"]) | ||
| # print("Min stable load:", self.plant_parameters[self.cname]["min_stable_load"]) | ||
| # print(f"Day-ahead LMP: {day_ahead_lmp}, Power bid from curve: {power_bids}, | ||
| # Plant status: {plant_status}") | ||
| # print(f"Minimum power value based on min stable load: {min_power_value}") | ||
|
|
There was a problem hiding this comment.
Reminder to remove prior to merger
| # NOTE: Current power calculation is in MW!! | ||
| day_ahead_lmp = measurements_dict["DA_LMP"] | ||
| power_bids = self.bid_interpolator(day_ahead_lmp) | ||
| plant_status = measurements_dict[self.cname]["status_reference"] |
There was a problem hiding this comment.
I'll work on options for this---I think we want this to be something that can be set either in external signals as a forced outage, or by a hybrid plant controller that is committing different types of generators
This PR adds a coal controller to Hycon. The controller takes in the day ahead LMP price, a plant status signal (0 if the plant is off, 1 if the plant is on), and a plant power bid curve.
If the plant is on, then the power set point is set according to the bid curve. If the plant is off, the power set point is zero.
To do: