kerbaldyn 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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