Source code for hyperloop.Python.pod.drivetrain.electric_motor

"""
Models the equivalent circuit model of a brushless DC (BLDC) motor to perform motor sizing.
Calculates Phase Current, Phase Voltage, Frequency, motor size, and Weight.
"""
from __future__ import print_function

import numpy as np
from openmdao.api import Component, Problem, Group, Newton, IndepVarComp, ScipyGMRES

[docs]class MotorBalance(Component): """Creates an implicit connection used to balance conservation of energy across the motor by computing the residual of power_in - power_out. Params ------ motor_power_input : float total required power input into motor (W) current : float current through motor (A) voltage : float voltage across motor (V) Unknowns -------- I0 : float motor No-load Current (A) """ def __init__(self): super(MotorBalance, self).__init__() self.deriv_options['type'] = 'cs' self.add_state('I0', val=40.0, desc='motor no load current', units='A') self.add_param('motor_power_input', 0.0, desc='total power input into motor', units='W') self.add_param('current', val=2.0, desc='total current through motor', units='A') self.add_param('voltage', val=500.0, desc='total voltage through motor', units='V')
[docs] def solve_nonlinear(self, params, unknowns, resids): pass
[docs] def apply_nonlinear(self, params, unknowns, resids): resids['I0'] = ( params['current'] * params['voltage'] - params['motor_power_input'])
[docs]class MotorGroup(Group): """MotorGroup represents a BLDC motor in an OpenMDAO model which can calculate size, mass, and various performance characteristics of a BLDC motor based on input paramters Components ---------- motor : Motor Calculates the electrical characteristics of the motor motor_size : MotorSize Calculates the size, mass, and performance characteristics of the motor motor_balance : MotorBalance Calculates the residual in the conservation of energy equation between input power and total power used by the motor from mechanical output and additional losses Params ------ design_power : float desired design value for motor power (W) design_torque : float desired torque at max rpm (N*m) motor_max_current : float max motor phase current (A) motor_LD_ratio : float length to diameter ratio of motor (unitless) motor_oversize_factor : float scales peak motor power by this figure (unitless) Outputs ------- current : float current through motor (A) voltage : float voltage across motor in (V) phase_current : float phase current through motor (A) phase_voltage : float phase voltage across motor (V) frequency : float Frequency of electric output waveform (Hz) motor_power_input : float total required power input into motor (W) motor_volume : float D^2*L parameter which is proportional to Torque (mm^3) motor_diameter : float diameter of motor (m) motor_mass : float mass of motor (kg) motor_length : float motor length (m) motor_power_input : float total required power input into motor (W) """ def __init__(self): super(MotorGroup, self).__init__() self.add('motor', Motor(), promotes=['design_torque', 'motor_max_current', 'phase_current', 'phase_voltage', 'current', 'voltage', 'frequency', 'motor_power_input']) self.add('motor_size', MotorSize(), promotes=['motor_mass', 'design_torque', 'design_power', 'motor_max_current', 'motor_length', 'motor_diameter', 'motor_volume', 'motor_LD_ratio', 'motor_oversize_factor']) self.add('motor_balance', MotorBalance(), promotes=['current', 'voltage', 'motor_power_input']) self.connect('motor_size.max_torque', 'motor.max_torque') self.add('idp1', IndepVarComp('n_phases', 3.0, units='unitless')) self.connect('idp1.n_phases', ['motor_size.n_phases', 'motor.n_phases']) self.add('idp2', IndepVarComp('pole_pairs', 6.0, units='unitless')) self.connect('idp2.pole_pairs', ['motor_size.pole_pairs', 'motor.pole_pairs']) #self.add('idp3', # IndepVarComp('motor_max_current', 42.0, # units='A'), # promotes=['motor_max_current']) # self.add('idp4', # IndepVarComp('design_torque', 1.0, # units='N*m'), # promotes=['design_torque']) self.connect('motor_size.power_iron_loss', 'motor.power_iron_loss') self.connect('motor_size.power_mech', 'motor.power_mech') self.connect('motor_size.power_windage_loss', 'motor.power_windage_loss') self.connect('motor_size.w_operating', 'motor.w_operating') self.connect('motor_size.winding_resistance', 'motor.winding_resistance') self.connect('motor_balance.I0', 'motor.I0') self.nl_solver = Newton() self.nl_solver.options['maxiter'] = 1000 self.nl_solver.options['atol'] = 0.0001 self.ln_solver = ScipyGMRES() self.ln_solver.options['maxiter'] = 100
[docs]class MotorSize(Component): """MotorSize models the size of a BLDC motor based on a set of input paramters using data from existing commerical BLDC motors and work done by ([1]_) Parameters ---------- design_power : float desired design value for motor power (W) design_torque : float desired torque at max rpm (N*m) motor_max_current : float max motor phase current (A) n_phases : float number of motor phases (unitless) motor_oversize_factor : float scales peak motor power by this figure (unitless) kappa : float ratio of base speed to max speed (unitless) pole_pairs : float number of motor pole pairs (unitless) motor_LD_ratio : float length to diameter ratio of motor (unitless) core_radius_ratio : float ratio of inner diamter to outer diameter of core (unitless) Outputs ------- motor_volume : float D^2*L parameter which is proportional to Torque (mm^3) motor_diameter : float diameter of motor winding (m) motor_mass :float mass of motor (kg) motor_length : float length of motor (m) w_base : float base speed of motor (rad/s) max_torque : float maximum possible torque for motor (N*m) power_iron_loss : float total power loss due to iron core (W) power_mech : float mechanical power output of motor (W) power_windage_loss : float friction loss from motor operation (W) winding_resistance : float total resistance of copper winding (ohm) w_operating : float operating speed of motor (rad/s) References ---------- .. [1] "J. Gladin, K. Ali, K. Collins, "Conceptual Modeling of Electric and Hybrid-Electric Propulsion for UAS Applications," Georgia Tech, 2015. """ def __init__(self): """Initalizes the motor_size component to its default values""" super(MotorSize, self).__init__() self.deriv_options['type'] = 'cs' self.add_param('motor_max_current', val=42.0, desc='max motor phase current', units='A') self.add_param('motor_LD_ratio', val=0.822727, desc='length to diameter ratio of motor', units='unitless') self.add_param('core_radius_ratio', 0.0, desc='ratio of inner diamter to outer diameter of core', units='unitless') self.add_param('pole_pairs', val=6.0, desc='number of motor pole pairs', units='unitless') self.add_param('design_power', val=0.394 * 746, desc='desired design value for motor power', units='W') self.add_param('motor_oversize_factor', 1.0, desc='scales peak motor power by this figure', units='unitless') self.add_param('design_torque', val=1.0, desc='torque at max rpm', units='N*m') self.add_param('n_phases', val=3.0, desc='number of motor power phases', units='unitless') self.add_param('kappa', val=1 / 1.75, desc='ratio of base speed to max speed', units='unitless') self.add_output('motor_diameter', val=0.48, desc='diameter of motor winding', units='m') self.add_output('motor_length', val=0.4, desc='motor length', units='m') self.add_output('motor_mass', val=0.0, desc='mass of motor', units='kg') self.add_output('max_torque', val=0.0, desc='maximum possible torque for motor', units='N*m') self.add_output('w_base', val=3000.0, desc=' base speed of motor ', units='rad/s') self.add_output( 'motor_volume', val=1.0, desc='D-squared*L parameter which is proportional to Torque', units='mm^3') self.add_output('power_mech', 0.0, desc='mechanical power output of motor', units='W') self.add_output('w_operating', 0.0, desc='operating speed of motor', units='rad/s') self.add_output('power_iron_loss', 0.0, desc='total power loss due to iron core', units='W') self.add_output('winding_resistance', 0.0, desc='total resistance of copper winding', units='ohm') self.add_output('power_windage_loss', 0.0, desc='friction loss from motor operation', units='W')
[docs] def solve_nonlinear(self, params, unknowns, resids): """Runs the MotorSize component and sets its respective outputs to their calculated results in the unknowns `VecWrapper`. Args ---------- params : `VecWrapper` `VecWrapper` containing parameters unknowns : `VecWrapper` `VecWrapper` containing outputs and states resids : `VecWrapper` `VecWrapper` containing residuals """ # following sign convention for pycycle design_torque = -params['design_torque'] design_power = -params['design_power'] * params['motor_oversize_factor'] # calc max torque, rotational velocity, power # w_max = params['max_rpm'] * 2.0 * np.pi / 60.0 # rad/sec unknowns['w_operating'] = design_power / design_torque # operating at maximum speed unknowns['w_base'] = params['kappa'] * unknowns['w_operating'] unknowns['max_torque'] = design_power / unknowns['w_base'] # unknowns['torque'] = design_power / w_max # unknowns['w_operating'] = params['speed'] * 2 * np.pi / 60.0 unknowns['power_mech'] = unknowns['w_operating'] * design_torque # calc size # print('max_torque %f' % unknowns['max_torque']) unknowns['motor_volume'] = 293722.0 * np.power(unknowns['max_torque'], 0.7592) # mm^3 unknowns['motor_diameter'] = np.power(unknowns['motor_volume'] / params['motor_LD_ratio'], 1.0 / 3.0) / 1000.0 # m unknowns['motor_length'] = unknowns['motor_diameter'] * params['motor_LD_ratio'] # m unknowns['motor_mass'] = 0.0000070646 * np.power( unknowns['motor_volume'], 0.9386912061) # kg, relation in GT paper (Figure 6) # calc loss parameters unknowns['power_iron_loss'] = self.calculate_iron_loss( unknowns['motor_diameter'], unknowns['w_operating'], unknowns['motor_length'], params['core_radius_ratio'], params['pole_pairs']) unknowns['winding_resistance'] = self.calculate_copper_loss( unknowns['motor_diameter'], params['motor_max_current'], params['n_phases']) unknowns['power_windage_loss'] = self.calculate_windage_loss( unknowns['w_operating'], unknowns['motor_diameter'], unknowns['motor_length'])
[docs] def calculate_windage_loss(self, w_operating, motor_diameter, motor_length): """Calculates the windage or frictional losses of a BLDC motor with dimensions given by `motor_length` and `motor_diameter` operating at motor_speed `w_operating`. Args ---- w_operating : float operating speed of motor (rad/s) motor_diameter : float diameter of motor winding (m) motor_length : float motor length (m) Returns ------- float the total windage losses of the motor (W) """ return 0 # # calc Reynolds number losses # Re = np.power(diameter, 2.0) / 4.0 * w_operating / 2.075e-5 * 0.05 # c_friction = 0.01 # # # TODO no nested loop # # while (abs(diff) > 0.001): # # Cd = 1.0 / np.power(2.04+1.768*np.log(Re*np.power(c_friction, 0.5)), 2.0) # # diff = (Cd - c_friction) / Cd # # c_friction = Cd # # # calculate cylinder loss # P_windage_cylinder_loss = c_friction * np.pi * np.power(w_operating, 3.0) * np.power( # diameter / 2.0, 4.0) * motor_length * 1.2041 # # calculate cylinder face loss # Re_r = np.power(diameter, 2.0) / 4.0 * w_operating / 2.075e-5 # c_disk_friction = 0.08 / np.power(0.05, 0.167) / np.power(Re, 0.25) # P_windage_face_loss = 0.5 * c_friction * 1.2041 * np.power(w_operating, 3.0) * np.power( # diameter / 2.0, 5.0) * (1.0 - np.power(core_radius_ratio, 5.0)) # # P_windage_total_loss = P_windage_face_loss + P_windage_face_loss # P_windage_total_loss = 0
[docs] def calculate_copper_loss(self, motor_diameter, motor_max_current, n_phases): """Calculates the resistive losses in the copper winding of a BLDC motor operating at `motor_max_current` and `n_phases` with dimension specified by `motor_diameter`. Parameters ---------- self motor_diameter : float dameter of motor winding (m) motor_max_current : float max motor phase current (A) n_phases : float number of motor phases (unitless) Returns ------- float the total resistive losses of the copper winding (W) """ # D-axis resistance per motor phase at very high-speed (short-cruit) Rd = 0.0 # calc static loading factor from GT paper As = 688.7 * motor_max_current # calc total resistance in winding n_coil_turns = As * np.pi * motor_diameter / motor_max_current / n_phases / 2.0 resistance_per_km_per_turn = 48.8387296964863 * np.power( motor_max_current, -1.00112597971171) winding_len = motor_diameter * 3.14159 resistance_per_turn = resistance_per_km_per_turn * winding_len / 1000. return resistance_per_turn * n_coil_turns * n_phases
[docs] def calculate_iron_loss(self, motor_diameter, motor_speed, motor_length, core_radius_ratio, pole_pairs): """Calculates the iron core magnetic losses of a BLDC motor with dimensions given by `motor_length` and `motor_diameter` operating at speed `motor_speed`. Args ---- motor_diameter : float diameter of motor winding (m) speed : float desired output shaft mechanical motor_speed (RPM) motor_length : float motor length (m) core_radius_ratio : float ratio of inner diameter to outer diameter of core (unitless) Returns ------- float the total iron core losses of the motor (W) """ stator_core_density = 7650.0 # kg/m^3 # hysteresis loss constant Kh = 0.0275 # W/(kg T^2 Hz) # iron eddy loss constant Kc = 1.83e-5 # W/(kg T^2 Hz^2) # correction factor Ke = 2.77e-5 # W/(kg T^1.5 Hz^1.5) # stator magnetic flux density Bp = 1.22 # T # Bp = 1.5 # iron losses freq = motor_speed / (2.0 * np.pi) * pole_pairs volume_iron = np.pi * motor_length * np.power(motor_diameter / 2.0, 2.0) * ( 1.0 - np.power(core_radius_ratio, 2.0)) iron_core_mass = stator_core_density * volume_iron power_iron_loss = (Kh * np.power(Bp, 2.0) * freq + Kc * np.power( Bp * freq, 2.0) + Ke * np.power(Bp * freq, 1.5)) * iron_core_mass return power_iron_loss
[docs]class Motor(Component): """ Represents an electric motor which can calculate output current and voltage based on motor sizing parameters and losses. Used in conjunction with motor_balance to find the correct no load current for this motor Based on work done by Gladin et. al. ([1]_) Params ------ w_operating : float operating speed of motor (rad/s) design_torque : float torque at max rpm (N*m) I0 : float motor No-load Current (A) motor_max_current : float max motor phase current (A) pole_pairs : float number of motor pole pairs (unitless) n_phases : float number of motor phases (unitless) max_torque : float maximum possible torque for motor (N*m) power_iron_loss : float total power loss due to iron core (W) power_mech : float mechanical power output of motor (W) power_windage_loss : float friction loss from motor operation (W) winding_resistance : float total resistance of copper winding (ohm) Outputs ------- current : float current through motor (A) voltage : float voltage across motor (V) phase_current : float phase current through motor (A) phase_voltage : float phase voltage across motor (V) frequency : float Frequency of electric output waveform (Hz) motor_power_input : float total required power input into motor (W) References ---------- .. [1] "J. Gladin, K. Ali, K. Collins, "Conceptual Modeling of Electric and Hybrid-Electric Propulsion for UAS Applications," Georgia Tech, 2015. """ def __init__(self): """Creates an instance of motor_size and initializes it to the default values below""" super(Motor, self).__init__() self.deriv_options['type'] = 'cs' self.add_param('motor_max_current', val=42.0, desc='max operating current', units='A') self.add_param('I0', val=40.0, desc='motor no load current', units='A') self.add_param('n_phases', val=3.0, desc='number of motor power phases', units='unitless') self.add_param('w_operating', 0.0, desc='operating speed of motor', units='rad/s') self.add_param('pole_pairs', val=6.0, desc='number of motor pole_pairs', units='unitless') self.add_param('design_torque', val=1.0, desc='torque at max_rpm', units='N*m') self.add_param('winding_resistance', 0.0, desc='total resistance of copper winding', units='ohm') self.add_param('power_windage_loss', 0.0, desc='friction loss from motor operation', units='W') self.add_param('power_mech', 0.0, desc='mechanical power output of motor', units='W') self.add_param('power_iron_loss', 0.0, desc='total power loss due to iron core', units='W') self.add_param('max_torque', val=0.0, desc='maximum possible torque for the motor', units='N*m') self.add_output('current', val=2.0, desc='current through motor', units='A') self.add_output('phase_current', val=1.0, desc='phase current through motor', units='A') self.add_output('voltage', val=500.0, desc='voltage across motor', units='V') self.add_output('phase_voltage', val=1.0, desc='phase voltage across motor', units='V') self.add_output('frequency', val=60.0, desc='frequency of electric output waveform', units='Hz') self.add_output('motor_power_input', 1.0, desc='total required power input into motor', units='W')
[docs] def solve_nonlinear(self, params, unknowns, resids): # following sign convention for pycycle design_torque = -1 * params['design_torque'] # voltage constant k_v = (params['motor_max_current'] - params['I0'] ) / params['max_torque'] * 30.0 / np.pi # torque constant k_t = 30.0 / np.pi * 1.0 / k_v # Calculating phase current, phase voltage, frequency, and phase unknowns['current'] = params['I0'] + design_torque / k_t power_copper_loss = np.power(unknowns['current'], 2.0) * params['winding_resistance'] unknowns['motor_power_input'] = params['power_mech'] + params[ 'power_windage_loss'] + params[ 'power_iron_loss'] + power_copper_loss unknowns['current'] = params['I0'] + design_torque / k_t unknowns['phase_current'] = unknowns['current'] / params['n_phases'] unknowns['voltage'] = unknowns['current'] * params[ 'winding_resistance'] + params['w_operating'] / (k_v * np.pi / 30.0 ) unknowns['phase_voltage'] = unknowns['voltage'] * np.sqrt(3.0 / 2.0) unknowns['frequency'] = params['w_operating'] / np.pi * params[ 'pole_pairs'] / 60.0
if __name__ == '__main__': prob = Problem() prob.root = MotorGroup() # rec = SqliteRecorder('drivetraindb') # rec.options['record_params'] = True # rec.options['record_metadata'] = True # prob.driver.add_recorder(rec) prob.setup(check=True) prob['motor_max_current'] = 450.0 prob['motor_LD_ratio'] = 0.83 prob['design_power'] = 110000 prob['design_torque'] = 420.169 prob['motor_oversize_factor'] = 1.0 prob['idp1.n_phases'] = 3.0 prob['motor_size.kappa'] = 0.5 prob['idp2.pole_pairs'] = 6.0 prob['motor_size.core_radius_ratio'] = 0.7 prob.root.list_connections() prob.run() # prob.print_all_convergence() print("FINAL") print(prob['motor.I0']) print(prob['voltage']) print(prob['current']) print(prob['motor_mass']) print(prob['motor_length']) print(prob['motor_volume']) print(prob['motor_size.max_torque']) prob.cleanup()