Skip to content

Commit e59fe73

Browse files
authored
move calculations from spacecraft into calculations (#28)
* move calculations from spacecraft into calculations * format
1 parent b6cf534 commit e59fe73

File tree

3 files changed

+202
-214
lines changed

3 files changed

+202
-214
lines changed

src/Spacecraft.zig

Lines changed: 34 additions & 212 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,6 @@ const CelestialBody = constants.CelestialBody;
1010

1111
const Spacecraft = @This();
1212

13-
/// State Vector - Used for position and velocity knowledge
14-
const StateV = [6]f64;
15-
16-
/// Contains time and state vector to be used during propagation
17-
const StateTime = struct {
18-
time: f64,
19-
state: StateV,
20-
};
21-
22-
// an easy win would be to combine this with StateTime to form a *spacecraftState* struct to deal with all of this
23-
/// Responsible for spacecraft orientation
24-
const AttitudeState = struct {
25-
quaternion: [4]f64,
26-
angularVelocity: [3]f64,
27-
};
28-
2913
/// Satellite details used in calculations
3014
pub const SatelliteParameters = struct {
3115
drag: f64,
@@ -47,16 +31,6 @@ pub const Impulse = struct {
4731
} = null,
4832
};
4933

50-
/// Needed for propagation.
51-
pub const OrbitalElements = struct {
52-
a: f64,
53-
e: f64,
54-
i: f64,
55-
raan: f64,
56-
argPeriapsis: f64,
57-
trueAnomaly: f64,
58-
};
59-
6034
/// Determines the values in the SatelliteParameters struct
6135
pub const SatelliteSize = enum {
6236
Cube,
@@ -108,7 +82,7 @@ inertiaTensor: [3][3]f64,
10882
bodyVectors: [2][3]f64,
10983
referenceVectors: [2][3]f64,
11084
orbitingObject: CelestialBody = constants.earth,
111-
orbitPredictions: std.ArrayList(StateTime),
85+
orbitPredictions: std.ArrayList(calculations.StateTime),
11286
allocator: std.mem.Allocator,
11387

11488
pub fn init(name: []const u8, tle: Tle, mass: f64, size: SatelliteSize, orbitingObject: ?CelestialBody, allocator: std.mem.Allocator) Spacecraft {
@@ -133,7 +107,7 @@ pub fn init(name: []const u8, tle: Tle, mass: f64, size: SatelliteSize, orbiting
133107
.{ 0.0, 1.0, 0.0 },
134108
},
135109
.orbitingObject = orbitingObject.?,
136-
.orbitPredictions = std.ArrayList(StateTime).init(allocator),
110+
.orbitPredictions = std.ArrayList(calculations.StateTime).init(allocator),
137111
.allocator = allocator,
138112
};
139113
}
@@ -143,12 +117,12 @@ pub fn deinit(self: *Spacecraft) void {
143117
}
144118

145119
pub fn updateAttitude(self: *Spacecraft) void {
146-
const attitudeMatrix = triad(self.bodyVectors[0], self.bodyVectors[1], self.referenceVectors[0], self.referenceVectors[1]);
147-
self.quaternion = matrixToQuaternion(attitudeMatrix);
120+
const attitudeMatrix = calculations.triad(self.bodyVectors[0], self.bodyVectors[1], self.referenceVectors[0], self.referenceVectors[1]);
121+
self.quaternion = calculations.matrixToQuaternion(attitudeMatrix);
148122
}
149123

150124
pub fn propagateAttitude(self: *Spacecraft, dt: f64) void {
151-
const state = AttitudeState{ .quaternion = self.quaternion, .angularVelocity = self.angularVelocity };
125+
const state = calculations.AttitudeState{ .quaternion = self.quaternion, .angularVelocity = self.angularVelocity };
152126
const newState = rk4Attitude(self, state, dt);
153127
self.quaternion = newState.quaternion;
154128
self.angularVelocity = newState.angularVelocity;
@@ -166,7 +140,7 @@ pub fn propagate(self: *Spacecraft, t0: f64, days: f64, h: f64, impulseList: ?[]
166140

167141
log.info("Initial energy established: {d}\n", .{initialEnergy});
168142

169-
try self.orbitPredictions.append(StateTime{ .time = t, .state = y });
143+
try self.orbitPredictions.append(calculations.StateTime{ .time = t, .state = y });
170144
var step: usize = 0;
171145
var impulseIndex: usize = 0;
172146

@@ -177,12 +151,12 @@ pub fn propagate(self: *Spacecraft, t0: f64, days: f64, h: f64, impulseList: ?[]
177151
if (dt > 0) {
178152
y = self.rk4(y, dt);
179153
t += dt;
180-
try self.orbitPredictions.append(StateTime{ .time = t, .state = y });
154+
try self.orbitPredictions.append(calculations.StateTime{ .time = t, .state = y });
181155
}
182156

183157
switch (impulses[impulseIndex].mode) {
184158
.Absolute => {
185-
y = impulse(y, impulses[impulseIndex].deltaV);
159+
y = calculations.impulse(y, impulses[impulseIndex].deltaV);
186160
log.info("Impulse completed at t={d:.2}s", .{t});
187161
},
188162
.Prograde => {
@@ -193,7 +167,7 @@ pub fn propagate(self: *Spacecraft, t0: f64, days: f64, h: f64, impulseList: ?[]
193167
y[4] / velocityMagnitude * deltaVMagnitude,
194168
y[5] / velocityMagnitude * deltaVMagnitude,
195169
};
196-
y = impulse(y, deltaV);
170+
y = calculations.impulse(y, deltaV);
197171
log.info("Prograde impulse completed at t={d:.2}s", .{t});
198172
},
199173
.Phase => {
@@ -207,27 +181,27 @@ pub fn propagate(self: *Spacecraft, t0: f64, days: f64, h: f64, impulseList: ?[]
207181
y[4] / velocityMagnitude * deltaVMagnitude,
208182
y[5] / velocityMagnitude * deltaVMagnitude,
209183
};
210-
y = impulse(y, deltaV);
184+
y = calculations.impulse(y, deltaV);
211185

212186
const period = 2 * std.math.pi * @sqrt(std.math.pow(f64, r, 3) / self.orbitingObject.mu);
213187
const secondImpulseTime = t + period * transferOrbits;
214188
while (t < secondImpulseTime) : (t += h) {
215189
y = self.rk4(y, h);
216-
try self.orbitPredictions.append(StateTime{ .time = t + h, .state = y });
190+
try self.orbitPredictions.append(calculations.StateTime{ .time = t + h, .state = y });
217191
}
218-
y = impulse(y, .{ -deltaV[0], -deltaV[1], -deltaV[2] });
219-
try self.orbitPredictions.append(StateTime{ .time = t, .state = y });
192+
y = calculations.impulse(y, .{ -deltaV[0], -deltaV[1], -deltaV[2] });
193+
try self.orbitPredictions.append(calculations.StateTime{ .time = t, .state = y });
220194
log.info("Phase change completed at t={d:.2}s", .{t});
221195
},
222196
.PlaneChange => {
223197
const planeChange = impulses[impulseIndex];
224198
y = self.applyPlaneChange(y, planeChange);
225-
try self.orbitPredictions.append(StateTime{ .time = t, .state = y });
199+
try self.orbitPredictions.append(calculations.StateTime{ .time = t, .state = y });
226200
log.info("Plane change completed at t={d:.2}s", .{t});
227201
},
228202
}
229203

230-
try self.orbitPredictions.append(StateTime{ .time = t, .state = y });
204+
try self.orbitPredictions.append(calculations.StateTime{ .time = t, .state = y });
231205

232206
impulseIndex += 1;
233207
}
@@ -240,7 +214,7 @@ pub fn propagate(self: *Spacecraft, t0: f64, days: f64, h: f64, impulseList: ?[]
240214
const r = @sqrt(y[0] * y[0] + y[1] * y[1] + y[2] * y[2]);
241215
const energy = self.calculateEnergy(y);
242216

243-
try self.orbitPredictions.append(StateTime{ .time = t, .state = y });
217+
try self.orbitPredictions.append(calculations.StateTime{ .time = t, .state = y });
244218

245219
if (energy > 0 or std.math.isNan(energy) or r > 100_000) {
246220
log.warn("Abnormal orbit detected at step {d}", .{step});
@@ -253,126 +227,33 @@ pub fn propagate(self: *Spacecraft, t0: f64, days: f64, h: f64, impulseList: ?[]
253227
}
254228
}
255229

256-
fn rk4(self: Spacecraft, y: StateV, h: f64) StateV {
257-
const k1 = scalarMultiply(h, self.derivative(y));
258-
const k2 = scalarMultiply(h, self.derivative(vectorAdd(y, scalarMultiply(0.5, k1))));
259-
const k3 = scalarMultiply(h, self.derivative(vectorAdd(y, scalarMultiply(0.5, k2))));
260-
const k4 = scalarMultiply(h, self.derivative(vectorAdd(y, k3)));
261-
return vectorAdd(
230+
fn rk4(self: Spacecraft, y: calculations.StateV, h: f64) calculations.StateV {
231+
const k1 = calculations.scalarMultiply(h, self.derivative(y));
232+
const k2 = calculations.scalarMultiply(h, self.derivative(calculations.vectorAdd(y, calculations.scalarMultiply(0.5, k1))));
233+
const k3 = calculations.scalarMultiply(h, self.derivative(calculations.vectorAdd(y, calculations.scalarMultiply(0.5, k2))));
234+
const k4 = calculations.scalarMultiply(h, self.derivative(calculations.vectorAdd(y, k3)));
235+
return calculations.vectorAdd(
262236
y,
263-
scalarMultiply(
237+
calculations.scalarMultiply(
264238
1.0 / 6.0,
265-
vectorAdd(
266-
vectorAdd(k1, scalarMultiply(2, k2)),
267-
vectorAdd(scalarMultiply(2, k3), k4),
239+
calculations.vectorAdd(
240+
calculations.vectorAdd(k1, calculations.scalarMultiply(2, k2)),
241+
calculations.vectorAdd(calculations.scalarMultiply(2, k3), k4),
268242
),
269243
),
270244
);
271245
}
272246

273-
fn triad(v1_body: [3]f64, v2_body: [3]f64, v1_ref: [3]f64, v2_ref: [3]f64) [3][3]f64 {
274-
const t1Body = normalize(v1_body);
275-
const t2Body = normalize(cross(v1_body, v2_body));
276-
const t3Body = cross(t1Body, t2Body);
277-
278-
const t1Ref = normalize(v1_ref);
279-
const t2Ref = normalize(cross(v1_ref, v2_ref));
280-
const t3Ref = cross(t1Ref, t2Ref);
281-
282-
const bodyMatrix = [3][3]f64{
283-
.{ t1Body[0], t2Body[0], t3Body[0] },
284-
.{ t1Body[1], t2Body[1], t3Body[1] },
285-
.{ t1Body[2], t2Body[2], t3Body[2] },
286-
};
287-
288-
const refMatrix = [3][3]f64{
289-
.{ t1Ref[0], t2Ref[0], t3Ref[0] },
290-
.{ t1Ref[1], t2Ref[1], t3Ref[1] },
291-
.{ t1Ref[2], t2Ref[2], t3Ref[2] },
292-
};
293-
294-
return multiplyMatrices(bodyMatrix, transposeMatrix(refMatrix));
295-
}
296-
297-
fn normalize(v: [3]f64) [3]f64 {
298-
const magni = @sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
299-
return .{ v[0] / magni, v[1] / magni, v[2] / magni };
300-
}
301-
302-
fn cross(a: [3]f64, b: [3]f64) [3]f64 {
303-
return .{
304-
a[1] * b[2] - a[2] * b[1],
305-
a[2] * b[0] - a[0] * b[2],
306-
a[0] * b[1] - a[1] * b[0],
307-
};
308-
}
309-
310-
pub fn multiplyMatrices(a: [3][3]f64, b: [3][3]f64) [3][3]f64 {
311-
var result: [3][3]f64 = undefined;
312-
for (0..3) |i| {
313-
for (0..3) |j| {
314-
result[i][j] = 0;
315-
for (0..3) |k| {
316-
result[i][j] += a[i][k] * b[k][j];
317-
}
318-
}
319-
}
320-
return result;
321-
}
322-
323-
fn transposeMatrix(m: [3][3]f64) [3][3]f64 {
324-
return .{
325-
.{ m[0][0], m[1][0], m[2][0] },
326-
.{ m[0][1], m[1][1], m[2][1] },
327-
.{ m[0][2], m[1][2], m[2][2] },
328-
};
329-
}
330-
331-
fn matrixToQuaternion(m: [3][3]f64) [4]f64 {
332-
var q: [4]f64 = undefined;
333-
const trace = m[0][0] + m[1][1] + m[2][2];
334-
335-
if (trace > 0) {
336-
const s = 0.5 / @sqrt(trace + 1.0);
337-
q[0] = 0.25 / s;
338-
q[1] = (m[2][1] - m[1][2]) * s;
339-
q[2] = (m[0][2] - m[2][0]) * s;
340-
q[3] = (m[1][0] - m[0][1]) * s;
341-
} else {
342-
if (m[0][0] > m[1][1] and m[0][0] > m[2][2]) {
343-
const s = 2.0 * @sqrt(1.0 + m[0][0] - m[1][1] - m[2][2]);
344-
q[0] = (m[2][1] - m[1][2]) / s;
345-
q[1] = 0.25 * s;
346-
q[2] = (m[0][1] + m[1][0]) / s;
347-
q[3] = (m[0][2] + m[2][0]) / s;
348-
} else if (m[1][1] > m[2][2]) {
349-
const s = 2.0 * @sqrt(1.0 + m[1][1] - m[0][0] - m[2][2]);
350-
q[0] = (m[0][2] - m[2][0]) / s;
351-
q[1] = (m[0][1] + m[1][0]) / s;
352-
q[2] = 0.25 * s;
353-
q[3] = (m[1][2] + m[2][1]) / s;
354-
} else {
355-
const s = 2.0 * @sqrt(1.0 + m[2][2] - m[0][0] - m[1][1]);
356-
q[0] = (m[1][0] - m[0][1]) / s;
357-
q[1] = (m[0][2] + m[2][0]) / s;
358-
q[2] = (m[1][2] + m[2][1]) / s;
359-
q[3] = 0.25 * s;
360-
}
361-
}
362-
363-
return q;
364-
}
365-
366-
fn rk4Attitude(spacecraft: *Spacecraft, state: AttitudeState, dt: f64) AttitudeState {
247+
fn rk4Attitude(spacecraft: *Spacecraft, state: calculations.AttitudeState, dt: f64) calculations.AttitudeState {
367248
const k1 = attitudeDerivative(spacecraft, state);
368-
const k2 = attitudeDerivative(spacecraft, addScaledAttitudeState(state, k1, 0.5 * dt));
369-
const k3 = attitudeDerivative(spacecraft, addScaledAttitudeState(state, k2, 0.5 * dt));
370-
const k4 = attitudeDerivative(spacecraft, addScaledAttitudeState(state, k3, dt));
249+
const k2 = attitudeDerivative(spacecraft, calculations.addScaledAttitudeState(state, k1, 0.5 * dt));
250+
const k3 = attitudeDerivative(spacecraft, calculations.addScaledAttitudeState(state, k2, 0.5 * dt));
251+
const k4 = attitudeDerivative(spacecraft, calculations.addScaledAttitudeState(state, k3, dt));
371252

372-
return addScaledAttitudeState(state, addAttitudeStates(addAttitudeStates(k1, scaleAttitudeState(k2, 2)), addAttitudeStates(scaleAttitudeState(k3, 2), k4)), dt / 6.0);
253+
return calculations.addScaledAttitudeState(state, calculations.addAttitudeStates(calculations.addAttitudeStates(k1, calculations.scaleAttitudeState(k2, 2)), calculations.addAttitudeStates(calculations.scaleAttitudeState(k3, 2), k4)), dt / 6.0);
373254
}
374255

375-
fn attitudeDerivative(spacecraft: *Spacecraft, state: AttitudeState) AttitudeState {
256+
fn attitudeDerivative(spacecraft: *Spacecraft, state: calculations.AttitudeState) calculations.AttitudeState {
376257
const q = state.quaternion;
377258
const w = state.angularVelocity;
378259

@@ -396,59 +277,7 @@ fn attitudeDerivative(spacecraft: *Spacecraft, state: AttitudeState) AttitudeSta
396277
};
397278
}
398279

399-
fn addAttitudeStates(a: AttitudeState, b: AttitudeState) AttitudeState {
400-
return .{
401-
.quaternion = .{
402-
a.quaternion[0] + b.quaternion[0],
403-
a.quaternion[1] + b.quaternion[1],
404-
a.quaternion[2] + b.quaternion[2],
405-
a.quaternion[3] + b.quaternion[3],
406-
},
407-
.angularVelocity = .{
408-
a.angularVelocity[0] + b.angularVelocity[0],
409-
a.angularVelocity[1] + b.angularVelocity[1],
410-
a.angularVelocity[2] + b.angularVelocity[2],
411-
},
412-
};
413-
}
414-
415-
fn scaleAttitudeState(state: AttitudeState, scalar: f64) AttitudeState {
416-
return .{
417-
.quaternion = .{
418-
state.quaternion[0] * scalar,
419-
state.quaternion[1] * scalar,
420-
state.quaternion[2] * scalar,
421-
state.quaternion[3] * scalar,
422-
},
423-
.angularVelocity = .{
424-
state.angularVelocity[0] * scalar,
425-
state.angularVelocity[1] * scalar,
426-
state.angularVelocity[2] * scalar,
427-
},
428-
};
429-
}
430-
431-
fn addScaledAttitudeState(state: AttitudeState, delta: AttitudeState, scalar: f64) AttitudeState {
432-
return addAttitudeStates(state, scaleAttitudeState(delta, scalar));
433-
}
434-
435-
fn vectorAdd(a: StateV, b: StateV) StateV {
436-
var result: StateV = undefined;
437-
for (0..6) |i| {
438-
result[i] = a[i] + b[i];
439-
}
440-
return result;
441-
}
442-
443-
fn scalarMultiply(scalar: f64, vector: StateV) StateV {
444-
var result: StateV = undefined;
445-
for (0..6) |i| {
446-
result[i] = scalar * vector[i];
447-
}
448-
return result;
449-
}
450-
451-
fn derivative(self: Spacecraft, state: StateV) StateV {
280+
fn derivative(self: Spacecraft, state: calculations.StateV) calculations.StateV {
452281
const x = state[0];
453282
const y = state[1];
454283
const z = state[2];
@@ -488,7 +317,7 @@ fn atmosphericDensity(self: Spacecraft, altitude: f64) f64 {
488317
return self.orbitingObject.seaLevelDensity * std.math.exp(-altitude / self.orbitingObject.scaleHeight);
489318
}
490319

491-
fn calculateEnergy(self: Spacecraft, state: StateV) f64 {
320+
fn calculateEnergy(self: Spacecraft, state: calculations.StateV) f64 {
492321
const r = @sqrt(state[0] * state[0] + state[1] * state[1] + state[2] * state[2]);
493322
const vSquared = state[3] * state[3] + state[4] * state[4] + state[5] * state[5];
494323
return 0.5 * vSquared - self.orbitingObject.mu / r;
@@ -506,13 +335,6 @@ fn applyPlaneChange(self: *Spacecraft, y: [6]f64, plane_change: Impulse) [6]f64
506335
return calculations.orbitalElementsToStateVector(elements, mu);
507336
}
508337

509-
fn impulse(state: StateV, delta_v: [3]f64) StateV {
510-
return .{
511-
state[0], state[1], state[2],
512-
state[3] + delta_v[0], state[4] + delta_v[1], state[5] + delta_v[2],
513-
};
514-
}
515-
516338
fn calculatePhaseChange(self: Spacecraft, radius: f64, phase_change: f64, transfer_orbits: f64) f64 {
517339
const vCircular = @sqrt(self.orbitingObject.mu / radius);
518340
const period = 2 * std.math.pi * @sqrt(std.math.pow(f64, radius, 3) / self.orbitingObject.mu);

src/WorldCoordinateSystem.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ pub fn fromTle(tle: Tle, t0: f64, celestialObject: constants.CelestialBody) Worl
2626
return .{ .x = ecef[0], .y = ecef[1], .z = ecef[2] };
2727
}
2828

29-
fn orbitalElementsToECI(elements: Spacecraft.OrbitalElements) Vector3 {
29+
fn orbitalElementsToECI(elements: calculations.OrbitalElements) Vector3 {
3030
const r = elements.a * (1 - elements.e * elements.e) / (1 + elements.e * @cos(elements.trueAnomaly));
3131
const xOrbit = r * @cos(elements.trueAnomaly);
3232
const yOrbit = r * @sin(elements.trueAnomaly);

0 commit comments

Comments
 (0)