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