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,33 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe KerbalDyn::Mixin::OptionsProcessor do
4
+
5
+ class OptionsProcessorTestClass
6
+ include KerbalDyn::Mixin::OptionsProcessor
7
+ DEFAULTS = {:foo => :default}
8
+
9
+ def initialize(opts={})
10
+ process_options(opts, DEFAULTS)
11
+ end
12
+
13
+ attr_accessor :foo
14
+ attr_accessor :alpha
15
+ end
16
+
17
+ it 'should set valid keys' do
18
+ obj = OptionsProcessorTestClass.new(:foo => :bar, :alpha => :beta)
19
+ obj.foo.should == :bar
20
+ obj.alpha.should == :beta
21
+ end
22
+
23
+ it 'should error on invalid keys' do
24
+ lambda {OptionsProcessorTestClass.new(:miss => :sunshine)}.should raise_error(NoMethodError)
25
+ end
26
+
27
+ it 'should use a block as a defaults hook' do
28
+ obj = OptionsProcessorTestClass.new
29
+ obj.foo.should == :default
30
+ obj.alpha.should == nil
31
+ end
32
+
33
+ end
@@ -0,0 +1,357 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe KerbalDyn::Orbit do
4
+
5
+ describe 'Circularize' do
6
+
7
+ before(:all) do
8
+ @planet = KerbalDyn::Planetoid.new('Test Planet', :gravitational_parameter => 3.5316e12, :radius => 600000)
9
+ @satellite = KerbalDyn::Planetoid.test_particle
10
+ @elliptical = KerbalDyn::Orbit.new(@planet, :secondary_body => @satellite, :periapsis => 700000.0, :apoapsis => 3e6, :inclination => 0.2)
11
+ @circular = @elliptical.circularize
12
+ end
13
+
14
+ it 'should have the same primary body' do
15
+ @circular.primary_body.should == @elliptical.primary_body
16
+ end
17
+
18
+ it 'should have the same secondary body' do
19
+ @circular.secondary_body.should == @elliptical.secondary_body
20
+ end
21
+
22
+ it 'should be circular' do
23
+ @circular.kind.should == :circular
24
+ end
25
+
26
+ it 'should have the same semimajor_axis' do
27
+ @circular.semimajor_axis.should == @elliptical.semimajor_axis
28
+ end
29
+
30
+ it 'should have zero inclination' do
31
+ @circular.inclination.should be_within(1e-9).of(0.0)
32
+ end
33
+
34
+ it 'should have the same period' do
35
+ @circular.period.should be_within_six_sigma_of(@elliptical.period)
36
+ end
37
+
38
+ it 'should have a periapsis and apoapsis velocity that is the same as original mean velocity.' do
39
+ @circular.mean_velocity.should be_within_six_sigma_of(@elliptical.mean_velocity)
40
+ @circular.periapsis_velocity.should be_within_six_sigma_of(@elliptical.mean_velocity)
41
+ @circular.apoapsis_velocity.should be_within_six_sigma_of(@elliptical.mean_velocity)
42
+ end
43
+
44
+ end
45
+
46
+ describe 'Orbit Types' do
47
+
48
+ describe 'Circular Orbit' do
49
+ before(:all, &BeforeFactory.earth)
50
+ before(:all, &BeforeFactory.earth_geostationary_orbit)
51
+
52
+ it 'should be closed' do
53
+ @orbit.closed?.should be_true
54
+ @orbit.open?.should be_false
55
+ end
56
+
57
+ it 'should be circular' do
58
+ @orbit.kind.should == :circular
59
+ end
60
+
61
+ it 'should have zero eccentricity' do
62
+ @orbit.eccentricity.should be_within(0.000001).of(0.0)
63
+ end
64
+
65
+ it 'should have the parent body gravitational parameter' do
66
+ @orbit.gravitational_parameter.should be_within_four_sigma_of(@gravitational_parameter)
67
+ end
68
+
69
+ it 'should have the right specific kinetic energy' do
70
+ @orbit.specific_kinetic_energy(@orbital_velocity).should be_within_four_sigma_of(@orbital_specific_kinetic_energy)
71
+ end
72
+
73
+ it 'should have the right specific potential energy' do
74
+ @orbit.specific_potential_energy(@orbital_radius).should be_within_four_sigma_of(@orbital_specific_potential_energy)
75
+ end
76
+
77
+ it 'should have total specific energy that is the sum of kinetic and poential specific energy' do
78
+ @orbit.specific_energy.should be_within_four_sigma_of( @orbital_specific_kinetic_energy + @orbital_specific_potential_energy )
79
+ end
80
+
81
+ it 'should have the right rotational momentum' do
82
+ @orbit.specific_angular_momentum.should be_within_four_sigma_of( @orbital_specific_angular_momentum )
83
+ end
84
+
85
+ it 'should have a semimajor_axis equal to the radius' do
86
+ @orbit.semimajor_axis.should be_within_six_sigma_of(@orbital_radius)
87
+ end
88
+
89
+ it 'should have a semiminor_axis equal to the radius' do
90
+ @orbit.semiminor_axis.should be_within_six_sigma_of(@orbital_radius)
91
+ end
92
+
93
+ it 'should have a periapsis equal to the radius' do
94
+ @orbit.periapsis.should be_within_six_sigma_of(@orbital_radius)
95
+ end
96
+
97
+ it 'should have an apoapsis equal to the radius' do
98
+ @orbit.apoapsis.should be_within_six_sigma_of(@orbital_radius)
99
+ end
100
+
101
+ it 'should have the expected period' do
102
+ @orbit.period.should be_within_four_sigma_of(@orbital_period)
103
+ end
104
+
105
+ it 'should have the expected mean_angular_velocity' do
106
+ @orbit.mean_angular_velocity.should be_within_four_sigma_of(2.0*Math::PI / @orbital_period)
107
+ end
108
+
109
+ it 'should have the correct periapsis_velocity' do
110
+ @orbit.periapsis_velocity.should be_within_four_sigma_of(@orbital_velocity)
111
+ end
112
+
113
+ it 'should have the correct apoapsis_velocity' do
114
+ @orbit.apoapsis_velocity.should be_within_four_sigma_of(@orbital_velocity)
115
+ end
116
+
117
+ end
118
+
119
+ describe 'Elliptical Orbit' do
120
+ before(:all, &BeforeFactory.earth)
121
+ before(:all, &BeforeFactory.geostationary_transfer_orbit)
122
+
123
+ it 'should be of kind elliptical' do
124
+ @orbit.kind.should == :elliptical
125
+ end
126
+
127
+ [
128
+ :periapsis,
129
+ :apoapsis,
130
+ :periapsis_velocity,
131
+ :apoapsis_velocity,
132
+ :eccentricity,
133
+ :semimajor_axis
134
+ ].each do |parameter|
135
+ it "should calculate the correct #{parameter} value" do
136
+ expected_value = instance_variable_get("@#{parameter}")
137
+ @orbit.send(parameter).should be_within_three_sigma_of(expected_value)
138
+ end
139
+ end
140
+
141
+ end
142
+
143
+ describe 'Parabolic Orbit' do
144
+ before(:all, &BeforeFactory.earth)
145
+ before(:all, &BeforeFactory.earth_escape_orbit)
146
+
147
+ it 'should have the correct escape velocity' do
148
+ @orbit.periapsis_velocity.should be_within_six_sigma_of(@escape_velocity)
149
+ end
150
+
151
+ it 'should be of kind :parabolic' do
152
+ @orbit.kind.should == :parabolic
153
+ end
154
+
155
+ it 'should have an eccentricity of 1' do
156
+ @orbit.eccentricity.should be_within_six_sigma_of(1.0)
157
+ end
158
+
159
+ it 'should have a total specific energy of zero' do
160
+ @orbit.specific_energy.should be_within(1e-6).of(0.0)
161
+ end
162
+
163
+ end
164
+
165
+ describe 'Hyperbolic Orbit' do
166
+ before(:all, &BeforeFactory.earth)
167
+ before(:all, &BeforeFactory.earth_hyperbolic_orbit)
168
+
169
+ it 'should be of kind :hyperbolic' do
170
+ @orbit.kind.should == :hyperbolic
171
+ end
172
+
173
+ it 'should have an eccentricity greater than 1' do
174
+ @orbit.eccentricity.should > 1.0
175
+ @orbit.eccentricity.should be_within_four_sigma_of(@eccentricity)
176
+ end
177
+
178
+ end
179
+
180
+ end
181
+
182
+ describe 'Eccentricity Independent Quantities' do
183
+
184
+ before(:all) do
185
+ @primary_planetoid = KerbalDyn::Planetoid.new('Kerbin', :mass => 5.29e22, :radius => 600e3)
186
+ @secondary_planetoid = KerbalDyn::Planetoid.new('Mun', :mass => 9.76e20, :radius => 200e3)
187
+ @semimajor_axis = 12000e3
188
+ @secondary_soi = 2430e3
189
+ @primary_soi = 59.26e6
190
+ @orbit = KerbalDyn::Orbit.new(@primary_planetoid, :secondary_body => @secondary_planetoid, :semimajor_axis => @semimajor_axis, :eccentricity => 0.0)
191
+ end
192
+
193
+ it 'should calculate SOI of primary body' do
194
+ @orbit.primary_body_sphere_of_influence.should be_within_four_sigma_of(@primary_soi)
195
+ end
196
+
197
+ it 'should cacluate SOI of secondary body' do
198
+ @orbit.secondary_body_sphere_of_influence.should be_within_four_sigma_of(@secondary_soi)
199
+ end
200
+
201
+ it 'should default to the secondary body SOI' do
202
+ @orbit.sphere_of_influence.should be_within_four_sigma_of(@secondary_soi)
203
+ end
204
+
205
+ end
206
+
207
+ describe 'Orbit Initializers and Factory Methods' do
208
+ before(:all, &BeforeFactory.earth)
209
+
210
+ describe 'constructing orbits from periapsis and periapsis velocity' do
211
+ before(:all, &BeforeFactory.geostationary_transfer_orbit)
212
+
213
+ it 'should create from the initializer' do
214
+ orbit = KerbalDyn::Orbit.new(@planetoid, :periapsis => @periapsis, :periapsis_velocity => @periapsis_velocity)
215
+ [:periapsis, :apoapsis, :periapsis_velocity, :apoapsis_velocity, :eccentricity, :semimajor_axis].each do |parameter|
216
+ orbit.send(parameter).should be_within_three_sigma_of( instance_variable_get("@#{parameter}") )
217
+ end
218
+ end
219
+
220
+ end
221
+
222
+ describe 'constructing orbits from periapsis and apoapsis' do
223
+ before(:all, &BeforeFactory.geostationary_transfer_orbit)
224
+
225
+ it 'should create from the initializer' do
226
+ orbit = KerbalDyn::Orbit.new(@planetoid, :periapsis => @periapsis, :apoapsis => @apoapsis)
227
+ [:periapsis, :apoapsis, :periapsis_velocity, :apoapsis_velocity, :eccentricity, :semimajor_axis].each do |parameter|
228
+ orbit.send(parameter).should be_within_three_sigma_of( instance_variable_get("@#{parameter}") )
229
+ end
230
+ end
231
+
232
+ end
233
+
234
+ describe 'constructing orbits from semimajor_axis and eccentricity' do
235
+ before(:all, &BeforeFactory.geostationary_transfer_orbit)
236
+
237
+ it 'should create from the initializer' do
238
+ orbit = KerbalDyn::Orbit.new(@planetoid, :semimajor_axis => @semimajor_axis, :eccentricity => @eccentricity)
239
+ [:periapsis, :apoapsis, :periapsis_velocity, :apoapsis_velocity, :eccentricity, :semimajor_axis].each do |parameter|
240
+ orbit.send(parameter).should be_within_three_sigma_of( instance_variable_get("@#{parameter}") )
241
+ end
242
+ end
243
+
244
+ end
245
+
246
+ describe 'constructing cicular orbits from a radius' do
247
+ before(:all, &BeforeFactory.earth_geostationary_orbit)
248
+
249
+ it 'should be creatable from the initializer' do
250
+ orbit = KerbalDyn::Orbit.new(@planetoid, :radius => @geostationary_orbit_radius)
251
+ orbit.eccentricity.should be_within(1e-9).of(0.0)
252
+ orbit.periapsis.should be_within_four_sigma_of(@geostationary_orbit_radius)
253
+ orbit.periapsis_velocity.should be_within_four_sigma_of(@geostationary_orbit_velocity)
254
+ orbit.apoapsis.should be_within_four_sigma_of(@geostationary_orbit_radius)
255
+ orbit.apoapsis_velocity.should be_within_four_sigma_of(@geostationary_orbit_velocity)
256
+ end
257
+
258
+ it 'should be creatable from the factory method' do
259
+ orbit = KerbalDyn::Orbit.circular_orbit(@planetoid, @geostationary_orbit_radius)
260
+ orbit.eccentricity.should be_within(1e-9).of(0.0)
261
+ orbit.periapsis.should be_within_four_sigma_of(@geostationary_orbit_radius)
262
+ orbit.periapsis_velocity.should be_within_four_sigma_of(@geostationary_orbit_velocity)
263
+ orbit.apoapsis.should be_within_four_sigma_of(@geostationary_orbit_radius)
264
+ orbit.apoapsis_velocity.should be_within_four_sigma_of(@geostationary_orbit_velocity)
265
+ end
266
+
267
+ it 'should construct using the orbital period' do
268
+ orbit = KerbalDyn::Orbit.circular_orbit_of_period(@planetoid, @orbital_period)
269
+ orbit.eccentricity.should be_within(1e-9).of(0.0)
270
+ orbit.periapsis.should be_within_four_sigma_of(@geostationary_orbit_radius)
271
+ orbit.periapsis_velocity.should be_within_four_sigma_of(@geostationary_orbit_velocity)
272
+ orbit.apoapsis.should be_within_four_sigma_of(@geostationary_orbit_radius)
273
+ orbit.apoapsis_velocity.should be_within_four_sigma_of(@geostationary_orbit_velocity)
274
+ end
275
+
276
+ end
277
+
278
+ describe 'constructing geostationary orbits' do
279
+ before(:all, &BeforeFactory.earth_geostationary_orbit)
280
+
281
+ it 'should construct geostationary orbit' do
282
+ orbit = KerbalDyn::Orbit.geostationary_orbit(@planetoid)
283
+ orbit.eccentricity.should be_within(1e-9).of(0.0)
284
+ orbit.periapsis.should be_within_four_sigma_of(@geostationary_orbit_radius)
285
+ orbit.apoapsis.should be_within_four_sigma_of(@geostationary_orbit_radius)
286
+ orbit.apoapsis_velocity.should be_within_four_sigma_of(@geostationary_orbit_velocity)
287
+ orbit.periapsis_velocity.should be_within_four_sigma_of(@geostationary_orbit_velocity)
288
+ end
289
+
290
+ end
291
+
292
+ describe 'constructing escape orbits' do
293
+
294
+ it 'should construct an escape orbit' do
295
+ @periapsis = (@planetoid.radius * 2.0) # Just picked one at pseudo-random.
296
+ orbit = KerbalDyn::Orbit.escape_orbit(@planetoid, @periapsis)
297
+ orbit.eccentricity.should be_within(1e-9).of(1.0)
298
+ orbit.specific_energy.should be_within(1e-6).of(0.0)
299
+ end
300
+
301
+ end
302
+
303
+ end
304
+
305
+ describe 'Orbit Library' do
306
+
307
+ PLANET_TEST_DATA.each do |planet_key, test_data|
308
+ data = test_data[:orbit]
309
+
310
+ describe "#{data[:name]}" do
311
+
312
+ before(:all) do
313
+ @library_method = planet_key
314
+ @orbit = KerbalDyn::Orbit.send(@library_method)
315
+ end
316
+
317
+ it 'should be memoized' do
318
+ unique_ids = [@orbit, KerbalDyn::Orbit.send(@library_method)].map {|obj| obj.object_id}.uniq
319
+ unique_ids.length.should == 1
320
+ end
321
+
322
+ it 'should be frozen' do
323
+ @orbit.should be_frozen
324
+ end
325
+
326
+ it "should have the primary_body #{data[:primary_body]}" do
327
+ @orbit.primary_body.should == KerbalDyn::Planetoid.send( data[:primary_body] )
328
+ end
329
+
330
+ it "should have the secondary_body #{data[:secondary_body]}" do
331
+ @orbit.secondary_body.should == KerbalDyn::Planetoid.send( data[:secondary_body] )
332
+ end
333
+
334
+
335
+ data.each do |property, expected_value|
336
+ next if [:primary_body, :secondary_body, :name].include?(property)
337
+ it "should have #{property} of #{expected_value}" do
338
+ value = @orbit.send(property)
339
+ case expected_value
340
+ when Float
341
+ if( expected_value.abs < 1e-15 )
342
+ value.should be_within(1e-15).of(expected_value)
343
+ else
344
+ value.should be_within_five_sigma_of(expected_value)
345
+ end
346
+ else
347
+ value.should == expected_value
348
+ end
349
+ end
350
+ end
351
+
352
+ end
353
+ end
354
+
355
+ end
356
+
357
+ end
@@ -0,0 +1,74 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe KerbalDyn::OrbitalManeuver::Base do
4
+
5
+ before(:all) do
6
+ @dummy_body = KerbalDyn::Body.test_particle
7
+ @dummy_orbit = KerbalDyn::Orbit.new(@dummy_body)
8
+
9
+ @body = KerbalDyn::Planetoid.kerbin
10
+ @orbit1 = KerbalDyn::Orbit.new(@body, :radius => 100e3)
11
+ @orbit2 = KerbalDyn::Orbit.new(@body, :radius => 12000e3)
12
+ @maneuver = KerbalDyn::OrbitalManeuver::Base.new(@orbit1, @orbit2)
13
+ end
14
+
15
+ it 'should check that the initial and final orbits are orbiting the same body' do
16
+ @orbit1.primary_body.should == @orbit2.primary_body
17
+ lambda { KerbalDyn::OrbitalManeuver::Base.new(@orbit1, @orbit2) }.should_not raise_error
18
+
19
+ @orbit1.primary_body.should_not == @dummy_orbit.primary_body
20
+ lambda { KerbalDyn::OrbitalManeuver::Base.new(@orbit1, @dummy_orbit) }.should raise_error
21
+ end
22
+
23
+ it 'should have an initial_orbit' do
24
+ @maneuver.initial_orbit.should == @orbit1
25
+ end
26
+
27
+ it 'should have a final_orbit' do
28
+ @maneuver.final_orbit.should == @orbit2
29
+ end
30
+
31
+ it 'should have delta_time' do
32
+ @maneuver.should respond_to(:delta_time)
33
+ end
34
+
35
+ it 'should map delta_t to delta_time' do
36
+ # Override delta_time just like a subclass would.
37
+ maneuver = @maneuver.dup.tap {|m| m.stub(:delta_time){:jeb}}
38
+
39
+ # The real test.
40
+ maneuver.delta_t.should == maneuver.delta_time
41
+ end
42
+
43
+ describe 'delta_v' do
44
+
45
+ class SettableDeltaVelocitiesTestOrbitalManeuver < KerbalDyn::OrbitalManeuver::Base
46
+ attr_reader :delta_velocities
47
+ attr_writer :delta_velocities
48
+ end
49
+
50
+ it 'should give a list of delta velocities' do
51
+ KerbalDyn::OrbitalManeuver::Base.new(@dummy_orbit, @dummy_orbit).delta_velocities.should == []
52
+ end
53
+
54
+ it 'should give the sums of the absolute velocities as delta_v' do
55
+ maneuver = SettableDeltaVelocitiesTestOrbitalManeuver.new(@dummy_orbit, @dummy_orbit).tap {|m| m.delta_velocities = [5, -2, 4, 0]}
56
+ maneuver.delta_velocity.should == 11
57
+ maneuver.delta_v.should == 11
58
+ end
59
+
60
+ it 'should sum an empty velocity list to zero' do
61
+ maneuver = SettableDeltaVelocitiesTestOrbitalManeuver.new(@dummy_orbit, @dummy_orbit).tap {|m| m.delta_velocities = []}
62
+ maneuver.delta_velocity.should == 0
63
+ maneuver.delta_v.should == 0
64
+ end
65
+
66
+ it 'should sum a single negative velocity to its absolute value' do
67
+ maneuver = SettableDeltaVelocitiesTestOrbitalManeuver.new(@dummy_orbit, @dummy_orbit).tap {|m| m.delta_velocities = [-2]}
68
+ maneuver.delta_velocity.should == 2
69
+ maneuver.delta_v.should == 2
70
+ end
71
+
72
+ end
73
+
74
+ end