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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: RunFeemsSim
3
- Version: 0.4.4
3
+ Version: 0.6.0
4
4
  Summary: A library for running feems simulation
5
5
  Author-email: Kevin Koosup Yum <kevinkoosup.yum@gmail.com>
6
6
  License-Expression: Apache-2.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
- len(propulsion_power) == len(auxiliary_power_kw) or len(auxiliary_power_kw) == 1
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,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: RunFeemsSim
3
- Version: 0.4.4
3
+ Version: 0.6.0
4
4
  Summary: A library for running feems simulation
5
5
  Author-email: Kevin Koosup Yum <kevinkoosup.yum@gmail.com>
6
6
  License-Expression: Apache-2.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "RunFeemsSim"
7
- version = "0.4.4"
7
+ version = "0.6.0"
8
8
  description = "A library for running feems simulation"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -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 Genset
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