Skip to content

Commit 9885c44

Browse files
Add initial coal plant model (#226)
* Add initial coal plant model * Update docs and example * Add tests * Update controller to scale with total simulation time * Update hercules runscript doc strings * Example number update * Update documentation
1 parent 0ca13da commit 9885c44

8 files changed

Lines changed: 652 additions & 0 deletions

File tree

docs/hard_coal_steam_turbine.md

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
# Hard Coal Steam Turbine
2+
3+
The `HardCoalSteamTurbine` (HCST) class models a hard coal power production plant using steam turbines. This class is a subclass of {doc}`ThermalComponentBase <thermal_component_base>` and inherits all state machine behavior, ramp constraints, and operational logic from the base class.
4+
5+
Set `component_type: HardCoalSteamTurbine` in the component's YAML section. The section key is a user-chosen `component_name` (e.g. `hard_coal_steam_turbine`); see [Component Names, Types, and Categories](component_types.md) for details.
6+
7+
For details on the state machine, startup/shutdown behavior, and base parameters, see {doc}`thermal_component_base`.
8+
9+
## HCST-Specific Parameters
10+
11+
The HCST class provides default values for bituminous coal properties from [4]:
12+
13+
| Parameter | Units | Default | Description |
14+
|-----------|-------|---------|-------------|
15+
| `hhv` | J/m³ | 29310000000 | Higher heating value of bituminous coal (29.31 MJ/kg) [4] |
16+
| `fuel_density` | kg/m³ | 1000 | Fuel density for mass calculations |
17+
18+
The `efficiency_table` parameter is **optional**. If not provided, default values based on approximate readings from the [2] are used. All efficiency values are **HHV (Higher Heating Value) net plant efficiencies**. See {doc}`thermal_component_base` for details on the efficiency table format.
19+
20+
## Default Parameter Values
21+
22+
The `HardCoalSteamTurbine` class provides default values for base class parameters based on References [1-4]. Only `rated_capacity` and `initial_conditions` are required in the YAML configuration.
23+
24+
| Parameter | Default Value | Source |
25+
|-----------|---------------|--------|
26+
| `min_stable_load_fraction` | 0.30 (30%) | [2] |
27+
| `ramp_rate_fraction` | 0.03 (3%/min) | [1] |
28+
| `run_up_rate_fraction` | Same as `ramp_rate_fraction` ||
29+
| `hot_startup_time` | 7.5 hours | [1] |
30+
| `warm_startup_time` | 7.5 hours | [1] |
31+
| `cold_startup_time` | 7.5 hours | [1] |
32+
| `min_up_time` | 48 hours | [2] |
33+
| `min_down_time` | 48 hours | [2] |
34+
| `efficiency_table` | Average plant efficiency | [2,3] |
35+
36+
### Default Efficiency Table
37+
38+
The default HHV net plant efficiency table is based on [2,3]:
39+
40+
| Power Fraction | HHV Net Efficiency |
41+
|---------------|-------------------|
42+
| 1.00 | 0.35 (35%) |
43+
| 0.5o | 0.32 (32%) |
44+
| 0.30 | 0.30 (30%) |
45+
46+
## HCST Outputs
47+
48+
The HCST model provides the following outputs (inherited from base class):
49+
50+
| Output | Units | Description |
51+
|--------|-------|-------------|
52+
| `power` | kW | Actual power output |
53+
| `state` | integer | Operating state number (0-5), corresponding to the `STATES` enum |
54+
| `efficiency` | fraction (0-1) | Current HHV net plant efficiency |
55+
| `fuel_volume_rate` | m³/s | Fuel volume flow rate |
56+
| `fuel_mass_rate` | kg/s | Fuel mass flow rate (computed using `fuel_density` |
57+
58+
### Efficiency and Fuel Rate
59+
60+
HHV net plant efficiency varies with load based on the `efficiency_table`. The fuel volume rate is calculated as:
61+
62+
$$
63+
\text{fuel\_volume\_rate} = \frac{\text{power}}{\text{efficiency} \times \text{hhv}}
64+
$$
65+
66+
Where:
67+
- `power` is in W (converted from kW internally)
68+
- `efficiency` is the HHV net efficiency interpolated from the efficiency table
69+
- `hhv` is the higher heating value in J/m³
70+
- Result is fuel volume rate in m³/s
71+
72+
The fuel mass rate is then computed from the volume rate using the fuel density:
73+
74+
$$
75+
\text{fuel\_mass\_rate} = \text{fuel\_volume\_rate} \times \text{fuel\_density}
76+
$$
77+
78+
Where:
79+
- `fuel_volume_rate` is in m³/s
80+
- `fuel_density` is in kg/m³
81+
- Result is fuel mass rate in kg/s
82+
83+
## YAML Configuration
84+
85+
### Minimal Configuration
86+
87+
Required parameters only (uses defaults for `hhv`, `efficiency_table`, and other parameters):
88+
89+
```yaml
90+
hard_coal_steam_turbine:
91+
component_type: HardCoalSteamTurbine
92+
rated_capacity: 100000 # kW (100 MW)
93+
initial_conditions:
94+
power: 0 # 0 kW means OFF; power > 0 means ON
95+
```
96+
97+
### Full Configuration
98+
99+
All parameters explicitly specified:
100+
101+
```yaml
102+
hard_coal_steam_turbine:
103+
component_type: HardCoalSteamTurbine
104+
rated_capacity: 500000 # kW (500 MW)
105+
min_stable_load_fraction: 0.3 # 30% minimum operating point
106+
ramp_rate_fraction: 0.03 # 3%/min ramp rate
107+
run_up_rate_fraction: 0.02 # 2%/min run up rate
108+
hot_startup_time: 27000.0 # 7.5 hours
109+
warm_startup_time: 27000.0 # 7.5 hours
110+
cold_startup_time: 27000.0 # 7.5 hours
111+
min_up_time: 172800 # 48 hours
112+
min_down_time: 172800 # 48 hour
113+
hhv: 29310000000 # J/m³ for bituminous coal (29.31 MJ/m³) [4]
114+
fuel_density: 1000 # kg/m³ for bituminous coal
115+
efficiency_table:
116+
power_fraction:
117+
- 1.0
118+
- 0.50
119+
- 0.30
120+
efficiency: # HHV net plant efficiency, fractions (0-1)
121+
- 0.35
122+
- 0.32
123+
- 0.32
124+
log_channels:
125+
- power
126+
- fuel_volume_rate
127+
- fuel_mass_rate
128+
- state
129+
- efficiency
130+
- power_setpoint
131+
initial_conditions:
132+
power: 0 # 0 kW means OFF; power > 0 means ON
133+
```
134+
135+
## Logging Configuration
136+
137+
The `log_channels` parameter controls which outputs are written to the HDF5 output file.
138+
139+
**Available Channels:**
140+
- `power`: Actual power output in kW (always logged)
141+
- `state`: Operating state number (0-5), corresponding to the `STATES` enum
142+
- `fuel_volume_rate`: Fuel volume flow rate in m³/s
143+
- `fuel_mass_rate`: Fuel mass flow rate in kg/s
144+
- `efficiency`: Current HHV net plant efficiency (0-1)
145+
- `power_setpoint`: Requested power setpoint in kW
146+
147+
## References
148+
149+
1. Agora Energiewende (2017): "Flexibility in thermal power plants - With a focus on existing coal-fired power plants."
150+
151+
2. IRENA (2019), Innovation landscape brief: Flexibility in conventional power plants, International Renewable Energy Agency, Abu Dhabi.
152+
153+
3. T. Schmitt, S. Leptinsky, M. Turner, A. Zoelle, C. White, S. Hughes, S. Homsy, et al. “Cost And Performance Baseline for Fossil Energy Plants Volume 1: Bituminous Coal and Natural Gas Electricity.” Pittsburgh, PA: National Energy Technology Laboratory, October 14, 2022b. https://doi.org/10.2172/1893822.
154+
155+
4. I. Staffell, "The Energy and Fuel Data Sheet," University of Birmingham, March 2011. https://claverton-energy.com/cms4/wp-content/uploads/2012/08/the_energy_and_fuel_data_sheet.pdf
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Input YAML for hercules
2+
# Explicitly specify the parameters for demonstration purposes
3+
4+
# Name
5+
name: example_08
6+
7+
###
8+
# Describe this simulation setup
9+
description: Hard Coal Steam Turbine (HCST) Example
10+
11+
dt: 60.0 # 1 minute time step
12+
starttime_utc: "2020-01-01T00:00:00Z" # Midnight Jan 1, 2020 UTC
13+
endtime_utc: "2020-01-10T00:00:00Z" # 10 days later
14+
verbose: False
15+
log_every_n: 1
16+
17+
plant:
18+
interconnect_limit: 500000 # kW (500 MW)
19+
20+
hard_coal_steam_turbine:
21+
component_type: HardCoalSteamTurbine
22+
rated_capacity: 500000 # kW (500 MW)
23+
min_stable_load_fraction: 0.3 # 30% minimum operating point
24+
ramp_rate_fraction: 0.03 # 3%/min ramp rate
25+
run_up_rate_fraction: 0.02 # 2%/min run up rate
26+
hot_startup_time: 27000.0 # 7.5 hours
27+
warm_startup_time: 27000.0 # 7.5 hours
28+
cold_startup_time: 27000.0 # 7.5 hours
29+
min_up_time: 172800 # 48 hours
30+
min_down_time: 172800 # 48 hour
31+
efficiency_table:
32+
power_fraction:
33+
- 1.0
34+
- 0.50
35+
- 0.30
36+
efficiency: # HHV net plant efficiency, fractions (0-1)
37+
- 0.35
38+
- 0.32
39+
- 0.32
40+
log_channels:
41+
- power
42+
- fuel_volume_rate
43+
- fuel_mass_rate
44+
- state
45+
- efficiency
46+
- power_setpoint
47+
initial_conditions:
48+
power: 500000 # Start ON at rated capacity
49+
50+
controller:
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
"""Example 08: Hard Coal Steam Turbine (HCST) simulation.
2+
3+
This example demonstrates a simple hard coal steam turbine (HCST) that:
4+
- Starts on at rated capacity
5+
- Receives a shutdown command and begins ramping down
6+
- Transitions to off
7+
- Receives a turn-on command with a setpoint of 100% of rated capacity
8+
- Minimum down-time requirement is reached and the turbine begins ramping up
9+
- Ramps up to 100% of rated capacity
10+
- Receives a command to reduce power to 50% of rated capacity
11+
- Receives a command to reduce power to 10% of rated capacity
12+
(note this is below the minimum stable load)
13+
- Receives a command to increase power to 100% of rated capacity
14+
- Receives a shutdown command
15+
- Simulation runs for 10 days total with 1 minute time steps
16+
"""
17+
18+
from hercules.hercules_model import HerculesModel
19+
from hercules.utilities_examples import prepare_output_directory
20+
21+
prepare_output_directory()
22+
23+
# Initialize the Hercules model
24+
hmodel = HerculesModel("hercules_input.yaml")
25+
26+
27+
class ControllerHCST:
28+
"""Controller implementing the HCST schedule described in the module docstring."""
29+
30+
def __init__(self, h_dict, component_name="hard_coal_steam_turbine"):
31+
"""Initialize the controller.
32+
33+
Args:
34+
h_dict (dict): The hercules input dictionary.
35+
36+
"""
37+
self.component_name = component_name
38+
self.rated_capacity = h_dict[self.component_name]["rated_capacity"]
39+
40+
simulation_length = h_dict["endtime_utc"] - h_dict["starttime_utc"]
41+
self.total_simulation_time = simulation_length.total_seconds()
42+
43+
def step(self, h_dict):
44+
"""Execute one control step.
45+
This controller is scaled by the total simulation time, pulled from the h_dict
46+
This preserves the relative distance between control actions, but changes the
47+
simulation times that they are applied.
48+
49+
Args:
50+
h_dict (dict): The hercules input dictionary.
51+
52+
Returns:
53+
dict: The updated hercules input dictionary.
54+
55+
"""
56+
current_time = h_dict["time"]
57+
58+
# Determine power setpoint based on time
59+
if current_time < 0.05 * self.total_simulation_time:
60+
# First 5% of simulation time, run at full capacity
61+
power_setpoint = self.rated_capacity
62+
elif current_time < 0.15 * self.total_simulation_time:
63+
# Between 5% and 15% of simulation time: shut down
64+
power_setpoint = 0.0
65+
elif current_time < 0.45 * self.total_simulation_time:
66+
# Between 15% and 45% of simulation time: signal to run at full capacity
67+
power_setpoint = self.rated_capacity
68+
elif current_time < 0.65 * self.total_simulation_time:
69+
# Between 45% and 65% of simulation time: reduce power to 50% of rated capacity
70+
power_setpoint = 0.5 * self.rated_capacity
71+
elif current_time < 0.75 * self.total_simulation_time:
72+
# Between 65% and 75% of simulation time: reduce power to 10% of rated capacity
73+
power_setpoint = 0.1 * self.rated_capacity
74+
elif current_time < 0.9 * self.total_simulation_time: #
75+
# Between 75% and 90% of simulation time: increase power to 100% of rated capacity
76+
power_setpoint = self.rated_capacity
77+
else:
78+
# After 90% of simulation time: shut down
79+
power_setpoint = 0.0
80+
81+
h_dict[self.component_name]["power_setpoint"] = power_setpoint
82+
83+
return h_dict
84+
85+
86+
# Instantiate the controller and assign to the Hercules model
87+
hmodel.assign_controller(ControllerHCST(hmodel.h_dict))
88+
89+
# Run the simulation
90+
hmodel.run()
91+
92+
hmodel.logger.info("Process completed successfully")
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# Plot the outputs of the simulation for the OCGT example
2+
3+
import matplotlib.pyplot as plt
4+
from hercules import HerculesOutput
5+
6+
# Read the Hercules output file using HerculesOutput
7+
ho = HerculesOutput("outputs/hercules_output.h5")
8+
component_name = "hard_coal_steam_turbine" # Change to "open_cycle_gas_turbine" if needed
9+
10+
# Print metadata information
11+
print("Simulation Metadata:")
12+
ho.print_metadata()
13+
print()
14+
15+
# Create a shortcut to the dataframe
16+
df = ho.df
17+
18+
# Get the h_dict from metadata
19+
h_dict = ho.h_dict
20+
21+
# Convert time to hours for easier reading
22+
time_hours = df["time"] / 60 / 60
23+
24+
print("TONNES OF COAL USED:", df[component_name + ".fuel_volume_rate"].sum() * h_dict["dt"])
25+
26+
fig, axarr = plt.subplots(4, 1, sharex=True, figsize=(10, 10))
27+
28+
# Plot the power output and setpoint
29+
ax = axarr[0]
30+
ax.plot(time_hours, df[component_name + ".power"] / 1000, label="Power Output", color="b")
31+
ax.plot(
32+
time_hours,
33+
df[component_name + ".power_setpoint"] / 1000,
34+
label="Power Setpoint",
35+
color="r",
36+
linestyle="--",
37+
)
38+
ax.axhline(
39+
h_dict[component_name]["rated_capacity"] / 1000,
40+
color="gray",
41+
linestyle=":",
42+
label="Rated Capacity",
43+
)
44+
ax.axhline(
45+
h_dict[component_name]["min_stable_load_fraction"]
46+
* h_dict[component_name]["rated_capacity"]
47+
/ 1000,
48+
color="gray",
49+
linestyle="--",
50+
label="Minimum Stable Load",
51+
)
52+
ax.set_ylabel("Power [MW]")
53+
ax.set_title("Open Cycle Gas Turbine Power Output")
54+
ax.legend()
55+
ax.grid(True)
56+
57+
# Plot the state
58+
ax = axarr[1]
59+
ax.plot(time_hours, df[component_name + ".state"], label="State", color="k")
60+
ax.set_ylabel("State")
61+
ax.set_yticks([0, 1, 2, 3, 4, 5])
62+
ax.set_yticklabels(["Off", "Hot Starting", "Warm Starting", "Cold Starting", "On", "Stopping"])
63+
ax.set_title(
64+
"Turbine State (0=Off, 1=Hot Starting, 2=Warm Starting, 3=Cold Starting, 4=On, 5=Stopping)"
65+
)
66+
ax.grid(True)
67+
68+
# Plot the efficiency
69+
ax = axarr[2]
70+
ax.plot(
71+
time_hours,
72+
df[component_name + ".efficiency"] * 100,
73+
label="Efficiency",
74+
color="g",
75+
)
76+
ax.set_ylabel("Efficiency [%]")
77+
ax.set_title("Thermal Efficiency")
78+
ax.grid(True)
79+
80+
# Plot the fuel consumption
81+
ax = axarr[3]
82+
ax.plot(
83+
time_hours,
84+
df[component_name + ".fuel_volume_rate"],
85+
label="Fuel Volume Rate",
86+
color="orange",
87+
)
88+
ax.set_ylabel("Fuel [m³/s]")
89+
ax.set_title("Fuel Volume Rate")
90+
ax.grid(True)
91+
92+
ax.set_xlabel("Time [hours]")
93+
94+
plt.tight_layout()
95+
plt.show()

0 commit comments

Comments
 (0)