kerbaldyn 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. data/.rspec +2 -0
  2. data/Gemfile +11 -0
  3. data/README.rdoc +57 -0
  4. data/Rakefile +23 -0
  5. data/kerbaldyn.gemspec +30 -0
  6. data/lib/kerbaldyn.rb +14 -0
  7. data/lib/kerbaldyn/body.rb +48 -0
  8. data/lib/kerbaldyn/constants.rb +12 -0
  9. data/lib/kerbaldyn/data.rb +56 -0
  10. data/lib/kerbaldyn/data/planet_data.json +249 -0
  11. data/lib/kerbaldyn/mixin.rb +2 -0
  12. data/lib/kerbaldyn/mixin/options_processor.rb +17 -0
  13. data/lib/kerbaldyn/mixin/parameter_attributes.rb +38 -0
  14. data/lib/kerbaldyn/orbit.rb +379 -0
  15. data/lib/kerbaldyn/orbital_maneuver.rb +6 -0
  16. data/lib/kerbaldyn/orbital_maneuver/base.rb +159 -0
  17. data/lib/kerbaldyn/orbital_maneuver/bielliptic.rb +61 -0
  18. data/lib/kerbaldyn/orbital_maneuver/burn_event.rb +57 -0
  19. data/lib/kerbaldyn/orbital_maneuver/hohmann.rb +48 -0
  20. data/lib/kerbaldyn/orbital_maneuver/inclination_change.rb +0 -0
  21. data/lib/kerbaldyn/part.rb +15 -0
  22. data/lib/kerbaldyn/part/base.rb +154 -0
  23. data/lib/kerbaldyn/part/fuel_tank.rb +11 -0
  24. data/lib/kerbaldyn/part/generic.rb +10 -0
  25. data/lib/kerbaldyn/part/liquid_fuel_engine.rb +69 -0
  26. data/lib/kerbaldyn/part/mixin.rb +1 -0
  27. data/lib/kerbaldyn/part/mixin/fuel_tank.rb +35 -0
  28. data/lib/kerbaldyn/part/rcs_fuel_tank.rb +10 -0
  29. data/lib/kerbaldyn/part/solid_rocket.rb +30 -0
  30. data/lib/kerbaldyn/part_library.rb +55 -0
  31. data/lib/kerbaldyn/planetoid.rb +214 -0
  32. data/lib/kerbaldyn/version.rb +13 -0
  33. data/spec/bielliptic_orbital_maneuver_spec.rb +60 -0
  34. data/spec/constants_spec.rb +9 -0
  35. data/spec/hohmann_orbital_maneuver_spec.rb +385 -0
  36. data/spec/options_processor_spec.rb +33 -0
  37. data/spec/orbit_spec.rb +357 -0
  38. data/spec/orbital_maneuver_base_spec.rb +74 -0
  39. data/spec/parameter_attributes_spec.rb +82 -0
  40. data/spec/part_library_spec.rb +21 -0
  41. data/spec/part_spec.rb +218 -0
  42. data/spec/planetoid_spec.rb +117 -0
  43. data/spec/spec_helper.rb +110 -0
  44. data/spec/support/parts/RCSFuelTank/part.cfg +42 -0
  45. data/spec/support/parts/fuelTank/part.cfg +48 -0
  46. data/spec/support/parts/liquidEngine1/part.cfg +60 -0
  47. data/spec/support/parts/liquidEngine2/part.cfg +64 -0
  48. data/spec/support/parts/solidBooster/part.cfg +67 -0
  49. data/spec/support/planet_test_data.json +340 -0
  50. metadata +95 -0
@@ -0,0 +1,10 @@
1
+ module KerbalDyn
2
+ module Part
3
+ # A class respresenting all RCS fuel tanks.
4
+ #
5
+ # Most of its methods are defined in the Mixin::FuelTank module.
6
+ class RCSFuelTank < Base
7
+ include Mixin::FuelTank
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,30 @@
1
+ module KerbalDyn
2
+ module Part
3
+ # A class respresenting booster rockets
4
+ #
5
+ # Some of its methods are defined in the Mixin::FuelTank module.
6
+ class SolidRocket < Base
7
+ include Mixin::FuelTank
8
+
9
+ def thrust
10
+ return self['thrust'].to_f
11
+ end
12
+
13
+ def heat_production
14
+ return self['heatProduction'].to_f
15
+ end
16
+
17
+ # The fuel consumption in m^3/s
18
+ #
19
+ # To convert to liters/second, multiply by 1000
20
+ def fuel_consumption
21
+ return self['fuelConsumption'].to_f / 1000.0
22
+ end
23
+
24
+ def burn_time
25
+ return self.capacity / self.fuel_consumption
26
+ end
27
+
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,55 @@
1
+ require 'pathname'
2
+
3
+ module KerbalDyn
4
+ # PartLibrary is an array like list of Part objects with convenience methods
5
+ # for common operations.
6
+ class PartLibrary
7
+ include Enumerable
8
+
9
+ # Loads parts from a given directory into the library.
10
+ def self.load_parts(directory)
11
+ dir = Pathname.new(directory)
12
+ raise nil unless dir.directory?
13
+
14
+ parts = dir.children.reject do |part_dir|
15
+ # Reject if it starts with a dot or is not a directory.
16
+ (part_dir.basename.to_s =~ /^\./) || !part_dir.directory?
17
+ end.inject([]) do |parts, part_dir|
18
+ parts << Part::Base.load_part(part_dir)
19
+ end
20
+
21
+ return self.new(parts)
22
+ end
23
+
24
+ # Initialize with an array of parts or a list of parts.
25
+ def initialize(*parts)
26
+ @parts = parts.to_a.flatten
27
+ end
28
+
29
+ # Iterates over each part using a block.
30
+ #
31
+ # This is the root for all Enumerable methods.
32
+ def each(&block)
33
+ return @parts.each(&block)
34
+ end
35
+
36
+ # The lenght
37
+ def length
38
+ return @parts.length
39
+ end
40
+
41
+ # Returns the part with the given index or directory name
42
+ def [](val)
43
+ case val
44
+ when Numeric
45
+ return @parts[val]
46
+ else
47
+ return @parts.find {|part| part.directory_name == val}
48
+ end
49
+ end
50
+
51
+ end
52
+ end
53
+
54
+ #$lib = KerbalDyn::PartLibrary.load_parts("/Users/jreinecke/Downloads/NovaPunch1_3beta/NovaPunch/Parts")
55
+ #$lib = KerbalDyn::PartLibrary.load_parts("/Users/jreinecke/Downloads/KSPDefaultParts")
@@ -0,0 +1,214 @@
1
+ module KerbalDyn
2
+
3
+ # Planetoid is the superclass of any planetary object. It is primarily used
4
+ # to build existing planetoids via factory methods to calculate orbit
5
+ # characteristics around the planetoid.
6
+ #
7
+ # Most interesting parameters are included through the DerivedParameters module.
8
+ class Planetoid < Body
9
+
10
+ # For data read in from data files, this private method DRYs the process.
11
+ def self.make(planet_ref)
12
+ data = Data.fetch(:planet_data)[planet_ref][:planetoid]
13
+ name = data[:name]
14
+ parameters = data.reject {|k,v| k == :name}
15
+ return self.new(name, parameters).freeze
16
+ end
17
+
18
+ class << self
19
+ private :make
20
+ end
21
+
22
+ # :category: Library Methods
23
+ # The Kerbal Sun.
24
+ def self.kerbol
25
+ # TODO: Refactor to calculate these from data on-the-fly (need to output that kind of data first).
26
+ #
27
+ # Grav Parameter calculated from semimajor axis and velocity vector dumps via mu = a*v**2, and is good to around the 15th digit.
28
+ # Radius calculated by subtracting Kerbin Apa from Apr in data dump
29
+ return @kerbol ||= self.new('Kerbol', :gravitational_parameter => 1172332794832492300.0, :radius => 261600000).freeze
30
+ end
31
+
32
+ # :category: Library Methods
33
+ #The Earth-like Homeworld.
34
+ def self.kerbin
35
+ return @kerbin ||= make(__method__)
36
+ end
37
+
38
+ # :category: Library Methods
39
+ # The Moon equivalient; orbits Kerbin.
40
+ def self.mun
41
+ return @mun ||= make(__method__)
42
+ end
43
+
44
+ # :category: Library Methods
45
+ # A small outter moon of Kerbin; no Solar System equivalent.
46
+ def self.minmus
47
+ return @minmus ||= make(__method__)
48
+ end
49
+
50
+ # :category: Library Methods
51
+ # The inner planet (Mercury like)
52
+ def self.moho
53
+ return @moho ||= make(__method__)
54
+ end
55
+
56
+ # :category: Library Methods
57
+ # The second planet (Venus like)
58
+ def self.eve
59
+ return @eve ||= make(__method__)
60
+ end
61
+
62
+ # :category: Library Methods
63
+ # The asteroid moon of Gilly
64
+ def self.gilly
65
+ return @gilly ||= make(__method__)
66
+ end
67
+
68
+ # :category: Library Methods
69
+ # The fourth planet (Mars like)
70
+ def self.duna
71
+ return @duna ||= make(__method__)
72
+ end
73
+
74
+ # :category: Library Methods
75
+ # The moon of Duna
76
+ def self.ike
77
+ return @ike ||= make(__method__)
78
+ end
79
+
80
+ # :category: Library Methods
81
+ # The fifth planet (Jupiter like)
82
+ def self.jool
83
+ return @jool ||= make(__method__)
84
+ end
85
+
86
+ # :category: Library Methods
87
+ # The first moon of Jool, ocean moon with atmosphere
88
+ def self.laythe
89
+ return @laythe ||= make(__method__)
90
+ end
91
+
92
+ # :category: Library Methods
93
+ # The second moon of Jool, ice moon
94
+ def self.vall
95
+ return @vall ||= make(__method__)
96
+ end
97
+
98
+ # :category: Library Methods
99
+ # The third moon of Jool, rocky
100
+ def self.tylo
101
+ return @tylo ||= make(__method__)
102
+ end
103
+
104
+ # :category: Library Methods
105
+ # The captured asteroid around Jool.
106
+ def self.bop
107
+ return @bop ||= make(__method__)
108
+ end
109
+
110
+ def initialize(name, options={})
111
+ super
112
+ end
113
+
114
+ alias_parameter :radius, :bounding_sphere_radius
115
+
116
+ # Returns the gravtiational_parameter (G*M) of the planetoid.
117
+ def gravitational_parameter
118
+ Constants::G * self.mass
119
+ end
120
+
121
+ # Sets the gravitational parameter (G*M) by deriving the mass and setting it.
122
+ def gravitational_parameter=(mu)
123
+ self.send(:mass=, mu / Constants::G)
124
+ end
125
+ private :gravitational_parameter=
126
+
127
+ # The rotational period of this body around its axis.
128
+ def rotational_period
129
+ return 2.0 * Math::PI / self.angular_velocity
130
+ end
131
+
132
+ # Set the rotational period of this body.
133
+ def rotational_period=(period)
134
+ self.angular_velocity = period && (2.0 * Math::PI / period)
135
+ end
136
+
137
+ # Returns the gravitational accelaration at a given radius from the
138
+ # center of the planet.
139
+ def gravitational_acceleration(r)
140
+ return self.gravitational_parameter / r**2
141
+ end
142
+
143
+ # Returns the surface gravity (acceleration) of this planetoid.
144
+ #
145
+ # If a value is given, then this is used as the height above the surface
146
+ # to calculate the gravity.
147
+ def surface_gravity(h=0.0)
148
+ return self.gravitational_acceleration( self.radius + h )
149
+ end
150
+
151
+ # The volume of the planetoid.
152
+ def volume
153
+ return (4.0/3.0) * Math::PI * self.radius**3
154
+ end
155
+
156
+ # The density of the planetoid.
157
+ def density
158
+ return self.mass / self.volume
159
+ end
160
+
161
+ # Equitorial linear velocity of the planetoid.
162
+ #
163
+ # If a value is gien, then this is used as the height above the surface for
164
+ # the calculation.
165
+ def equitorial_velocity(h=0.0)
166
+ return self.angular_velocity * (self.radius + h)
167
+ end
168
+
169
+ # Equitorial linear velocity of the planetoid.
170
+ #
171
+ # If a value is gien, then this is used as the height above the surface for
172
+ # the calculation.
173
+ def equitorial_velocity(h=0.0)
174
+ return self.angular_velocity * (self.radius + h)
175
+ end
176
+
177
+ # Calculates the escape velocity of the planetoid.
178
+ def escape_velocity
179
+ return Math.sqrt( 2.0 * self.gravitational_parameter / self.radius )
180
+ end
181
+
182
+ # ===== Orbit Library Methods =====
183
+
184
+ def geostationary_orbit
185
+ return Orbit.geostationary_orbit(self)
186
+ end
187
+
188
+ def circular_orbit(radius)
189
+ return Orbit.circular_orbit(self, radius)
190
+ end
191
+
192
+ def circular_orbit_of_period(period)
193
+ return Orbit.circular_orbit_of_period(self, period)
194
+ end
195
+
196
+ def escape_orbit(periapsis)
197
+ return Orbit.escape_orbit(self, periapsis)
198
+ end
199
+
200
+ def self.orbit(options={})
201
+ return Orbit.new(self, options)
202
+ end
203
+
204
+ # ===== ALIASES =====
205
+
206
+ alias_method :m, :mass
207
+ alias_method :r, :radius
208
+ alias_method :t, :rotational_period
209
+ alias_method :g, :surface_gravity
210
+ alias_method :rho, :density
211
+ alias_method :mu, :gravitational_parameter
212
+
213
+ end
214
+ end
@@ -0,0 +1,13 @@
1
+ module KerbalDyn
2
+ class Version
3
+ MAJOR = 0
4
+ MINOR = 7
5
+ TINY = 0
6
+
7
+ def self.to_s
8
+ return [MAJOR, MINOR, TINY].join('.')
9
+ end
10
+ end
11
+
12
+ VERSION = Version.to_s
13
+ end
@@ -0,0 +1,60 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe KerbalDyn::OrbitalManeuver::Bielliptic do
4
+
5
+ describe 'Test Maneuver' do
6
+
7
+ before(:all) do
8
+ @planetoid = KerbalDyn::Planetoid.new('Gallifrey', :gravitational_parameter => 2.0**12, :radius => 2.0)
9
+ end
10
+
11
+ [
12
+ {:name => 'Low Transfer Up', :r1 => 16.0, :r2 => 64.0, :rt => 4.0, :delta_v => 14.563217871508062, :delta_vs => [-5.88071148746, 3.42648374633, 5.25602263772], :times => [0.0, 1.55227941653, 11.2839695983]},
13
+ {:name => 'Med Transfer Up', :r1 => 16.0, :r2 => 64.0, :rt => 32.0, :delta_v => 7.769576954455817, :delta_vs => [ 2.47520861407, 3.82634098781, 1.46802735258], :times => [0.0, 5.77147423573, 22.0956685138]},
14
+ {:name => 'High Transfer Up', :r1 => 16.0, :r2 => 64.0, :rt => 256.0, :delta_v => 9.228940857774582, :delta_vs => [ 5.95181889824, 1.15783344699, -2.11928851254], :times => [0.0, 77.8535214542, 177.199404112]},
15
+ ].each do |test_case|
16
+
17
+ describe test_case[:name] do
18
+
19
+ before(:all) do
20
+ @initial_orbit = KerbalDyn::Orbit.new(@planetoid, :radius => test_case[:r1])
21
+ @final_orbit = KerbalDyn::Orbit.new(@planetoid, :radius => test_case[:r2])
22
+ @bielliptic = KerbalDyn::OrbitalManeuver::Bielliptic.new(@initial_orbit, @final_orbit, :transfer_radius => test_case[:rt])
23
+ end
24
+
25
+ it "should have the right transfer radius" do
26
+ @bielliptic.transfer_radius.should be_within_six_sigma_of( test_case[:rt] )
27
+ end
28
+
29
+ it "should calculate the right delta_vs" do
30
+ test_case[:delta_vs].zip( @bielliptic.delta_velocities ).each do |expected, actual|
31
+ actual.should be_within_six_sigma_of(expected)
32
+ end
33
+ end
34
+
35
+ it "should calculate the right delta v" do
36
+ @bielliptic.delta_v.should > 0.0
37
+ @bielliptic.delta_v.should be_within_six_sigma_of( test_case[:delta_v] )
38
+ end
39
+
40
+ it "should calculate the right times" do
41
+ test_case[:times].zip( @bielliptic.times ).each do |expected, actual|
42
+ if( expected.abs <= 1e-9 )
43
+ expected.should be_within(1e-9).of(expected)
44
+ else
45
+ actual.should be_within_six_sigma_of(expected)
46
+ end
47
+ end
48
+ end
49
+
50
+ it "should calculate the right delta_anomaly" do
51
+ @bielliptic.delta_mean_anomaly.should be_within_six_sigma_of( 2.0 * Math::PI )
52
+ end
53
+
54
+ end
55
+
56
+ end
57
+
58
+ end
59
+
60
+ end
@@ -0,0 +1,9 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe KerbalDyn::Constants do
4
+
5
+ it "should define Newton's gravitational constant" do
6
+ KerbalDyn::Constants::G.should be_kind_of(Numeric)
7
+ end
8
+
9
+ end
@@ -0,0 +1,385 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe KerbalDyn::OrbitalManeuver::Hohmann do
4
+
5
+ # I'm purposefully violating a purist OrbitalManeuver::Base Unit Test for the sake of time,
6
+ # since the necessary tests involve implementing the same two methods that that Hohmann does.
7
+ describe "Standard Hohmann, including testing OrbitalManeuver::Base methods with a real transfer." do
8
+
9
+ before(:all) do
10
+ # Set all the testable data with hard-set values.
11
+ @initial = {:mean_velocity => 16.0, :semimajor_axis => 16.0}
12
+ @final = {:mean_velocity => 8.0, :semimajor_axis => 64.0}
13
+ @transfer = {:periapsis => 16.0, :periapsis_velocity => 20.2385770251, :apoapsis => 64.0, :apoapsis_velocity => 5.05964425627}
14
+ @time = 12.41823533225
15
+ @delta_vs = [4.23857702508, 2.94035574373]
16
+ @delta_v = 7.178932768808223
17
+
18
+ # Build the test transfer object up from basic pieces.
19
+ # TODO: Stub these classes to make a truly independent class.
20
+ @planetoid = KerbalDyn::Planetoid.new('Gallifrey', :gravitational_parameter => 2.0**12, :radius => 2.0)
21
+ @initial_orbit = KerbalDyn::Orbit.new(@planetoid, :radius => @initial[:semimajor_axis])
22
+ @final_orbit = KerbalDyn::Orbit.new(@planetoid, :radius => @final[:semimajor_axis])
23
+ @hohmann = KerbalDyn::OrbitalManeuver::Hohmann.new(@initial_orbit, @final_orbit)
24
+ end
25
+
26
+ it 'should produce the transfer orbit' do
27
+ transfer_orbit = @hohmann.transfer_orbit
28
+ @transfer.each {|k,v| transfer_orbit.send(k).should be_within_six_sigma_of(v)}
29
+ end
30
+
31
+ it 'should produce the list of orbits' do
32
+ orbits = @hohmann.orbits
33
+
34
+ orbits.length.should == 3
35
+ orbits[0].should == @hohmann.initial_orbit
36
+ orbits[1].should == @hohmann.transfer_orbit
37
+ orbits[2].should == @hohmann.final_orbit
38
+ end
39
+
40
+ it 'should produce the list of burn events' do
41
+ burn_events = @hohmann.burn_events
42
+
43
+ burn_events.length.should == 2
44
+
45
+ burn_events.first.tap do |be|
46
+ be.initial_velocity.should be_within_six_sigma_of( @initial[:mean_velocity] )
47
+ be.final_velocity.should be_within_six_sigma_of( @transfer[:periapsis_velocity] )
48
+ be.orbital_radius.should be_within_six_sigma_of( @initial[:semimajor_axis] )
49
+ be.time.should be_within(1e-9).of(0.0)
50
+ be.mean_anomaly.should be_within(1e-9).of(0.0)
51
+ end
52
+
53
+ burn_events.last.tap do |be|
54
+ be.initial_velocity.should be_within_six_sigma_of( @transfer[:apoapsis_velocity] )
55
+ be.final_velocity.should be_within_six_sigma_of( @final[:mean_velocity] )
56
+ be.orbital_radius.should be_within_six_sigma_of( @final[:semimajor_axis] )
57
+ be.time.should be_within_six_sigma_of(@time)
58
+ be.mean_anomaly.should be_within_six_sigma_of(Math::PI)
59
+ end
60
+ end
61
+
62
+ it 'should know if it is moving ot a higher orbit' do
63
+ @hohmann.moving_to_higher_orbit?.should be_true
64
+ end
65
+
66
+ it 'should produce the list of before/after velocities' do
67
+ velocities = @hohmann.velocities
68
+ expected_velocities = [ [@initial[:mean_velocity], @transfer[:periapsis_velocity]], [@transfer[:apoapsis_velocity], @final[:mean_velocity]] ]
69
+
70
+ [0,1].each do |i|
71
+ [0,1].each do |j|
72
+ velocities[i][j].should be_within_six_sigma_of(expected_velocities[i][j])
73
+ end
74
+ end
75
+ end
76
+
77
+ it 'should produce the list of delta velocities' do
78
+ @hohmann.delta_velocities.zip(@delta_vs).each do |actual, expected|
79
+ actual.should be_within_six_sigma_of(expected)
80
+ end
81
+ end
82
+
83
+ it 'should produce the list of impulse burn time stamps' do
84
+ @hohmann.times.first.should be_within(1e-9).of(0.0)
85
+ @hohmann.times.last.should be_within_six_sigma_of(@time)
86
+ end
87
+
88
+ it 'should produce the list of impulse burn radii' do
89
+ @hohmann.orbital_radii.zip([@initial[:semimajor_axis], @final[:semimajor_axis]]).each do |actual, expected|
90
+ actual.should be_within_six_sigma_of(expected)
91
+ end
92
+ end
93
+
94
+ it 'should produce the list of impulse burn mean anomalies' do
95
+ @hohmann.mean_anomalies.first.should be_within(1e-9).of(0.0)
96
+ @hohmann.mean_anomalies.last.should be_within_six_sigma_of(Math::PI)
97
+ end
98
+
99
+ it 'should produce the final delta velocity' do
100
+ @hohmann.delta_velocity.should >= 0.0
101
+ @hohmann.delta_velocity.should be_within_six_sigma_of(@delta_v)
102
+ @hohmann.delta_v.should == @hohmann.delta_velocity
103
+ end
104
+
105
+ it 'should produce the delta_t' do
106
+ @hohmann.delta_t.should be_within_six_sigma_of(@time)
107
+ end
108
+
109
+ it 'should produce the delta anomaly' do
110
+ @hohmann.delta_mean_anomaly.should be_within_six_sigma_of(Math::PI)
111
+ end
112
+
113
+ it 'should produce the mean lead angle' do
114
+ @hohmann.mean_lead_angle.should be_within_six_sigma_of(1.5893132370591518)
115
+ end
116
+
117
+ it 'should produce the mean lead time' do
118
+ @hohmann.mean_lead_time.should be_within_six_sigma_of(5.364425222994782)
119
+ end
120
+
121
+ it 'should produce the relative anomaly delta' do
122
+ @hohmann.relative_anomaly_delta.should be_within_six_sigma_of(-5.497787143782138)
123
+ end
124
+
125
+ end
126
+
127
+ describe "Descending Hohmann" do
128
+
129
+ before(:all) do
130
+ # Set all the testable data with hard-set values.
131
+ @initial = {:mean_velocity => 8.0, :semimajor_axis => 64.0}
132
+ @final = {:mean_velocity => 16.0, :semimajor_axis => 16.0}
133
+ @transfer = {:periapsis => 16.0, :periapsis_velocity => 20.2385770251, :apoapsis => 64.0, :apoapsis_velocity => 5.05964425627}
134
+ @time = 12.41823533225
135
+ @delta_vs = [-2.94035574373, -4.23857702508]
136
+ @delta_v = 7.178932768808223
137
+
138
+ # Build the test transfer object up from basic pieces.
139
+ # TODO: Stub these classes to make a truly independent class.
140
+ @planetoid = KerbalDyn::Planetoid.new('Gallifrey', :gravitational_parameter => 2.0**12, :radius => 2.0)
141
+ @initial_orbit = KerbalDyn::Orbit.new(@planetoid, :radius => @initial[:semimajor_axis])
142
+ @final_orbit = KerbalDyn::Orbit.new(@planetoid, :radius => @final[:semimajor_axis])
143
+ @hohmann = KerbalDyn::OrbitalManeuver::Hohmann.new(@initial_orbit, @final_orbit)
144
+ end
145
+
146
+ it 'should produce the transfer orbit' do
147
+ transfer_orbit = @hohmann.transfer_orbit
148
+ @transfer.each {|k,v| transfer_orbit.send(k).should be_within_six_sigma_of(v)}
149
+ end
150
+
151
+ it 'should produce the list of orbits' do
152
+ orbits = @hohmann.orbits
153
+
154
+ orbits.length.should == 3
155
+ orbits[0].should == @hohmann.initial_orbit
156
+ orbits[1].should == @hohmann.transfer_orbit
157
+ orbits[2].should == @hohmann.final_orbit
158
+ end
159
+
160
+ it 'should produce the list of burn events' do
161
+ burn_events = @hohmann.burn_events
162
+
163
+ burn_events.length.should == 2
164
+
165
+ burn_events.first.tap do |be|
166
+ be.initial_velocity.should be_within_six_sigma_of( @initial[:mean_velocity] )
167
+ be.final_velocity.should be_within_six_sigma_of( @transfer[:apoapsis_velocity] )
168
+ be.orbital_radius.should be_within_six_sigma_of( @initial[:semimajor_axis] )
169
+ be.time.should be_within(1e-9).of(0.0)
170
+ be.mean_anomaly.should be_within(1e-9).of(0.0)
171
+ end
172
+
173
+ burn_events.last.tap do |be|
174
+ be.initial_velocity.should be_within_six_sigma_of( @transfer[:periapsis_velocity] )
175
+ be.final_velocity.should be_within_six_sigma_of( @final[:mean_velocity] )
176
+ be.orbital_radius.should be_within_six_sigma_of( @final[:semimajor_axis] )
177
+ be.time.should be_within_six_sigma_of(@time)
178
+ be.mean_anomaly.should be_within_six_sigma_of(Math::PI)
179
+ end
180
+ end
181
+
182
+ it 'should know if it is moving ot a higher orbit' do
183
+ @hohmann.moving_to_higher_orbit?.should be_false
184
+ end
185
+
186
+ it 'should produce the list of delta velocities' do
187
+ @hohmann.delta_velocities.zip(@delta_vs).each do |actual, expected|
188
+ actual.should be_within_six_sigma_of(expected)
189
+ end
190
+ end
191
+
192
+ it 'should produce the final delta velocity' do
193
+ @hohmann.delta_velocity.should >= 0.0
194
+ @hohmann.delta_velocity.should be_within_six_sigma_of(@delta_v)
195
+ @hohmann.delta_v.should == @hohmann.delta_velocity
196
+ end
197
+
198
+ end
199
+
200
+ describe "Earth lower-to-higher orbit" do
201
+
202
+ before(:all, &BeforeFactory.earth)
203
+
204
+ before(:all) do
205
+ # The starting circ orbit.
206
+ @r1 = 6678e3
207
+ @v1 = 7.73e3
208
+
209
+ # The final circ orbit.
210
+ @r2 = 42164e3
211
+ @v2 = 3.07e3
212
+
213
+ # The transfer orbit apoapsis and periapsis velocities
214
+ @vp = 10.15e3
215
+ @va = 1.61e3
216
+
217
+ # The deltas
218
+ @dv1 = 2.42e3
219
+ @dv2 = 1.46e3
220
+ @dv = 3.88e3
221
+
222
+
223
+ @orbit1 = KerbalDyn::Orbit.new(@planetoid, :semimajor_axis => @r1, :eccentricity => 0.0)
224
+ @orbit2 = KerbalDyn::Orbit.new(@planetoid, :semimajor_axis => @r2, :eccentricity => 0.0)
225
+
226
+ @hohmann = KerbalDyn::OrbitalManeuver::Hohmann.new(@orbit1, @orbit2)
227
+ end
228
+
229
+ it 'should calculate burn one from velocity' do
230
+ @hohmann.initial_orbit.apoapsis_velocity.should be_within_two_sigma_of(@v1)
231
+ @hohmann.velocities[0][0].should be_within_two_sigma_of(@v1)
232
+ end
233
+
234
+ it 'should calculate burn one to velocity' do
235
+ @hohmann.transfer_orbit.periapsis_velocity.should be_within_two_sigma_of(@vp)
236
+ @hohmann.velocities[0][1].should be_within_two_sigma_of(@vp)
237
+ end
238
+
239
+ it 'should calculate burn two from velocity' do
240
+ @hohmann.transfer_orbit.apoapsis_velocity.should be_within_two_sigma_of(@va)
241
+ @hohmann.velocities[1][0].should be_within_two_sigma_of(@va)
242
+ end
243
+
244
+ it 'should calculate burn two to velocity' do
245
+ @hohmann.final_orbit.apoapsis_velocity.should be_within_two_sigma_of(@v2)
246
+ @hohmann.velocities[1][1].should be_within_two_sigma_of(@v2)
247
+ end
248
+
249
+ it 'should calculate burn one delta velocity' do
250
+ @hohmann.delta_velocities[0].should be_within_two_sigma_of(@dv1)
251
+ end
252
+
253
+ it 'should calculate burn two delta velocity' do
254
+ @hohmann.delta_velocities[1].should be_within_two_sigma_of(@dv2)
255
+ end
256
+
257
+ it 'should calculate total delta velocity' do
258
+ @hohmann.delta_v.should be_within_two_sigma_of(@dv)
259
+ end
260
+
261
+ it 'should calculate the lead angle for intercept' do
262
+ # Value calculated several times, several different ways to cross-check
263
+ @hohmann.mean_lead_angle.should be_within_two_sigma_of(1.756)
264
+ end
265
+
266
+ it 'should calculate the lead time for intercept' do
267
+ # Value calculated
268
+ @hohmann.mean_lead_time.should be_within_two_sigma_of(4175.58)
269
+ end
270
+
271
+ it 'should calculate the realtive anomaly change per complete orbit' do
272
+ delta_theta = -5.887 #separation change per complete rotation ofinitial orbit as compared to something in the final orbit; negative since the lower orbit should close the gap.
273
+ @hohmann.relative_anomaly_delta.should be_within_two_sigma_of(delta_theta)
274
+ end
275
+
276
+ it 'should recognize as a move to a lower orbit' do
277
+ @hohmann.moving_to_higher_orbit?.should be_true
278
+ end
279
+
280
+ end
281
+
282
+ describe "Earth higher-to-lower orbit" do
283
+
284
+ before(:all, &BeforeFactory.earth)
285
+
286
+ before(:all) do
287
+ # The final circ orbit.
288
+ @r1 = 42164e3
289
+ @v1 = 3.07e3
290
+
291
+ # The starting circ orbit.
292
+ @r2 = 6678e3
293
+ @v2 = 7.73e3
294
+
295
+ # The transfer orbit apoapsis and periapsis velocities
296
+ @vp = 10.15e3
297
+ @va = 1.61e3
298
+
299
+ # The deltas
300
+ @dv1 = -1.46e3
301
+ @dv2 = -2.42e3
302
+ @dv = 3.88e3
303
+
304
+
305
+ @orbit1 = KerbalDyn::Orbit.new(@planetoid, :semimajor_axis => @r1, :eccentricity => 0.0)
306
+ @orbit2 = KerbalDyn::Orbit.new(@planetoid, :semimajor_axis => @r2, :eccentricity => 0.0)
307
+
308
+ @hohmann = KerbalDyn::OrbitalManeuver::Hohmann.new(@orbit1, @orbit2)
309
+ end
310
+
311
+ it 'should calculate burn one from velocity' do
312
+ @hohmann.initial_orbit.apoapsis_velocity.should be_within_two_sigma_of(@v1)
313
+ @hohmann.velocities[0][0].should be_within_two_sigma_of(@v1)
314
+ end
315
+
316
+ it 'should calculate burn one to velocity' do
317
+ @hohmann.transfer_orbit.apoapsis_velocity.should be_within_two_sigma_of(@va)
318
+ @hohmann.velocities[0][1].should be_within_two_sigma_of(@va)
319
+ end
320
+
321
+ it 'should calculate burn two from velocity' do
322
+ @hohmann.transfer_orbit.periapsis_velocity.should be_within_two_sigma_of(@vp)
323
+ @hohmann.velocities[1][0].should be_within_two_sigma_of(@vp)
324
+ end
325
+
326
+ it 'should calculate burn two to velocity' do
327
+ @hohmann.final_orbit.apoapsis_velocity.should be_within_two_sigma_of(@v2)
328
+ @hohmann.velocities[1][1].should be_within_two_sigma_of(@v2)
329
+ end
330
+
331
+ it 'should calculate burn one delta velocity' do
332
+ @hohmann.delta_velocities[0].should be_within_two_sigma_of(@dv1)
333
+ end
334
+
335
+ it 'should calculate burn two delta velocity' do
336
+ @hohmann.delta_velocities[1].should be_within_two_sigma_of(@dv2)
337
+ end
338
+
339
+ it 'should calculate total delta velocity' do
340
+ @hohmann.delta_v.should be_within_two_sigma_of(@dv)
341
+ end
342
+
343
+ it 'should calculate the lead angle for intercept' do
344
+ # Value calculated several times, several different ways to cross-check
345
+ # This is a large negative number because the lower orbit gets nearly 3.5 periods in during the transfer, which is nearly 22 radians.
346
+ @hohmann.mean_lead_angle.should be_within_two_sigma_of(-18.83)
347
+ end
348
+
349
+ it 'should calculate the lead time for intercept' do
350
+ # The lead angle, -18.828, is so insanely close to three times around the circle, that about 20s is actually the lead time!
351
+ @hohmann.mean_lead_time.should be_within_two_sigma_of(19.73)
352
+ end
353
+
354
+ it 'should calculate the realtive anomaly change per complete orbit' do
355
+ # This is large because the inner planet gets nearly 16 loops (nearly 100 radians) in for each of the slow orbit's
356
+ delta_theta = 93.40
357
+ @hohmann.relative_anomaly_delta.should be_within_two_sigma_of(delta_theta)
358
+ end
359
+
360
+ it 'should recognize as a move to a lower orbit' do
361
+ @hohmann.moving_to_higher_orbit?.should be_false
362
+ end
363
+
364
+ end
365
+
366
+ describe 'Null transition' do
367
+ before(:all, &BeforeFactory.earth)
368
+
369
+ before(:all) do
370
+ @orbit1 = KerbalDyn::Orbit.new(@planetoid, :semimajor_axis => 1000e3, :eccentricity => 0.0)
371
+ @orbit2 = KerbalDyn::Orbit.new(@planetoid, :semimajor_axis => 1000e3, :eccentricity => 0.0)
372
+ @hohmann = KerbalDyn::OrbitalManeuver::Hohmann.new(@orbit1, @orbit2)
373
+ end
374
+
375
+ it 'should have zero relative anomaly delta' do
376
+ @hohmann.relative_anomaly_delta.should be_within(1e-6).of(0.0)
377
+ end
378
+
379
+ it 'should have zero lead angle' do
380
+ @hohmann.mean_lead_angle.should be_within(1e-6).of(0.0)
381
+ end
382
+
383
+ end
384
+
385
+ end