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,82 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe KerbalDyn::Mixin::ParameterAttributes do
4
+
5
+ class ParameterAttributesTestClass
6
+ include KerbalDyn::Mixin::ParameterAttributes
7
+
8
+ def initialize(foo)
9
+ self.foo = foo
10
+ end
11
+
12
+ attr_parameter :foo
13
+ alias_parameter :bar, :foo
14
+
15
+ attr_parameter :alpha, :beta, :gamma
16
+ end
17
+
18
+ describe 'attr_parameter' do
19
+
20
+ before(:each) do
21
+ @obj = ParameterAttributesTestClass.new(42)
22
+ end
23
+
24
+ it 'should supply parameter getter' do
25
+ @obj.should respond_to(:foo)
26
+ @obj.foo.should == 42
27
+ end
28
+
29
+ it 'should supply parameter setter' do
30
+ @obj.should respond_to(:foo=)
31
+ @obj.foo = 88
32
+ @obj.foo.should == 88
33
+ end
34
+
35
+ [Integer, Float, Rational, Complex].each do |type|
36
+ it "should convert parameter of type #{type} as a float" do
37
+ value = Kernel.send(type.name.to_sym, 88)
38
+ value.should be_kind_of(type)
39
+
40
+ @obj.foo = value
41
+
42
+ @obj.foo.should be_kind_of(Float)
43
+ @obj.foo.should be_within_six_sigma_of(88.0)
44
+ end
45
+ end
46
+
47
+ it 'should accept nil' do
48
+ @obj.foo = nil
49
+ @obj.foo.should == nil
50
+ end
51
+
52
+ it 'should accept multiple arguments' do
53
+ @obj.should respond_to :alpha
54
+ @obj.should respond_to :beta
55
+ @obj.should respond_to :gamma
56
+ end
57
+
58
+ end
59
+
60
+ describe 'alias_parameter' do
61
+
62
+ before(:each) do
63
+ @obj = ParameterAttributesTestClass.new(42)
64
+ end
65
+
66
+ it "should read aliased parameter value" do
67
+ @obj.should respond_to(:bar)
68
+ @obj.bar.should == @obj.foo
69
+ @obj.foo = 88
70
+ @obj.bar.should == @obj.foo
71
+ end
72
+
73
+ it "should set aliased parameter value" do
74
+ @obj.should respond_to(:bar=)
75
+ @obj.bar = 88
76
+ @obj.bar.should == @obj.foo
77
+ end
78
+
79
+ end
80
+
81
+ end
82
+
@@ -0,0 +1,21 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe KerbalDyn::PartLibrary do
4
+
5
+ it 'should be enumerable'
6
+
7
+ it 'should respond to length'
8
+
9
+ it 'should have convenience methods for making sub libraries by module'
10
+
11
+ it 'should export as JSON'
12
+
13
+ it 'should export as CSV'
14
+
15
+ describe 'parser' do
16
+
17
+ it 'should parse a directory of parts into a library'
18
+
19
+ end
20
+
21
+ end
@@ -0,0 +1,218 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe KerbalDyn::Part do
4
+
5
+ before(:all) do
6
+ @liquid_engine_attr = {"name"=>"liquidEngine", "module"=>"LiquidFuelEngine", "author"=>"Mrbrownce", "mesh"=>"model.mu", "scale"=>"0.1", "node_stack_top"=>"0.0, 7.21461, 0.0, 0.0, 1.0, 0.0", "node_stack_bottom"=>"0.0, -7.27403, 0.0, 0.0, 1.0, 0.0", "fx_exhaustFlame_blue"=>"0.0, -10.3, 0.0, 0.0, 1.0, 0.0, active", "fx_exhaustLight_blue"=>"0.0, -10.3, 0.0, 0.0, 0.0, 1.0, active", "fx_smokeTrail_light"=>"0.0, -10.3, 0.0, 0.0, 1.0, 0.0, active", "sound_vent_medium"=>"activate", "sound_rocket_hard"=>"active", "sound_vent_soft"=>"deactivate", "cost"=>"850", "category"=>"0", "subcategory"=>"0", "title"=>"LV-T30 Liquid Fuel Engine", "manufacturer"=>"Jebediah Kerman's Junkyard and Spaceship Parts Co.", "description"=>"Although criticized by some due to its not unsignificant use of so-called \"pieces found lying about\", the LV-T series has proven itself as a comparatively reliable engine. The T30 model boasts a failure ratio below the 50% mark. This has been considered a major improvement over previous models by engineers and LV-T enthusiasts.", "attachRules"=>"1,0,1,0,0", "mass"=>"1.25", "dragModelType"=>"default", "maximum_drag"=>"0.2", "minimum_drag"=>"0.2", "angularDrag"=>"2", "crashTolerance"=>"7", "maxTemp"=>"3600", "maxThrust"=>"215", "minThrust"=>"0", "heatProduction"=>"400", "Isp"=>"320", "vacIsp"=>"370"}
7
+ @liquid_engine2_attr = {"name"=>"liquidEngine2", "module"=>"LiquidFuelEngine", "author"=>"Mrbrownce", "mesh"=>"model.mu", "scale"=>"0.1", "node_stack_top"=>"0.0, 7.21461, 0.0, 0.0, 1.0, 0.0", "node_stack_bottom"=>"0.0, -5.74338, 0.0, 0.0, 1.0, 0.0", "fx_exhaustFlame_blue"=>"0.0, -5.74338, 0.0, 0.0, 1.0, 0.0, active", "fx_exhaustLight_blue"=>"0.0, -5.74338, 0.0, 0.0, 0.0, 1.0, active", "fx_smokeTrail_light"=>"0.0, -5.74338, 0.0, 0.0, 1.0, 0.0, active", "sound_vent_medium"=>"activate", "sound_rocket_hard"=>"active", "sound_vent_soft"=>"deactivate", "cost"=>"950", "category"=>"0", "subcategory"=>"0", "title"=>"LV-T45 Liquid Fuel Engine", "manufacturer"=>"Jebediah Kerman's Junkyard and Spaceship Parts Co.", "description"=>"The LV-T45 engine was considered another breakthrough in the LV-T series, due to its Thrust Vectoring feature. The LV-T45 can deflect its thrust to aid in craft control. All these added mechanics however, make for a slightly smaller and less powerful engine in comparison with earlier LV-T models.", "attachRules"=>"1,0,1,0,0", "mass"=>"1.5", "dragModelType"=>"default", "maximum_drag"=>"0.2", "minimum_drag"=>"0.2", "angularDrag"=>"2", "crashTolerance"=>"7", "maxTemp"=>"3600", "maxThrust"=>"200", "minThrust"=>"0", "heatProduction"=>"440", "fuelConsumption"=>"7", "Isp"=>"320", "vacIsp"=>"370", "thrustVectoringCapable"=>"True", "gimbalRange"=>"1.0"}
8
+ @fuel_tank_attr = {"name"=>"fuelTank", "module"=>"FuelTank", "author"=>"Mrbrownce", "mesh"=>"model.mu", "scale"=>"0.1", "node_stack_top"=>"0.0, 7.72552, 0.0, 0.0, 1.0, 0.0", "node_stack_bottom"=>"0.0, -7.3, 0.0, 0.0, 1.0, 0.0", "node_attach"=>"5.01, 0.0, 0.0, 1.0, 0.0, 0.0, 1", "cost"=>"850", "category"=>"0", "subcategory"=>"0", "title"=>"FL-T400 Fuel Tank", "manufacturer"=>"Jebediah Kerman's Junkyard and Spaceship Parts Co.", "description"=>"The FL series was received as a substantial upgrade over previous fuel containers used in the Space Program, generally due to its ability to keep the fuel unexploded more often than not. Fuel tanks are useless if there isn't a Liquid Engine attached under it. They can also be stacked with other fuel tanks to increase the amount of fuel for the engine below.", "attachRules"=>"1,1,1,1,0", "mass"=>"2.25", "dragModelType"=>"default", "maximum_drag"=>"0.2", "minimum_drag"=>"0.3", "angularDrag"=>"2", "crashTolerance"=>"6", "breakingForce"=>"50", "breakingTorque"=>"50", "maxTemp"=>"2900", "fuel"=>"400.0", "dryMass"=>"0.25", "fullExplosionPotential"=>"0.9", "emptyExplosionPotential"=>"0.1"}
9
+ @rcs_tank_attr = {"name"=>"RCSFuelTank", "module"=>"RCSFuelTank", "author"=>"Mrbrownce || HarvesteR", "mesh"=>"model.mu", "scale"=>"0.1", "node_stack_top"=>"0.0, 4.64624, 0.0, 0.0, 1.0, 0.0", "node_stack_bottom"=>"0.0, 0.23193, 0.0, 0.0, 1.0, 0.0", "cost"=>"800", "category"=>"0", "subcategory"=>"0", "title"=>"FL-R25 RCS Fuel Tank", "manufacturer"=>"Jebediah Kerman's Junkyard and Spaceship Parts Co.", "description"=>"These fuel tanks carry pressurized gas propellant for RCS thrusters. New advances in plumbing technology made it possible to route RCS lines to any point in the ship. So unlike liquid fuel tanks, RCS Fuel tanks can be placed anywhere.", "attachRules"=>"1,0,1,1,0", "mass"=>"0.5", "dragModelType"=>"default", "maximum_drag"=>"0.2", "minimum_drag"=>"0.2", "angularDrag"=>"2", "crashTolerance"=>"12", "maxTemp"=>"2900", "fuel"=>"200", "dryMass"=>"0.1", "fullExplosionPotential"=>"0.7", "emptyExplosionPotential"=>"0.1"}
10
+ @booster_attr = {"name"=>"solidBooster", "module"=>"SolidRocket", "author"=>"Il Carnefice", "mesh"=>"model.mu", "scale"=>"0.1", "node_stack_bottom"=>"0.0, -12.5127, 0.0, 0.0, 1.0, 0.0, 1", "node_stack_top"=>"0.0, 10.2547, 0.0, 0.0, 1.0, 0.0, 1", "node_attach"=>"0.0, 0.0, -5, 0.0, 0.0, 1.0, 1", "fx_exhaustFlame_yellow"=>"0.0, -11.2673, 0.0, 0.0, 1.0, 0.0, active", "fx_exhaustSparks_yellow"=>"0.0, -11.2673, 0.0, 0.0, 1.0, 0.0, active", "fx_smokeTrail_medium"=>"0.0, -11.2673, 0.0, 0.0, 1.0, 0.0, active", "sound_vent_medium"=>"activate", "sound_rocket_hard"=>"active", "sound_vent_soft"=>"deactivate", "cost"=>"450", "category"=>"0", "subcategory"=>"0", "title"=>"RT-10 Solid Fuel Booster", "description"=>"While considered by some to be little more than \"a trash bin full o' boom\", The RT-10 is used in many space programs, whenever the need to save cash is greater than the need to keep astronauts alive. Use with caution, though. Once lit, solid fuel motors cannot be put out until the fuel runs out.", "attachRules"=>"1,1,1,1,0", "mass"=>"1.8", "dragModelType"=>"default", "maximum_drag"=>"0.3", "minimum_drag"=>"0.2", "angularDrag"=>"2", "crashTolerance"=>"7", "maxTemp"=>"3600", "thrust"=>"250", "dryMass"=>"0.36", "heatProduction"=>"550", "fuelConsumption"=>"4", "internalFuel"=>"100", "fullExplosionPotential"=>"0.8", "emptyExplosionPotential"=>"0.1", "thrustCenter"=>"0, -0.5, 0", "thrustVector"=>"0, 1, 0"}
11
+ end
12
+
13
+ describe KerbalDyn::Part::Base do
14
+
15
+ describe 'properties' do
16
+
17
+ before(:each) do
18
+ @attributes = {'name' => 'fooThruster', 'module' => 'LiquidFuelEngine', 'category' => '0'}
19
+ @part = KerbalDyn::Part::Base.new(@attributes)
20
+ end
21
+
22
+ # Rather than test every single property, we test those that have non-trivial
23
+ # accessors.
24
+
25
+ it 'should provide string and symbol access to attributes' do
26
+ @part['name'].should == @attributes['name']
27
+ @part[:name].should == @attributes['name']
28
+ end
29
+
30
+ it 'should provide category name' do
31
+ @part.category.should == KerbalDyn::Part::CATEGORIES['Propulsion']
32
+ @part.category_name.should == 'Propulsion'
33
+ end
34
+
35
+ it 'should provide module class' do
36
+ @part.module_class.should == KerbalDyn::Part::LiquidFuelEngine
37
+ end
38
+
39
+ it 'should copy the attributes passed to it' do
40
+ attributes = @attributes.dup
41
+ part = KerbalDyn::Part::Base.new(attributes)
42
+
43
+ part.attributes.object_id.should_not == attributes.object_id
44
+
45
+ part['name'].should == 'fooThruster'
46
+ attributes['name'] == 'barThruster'
47
+ part['name'].should_not == 'barThruster'
48
+ end
49
+
50
+ it 'should convert to hash that is not the attributes hash itself' do
51
+ hash = @part.to_hash
52
+
53
+ hash.should == @part.attributes
54
+ hash.object_id.should_not == @part.attributes.object_id
55
+ end
56
+
57
+ it 'should export as JSON' do
58
+ part_json = @part.to_json
59
+ attributes_json = @part.to_hash.to_json
60
+
61
+ part_json.should == attributes_json
62
+ end
63
+
64
+ it 'should export as CSV' # This will need a predefined set of properties to output.
65
+
66
+ end
67
+
68
+ describe 'loader' do
69
+
70
+ before(:all) do
71
+ @parts_directory = File.join(File.dirname(__FILE__), 'support', 'parts')
72
+ @liquid_engine_part_dir = File.join(@parts_directory, 'liquidEngine1')
73
+ @part_dir = @liquid_engine_part_dir
74
+ end
75
+
76
+ before(:each) do
77
+ @part = KerbalDyn::Part::Base.load_part(@part_dir)
78
+ end
79
+
80
+ it 'should load parts from part directories' do
81
+ @part.should be_kind_of(KerbalDyn::Part::Base)
82
+ end
83
+
84
+ it 'should instantiate as the module attribute class' do
85
+ @part.class.name.should =~ Regexp.new( 'KerbalDyn::Part::' + @part[:module] + "$")
86
+ end
87
+
88
+ it 'should default to instantiating as generic'
89
+
90
+ it 'should return nil if no part was found' do
91
+ dir_path = File.join(@parts_directory, 'tardis') # Doesn't exist.
92
+ part = KerbalDyn::Part::Base.load_part(dir_path)
93
+ part.should == nil
94
+ end
95
+
96
+ it 'should log parse errors'
97
+
98
+ end
99
+
100
+ end
101
+
102
+ describe KerbalDyn::Part::Generic do
103
+
104
+ it 'should be a subclass of Base' do
105
+ KerbalDyn::Part::Generic < KerbalDyn::Part::Base
106
+ end
107
+
108
+ end
109
+
110
+ describe KerbalDyn::Part::FuelTank do
111
+
112
+ before(:each) do
113
+ @fuel_tank = KerbalDyn::Part::FuelTank.new(@fuel_tank_attr)
114
+ end
115
+
116
+ it 'should calculate capacity in m^3' do
117
+ @fuel_tank.capacity.should be_within_six_sigma_of(0.4)
118
+ end
119
+
120
+ it 'should calculate fuel mass' do
121
+ @fuel_tank.fuel_mass.should be_within_six_sigma_of(2.0)
122
+ end
123
+
124
+ it 'should calculate fuel density in kg/m^3' do
125
+ @fuel_tank.fuel_density.should be_within_six_sigma_of(5.0)
126
+ end
127
+
128
+ end
129
+
130
+ describe KerbalDyn::Part::RCSFuelTank do
131
+
132
+ before(:each) do
133
+ @rcs_tank = KerbalDyn::Part::FuelTank.new(@rcs_tank_attr)
134
+ end
135
+
136
+ it 'should calculate capacity in m^3' do
137
+ @rcs_tank.capacity.should be_within_six_sigma_of(0.2)
138
+ end
139
+
140
+ it 'should calculate fuel mass' do
141
+ @rcs_tank.fuel_mass.should be_within_six_sigma_of(0.4)
142
+ end
143
+
144
+ it 'should calculate fuel density in kg/m^3' do
145
+ @rcs_tank.fuel_density.should be_within_six_sigma_of(2.0)
146
+ end
147
+
148
+ end
149
+
150
+ describe KerbalDyn::Part::LiquidFuelEngine do
151
+
152
+ before(:each) do
153
+ @liquid_engine = KerbalDyn::Part::LiquidFuelEngine.new(@liquid_engine_attr)
154
+ @liquid_engine2 = KerbalDyn::Part::LiquidFuelEngine.new(@liquid_engine2_attr)
155
+ end
156
+
157
+ it 'should give Isp at sealevel' do
158
+ measured = 320.7
159
+ @liquid_engine.isp.should be_within_three_sigma_of(measured)
160
+ end
161
+
162
+ it 'should give vacuum Isp' do
163
+ specified = 370.0
164
+ @liquid_engine.vac_isp.should be_within_six_sigma_of(specified)
165
+ end
166
+
167
+ it 'should calculate mass flow rate' do
168
+ expected = (215.0/320.0) / 9.8066 # Not sure what the 5th digit of g should be.
169
+ @liquid_engine.mass_flow_rate.should be_within_three_sigma_of(expected)
170
+ end
171
+
172
+ it 'should calculate vacuum mass flow rate' do
173
+ expected = (215.0/370.0) / 9.8066 # Not sure what hte 5th digit of g should be.
174
+ @liquid_engine.vac_mass_flow_rate.should be_within_three_sigma_of(expected)
175
+ end
176
+
177
+ it 'should calculate fuel consumption in m^3/s' do
178
+ fuel_tank = KerbalDyn::Part::FuelTank.new(@fuel_tank_attr)
179
+ measured = 13.7 # in liters/second
180
+ @liquid_engine.fuel_consumption(fuel_tank).should be_within_three_sigma_of(measured/1000.0)
181
+ end
182
+
183
+ it 'should calculate vacuum fuel consumption in m^3/s' do
184
+ fuel_tank = KerbalDyn::Part::FuelTank.new(@fuel_tank_attr)
185
+ @liquid_engine.vac_fuel_consumption(fuel_tank).should be_within_two_sigma_of(0.0119)
186
+ end
187
+
188
+ end
189
+
190
+ describe KerbalDyn::Part::SolidRocket do
191
+
192
+ before(:each) do
193
+ @booster = KerbalDyn::Part::SolidRocket.new(@booster_attr)
194
+ end
195
+
196
+ it 'should calculate capacity in m^3' do
197
+ @booster.capacity.should be_within_six_sigma_of(0.1)
198
+ end
199
+
200
+ it 'should calculate fuel mass' do
201
+ @booster.fuel_mass.should be_within_six_sigma_of(1.44)
202
+ end
203
+
204
+ it 'should calculate fuel density in kg/m^3' do
205
+ @booster.fuel_density.should be_within_six_sigma_of(14.4)
206
+ end
207
+
208
+ it 'should calculate fuel consumption in m^3/s' do
209
+ @booster.fuel_consumption.should be_within_six_sigma_of(0.004)
210
+ end
211
+
212
+ it 'should calculate burn time' do
213
+ @booster.burn_time.should be_within_six_sigma_of(25.0)
214
+ end
215
+
216
+ end
217
+
218
+ end
@@ -0,0 +1,117 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe KerbalDyn::Planetoid do
4
+
5
+ describe 'Properties' do
6
+ before(:each, &BeforeFactory.earth)
7
+
8
+ it "should have a name" do
9
+ @planetoid.name.should == @name
10
+ end
11
+
12
+ [
13
+ :mass,
14
+ :radius,
15
+ :rotational_period,
16
+ :gravitational_parameter,
17
+ :surface_gravity,
18
+ :volume,
19
+ :density,
20
+ :angular_velocity,
21
+ :equitorial_velocity,
22
+ :escape_velocity,
23
+ ].each do |method|
24
+ it "should calculate #{method}" do
25
+ value = self.instance_variable_get("@#{method}")
26
+ # Two sigma let's rough calculations (like those that assume a sphere instead of a spheroid) pass.
27
+ @planetoid.send(method).should be_within_two_sigma_of(value)
28
+ end
29
+ end
30
+
31
+ [
32
+ [:gravitational_acceleration, :surface_gravity, :radius],
33
+ ].each do |method, expected_var, *args_vars|
34
+ it "should calculate #{method}" do
35
+ expected = self.instance_variable_get("@#{expected_var}")
36
+ args = args_vars.map {|var| self.instance_variable_get("@#{var}")}
37
+ @planetoid.send(method, *args).should be_within_two_sigma_of(expected)
38
+ end
39
+ end
40
+
41
+ end
42
+
43
+
44
+ describe 'Planetoid Library' do
45
+
46
+ describe 'Kerbol' do
47
+
48
+ before(:all) do
49
+ @planet_fact = KerbalDyn::Planetoid.kerbol
50
+ @expected_name = 'Kerbol'
51
+ end
52
+
53
+ it 'should be available via a factory method' do
54
+ @planet_fact.should be_kind_of(KerbalDyn::Planetoid)
55
+ @planet_fact.name.should == @expected_name
56
+ end
57
+
58
+ it 'should be a memoized' do
59
+ # We fetch the constant and the factory method value twice, to be sure they are all the same.
60
+ unique_ids = [@planet_fact, @planet_fact].map {|obj| obj.object_id}.uniq
61
+ unique_ids.length.should == 1
62
+ end
63
+
64
+ it 'should be frozen' do
65
+ @planet_fact.should be_frozen
66
+ end
67
+
68
+ {
69
+ :radius => 261600000.0,
70
+ :mass => 1.756830203555361e28,
71
+ :surface_gravity => 17.13071282744409,
72
+ :gravitational_parameter => 1.172332794832492300e18,
73
+ :escape_velocity => 94672.00722134684
74
+ }.each do |param, value|
75
+ it "should have a #{param} of #{value}" do
76
+ @planet_fact.send(param).should be_within_six_sigma_of(value)
77
+ end
78
+ end
79
+
80
+ end
81
+
82
+ PLANET_TEST_DATA.each do |planet_key, test_data|
83
+ data = test_data[:planetoid]
84
+
85
+ describe "#{data[:name]}" do
86
+
87
+ before(:all) do
88
+ @planetoid = KerbalDyn::Planetoid.send(planet_key)
89
+ end
90
+
91
+ it 'should be memoized' do
92
+ unique_ids = [@planetoid, KerbalDyn::Planetoid.send(planet_key)].map {|obj| obj.object_id}.uniq
93
+ unique_ids.length.should == 1
94
+ end
95
+
96
+ it 'should be frozen' do
97
+ @planetoid.should be_frozen
98
+ end
99
+
100
+ data.each do |property, expected_value|
101
+ it "should have #{property} of #{expected_value}" do
102
+ case expected_value
103
+ when Float
104
+ @planetoid.send(property).should be_within_six_sigma_of(expected_value)
105
+ else
106
+ @planetoid.send(property).should == expected_value
107
+ end
108
+ end
109
+ end
110
+
111
+ end
112
+
113
+ end
114
+
115
+ end
116
+
117
+ end
@@ -0,0 +1,110 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+ Bundler.require(:test)
4
+
5
+ require 'kerbaldyn'
6
+
7
+
8
+ require 'json'
9
+ require 'pathname'
10
+ planet_test_data_file = Pathname.new(__FILE__).dirname + 'support' + 'planet_test_data.json'
11
+ PLANET_TEST_DATA = JSON.parse( planet_test_data_file.read, :symbolize_names => true )
12
+
13
+
14
+ RSpec::Matchers.define :be_within_error do |err|
15
+ chain :of do |expected|
16
+ @expected = expected
17
+ end
18
+
19
+ match do |actual|
20
+ actual.should be_within( (err*@expected).abs ).of(@expected)
21
+ end
22
+ end
23
+
24
+ {:two => 21.977895, :three => 370.398, :four => 15787.0, :five => 1744278.0, :six => 506797346}.each do |level, frac|
25
+ err = 1.0 / frac.to_f
26
+ RSpec::Matchers.define "be_within_#{level}_sigma_of".to_sym do |expected|
27
+ match do |actual|
28
+ actual.should be_within_error(err).of(expected)
29
+ end
30
+ end
31
+ end
32
+
33
+ class BeforeFactory
34
+
35
+ # I looked up real values off of tables for the Earth as test data where possible.
36
+ def self.earth
37
+ return Proc.new do
38
+ @name = 'Earth'
39
+ @mass = 5.9736e24
40
+ @radius = 6378.1e3
41
+ @rotational_period = 86164.091
42
+ @angular_velocity = 7.2921150e-5
43
+ @volume = 1.08321e21
44
+ @density = 5515.0
45
+ @gravitational_parameter = 398600.4418e9 # 398600.4418e9 is on some tables, but other tables vary starting in the 4th digit!
46
+ @surface_gravity = 9.780327
47
+ @escape_velocity = 11.186e3
48
+ @equitorial_velocity = 465.1
49
+
50
+ @options = {
51
+ :mass => @mass,
52
+ :radius => @radius,
53
+ :rotational_period => @rotational_period
54
+ }
55
+ @planetoid = KerbalDyn::Planetoid.new(@name, @options)
56
+ end
57
+ end
58
+
59
+ # I looked up values from tables where possible.
60
+ def self.earth_geostationary_orbit
61
+ return Proc.new do
62
+ @geostationary_orbit_radius = 42164e3
63
+ @geostationary_orbit_altitude = 35786e3
64
+ @geostationary_orbit_angular_velocity = 7.2921e-5
65
+ @geostationary_orbit_velocity = 3074.6
66
+ @orbital_radius = @geostationary_orbit_radius
67
+ @orbital_period = @rotational_period
68
+ @orbital_velocity = @geostationary_orbit_velocity
69
+ @orbital_specific_kinetic_energy = 4726582.58 # Computed
70
+ @orbital_specific_potential_energy = -9453572.75875 # Computed
71
+ @orbital_specific_angular_momentum = 129640229204 # Computed using an alternative formula from a different source.
72
+ @orbit = KerbalDyn::Orbit.new(@planetoid, :radius => @orbital_radius)
73
+ end
74
+ end
75
+
76
+ # A third party transfer to geosynchronous orbit from 300km alt example that
77
+ # can be used for the basis of elliptical orbit tests.
78
+ def self.geostationary_transfer_orbit
79
+ return Proc.new do
80
+ @periapsis = 6678e3
81
+ @apoapsis = 42164e3
82
+ @periapsis_velocity = 10150
83
+ @apoapsis_velocity = 1610
84
+ @eccentricity = 0.72655
85
+ @semimajor_axis = 24421e3
86
+ @orbit = KerbalDyn::Orbit.new(@planetoid, :periapsis => @periapsis, :apoapsis => @apoapsis)
87
+ end
88
+ end
89
+
90
+ # A pre-calculated and verified parabloic escape orbit from earth.
91
+ def self.earth_escape_orbit
92
+ return Proc.new do
93
+ @periapsis = 6678e3
94
+ # I used the calculated gavitational parameter for better precision,
95
+ # since this is a "critical" orbit between elliptical and hyperbolc orbits.
96
+ @escape_velocity = Math.sqrt( 2.0 * @planetoid.gravitational_parameter / @periapsis )
97
+ @orbit = KerbalDyn::Orbit.new(@planetoid, :periapsis => @periapsis, :periapsis_velocity => @escape_velocity)
98
+ end
99
+ end
100
+
101
+ def self.earth_hyperbolic_orbit
102
+ return Proc.new do
103
+ @periapsis = 6678e3
104
+ @periapsis_velocity = 20000
105
+ @eccentricity = 5.701 #Precomputed
106
+ @orbit = KerbalDyn::Orbit.new(@planetoid, :periapsis => @periapsis, :periapsis_velocity => @periapsis_velocity)
107
+ end
108
+ end
109
+
110
+ end