From 19a4b0cf75aedc821aaf161875386f4ef9e03a69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lex=20S=C3=A1ez?= Date: Thu, 24 Dec 2015 01:38:11 +0100 Subject: [PATCH] * Changed repo structure from src/ to src/pyfme * Added aricraft folder with an empty file that will contain the test aircraft. * Added some functions in utils/coordinates to change from body to local horizon coords and from local horizon coords to body (tested). --- .gitignore | 4 + src/pyfme/__init__.py | 2 + src/{ => pyfme}/aero/tests/test_aero.py | 0 .../aircrafts/aircraft.py} | 0 .../environment/tests/test_environment.py} | 0 .../models/tests/test_models.py} | 0 .../prop/tests/test_prop.py} | 0 .../tests/test_PyFME.py} | 0 src/pyfme/utils/__init__.py | 0 src/pyfme/utils/coordinates.py | 161 ++++++++++++++++++ src/pyfme/utils/tests/__init__.py | 0 src/pyfme/utils/tests/test_utils.py | 92 ++++++++++ 12 files changed, 259 insertions(+) create mode 100644 .gitignore create mode 100644 src/pyfme/__init__.py rename src/{ => pyfme}/aero/tests/test_aero.py (100%) rename src/{environment/tests/test_environment.py => pyfme/aircrafts/aircraft.py} (100%) rename src/{models/tests/test_models.py => pyfme/environment/tests/test_environment.py} (100%) rename src/{prop/tests/test_prop.py => pyfme/models/tests/test_models.py} (100%) rename src/{tests/test_PyFME.py => pyfme/prop/tests/test_prop.py} (100%) rename src/{utils/tests/test_utils.py => pyfme/tests/test_PyFME.py} (100%) create mode 100644 src/pyfme/utils/__init__.py create mode 100644 src/pyfme/utils/coordinates.py create mode 100644 src/pyfme/utils/tests/__init__.py create mode 100644 src/pyfme/utils/tests/test_utils.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4bc8df2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +__pycache__ +.cache + +*.py[cod] diff --git a/src/pyfme/__init__.py b/src/pyfme/__init__.py new file mode 100644 index 0000000..139597f --- /dev/null +++ b/src/pyfme/__init__.py @@ -0,0 +1,2 @@ + + diff --git a/src/aero/tests/test_aero.py b/src/pyfme/aero/tests/test_aero.py similarity index 100% rename from src/aero/tests/test_aero.py rename to src/pyfme/aero/tests/test_aero.py diff --git a/src/environment/tests/test_environment.py b/src/pyfme/aircrafts/aircraft.py similarity index 100% rename from src/environment/tests/test_environment.py rename to src/pyfme/aircrafts/aircraft.py diff --git a/src/models/tests/test_models.py b/src/pyfme/environment/tests/test_environment.py similarity index 100% rename from src/models/tests/test_models.py rename to src/pyfme/environment/tests/test_environment.py diff --git a/src/prop/tests/test_prop.py b/src/pyfme/models/tests/test_models.py similarity index 100% rename from src/prop/tests/test_prop.py rename to src/pyfme/models/tests/test_models.py diff --git a/src/tests/test_PyFME.py b/src/pyfme/prop/tests/test_prop.py similarity index 100% rename from src/tests/test_PyFME.py rename to src/pyfme/prop/tests/test_prop.py diff --git a/src/utils/tests/test_utils.py b/src/pyfme/tests/test_PyFME.py similarity index 100% rename from src/utils/tests/test_utils.py rename to src/pyfme/tests/test_PyFME.py diff --git a/src/pyfme/utils/__init__.py b/src/pyfme/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/pyfme/utils/coordinates.py b/src/pyfme/utils/coordinates.py new file mode 100644 index 0000000..3aabec4 --- /dev/null +++ b/src/pyfme/utils/coordinates.py @@ -0,0 +1,161 @@ +# -*- coding: utf-8 -*- +""" +Frames of Reference orientation functions +""" + +import numpy as np + +# Reassing for readability +sin = np.sin +cos = np.cos + + +def check_theta_phi_psi_range(theta, phi, psi): + """Check theta, phi, psi values are inside the defined range. This + comprobation can also detect if the value of the angle is in degrees in + some cases. + """ + + theta_min, theta_max = (-np.pi/2, np.pi/2) + phi_min, phi_max = (-np.pi, np.pi) + psi_min, psi_max = (0, 2 * np.pi) + + if not (theta_min <= theta <= theta_max): + raise ValueError('Theta value is not inside correct range') + elif not (phi_min <= phi <= phi_max): + raise ValueError('Phi value is not inside correct range') + elif not (psi_min <= phi <= psi_max): + raise ValueError('Psi value is not inside correct range') + + + +def body2hor(body_coords, theta, phi, psi): + """Transforms the vector coordinates in body frame of reference to local + horizon frame of reference. + + Parameters + ---------- + body_coords : array_like + 3 dimensional vector with (x,y,z) coordinates in body axes. + theta : float + Pitch (or elevation) angle (rad). + phi : float + Bank angle (rad). + psi : float + Yaw (or azimuth) angle (rad) + + Returns + ------- + hor_coords : array_like + 3 dimensional vector with (x,y,z) coordinates in local horizon axes. + + Raises + ------ + ValueError + If the values of the euler angles are outside the proper ranges. + + See Also + -------- + `hor2body` function. + + Notes + ----- + See [1] or [2] for frame of reference definition. + Note that in order to avoid ambiguities ranges in angles are limited to: + + * -pi/2 <= theta <= pi/2 + * -pi <= phi <= pi + * 0 <= psi <= 2*pi + + References + ---------- + .. [1] B. Etkin, "Dynamics of Atmospheric Flight," Courier Corporation, + pp. 104-120, 2012. + .. [2] Gómez Tierno, M.A. et al, "Mecánica del Vuelo," Garceta, pp. 1-12, + 2012 + """ + + check_theta_phi_psi_range(theta, phi, psi) + + # Transformation matrix from body to local horizon + Lhb = np.array([ + [cos(theta) * cos(psi), + sin(phi) * sin(theta) * cos(psi) - cos(phi) * sin(psi), + cos(phi) * sin(theta) * cos(psi) + sin(phi) * sin(psi)], + [cos(theta) * sin(psi), + sin(phi) * sin(theta) * sin(psi) + cos(phi) * cos(psi), + cos(phi) * sin(theta) * sin(psi) - sin(phi) * cos(psi)], + [- sin(theta), + sin(phi) * cos(theta), + cos(phi) * cos(theta)] + ]) + + hor_coords = Lhb.dot(body_coords) + + return hor_coords + + +def hor2body(hor_coords, theta, phi, psi): + """Transforms the vector coordinates in local horizon frame of reference + to body frame of reference. + + Parameters + ---------- + hor_coords : array_like + 3 dimensional vector with (x,y,z) coordinates in local horizon axes. + theta : float + Pitch (or elevation) angle (rad). + phi : float + Bank angle (rad). + psi : float + Yaw (or azimuth) angle (rad) + + Returns + ------- + body_coords : array_like + 3 dimensional vector with (x,y,z) coordinates in body axes. + + Raises + ------ + ValueError + If the values of the euler angles are outside the proper ranges. + + See Also + -------- + `body2hor` function. + + Notes + ----- + See [1] or [2] for frame of reference definition. + Note that in order to avoid ambiguities ranges in angles are limited to: + + * -pi/2 <= theta <= pi/2 + * -pi <= phi <= pi + * 0 <= psi <= 2*pi + + References + ---------- + .. [1] B. Etkin, "Dynamics of Atmospheric Flight," Courier Corporation, + pp. 104-120, 2012. + .. [2] Gómez Tierno, M.A. et al, "Mecánica del Vuelo," Garceta, pp. 1-12, + 2012 + """ + + check_theta_phi_psi_range(theta, phi, psi) + + # Transformation matrix local horizon to body + Lbh = np.array([ + [cos(theta) * cos(psi), + cos(theta) * sin(psi), + - sin(theta)], + [sin(phi) * sin(theta) * cos(psi) - cos(phi) * sin(psi), + sin(phi) * sin(theta) * sin(psi) + cos(phi) * cos(psi), + sin(phi) * cos(theta)], + [cos(phi) * sin(theta) * cos(psi) + sin(phi) * sin(psi), + cos(phi) * sin(theta) * sin(psi) - sin(phi) * cos(psi), + cos(phi) * cos(theta)] + ]) + + body_coords = Lbh.dot(hor_coords) + + return body_coords diff --git a/src/pyfme/utils/tests/__init__.py b/src/pyfme/utils/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/pyfme/utils/tests/test_utils.py b/src/pyfme/utils/tests/test_utils.py new file mode 100644 index 0000000..a895e5d --- /dev/null +++ b/src/pyfme/utils/tests/test_utils.py @@ -0,0 +1,92 @@ +# -*- coding: utf-8 -*- +""" +Frames of Reference orientation test functions +""" +import pytest + +import numpy as np +from numpy.testing import (assert_equal, assert_almost_equal, + assert_array_equal, assert_array_almost_equal) + + +from pyfme.utils.coordinates import (body2hor, hor2body, + check_theta_phi_psi_range) + + +def test_check_theta_phi_psi_range(): + + wrong_values = (3 * np.pi, - 3 * np.pi) + + for value in wrong_values: + # 0 is always a correct value + angles = [0, 0, 0] + for ii in range(3): + angles[ii] = value + with pytest.raises(ValueError): + check_theta_phi_psi_range(*angles) + + +def test_body2hor(): + + # Test with a pitch rotation + vector_body = np.array([1, 1, 1]) + theta, phi, psi = np.deg2rad(45), 0, 0 + + vector_hor = body2hor(vector_body, theta, phi, psi) + + vector_hor_expected = np.array([2 * 0.70710678118654757, 1, 0]) + + assert_array_almost_equal(vector_hor, vector_hor_expected) + + # Test with a roll rotation + vector_body = np.array([1, 1, 1]) + theta, phi, psi = 0, np.deg2rad(45), 0 + + vector_hor = body2hor(vector_body, theta, phi, psi) + + vector_hor_expected = np.array([1, 0, 2 * 0.70710678118654757]) + + assert_array_almost_equal(vector_hor, vector_hor_expected) + + # Test with a yaw rotation + vector_body = np.array([1, 1, 1]) + theta, phi, psi = 0, 0, np.deg2rad(45) + + vector_hor = body2hor(vector_body, theta, phi, psi) + + vector_hor_expected = np.array([0, 2 * 0.70710678118654757, 1]) + + assert_array_almost_equal(vector_hor, vector_hor_expected) + + +def test_hor2body(): + + # Test with a pitch rotation + vector_hor = np.array([2 * 0.70710678118654757, 1, 0]) + theta, phi, psi = np.deg2rad(45), 0, 0 + + vector_body_expected = np.array([1, 1, 1]) + + vector_body = hor2body(vector_hor, theta, phi, psi) + + assert_array_almost_equal(vector_body, vector_body_expected) + + # Test with a roll rotation + vector_hor = np.array([1, 0, 2 * 0.70710678118654757]) + theta, phi, psi = 0, np.deg2rad(45), 0 + + vector_body_expected = np.array([1, 1, 1]) + + vector_body = hor2body(vector_hor, theta, phi, psi) + + assert_array_almost_equal(vector_body, vector_body_expected) + + # Test with a yaw rotation + vector_hor = np.array([0, 2 * 0.70710678118654757, 1]) + theta, phi, psi = 0, 0, np.deg2rad(45) + + vector_body_expected = np.array([1, 1, 1]) + + vector_body = hor2body(vector_hor, theta, phi, psi) + + assert_array_almost_equal(vector_body, vector_body_expected)