docs: def compile_sequence(cycles, program_or_profile='program',
                       unit_converter=None):
      """ Makes the command list for a move sequence.
  
      Constructs the list of commands to execute the given sequence of
      motion. Program/command line commands or profile commands can be
      generated depending on the value of `program_or_profile` so that the
      commands can be used to construct a program or profile later. Types
      of motion supported (see Notes for how to specify) are moves from
      one position to another (the motion will always come to a stop
      before doing the next motion), waiting a given interval of time till
      starting the next move, and looping over a sequence of moves.
  
      Parameters
      ----------
      cycles : iterable of dicts
          The iterable of cycles of motion to do one after another. See
          Notes for format.
      program_or_profile : {'program', 'profile'}, optional
          Whether program or profile motion commands should be used.
          Anything other than these two values implies the default.
      unit_converter : UnitConverter, optional
          ``GeminiMotorDrive.utilities.UnitConverter`` to use to convert
          the units in `cycles` to motor units. ``None`` indicates that
          they are already in motor units.
  
      Returns
      -------
      commands : list of str
          ``list`` of ``str`` commands making up the move sequence.
  
      Notes
      -----
      `cycles` is an iterable of individual cycles of motion. Each cycle
      is a ``dict`` that represents a sequence of moves that could
      possibly be looped over. The field ``'iterations'`` gives how many
      times the sequence of moves should be done (a value > 1 implies a
      loop). Then the field ``'moves'`` is an iterable of the individual
      moves. Each individual move is a ``dict`` with the acceleration
      (``'A'``), deceleration (``'AD'`` with 0 meaning the value of the
      acceleration is used), velocity (``'V'``), and the distance/position
      (``'D'``). Back in the cycle, the field ``'wait_times'`` is an
      iterable of numbers giving the time in seconds to wait after each
      move before going onto the next.
  
      See Also
      --------
      get_sequence_time
      convert_sequence_to_motor_units
      GeminiMotorDrive.utilities.UnitConverter
  
      Examples
      --------
  
      Simple program style two motions with a pause in between.
  
      >>> from GeminiMotorDrive.compilers.move_sequence import *
      >>> cycles = [{'iterations':1, 'wait_times':[1, 0],
      ...           'moves':[{'A':100, 'AD':0, 'D':-1000, 'V':100},
      ...           {'A':90, 'AD':0, 'D':-1000, 'V':100}]}]
      >>> compile_sequence(cycles)
      ['A100',
       'AD0',
       'V100',
       'D-1000',
       'GO1',
       'WAIT(AS.1=b0)',
       'T1',
       'A90',
       'GO1',
       'WAIT(AS.1=b0)']
  
      The same motion but in profile style commands
  
      >>> from GeminiMotorDrive.compilers.move_sequence import *
      >>> cycles = [{'iterations':1, 'wait_times':[1, 0],
      ...           'moves':[{'A':100, 'AD':0, 'D':-1000, 'V':100},
      ...           {'A':90, 'AD':0, 'D':-1000, 'V':100}]}]
      >>> compile_sequence(cycles, program_or_profile='profile')
      ['A100',
       'AD100',
       'V100',
       'D-1000',
       'VF0',
       'GOBUF1',
       'GOWHEN(T=1000)',
       'A90',
       'AD90',
       'VF0',
       'GOBUF1']
  
      Another motion with a back and forth loop (100 iterations) in the
      middle, done in program style commands.
  
      >>> from GeminiMotorDrive.compilers.move_sequence import *
      >>> cycles = [{'iterations':1, 'wait_times':[1],
      ...           'moves':[{'A':100, 'AD':0, 'D':-1000, 'V':100}]},
      ...           {'iterations':100, 'wait_times':[0, 0],
      ...           'moves':[{'A':50, 'AD':40, 'D':-1000, 'V':30},
      ...           {'A':50, 'AD':40, 'D':1000, 'V':30}]},
      ...           {'iterations':1, 'wait_times':[0],
      ...           'moves':[{'A':100, 'AD':0, 'D':1000, 'V':100}]}]
      >>> compile_sequence(cycles)
      ['A100',
       'AD0',
       'V100',
       'D-1000',
       'GO1',
       'WAIT(AS.1=b0)',
       'T1',
       'L100',
       'A50',
       'AD40',
       'V30',
       'D-1000',
       'GO1',
       'WAIT(AS.1=b0)',
       'D~',
       'GO1',
       'WAIT(AS.1=b0)',
       'LN',
       'A100',
       'AD0',
       'V100',
       'GO1',
       'WAIT(AS.1=b0)']
  
      """
      # If needed, cycles needs to be converted to motor units.
      if unit_converter is None:
          cv_cycles = cycles
      else:
          cv_cycles = convert_sequence_to_motor_units(cycles, \
              unit_converter=unit_converter)
  
      # Initially, we have no commands in our command list.
      commands = []
  
      # The A, AD, D, and V parameters of the previous motion should be
      # kept track of because if they don't change from one motion to the
      # next, the commands to set them don't need to be included. They
      # will be started blank since there are no previous motions yet.
      previous_motion = {'A': [], 'AD': [], 'D': [], 'V': []}
  
      # Construct each cycle one by one.
      for cycle in cv_cycles:
          # If more than one iteration is being done, a loop needs to be
          # setup. It will be either 'L' or 'PLOOP' with the number of
          # iterations attached if it is a program or a profile
          # respectively. Since it will be tough to keep track of what
          # motion changed from the end of a loop to the beginning of it,
          # it is easier to just forget all previous motion values and set
          # them all at the beginning of the loop (clear previous_motion).
          iterations = int(cycle['iterations'])
          if iterations > 1:
              previous_motion = {'A': [], 'AD': [], 'D': [], 'V': []}
              if program_or_profile != 'profile':
                  commands.append('L' + str(iterations))
              else:
                  commands.append('PLOOP' + str(iterations))
  
          # Construct each individual move in the cycle.
          for i in range(0, len(cycle['moves'])):
              # Grab the motion indicated by the current move.
              new_motion = cycle['moves'][i]
  
              # If we are doing a profile, AD must be set explicitly
              # to A if it is 0.
              if program_or_profile == 'profile' \
                      and new_motion['AD'] == 0.0:
                  new_motion['AD'] = new_motion['A']
  
              # Set A, AD, and V if they have changed.
              for k in ('A', 'AD', 'V'):
                  if previous_motion[k] != new_motion[k]:
                      # Grab it and round it to 4 places after the decimal
                      # point because that is the most that is
                      # supported. Then, if it is an integer value,
                      # convert it to an integer because that is what the
                      # drive will send back if requested (makes
                      # comparisons easier). Then add the command.
                      val = round(float(new_motion[k]), 4)
                      if val == int(val):
                          val = int(val)
                      commands.append(k + str(val))
  
              # If the sign of D has flipped, we just need to issue a 'D~'
              # command. If the value has changed in another way, it needs
              # to be reset.
              if previous_motion['D'] != new_motion['D']:
                  if previous_motion['D'] == -new_motion['D']:
                      commands.append('D~')
                  else:
                      commands.append('D'
                                      + str(int(new_motion['D'])))
  
              # Grab the amount of time that should be waited after the
              # move is done.
              wait_time = cycle['wait_times'][i]
  
              # Give the motion command (GO or GOBUF), tell the drive to
              # wait till the motor has stopped (a WAIT command if it is a
              # program and a VF0 command if it is a profile), and make it
              # wait the period of time wait_time (T and GOWHEN commands).
              if program_or_profile != 'profile':
                  commands.append('GO1')
                  commands.append('WAIT(AS.1=b0)')
                  if wait_time != 0:
                      # The wait time needs to be rounded to 3 places
                      # after the decimal. If it is an integer, it should
                      # be converted to an int so that the drive will send
                      # back what we send (makes compairisons easier).
                      wait_time = round(float(wait_time), 3)
                      if wait_time == int(wait_time):
                          wait_time = int(wait_time)
                      commands.append('T' + str(wait_time))
              else:
                  commands.append('VF0')
                  commands.append('GOBUF1')
                  if wait_time != 0:
                      commands.append('GOWHEN(T='
                                      + str(int(1000*wait_time))
                                      + ')')
  
              # Before going onto the next move, previous_motion needs to
              # be set to the one just done.
              previous_motion = new_motion
  
          # Done with all the moves of the cycle. If we are looping, the
          # loop end needs to be put in.
          if iterations > 1:
              if program_or_profile != 'profile':
                  commands.append('LN')
              else:
                  commands.append('PLN')
  
      # Done constructing the command list.
      return commands