Skip to content

Commit afc4c2d

Browse files
authored
Merge pull request #140 from octue/reactive-power-derating
Reactive power derating
2 parents e7e59a1 + 8a36805 commit afc4c2d

File tree

4 files changed

+144
-1
lines changed

4 files changed

+144
-1
lines changed

power-curve-schema/examples/generic-274-20.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,24 @@
7070
"air_density": 0.9,
7171
"temperature": [40, 41, 42, 43, 44, 45],
7272
"power_limit": [2e6, 1.98e6, 1.88e6, 2.0e6, 1.7e6, 1.1e6]
73+
},
74+
{
75+
"legend": "High temperature derating, reactive power 0 VAr",
76+
"reactive_power": 0,
77+
"temperature": [35, 36, 37, 38, 39, 40],
78+
"power_limit": [20e6, 18.8e6, 17.5e6, 16.5e6, 15.3e6, 14e6]
79+
},
80+
{
81+
"legend": "High temperature derating, reactive power 1.8 MVAr",
82+
"reactive_power": 1.8e6,
83+
"temperature": [30, 32, 34, 36, 38, 40],
84+
"power_limit": [20e6, 18e6, 16e6, 14.5e6, 13.3e6, 12.3e6]
85+
},
86+
{
87+
"legend": "High temperature derating, reactive power 2.7 MVAr",
88+
"reactive_power": 2.7e6,
89+
"temperature": [30, 32, 34, 36, 38, 40, 41],
90+
"power_limit": [20e6, 16.5e6, 14e6, 12.5e6, 11.2e6, 10.2e6, 0]
7391
}
7492
],
7593
"cold": {

power-curve-schema/schema.json

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@
202202
},
203203
"derating": {
204204
"title": "Derating",
205-
"description": "Define any number of thermal derating curves to limit power performance in extreme temperature ranges. Curves can be based on either altitude or air density.",
205+
"description": "Define any number of thermal derating curves to limit power performance in extreme temperature ranges. Curves can be based on altitude, air density, or reactive power output.",
206206
"type": "array",
207207
"minItems": 1,
208208
"items": {
@@ -296,6 +296,38 @@
296296
}
297297
}
298298
}
299+
},
300+
{
301+
"title": "Reactive power and temperature based derating",
302+
"required": ["reactive_power", "power_limit", "temperature"],
303+
"additionalProperties": false,
304+
"properties": {
305+
"legend": {
306+
"title": "Legend",
307+
"description": "A note related to the applicability or extra context describing the derating",
308+
"type": "string",
309+
"maxLength": 80
310+
},
311+
"reactive_power": {
312+
"description": "The absolute value of reactive power at which this derating curve applies [VAr]",
313+
"type": "number",
314+
"minimum": 0
315+
},
316+
"temperature": {
317+
"description": "A list of atmospheric temperature values [degrees C]",
318+
"type": "array",
319+
"items": {
320+
"type": "number"
321+
}
322+
},
323+
"power_limit": {
324+
"description": "A list of power limits each corresponding to an atmospheric temperature value in the previous list [W]",
325+
"type": "array",
326+
"items": {
327+
"type": "number"
328+
}
329+
}
330+
}
299331
}
300332
]
301333
}

test/fixtures/generic-274-20-alpha-3.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,24 @@
6969
"air_density": 0.9,
7070
"temperature": [40, 41, 42, 43, 44, 45],
7171
"power_limit": [2e6, 1.98e6, 1.88e6, 2.0e6, 1.7e6, 1.1e6]
72+
},
73+
{
74+
"legend": "High temperature derating, reactive power 0 VAr",
75+
"reactive_power": 0,
76+
"temperature": [35, 36, 37, 38, 39, 40],
77+
"power_limit": [20e6, 18.8e6, 17.5e6, 16.5e6, 15.3e6, 14e6]
78+
},
79+
{
80+
"legend": "High temperature derating, reactive power 1.8 MVAr",
81+
"reactive_power": 1.8e6,
82+
"temperature": [30, 32, 34, 36, 38, 40],
83+
"power_limit": [20e6, 18e6, 16e6, 14.5e6, 13.3e6, 12.3e6]
84+
},
85+
{
86+
"legend": "High temperature derating, reactive power 2.7 MVAr",
87+
"reactive_power": 2.7e6,
88+
"temperature": [30, 32, 34, 36, 38, 40, 41],
89+
"power_limit": [20e6, 16.5e6, 14e6, 12.5e6, 11.2e6, 10.2e6, 0]
7290
}
7391
],
7492
"cold": {

test/test_turbine.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,3 +166,78 @@ def test_invalid_regulation_type(subschema, generic_turbine):
166166
with pytest.raises(ValidationError) as e:
167167
validate(instance=generic_turbine, schema=subschema)
168168
assert "'aeroflap' is not one of ['pitch', 'stall', 'other']" in str(e)
169+
170+
171+
def test_reactive_power_derating(subschema, generic_turbine):
172+
"""Validation should pass for reactive power based thermal derating"""
173+
generic_turbine["turbine"]["thermal_regulation"] = {
174+
"derating": [
175+
{
176+
"legend": "High temperature derating, reactive power 0 VAr",
177+
"reactive_power": 0,
178+
"temperature": [35, 36, 37, 38, 39, 40],
179+
"power_limit": [20e6, 18.8e6, 17.5e6, 16.5e6, 15.3e6, 14e6],
180+
}
181+
]
182+
}
183+
validate(instance=generic_turbine, schema=subschema)
184+
185+
186+
def test_reactive_power_derating_with_multiple_curves(subschema, generic_turbine):
187+
"""Validation should pass for multiple reactive power based thermal derating curves"""
188+
generic_turbine["turbine"]["thermal_regulation"] = {
189+
"derating": [
190+
{
191+
"legend": "High temperature derating, reactive power 0 VAr",
192+
"reactive_power": 0,
193+
"temperature": [35, 40],
194+
"power_limit": [20e6, 14e6],
195+
},
196+
{
197+
"legend": "High temperature derating, reactive power 6.5 MVAr",
198+
"reactive_power": 6.5e6,
199+
"temperature": [30, 40],
200+
"power_limit": [20e6, 12.3e6],
201+
},
202+
{
203+
"legend": "High temperature derating, reactive power 9.5 MVAr",
204+
"reactive_power": 9.5e6,
205+
"temperature": [30, 40, 41],
206+
"power_limit": [20e6, 10.2e6, 0],
207+
},
208+
]
209+
}
210+
validate(instance=generic_turbine, schema=subschema)
211+
212+
213+
def test_reactive_power_derating_negative_value_invalid(subschema, generic_turbine):
214+
"""Validation should fail for negative reactive power values"""
215+
generic_turbine["turbine"]["thermal_regulation"] = {
216+
"derating": [
217+
{
218+
"reactive_power": -1000,
219+
"temperature": [35, 40],
220+
"power_limit": [20e6, 14e6],
221+
}
222+
]
223+
}
224+
with pytest.raises(ValidationError) as e:
225+
validate(instance=generic_turbine, schema=subschema)
226+
assert "minimum" in str(e)
227+
228+
229+
def test_reactive_power_derating_missing_required_field(subschema, generic_turbine):
230+
"""Validation should fail if reactive_power derating is missing temperature or power_limit"""
231+
generic_turbine["turbine"]["thermal_regulation"] = {
232+
"derating": [
233+
{
234+
"reactive_power": 0,
235+
"temperature": [35, 40],
236+
# missing power_limit
237+
}
238+
]
239+
}
240+
with pytest.raises(ValidationError) as e:
241+
validate(instance=generic_turbine, schema=subschema)
242+
# Since derating uses anyOf, the error message indicates it doesn't match any schema
243+
assert "is not valid under any of the given schemas" in str(e)

0 commit comments

Comments
 (0)