This example shows how to create a sun-synchronous repeat ground track trajectory using the GODOT libraries.

The objective of the optimisation problem is to generate an orbit with a ground track repeat pattern. We need to specify the longitude and local time of the first ascending node. Besides, at the end of the 175-orbits cycle, the longitude and local time have to be the same as in the first node.

Import all the necessary libraries

In [1]:
import numpy as np

from godot.core import tempo, astro
from godot.model import common
from godot import cosmos
from godot.model import interface
from godot.core.autodif import bridge as br

# optionally avoid verbose logging messages
import godot.core.util as util
util.suppressLogger()

Input files:

In [2]:
uni_file = "universe.yml"
tra_file = "trajectory.yml"
pro_file = "problem.yml"

Load the universe and trajectory configuration, and instantiate them.

In [3]:
uni_config = cosmos.util.load_yaml(uni_file)
uni = cosmos.Universe(uni_config)

tra_config = cosmos.util.load_yaml(tra_file)
tra = cosmos.Trajectory(uni, tra_config)

This class provides the longitude at a specific point defined in the trajectory input file.

In [4]:
class LonPnt(interface.ScalarTimeEvaluable):
    def __init__(self, uni, tra, point):
        interface.ScalarTimeEvaluable.__init__(self)
        self.__uni = uni
        self.__tra = tra
        self.__point = point
    def eval(self, epoch):
        t1 = self.__tra.point(self.__point) if type(epoch) is tempo.Epoch else self.__tra.xpoint(self.__point)
        x1 = self.__uni.frames.vector3('Earth', 'SCREF_center', 'ITRF', t1)
        lon1 = astro.sphericalFromCart(x1)[1]
        return lon1

This class provides the local time at a specific point defined in the trajectory input file.

In [5]:
class LtPnt(interface.ScalarTimeEvaluable):
    def __init__(self, uni, tra, point):
        interface.ScalarTimeEvaluable.__init__(self)
        self.__uni = uni
        self.__tra = tra
        self.__point = point
        self.__erpFile = "data/orient/erp2000A.ipf"
        self.__nutFile = "data/orient/nutation2000A.ipf"
        self.__erot = astro.EarthRot(1, self.__erpFile, self.__nutFile)
    def eval(self, epoch):
        t1 = self.__tra.point(self.__point) if type(epoch) is tempo.Epoch else self.__tra.xpoint(self.__point)
        x1 = self.__uni.frames.vector6('Earth', 'SCREF_center', 'ICRF', t1)
        kep1 = astro.convert("Cart", "Kep", x1, self.__uni.constants.getMu("Earth"))
        raan = kep1[3]
        ut1 = self.__erot.ut1(br.Epoch(t1,False))
        gst = self.__erot.gmst(br.Epoch(t1,False))
        ut1 = (np.mod(ut1, 1) - 0.5)*2*np.pi
        lt = raan - (gst - ut1) + np.pi + 10.0*2.0*np.pi
        while (lt >= 2*np.pi):
          lt = lt - 2*np.pi
        while (lt < 0.0):
          lt = lt + 2*np.pi
    
        lt = 24.0*lt/2.0/np.pi
    
        return lt

This class allows us computing differences of objects from the previous two classes.

In [6]:
class TEDiff(interface.ScalarTimeEvaluable):
    def __init__(self, te1, te2):
        interface.ScalarTimeEvaluable.__init__(self)
        self.__te1 = te1
        self.__te2 = te2
    def eval(self, epoch):
        return self.__te1.eval(epoch) - self.__te2.eval(epoch)

Load the problem configuration and create the problem object, compute the trajectory.

In [7]:
pro_config = cosmos.util.load_yaml(pro_file)
pro = cosmos.Problem(uni, [tra], pro_config)

tra.compute(True)

Now, we add instances of the previously defined classes to the universe as time evaluables.

In [8]:
if uni.evaluables.contains( "firstLongitude" ):
    uni.evaluables.remove( "firstLongitude" )
uni.evaluables.add( "firstLongitude", LonPnt(uni, tra, "refASC00001"))

if uni.evaluables.contains( "firstLTAN" ):
    uni.evaluables.remove( "firstLTAN" )
uni.evaluables.add( "firstLTAN", LtPnt(uni, tra, "refASC00001"))

if uni.evaluables.contains( "diffLongitude" ):
    uni.evaluables.remove( "diffLongitude" )
uni.evaluables.add( "diffLongitude", TEDiff(LonPnt(uni, tra, "refASC00176"), LonPnt(uni, tra, "refASC00001")))

if uni.evaluables.contains( "diffLTAN" ):
    uni.evaluables.remove( "diffLTAN" )
uni.evaluables.add( "diffLTAN", TEDiff(LtPnt(uni, tra, "refASC00176"), LtPnt(uni, tra, "refASC00001")))

The following function allows us computing a variation in a specified universe's parameter in order to change a universe's time evaluable to meet a given target. The variation applied is linear, making use of the partial of the time evaluable with respect to the parameter.

In [9]:
def control_constraint(constraint_name, parameter, target):
    # Evaluate the constraint at any point
    constraint = common.resolve( uni.evaluables.get(constraint_name).eval(tempo.XEpoch("0.0 TT") ) )

    # Extract the partial you want 
    dg_dp = constraint.at(parameter)

    # Get the initial value of the contraint 
    g0 = constraint.value()

    # Set the target value
    g1 = target

    # update the value of the parameter in the parameter book
    p = uni.parameters.get(parameter).getPhysicalValue()
    uni.parameters.get(parameter).setPhysicalValue( p + ( g1 - g0 ) / dg_dp )

Iterate solve the problem

In [10]:
for it in [1, 2]:
    print("Iteration: ", it)
    control_constraint("firstLTAN", "start_SCREF_center_ran", 18.0)
    tra.compute(True)
    control_constraint("firstLongitude", "start_dt", np.deg2rad(74.20205))
    tra.compute(True)
    control_constraint("diffLTAN", "start_SCREF_center_inc", 0.0)
    tra.compute(True)
    control_constraint("diffLongitude", "start_SCREF_center_sma", 0.0)
    tra.compute(True)
Iteration:  1
Iteration:  2

Check the final solution. Print the output trajectory in yml.

In [11]:
lon1 = uni.evaluables.get("firstLongitude")
ltan1 = uni.evaluables.get("firstLTAN")
lonDiff = uni.evaluables.get("diffLongitude")
ltanDiff = uni.evaluables.get("diffLTAN")
print("Longitude of first node: ", np.rad2deg(lon1.eval(tempo.Epoch())))
print("Local time of first node: ", ltan1.eval(tempo.Epoch()))
print("Longitude difference: ", lonDiff.eval(tempo.Epoch()))
print("Local time difference: ", ltanDiff.eval(tempo.Epoch()))

up = tra.applyParameterChanges()
tra_config = cosmos.util.deep_update(tra_config, up)
cosmos.util.save_yaml(tra_config, "trajectory_reforb.yml")
Longitude of first node:  74.2020494731503
Local time of first node:  17.99999445685843
Longitude difference:  -8.899491366065604e-09
Local time difference:  -2.4576099377782157e-08