Changing attitude on user interaction

Hello everyone!

I am new to Orekit and want to achieve something quite simple. My situation is as follows:
I have a simulation with a satellite orbiting the Earth and its attitude is set to sun-pointing. The user of the simulation has the possibility to set another attitude mode for the satellite by interacting with it over a GUI. What I would like to achieve is that the user can set another attitude mode (e.g. nadir pointing) and the satellite transitions smoothly to the new attitude.
What I tried first is to have two AttitudeProviders running (CelestialBodyPointed for sun-pointing and NadirPointing for nadir pointing) and to use resetActiveProvider of the AttitudeSequence class. However, when I trigger the transition the satellite just does an instantaneous flip which is not the result I am looking for.
My next approach was to use addSwitchingCondition of the AttitudeSequence class. However, none of the already existing EventDetectors matched my condition (i.e. setting a flag from false to true), so I tried to implement a custom EventDetector. But now I am stuck there trying to implement the g method which has to be continuous around its roots because my condition is discrete.
Could you point me in a direction how I could tackle the g function or is there even another more elegant way to achieve what I need?

Cheers,
Yannick

Hi Yannick,

First of all, I suggest you to use the dev branch and then switch to the 10.0 official release once it will be out, because some bugs with AttitudeSequence have been corrected recently.

Your second approach is the correct way to use AttitudeSequence : initializing the two attitude provider in the AttitudeSequence, then adding some switching conditions. With this use, the Attitude Sequence will compute a smooth transition between the two states, over a transition time that you define.

You could imagine an additional state that contains the current attitude state (for instance 1 if nadir and -1 if sun pointing) and a g function that is a linear function going from the previous value (1 or -1) to the new attitude state (1 or -1) over a short time in case of attitude switch (This method will not be precise on the transition date).

Hope it can help you to use the AttitudeSequence class.
Romaric

Hello Romaric,

thank you for your reply! I will give your suggestion a try and let you know how it went :slight_smile:

Cheers,
Yannick

Okay, so I tried your idea and I think I am on a good way, but it still is not quite working. My detector looks as follows:

public class NadirDetector extends AbstractDetector<NadirDetector> {
    public static final double ATT_VALUE = 1;
    private boolean transitionStarted;
    private double transitionTime = 10000; // milliseconds
    private Instant startTime;

    public NadirDetector() {
      super(DEFAULT_MAXCHECK, DEFAULT_THRESHOLD, DEFAULT_MAX_ITER, null);
      transitionStarted = false;
    }

    @Override
    public void init(SpacecraftState s0, AbsoluteDate t) {
    }

    @Override
    public double g(SpacecraftState s) throws OrekitException {
      try {
        double att = s.getAdditionalState("attitude")[0];
        Instant now = Instant.now();
        double res = 0;
        if (att == ATT_VALUE && !transitionStarted) {
          startTime = now;
          transitionStarted = true;
          res = -1;
        } else if (att == ATT_VALUE && transitionStarted) {
          long ms = Duration.between(startTime, now).toMillis();
          res = 2.0 * ms / transitionTime - 1.0;
          if (ms >= transitionTime) {
            res = 1;
          }
        } else {
          transitionStarted = false;
          res = -1;
        }
        System.out.println("g = " + res);
        return res;
      } catch (OrekitException oe) {
        return -1;
      }
    }

    @Override
    public Action eventOccurred(SpacecraftState s, boolean increasing) throws OrekitException {
      System.out.println(
          "SWiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiITCH");
      return Action.CONTINUE;
    }

    @Override
    public SpacecraftState resetState(SpacecraftState oldState) throws OrekitException {
      transitionStarted = false;
      return oldState;
    }

    @Override
    protected NadirDetector create(double newMaxCheck, double newThreshold, int newMaxIter,
        EventHandler<? super NadirDetector> newHandler) {
      return new NadirDetector();
    }
  }

I noted the start time and started with a value of -1 and after 10000ms it should achieve a value of 1 by using a linear function.

I then implemented an AdditionalState provider which updates the currently set attitude inside the spacecraft state. The values show up when g is called, so I assume that worked.

I finally registered the EventDetector in the following way:

this.attitudesSequence = new AttitudesSequence();
NadirDetector nadirDetector = new NadirDetector();
attitudesSequence.addSwitchingCondition(sunPointing, nadirPointing, nadirDetector, true, true,
    10.0, null, null);
this.attitudesSequence.registerSwitchEvents(runningPropagator);
this.attitudeState = new AttitudeStateProvider(attitudeMode.getDoubleValue());
this.runningPropagator.addAdditionalStateProvider(attitudeState);

I update the attitudeStateProvider everytime before propagate is called.
Unfortunately, when I initialize the attitude change, I can see how my value of g transitions slowly from -1 to 1, but the eventOccured method is never called. Did I miss anything big? Are my time steps with milliseconds inside g too discrete?
Thanks,
Yannick

Hi Yannick, sorry for the delay.

This is not the way the events detection is supposed to work.

If g changes its sign between 2 propagation steps, the events detection will try to find the zero of the g inside this step. To tell it simply, if g goes from negative to positive during a step, the propagator starts from the current state (g positive), then propagate backward until g becomes negative, then propagate forward with a smaller propagation step until g becomes positive, and so on until it find the zero with enough accuracy. So the g function must be linked to the spacecraft state at a certain date and always have the same value for the same state.

In your code, if i understand it correctly, the g function will only increase to 1 once the transition has started, no matter the spacecraft state or the date. In this case, the zero is never found since g still increase during the backward propagation instead of going back to a negative value.

As I see it, a simple way to do this kind of detection could be to implements it in the additional state. I made an exemple

private class AttitudeStateProvider implements AdditionalStateProvider {

	// Not the real transition time, only used for the g function
	private double transitionTime = 1;
		
	// The spacecraft state date at the beginning of the transition
	private AbsoluteDate transitionDate = null;
		
	// If the transition button have been pressed
	private boolean transitionButton = false;
		
	@Override
	public String getName() {
		return "attitude state";
	}

	@Override
	public double[] getAdditionalState(SpacecraftState state) {
			
		double[] additionalState = new double[1];			
		
		// Stays in its attitude if the transition button have not been pressed
		if(!transitionButton) {				
			additionalState[0] = -1.;
		}
			
		// linearly switch to 1 over the transition time after the step where the button have been pressed
		else {
			if(transitionDate == null) {
				transitionDate = state.getDate();
			}
			double transition = 2*state.getDate().durationFrom(transitionDate)/transitionTime - 1.;
			if(transition < 1.) {
				additionalState[0] = transition;
			}
			else {
				additionalState[0] = 1.;
			}
		}			
		return additionalState;
	}		
}

With this exemple, the g function of the detector must simply returns the value of the additional state “attitude state”.

@Override
public double g(SpacecraftState s) {
	return s.getAdditionalState("attitude state")[0];
}

g will be zero 0.5s after the state where you pressed the button. That’s where your attitude transition starts.

You still have to find a way to change the boolean value transitionButton when the user press the button.

I didn’t test this code (I tested a variant), it may need some modifications to fit your uses.

Regards,
Romaric

Hello Romaric,

it is now changing the attitude. However it is still doing an instantaneous flip. My transitiontime that I put into addSwitchingCondition is 10000. My convergence threshold is 1.e-6 since my NadirDetector is extending AbstractDetector. Is there any way I can tune these parameters to make the transition between my current attitude and next attitude appear continuous?

Cheers,
Yannick

Hi Yannick,

This behaviour is not linked to the detector itself. The AttitudeSequence is supposed to compute a smooth transition. On my computer, the transition is continuous with the code I sent in my previous post.
Can you send me your code so I can try to reproduce your problem ?
You can also find some exemples of attitude sequence in the tests.

Romaric

My attitude state provider:

public class AttitudeStateProvider implements AdditionalStateProvider {

  private double currentAttitude;
  private String name = "attitude";
  private double transitionTime = 10;
  private AbsoluteDate transitionDate = null;
  private boolean switched = false;

  public AttitudeStateProvider(double initialAttitude) {
    this.currentAttitude = initialAttitude;
  }

  public void setSwitched(boolean b) {
    switched = b;
  }

  @Override
  public String getName() {
    return name;
  }

  /**
   * Use this to keep the provider up to date since the attitude mode will not be
   * determined by the spacecraft state.
   * 
   * @param val The representation value of the attitude mode
   */
  public void updateAttitude(double val) {
    currentAttitude = val;
  }

  @Override
  public double[] getAdditionalState(SpacecraftState state) throws PropagationException {
    double[] additionalState = new double[1];

    // Stays in its attitude if the transition button have not been pressed
    if (!switched) {
      additionalState[0] = -1.;
    }

    // linearly switch to 1 over the transition time after the step where the button
    // have been pressed
    else {
      if (transitionDate == null) {
        transitionDate = state.getDate();
      }
      double transition = 2 * state.getDate().durationFrom(transitionDate) / transitionTime - 1.;
      System.out.println(transition);
      if (transition < 1.) {
        additionalState[0] = transition;
      } else {
        additionalState[0] = 1.;
      }
    }
    return additionalState;
  }
}

My detector:

public class NadirDetector extends AbstractDetector<NadirDetector> {

  public static final double ATT_VALUE = 1;
  private boolean transitionStarted;
  private double transitionTime = 10000; // milliseconds
  private Instant startTime;

  public NadirDetector() {
    super(DEFAULT_MAXCHECK, DEFAULT_THRESHOLD, DEFAULT_MAX_ITER, null);
    transitionStarted = false;
  }

  @Override
  public void init(SpacecraftState s0, AbsoluteDate t) {
  }

  @Override
  public double g(SpacecraftState s) throws OrekitException {
    return s.getAdditionalState("attitude")[0];
  }

  @Override
  public Action eventOccurred(SpacecraftState s, boolean increasing) throws OrekitException {
    return Action.CONTINUE;
  }

  @Override
  public SpacecraftState resetState(SpacecraftState oldState) throws OrekitException {
    transitionStarted = false;
    return oldState;
  }

  @Override
  protected NadirDetector create(double newMaxCheck, double newThreshold, int newMaxIter,
       EventHandler<? super NadirDetector> newHandler) {
    return new NadirDetector();
  }
}

My declaration of the attitude sequence is:

this.attitudesSequence = new AttitudesSequence();
NadirDetector nadirDetector = new NadirDetector();
attitudesSequence.addSwitchingCondition(sunPointing, nadirPointing, nadirDetector, true, true,
    10000.0, AngularDerivativesFilter.USE_RRA, null);
this.attitudesSequence.registerSwitchEvents(runningPropagator);

I call setSwitched every time before I call propagate() in my current propagator.

this.attitudeState.setSwitched(attitudeMode.equals(ATTITUDE_MODE.NADIR_POINTING));
this.spacecraftState = this.runningPropagator.propagate(extrapDate);

Hi Yannick,

I tried your code and it works fine for me. I just removed the unused parts of your code. Here is the way I use Attitude Sequence, the switch method and the propagator :

propagator.setAttitudeProvider(attitudesSequence);

attitudesSequence.registerSwitchEvents(propagator);
		
AttitudeStateProvider attitudeState = new AttitudeStateProvider();
propagator.addAdditionalStateProvider(attitudeState);

propagator.setMasterMode(60, new CustomStepHandler());

propagator.propagate(initialDate, initialDate.shiftedBy(1200));  
attitudeState.setSwitched(true);
propagator.propagate(initialDate.shiftedBy(1200), initialDate.shiftedBy(2400));

My step handler is used to print the date and the current attitude of the satellite every 120 seconds of propagation time. My transition is between the inertial pointing (1, 0, 0, 0) and the inertial pointing (0, 1, 0, 0) and is planned at 2000-01-01T12:20:00 with a transition time of 600s.

attitudesSequence.addSwitchingCondition(before, after, transitionDetector, true, true,
		600.0, AngularDerivativesFilter.USE_RRA, null);

See the prints of my step handler :

2000-01-01T12:18:00.000
1,000000   0,000000   0,000000   0,000000
attitude state : -1.0

2000-01-01T12:20:00.000
1,000000   0,000000   0,000000   0,000000
attitude state : -1.0

Transition Detected
2000-01-01T12:20:00.000
1,000000   0,000000   0,000000   0,000000
attitude state : -1.0

2000-01-01T12:22:00.000
0,993459   0,114189   0,000000   0,000000
attitude state : 1.0

2000-01-01T12:24:00.000
0,818419   0,574622   0,000000   0,000000
attitude state : 1.0

2000-01-01T12:26:00.000
0,366192   0,930539   0,000000   0,000000
attitude state : 1.0

2000-01-01T12:28:00.000
0,060274   0,998182   0,000000   0,000000
attitude state : 1.0

2000-01-01T12:30:00.000
0,000000   1,000000   0,000000   0,000000
attitude state : 1.0

The transition is smooth and lasts 600s, as expected.

I don’t see any problem in the code you sent. I think the problem comes from the way you use the propagator and how you synchronize it with an input command. Again, you can find exemples of how to use it in the attitudeSequenceTest class (even if they are not the most beautiful tests of Orekit
…).

You should try an attitude transition with an easier trigger (a DateDetector for exemple) to get some practise with it, and then try harder way to trigger the transition.

Regards,
Romaric

By the way, I don’t know how you want to use it, but Orekit is not well fitted to synchronous usages. The numerical propagation takes few seconds of computation (the analytical propagation is almost instantaneous) and the “nominal” way to use it is to compute the propagation first and then use the results once the propagation is done.

If you try to interact with the propagator during the propagation, the problem is probably here.

Concerning your last answer: When I set the condition in attitudeState before calling propagete this should not be considered as “during the propagation”, right? Btw, does the transition time (e.g. 600s in your example) need to be shorter than the dt in propagate (e.g. 1200s in your example)?

Cheers,
Yannick

Yes, that’s what I mean. The conditions must be fixed before the propagation. Unless you use multithreading, you cannot interact with the propagator once the propagation is started. (And I do not recommand to do it)

It’s not a problem, you can put whatever you want in transition time (strictly positive). The AttitudeSequence object is an attitude provider (an analytical attitude law) that covers absolutely all possible dates. The propagator just ask for the attitude at it’s current step date.

If I understand your needs, you want to see the orbit in a visualization tool and be able to trigger the attitude switch with a button ?

Yes exactly. I have a server running with Orekit, simulating satellite position and attitude and want to visualize the satellite in Celestia. On the satellite, the user has the possibility to change the attitude with a simple call to a method (i.e. pressing a button in control software) and I want to give him a kinda smooth transition for visualization.

Okay apparently I call propagate several times per second. This might be a problem?

I tried to make a for loop with very small propagations (from date to date+dt, with dt = 1s, 0.1s and 0.0001s) and it still works fine if I use the setSwitch method between 2 iterations of the loop. The switch is detected and the transition is smooth. This is my loop code :

for(int i = 0; i < 1000; i++) {		
  SpacecraftState currentState = propagator.propagate(initialDate, initialDate.shiftedBy(1.));  
  initialDate = initialDate.shiftedBy(0.0001);
  if(i==20) {attitudeState.setSwitched(true);}
  Rotation rotation = currentState.getAttitude().getRotation(); 
  double attitude = currentState.getAdditionalState("attitude")[0];
  System.out.println(currentState.getDate());
  System.out.format("%8f   %8f   %8f   %8f%n", rotation.getQ0(), rotation.getQ1(), rotation.getQ2(), rotation.getQ3());	
  System.out.println("attitude state : " + attitude + "\n");
}

of course I adjusted the transition duration to see something. (you can try 0.1s of transition duration in the AttitudeSequence and 0.01s of transition time in the AttitudeStateProvider).

Can you try this loop and tell me what is printed ? It should show the date, the quaternion of the current attitude and the attitude state (1 or -1) for each step. (I removed the step handler to not be redundant)
Also, make sure to initialize the propagator with both the attitude sequence, the switch event and the attitude provider.

propagator.resetInitialState(new SpacecraftState(orbit));

propagator.setAttitudeProvider(attitudesSequence);

attitudesSequence.registerSwitchEvents(propagator);
		
AttitudeStateProvider attitudeState = new AttitudeStateProvider();
propagator.addAdditionalStateProvider(attitudeState);

Hello Romaric,

I tried your code right after initialization to make sure nothing else of my stuff interferes. I was so free to export the output to a csv and to visualize it. This is what happens:


Is it important that I call setAttitudeProvider or is it sufficient if I define the propagater after defining the AttitudeSequence with switching condition and passing the attitudeSequence to the propagator’s constructor?

Just saw that I am still using version 7.2. Is that a possible issue?

It can be an big issue, indeed. As I told you, I corrected some bugs in AttitudeSequence recently, especially for stop and restart propagation during the transition.

Try to use the develop branch and to switch on the v10.0 once it is released (in a few days).

Hello Romaric,
I now used version 10.0-SNAPSHOT from your development branch and get the following output for the attitude change using your for-loop. First column is the current value of i, followed by the values of the quaternion. I’ll only post the values around the attitude change here.

16,0.219787,-0.672082,0.065653,-0.704052
17,0.219787,-0.672082,0.065653,-0.704052
18,0.219787,-0.672082,0.065653,-0.704052
19,0.219787,-0.672082,0.065653,-0.704052
20,0.219787,-0.672082,0.065653,-0.704052
21,0.685632,-0.073701,-0.703651,-0.171322
22,0.685632,-0.073701,-0.703651,-0.171322
23,0.685632,-0.073701,-0.703651,-0.171322
24,-1.000000,0.000003,-0.000003,0.000003
25,-1.000000,0.000003,-0.000003,0.000003
26,-1.000000,0.000003,-0.000003,0.000003

Does this look fine to you?

No, it is not fine.
I think I have found a bug for very small propagations duration, the switching event is detected multiple times and cause a strange behaviour.

Can you try the loop with propagations of at least 1 second ?

initialDate = initialDate.shiftedBy(1.);

and an transition duration of 30s for instance ?

EDIT : It is not a bug in Orekit, it is just a problem in our AttitudeStateProvider