-
-
Notifications
You must be signed in to change notification settings - Fork 252
Expand file tree
/
Copy pathrocket.py
More file actions
2005 lines (1822 loc) · 82.7 KB
/
rocket.py
File metadata and controls
2005 lines (1822 loc) · 82.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import math
import numpy as np
from rocketpy.control.controller import _Controller
from rocketpy.mathutils.function import Function
from rocketpy.mathutils.vector_matrix import Matrix, Vector
from rocketpy.motors.empty_motor import EmptyMotor
from rocketpy.plots.rocket_plots import _RocketPlots
from rocketpy.prints.rocket_prints import _RocketPrints
from rocketpy.rocket.aero_surface import (
AirBrakes,
EllipticalFins,
Fins,
NoseCone,
RailButtons,
Tail,
TrapezoidalFins,
)
from rocketpy.rocket.aero_surface.fins.free_form_fins import FreeFormFins
from rocketpy.rocket.aero_surface.generic_surface import GenericSurface
from rocketpy.rocket.components import Components
from rocketpy.rocket.parachute import Parachute
from rocketpy.tools import deprecated, parallel_axis_theorem_from_com
# pylint: disable=too-many-instance-attributes, too-many-public-methods, too-many-instance-attributes
class Rocket:
"""Keeps rocket information.
Attributes
----------
Rocket.radius : float
Rocket's largest radius in meters.
Rocket.area : float
Rocket's circular cross section largest frontal area in squared
meters.
Rocket.center_of_dry_mass_position : float
Position, in m, of the rocket's center of dry mass (i.e. center of
mass without propellant) relative to the rocket's coordinate system.
See :doc:`Positions and Coordinate Systems </user/positions>`
for more information
regarding the rocket's coordinate system.
Rocket.center_of_mass_without_motor : int, float
Position, in m, of the rocket's center of mass without motor
relative to the rocket's coordinate system. This does not include
the motor or propellant mass.
Rocket.motor_center_of_mass_position : Function
Position, in meters, of the motor's center of mass relative to the user
defined rocket coordinate system. This is a function of time since the
propellant mass decreases with time. For more information, see the
:doc:`Positions and Coordinate Systems </user/positions>`.
Rocket.motor_center_of_dry_mass_position : float
Position, in meters, of the motor's center of dry mass (i.e. center of
mass without propellant) relative to the user defined rocket coordinate
system. This is constant since the motor dry mass is constant.
Rocket.coordinate_system_orientation : string
String defining the orientation of the rocket's coordinate system.
The coordinate system is defined by the rocket's axis of symmetry.
The system's origin may be placed anywhere along such axis, such as
in the nozzle or in the nose cone, and must be kept the same for all
other positions specified. If "tail_to_nose", the coordinate system
is defined with the rocket's axis of symmetry pointing from the
rocket's tail to the rocket's nose cone. If "nose_to_tail", the
coordinate system is defined with the rocket's axis of symmetry
pointing from the rocket's nose cone to the rocket's tail.
Rocket.mass : float
Rocket's mass without motor and propellant, measured in kg.
Rocket.dry_mass : float
Rocket's mass without propellant, measured in kg. It does include the
motor mass.
Rocket.center_of_mass : Function
Position of the rocket's center of mass, including propellant, relative
to the user defined rocket reference system.
See :doc:`Positions and Coordinate Systems </user/positions>`
for more information
regarding the coordinate system.
Expressed in meters as a function of time.
Rocket.com_to_cdm_function : Function
Function of time expressing the z-coordinate of the center of mass
relative to the center of dry mass.
Rocket.reduced_mass : Function
Function of time expressing the reduced mass of the rocket,
defined as the product of the propellant mass and the mass
of the rocket without propellant, divided by the sum of the
propellant mass and the rocket mass.
Rocket.total_mass : Function
Function of time expressing the total mass of the rocket,
defined as the sum of the propellant mass and the rocket
mass without propellant.
Rocket.structural_mass_ratio: float
Initial ratio between the dry mass and the total mass.
Rocket.total_mass_flow_rate : Function
Time derivative of rocket's total mass in kg/s as a function
of time as obtained by the thrust source of the added motor.
Rocket.thrust_to_weight : Function
Function of time expressing the motor thrust force divided by rocket
weight. The gravitational acceleration is assumed as 9.80665 m/s^2.
Rocket.cp_eccentricity_x : float
Center of pressure position relative to center of mass in the x
axis, perpendicular to axis of cylindrical symmetry, in meters.
Rocket.cp_eccentricity_y : float
Center of pressure position relative to center of mass in the y
axis, perpendicular to axis of cylindrical symmetry, in meters.
Rocket.thrust_eccentricity_y : float
Thrust vector position relative to center of mass in the y
axis, perpendicular to axis of cylindrical symmetry, in meters.
Rocket.thrust_eccentricity_x : float
Thrust vector position relative to center of mass in the x
axis, perpendicular to axis of cylindrical symmetry, in meters.
Rocket.aerodynamic_surfaces : list
Collection of aerodynamic surfaces of the rocket. Holds Nose cones,
Fin sets, and Tails.
Rocket.surfaces_cp_to_cdm : dict
Dictionary containing the relative position of each aerodynamic surface
center of pressure to the rocket's center of mass. The key is the
aerodynamic surface object and the value is the relative position Vector
in meters.
Rocket.parachutes : list
Collection of parachutes of the rocket.
Rocket.air_brakes : list
Collection of air brakes of the rocket.
Rocket._controllers : list
Collection of controllers of the rocket.
Rocket.cp_position : Function
Function of Mach number expressing the rocket's center of pressure
position relative to user defined rocket reference system.
See :doc:`Positions and Coordinate Systems </user/positions>`
for more information.
Rocket.stability_margin : Function
Stability margin of the rocket, in calibers, as a function of mach
number and time. Stability margin is defined as the distance between
the center of pressure and the center of mass, divided by the
rocket's diameter.
Rocket.static_margin : Function
Static margin of the rocket, in calibers, as a function of time. Static
margin is defined as the distance between the center of pressure and the
center of mass, divided by the rocket's diameter.
Rocket.static_margin : float
Float value corresponding to rocket static margin when
loaded with propellant in units of rocket diameter or calibers.
Rocket.power_off_drag : Function
Rocket's drag coefficient as a function of Mach number when the
motor is off.
Rocket.power_on_drag : Function
Rocket's drag coefficient as a function of Mach number when the
motor is on.
Rocket.rail_buttons : RailButtons
RailButtons object containing the rail buttons information.
Rocket.motor : Motor
Rocket's motor. See Motor class for more details.
Rocket.motor_position : float
Position, in meters, of the motor's coordinate system origin
relative to the user defined rocket coordinate system.
See :doc:`Positions and Coordinate Systems </user/positions>`
for more information.
regarding the rocket's coordinate system.
Rocket.nozzle_position : float
Position, in meters, of the motor's nozzle exit relative to the user
defined rocket coordinate system.
See :doc:`Positions and Coordinate Systems </user/positions>`
for more information.
Rocket.nozzle_to_cdm : float
Distance between the nozzle exit and the rocket's center of dry mass
position, in meters.
Rocket.nozzle_gyration_tensor: Matrix
Matrix representing the nozzle gyration tensor.
Rocket.center_of_propellant_position : Function
Position of the propellant's center of mass relative to the user defined
rocket reference system. See
:doc:`Positions and Coordinate Systems </user/positions>` for more
information regarding the rocket's coordinate system. Expressed in
meters as a function of time.
Rocket.I_11_without_motor : float
Rocket's inertia tensor 11 component without any motors, in kg*m^2. This
is the same value that is passed in the Rocket.__init__() method.
Rocket.I_22_without_motor : float
Rocket's inertia tensor 22 component without any motors, in kg*m^2. This
is the same value that is passed in the Rocket.__init__() method.
Rocket.I_33_without_motor : float
Rocket's inertia tensor 33 component without any motors, in kg*m^2. This
is the same value that is passed in the Rocket.__init__() method.
Rocket.I_12_without_motor : float
Rocket's inertia tensor 12 component without any motors, in kg*m^2. This
is the same value that is passed in the Rocket.__init__() method.
Rocket.I_13_without_motor : float
Rocket's inertia tensor 13 component without any motors, in kg*m^2. This
is the same value that is passed in the Rocket.__init__() method.
Rocket.I_23_without_motor : float
Rocket's inertia tensor 23 component without any motors, in kg*m^2. This
is the same value that is passed in the Rocket.__init__() method.
Rocket.dry_I_11 : float
Rocket's inertia tensor 11 component with unloaded motor,in kg*m^2.
Rocket.dry_I_22 : float
Rocket's inertia tensor 22 component with unloaded motor,in kg*m^2.
Rocket.dry_I_33 : float
Rocket's inertia tensor 33 component with unloaded motor,in kg*m^2.
Rocket.dry_I_12 : float
Rocket's inertia tensor 12 component with unloaded motor,in kg*m^2.
Rocket.dry_I_13 : float
Rocket's inertia tensor 13 component with unloaded motor,in kg*m^2.
Rocket.dry_I_23 : float
Rocket's inertia tensor 23 component with unloaded motor,in kg*m^2.
"""
def __init__( # pylint: disable=too-many-statements
self,
radius,
mass,
inertia,
power_off_drag,
power_on_drag,
center_of_mass_without_motor,
coordinate_system_orientation="tail_to_nose",
):
"""Initializes Rocket class, process inertial, geometrical and
aerodynamic parameters.
Parameters
----------
radius : int, float
Rocket largest outer radius in meters.
mass : int, float
Rocket total mass without motor in kg.
inertia : tuple, list
Tuple or list containing the rocket's inertia tensor components,
in kg*m^2. This should be measured without motor and propellant so
that the inertia reference point is the
`center_of_mass_without_motor`.
Assuming e_3 is the rocket's axis of symmetry, e_1 and e_2 are
orthogonal and form a plane perpendicular to e_3, the inertia tensor
components must be given in the following order: (I_11, I_22, I_33,
I_12, I_13, I_23), where I_ij is the component of the inertia tensor
in the direction of e_i x e_j. Alternatively, the inertia tensor can
be given as (I_11, I_22, I_33), where I_12 = I_13 = I_23 = 0. This
can also be called as "rocket dry inertia tensor".
power_off_drag : int, float, callable, string, array
Rocket's drag coefficient when the motor is off. Can be given as an
entry to the Function class. See help(Function) for more
information. If int or float is given, it is assumed constant. If
callable, string or array is given, it must be a function of Mach
number only.
power_on_drag : int, float, callable, string, array
Rocket's drag coefficient when the motor is on. Can be given as an
entry to the Function class. See help(Function) for more
information. If int or float is given, it is assumed constant. If
callable, string or array is given, it must be a function of Mach
number only.
center_of_mass_without_motor : int, float
Position, in m, of the rocket's center of mass without motor
relative to the rocket's coordinate system. Default is 0, which
means the center of dry mass is chosen as the origin, to comply
with the legacy behavior of versions 0.X.Y.
See :doc:`Positions and Coordinate Systems </user/positions>`
for more information
regarding the rocket's coordinate system.
coordinate_system_orientation : string, optional
String defining the orientation of the rocket's coordinate system.
The coordinate system is defined by the rocket's axis of symmetry.
The system's origin may be placed anywhere along such axis, such as
in the nozzle or in the nose cone, and must be kept the same for all
other positions specified. The two options available are:
"tail_to_nose" and "nose_to_tail". The first defines the coordinate
system with the rocket's axis of symmetry pointing from the rocket's
tail to the rocket's nose cone. The second option defines the
coordinate system with the rocket's axis of symmetry pointing from
the rocket's nose cone to the rocket's tail. Default is
"tail_to_nose".
Returns
-------
None
"""
# Define coordinate system orientation
self.coordinate_system_orientation = coordinate_system_orientation
if coordinate_system_orientation == "tail_to_nose":
self._csys = 1
elif coordinate_system_orientation == "nose_to_tail":
self._csys = -1
else: # pragma: no cover
raise TypeError(
"Invalid coordinate system orientation. Please choose between "
+ '"tail_to_nose" and "nose_to_tail".'
)
# Define rocket inertia attributes in SI units
self.mass = mass
inertia = (*inertia, 0, 0, 0) if len(inertia) == 3 else inertia
self.I_11_without_motor = inertia[0]
self.I_22_without_motor = inertia[1]
self.I_33_without_motor = inertia[2]
self.I_12_without_motor = inertia[3]
self.I_13_without_motor = inertia[4]
self.I_23_without_motor = inertia[5]
# Define rocket geometrical parameters in SI units
self.center_of_mass_without_motor = center_of_mass_without_motor
self.radius = radius
self.area = np.pi * self.radius**2
# Eccentricity data initialization
self.cm_eccentricity_x = 0
self.cm_eccentricity_y = 0
self.cp_eccentricity_x = 0
self.cp_eccentricity_y = 0
self.thrust_eccentricity_y = 0
self.thrust_eccentricity_x = 0
# Parachute, Aerodynamic, Buttons, Controllers, Sensor data initialization
self.parachutes = []
self._controllers = []
self.air_brakes = []
self.sensors = Components()
self.aerodynamic_surfaces = Components()
self.surfaces_cp_to_cdm = {}
self.rail_buttons = Components()
self.cp_position = Function(
lambda mach: 0,
inputs="Mach Number",
outputs="Center of Pressure Position (m)",
)
self.total_lift_coeff_der = Function(
lambda mach: 0,
inputs="Mach Number",
outputs="Total Lift Coefficient Derivative",
)
self.static_margin = Function(
lambda time: 0, inputs="Time (s)", outputs="Static Margin (c)"
)
self.stability_margin = Function(
lambda mach, time: 0,
inputs=["Mach", "Time (s)"],
outputs="Stability Margin (c)",
)
# Define aerodynamic drag coefficients
self.power_off_drag = Function(
power_off_drag,
"Mach Number",
"Drag Coefficient with Power Off",
"linear",
"constant",
)
self.power_on_drag = Function(
power_on_drag,
"Mach Number",
"Drag Coefficient with Power On",
"linear",
"constant",
)
# Create a, possibly, temporary empty motor
# self.motors = Components() # currently unused, only 1 motor is supported
self.add_motor(motor=EmptyMotor(), position=0)
# Important dynamic inertial quantities
self.center_of_mass = None
self.reduced_mass = None
self.total_mass = None
self.dry_mass = None
# calculate dynamic inertial quantities
self.evaluate_dry_mass()
self.evaluate_structural_mass_ratio()
self.evaluate_total_mass()
self.evaluate_center_of_dry_mass()
self.evaluate_center_of_mass()
self.evaluate_reduced_mass()
self.evaluate_thrust_to_weight()
# Evaluate stability (even though no aerodynamic surfaces are present yet)
self.evaluate_center_of_pressure()
self.evaluate_stability_margin()
self.evaluate_static_margin()
# Initialize plots and prints object
self.prints = _RocketPrints(self)
self.plots = _RocketPlots(self)
@property
def nosecones(self):
"""A list containing all the nose cones currently added to the rocket."""
return self.aerodynamic_surfaces.get_by_type(NoseCone)
@property
def fins(self):
"""A list containing all the fins currently added to the rocket."""
return self.aerodynamic_surfaces.get_by_type(Fins)
@property
def tails(self):
"""A list with all the tails currently added to the rocket"""
return self.aerodynamic_surfaces.get_by_type(Tail)
def evaluate_total_mass(self):
"""Calculates and returns the rocket's total mass. The total
mass is defined as the sum of the motor mass with propellant and the
rocket mass without propellant. The function returns an object
of the Function class and is defined as a function of time.
Returns
-------
self.total_mass : Function
Function of time expressing the total mass of the rocket,
defined as the sum of the propellant mass and the rocket
mass without propellant.
"""
# Make sure there is a motor associated with the rocket
if self.motor is None:
print("Please associate this rocket with a motor!")
return False
self.total_mass = self.mass + self.motor.total_mass
self.total_mass.set_outputs("Total Mass (Rocket + Motor + Propellant) (kg)")
self.total_mass.set_title("Total Mass (Rocket + Motor + Propellant)")
return self.total_mass
def evaluate_dry_mass(self):
"""Calculates and returns the rocket's dry mass. The dry
mass is defined as the sum of the motor's dry mass and the
rocket mass without motor.
Returns
-------
self.dry_mass : float
Rocket's dry mass (Rocket + Motor) (kg)
"""
# Make sure there is a motor associated with the rocket
if self.motor is None:
print("Please associate this rocket with a motor!")
return False
self.dry_mass = self.mass + self.motor.dry_mass
return self.dry_mass
def evaluate_structural_mass_ratio(self):
"""Calculates and returns the rocket's structural mass ratio.
It is defined as the ratio between of the dry mass
(Motor + Rocket) and the initial total mass
(Motor + Propellant + Rocket).
Returns
-------
self.structural_mass_ratio: float
Initial structural mass ratio dry mass (Rocket + Motor) (kg)
divided by total mass (Rocket + Motor + Propellant) (kg).
"""
try:
self.structural_mass_ratio = self.dry_mass / (
self.dry_mass + self.motor.propellant_initial_mass
)
except ZeroDivisionError as e:
raise ValueError(
"Total rocket mass (dry + propellant) cannot be zero"
) from e
return self.structural_mass_ratio
def evaluate_center_of_mass(self):
"""Evaluates rocket center of mass position relative to user defined
rocket reference system.
Returns
-------
self.center_of_mass : Function
Function of time expressing the rocket's center of mass position
relative to user defined rocket reference system.
See :doc:`Positions and Coordinate Systems </user/positions>`
for more information.
"""
self.center_of_mass = (
self.center_of_mass_without_motor * self.mass
+ self.motor_center_of_mass_position * self.motor.total_mass
) / self.total_mass
self.center_of_mass.set_inputs("Time (s)")
self.center_of_mass.set_outputs("Center of Mass Position (m)")
self.center_of_mass.set_title(
"Center of Mass Position (Rocket + Motor + Propellant)"
)
return self.center_of_mass
def evaluate_center_of_dry_mass(self):
"""Evaluates the rocket's center of dry mass (i.e. rocket with motor but
without propellant) position relative to user defined rocket reference
system.
Returns
-------
self.center_of_dry_mass_position : int, float
Rocket's center of dry mass position (with unloaded motor)
"""
self.center_of_dry_mass_position = (
self.center_of_mass_without_motor * self.mass
+ self.motor_center_of_dry_mass_position * self.motor.dry_mass
) / self.dry_mass
return self.center_of_dry_mass_position
def evaluate_reduced_mass(self):
"""Calculates and returns the rocket's total reduced mass. The reduced
mass is defined as the product of the propellant mass and the rocket dry
mass (i.e. with unloaded motor), divided by the loaded rocket mass.
The function returns an object of the Function class and is defined as a
function of time.
Returns
-------
self.reduced_mass : Function
Function of time expressing the reduced mass of the rocket.
"""
# TODO: add tests for reduced_mass values
# Make sure there is a motor associated with the rocket
if self.motor is None:
print("Please associate this rocket with a motor!")
return False
# Get nicknames
prop_mass = self.motor.propellant_mass
dry_mass = self.dry_mass
# calculate reduced mass and return it
self.reduced_mass = prop_mass * dry_mass / (prop_mass + dry_mass)
self.reduced_mass.set_outputs("Reduced Mass (kg)")
self.reduced_mass.set_title("Reduced Mass")
return self.reduced_mass
def evaluate_thrust_to_weight(self):
"""Evaluates thrust to weight as a Function of time. This is defined as
the motor thrust force divided by rocket weight. The gravitational
acceleration is assumed constant and equals to 9.80665 m/s^2.
Returns
-------
None
"""
self.thrust_to_weight = self.motor.thrust / (9.80665 * self.total_mass)
self.thrust_to_weight.set_inputs("Time (s)")
self.thrust_to_weight.set_outputs("Thrust/Weight")
self.thrust_to_weight.set_title("Thrust to Weight ratio")
def evaluate_center_of_pressure(self):
"""Evaluates rocket center of pressure position relative to user defined
rocket reference system. It can be called as many times as needed, as it
will update the center of pressure function every time it is called. The
code will iterate through all aerodynamic surfaces and consider each of
their center of pressure position and derivative of the coefficient of
lift as a function of Mach number.
Returns
-------
self.cp_position : Function
Function of Mach number expressing the rocket's center of pressure
position relative to user defined rocket reference system.
See :doc:`Positions and Coordinate Systems </user/positions>`
for more information.
"""
# Re-Initialize total lift coefficient derivative and center of pressure position
self.total_lift_coeff_der.set_source(lambda mach: 0)
self.cp_position.set_source(lambda mach: 0)
# Calculate total lift coefficient derivative and center of pressure
if len(self.aerodynamic_surfaces) > 0:
for aero_surface, position in self.aerodynamic_surfaces:
if isinstance(aero_surface, GenericSurface):
continue
# ref_factor corrects lift for different reference areas
ref_factor = (aero_surface.rocket_radius / self.radius) ** 2
self.total_lift_coeff_der += ref_factor * aero_surface.clalpha
self.cp_position += (
ref_factor
* aero_surface.clalpha
* (position.z - self._csys * aero_surface.cpz)
)
# Avoid errors when only generic surfaces are added
if self.total_lift_coeff_der.get_value(0) != 0:
self.cp_position /= self.total_lift_coeff_der
return self.cp_position
def evaluate_surfaces_cp_to_cdm(self):
"""Calculates the relative position of each aerodynamic surface center
of pressure to the rocket's center of dry mass in Body Axes Coordinate
System.
Returns
-------
self.surfaces_cp_to_cdm : dict
Dictionary mapping the relative position of each aerodynamic
surface center of pressure to the rocket's center of mass.
"""
for surface, position in self.aerodynamic_surfaces:
self.__evaluate_single_surface_cp_to_cdm(surface, position)
return self.surfaces_cp_to_cdm
def __evaluate_single_surface_cp_to_cdm(self, surface, position):
"""Calculates the relative position of each aerodynamic surface
center of pressure to the rocket's center of dry mass in Body Axes
Coordinate System."""
pos = Vector(
[
(position.x - self.cm_eccentricity_x) * self._csys - surface.cpx,
(position.y - self.cm_eccentricity_y) - surface.cpy,
(position.z - self.center_of_dry_mass_position) * self._csys
- surface.cpz,
]
)
self.surfaces_cp_to_cdm[surface] = pos
def evaluate_stability_margin(self):
"""Calculates the stability margin of the rocket as a function of mach
number and time.
Returns
-------
stability_margin : Function
Stability margin of the rocket, in calibers, as a function of mach
number and time. Stability margin is defined as the distance between
the center of pressure and the center of mass, divided by the
rocket's diameter.
"""
self.stability_margin.set_source(
lambda mach, time: (
(
self.center_of_mass.get_value_opt(time)
- self.cp_position.get_value_opt(mach)
)
/ (2 * self.radius)
)
* self._csys
)
return self.stability_margin
def evaluate_static_margin(self):
"""Calculates the static margin of the rocket as a function of time.
Returns
-------
static_margin : Function
Static margin of the rocket, in calibers, as a function of time.
Static margin is defined as the distance between the center of
pressure and the center of mass, divided by the rocket's diameter.
"""
# Calculate static margin
self.static_margin.set_source(
lambda time: (
self.center_of_mass.get_value_opt(time)
- self.cp_position.get_value_opt(0)
)
/ (2 * self.radius)
)
# Change sign if coordinate system is upside down
self.static_margin *= self._csys
self.static_margin.set_inputs("Time (s)")
self.static_margin.set_outputs("Static Margin (c)")
self.static_margin.set_title("Static Margin")
self.static_margin.set_discrete(
lower=0, upper=self.motor.burn_out_time, samples=200
)
return self.static_margin
def evaluate_dry_inertias(self):
"""Calculates and returns the rocket's dry inertias relative to
the rocket's center of dry mass. The inertias are saved and returned
in units of kg*m². This does not consider propellant mass but does take
into account the motor dry mass.
Returns
-------
self.dry_I_11 : float
Float value corresponding to rocket inertia tensor 11
component, which corresponds to the inertia relative to the
e_1 axis, centered at the center of dry mass.
self.dry_I_22 : float
Float value corresponding to rocket inertia tensor 22
component, which corresponds to the inertia relative to the
e_2 axis, centered at the center of dry mass.
self.dry_I_33 : float
Float value corresponding to rocket inertia tensor 33
component, which corresponds to the inertia relative to the
e_3 axis, centered at the center of dry mass.
self.dry_I_12 : float
Float value corresponding to rocket inertia tensor 12
component, which corresponds to the inertia relative to the
e_1 and e_2 axes, centered at the center of dry mass.
self.dry_I_13 : float
Float value corresponding to rocket inertia tensor 13
component, which corresponds to the inertia relative to the
e_1 and e_3 axes, centered at the center of dry mass.
self.dry_I_23 : float
Float value corresponding to rocket inertia tensor 23
component, which corresponds to the inertia relative to the
e_2 and e_3 axes, centered at the center of dry mass.
Notes
-----
#. The ``e_1`` and ``e_2`` directions are assumed to be the directions \
perpendicular to the rocket axial direction.
#. The ``e_3`` direction is assumed to be the direction parallel to the \
axis of symmetry of the rocket.
#. RocketPy follows the definition of the inertia tensor that includes \
the minus sign for all products of inertia.
See Also
--------
`Inertia Tensor <https://en.wikipedia.org/wiki/Moment_of_inertia#Inertia_tensor>`_
"""
# Get masses
motor_dry_mass = self.motor.dry_mass
mass = self.mass
# Compute axes distances (CDM: Center of Dry Mass)
center_of_mass_without_motor_to_CDM = (
self.center_of_mass_without_motor - self.center_of_dry_mass_position
)
motor_center_of_dry_mass_to_CDM = (
self.motor_center_of_dry_mass_position - self.center_of_dry_mass_position
)
# Compute dry inertias
self.dry_I_11 = parallel_axis_theorem_from_com(
self.I_11_without_motor, mass, center_of_mass_without_motor_to_CDM
) + parallel_axis_theorem_from_com(
self.motor.dry_I_11, motor_dry_mass, motor_center_of_dry_mass_to_CDM
)
self.dry_I_22 = parallel_axis_theorem_from_com(
self.I_22_without_motor, mass, center_of_mass_without_motor_to_CDM
) + parallel_axis_theorem_from_com(
self.motor.dry_I_22, motor_dry_mass, motor_center_of_dry_mass_to_CDM
)
self.dry_I_33 = self.I_33_without_motor + self.motor.dry_I_33
self.dry_I_12 = self.I_12_without_motor + self.motor.dry_I_12
self.dry_I_13 = self.I_13_without_motor + self.motor.dry_I_13
self.dry_I_23 = self.I_23_without_motor + self.motor.dry_I_23
return (
self.dry_I_11,
self.dry_I_22,
self.dry_I_33,
self.dry_I_12,
self.dry_I_13,
self.dry_I_23,
)
def evaluate_inertias(self):
"""Calculates and returns the rocket's inertias relative to
the rocket's center of dry mass. The inertias are saved and returned
in units of kg*m².
Returns
-------
self.I_11 : float
Float value corresponding to rocket inertia tensor 11
component, which corresponds to the inertia relative to the
e_1 axis, centered at the center of dry mass.
self.I_22 : float
Float value corresponding to rocket inertia tensor 22
component, which corresponds to the inertia relative to the
e_2 axis, centered at the center of dry mass.
self.I_33 : float
Float value corresponding to rocket inertia tensor 33
component, which corresponds to the inertia relative to the
e_3 axis, centered at the center of dry mass.
Notes
-----
#. The ``e_1`` and ``e_2`` directions are assumed to be the directions \
perpendicular to the rocket axial direction.
#. The ``e_3`` direction is assumed to be the direction parallel to the \
axis of symmetry of the rocket.
#. RocketPy follows the definition of the inertia tensor that includes \
the minus sign for all products of inertia.
See Also
--------
`Inertia Tensor <https://en.wikipedia.org/wiki/Moment_of_inertia#Inertia_tensor>`_
"""
# Get masses
prop_mass = self.motor.propellant_mass # Propellant mass as a function of time
# Compute axes distances
CDM_to_CPM = (
self.center_of_dry_mass_position - self.center_of_propellant_position
)
# Compute inertias
self.I_11 = self.dry_I_11 + parallel_axis_theorem_from_com(
self.motor.propellant_I_11, prop_mass, CDM_to_CPM
)
self.I_22 = self.dry_I_22 + parallel_axis_theorem_from_com(
self.motor.propellant_I_22, prop_mass, CDM_to_CPM
)
self.I_33 = self.dry_I_33 + self.motor.propellant_I_33
self.I_12 = self.dry_I_12 + self.motor.propellant_I_12
self.I_13 = self.dry_I_13 + self.motor.propellant_I_13
self.I_23 = self.dry_I_23 + self.motor.propellant_I_23
# Return inertias
return (
self.I_11,
self.I_22,
self.I_33,
self.I_12,
self.I_13,
self.I_23,
)
def evaluate_nozzle_to_cdm(self):
"""Evaluates the distance between the nozzle exit and the rocket's
center of dry mass.
Returns
-------
self.nozzle_to_cdm : float
Distance between the nozzle exit and the rocket's center of dry
mass position, in meters.
"""
self.nozzle_to_cdm = (
-(self.nozzle_position - self.center_of_dry_mass_position) * self._csys
)
return self.nozzle_to_cdm
def evaluate_nozzle_gyration_tensor(self):
"""Calculates and returns the nozzle gyration tensor relative to the
rocket's center of dry mass. The gyration tensor is saved and returned
in units of kg*m².
Returns
-------
self.nozzle_gyration_tensor : Matrix
Matrix containing the nozzle gyration tensor.
"""
S_noz_33 = 0.5 * self.motor.nozzle_radius**2
S_noz_11 = S_noz_22 = 0.5 * S_noz_33 + 0.25 * self.nozzle_to_cdm**2
S_noz_12, S_noz_13, S_noz_23 = 0, 0, 0 # Due to axis symmetry
self.nozzle_gyration_tensor = Matrix(
[
[S_noz_11, S_noz_12, S_noz_13],
[S_noz_12, S_noz_22, S_noz_23],
[S_noz_13, S_noz_23, S_noz_33],
]
)
return self.nozzle_gyration_tensor
def evaluate_com_to_cdm_function(self):
"""Evaluates the z-coordinate of the center of mass (COM) relative to
the center of dry mass (CDM).
Notes
-----
1. The `com_to_cdm_function` plus `center_of_mass` should be equal
to `center_of_dry_mass_position` at every time step.
2. The `com_to_cdm_function` is a function of time and will usually
already be discretized.
Returns
-------
self.com_to_cdm_function : Function
Function of time expressing the z-coordinate of the center of mass
relative to the center of dry mass.
"""
self.com_to_cdm_function = (
-1
* (
(self.center_of_propellant_position - self.center_of_dry_mass_position)
* self._csys
)
* self.motor.propellant_mass
/ self.total_mass
)
self.com_to_cdm_function.set_inputs("Time (s)")
self.com_to_cdm_function.set_outputs("Z Coordinate COM to CDM (m)")
self.com_to_cdm_function.set_title("Z Coordinate COM to CDM")
return self.com_to_cdm_function
def get_inertia_tensor_at_time(self, t):
"""Returns a Matrix representing the inertia tensor of the rocket with
respect to the rocket's center of dry mass at a given time. It evaluates
each inertia tensor component at the given time and returns a Matrix
with the computed values.
Parameters
----------
t : float
Time at which the inertia tensor is to be evaluated.
Returns
-------
Matrix
Inertia tensor of the rocket at time t.
"""
I_11 = self.I_11.get_value_opt(t)
I_12 = self.I_12.get_value_opt(t)
I_13 = self.I_13.get_value_opt(t)
I_22 = self.I_22.get_value_opt(t)
I_23 = self.I_23.get_value_opt(t)
I_33 = self.I_33.get_value_opt(t)
return Matrix(
[
[I_11, I_12, I_13],
[I_12, I_22, I_23],
[I_13, I_23, I_33],
]
)
def get_inertia_tensor_derivative_at_time(self, t):
"""Returns a Matrix representing the time derivative of the inertia
tensor of the rocket with respect to the rocket's center of dry mass at
a given time. It evaluates each inertia tensor component's derivative at
the given time and returns a Matrix with the computed values.
Parameters
----------
t : float
Time at which the inertia tensor derivative is to be evaluated.
Returns
-------
Matrix
Inertia tensor time derivative of the rocket at time t.
"""
I_11_dot = self.I_11.differentiate_complex_step(t)
I_12_dot = self.I_12.differentiate_complex_step(t)
I_13_dot = self.I_13.differentiate_complex_step(t)
I_22_dot = self.I_22.differentiate_complex_step(t)
I_23_dot = self.I_23.differentiate_complex_step(t)
I_33_dot = self.I_33.differentiate_complex_step(t)
return Matrix(
[
[I_11_dot, I_12_dot, I_13_dot],
[I_12_dot, I_22_dot, I_23_dot],
[I_13_dot, I_23_dot, I_33_dot],
]
)
def add_motor(self, motor, position): # pylint: disable=too-many-statements
"""Adds a motor to the rocket.
Parameters
----------
motor : Motor, SolidMotor, HybridMotor, LiquidMotor, GenericMotor
Motor to be added to the rocket.
position : int, float
Position, in meters, of the motor's coordinate system origin
relative to the user defined rocket coordinate system.
See Also
--------
:ref:`addsurface`
Returns
-------
None
"""
if hasattr(self, "motor"):
# pylint: disable=access-member-before-definition
if not isinstance(self.motor, EmptyMotor):
print(
"Only one motor per rocket is currently supported. "
+ "Overwriting previous motor."
)
self.motor = motor
self.motor_position = position
_ = self._csys * self.motor._csys
self.center_of_propellant_position = (
self.motor.center_of_propellant_mass * _ + self.motor_position
)
self.motor_center_of_mass_position = (
self.motor.center_of_mass * _ + self.motor_position
)
self.motor_center_of_dry_mass_position = (
self.motor.center_of_dry_mass_position * _ + self.motor_position
)
self.nozzle_position = self.motor.nozzle_position * _ + self.motor_position
self.total_mass_flow_rate = self.motor.total_mass_flow_rate
self.evaluate_dry_mass()
self.evaluate_structural_mass_ratio()
self.evaluate_total_mass()
self.evaluate_center_of_dry_mass()
self.evaluate_nozzle_to_cdm()
self.evaluate_center_of_mass()
self.evaluate_dry_inertias()
self.evaluate_inertias()
self.evaluate_reduced_mass()
self.evaluate_thrust_to_weight()
self.evaluate_center_of_pressure()
self.evaluate_surfaces_cp_to_cdm()
self.evaluate_stability_margin()
self.evaluate_static_margin()
self.evaluate_com_to_cdm_function()
self.evaluate_nozzle_gyration_tensor()
def __add_single_surface(self, surface, position):
"""Adds a single aerodynamic surface to the rocket. Makes checks for
rail buttons case, and position type.
"""
position = (
Vector([0, 0, position])
if not isinstance(position, (Vector, tuple, list))