|
| 1 | +package ltd.mbor.sciko.orbital.examples |
| 2 | + |
| 3 | +import ltd.mbor.sciko.linalg.norm |
| 4 | +import ltd.mbor.sciko.orbital.* |
| 5 | +import org.jetbrains.kotlinx.kandy.letsplot.export.save |
| 6 | +import org.jetbrains.kotlinx.multik.api.KEEngineType |
| 7 | +import org.jetbrains.kotlinx.multik.api.mk |
| 8 | +import org.jetbrains.kotlinx.multik.api.ndarray |
| 9 | +import org.jetbrains.kotlinx.multik.ndarray.data.D1 |
| 10 | +import org.jetbrains.kotlinx.multik.ndarray.data.MultiArray |
| 11 | +import org.jetbrains.kotlinx.multik.ndarray.data.get |
| 12 | +import kotlin.math.* |
| 13 | + |
| 14 | +/** |
| 15 | + * Kotlin port of MATLAB Example_10_09.m |
| 16 | + * |
| 17 | + * Solves Gauss planetary equations with solar radiation pressure (SRP) |
| 18 | + * over 3 years, including Earth eclipse (line-of-sight) shadow function. |
| 19 | + */ |
| 20 | +fun main() { |
| 21 | + mk.setEngine(KEEngineType) |
| 22 | + |
| 23 | + // Conversion factors |
| 24 | + val hours = 3600.0 |
| 25 | + val days = 24.0 * hours |
| 26 | + |
| 27 | + // Constants |
| 28 | + val mu = muEarth // km^3/s^2 |
| 29 | + val c = 2.998e8 // m/s |
| 30 | + val S = 1367.0 // W/m^2 |
| 31 | + val Psr = S / c // Pa = N/m^2 |
| 32 | + |
| 33 | + // Spacecraft properties |
| 34 | + val CR = 2.0 // radiation pressure coefficient |
| 35 | + val m = 100.0 // kg |
| 36 | + val As = 200.0 // m^2 |
| 37 | + |
| 38 | + // Initial orbital parameters (given) |
| 39 | + val a0 = 10085.44 // km |
| 40 | + val e0 = 0.025422 |
| 41 | + val i0 = 88.3924.degrees |
| 42 | + val RA0 = 45.38124.degrees |
| 43 | + val TA0 = 343.4268.degrees |
| 44 | + val w0 = 227.493.degrees |
| 45 | + |
| 46 | + // Inferred parameters |
| 47 | + val h0 = sqrt(mu * a0 * (1 - e0.pow(2))) |
| 48 | + val T0 = 2 * PI / sqrt(mu) * a0.pow(1.5) |
| 49 | + |
| 50 | + // Initial element state vector |
| 51 | + val coe0 = mk.ndarray(mk[h0, e0, RA0, i0, w0, TA0]) |
| 52 | + |
| 53 | + // Time span |
| 54 | + val JD0 = 2438400.5 // 6 January 1964 0 UT |
| 55 | + val t0 = 0.0 |
| 56 | + val tf = 3.0 * 365.0 * days |
| 57 | + |
| 58 | + val (tOut, yOut) = rkf45( |
| 59 | + t0..tf, |
| 60 | + coe0, |
| 61 | + tolerance = 1e-10, |
| 62 | + h0 = T0 / 1000.0, |
| 63 | + ) { t, y -> |
| 64 | + ratesSRP(t, y, mu, JD0, days, Psr, CR, As, m) |
| 65 | + } |
| 66 | + |
| 67 | + val h = yOut.map { it[0] } |
| 68 | + val e = yOut.map { it[1] } |
| 69 | + val RA = yOut.map { it[2] } |
| 70 | + val inc = yOut.map { it[3] } |
| 71 | + val w = yOut.map { it[4] } |
| 72 | + val TA = yOut.map { it[5] } |
| 73 | + |
| 74 | + // a = h^2 / mu / (1 - e^2) |
| 75 | + val a = yOut.map { it[0].pow(2) / mu / (1 - it[1].pow(2)) } |
| 76 | + |
| 77 | + val tDays = tOut.map { it / days } |
| 78 | + |
| 79 | + // Plots similar to MATLAB example |
| 80 | + plotScalar( |
| 81 | + Triple(tDays, h.map { it - h0 }, "Δh (km^2/s)") |
| 82 | + , xLabel = "days", yLabel = "Δh (km^2/s)").save("Example10_09_h.png") |
| 83 | + |
| 84 | + plotScalar( |
| 85 | + Triple(tDays, e.map { it - e0 }, "Δe") |
| 86 | + , xLabel = "days", yLabel = "Δe").save("Example10_09_e.png") |
| 87 | + |
| 88 | + plotScalar( |
| 89 | + Triple(tDays, RA.map { (it - RA0).toDegrees() }, "ΔΩ (deg)") |
| 90 | + , xLabel = "days", yLabel = "deg").save("Example10_09_RA.png") |
| 91 | + |
| 92 | + plotScalar( |
| 93 | + Triple(tDays, inc.map { (it - i0).toDegrees() }, "Δi (deg)") |
| 94 | + , xLabel = "days", yLabel = "deg").save("Example10_09_i.png") |
| 95 | + |
| 96 | + plotScalar( |
| 97 | + Triple(tDays, w.map { (it - w0).toDegrees() }, "Δω (deg)") |
| 98 | + , xLabel = "days", yLabel = "deg").save("Example10_09_w.png") |
| 99 | + |
| 100 | + plotScalar( |
| 101 | + Triple(tDays, a.map { it - a0 }, "Δa (km)") |
| 102 | + , xLabel = "days", yLabel = "km").save("Example10_09_a.png") |
| 103 | +} |
| 104 | + |
| 105 | +private fun ratesSRP( |
| 106 | + t: Double, |
| 107 | + f: MultiArray<Double, D1>, |
| 108 | + mu: Double, |
| 109 | + JD0: Double, |
| 110 | + days: Double, |
| 111 | + Psr: Double, |
| 112 | + CR: Double, |
| 113 | + As: Double, |
| 114 | + m: Double, |
| 115 | +): MultiArray<Double, D1> { |
| 116 | + val h = f[0] |
| 117 | + val e = f[1] |
| 118 | + val RA = f[2] |
| 119 | + val i = f[3] |
| 120 | + val w = f[4] |
| 121 | + val TA = f[5] |
| 122 | + |
| 123 | + val u = w + TA // argument of latitude |
| 124 | + |
| 125 | + // State vectors from COEs at current time |
| 126 | + val coe = f |
| 127 | + val (R, V) = svFromCoe(coe, mu) |
| 128 | + val r = R.norm() |
| 129 | + |
| 130 | + // Sun position at current Julian date |
| 131 | + val JD = JD0 + t / days |
| 132 | + val (lambda, eps, rSun) = solarPosition(JD) // km, earth->sun |
| 133 | + |
| 134 | + // Eclipse shadow function (1 in sunlight, 0 in shadow) |
| 135 | + val nu = if (los(R, rSun)) 0.0 else 1.0 |
| 136 | + |
| 137 | + // Solar radiation pressure acceleration magnitude (km/s^2) |
| 138 | + val pSR = nu * (Psr * CR * As / m) / 1000.0 |
| 139 | + |
| 140 | +// // Compute RSW frame unit vectors |
| 141 | +// val rHat = R / r |
| 142 | +// val hVec = R cross V |
| 143 | +// val wHat = hVec / hVec.norm() |
| 144 | +// val sHat = wHat cross rHat |
| 145 | +// |
| 146 | +// // Unit vector from Earth to Sun |
| 147 | +// val uSun = rSun / rSun.norm() |
| 148 | +// |
| 149 | +// // Components of sun direction in RSW frame |
| 150 | +// val ur = (uSun dot rHat) |
| 151 | +// val us = (uSun dot sHat) |
| 152 | +// val uw = (uSun dot wHat) |
| 153 | +// |
| 154 | +// val si = sin(i) |
| 155 | + |
| 156 | + val sl = sin(lambda); val cl = cos(lambda) |
| 157 | + val se = sin(eps); val ce = cos(eps) |
| 158 | + val sW = sin(RA); val cW = cos(RA) |
| 159 | + val si = sin(i); val ci = cos(i) |
| 160 | + val su = sin(u); val cu = cos(u) |
| 161 | + val sT = sin(TA); val cT = cos(TA) |
| 162 | + |
| 163 | + val ur = sl*ce*cW*ci*su + sl*ce*sW*cu - cl*sW*ci*su + cl*cW*cu + sl*se*si*su |
| 164 | + |
| 165 | + val us = sl*ce*cW*ci*cu - sl*ce*sW*su - cl*sW*ci*cu - cl*cW*su + sl*se*si*cu |
| 166 | + |
| 167 | + val uw = - sl*ce*cW*si + cl*sW*si + sl*se*ci |
| 168 | + |
| 169 | + // Gauss planetary equations with SRP (matching MATLAB lines 178-191) |
| 170 | + val hdot = -pSR * r * us |
| 171 | + |
| 172 | + val edot = -pSR * ( |
| 173 | + (h / mu) * sT * ur + (1.0 / (mu * h)) * ((h.pow(2) + mu * r) * cT + mu * e * r) * us |
| 174 | + ) |
| 175 | + |
| 176 | + val TAdot = h / r.pow(2) - pSR / (e * h) * ( |
| 177 | + h.pow(2) / mu * cT * ur - (r + h.pow(2) / mu) * sT * us |
| 178 | + ) |
| 179 | + |
| 180 | + val RAdot = -pSR * r / h / si * su * uw |
| 181 | + |
| 182 | + val idot = -pSR * r / h * cu * uw |
| 183 | + |
| 184 | + val wdot = -pSR * ( |
| 185 | + -1.0 / (e * h) * ((h * h / mu) * cT * ur - (r + h * h / mu) * sT * us) - |
| 186 | + r * sin(u) / h / si * cos(i) * uw |
| 187 | + ) |
| 188 | + |
| 189 | + return mk.ndarray(mk[hdot, edot, RAdot, idot, wdot, TAdot]) |
| 190 | +} |
0 commit comments