RunFeemsSim 0.4.4__tar.gz → 0.6.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {runfeemssim-0.4.4 → runfeemssim-0.6.0}/PKG-INFO +1 -1
- {runfeemssim-0.4.4 → runfeemssim-0.6.0}/RunFeemsSim/machinery_calculation.py +46 -2
- {runfeemssim-0.4.4 → runfeemssim-0.6.0}/RunFeemsSim.egg-info/PKG-INFO +1 -1
- {runfeemssim-0.4.4 → runfeemssim-0.6.0}/pyproject.toml +1 -1
- {runfeemssim-0.4.4 → runfeemssim-0.6.0}/tests/test_machinery_calculation.py +121 -3
- {runfeemssim-0.4.4 → runfeemssim-0.6.0}/LICENSE +0 -0
- {runfeemssim-0.4.4 → runfeemssim-0.6.0}/README.md +0 -0
- {runfeemssim-0.4.4 → runfeemssim-0.6.0}/RunFeemsSim/__init__.py +0 -0
- {runfeemssim-0.4.4 → runfeemssim-0.6.0}/RunFeemsSim/pms_basic.py +0 -0
- {runfeemssim-0.4.4 → runfeemssim-0.6.0}/RunFeemsSim.egg-info/SOURCES.txt +0 -0
- {runfeemssim-0.4.4 → runfeemssim-0.6.0}/RunFeemsSim.egg-info/dependency_links.txt +0 -0
- {runfeemssim-0.4.4 → runfeemssim-0.6.0}/RunFeemsSim.egg-info/requires.txt +0 -0
- {runfeemssim-0.4.4 → runfeemssim-0.6.0}/RunFeemsSim.egg-info/top_level.txt +0 -0
- {runfeemssim-0.4.4 → runfeemssim-0.6.0}/setup.cfg +0 -0
- {runfeemssim-0.4.4 → runfeemssim-0.6.0}/tests/test_pms_basic.py +0 -0
|
@@ -298,6 +298,7 @@ class MachineryCalculation:
|
|
|
298
298
|
fuel_specified_by: FuelSpecifiedBy = FuelSpecifiedBy.IMO,
|
|
299
299
|
ignore_power_balance: bool = False,
|
|
300
300
|
fuel_option: Optional[FuelOption] = None,
|
|
301
|
+
steam_demand_kg_per_h: Optional[np.ndarray] = None,
|
|
301
302
|
) -> Union[FEEMSResult, FEEMSResultForMachinerySystem]:
|
|
302
303
|
"""
|
|
303
304
|
Calculate the machinery system output from a time series of the propulsion power and
|
|
@@ -313,6 +314,8 @@ class MachineryCalculation:
|
|
|
313
314
|
ignore_power_balance(bool): If True, the power balance calculation will be ignored.
|
|
314
315
|
fuel_option(FuelOption): The fuel option to be used in the simulation. If None, the
|
|
315
316
|
default fuel option in the system will be used.
|
|
317
|
+
steam_demand_kg_per_h: Steam demand time series (kg/h). Must have the same length as
|
|
318
|
+
propulsion_power. If None and a boiler is attached, demand is treated as zero.
|
|
316
319
|
|
|
317
320
|
Returns:
|
|
318
321
|
The result of the calculation. FEEMSResult or FEEMSResultForMachinerySystem.
|
|
@@ -347,11 +350,23 @@ class MachineryCalculation:
|
|
|
347
350
|
f"The length of the auxiliary power({len(auxiliary_power_kw)}) must be 1"
|
|
348
351
|
f" or the same as the propulsion power ({len(propulsion_power)})"
|
|
349
352
|
)
|
|
353
|
+
n_steps = len(propulsion_power)
|
|
354
|
+
if steam_demand_kg_per_h is not None:
|
|
355
|
+
if len(steam_demand_kg_per_h) != n_steps:
|
|
356
|
+
raise ValueError(
|
|
357
|
+
f"The length of steam_demand_kg_per_h ({len(steam_demand_kg_per_h)}) must match "
|
|
358
|
+
f"propulsion_power ({n_steps})."
|
|
359
|
+
)
|
|
350
360
|
|
|
351
361
|
self._set_input_load_time_interval_from_propulsion_power_time_series(
|
|
352
362
|
propulsion_power_time_series=propulsion_power,
|
|
353
363
|
auxiliary_load_kw=auxiliary_power_kw,
|
|
354
364
|
)
|
|
365
|
+
if self.system_feems.boiler is not None:
|
|
366
|
+
self.system_feems.boiler.steam_out_kg_per_h = (
|
|
367
|
+
steam_demand_kg_per_h if steam_demand_kg_per_h is not None
|
|
368
|
+
else np.zeros(n_steps)
|
|
369
|
+
)
|
|
355
370
|
return self._run_simulation(
|
|
356
371
|
fuel_specified_by=fuel_specified_by,
|
|
357
372
|
ignore_power_balance=ignore_power_balance,
|
|
@@ -368,6 +383,7 @@ class MachineryCalculation:
|
|
|
368
383
|
fuel_specified_by: FuelSpecifiedBy = FuelSpecifiedBy.IMO,
|
|
369
384
|
ignore_power_balance: bool = False,
|
|
370
385
|
fuel_option: Optional[FuelOption] = None,
|
|
386
|
+
steam_demand_kg_per_h: Optional[np.ndarray] = None,
|
|
371
387
|
) -> Union[FEEMSResult, FEEMSResultForMachinerySystem]:
|
|
372
388
|
"""
|
|
373
389
|
Calculate the machinery system output from statistics of the propulsion power.
|
|
@@ -377,6 +393,8 @@ class MachineryCalculation:
|
|
|
377
393
|
ignore_power_balance(bool): If True, the power balance calculation will be ignored.
|
|
378
394
|
fuel_option(FuelOption): The fuel option to be used in the simulation. If None, the
|
|
379
395
|
default fuel option in the system will be used.
|
|
396
|
+
steam_demand_kg_per_h: Steam demand time series (kg/h). Must have the same length as
|
|
397
|
+
the time series. If None and a boiler is attached, demand is treated as zero.
|
|
380
398
|
|
|
381
399
|
Returns:
|
|
382
400
|
The result of the simulation. FEEMSResult or FEEMSResultForMachinery system.
|
|
@@ -398,7 +416,18 @@ class MachineryCalculation:
|
|
|
398
416
|
raise TypeError(
|
|
399
417
|
"time_series must be a TimeSeriesResult or TimeSeriesResultForMultiplePropulsors."
|
|
400
418
|
)
|
|
401
|
-
|
|
419
|
+
n_steps = len(df)
|
|
420
|
+
if steam_demand_kg_per_h is not None:
|
|
421
|
+
if len(steam_demand_kg_per_h) != n_steps:
|
|
422
|
+
raise ValueError(
|
|
423
|
+
f"The length of steam_demand_kg_per_h ({len(steam_demand_kg_per_h)}) must match "
|
|
424
|
+
f"the time series ({n_steps})."
|
|
425
|
+
)
|
|
426
|
+
if self.system_feems.boiler is not None:
|
|
427
|
+
self.system_feems.boiler.steam_out_kg_per_h = (
|
|
428
|
+
steam_demand_kg_per_h if steam_demand_kg_per_h is not None
|
|
429
|
+
else np.zeros(n_steps)
|
|
430
|
+
)
|
|
402
431
|
return self._run_simulation(
|
|
403
432
|
fuel_specified_by=fuel_specified_by,
|
|
404
433
|
ignore_power_balance=ignore_power_balance,
|
|
@@ -414,6 +443,7 @@ class MachineryCalculation:
|
|
|
414
443
|
fuel_specified_by: FuelSpecifiedBy = FuelSpecifiedBy.IMO,
|
|
415
444
|
ignore_power_balance: bool = False,
|
|
416
445
|
fuel_option: Optional[FuelOption] = None,
|
|
446
|
+
steam_demand_kg_per_h: Optional[np.ndarray] = None,
|
|
417
447
|
) -> Union[FEEMSResult, FEEMSResultForMachinerySystem]:
|
|
418
448
|
"""
|
|
419
449
|
Calculate the machinery system output from statistics of the propulsion power.
|
|
@@ -428,19 +458,33 @@ class MachineryCalculation:
|
|
|
428
458
|
ignore_power_balance(bool): If True, the power balance calculation will be ignored.
|
|
429
459
|
fuel_option(FuelOption): The fuel option to be used in the simulation. If None, the
|
|
430
460
|
default fuel option in the system will be used.
|
|
461
|
+
steam_demand_kg_per_h: Steam demand for each mode in kg/h. Must have the same length
|
|
462
|
+
as propulsion_power. If None and a boiler is attached, demand is treated as zero.
|
|
431
463
|
|
|
432
464
|
Returns:
|
|
433
465
|
The result of the simulation. FEEMSResult or FEEMSResultForMachinery system.
|
|
434
466
|
"""
|
|
467
|
+
n_steps = len(propulsion_power)
|
|
435
468
|
if not np.isscalar(auxiliary_power_kw):
|
|
436
469
|
assert (
|
|
437
|
-
|
|
470
|
+
n_steps == len(auxiliary_power_kw) or len(auxiliary_power_kw) == 1
|
|
438
471
|
), "The length of the auxiliary power must be 1 or the same as the propulsion power"
|
|
472
|
+
if steam_demand_kg_per_h is not None:
|
|
473
|
+
if len(steam_demand_kg_per_h) != n_steps:
|
|
474
|
+
raise ValueError(
|
|
475
|
+
f"The length of steam_demand_kg_per_h ({len(steam_demand_kg_per_h)}) must match "
|
|
476
|
+
f"propulsion_power ({n_steps})."
|
|
477
|
+
)
|
|
439
478
|
self._set_input_load_time_interval_from_propulsion_power_time_series(
|
|
440
479
|
propulsion_power_time_series=pd.Series(data=propulsion_power, index=frequency),
|
|
441
480
|
auxiliary_load_kw=auxiliary_power_kw,
|
|
442
481
|
time_is_given_as_interval=True,
|
|
443
482
|
)
|
|
483
|
+
if self.system_feems.boiler is not None:
|
|
484
|
+
self.system_feems.boiler.steam_out_kg_per_h = (
|
|
485
|
+
steam_demand_kg_per_h if steam_demand_kg_per_h is not None
|
|
486
|
+
else np.zeros(n_steps)
|
|
487
|
+
)
|
|
444
488
|
return self._run_simulation(
|
|
445
489
|
fuel_specified_by=fuel_specified_by,
|
|
446
490
|
ignore_power_balance=ignore_power_balance,
|
|
@@ -3,14 +3,20 @@ import random
|
|
|
3
3
|
|
|
4
4
|
import numpy as np
|
|
5
5
|
import pandas as pd
|
|
6
|
-
from feems.components_model.component_electric import
|
|
6
|
+
from feems.components_model.component_electric import (
|
|
7
|
+
COGES,
|
|
8
|
+
ElectricComponent,
|
|
9
|
+
ElectricMachine,
|
|
10
|
+
Genset,
|
|
11
|
+
)
|
|
7
12
|
from feems.components_model.component_mechanical import (
|
|
13
|
+
COGAS,
|
|
8
14
|
EngineMultiFuel,
|
|
9
15
|
FuelCharacteristics,
|
|
10
16
|
)
|
|
11
17
|
from feems.fuel import FuelOrigin, FuelSpecifiedBy, TypeFuel
|
|
12
|
-
from feems.system_model import FuelOption
|
|
13
|
-
from feems.types_for_feems import EngineCycleType, TypeComponent
|
|
18
|
+
from feems.system_model import ElectricPowerSystem, FuelOption
|
|
19
|
+
from feems.types_for_feems import EngineCycleType, TypeComponent, TypePower
|
|
14
20
|
from MachSysS.convert_to_feems import convert_proto_propulsion_system_to_feems
|
|
15
21
|
from MachSysS.gymir_result_pb2 import (
|
|
16
22
|
GymirResult,
|
|
@@ -343,3 +349,115 @@ def test_machinery_calculation_multifuel():
|
|
|
343
349
|
< 1e-2
|
|
344
350
|
)
|
|
345
351
|
|
|
352
|
+
|
|
353
|
+
def _build_coges_electric_system(eff_curve: np.ndarray) -> ElectricPowerSystem:
|
|
354
|
+
"""Build a minimal ElectricPowerSystem with a multi-fuel COGES (LNG + H2) and an other_load."""
|
|
355
|
+
generator = ElectricMachine(
|
|
356
|
+
type_=TypeComponent.GENERATOR,
|
|
357
|
+
name="COGES Gen",
|
|
358
|
+
rated_power=1000,
|
|
359
|
+
rated_speed=3000,
|
|
360
|
+
power_type=TypePower.POWER_SOURCE,
|
|
361
|
+
switchboard_id=1,
|
|
362
|
+
)
|
|
363
|
+
cogas = COGAS(
|
|
364
|
+
name="COGAS",
|
|
365
|
+
rated_power=1000,
|
|
366
|
+
rated_speed=3000,
|
|
367
|
+
eff_curve=eff_curve,
|
|
368
|
+
fuel_type=TypeFuel.NATURAL_GAS,
|
|
369
|
+
fuel_origin=FuelOrigin.FOSSIL,
|
|
370
|
+
multi_fuel_characteristics=[
|
|
371
|
+
FuelCharacteristics(
|
|
372
|
+
main_fuel_type=TypeFuel.NATURAL_GAS,
|
|
373
|
+
main_fuel_origin=FuelOrigin.FOSSIL,
|
|
374
|
+
eff_curve=eff_curve,
|
|
375
|
+
engine_cycle_type=EngineCycleType.BRAYTON,
|
|
376
|
+
),
|
|
377
|
+
FuelCharacteristics(
|
|
378
|
+
main_fuel_type=TypeFuel.HYDROGEN,
|
|
379
|
+
main_fuel_origin=FuelOrigin.RENEWABLE_NON_BIO,
|
|
380
|
+
eff_curve=eff_curve,
|
|
381
|
+
engine_cycle_type=EngineCycleType.BRAYTON,
|
|
382
|
+
),
|
|
383
|
+
],
|
|
384
|
+
)
|
|
385
|
+
coges_component = COGES(name="COGES unit", cogas=cogas, generator=generator)
|
|
386
|
+
|
|
387
|
+
other_load = ElectricComponent(
|
|
388
|
+
type_=TypeComponent.OTHER_LOAD,
|
|
389
|
+
name="Aux load",
|
|
390
|
+
rated_power=500,
|
|
391
|
+
power_type=TypePower.POWER_CONSUMER,
|
|
392
|
+
switchboard_id=1,
|
|
393
|
+
eff_curve=np.array([[0.0, 1.0], [1.0, 1.0]]),
|
|
394
|
+
)
|
|
395
|
+
return ElectricPowerSystem(
|
|
396
|
+
name="COGES test system",
|
|
397
|
+
power_plant_components=[coges_component, other_load],
|
|
398
|
+
bus_tie_connections=[],
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def test_machinery_calculation_multifuel_coges():
|
|
403
|
+
eff_curve = np.array([[0.0, 0.30], [0.5, 0.40], [1.0, 0.44]])
|
|
404
|
+
system = _build_coges_electric_system(eff_curve)
|
|
405
|
+
|
|
406
|
+
machinery_calculation = MachineryCalculation(feems_system=system)
|
|
407
|
+
|
|
408
|
+
run_kwargs = dict(
|
|
409
|
+
propulsion_power=np.array([0.0]),
|
|
410
|
+
frequency=np.array([3600.0]),
|
|
411
|
+
auxiliary_power_kw=500.0,
|
|
412
|
+
fuel_specified_by=FuelSpecifiedBy.IMO,
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
# Default (no fuel_option) → first mode = LNG
|
|
416
|
+
res_default = machinery_calculation.calculate_machinery_system_output_from_statistics(
|
|
417
|
+
**run_kwargs
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
# Force H2
|
|
421
|
+
res_h2 = machinery_calculation.calculate_machinery_system_output_from_statistics(
|
|
422
|
+
**run_kwargs,
|
|
423
|
+
fuel_option=FuelOption(
|
|
424
|
+
fuel_type=TypeFuel.HYDROGEN,
|
|
425
|
+
fuel_origin=FuelOrigin.RENEWABLE_NON_BIO,
|
|
426
|
+
for_pilot=False,
|
|
427
|
+
primary=False,
|
|
428
|
+
),
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
# Force LNG explicitly
|
|
432
|
+
res_lng = machinery_calculation.calculate_machinery_system_output_from_statistics(
|
|
433
|
+
**run_kwargs,
|
|
434
|
+
fuel_option=FuelOption(
|
|
435
|
+
fuel_type=TypeFuel.NATURAL_GAS,
|
|
436
|
+
fuel_origin=FuelOrigin.FOSSIL,
|
|
437
|
+
for_pilot=False,
|
|
438
|
+
primary=True,
|
|
439
|
+
),
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
fuels_default = res_default.multi_fuel_consumption_total_kg.fuels
|
|
443
|
+
fuels_h2 = res_h2.multi_fuel_consumption_total_kg.fuels
|
|
444
|
+
|
|
445
|
+
# Default = LNG: natural_gas consumed, no hydrogen
|
|
446
|
+
assert any(f.fuel_type == TypeFuel.NATURAL_GAS and f.mass_or_mass_fraction > 0
|
|
447
|
+
for f in fuels_default), "Default mode should consume natural gas"
|
|
448
|
+
assert not any(f.fuel_type == TypeFuel.HYDROGEN and f.mass_or_mass_fraction > 0
|
|
449
|
+
for f in fuels_default), "Default mode should not consume hydrogen"
|
|
450
|
+
|
|
451
|
+
# H2 forced: hydrogen consumed, no natural_gas
|
|
452
|
+
assert any(f.fuel_type == TypeFuel.HYDROGEN and f.mass_or_mass_fraction > 0
|
|
453
|
+
for f in fuels_h2), "H2 mode should consume hydrogen"
|
|
454
|
+
assert not any(f.fuel_type == TypeFuel.NATURAL_GAS and f.mass_or_mass_fraction > 0
|
|
455
|
+
for f in fuels_h2), "H2 mode should not consume natural gas"
|
|
456
|
+
|
|
457
|
+
# LNG forced matches default
|
|
458
|
+
assert np.isclose(
|
|
459
|
+
res_default.fuel_consumption_total_kg,
|
|
460
|
+
res_lng.fuel_consumption_total_kg,
|
|
461
|
+
rtol=1e-6,
|
|
462
|
+
), "Explicit LNG option should match default"
|
|
463
|
+
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|