From 6a8a7ad075b118a77ad6b453c3e4d3d7f3519a29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Luis=20Cano=20Rodr=C3=ADguez?= Date: Fri, 1 Mar 2013 13:23:01 +0100 Subject: [PATCH] On with the shocks refactor The API is the same, the implementation is much better now. Also updated examples from README.rst and charts example. --- README.rst | 4 +- examples/Oblique shocks chart.ipynb | 6 +-- skaero/gasdynamics/shocks.py | 82 ++++++++++++++++++++++------- 3 files changed, 68 insertions(+), 24 deletions(-) diff --git a/README.rst b/README.rst index 5ecb2f5..420b6b0 100644 --- a/README.rst +++ b/README.rst @@ -57,9 +57,9 @@ Gas dynamics calculations:: >>> from skaero.gasdynamics import isentropic, shocks >>> fl = isentropic.IsentropicFlow(gamma=1.4) >>> p = 101325 * fl.p_p0(M=0.8) # Static pressure given total pressure of 1 atm - >>> ns = shocks.NormalShock(M_1=2.5, gamma=1.4) + >>> ns = shocks.Shock(M_1=2.5, gamma=1.4) >>> M_2 = ns.M_2 # Mach number behind a normal shock wave - >>> os = shocks.from_deflection_angle(3.0, np.radians(25), weak=True) # Known M and deflection angle + >>> os = shocks.Shock(M_1=3.0, theta=np.radians(25), weak=True) Dependencies ============ diff --git a/examples/Oblique shocks chart.ipynb b/examples/Oblique shocks chart.ipynb index 384697b..45b988a 100644 --- a/examples/Oblique shocks chart.ipynb +++ b/examples/Oblique shocks chart.ipynb @@ -66,7 +66,7 @@ " beta = np.linspace(mu, np.pi / 2, N)\n", " theta = np.zeros_like(beta)\n", " for i in range(N):\n", - " theta[i] = shocks.ObliqueShock(M_1, beta[i], gamma).theta\n", + " theta[i] = shocks.Shock(M_1=M_1, beta=beta[i], gamma=gamma).theta\n", " ax.plot(np.degrees(theta), np.degrees(beta), label=r\"$M_1 = \\,{}$\".format(M if np.isfinite(M) else \"\\infty\"), *args, **kwargs)" ], "language": "python", @@ -114,13 +114,13 @@ " theta_sonic = np.empty_like(M)\n", " beta_sonic = np.empty_like(M)\n", " def eq(beta, M_1, gamma):\n", - " os = shocks.ObliqueShock(M_1, beta, gamma)\n", + " os = shocks.Shock(M_1=M_1, beta=beta, gamma=gamma)\n", " return os.M_2 - 1\n", " for i in range(N):\n", " mu = np.arcsin(1.0 / M[i])\n", " __, beta_max = shocks.max_deflection(M[i], gamma)\n", " beta_sonic[i] = sp.optimize.bisect(eq, mu, beta_max, args=(M[i], gamma))\n", - " theta_sonic[i] = shocks.ObliqueShock(M[i], beta_sonic[i], gamma).theta\n", + " theta_sonic[i] = shocks.Shock(M_1=M[i], beta=beta_sonic[i], gamma=gamma).theta\n", " ax.plot(np.degrees(theta_sonic), np.degrees(beta_sonic), *args, **kwargs)" ], "language": "python", diff --git a/skaero/gasdynamics/shocks.py b/skaero/gasdynamics/shocks.py index 409940c..9a6dd73 100644 --- a/skaero/gasdynamics/shocks.py +++ b/skaero/gasdynamics/shocks.py @@ -5,24 +5,25 @@ Routines -------- -from_deflection_angle(M_1, theta, weak=True, gamma=1.4) max_deflection(M_1, gamma=1.4) +Shock(**kwargs) -Classes -------- -NormalShock(M_1, gamma) -ObliqueShock(M_1, beta, gamma) +The important piece of the module is `Shock`, which returns a shock object +from a variety of combinations of parameters. For more information and +examples, see the docstring of `Shock`. Examples -------- >>> from skaero.gasdynamics import shocks ->>> ns = shocks.NormalShock(2.0, gamma=1.4) # Normal shock with M_1 = 2.0 ->>> shocks.from_deflection_angle(3.0, np.radians(25), weak=True) +>>> ns = shocks.Shock(M_1=2.0, gamma=1.4) # Normal shock by default +>>> shocks.Shock(M_1=3.0, theta=np.radians(25), weak=True) """ from __future__ import division, absolute_import +import inspect + import numpy as np import scipy as sp import scipy.optimize @@ -67,6 +68,11 @@ def eq(beta, M_1, gamma): def _ShockFactory(**kwargs): """Returns an object representing a shock wave. + Parameters + ---------- + gamma : float, optional + Specific heat ratio, default 7 / 5. + Examples -------- >>> ss1 = Shock(M_1=1.5) # Given upstream Mach number (default beta = 90°) @@ -80,26 +86,63 @@ def _ShockFactory(**kwargs): >>> ss2.beta # Notice it is an oblique shock 0.6590997534071927 + TODO + ---- + * This is a list of possible cases + + * M_1(, beta=np.pi / 2) -> _ShockClass(M_1, beta) (only direct case) + * M_2(, beta=np.pi / 2) + * p2_p1(, beta=np.pi / 2) + * ... + * M_1, theta(, weak=True) + * M_2, theta(, weak=True) + * p2_p1, theta(, weak=True) + """ + kwargs.setdefault('gamma', 1.4) + try: + # We want a view of the keys, but the syntax changed in Python 3 + params = kwargs.viewkeys() + except AttributeError: + params = kwargs.keys() + if not 'theta' in params: + kwargs.setdefault('beta', np.pi / 2) + # ['X', 'beta', 'gamma'] + if len(params) != 3: + raise InvalidParametersError("Invalid list of parameters") + else: + if 'beta' in params: + raise InvalidParametersError("Invalid list of parameters") + kwargs.setdefault('weak', True) + # ['X', 'theta', 'weak', 'gamma'] + if len(params) != 4: + raise InvalidParametersError("Invalid list of parameters") + + # Here is the list of available resolution methods + methods_list = [ + _from_deflection_angle + ] + + # And we generate a dictionary from it, indexed by their call arguments methods = { - frozenset(['M_1']): _ShockClass, - frozenset(['M_1', 'theta']): _from_deflection_angle, - frozenset(['M_1', 'theta', 'weak']): _from_deflection_angle - } - beta = kwargs.pop('beta', np.pi / 2) - gamma = kwargs.pop('gamma', 1.4) - params = frozenset(kwargs.keys()) + frozenset(inspect.getargspec(f)[0]): f + for f in methods_list} + # HACK, see http://stackoverflow.com/a/3999604/554319 + _k_class = ( + frozenset(inspect.getargspec(_ShockClass.__init__)[0]) - set(['self'])) + methods[_k_class] = _ShockClass try: - shock = methods[params](beta=beta, gamma=gamma, **kwargs) + call_sig = frozenset(params) + shock = methods[call_sig](**kwargs) except KeyError: - raise InvalidParametersError("Invalid list of parameters") + raise NotImplementedError return shock Shock = _ShockFactory -def _from_deflection_angle(M_1, theta, weak=True, gamma=1.4, **kwargs): +def _from_deflection_angle(M_1, theta, weak, gamma): """Returns oblique shock given upstream Mach number and deflection angle. """ @@ -126,7 +169,7 @@ class _ShockClass(object): """Class representing a shock. """ - def __init__(self, M_1, beta, gamma=1.4): + def __init__(self, M_1, beta, gamma): mu = mach_angle(M_1) if beta < mu: raise ValueError( @@ -148,7 +191,7 @@ def theta(self): """Deflection angle of the shock. """ - if self.beta == 0.0 or self.beta == np.pi / 2: + if self.beta == mach_angle(self.M_1) or self.beta == np.pi / 2: theta = 0.0 else: theta = np.arctan( @@ -161,6 +204,7 @@ def theta(self): def M_2n(self): """Normal Mach number behind the shock. + FIXME: Raises ZeroDivisionError if M_1n == 0.0. Consistent? """ M_2n = np.sqrt( (1 / (self.M_1n * self.M_1n) + (self.gamma - 1) / 2) /