Module MAPLEAF.Rocket.Propulsion
Expand source code
import re
from MAPLEAF.Motion import ForceMomentSystem, Inertia, Vector, linInterp
from MAPLEAF.Rocket import RocketComponent
__all__ = [ "TabulatedMotor" ]
class TabulatedMotor(RocketComponent):
    '''
    Interface:
        Initialization:
            In rocket text file, attribute: "path", pointing at a motor definition text file
            Format:"test/testMotorDefintion.txt"
        Functions:
            .Thrust(time) returns current thrust level
            .OxWeight(time) returns current oxidizer weight
            .FuelWeight(time) returns current fuel weight
        Attributes:
            .initialOxidizerWeight
            .initialFuelWeight
        All in same units as in the motor definition file (linearly interpolated)
        The motor is assumed to apply zero moment to the rocket, thrust is applied in z-direction
    '''
    #### Init Functions ####
    def __init__(self, componentDictReader, rocket, stage):
        #TODO: Oxidizer and Fuel CG Locations should be defined relative to the motor location
        self.rocket = rocket
        self.stage = stage
        self.componentDictReader = componentDictReader
        self.name = componentDictReader.getDictName()
        stage.motor = self
        self.classType = componentDictReader.getString("class")
        self.ignitionTime = 0 # Modified by Rocket._initializeStaging and Rocket._stageSeparation
        # Impulse adjustments (mostly for Monte Carlo sims)
        self.impulseAdjustFactor = componentDictReader.getFloat("impulseAdjustFactor")
        self.burnTimeAdjustFactor = componentDictReader.getFloat("burnTimeAdjustFactor")
        motorFilePath = componentDictReader.getString("path")
        self._parseMotorDefinitionFile(motorFilePath)
        # Set the position to the initial CG location
        initInertia = self.getInertia(0, "fakeState")
        self.position = initInertia.CG
    #TODO: Build converter/parser for standard engine format like rasp/.eng or something like that
    def _parseMotorDefinitionFile(self, motorFilePath):
        ''' Parses a motor definition text file. See MAPLEAF/Examples/Motors for examples '''
      
        # Get motor definition text
        with open(motorFilePath, "r") as motorFile:
            motorFileText = motorFile.read()
        # Remove all comment rows
        comment = re.compile("#.*") 
        motorFileText = re.sub(comment, "", motorFileText)
        
        #Remove blank lines
        motorFileText = [line for line in motorFileText.split('\n') if line.strip() != '']
        
        # Parse CG locations
        # TODO: Future motors should be able to exist off the rocket's center axis
        self.initOxCG_Z = float(motorFileText[0].split()[1]) + self.stage.position.Z
        self.finalOxCG_Z = float(motorFileText[1].split()[1]) + self.stage.position.Z
        self.initFuelCG_Z = float(motorFileText[2].split()[1]) + self.stage.position.Z
        self.finalFuelCG_Z = float(motorFileText[3].split()[1]) + self.stage.position.Z
        motorFileText = motorFileText[4:]
        # Parse data; Columns defined in MAPLEAF/Examples/Motors/test.txt
        # Gets defined values for: Time, thrust, oxFlowRate, fuelFlowRate, oxMOI, fuelMOI
        self.times = []
        self.thrustLevels = []
        oxFlowRate = []
        fuelFlowRate = []
        self.oxMOIs = []
        self.fuelMOIs = []
        for dataLine in motorFileText:
            # Splits line at each white space
            info = dataLine.split()
            self.times.append(float(info[0]))
            self.thrustLevels.append(float(info[1]))
            oxFlowRate.append(float(info[2]))
            fuelFlowRate.append(float(info[3]))
            
            oxVecStartIndex = dataLine.index('(')
            oxVecEndIndex = dataLine.index(')', oxVecStartIndex)+1
            oxVecString  = dataLine[oxVecStartIndex:oxVecEndIndex]
            oxMOIVec = Vector(oxVecString)
            self.oxMOIs.append(oxMOIVec)
            fuelVecStartIndex = dataLine.index('(', oxVecEndIndex)
            fuelVecEndIndex = dataLine.index(')', fuelVecStartIndex)+1
            fuelVecString  = dataLine[fuelVecStartIndex:fuelVecEndIndex]
            fuelMOIVec = Vector(fuelVecString)
            self.fuelMOIs.append(fuelMOIVec)
        # Tell the rocket and stage when their engines shut off -> used for flight animations
        self.stage.engineShutOffTime = self.times[-1]
        if self.rocket.engineShutOffTime == None:
            self.rocket.engineShutOffTime = self.times[-1]
        else:
            self.rocket.engineShutOffTime = max(self.rocket.engineShutOffTime, self.times[-1])
        # Apply adjustment factors for monte carlo sims
        self.thrustLevels = [ thrust*self.impulseAdjustFactor/self.burnTimeAdjustFactor for thrust in self.thrustLevels ]
        self.times = [ t*self.burnTimeAdjustFactor for t in self.times ]
        # Calculate initial fuel and oxidizer masses through trapezoid rule
        # Trapezoid rule matches the linear interpolation used to find thrust values
        self.initialOxidizerWeight = 0
        self.initialFuelWeight = 0
        self.oxWeights = [ 0 ]
        self.fuelWeights = [ 0 ]
        for i in range(len(self.times)-1, 0, -1):
            deltaT = self.times[i] - self.times[i-1]
            def integrateVal(value, sum, timeSeries):
                sum += deltaT * (value[i-1] + value[i]) / 2
                timeSeries.insert(0, sum)
                return sum
            self.initialOxidizerWeight = integrateVal(oxFlowRate, self.initialOxidizerWeight, self.oxWeights)
            self.initialFuelWeight = integrateVal(fuelFlowRate, self.initialFuelWeight, self.fuelWeights)
    #### Operational Functions ####
    def getInertia(self, time, state):
        timeSinceIgnition = max(0, time - self.ignitionTime)
        oxInertia = self._getOxInertia(timeSinceIgnition)
        fuelInertia = self._getFuelInertia(timeSinceIgnition)
        
        return oxInertia + fuelInertia
    def getAppliedForce(self, state, time, environment, CG):
        #TODO: Model "thrust damping" - where gases moving quickly in the engine act to damp out rotation about the x and y axes
        #TODO: Thrust vs altitude compensation
        timeSinceIgnition = max(0, time - self.ignitionTime)
        
        # Determine thrust magnitude
        if timeSinceIgnition < 0 or timeSinceIgnition > self.times[-1]:
            thrustMagnitude = 0
        else:
            thrustMagnitude = linInterp(self.times, self.thrustLevels, timeSinceIgnition)
        
        # Create Vector
        thrust = Vector(0,0, thrustMagnitude)
        return ForceMomentSystem(thrust)
    def updateIgnitionTime(self, ignitionTime, fakeValue=False):
        self.ignitionTime = ignitionTime
        if not fakeValue:
            self.rocket.engineShutOffTime = max(self.rocket.engineShutOffTime, self.ignitionTime + self.times[-1])
            self.stage.engineShutOffTime = self.ignitionTime + self.times[-1]
    def getTotalImpulse(self):
        # Integrate the thrust - assume linear interpolations between points given -> midpoint rule integrates this perfectly
        totalImpulse = 0
        for i in range(1, len(self.times)):
            deltaT = self.times[i] - self.times[i-1]
            totalImpulse += deltaT * (self.thrustLevels[i-1] + self.thrustLevels[i]) / 2
        
        return totalImpulse
    def _getMass(self, timeSinceIgnition):
        return self.OxWeight(timeSinceIgnition) + self.FuelWeight(timeSinceIgnition)
    def _getOxInertia(self, timeSinceIgnition):
        if self.initialOxidizerWeight == 0:
            return Inertia(Vector(0,0,0), Vector(0,0,0), 0)
        oxWeight = linInterp(self.times, self.oxWeights, timeSinceIgnition)
        
        # Find fraction of oxidizer burned
        oxBurnedFrac = 1 - (oxWeight/self.initialOxidizerWeight)
        
        #Linearly interpolate CG location based on fraction of oxidizer burned
        oxCG_Z = self.initOxCG_Z*(1 - oxBurnedFrac) + self.finalOxCG_Z*oxBurnedFrac
        #TODO: Allow motor(s) to be defined off-axis
        oxCG = Vector(0,0,oxCG_Z)
        # Get MOI
        oxMOI = linInterp(self.times, self.oxMOIs, timeSinceIgnition)
        
        return Inertia(oxMOI, oxCG, oxWeight)
    def _getFuelInertia(self, timeSinceIgnition):
        if self.initialFuelWeight == 0:
            return Inertia(Vector(0,0,0), Vector(0,0,0), 0)
        #See comments in _getOxInertia()
        fuelWeight = linInterp(self.times, self.fuelWeights, timeSinceIgnition)
        fuelBurnedFrac = 1 - (fuelWeight / self.initialFuelWeight)
        fuelCG_Z = self.initFuelCG_Z*(1 - fuelBurnedFrac) + self.finalFuelCG_Z*fuelBurnedFrac
        fuelCG = Vector(0,0,fuelCG_Z)
        fuelMOI = linInterp(self.times, self.fuelMOIs, timeSinceIgnition)
        return Inertia(fuelMOI, fuelCG, fuelWeight)
Classes
class TabulatedMotor (componentDictReader, rocket, stage)- 
Interface
Initialization: In rocket text file, attribute: "path", pointing at a motor definition text file Format:"test/testMotorDefintion.txt" Functions: .Thrust(time) returns current thrust level .OxWeight(time) returns current oxidizer weight .FuelWeight(time) returns current fuel weight Attributes: .initialOxidizerWeight .initialFuelWeight
All in same units as in the motor definition file (linearly interpolated) The motor is assumed to apply zero moment to the rocket, thrust is applied in z-direction
Expand source code
class TabulatedMotor(RocketComponent): ''' Interface: Initialization: In rocket text file, attribute: "path", pointing at a motor definition text file Format:"test/testMotorDefintion.txt" Functions: .Thrust(time) returns current thrust level .OxWeight(time) returns current oxidizer weight .FuelWeight(time) returns current fuel weight Attributes: .initialOxidizerWeight .initialFuelWeight All in same units as in the motor definition file (linearly interpolated) The motor is assumed to apply zero moment to the rocket, thrust is applied in z-direction ''' #### Init Functions #### def __init__(self, componentDictReader, rocket, stage): #TODO: Oxidizer and Fuel CG Locations should be defined relative to the motor location self.rocket = rocket self.stage = stage self.componentDictReader = componentDictReader self.name = componentDictReader.getDictName() stage.motor = self self.classType = componentDictReader.getString("class") self.ignitionTime = 0 # Modified by Rocket._initializeStaging and Rocket._stageSeparation # Impulse adjustments (mostly for Monte Carlo sims) self.impulseAdjustFactor = componentDictReader.getFloat("impulseAdjustFactor") self.burnTimeAdjustFactor = componentDictReader.getFloat("burnTimeAdjustFactor") motorFilePath = componentDictReader.getString("path") self._parseMotorDefinitionFile(motorFilePath) # Set the position to the initial CG location initInertia = self.getInertia(0, "fakeState") self.position = initInertia.CG #TODO: Build converter/parser for standard engine format like rasp/.eng or something like that def _parseMotorDefinitionFile(self, motorFilePath): ''' Parses a motor definition text file. See MAPLEAF/Examples/Motors for examples ''' # Get motor definition text with open(motorFilePath, "r") as motorFile: motorFileText = motorFile.read() # Remove all comment rows comment = re.compile("#.*") motorFileText = re.sub(comment, "", motorFileText) #Remove blank lines motorFileText = [line for line in motorFileText.split('\n') if line.strip() != ''] # Parse CG locations # TODO: Future motors should be able to exist off the rocket's center axis self.initOxCG_Z = float(motorFileText[0].split()[1]) + self.stage.position.Z self.finalOxCG_Z = float(motorFileText[1].split()[1]) + self.stage.position.Z self.initFuelCG_Z = float(motorFileText[2].split()[1]) + self.stage.position.Z self.finalFuelCG_Z = float(motorFileText[3].split()[1]) + self.stage.position.Z motorFileText = motorFileText[4:] # Parse data; Columns defined in MAPLEAF/Examples/Motors/test.txt # Gets defined values for: Time, thrust, oxFlowRate, fuelFlowRate, oxMOI, fuelMOI self.times = [] self.thrustLevels = [] oxFlowRate = [] fuelFlowRate = [] self.oxMOIs = [] self.fuelMOIs = [] for dataLine in motorFileText: # Splits line at each white space info = dataLine.split() self.times.append(float(info[0])) self.thrustLevels.append(float(info[1])) oxFlowRate.append(float(info[2])) fuelFlowRate.append(float(info[3])) oxVecStartIndex = dataLine.index('(') oxVecEndIndex = dataLine.index(')', oxVecStartIndex)+1 oxVecString = dataLine[oxVecStartIndex:oxVecEndIndex] oxMOIVec = Vector(oxVecString) self.oxMOIs.append(oxMOIVec) fuelVecStartIndex = dataLine.index('(', oxVecEndIndex) fuelVecEndIndex = dataLine.index(')', fuelVecStartIndex)+1 fuelVecString = dataLine[fuelVecStartIndex:fuelVecEndIndex] fuelMOIVec = Vector(fuelVecString) self.fuelMOIs.append(fuelMOIVec) # Tell the rocket and stage when their engines shut off -> used for flight animations self.stage.engineShutOffTime = self.times[-1] if self.rocket.engineShutOffTime == None: self.rocket.engineShutOffTime = self.times[-1] else: self.rocket.engineShutOffTime = max(self.rocket.engineShutOffTime, self.times[-1]) # Apply adjustment factors for monte carlo sims self.thrustLevels = [ thrust*self.impulseAdjustFactor/self.burnTimeAdjustFactor for thrust in self.thrustLevels ] self.times = [ t*self.burnTimeAdjustFactor for t in self.times ] # Calculate initial fuel and oxidizer masses through trapezoid rule # Trapezoid rule matches the linear interpolation used to find thrust values self.initialOxidizerWeight = 0 self.initialFuelWeight = 0 self.oxWeights = [ 0 ] self.fuelWeights = [ 0 ] for i in range(len(self.times)-1, 0, -1): deltaT = self.times[i] - self.times[i-1] def integrateVal(value, sum, timeSeries): sum += deltaT * (value[i-1] + value[i]) / 2 timeSeries.insert(0, sum) return sum self.initialOxidizerWeight = integrateVal(oxFlowRate, self.initialOxidizerWeight, self.oxWeights) self.initialFuelWeight = integrateVal(fuelFlowRate, self.initialFuelWeight, self.fuelWeights) #### Operational Functions #### def getInertia(self, time, state): timeSinceIgnition = max(0, time - self.ignitionTime) oxInertia = self._getOxInertia(timeSinceIgnition) fuelInertia = self._getFuelInertia(timeSinceIgnition) return oxInertia + fuelInertia def getAppliedForce(self, state, time, environment, CG): #TODO: Model "thrust damping" - where gases moving quickly in the engine act to damp out rotation about the x and y axes #TODO: Thrust vs altitude compensation timeSinceIgnition = max(0, time - self.ignitionTime) # Determine thrust magnitude if timeSinceIgnition < 0 or timeSinceIgnition > self.times[-1]: thrustMagnitude = 0 else: thrustMagnitude = linInterp(self.times, self.thrustLevels, timeSinceIgnition) # Create Vector thrust = Vector(0,0, thrustMagnitude) return ForceMomentSystem(thrust) def updateIgnitionTime(self, ignitionTime, fakeValue=False): self.ignitionTime = ignitionTime if not fakeValue: self.rocket.engineShutOffTime = max(self.rocket.engineShutOffTime, self.ignitionTime + self.times[-1]) self.stage.engineShutOffTime = self.ignitionTime + self.times[-1] def getTotalImpulse(self): # Integrate the thrust - assume linear interpolations between points given -> midpoint rule integrates this perfectly totalImpulse = 0 for i in range(1, len(self.times)): deltaT = self.times[i] - self.times[i-1] totalImpulse += deltaT * (self.thrustLevels[i-1] + self.thrustLevels[i]) / 2 return totalImpulse def _getMass(self, timeSinceIgnition): return self.OxWeight(timeSinceIgnition) + self.FuelWeight(timeSinceIgnition) def _getOxInertia(self, timeSinceIgnition): if self.initialOxidizerWeight == 0: return Inertia(Vector(0,0,0), Vector(0,0,0), 0) oxWeight = linInterp(self.times, self.oxWeights, timeSinceIgnition) # Find fraction of oxidizer burned oxBurnedFrac = 1 - (oxWeight/self.initialOxidizerWeight) #Linearly interpolate CG location based on fraction of oxidizer burned oxCG_Z = self.initOxCG_Z*(1 - oxBurnedFrac) + self.finalOxCG_Z*oxBurnedFrac #TODO: Allow motor(s) to be defined off-axis oxCG = Vector(0,0,oxCG_Z) # Get MOI oxMOI = linInterp(self.times, self.oxMOIs, timeSinceIgnition) return Inertia(oxMOI, oxCG, oxWeight) def _getFuelInertia(self, timeSinceIgnition): if self.initialFuelWeight == 0: return Inertia(Vector(0,0,0), Vector(0,0,0), 0) #See comments in _getOxInertia() fuelWeight = linInterp(self.times, self.fuelWeights, timeSinceIgnition) fuelBurnedFrac = 1 - (fuelWeight / self.initialFuelWeight) fuelCG_Z = self.initFuelCG_Z*(1 - fuelBurnedFrac) + self.finalFuelCG_Z*fuelBurnedFrac fuelCG = Vector(0,0,fuelCG_Z) fuelMOI = linInterp(self.times, self.fuelMOIs, timeSinceIgnition) return Inertia(fuelMOI, fuelCG, fuelWeight)Ancestors
- RocketComponent
 - abc.ABC
 
Methods
def getAppliedForce(self, state, time, environment, CG)- 
Expand source code
def getAppliedForce(self, state, time, environment, CG): #TODO: Model "thrust damping" - where gases moving quickly in the engine act to damp out rotation about the x and y axes #TODO: Thrust vs altitude compensation timeSinceIgnition = max(0, time - self.ignitionTime) # Determine thrust magnitude if timeSinceIgnition < 0 or timeSinceIgnition > self.times[-1]: thrustMagnitude = 0 else: thrustMagnitude = linInterp(self.times, self.thrustLevels, timeSinceIgnition) # Create Vector thrust = Vector(0,0, thrustMagnitude) return ForceMomentSystem(thrust) def getInertia(self, time, state)- 
Expand source code
def getInertia(self, time, state): timeSinceIgnition = max(0, time - self.ignitionTime) oxInertia = self._getOxInertia(timeSinceIgnition) fuelInertia = self._getFuelInertia(timeSinceIgnition) return oxInertia + fuelInertia def getTotalImpulse(self)- 
Expand source code
def getTotalImpulse(self): # Integrate the thrust - assume linear interpolations between points given -> midpoint rule integrates this perfectly totalImpulse = 0 for i in range(1, len(self.times)): deltaT = self.times[i] - self.times[i-1] totalImpulse += deltaT * (self.thrustLevels[i-1] + self.thrustLevels[i]) / 2 return totalImpulse def updateIgnitionTime(self, ignitionTime, fakeValue=False)- 
Expand source code
def updateIgnitionTime(self, ignitionTime, fakeValue=False): self.ignitionTime = ignitionTime if not fakeValue: self.rocket.engineShutOffTime = max(self.rocket.engineShutOffTime, self.ignitionTime + self.times[-1]) self.stage.engineShutOffTime = self.ignitionTime + self.times[-1]