Module MAPLEAF.Rocket.CompositeObject

Both Stage and Rocket objects inherit from CompositeObject. It implements functionality to add forces and inertias from an arbitrary number of subcomponents.

Expand source code
'''
Both `MAPLEAF.Rocket.Stage` and `MAPLEAF.Rocket.Rocket` objects inherit from `CompositeObject`.
It implements functionality to add forces and inertias from an arbitrary number of subcomponents.
'''

from MAPLEAF.Motion import ForceMomentSystem, Inertia, Vector
from MAPLEAF.Utilities import cacheLastResult
from MAPLEAF.Rocket import FixedMass

__all__ = [ 'CompositeObject' ]

class CompositeObject():
    """
        Represents a collection of physical objects with masses.  
        Expects:  
            Components to have the following methods:  
                .getAppliedForce(state, time, environmentalConditions, rocketCG)  
                .getInertia(time)  
                .getMass(time) (optional as long as getInertia is implemented)  
            Objects that inherit from FixedMass to be fixed mass objects. Their inertias are cached and not recomputed  
            Objects that do not inherit from FixedMass to be variable mass objects. Their inertias are recalculated all the time.  
    """
    def __init__(self, components=[], CGOverride=None, MOIOverride=None, massOverride=None):
        self.components = components

        self.CGOverride = CGOverride
        self.MOIOverride = MOIOverride
        self.massOverride = massOverride

        # Initialize lists of fixed and variable-mass components
        self.fixedMassComponents = []
        self.variableMassComponents = []
        self._initializeComponentLists()

        # Pre-calculate inertia of all fixed-mass components
        self.fixedMassInertiaComponent = None
        self.fixedMassMassComponent = None
        self._initializeFixedMassInertia()

        #TODO: Add ability to override aero-properties

    def _initializeComponentLists(self):
        # Sort component lists
        self.fixedMassComponents = []
        self.variableMassComponents = []

        for component in self.components:
            if isinstance(component, FixedMass):
                self.fixedMassComponents.append(component)
            else:
                self.variableMassComponents.append(component)

    def _initializeFixedMassInertia(self):
        if len(self.fixedMassComponents) > 0:
            inertiasList = []
            for fixedMassComponent in self.fixedMassComponents:
                inertiasList.append(fixedMassComponent.getInertia(0, None))

            self.fixedMassInertiaComponent = inertiasList[0].combineInertias(inertiasList)
            self.fixedMassMassComponent = self.fixedMassInertiaComponent.mass
        else:
            self.fixedMassInertiaComponent = Inertia(Vector(0,0,0), Vector(0,0,0), 0)
            self.fixedMassMassComponent = 0

    def recomputeFixedMassInertia(self):
        ''' Intended to be called if fixedMass components are added/removed or modified from the composite object '''

        self._initializeComponentLists()
        self._initializeFixedMassInertia()

        # Try to recompute inertias of sub-object, in case any of them are also CompositeObjects
            # This happens in the case of a Rocket, where the components are Stages (also CompositeObjects)
        for component in self.components:
            try:
                component.recomputeFixedMassInertia()
            except AttributeError:
                pass # Not a CompositeObject

    #TODO: Compute forces at interfaces between components
    
    @cacheLastResult
    def getAppliedForce(self, state, time, environmentalConditions, rocketCG):
        '''
            Computes the aerodynamic force experienced by the stage.
            Does not include gravitational force - gravity is added at the rocket level.
        '''
        # Add up forces from all subcomponents
        totalAppliedComponentForce = ForceMomentSystem(Vector(0,0,0), rocketCG)
        for component in self.components:
            componentForce = component.getAppliedForce(state, time, environmentalConditions, rocketCG)

            # Log applied forces and moments if desired
            if hasattr(component, "forcesLog"):
                component.forcesLog.append(componentForce.force)
                component.momentsLog.append(componentForce.moment)

            totalAppliedComponentForce += componentForce

        return totalAppliedComponentForce

    @cacheLastResult
    def getInertia(self, time, state):
        ''' Returns an Inertia object, used for 6DoF computations '''            
        # Add up component inertias
        inertiasList = [ self.fixedMassInertiaComponent ]

        for component in self.variableMassComponents:
            inertiasList.append(component.getInertia(time, state))

        combinedInertia = inertiasList[0].combineInertias(inertiasList)

        # Overrides
        if self.CGOverride != None:
            combinedInertia.CG = self.CGOverride
            combinedInertia.MOICentroidLocation = self.CGOverride
        if self.MOIOverride != None:
            combinedInertia.MOI = self.MOIOverride
        if self.massOverride != None:
            combinedInertia.mass = self.massOverride

        return combinedInertia

    def getMass(self, time, state):
        ''' 
            Returns an inertia object where the CG and MOI members are zero.
            Only the mass is filled out correctly.
            Used for 3DoF computations 
        '''
        if self.massOverride == None:
            totalMass = self.fixedMassMassComponent
            for component in self.variableMassComponents:
                try:
                    totalMass += component.getMass(time, state).mass
                except AttributeError:
                    totalMass += component.getInertia(time, state).mass
        else:
            return self.massOverride

        return totalMass

    def getCG(self, time, state):
        return self.getInertia(time, state).CG

Classes

class CompositeObject (components=[], CGOverride=None, MOIOverride=None, massOverride=None)

Represents a collection of physical objects with masses.
Expects:
Components to have the following methods:
.getAppliedForce(state, time, environmentalConditions, rocketCG)
.getInertia(time)
.getMass(time) (optional as long as getInertia is implemented)
Objects that inherit from FixedMass to be fixed mass objects. Their inertias are cached and not recomputed
Objects that do not inherit from FixedMass to be variable mass objects. Their inertias are recalculated all the time.

Expand source code
class CompositeObject():
    """
        Represents a collection of physical objects with masses.  
        Expects:  
            Components to have the following methods:  
                .getAppliedForce(state, time, environmentalConditions, rocketCG)  
                .getInertia(time)  
                .getMass(time) (optional as long as getInertia is implemented)  
            Objects that inherit from FixedMass to be fixed mass objects. Their inertias are cached and not recomputed  
            Objects that do not inherit from FixedMass to be variable mass objects. Their inertias are recalculated all the time.  
    """
    def __init__(self, components=[], CGOverride=None, MOIOverride=None, massOverride=None):
        self.components = components

        self.CGOverride = CGOverride
        self.MOIOverride = MOIOverride
        self.massOverride = massOverride

        # Initialize lists of fixed and variable-mass components
        self.fixedMassComponents = []
        self.variableMassComponents = []
        self._initializeComponentLists()

        # Pre-calculate inertia of all fixed-mass components
        self.fixedMassInertiaComponent = None
        self.fixedMassMassComponent = None
        self._initializeFixedMassInertia()

        #TODO: Add ability to override aero-properties

    def _initializeComponentLists(self):
        # Sort component lists
        self.fixedMassComponents = []
        self.variableMassComponents = []

        for component in self.components:
            if isinstance(component, FixedMass):
                self.fixedMassComponents.append(component)
            else:
                self.variableMassComponents.append(component)

    def _initializeFixedMassInertia(self):
        if len(self.fixedMassComponents) > 0:
            inertiasList = []
            for fixedMassComponent in self.fixedMassComponents:
                inertiasList.append(fixedMassComponent.getInertia(0, None))

            self.fixedMassInertiaComponent = inertiasList[0].combineInertias(inertiasList)
            self.fixedMassMassComponent = self.fixedMassInertiaComponent.mass
        else:
            self.fixedMassInertiaComponent = Inertia(Vector(0,0,0), Vector(0,0,0), 0)
            self.fixedMassMassComponent = 0

    def recomputeFixedMassInertia(self):
        ''' Intended to be called if fixedMass components are added/removed or modified from the composite object '''

        self._initializeComponentLists()
        self._initializeFixedMassInertia()

        # Try to recompute inertias of sub-object, in case any of them are also CompositeObjects
            # This happens in the case of a Rocket, where the components are Stages (also CompositeObjects)
        for component in self.components:
            try:
                component.recomputeFixedMassInertia()
            except AttributeError:
                pass # Not a CompositeObject

    #TODO: Compute forces at interfaces between components
    
    @cacheLastResult
    def getAppliedForce(self, state, time, environmentalConditions, rocketCG):
        '''
            Computes the aerodynamic force experienced by the stage.
            Does not include gravitational force - gravity is added at the rocket level.
        '''
        # Add up forces from all subcomponents
        totalAppliedComponentForce = ForceMomentSystem(Vector(0,0,0), rocketCG)
        for component in self.components:
            componentForce = component.getAppliedForce(state, time, environmentalConditions, rocketCG)

            # Log applied forces and moments if desired
            if hasattr(component, "forcesLog"):
                component.forcesLog.append(componentForce.force)
                component.momentsLog.append(componentForce.moment)

            totalAppliedComponentForce += componentForce

        return totalAppliedComponentForce

    @cacheLastResult
    def getInertia(self, time, state):
        ''' Returns an Inertia object, used for 6DoF computations '''            
        # Add up component inertias
        inertiasList = [ self.fixedMassInertiaComponent ]

        for component in self.variableMassComponents:
            inertiasList.append(component.getInertia(time, state))

        combinedInertia = inertiasList[0].combineInertias(inertiasList)

        # Overrides
        if self.CGOverride != None:
            combinedInertia.CG = self.CGOverride
            combinedInertia.MOICentroidLocation = self.CGOverride
        if self.MOIOverride != None:
            combinedInertia.MOI = self.MOIOverride
        if self.massOverride != None:
            combinedInertia.mass = self.massOverride

        return combinedInertia

    def getMass(self, time, state):
        ''' 
            Returns an inertia object where the CG and MOI members are zero.
            Only the mass is filled out correctly.
            Used for 3DoF computations 
        '''
        if self.massOverride == None:
            totalMass = self.fixedMassMassComponent
            for component in self.variableMassComponents:
                try:
                    totalMass += component.getMass(time, state).mass
                except AttributeError:
                    totalMass += component.getInertia(time, state).mass
        else:
            return self.massOverride

        return totalMass

    def getCG(self, time, state):
        return self.getInertia(time, state).CG

Subclasses

Methods

def getAppliedForce(*args)
Expand source code
def memoized_func(*args):
    # Return cached result if available
    if cache[1] == args:
        return cache[2]
    
    # Compute and cache result
    result = func(*args)
    cache[1] = args
    cache[2] = result
    return result
def getCG(self, time, state)
Expand source code
def getCG(self, time, state):
    return self.getInertia(time, state).CG
def getInertia(*args)
Expand source code
def memoized_func(*args):
    # Return cached result if available
    if cache[1] == args:
        return cache[2]
    
    # Compute and cache result
    result = func(*args)
    cache[1] = args
    cache[2] = result
    return result
def getMass(self, time, state)

Returns an inertia object where the CG and MOI members are zero. Only the mass is filled out correctly. Used for 3DoF computations

Expand source code
def getMass(self, time, state):
    ''' 
        Returns an inertia object where the CG and MOI members are zero.
        Only the mass is filled out correctly.
        Used for 3DoF computations 
    '''
    if self.massOverride == None:
        totalMass = self.fixedMassMassComponent
        for component in self.variableMassComponents:
            try:
                totalMass += component.getMass(time, state).mass
            except AttributeError:
                totalMass += component.getInertia(time, state).mass
    else:
        return self.massOverride

    return totalMass
def recomputeFixedMassInertia(self)

Intended to be called if fixedMass components are added/removed or modified from the composite object

Expand source code
def recomputeFixedMassInertia(self):
    ''' Intended to be called if fixedMass components are added/removed or modified from the composite object '''

    self._initializeComponentLists()
    self._initializeFixedMassInertia()

    # Try to recompute inertias of sub-object, in case any of them are also CompositeObjects
        # This happens in the case of a Rocket, where the components are Stages (also CompositeObjects)
    for component in self.components:
        try:
            component.recomputeFixedMassInertia()
        except AttributeError:
            pass # Not a CompositeObject