From cd2cb106f317f6b4e4d3a7de249f7860f3b0ac64 Mon Sep 17 00:00:00 2001 From: patrick Date: Sat, 7 Aug 2021 15:47:20 -0300 Subject: [PATCH 01/14] Add initial tests --- tests/conftest.py | 19 +++++++++++++++++++ tests/test_solidmotor.py | 19 ++++++++++++++++++- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index e446d0a1..70601255 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,5 @@ import pytest +from rocketpy import SolidMotor def pytest_addoption(parser): @@ -11,6 +12,24 @@ def pytest_configure(config): config.addinivalue_line("markers", "slow: mark test as slow to run") +@pytest.fixture +def solid_motor(): + example_motor = SolidMotor( + thrustSource="data/motors/Cesaroni_M1670.eng", + burnOut=3.9, + grainNumber=5, + grainSeparation=5 / 1000, + grainDensity=1815, + grainOuterRadius=33 / 1000, + grainInitialInnerRadius=15 / 1000, + grainInitialHeight=120 / 1000, + nozzleRadius=33 / 1000, + throatRadius=11 / 1000, + interpolationMethod="linear", + ) + return example_motor + + def pytest_collection_modifyitems(config, items): if config.getoption("--runslow"): # --runslow given in cli: do not skip slow tests diff --git a/tests/test_solidmotor.py b/tests/test_solidmotor.py index 8f545bd9..34c62baf 100644 --- a/tests/test_solidmotor.py +++ b/tests/test_solidmotor.py @@ -1,8 +1,9 @@ from unittest.mock import patch +import numpy as np import pytest -from rocketpy import Environment, SolidMotor, Rocket, Flight +from rocketpy import SolidMotor, Function @patch("matplotlib.pyplot.show") @@ -22,3 +23,19 @@ def test_motor(mock_show): ) assert example_motor.allInfo() == None + +@patch("matplotlib.pyplot.show") +def test_initilize_motor_correctly(mock_plt, solid_motor): + grain_vol = 0.12 * (np.pi * (0.033**2 - 0.015**2)) + grain_mass = grain_vol * 1815 + + assert solid_motor.maxThrust == 2200.0 + assert solid_motor.maxThrustTime == 0.15 + assert solid_motor.burnOutTime == 3.9 + assert solid_motor.totalImpulse == solid_motor.thrust.integral(0, 3.9) + assert solid_motor.averageThrust == solid_motor.thrust.integral(0, 3.9) / 3.9 + assert solid_motor.grainInitialVolume == grain_vol + assert solid_motor.grainInitialMass == grain_mass + assert solid_motor.propellantInitialMass == 5 * grain_mass + assert solid_motor.exaust_velocity == solid_motor.thrust.integral(0, 3.9) / (5 * grain_mass) + From 90207d69b1b077bf5e286493ac33cbac8f6d373b Mon Sep 17 00:00:00 2001 From: patrick Date: Sat, 7 Aug 2021 16:34:31 -0300 Subject: [PATCH 02/14] Added SolidMotor tests #1 --- rocketpy/SolidMotor.py | 48 +++++++++++++++++++++++++--------------- tests/test_solidmotor.py | 11 ++++++--- 2 files changed, 38 insertions(+), 21 deletions(-) diff --git a/rocketpy/SolidMotor.py b/rocketpy/SolidMotor.py index aa22aed7..75d6a656 100644 --- a/rocketpy/SolidMotor.py +++ b/rocketpy/SolidMotor.py @@ -435,6 +435,10 @@ def evaluateMass(self): # Return Mass Function return self.mass + @property + def throatArea(self): + return np.pi * self.throatRadius ** 2 + def evaluateGeometry(self): """Calculates grain inner radius and grain height as a function of time by assuming that every propellant mass @@ -464,10 +468,10 @@ def evaluateGeometry(self): # Define time mesh t = self.massDot.source[:, 0] - # Define system of differential equations density = self.grainDensity rO = self.grainOuterRadius + # Define system of differential equations def geometryDot(y, t): grainMassDot = self.massDot(t) / self.grainNumber rI, h = y @@ -497,25 +501,37 @@ def geometryDot(y, t): ) # Create functions describing burn rate, Kn and burn area - # Burn Area + self.evaluateBurnArea() + self.evaluateKn() + self.evaluateBurnRate() + + return [self.grainInnerRadius, self.grainHeight] + + def evaluateBurnArea(self): self.burnArea = ( - 2 - * np.pi - * ( - self.grainOuterRadius ** 2 - - self.grainInnerRadius ** 2 - + self.grainInnerRadius * self.grainHeight - ) - * self.grainNumber + 2 + * np.pi + * ( + self.grainOuterRadius ** 2 + - self.grainInnerRadius ** 2 + + self.grainInnerRadius * self.grainHeight + ) + * self.grainNumber ) self.burnArea.setOutputs("Burn Area (m2)") - # Kn - throatArea = np.pi * (self.throatRadius) ** 2 + return self.burnArea + + def evaluateBurnRate(self): + self.burnRate = (-1) * self.massDot / (self.burnArea * self.grainDensity) + self.burnRate.setOutputs("Burn Rate (m/s)") + return self.burnRate + + def evaluateKn(self): KnSource = ( np.concatenate( ( [self.grainInnerRadius.source[:, 1]], - [self.burnArea.source[:, 1] / throatArea], + [self.burnArea.source[:, 1] / self.throatArea], ) ).transpose() ).tolist() @@ -526,11 +542,7 @@ def geometryDot(y, t): self.interpolate, "constant", ) - # Burn Rate - self.burnRate = (-1) * self.massDot / (self.burnArea * self.grainDensity) - self.burnRate.setOutputs("Burn Rate (m/s)") - - return [self.grainInnerRadius, self.grainHeight] + return self.Kn def evaluateInertia(self): """Calculates propellant inertia I, relative to directions diff --git a/tests/test_solidmotor.py b/tests/test_solidmotor.py index 34c62baf..14bf2dfb 100644 --- a/tests/test_solidmotor.py +++ b/tests/test_solidmotor.py @@ -24,8 +24,8 @@ def test_motor(mock_show): assert example_motor.allInfo() == None -@patch("matplotlib.pyplot.show") -def test_initilize_motor_correctly(mock_plt, solid_motor): + +def test_initilize_motor_correctly(solid_motor): grain_vol = 0.12 * (np.pi * (0.033**2 - 0.015**2)) grain_mass = grain_vol * 1815 @@ -37,5 +37,10 @@ def test_initilize_motor_correctly(mock_plt, solid_motor): assert solid_motor.grainInitialVolume == grain_vol assert solid_motor.grainInitialMass == grain_mass assert solid_motor.propellantInitialMass == 5 * grain_mass - assert solid_motor.exaust_velocity == solid_motor.thrust.integral(0, 3.9) / (5 * grain_mass) + assert solid_motor.exhaustVelocity == solid_motor.thrust.integral(0, 3.9) / (5 * grain_mass) + +def test_grain_geometry_progession(solid_motor): + assert np.allclose(solid_motor.grainInnerRadius.getSource()[-1][-1], solid_motor.grainOuterRadius) + assert solid_motor.grainInnerRadius.getSource()[0][-1] < solid_motor.grainInnerRadius.getSource()[-1][-1] + assert solid_motor.grainHeight.getSource()[0][-1] > solid_motor.grainHeight.getSource()[-1][-1] From b0bcb629d397253f85a078be10b2d7eab9ae368b Mon Sep 17 00:00:00 2001 From: patrick Date: Sun, 8 Aug 2021 01:33:48 -0300 Subject: [PATCH 03/14] Tests: Added docs for burnArea and burnRate alongside with unit tests --- rocketpy/SolidMotor.py | 45 ++++++++++++++++++++++++++-------------- tests/test_solidmotor.py | 25 ++++++++++++++++++++-- 2 files changed, 52 insertions(+), 18 deletions(-) diff --git a/rocketpy/SolidMotor.py b/rocketpy/SolidMotor.py index 75d6a656..e3939f4b 100644 --- a/rocketpy/SolidMotor.py +++ b/rocketpy/SolidMotor.py @@ -228,7 +228,6 @@ def __init__( self.grainInitialInnerRadius = grainInitialInnerRadius self.grainInitialHeight = grainInitialHeight # Other quantities that will be computed - self.exhaustVelocity = None self.massDot = None self.mass = None self.grainInnerRadius = None @@ -259,7 +258,6 @@ def __init__( self.grainInitialMass = self.grainDensity * self.grainInitialVolume self.propellantInitialMass = self.grainNumber * self.grainInitialMass # Dynamic quantities - self.evaluateExhaustVelocity() self.evaluateMassDot() self.evaluateMass() self.evaluateGeometry() @@ -339,7 +337,8 @@ def evaluateTotalImpulse(self): # Return total impulse return self.totalImpulse - def evaluateExhaustVelocity(self): + @property + def exhaustVelocity(self): """Calculates and returns exhaust velocity by assuming it as a constant. The formula used is total impulse/propellant initial mass. The value is also stored in @@ -354,15 +353,7 @@ def evaluateExhaustVelocity(self): self.exhaustVelocity : float Constant gas exhaust velocity of the motor. """ - # Calculate total impulse if not yet done so - if self.totalImpulse is None: - self.evaluateTotalImpulse() - - # Calculate exhaust velocity - self.exhaustVelocity = self.totalImpulse / self.propellantInitialMass - - # Return exhaust velocity - return self.exhaustVelocity + return self.totalImpulse / self.propellantInitialMass def evaluateMassDot(self): """Calculates and returns the time derivative of propellant @@ -381,10 +372,6 @@ def evaluateMassDot(self): Time derivative of total propellant mas as a function of time. """ - # Calculate exhaust velocity if not done so already - if self.exhaustVelocity is None: - self.evaluateExhaustVelocity() - # Create mass dot Function self.massDot = self.thrust / (-self.exhaustVelocity) self.massDot.setOutputs("Mass Dot (kg/s)") @@ -508,6 +495,19 @@ def geometryDot(y, t): return [self.grainInnerRadius, self.grainHeight] def evaluateBurnArea(self): + """Calculates the BurnArea of the grain for + each time. Assuming that the grains are cylindrical + BATES grains. + + Parameters + ---------- + None + + Returns + ------- + burnArea : Function + Function representing the burn area progression with the time. + """ self.burnArea = ( 2 * np.pi @@ -522,6 +522,19 @@ def evaluateBurnArea(self): return self.burnArea def evaluateBurnRate(self): + """Calculates the BurnRate with respect to time. + This evaluation assumes that it was already + calculated the massDot, burnArea timeseries. + + Parameters + ---------- + None + + Returns + ------- + burnRate : Function + Rate of progression of the inner radius during the combustion. + """ self.burnRate = (-1) * self.massDot / (self.burnArea * self.grainDensity) self.burnRate.setOutputs("Burn Rate (m/s)") return self.burnRate diff --git a/tests/test_solidmotor.py b/tests/test_solidmotor.py index 14bf2dfb..8fb14955 100644 --- a/tests/test_solidmotor.py +++ b/tests/test_solidmotor.py @@ -25,7 +25,7 @@ def test_motor(mock_show): assert example_motor.allInfo() == None -def test_initilize_motor_correctly(solid_motor): +def test_initilize_motor_asserts_dynamic_values(solid_motor): grain_vol = 0.12 * (np.pi * (0.033**2 - 0.015**2)) grain_mass = grain_vol * 1815 @@ -40,7 +40,28 @@ def test_initilize_motor_correctly(solid_motor): assert solid_motor.exhaustVelocity == solid_motor.thrust.integral(0, 3.9) / (5 * grain_mass) -def test_grain_geometry_progession(solid_motor): +def test_grain_geometry_progession_asserts_extreme_values(solid_motor): assert np.allclose(solid_motor.grainInnerRadius.getSource()[-1][-1], solid_motor.grainOuterRadius) assert solid_motor.grainInnerRadius.getSource()[0][-1] < solid_motor.grainInnerRadius.getSource()[-1][-1] assert solid_motor.grainHeight.getSource()[0][-1] > solid_motor.grainHeight.getSource()[-1][-1] + + +def test_mass_curve_asserts_extreme_values(solid_motor): + grain_vol = 0.12 * (np.pi * (0.033 ** 2 - 0.015 ** 2)) + grain_mass = grain_vol * 1815 + + assert np.allclose(solid_motor.mass.getSource()[-1][-1], 0) + assert np.allclose(solid_motor.mass.getSource()[0][-1], 5 * grain_mass) + + +def test_burn_area_asserts_extreme_values(solid_motor): + initial_burn_area = 2 * np.pi * ( + solid_motor.grainOuterRadius**2 - solid_motor.grainInitialInnerRadius**2 + + solid_motor.grainInitialInnerRadius * solid_motor.grainInitialHeight + ) * 5 + final_burn_area = 2 * np.pi * ( + solid_motor.grainInnerRadius.getSource()[-1][-1] * solid_motor.grainHeight.getSource()[-1][-1] + ) * 5 + + assert np.allclose(solid_motor.burnArea.getSource()[0][-1], initial_burn_area) + assert np.allclose(solid_motor.burnArea.getSource()[-1][-1], final_burn_area) From ce8ee5f02623806d3f37a47033dcd71a8d64c89a Mon Sep 17 00:00:00 2001 From: patrick Date: Sun, 8 Aug 2021 10:00:10 -0300 Subject: [PATCH 04/14] Update makefile --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 5de27676..b89cf095 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,11 @@ test: - pytest tests -vv + python -m pytest tests -vv tests: test coverage: - pytest --cov=rocketpy tests -vv + python -m pytest --cov=rocketpy tests -vv install: python -m pip install --upgrade pip From ccfa5bb05f547f8378e124f62975d28f59ba815b Mon Sep 17 00:00:00 2001 From: patrick Date: Sun, 8 Aug 2021 10:00:33 -0300 Subject: [PATCH 05/14] Improved readbility of InertiaZDot --- rocketpy/SolidMotor.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/rocketpy/SolidMotor.py b/rocketpy/SolidMotor.py index e3939f4b..eb2cf17c 100644 --- a/rocketpy/SolidMotor.py +++ b/rocketpy/SolidMotor.py @@ -626,8 +626,9 @@ def evaluateInertia(self): # Inertia Z Dot self.inertiaZDot = ( - (1 / 2.0) * (self.massDot * self.grainOuterRadius ** 2) - + (1 / 2.0) * (self.massDot * self.grainInnerRadius ** 2) + (1 / 2.0) + * self.massDot + * (self.grainOuterRadius ** 2 + self.grainInnerRadius ** 2) + self.mass * self.grainInnerRadius * self.burnRate ) self.inertiaZDot.setOutputs("Propellant Inertia Z Dot (kg*m2/s)") From d3fc7dafcc77a85b0110797a2a359a5b7c8cc272 Mon Sep 17 00:00:00 2001 From: patrick Date: Sun, 8 Aug 2021 10:03:34 -0300 Subject: [PATCH 06/14] Add inertiaZDot to __init__ --- rocketpy/SolidMotor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rocketpy/SolidMotor.py b/rocketpy/SolidMotor.py index eb2cf17c..847fb4b9 100644 --- a/rocketpy/SolidMotor.py +++ b/rocketpy/SolidMotor.py @@ -238,7 +238,7 @@ def __init__( self.inertiaI = None self.inertiaIDot = None self.inertiaZ = None - self.inertiaDot = None + self.inertiaZDot = None self.maxThrust = None self.maxThrustTime = None self.averageThrust = None @@ -611,7 +611,7 @@ def evaluateInertia(self): ) # Calculate inertia I dot for all grains - self.inertiaIDot = grainNumber * (grainInertiaIDot) + grainMassDot * np.sum( + self.inertiaIDot = grainNumber * grainInertiaIDot + grainMassDot * np.sum( d ** 2 ) self.inertiaIDot.setOutputs("Propellant Inertia I Dot (kg*m2/s)") From 8989d4e4050eb7bcb507be5452919d496df3bbde Mon Sep 17 00:00:00 2001 From: patrick Date: Sun, 8 Aug 2021 10:26:41 -0300 Subject: [PATCH 07/14] Add coverage html report --- Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile b/Makefile index b89cf095..4c66b4b8 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,9 @@ tests: coverage: python -m pytest --cov=rocketpy tests -vv +coverage-report: + python -m pytest --cov=rocketpy tests -vv --cov-report html + install: python -m pip install --upgrade pip pip install -r requirements.txt From 23c9790211393f1c4bc119e4a3420460b5aaaef2 Mon Sep 17 00:00:00 2001 From: patrick Date: Sun, 8 Aug 2021 10:28:46 -0300 Subject: [PATCH 08/14] Add tests for inertia Z and I --- rocketpy/SolidMotor.py | 4 ++-- tests/test_solidmotor.py | 35 ++++++++++++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/rocketpy/SolidMotor.py b/rocketpy/SolidMotor.py index 847fb4b9..d0ff24b5 100644 --- a/rocketpy/SolidMotor.py +++ b/rocketpy/SolidMotor.py @@ -590,11 +590,11 @@ def evaluateInertia(self): # Calculate each grain's distance d to propellant center of mass initialValue = (grainNumber - 1) / 2 - d = np.linspace(-initialValue, initialValue, self.grainNumber) + d = np.linspace(-initialValue, initialValue, grainNumber) d = d * (self.grainInitialHeight + self.grainSeparation) # Calculate inertia for all grains - self.inertiaI = grainNumber * (grainInertiaI) + grainMass * np.sum(d ** 2) + self.inertiaI = grainNumber * grainInertiaI + grainMass * np.sum(d ** 2) self.inertiaI.setOutputs("Propellant Inertia I (kg*m2)") # Inertia I Dot diff --git a/tests/test_solidmotor.py b/tests/test_solidmotor.py index 8fb14955..682f8af8 100644 --- a/tests/test_solidmotor.py +++ b/tests/test_solidmotor.py @@ -3,7 +3,7 @@ import numpy as np import pytest -from rocketpy import SolidMotor, Function +from rocketpy import SolidMotor @patch("matplotlib.pyplot.show") @@ -65,3 +65,36 @@ def test_burn_area_asserts_extreme_values(solid_motor): assert np.allclose(solid_motor.burnArea.getSource()[0][-1], initial_burn_area) assert np.allclose(solid_motor.burnArea.getSource()[-1][-1], final_burn_area) + + +def test_evaluate_inertia_I_asserts_extreme_values(solid_motor): + grain_vol = 0.12 * (np.pi * (0.033 ** 2 - 0.015 ** 2)) + grain_mass = grain_vol * 1815 + + grainNumber = 5 + grainInertiaI_initial = grain_mass * ( + (1 / 4) * (0.033 ** 2 + 0.016 ** 2) + + (1 / 12) * 0.12 ** 2 + ) + + initialValue = (grainNumber - 1) / 2 + d = np.linspace(-initialValue, initialValue, grainNumber) + d = d * (0.12 + 0.005) + + inertiaI_initial = grainNumber * grainInertiaI_initial + grain_mass * np.sum(d ** 2) + + assert np.allclose(solid_motor.inertiaI.getSource()[0][-1], inertiaI_initial, atol=0.01) + assert np.allclose(solid_motor.inertiaI.getSource()[-1][-1], 0, atol=1e-16) + + +def test_evaluate_inertia_Z_asserts_extreme_values(solid_motor): + grain_vol = 0.12 * (np.pi * (0.033 ** 2 - 0.015 ** 2)) + grain_mass = grain_vol * 1815 + + grainInertiaZ_initial = \ + grain_mass \ + * (1 / 2.0) \ + * (0.016 ** 2 + 0.033 ** 2) + + assert np.allclose(solid_motor.inertiaZ.getSource()[0][-1], grainInertiaZ_initial, atol=0.01) + assert np.allclose(solid_motor.inertiaZ.getSource()[-1][-1], 0, atol=1e-16) From fc6ab9efec7f8a93356b06366e3bcef0d5b1af58 Mon Sep 17 00:00:00 2001 From: patrick Date: Sun, 8 Aug 2021 11:28:08 -0300 Subject: [PATCH 09/14] Add tests for reshaped thrust curve --- rocketpy/SolidMotor.py | 3 +- tests/fixtures/motor/Cesaroni_M1670.eng | 18 ++++ .../fixtures/motor/Cesaroni_M1670_shifted.eng | 16 ++++ tests/test_solidmotor.py | 92 ++++++++++++++++++- 4 files changed, 126 insertions(+), 3 deletions(-) create mode 100644 tests/fixtures/motor/Cesaroni_M1670.eng create mode 100644 tests/fixtures/motor/Cesaroni_M1670_shifted.eng diff --git a/rocketpy/SolidMotor.py b/rocketpy/SolidMotor.py index d0ff24b5..41279275 100644 --- a/rocketpy/SolidMotor.py +++ b/rocketpy/SolidMotor.py @@ -295,7 +295,6 @@ def reshapeThrustCurve( # Retrieve current thrust curve data points timeArray = self.thrust.source[:, 0] thrustArray = self.thrust.source[:, 1] - # Move start to time = 0 if startAtZero and timeArray[0] != 0: timeArray = timeArray - timeArray[0] @@ -715,7 +714,7 @@ def exportEng(self, fileName, motorName): ) # Write thrust curve data points - for item in self.thrust.source[:-1, :]: + for item in self.thrust.source[:-1, :][1:]: time = item[0] thrust = item[1] file.write("{:.4f} {:.3f}\n".format(time, thrust)) diff --git a/tests/fixtures/motor/Cesaroni_M1670.eng b/tests/fixtures/motor/Cesaroni_M1670.eng new file mode 100644 index 00000000..3cb9e1bb --- /dev/null +++ b/tests/fixtures/motor/Cesaroni_M1670.eng @@ -0,0 +1,18 @@ +M1670-BS 75 757 0 3.101 5.231 CTI +0.055 100 +0.092 1500 +0.1 2000 +0.15 2200 +0.2 1800 +0.5 1950 +1 2034 +;this motor is COTS +1.5 2000 +2 1900 +2.5 1760 +2.9 1700 +3 1650 +;3.9 burnTime +3.3 530 +3.4 350 +3.9 0 diff --git a/tests/fixtures/motor/Cesaroni_M1670_shifted.eng b/tests/fixtures/motor/Cesaroni_M1670_shifted.eng new file mode 100644 index 00000000..f3a229f1 --- /dev/null +++ b/tests/fixtures/motor/Cesaroni_M1670_shifted.eng @@ -0,0 +1,16 @@ +M1670-BS 75 757 0 3.101 5.231 CTI +0.155 100 +0.192 1500 +0.2 2000 +0.25 2200 +0.3 1800 +0.6 1950 +1.1 2034 +1.6 2000 +2.1 1900 +2.6 1760 +3 1700 +3.1 1650 +3.4 530 +3.5 350 +4.0 0 diff --git a/tests/test_solidmotor.py b/tests/test_solidmotor.py index 682f8af8..564d159e 100644 --- a/tests/test_solidmotor.py +++ b/tests/test_solidmotor.py @@ -1,4 +1,5 @@ from unittest.mock import patch +import os import numpy as np import pytest @@ -9,7 +10,7 @@ @patch("matplotlib.pyplot.show") def test_motor(mock_show): example_motor = SolidMotor( - thrustSource="data/motors/Cesaroni_M1670.eng", + thrustSource="tests/fixtures/motor/Cesaroni_M1670.eng", burnOut=3.9, grainNumber=5, grainSeparation=5 / 1000, @@ -98,3 +99,92 @@ def test_evaluate_inertia_Z_asserts_extreme_values(solid_motor): assert np.allclose(solid_motor.inertiaZ.getSource()[0][-1], grainInertiaZ_initial, atol=0.01) assert np.allclose(solid_motor.inertiaZ.getSource()[-1][-1], 0, atol=1e-16) + + +def tests_import_eng_asserts_read_values_correctly(solid_motor): + comments, description, dataPoints = solid_motor.importEng("tests/fixtures/motor/Cesaroni_M1670.eng") + + assert comments == [';this motor is COTS\n', ';3.9 burnTime\n'] + assert description == ['M1670-BS', '75', '757', '0', '3.101', '5.231', 'CTI', '\n'] + assert dataPoints == [ + [0, 0], + [0.055, 100.0], + [0.092, 1500.0], + [0.1, 2000.0], + [0.15, 2200.0], + [0.2, 1800.0], + [0.5, 1950.0], + [1.0, 2034.0], + [1.5, 2000.0], + [2.0, 1900.0], + [2.5, 1760.0], + [2.9, 1700.0], + [3.0, 1650.0], + [3.3, 530.0], + [3.4, 350.0], + [3.9, 0.0] + ] + + +def tests_export_eng_asserts_exported_values_correct(solid_motor): + grain_vol = 0.12 * (np.pi * (0.033 ** 2 - 0.015 ** 2)) + grain_mass = grain_vol * 1815 * 5 + + solid_motor.exportEng(fileName='tests/solid_motor.eng', motorName='test_motor') + comments, description, dataPoints = solid_motor.importEng('tests/solid_motor.eng') + os.remove('tests/solid_motor.eng') + + assert comments == [] + assert description == [ + 'test_motor', + '{:3.1f}'.format(2000 * 0.033), + '{:3.1f}'.format(1000 * 5 * (0.12 + 0.005)), + '0', + '{:2.3}'.format(grain_mass), + '{:2.3}'.format(grain_mass), + 'PJ', + '\n' + ] + + assert dataPoints == [ + [0, 0], + [0.055, 100.0], + [0.092, 1500.0], + [0.1, 2000.0], + [0.15, 2200.0], + [0.2, 1800.0], + [0.5, 1950.0], + [1.0, 2034.0], + [1.5, 2000.0], + [2.0, 1900.0], + [2.5, 1760.0], + [2.9, 1700.0], + [3.0, 1650.0], + [3.3, 530.0], + [3.4, 350.0], + [3.9, 0.0] + ] + + +def test_reshape_thrust_curve_asserts_resultant_thrust_curve_correct(): + example_motor = SolidMotor( + thrustSource="tests/fixtures/motor/Cesaroni_M1670_shifted.eng", + burnOut=3.9, + grainNumber=5, + grainSeparation=5 / 1000, + grainDensity=1815, + grainOuterRadius=33 / 1000, + grainInitialInnerRadius=15 / 1000, + grainInitialHeight=120 / 1000, + nozzleRadius=33 / 1000, + throatRadius=11 / 1000, + reshapeThrustCurve=(5, 3000), + interpolationMethod="linear", + ) + + thrust_reshaped = example_motor.thrust.getSource() + assert thrust_reshaped[1][0] == 0.155 * (5/4) + assert thrust_reshaped[-1][0] == 5 + + assert thrust_reshaped[1][1] == 100 * (3000/7539.1875) + assert thrust_reshaped[7][1] == 2034 * (3000/7539.1875) From ae277e314f293f6dddf21d8c18dc5d06fafbe464 Mon Sep 17 00:00:00 2001 From: patrick Date: Sun, 8 Aug 2021 11:29:42 -0300 Subject: [PATCH 10/14] Apply linter --- rocketpy/SolidMotor.py | 25 +++++----- tests/test_solidmotor.py | 102 ++++++++++++++++++++++++--------------- 2 files changed, 74 insertions(+), 53 deletions(-) diff --git a/rocketpy/SolidMotor.py b/rocketpy/SolidMotor.py index 41279275..f4d62268 100644 --- a/rocketpy/SolidMotor.py +++ b/rocketpy/SolidMotor.py @@ -508,14 +508,14 @@ def evaluateBurnArea(self): Function representing the burn area progression with the time. """ self.burnArea = ( - 2 - * np.pi - * ( - self.grainOuterRadius ** 2 - - self.grainInnerRadius ** 2 - + self.grainInnerRadius * self.grainHeight - ) - * self.grainNumber + 2 + * np.pi + * ( + self.grainOuterRadius ** 2 + - self.grainInnerRadius ** 2 + + self.grainInnerRadius * self.grainHeight + ) + * self.grainNumber ) self.burnArea.setOutputs("Burn Area (m2)") return self.burnArea @@ -624,12 +624,9 @@ def evaluateInertia(self): self.inertiaZ.setOutputs("Propellant Inertia Z (kg*m2)") # Inertia Z Dot - self.inertiaZDot = ( - (1 / 2.0) - * self.massDot - * (self.grainOuterRadius ** 2 + self.grainInnerRadius ** 2) - + self.mass * self.grainInnerRadius * self.burnRate - ) + self.inertiaZDot = (1 / 2.0) * self.massDot * ( + self.grainOuterRadius ** 2 + self.grainInnerRadius ** 2 + ) + self.mass * self.grainInnerRadius * self.burnRate self.inertiaZDot.setOutputs("Propellant Inertia Z Dot (kg*m2/s)") return [self.inertiaI, self.inertiaZ] diff --git a/tests/test_solidmotor.py b/tests/test_solidmotor.py index 564d159e..1176d6a5 100644 --- a/tests/test_solidmotor.py +++ b/tests/test_solidmotor.py @@ -27,7 +27,7 @@ def test_motor(mock_show): def test_initilize_motor_asserts_dynamic_values(solid_motor): - grain_vol = 0.12 * (np.pi * (0.033**2 - 0.015**2)) + grain_vol = 0.12 * (np.pi * (0.033 ** 2 - 0.015 ** 2)) grain_mass = grain_vol * 1815 assert solid_motor.maxThrust == 2200.0 @@ -38,13 +38,23 @@ def test_initilize_motor_asserts_dynamic_values(solid_motor): assert solid_motor.grainInitialVolume == grain_vol assert solid_motor.grainInitialMass == grain_mass assert solid_motor.propellantInitialMass == 5 * grain_mass - assert solid_motor.exhaustVelocity == solid_motor.thrust.integral(0, 3.9) / (5 * grain_mass) + assert solid_motor.exhaustVelocity == solid_motor.thrust.integral(0, 3.9) / ( + 5 * grain_mass + ) def test_grain_geometry_progession_asserts_extreme_values(solid_motor): - assert np.allclose(solid_motor.grainInnerRadius.getSource()[-1][-1], solid_motor.grainOuterRadius) - assert solid_motor.grainInnerRadius.getSource()[0][-1] < solid_motor.grainInnerRadius.getSource()[-1][-1] - assert solid_motor.grainHeight.getSource()[0][-1] > solid_motor.grainHeight.getSource()[-1][-1] + assert np.allclose( + solid_motor.grainInnerRadius.getSource()[-1][-1], solid_motor.grainOuterRadius + ) + assert ( + solid_motor.grainInnerRadius.getSource()[0][-1] + < solid_motor.grainInnerRadius.getSource()[-1][-1] + ) + assert ( + solid_motor.grainHeight.getSource()[0][-1] + > solid_motor.grainHeight.getSource()[-1][-1] + ) def test_mass_curve_asserts_extreme_values(solid_motor): @@ -56,13 +66,25 @@ def test_mass_curve_asserts_extreme_values(solid_motor): def test_burn_area_asserts_extreme_values(solid_motor): - initial_burn_area = 2 * np.pi * ( - solid_motor.grainOuterRadius**2 - solid_motor.grainInitialInnerRadius**2 + - solid_motor.grainInitialInnerRadius * solid_motor.grainInitialHeight - ) * 5 - final_burn_area = 2 * np.pi * ( - solid_motor.grainInnerRadius.getSource()[-1][-1] * solid_motor.grainHeight.getSource()[-1][-1] - ) * 5 + initial_burn_area = ( + 2 + * np.pi + * ( + solid_motor.grainOuterRadius ** 2 + - solid_motor.grainInitialInnerRadius ** 2 + + solid_motor.grainInitialInnerRadius * solid_motor.grainInitialHeight + ) + * 5 + ) + final_burn_area = ( + 2 + * np.pi + * ( + solid_motor.grainInnerRadius.getSource()[-1][-1] + * solid_motor.grainHeight.getSource()[-1][-1] + ) + * 5 + ) assert np.allclose(solid_motor.burnArea.getSource()[0][-1], initial_burn_area) assert np.allclose(solid_motor.burnArea.getSource()[-1][-1], final_burn_area) @@ -74,8 +96,7 @@ def test_evaluate_inertia_I_asserts_extreme_values(solid_motor): grainNumber = 5 grainInertiaI_initial = grain_mass * ( - (1 / 4) * (0.033 ** 2 + 0.016 ** 2) - + (1 / 12) * 0.12 ** 2 + (1 / 4) * (0.033 ** 2 + 0.016 ** 2) + (1 / 12) * 0.12 ** 2 ) initialValue = (grainNumber - 1) / 2 @@ -84,7 +105,9 @@ def test_evaluate_inertia_I_asserts_extreme_values(solid_motor): inertiaI_initial = grainNumber * grainInertiaI_initial + grain_mass * np.sum(d ** 2) - assert np.allclose(solid_motor.inertiaI.getSource()[0][-1], inertiaI_initial, atol=0.01) + assert np.allclose( + solid_motor.inertiaI.getSource()[0][-1], inertiaI_initial, atol=0.01 + ) assert np.allclose(solid_motor.inertiaI.getSource()[-1][-1], 0, atol=1e-16) @@ -92,20 +115,21 @@ def test_evaluate_inertia_Z_asserts_extreme_values(solid_motor): grain_vol = 0.12 * (np.pi * (0.033 ** 2 - 0.015 ** 2)) grain_mass = grain_vol * 1815 - grainInertiaZ_initial = \ - grain_mass \ - * (1 / 2.0) \ - * (0.016 ** 2 + 0.033 ** 2) + grainInertiaZ_initial = grain_mass * (1 / 2.0) * (0.016 ** 2 + 0.033 ** 2) - assert np.allclose(solid_motor.inertiaZ.getSource()[0][-1], grainInertiaZ_initial, atol=0.01) + assert np.allclose( + solid_motor.inertiaZ.getSource()[0][-1], grainInertiaZ_initial, atol=0.01 + ) assert np.allclose(solid_motor.inertiaZ.getSource()[-1][-1], 0, atol=1e-16) def tests_import_eng_asserts_read_values_correctly(solid_motor): - comments, description, dataPoints = solid_motor.importEng("tests/fixtures/motor/Cesaroni_M1670.eng") + comments, description, dataPoints = solid_motor.importEng( + "tests/fixtures/motor/Cesaroni_M1670.eng" + ) - assert comments == [';this motor is COTS\n', ';3.9 burnTime\n'] - assert description == ['M1670-BS', '75', '757', '0', '3.101', '5.231', 'CTI', '\n'] + assert comments == [";this motor is COTS\n", ";3.9 burnTime\n"] + assert description == ["M1670-BS", "75", "757", "0", "3.101", "5.231", "CTI", "\n"] assert dataPoints == [ [0, 0], [0.055, 100.0], @@ -122,7 +146,7 @@ def tests_import_eng_asserts_read_values_correctly(solid_motor): [3.0, 1650.0], [3.3, 530.0], [3.4, 350.0], - [3.9, 0.0] + [3.9, 0.0], ] @@ -130,20 +154,20 @@ def tests_export_eng_asserts_exported_values_correct(solid_motor): grain_vol = 0.12 * (np.pi * (0.033 ** 2 - 0.015 ** 2)) grain_mass = grain_vol * 1815 * 5 - solid_motor.exportEng(fileName='tests/solid_motor.eng', motorName='test_motor') - comments, description, dataPoints = solid_motor.importEng('tests/solid_motor.eng') - os.remove('tests/solid_motor.eng') + solid_motor.exportEng(fileName="tests/solid_motor.eng", motorName="test_motor") + comments, description, dataPoints = solid_motor.importEng("tests/solid_motor.eng") + os.remove("tests/solid_motor.eng") assert comments == [] assert description == [ - 'test_motor', - '{:3.1f}'.format(2000 * 0.033), - '{:3.1f}'.format(1000 * 5 * (0.12 + 0.005)), - '0', - '{:2.3}'.format(grain_mass), - '{:2.3}'.format(grain_mass), - 'PJ', - '\n' + "test_motor", + "{:3.1f}".format(2000 * 0.033), + "{:3.1f}".format(1000 * 5 * (0.12 + 0.005)), + "0", + "{:2.3}".format(grain_mass), + "{:2.3}".format(grain_mass), + "PJ", + "\n", ] assert dataPoints == [ @@ -162,7 +186,7 @@ def tests_export_eng_asserts_exported_values_correct(solid_motor): [3.0, 1650.0], [3.3, 530.0], [3.4, 350.0], - [3.9, 0.0] + [3.9, 0.0], ] @@ -183,8 +207,8 @@ def test_reshape_thrust_curve_asserts_resultant_thrust_curve_correct(): ) thrust_reshaped = example_motor.thrust.getSource() - assert thrust_reshaped[1][0] == 0.155 * (5/4) + assert thrust_reshaped[1][0] == 0.155 * (5 / 4) assert thrust_reshaped[-1][0] == 5 - assert thrust_reshaped[1][1] == 100 * (3000/7539.1875) - assert thrust_reshaped[7][1] == 2034 * (3000/7539.1875) + assert thrust_reshaped[1][1] == 100 * (3000 / 7539.1875) + assert thrust_reshaped[7][1] == 2034 * (3000 / 7539.1875) From e691415de31351ebbb50aca83a6494933bb69be0 Mon Sep 17 00:00:00 2001 From: patrick Date: Sun, 8 Aug 2021 13:37:21 -0300 Subject: [PATCH 11/14] Improve test code --- tests/test_solidmotor.py | 58 +++++++++++++++++++++++----------------- 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/tests/test_solidmotor.py b/tests/test_solidmotor.py index 1176d6a5..a08ee4cf 100644 --- a/tests/test_solidmotor.py +++ b/tests/test_solidmotor.py @@ -6,6 +6,15 @@ from rocketpy import SolidMotor +burnOut = 3.9 +grainNumber = 5 +grainSeparation = 5 / 1000 +grainDensity = 1815 +grainOuterRadius = 33 / 1000 +grainInitialInnerRadius = 15 / 1000 +grainInitialHeight = 120 / 1000 +nozzleRadius = 33 / 1000 +throatRadius = 11 / 1000 @patch("matplotlib.pyplot.show") def test_motor(mock_show): @@ -27,19 +36,19 @@ def test_motor(mock_show): def test_initilize_motor_asserts_dynamic_values(solid_motor): - grain_vol = 0.12 * (np.pi * (0.033 ** 2 - 0.015 ** 2)) - grain_mass = grain_vol * 1815 + grain_vol = grainInitialHeight * (np.pi * (grainOuterRadius ** 2 - grainInitialInnerRadius ** 2)) + grain_mass = grain_vol * grainDensity assert solid_motor.maxThrust == 2200.0 assert solid_motor.maxThrustTime == 0.15 - assert solid_motor.burnOutTime == 3.9 - assert solid_motor.totalImpulse == solid_motor.thrust.integral(0, 3.9) - assert solid_motor.averageThrust == solid_motor.thrust.integral(0, 3.9) / 3.9 + assert solid_motor.burnOutTime == burnOut + assert solid_motor.totalImpulse == solid_motor.thrust.integral(0, burnOut) + assert solid_motor.averageThrust == solid_motor.thrust.integral(0, burnOut) / burnOut assert solid_motor.grainInitialVolume == grain_vol assert solid_motor.grainInitialMass == grain_mass - assert solid_motor.propellantInitialMass == 5 * grain_mass - assert solid_motor.exhaustVelocity == solid_motor.thrust.integral(0, 3.9) / ( - 5 * grain_mass + assert solid_motor.propellantInitialMass == grainNumber * grain_mass + assert solid_motor.exhaustVelocity == solid_motor.thrust.integral(0, burnOut) / ( + grainNumber * grain_mass ) @@ -58,11 +67,11 @@ def test_grain_geometry_progession_asserts_extreme_values(solid_motor): def test_mass_curve_asserts_extreme_values(solid_motor): - grain_vol = 0.12 * (np.pi * (0.033 ** 2 - 0.015 ** 2)) - grain_mass = grain_vol * 1815 + grain_vol = grainInitialHeight * (np.pi * (grainOuterRadius ** 2 - grainInitialInnerRadius ** 2)) + grain_mass = grain_vol * grainDensity assert np.allclose(solid_motor.mass.getSource()[-1][-1], 0) - assert np.allclose(solid_motor.mass.getSource()[0][-1], 5 * grain_mass) + assert np.allclose(solid_motor.mass.getSource()[0][-1], grainNumber * grain_mass) def test_burn_area_asserts_extreme_values(solid_motor): @@ -70,11 +79,11 @@ def test_burn_area_asserts_extreme_values(solid_motor): 2 * np.pi * ( - solid_motor.grainOuterRadius ** 2 - - solid_motor.grainInitialInnerRadius ** 2 - + solid_motor.grainInitialInnerRadius * solid_motor.grainInitialHeight + grainOuterRadius ** 2 + - grainInitialInnerRadius ** 2 + + grainInitialInnerRadius * grainInitialHeight ) - * 5 + * grainNumber ) final_burn_area = ( 2 @@ -83,7 +92,7 @@ def test_burn_area_asserts_extreme_values(solid_motor): solid_motor.grainInnerRadius.getSource()[-1][-1] * solid_motor.grainHeight.getSource()[-1][-1] ) - * 5 + * grainNumber ) assert np.allclose(solid_motor.burnArea.getSource()[0][-1], initial_burn_area) @@ -91,17 +100,16 @@ def test_burn_area_asserts_extreme_values(solid_motor): def test_evaluate_inertia_I_asserts_extreme_values(solid_motor): - grain_vol = 0.12 * (np.pi * (0.033 ** 2 - 0.015 ** 2)) - grain_mass = grain_vol * 1815 + grain_vol = grainInitialHeight * (np.pi * (grainOuterRadius ** 2 - grainInitialInnerRadius ** 2)) + grain_mass = grain_vol * grainDensity - grainNumber = 5 grainInertiaI_initial = grain_mass * ( - (1 / 4) * (0.033 ** 2 + 0.016 ** 2) + (1 / 12) * 0.12 ** 2 + (1 / 4) * (grainOuterRadius ** 2 + grainInitialInnerRadius ** 2) + (1 / 12) * grainInitialHeight ** 2 ) initialValue = (grainNumber - 1) / 2 d = np.linspace(-initialValue, initialValue, grainNumber) - d = d * (0.12 + 0.005) + d = d * (grainInitialHeight + grainSeparation) inertiaI_initial = grainNumber * grainInertiaI_initial + grain_mass * np.sum(d ** 2) @@ -112,10 +120,10 @@ def test_evaluate_inertia_I_asserts_extreme_values(solid_motor): def test_evaluate_inertia_Z_asserts_extreme_values(solid_motor): - grain_vol = 0.12 * (np.pi * (0.033 ** 2 - 0.015 ** 2)) - grain_mass = grain_vol * 1815 + grain_vol = grainInitialHeight * (np.pi * (grainOuterRadius ** 2 - grainInitialInnerRadius ** 2)) + grain_mass = grain_vol * grainDensity - grainInertiaZ_initial = grain_mass * (1 / 2.0) * (0.016 ** 2 + 0.033 ** 2) + grainInertiaZ_initial = grain_mass * (1 / 2.0) * (grainInitialInnerRadius ** 2 + grainOuterRadius ** 2) assert np.allclose( solid_motor.inertiaZ.getSource()[0][-1], grainInertiaZ_initial, atol=0.01 @@ -161,7 +169,7 @@ def tests_export_eng_asserts_exported_values_correct(solid_motor): assert comments == [] assert description == [ "test_motor", - "{:3.1f}".format(2000 * 0.033), + "{:3.1f}".format(2000 * grainOuterRadius), "{:3.1f}".format(1000 * 5 * (0.12 + 0.005)), "0", "{:2.3}".format(grain_mass), From 945210d5620d0d833516be98dc45779e625aaf50 Mon Sep 17 00:00:00 2001 From: giovaniceotto Date: Sun, 12 Sep 2021 13:06:12 -0300 Subject: [PATCH 12/14] MAINT: minor code refactoring in eng file export --- rocketpy/SolidMotor.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/rocketpy/SolidMotor.py b/rocketpy/SolidMotor.py index f4d62268..004e3f7d 100644 --- a/rocketpy/SolidMotor.py +++ b/rocketpy/SolidMotor.py @@ -700,7 +700,7 @@ def exportEng(self, fileName, motorName): # Write first line file.write( motorName - + " {:3.1f} {:3.1f} 0 {:2.3} {:2.3} PJ \n".format( + + " {:3.1f} {:3.1f} 0 {:2.3} {:2.3} RocketPy \n".format( 2000 * self.grainOuterRadius, 1000 * self.grainNumber @@ -711,9 +711,8 @@ def exportEng(self, fileName, motorName): ) # Write thrust curve data points - for item in self.thrust.source[:-1, :][1:]: - time = item[0] - thrust = item[1] + for time, thrust in self.thrust.source[1:, :]: + # time, thrust = item file.write("{:.4f} {:.3f}\n".format(time, thrust)) # Write last line From 2dd6c39ca81a76a3d549812d42841d4c5a1a0dd7 Mon Sep 17 00:00:00 2001 From: giovaniceotto Date: Sun, 12 Sep 2021 13:12:58 -0300 Subject: [PATCH 13/14] MAINT: changed eng export manufacturer name to RocketPy --- rocketpy/SolidMotor.py | 2 +- tests/test_solidmotor.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rocketpy/SolidMotor.py b/rocketpy/SolidMotor.py index 004e3f7d..c441bbe0 100644 --- a/rocketpy/SolidMotor.py +++ b/rocketpy/SolidMotor.py @@ -700,7 +700,7 @@ def exportEng(self, fileName, motorName): # Write first line file.write( motorName - + " {:3.1f} {:3.1f} 0 {:2.3} {:2.3} RocketPy \n".format( + + " {:3.1f} {:3.1f} 0 {:2.3} {:2.3} RocketPy\n".format( 2000 * self.grainOuterRadius, 1000 * self.grainNumber diff --git a/tests/test_solidmotor.py b/tests/test_solidmotor.py index a08ee4cf..9b9899d2 100644 --- a/tests/test_solidmotor.py +++ b/tests/test_solidmotor.py @@ -174,7 +174,7 @@ def tests_export_eng_asserts_exported_values_correct(solid_motor): "0", "{:2.3}".format(grain_mass), "{:2.3}".format(grain_mass), - "PJ", + "RocketPy", "\n", ] From 8820fb4cece6e2f0e56682af184bbda3153c0ca1 Mon Sep 17 00:00:00 2001 From: giovaniceotto Date: Sun, 12 Sep 2021 13:28:07 -0300 Subject: [PATCH 14/14] BUG: fixed handling of whitespace in .eng description --- rocketpy/SolidMotor.py | 4 ++-- tests/test_solidmotor.py | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/rocketpy/SolidMotor.py b/rocketpy/SolidMotor.py index c441bbe0..ccb5961e 100644 --- a/rocketpy/SolidMotor.py +++ b/rocketpy/SolidMotor.py @@ -668,7 +668,7 @@ def importEng(self, fileName): else: if description == []: # Extract description - description = line.split(" ") + description = line.strip().split(" ") else: # Extract thrust curve data points time, thrust = re.findall(r"[-+]?\d*\.\d+|[-+]?\d+", line) @@ -711,7 +711,7 @@ def exportEng(self, fileName, motorName): ) # Write thrust curve data points - for time, thrust in self.thrust.source[1:, :]: + for time, thrust in self.thrust.source[1:-1, :]: # time, thrust = item file.write("{:.4f} {:.3f}\n".format(time, thrust)) diff --git a/tests/test_solidmotor.py b/tests/test_solidmotor.py index 9b9899d2..f7ba6875 100644 --- a/tests/test_solidmotor.py +++ b/tests/test_solidmotor.py @@ -137,7 +137,7 @@ def tests_import_eng_asserts_read_values_correctly(solid_motor): ) assert comments == [";this motor is COTS\n", ";3.9 burnTime\n"] - assert description == ["M1670-BS", "75", "757", "0", "3.101", "5.231", "CTI", "\n"] + assert description == ["M1670-BS", "75", "757", "0", "3.101", "5.231", "CTI"] assert dataPoints == [ [0, 0], [0.055, 100.0], @@ -174,8 +174,7 @@ def tests_export_eng_asserts_exported_values_correct(solid_motor): "0", "{:2.3}".format(grain_mass), "{:2.3}".format(grain_mass), - "RocketPy", - "\n", + "RocketPy" ] assert dataPoints == [