kerbaldyn 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.rspec +2 -0
- data/Gemfile +11 -0
- data/README.rdoc +57 -0
- data/Rakefile +23 -0
- data/kerbaldyn.gemspec +30 -0
- data/lib/kerbaldyn.rb +14 -0
- data/lib/kerbaldyn/body.rb +48 -0
- data/lib/kerbaldyn/constants.rb +12 -0
- data/lib/kerbaldyn/data.rb +56 -0
- data/lib/kerbaldyn/data/planet_data.json +249 -0
- data/lib/kerbaldyn/mixin.rb +2 -0
- data/lib/kerbaldyn/mixin/options_processor.rb +17 -0
- data/lib/kerbaldyn/mixin/parameter_attributes.rb +38 -0
- data/lib/kerbaldyn/orbit.rb +379 -0
- data/lib/kerbaldyn/orbital_maneuver.rb +6 -0
- data/lib/kerbaldyn/orbital_maneuver/base.rb +159 -0
- data/lib/kerbaldyn/orbital_maneuver/bielliptic.rb +61 -0
- data/lib/kerbaldyn/orbital_maneuver/burn_event.rb +57 -0
- data/lib/kerbaldyn/orbital_maneuver/hohmann.rb +48 -0
- data/lib/kerbaldyn/orbital_maneuver/inclination_change.rb +0 -0
- data/lib/kerbaldyn/part.rb +15 -0
- data/lib/kerbaldyn/part/base.rb +154 -0
- data/lib/kerbaldyn/part/fuel_tank.rb +11 -0
- data/lib/kerbaldyn/part/generic.rb +10 -0
- data/lib/kerbaldyn/part/liquid_fuel_engine.rb +69 -0
- data/lib/kerbaldyn/part/mixin.rb +1 -0
- data/lib/kerbaldyn/part/mixin/fuel_tank.rb +35 -0
- data/lib/kerbaldyn/part/rcs_fuel_tank.rb +10 -0
- data/lib/kerbaldyn/part/solid_rocket.rb +30 -0
- data/lib/kerbaldyn/part_library.rb +55 -0
- data/lib/kerbaldyn/planetoid.rb +214 -0
- data/lib/kerbaldyn/version.rb +13 -0
- data/spec/bielliptic_orbital_maneuver_spec.rb +60 -0
- data/spec/constants_spec.rb +9 -0
- data/spec/hohmann_orbital_maneuver_spec.rb +385 -0
- data/spec/options_processor_spec.rb +33 -0
- data/spec/orbit_spec.rb +357 -0
- data/spec/orbital_maneuver_base_spec.rb +74 -0
- data/spec/parameter_attributes_spec.rb +82 -0
- data/spec/part_library_spec.rb +21 -0
- data/spec/part_spec.rb +218 -0
- data/spec/planetoid_spec.rb +117 -0
- data/spec/spec_helper.rb +110 -0
- data/spec/support/parts/RCSFuelTank/part.cfg +42 -0
- data/spec/support/parts/fuelTank/part.cfg +48 -0
- data/spec/support/parts/liquidEngine1/part.cfg +60 -0
- data/spec/support/parts/liquidEngine2/part.cfg +64 -0
- data/spec/support/parts/solidBooster/part.cfg +67 -0
- data/spec/support/planet_test_data.json +340 -0
- metadata +95 -0
@@ -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,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,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
|