Skip to content

Commit

Permalink
Merge pull request OpenMDAO#317 from johnjasa/battery_integration
Browse files Browse the repository at this point in the history
Adding simple battery subsystem and Dymos integration test
  • Loading branch information
johnjasa authored Jun 25, 2024
2 parents 78f9097 + da273fc commit a1e3ccc
Show file tree
Hide file tree
Showing 24 changed files with 1,776 additions and 88 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,6 @@
meta_data=ExtendedMetaData
)

av.add_meta_data(
Aircraft.Battery.EFFICIENCY,
units=None,
desc="Battery efficiency (eta)",
default_value=0.85,
meta_data=ExtendedMetaData
)

av.add_meta_data(
Aircraft.Battery.ENERGY_REQUIRED,
units="kW*h",
Expand All @@ -41,14 +33,6 @@
meta_data=ExtendedMetaData
)

av.add_meta_data(
Aircraft.Battery.MASS,
units="kg",
desc="Battery mass",
default_value=1.0,
meta_data=ExtendedMetaData
)

av.add_meta_data(
Aircraft.Battery.N_PARALLEL,
units=None,
Expand All @@ -73,14 +57,6 @@
meta_data=ExtendedMetaData
)

av.add_meta_data(
Aircraft.Battery.VOLUME,
units="inch**3",
desc="Battery volume",
default_value=1.0,
meta_data=ExtendedMetaData
)

##### CASE VALUES #####

av.add_meta_data(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,13 @@ class Aircraft(AviaryAircraft):

# cell = single cell, battery = one case plus multiple cells

class Battery:
class Battery(AviaryAircraft.Battery):
CURRENT_MAX = "aircraft:battery:current_max"
EFFICIENCY = "aircraft:battery:efficiency"
ENERGY_REQUIRED = "aircraft:battery:energy_required"
HEAT_CAPACITY = "aircraft:battery:heat_capacity"
MASS = "aircraft:battery:mass"
N_PARALLEL = "aircraft:battery:n_parallel"
N_SERIES = "aircraft:battery:n_series"
VOLTAGE = "aircraft:battery:voltage"
VOLUME = "aircraft:battery:volume"

class Case:
HEAT_CAPACITY = "aircraft:battery:case:heat_capacity"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ def build_mission(self, num_nodes, aviary_inputs):
-engine_data[:, 4],
units='lbm/s',
desc='Current fuel flow rate ')
engine.add_output(Dynamic.Mission.ELECTRIC_POWER,
engine.add_output(Dynamic.Mission.ELECTRIC_POWER_IN,
zeros_array,
units='kW',
desc='Current electric energy rate')
Expand Down
4 changes: 2 additions & 2 deletions aviary/interface/test/test_timeseries_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,15 @@ def test_timeseries_report(self):
max_iter=0)

expected_header = [
"time (s)", "altitude (ft)", "altitude_rate (ft/s)", "distance (m)", "drag (lbf)",
"time (s)", "altitude (ft)", "altitude_rate (ft/s)", "distance (m)", "drag (lbf)", "electric_power_in_total (kW)",
"fuel_flow_rate_negative_total (lbm/h)", "mach (unitless)", "mach_rate (unitless/s)",
"mass (kg)", "specific_energy_rate_excess (m/s)", "throttle (unitless)",
"thrust_net_total (lbf)", "velocity (m/s)"
]

expected_rows = [
[
"0.0", "0.0", "8.333333333333337", "1.0", "21108.418300418845",
"0.0", "0.0", "8.333333333333337", "1.0", "21108.418300418845", "0.0",
"-10492.593707324704", "0.2", "0.0001354166666666668", "79560.101698",
"12.350271989430475", "0.565484286063171", "28478.788920867584",
"68.05737270077049"
Expand Down
5 changes: 5 additions & 0 deletions aviary/mission/flight_phase_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,11 @@ def build_phase(self, aviary_options: AviaryValues = None, phase_type=EquationsO
output_name=Dynamic.Mission.FUEL_FLOW_RATE_NEGATIVE_TOTAL, units='lbm/h'
)

phase.add_timeseries_output(
Dynamic.Mission.ELECTRIC_POWER_IN_TOTAL,
output_name=Dynamic.Mission.ELECTRIC_POWER_IN_TOTAL, units='kW'
)

phase.add_timeseries_output(
Dynamic.Mission.ALTITUDE_RATE,
output_name=Dynamic.Mission.ALTITUDE_RATE, units='ft/s'
Expand Down
3 changes: 2 additions & 1 deletion aviary/mission/flops_based/ode/mission_ODE.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,8 @@ def setup(self):
num_nodes=nn, aviary_inputs=aviary_options)
if subsystem_mission is not None:
add_subsystem_group = True
external_subsystem_group.add_subsystem(subsystem.name, subsystem_mission)
external_subsystem_group.add_subsystem(subsystem.name,
subsystem_mission)

# Only add the external subsystem group if it has at least one subsystem.
# Without this logic there'd be an empty OM group added to the ODE.
Expand Down
1,112 changes: 1,112 additions & 0 deletions aviary/models/engines/turbofan_28k_with_electric.deck

Large diffs are not rendered by default.

158 changes: 158 additions & 0 deletions aviary/models/test_aircraft/aircraft_for_bench_FwFm_with_electric.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
aircraft:air_conditioning:mass_scaler,1.0,unitless
aircraft:anti_icing:mass_scaler,1.0,unitless
aircraft:apu:mass_scaler,1.1,unitless
aircraft:avionics:mass_scaler,1.2,unitless
aircraft:canard:area,0.0,ft**2
aircraft:canard:aspect_ratio,0.0,unitless
aircraft:canard:thickness_to_chord,0.0,unitless
aircraft:crew_and_payload:baggage_mass_per_passenger,45.0,lbm
aircraft:crew_and_payload:cargo_container_mass_scaler,1.0,unitless
aircraft:crew_and_payload:flight_crew_mass_scaler,1.0,unitless
aircraft:crew_and_payload:mass_per_passenger,180.0,lbm
aircraft:crew_and_payload:misc_cargo,0.0,lbm
aircraft:crew_and_payload:non_flight_crew_mass_scaler,1.0,unitless
aircraft:crew_and_payload:num_business_class,0,unitless
aircraft:crew_and_payload:num_first_class,11,unitless
aircraft:crew_and_payload:num_flight_attendants,3,unitless
aircraft:crew_and_payload:num_flight_crew,2,unitless
aircraft:crew_and_payload:num_galley_crew,0,unitless
aircraft:crew_and_payload:num_non_flight_crew,3,unitless
aircraft:crew_and_payload:num_passengers,169,unitless
aircraft:crew_and_payload:num_tourist_class,158,unitless
aircraft:crew_and_payload:passenger_service_mass_scaler,1.0,unitless
aircraft:crew_and_payload:wing_cargo,0.0,lbm
aircraft:design:base_area,0.0,ft**2
aircraft:design:empty_mass_margin_scaler,0.0,unitless
aircraft:design:lift_dependent_drag_coeff_factor,0.909839381134961,unitless
aircraft:design:touchdown_mass,152800.0,lbm
aircraft:design:reserve_fuel_additional,3000.,lbm
aircraft:design:subsonic_drag_coeff_factor,1.0,unitless
aircraft:design:supersonic_drag_coeff_factor,1.0,unitless
aircraft:design:use_alt_mass,False,unitless
aircraft:design:zero_lift_drag_coeff_factor,0.930890028006548,unitless
aircraft:electrical:mass_scaler,1.25,unitless
aircraft:engine:additional_mass_fraction,0.,unitless
aircraft:engine:constant_fuel_consumption,0.,lbm/h
aircraft:engine:data_file,models/engines/turbofan_28k_with_electric.deck,unitless
aircraft:engine:flight_idle_thrust_fraction,0.0,unitless
aircraft:engine:flight_idle_max_fraction,1.0,unitless
aircraft:engine:flight_idle_min_fraction,0.08,unitless
aircraft:engine:fuel_flow_scaler_constant_term,0.,unitless
aircraft:engine:fuel_flow_scaler_linear_term,0.,unitless
aircraft:engine:generate_flight_idle,True,unitless
aircraft:engine:geopotential_alt,False,unitless
aircraft:engine:ignore_negative_thrust,False,unitless
aircraft:engine:interpolation_method,slinear,unitless
aircraft:engine:mass_scaler,1.15,unitless
aircraft:engine:mass,7400,lbm
aircraft:engine:num_engines,2,unitless
aircraft:engine:num_fuselage_engines,0,unitless
aircraft:engine:num_wing_engines,2,unitless
aircraft:engine:reference_mass,7400,lbm
aircraft:engine:reference_sls_thrust,28928.1,lbf
aircraft:engine:scale_mass,True,unitless
aircraft:engine:scale_performance,True,unitless
aircraft:engine:scaled_sls_thrust,28928.1,lbf
aircraft:engine:subsonic_fuel_flow_scaler,1.,unitless
aircraft:engine:supersonic_fuel_flow_scaler,1.,unitless
aircraft:engine:thrust_reversers_mass_scaler,0.0,unitless
aircraft:engine:wing_locations,[0.26869218],unitless
aircraft:fins:area,0.0,ft**2
aircraft:fins:mass_scaler,1.0,unitless
aircraft:fins:mass,0.0,lbm
aircraft:fins:num_fins,0,unitless
aircraft:fins:taper_ratio,10.0,unitless
aircraft:fuel:auxiliary_fuel_capacity,0.0,lbm
aircraft:fuel:density_ratio,1.0,unitless
aircraft:fuel:fuel_system_mass_scaler,1.0,unitless
aircraft:fuel:fuselage_fuel_capacity,0.0,lbm
aircraft:fuel:num_tanks,7,unitless
aircraft:fuel:total_capacity,45694.0,lbm
aircraft:fuel:unusable_fuel_mass_scaler,1.0,unitless
aircraft:furnishings:mass_scaler,1.1,unitless
aircraft:fuselage:length,128.0,ft
aircraft:fuselage:mass_scaler,1.05,unitless
aircraft:fuselage:max_height,13.17,ft
aircraft:fuselage:max_width,12.33,ft
aircraft:fuselage:military_cargo_floor,False,unitless
aircraft:fuselage:num_fuselages,1,unitless
aircraft:fuselage:passenger_compartment_length,85.5,ft
aircraft:fuselage:planform_area,1578.24,ft**2
aircraft:fuselage:wetted_area_scaler,1.0,unitless
aircraft:fuselage:wetted_area,4158.62,ft**2
aircraft:horizontal_tail:area,355.0,ft**2
aircraft:horizontal_tail:aspect_ratio,6.0,unitless
aircraft:horizontal_tail:mass_scaler,1.2,unitless
aircraft:horizontal_tail:taper_ratio,0.22,unitless
aircraft:horizontal_tail:thickness_to_chord,0.125,unitless
aircraft:horizontal_tail:vertical_tail_fraction,0.0,unitless
aircraft:horizontal_tail:wetted_area_scaler,1.0,unitless
aircraft:horizontal_tail:wetted_area,592.65,ft**2
aircraft:hydraulics:mass_scaler,1.0,unitless
aircraft:hydraulics:system_pressure,3000.0,lbf/ft**2
aircraft:instruments:mass_scaler,1.25,unitless
aircraft:landing_gear:carrier_based,False,unitless
aircraft:landing_gear:main_gear_mass_scaler,1.1,unitless
aircraft:landing_gear:main_gear_oleo_length,102.0,inch
aircraft:landing_gear:nose_gear_mass_scaler,1.0,unitless
aircraft:landing_gear:nose_gear_oleo_length,67.0,inch
aircraft:nacelle:avg_diameter,7.94,ft
aircraft:nacelle:avg_length,12.3,ft
aircraft:nacelle:count_factor,2,unitless
aircraft:nacelle:mass_scaler,1.0,unitless
aircraft:nacelle:wetted_area_scaler,1.0,unitless
aircraft:paint:mass_per_unit_area,0.037,lbm/ft**2
aircraft:propulsion:engine_oil_mass_scaler,1.0,unitless
aircraft:propulsion:misc_mass_scaler,1.0,unitless
aircraft:vertical_tail:area,284.0,ft**2
aircraft:vertical_tail:aspect_ratio,1.75,unitless
aircraft:vertical_tail:mass_scaler,1.0,unitless
aircraft:vertical_tail:num_tails,1,unitless
aircraft:vertical_tail:taper_ratio,0.33,unitless
aircraft:vertical_tail:thickness_to_chord,0.1195,unitless
aircraft:vertical_tail:wetted_area_scaler,1.0,unitless
aircraft:vertical_tail:wetted_area,581.13,ft**2
aircraft:wing:aeroelastic_tailoring_factor,0.0,unitless
aircraft:wing:airfoil_technology,1.92669766647637,unitless
aircraft:wing:area,1370.0,ft**2
aircraft:wing:aspect_ratio,11.22091,unitless
aircraft:wing:bending_mass_scaler,1.0,unitless
aircraft:wing:chord_per_semispan,0.31,0.23,0.084,unitless
aircraft:wing:composite_fraction,0.2,unitless
aircraft:wing:control_surface_area,137,ft**2
aircraft:wing:control_surface_area_ratio,0.1,unitless
aircraft:wing:glove_and_bat,134.0,ft**2
aircraft:wing:input_station_dist,0.,0.2759,0.9367,unitless
aircraft:wing:load_distribution_control,2.0,unitless
aircraft:wing:load_fraction,1.0,unitless
aircraft:wing:load_path_sweep_dist,0.,22.,deg
aircraft:wing:mass_scaler,1.23,unitless
aircraft:wing:max_camber_at_70_semispan,0.0,unitless
aircraft:wing:misc_mass_scaler,1.0,unitless
aircraft:wing:num_integration_stations,50,unitless
aircraft:wing:shear_control_mass_scaler,1.0,unitless
aircraft:wing:span_efficiency_reduction,False,unitless
aircraft:wing:span,117.83,ft
aircraft:wing:strut_bracing_factor,0.0,unitless
aircraft:wing:surface_ctrl_mass_scaler,1.0,unitless
aircraft:wing:sweep,25.0,deg
aircraft:wing:taper_ratio,0.278,unitless
aircraft:wing:thickness_to_chord_dist,0.145,0.115,0.104,unitless
aircraft:wing:thickness_to_chord,0.13,unitless
aircraft:wing:ultimate_load_factor,3.75,unitless
aircraft:wing:var_sweep_mass_penalty,0.0,unitless
aircraft:wing:wetted_area_scaler,1.0,unitless
aircraft:wing:wetted_area,2396.56,ft**2
mission:constraints:max_mach,0.785,unitless
mission:design:cruise_altitude,35000,ft
mission:design:gross_mass,175400.0,lbm
mission:design:range,3500,NM
mission:design:thrust_takeoff_per_eng,28928.1,lbf
mission:landing:lift_coefficient_max,2.0,unitless
mission:summary:cruise_mach,0.785,unitless
mission:summary:fuel_flow_scaler,1.0,unitless
mission:takeoff:fuel_simple,577,lbm
mission:takeoff:lift_coefficient_max,3.0,unitless
mission:takeoff:lift_over_drag,17.354,unitless
settings:equations_of_motion,height_energy
settings:mass_method,FLOPS
Empty file.
59 changes: 59 additions & 0 deletions aviary/subsystems/energy/battery_builder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import numpy as np
import openmdao.api as om

from aviary.subsystems.subsystem_builder_base import SubsystemBuilderBase
from aviary.subsystems.energy.battery_sizing import SizeBattery
from aviary.variable_info.variables import Aircraft, Dynamic


class BatteryBuilder(SubsystemBuilderBase):
default_name = 'battery'

def build_pre_mission(self, aviary_inputs=None):
return SizeBattery(aviary_inputs=aviary_inputs)

def get_mass_names(self):
return [Aircraft.Battery.MASS]

def build_mission(self, num_nodes, aviary_inputs=None) -> om.Group:
battery_group = om.Group()
# Here, the efficiency variable is used as an overall efficiency for the battery
soc = om.ExecComp('state_of_charge = (energy_capacity - (cumulative_electric_energy_used/efficiency)) / energy_capacity',
state_of_charge={'val': np.zeros(
num_nodes), 'units': 'unitless'},
energy_capacity={'val': 10.0, 'units': 'kJ'},
cumulative_electric_energy_used={
'val': np.zeros(num_nodes), 'units': 'kJ'},
efficiency={'val': 0.95, 'units': 'unitless'})

battery_group.add_subsystem('state_of_charge',
subsys=soc,
promotes_inputs=[('energy_capacity', Aircraft.Battery.ENERGY_CAPACITY),
('cumulative_electric_energy_used',
Dynamic.Mission.CUMULATIVE_ELECTRIC_ENERGY_USED),
('efficiency', Aircraft.Battery.EFFICIENCY)],
promotes_outputs=[('state_of_charge', Dynamic.Mission.BATTERY_STATE_OF_CHARGE)])

return battery_group

def get_states(self):
state_dict = {Dynamic.Mission.CUMULATIVE_ELECTRIC_ENERGY_USED: {'fix_initial': True,
'fix_final': False,
'lower': 0.0,
'ref': 1e4,
'defect_ref': 1e6,
'units': 'kJ',
'rate_source': Dynamic.Mission.ELECTRIC_POWER_IN_TOTAL,
'input_initial': 0.0}}

return state_dict

def get_constraints(self):
constraint_dict = {
# Can add constraints here; state of charge is a common one in many battery applications
f'battery.{Dynamic.Mission.BATTERY_STATE_OF_CHARGE}':
{'type': 'boundary',
'loc': 'final',
'lower': 0.2},
}
return constraint_dict
60 changes: 60 additions & 0 deletions aviary/subsystems/energy/battery_sizing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import openmdao.api as om

from aviary.utils.aviary_values import AviaryValues
from aviary.variable_info.functions import add_aviary_input, add_aviary_output
from aviary.variable_info.variables import Aircraft


class SizeBattery(om.ExplicitComponent):
'''
Calculates battery mass from specific energy and additional mass
'''

def initialize(self):
self.options.declare(
'aviary_inputs', types=AviaryValues,
desc='collection of Aircraft/Mission specific options')

def setup(self):
add_aviary_input(self, Aircraft.Battery.PACK_MASS, val=0.0, units='kg',
desc='mass of energy-storing components of battery')
add_aviary_input(self, Aircraft.Battery.ADDITIONAL_MASS, val=0.0, units='kg',
desc='mass of non energy-storing components of battery')
add_aviary_input(self, Aircraft.Battery.PACK_ENERGY_DENSITY, val=0.0, units='kJ/kg',
desc='energy density of battery pack')

add_aviary_output(self, Aircraft.Battery.MASS, val=0.0,
units='kg', desc='total battery mass')
add_aviary_output(self, Aircraft.Battery.ENERGY_CAPACITY, val=0.0,
units='kJ', desc='total battery energy storage')

def compute(self, inputs, outputs):
energy_density_kj_kg = inputs[Aircraft.Battery.PACK_ENERGY_DENSITY]
addtl_mass = inputs[Aircraft.Battery.ADDITIONAL_MASS]
pack_mass = inputs[Aircraft.Battery.PACK_MASS]

total_mass = pack_mass + addtl_mass
total_energy = pack_mass * energy_density_kj_kg

outputs[Aircraft.Battery.MASS] = total_mass
outputs[Aircraft.Battery.ENERGY_CAPACITY] = total_energy

def setup_partials(self):
self.declare_partials(Aircraft.Battery.ENERGY_CAPACITY,
Aircraft.Battery.PACK_ENERGY_DENSITY)
self.declare_partials(Aircraft.Battery.ENERGY_CAPACITY,
Aircraft.Battery.PACK_MASS)

self.declare_partials(Aircraft.Battery.MASS,
Aircraft.Battery.ADDITIONAL_MASS, val=1.0)
self.declare_partials(Aircraft.Battery.MASS,
Aircraft.Battery.PACK_MASS, val=1.0)

def compute_partials(self, inputs, J):
energy_density_kj_kg = inputs[Aircraft.Battery.PACK_ENERGY_DENSITY]
pack_mass = inputs[Aircraft.Battery.PACK_MASS]

J[Aircraft.Battery.ENERGY_CAPACITY,
Aircraft.Battery.PACK_ENERGY_DENSITY] = pack_mass
J[Aircraft.Battery.ENERGY_CAPACITY,
Aircraft.Battery.PACK_MASS] = energy_density_kj_kg
Empty file.
Loading

0 comments on commit a1e3ccc

Please sign in to comment.