# needed for sqrt, pi
from math import sqrt, pi, log10

class MAV:

    def __init__( self, dict ):
        """
        Constructor for the MAV class.
        Initializes properties from a dictionary.
        """
        for x in dict.keys():
            self.__dict__[x] = dict[x]

    def wingloading( self ):
        """
        Procedure sets the wing loading based on the
        loiter condition.
        """
        V = self.velocity
        A = self.aspect_ratio
        rho = self.air_density
        e = self.oswald_efficiency
        Cdo = self.parasitic_drag_coefficient
        self.wing_loading = 0.5 * rho * V**2 * sqrt( pi * A * e * Cdo )

    def wingarea( self ):
        """
        Function returns the wing area based on the MAV
        weight and wing loading.
        """
        return self.weight * self.gravity / self.wing_loading

    def dimension( self ):
        """
        Procedure finds the largest dimension for the
        design.
        """
        p = self.propeller_diameter
        S = self.wingarea()
        h = self.taper_ratio
        test = 100.0
        c = 0.10
        while( c/test > 1.01 or c/test < 0.99 ):
            test = c
            b = sqrt( c**2 + p**2 - (h*c)**2 )
            c = -2.0 * S / ( -p - h*b + h*p - b )
        self.wingchord = c
        self.wingspan = b
        self.largest_dimension = sqrt( c**2 + p**2 )

    def score( self ):
        """
        Function returns the score as defined in the rules.
        """
        return self.endurance * 3600 / ( self.largest_dimension * 100 )**3

    def parasiticDragCoefficient( self ):
        """
        Procedure finds the parasitic drag coefficient.
        """
        V = self.velocity
        test = 100.0
        Cdo = self.parasitic_drag_coefficient
        while( Cdo/test > 1.01 or Cdo/test < 0.99 ):
            test = Cdo
            self.dimension()
            L = self.wingchord
            rho = self.air_density
            mu = self.air_viscosity
            Rc = lambda V, L, rho=rho, mu=mu: rho*V*L/mu
            Cf = lambda Rc: 0.455 / ( (log10(Rc))**2.58 )
            # Wing
            FF = lambda xc, tc: 1 + 0.6/xc * tc + 100*tc**4
            # xc, tc are estimated here
            wing = Cf(Rc(V,L)) * FF(0.3,0.0999) * 2
            # Tail
            # L, xc, tc are estimated here
            tail = Cf(Rc(V,0.03)) * FF(0.3,0.04) * 2
            Cdo = wing + tail
        self.parasitic_drag_coefficient = Cdo

    def liftCoefficientRequired( self ):
        """
        Procedure finds the required lift coefficient.
        """
        rho = self.air_density
        WS = self.wing_loading
        V = self.velocity
        CLreq = WS / ( 0.5 * rho * V**2 )
        self.lift_coefficient = CLreq

    def inducedDragCoefficient( self ):
        """
        Procedure finds the induced drag coefficient.
        """
        e = self.oswald_efficiency
        A = self.aspect_ratio
        CL = self.lift_coefficient
        Cdi = CL**2 / ( pi * A * e )
        self.induced_drag_coefficient = Cdi

    def angleOfAttack( self ):
        """
        Procedure sets the angle of attack to obtain
        the required lift coefficient.
        """
        self.liftCoefficientRequired()
        x = self.lift_coefficient
        try:
            # use experimental data curve
            self.angle_of_attack = eval(self.experimentalAngleOfAttack,
                    {'x':x, 'sqrt':sqrt})
        except ValueError:
            # if there's a ValueError it's because the
            # required lift coefficient is not
            # attainable, so, decrease the wing loading
            # and try again
            self.wing_loading = self.wing_loading - 1
            self.angleOfAttack()
        except AttributeError:
            # otherwise use analytical approach
            print "Error: analytical approach to angle of attack is not implemented"

    def dragCoefficient( self ):
        """
        Procedure sets the drag coefficient based on
        experimental data and the design angle of attack.
        """
        self.angleOfAttack()
        try:
            # use experimental data curve
            x = self.angle_of_attack
            self.drag_coefficient = eval(self.experimentalDragCoefficient,
                    {'x':x})
        except AttributeError:
            # otherwise use analytical approach
            self.inducedDragCoefficient()
            self.drag_coefficient = self.parasitic_drag_coefficient \
                    + self.induced_drag_coefficient

    def thrust( self, velocity ):
        """
        Function returns the thrust at the given velocity.
        """
        return self.power * self.propeller_efficiency / velocity

    def drag( self, velocity ):
        """
        Function returns the drag at the given velocity.
        """
        rho = self.air_density
        CD = self.drag_coefficient
        S = self.wingarea()
        return 0.5 * rho * velocity**2 * S * CD

    def maxVelocity( self ):
        """
        Procedure sets the velocity based on the thrust
        and drag equations.
        """
        P = self.power
        eta = self.propeller_efficiency
        rho = self.air_density
        self.parasiticDragCoefficient()
        Cd = self.drag_coefficient
        S = self.wingarea()
        self.velocity = 2**(1.0/3.0) * ( P * eta * rho**2 * S**2 * Cd**2 )**(1.0/3.0) / ( rho * S * Cd )

    def thrustDragPlot( self ):
        """
        Procedure prints a thrust-drag plot for the current
        design properties as a tab deliminated table.
        """
        for x in range(8, 20):
            print "%s\t%s\t%s\t%s" % (x, self.thrust( self.velocity ), self.thrust( x ), self.drag( x ))

    def thrustDragPlotHTML( self ):
        print "<h2>Thrust-Drag Plot</h2>"
        print "<table>"
        print "<tr><th>Velocity</th><th>Thrust at Max Velocity</th><th>Thrust at Velocity</th><th>Drag at Velocity</th></tr>"
        for x in range(8, 20):
            print "<tr><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>" % (x, self.thrust( self.velocity ), self.thrust( x ), self.drag( x ))
        print "</table>"

    def analyzeMavHTML( self ):    
        self.wingloading()
        self.parasiticDragCoefficient()
        self.liftCoefficientRequired()
        self.dragCoefficient()
        self.maxVelocity()
        print "<h2>Design Parameters</h2>"
        print "<table>"
        print "<tr><td>%s</td><td>%s</td><td>%s</td></tr>" % ("Weight", self.weight*1000, "gm")
        print "<tr><td>%s</td><td>%s</td><td>%s</td></tr>" % ("Velocity", self.velocity, "m/s")
        print "<tr><td>%s</td><td>%s</td><td>%s</td></tr>" % ("Lift Coefficient", self.lift_coefficient, "-")
        print "<tr><td>%s</td><td>%s</td><td>%s</td></tr>" % ("Angle of Attack", self.angle_of_attack, "deg")
        print "<tr><td>%s</td><td>%s</td><td>%s</td></tr>" % ("Wingspan", self.wingspan*100, "cm")
        print "<tr><td>%s</td><td>%s</td><td>%s</td></tr>" % ("Wingchord", self.wingchord*100, "cm")
        print "<tr><td>%s</td><td>%s</td><td>%s</td></tr>" % ("Largest Dimension", self.largest_dimension*100, "cm")
        print "<tr><td>%s</td><td>%s</td><td>%s</td></tr>" % ("Score", self.score(), "smiles")
        print "<tr><td>%s</td><td>%s</td><td>%s</td></tr>" % ("Wingloading", self.wing_loading/9.8/10, "gm/cm^2")
        print "<tr><td>%s</td><td>%s</td><td>%s</td></tr>" % ("Drag Coefficient", self.drag_coefficient, "-")
        print "<tr><td>%s</td><td>%s</td><td>%s</td></tr>" % ("Wingarea", self.wingarea()*10000, "cm^2")
        print "</table>"
        self.thrustDragPlotHTML()

dict = {
    # Environmental Constants
    'air_density' : 1.1,
    'gravity' : 9.80,
    'air_viscosity' : 1.75E-5,
    # Design Parameters
    'aspect_ratio' : 1.4,
    'taper_ratio' : 0.4,
    # Initial Values
    'velocity' : 14.0,
    'parasitic_drag_coefficient' : 0.05,
    # Set Values
    'weight' : 0.033,
    'oswald_efficiency' : 0.8,
    'endurance' : .25,
    'power' : 1.5,
    # Component Values
    'propeller_diameter' : 0.075,
    'propeller_efficiency' : 0.60,
    # Lift and Drag functions
    'experimentalAngleOfAttack' : '227/18 - 1/18 * sqrt( 56407 - 18000*x )',
    'experimentalDragCoefficient' : '.0004*x**2 - .0006*x + .0653'
    }

def run():
    it = MAV(dict)
    it.analyzeMavHTML()

if __name__=='__main__':
    run()
