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,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