peregrine/iqgen/bits/tcxo_poly.py
class TCXOPoly(TCXOBase):
  '''
  Polynomial TCXO control class.
  '''

  def __init__(self, coeffs):
    '''
    Constructs TCXO control object.

    Parameters
    ----------
    coeffs : array-like
      Coefficients for TCXO polynome. These coeffificens define a TCXO drift
      over time in ppm.
    '''
    super(TCXOPoly, self).__init__()
    self.coeffs = tuple([x for x in coeffs])
    if coeffs:
      # Recompute drift coefficients from speed of drift into distance of drift
      new_coeffs = []
      power_c = len(coeffs)
      for idx, val in enumerate(coeffs):
        power = power_c - idx
        new_coeffs.append(val * 1e-6 / power)
      new_coeffs.append(0)
      self.poly = numpy.poly1d(new_coeffs)
    else:
      self.poly = None

  def __str__(self, *args, **kwargs):
    '''
    Provides string representation of the object
    '''
    return "TCXOPoly: coeffs=%s" % str(self.coeffs)

  def computeTcxoTime(self, fromSample, toSample, outputConfig):
    '''
    Method generates time vector for the given sample index range depending on
    TCXO behaviour.

    Parameters
    ----------
    fromSample : int
      Index of the first sample.
    toSample: int
      Index of the last sample plus 1.
    outputConfig : object
      Output configuration

    Returns
    -------
    numpy.ndarray(shape=(toSample - fromSample), dtype=numpy.float)
      Vector of the shifted time stamps for the given TCXO controller.
    '''
    poly = self.poly

    if poly:
      time0_s = fromSample / outputConfig.SAMPLE_RATE_HZ
      timeX_s = toSample / outputConfig.SAMPLE_RATE_HZ
      timeAll_s = numpy.linspace(time0_s,
                                 timeX_s,
                                 toSample - fromSample,
                                 endpoint=False,
                                 dtype=numpy.float)
      result = poly(timeAll_s)
    else:
      result = None

    return result
peregrine/iqgen/bits/encoder_glo.py
class GLONASSL1L2TwoBitsEncoder(TwoBandsTwoBitsEncoder):
  '''
  Generic single bit encoder for GLONASS L1 and L2 signals
  '''

  def __init__(self, outputConfig):
    '''
    Constructs GLONASS L1 and L2 dual band single bit encoder object.

    Parameters
    ----------
    outputConfig : object
      Output parameters object.
    '''
    super(GLONASSL1L2TwoBitsEncoder, self).__init__(outputConfig.GLONASS.L1.INDEX,
                                                    outputConfig.GLONASS.L2.INDEX)
peregrine/iqgen/bits/encoder_gps.py
class GPSL1L2BitEncoder(TwoBandsBitEncoder):
  '''
  Generic single bit encoder for GPS L1 C/A and L2 Civil signals
  '''

  def __init__(self, outputConfig):
    '''
    Constructs GPS L1 C/A and L2 C dual band single bit encoder object.

    Parameters
    ----------
    outputConfig : object
      Output parameters object.
    '''
    super(GPSL1L2BitEncoder, self).__init__(outputConfig.GPS.L1.INDEX,
                                            outputConfig.GPS.L2.INDEX)
peregrine/iqgen/bits/doppler_sine.py
class Doppler(DopplerBase):
  '''
  Doppler control for an object that has peridic acceleration.
  '''

  TWO_PI = scipy.constants.pi * 2.

  def __init__(self, distance0_m,  tec_epm2, speed0_mps, amplutude_mps, period_s):
    '''
    Constructs doppler control object for linear acceleration.

    Parameters
    ----------
    distance0_m : float
      Distance to object in meters at time 0.
    tec_epm2 : float
      Total free electron content integrated along line of sight to the object
      in electrons per m^2.
    speed0_mps : float
      Speed of satellite at time 0 in meters per second.
    amplutude_mps : float
      Amplitude of change
    period_s : float
      Period of change
    '''
    super(Doppler, self).__init__(distance0_m=distance0_m,
                                  tec_epm2=tec_epm2)
    self.speed0_mps = speed0_mps
    self.amplutude_mps = amplutude_mps
    self.period_s = period_s

  def __str__(self):
    '''
    Constructs literal presentation of object.

    Returns
    -------
    string
      Literal presentation of object
    '''
    return "SineDoppler(distance0_m={}, tec_epm2={}," \
           " speed0_mps={}, amplitude_mps={}, period_s={}," \
           " codeDopplerIgnored={})".\
        format(self.distance0_m, self.tec_epm2, self.speed0_mps,
               self.amplutude_mps, self.period_s, self.codeDopplerIgnored)

  def computeDistanceM(self, svTime_s):
    '''
    Computes doppler shift in meters.

    Parameters
    ----------
    svTime_s : float
      Time in seconds at which distance is computed. Please note that  is not
      a time of the observer.

    Returns
    -------
    float
      Distance to satellite in meters.
    '''
    return self.distance0_m + self.speed0_mps * svTime_s + \
        self.amplutude_mps * \
        (1 - numpy.cos(Doppler.TWO_PI * svTime_s / self.period_s))

  def computeSpeedMps(self, svTime_s):
    '''
    Computes speed along the vector to satellite in meters per second.

    Parameters
    ----------
    svTime_s : float
      Time in seconds at which speed is computed. Please note that  is not
      a time of the observer.

    Returns
    -------
    float
      Speed of satellite in meters per second.
    '''
    return self.speed0_mps + self.amplutude_mps * \
        numpy.sin(Doppler.TWO_PI * svTime_s / self.period_s)

  def computeDopplerShiftM(self, userTimeAll_s):
    '''
    Method to compute metric doppler shift

    Parameters
    ----------
    userTimeAll_s : numpy.ndarray(shape=(1, nSamples), dtype=numpy.float)
      Time vector for sample timestamps in seconds

    Returns
    -------
    numpy.ndarray(shape=(1, nSamples), dtype=numpy.float)
      Computed doppler shift in meters
    '''
    D_0 = self.speed0_mps
    D_1 = self.amplutude_mps * self.period_s / self.twoPi
    D_2 = self.twoPi / self.period_s

    doppler_m = numpy.cos(D_2 * userTimeAll_s)
    doppler_m -= 1.
    doppler_m *= -D_1
    if D_0:
      doppler_m += D_0 * userTimeAll_s

    return doppler_m

  def computeDopplerShiftHz(self, userTimeAll_s, carrierSignal):
    '''
    Method to compute doppler shift in Hz.

    Parameters
    ----------
    userTimeAll_s : numpy.ndarray(shape=(1, nSamples), dtype=numpy.float)
      Time vector for sample timestamps in seconds
    carrierSignal : object
      Carrier signal parameters

    Returns
    -------
    numpy.ndarray(shape=(1, nSamples), dtype=numpy.float)
      Computed doppler frquency shift in hertz
    '''
    D_0 = self.speed0_mps
    D_1 = self.amplutude_mps
    D_2 = self.twoPi / self.period_s

    doppler_hz = numpy.sin(D_2 * userTimeAll_s) * D_1
    if D_0:
      doppler_hz += D_0
    doppler_hz *= -float(carrierSignal.CENTER_FREQUENCY_HZ) / scipy.constants.c
    return doppler_hz
peregrine/iqgen/bits/encoder_glo.py
class GLONASSL1BitEncoder(BandBitEncoder):
  '''
  Generic single bit encoder for GLPNASS L1 signal
  '''

  def __init__(self, outputConfig):
    '''
    Constructs GLONASS L1 band single bit encoder object.

    Parameters
    ----------
    outputConfig : object
      Output parameters object.
    '''
    super(GLONASSL1BitEncoder, self).__init__(outputConfig.GLONASS.L1.INDEX)
peregrine/iqgen/if_iface.py
class LowRateConfig(object):
  '''
  Output control configuration for quick tests.

  Attributes
  ----------
  NAME : string
    Configuration name
  SAMPLE_RATE_HZ : float
    Sample rate in hertz for data generation.
  SAMPLE_BATCH_SIZE : int
    Size of the sample batch in samples.
  N_GROUPS : int
    Number of groups in the configuration
  GROUP_DELAYS: tuple(float * 4)
    Group delays for the configuration
  GPS : object
    GPS band information
  Galileo : object
    Galileo band information
  Beidou : object
    Beidou band information
  GLONASS : object
    Glonass band information
  '''
  NAME = "Low rate configuration for fast tests"
  SAMPLE_RATE_HZ = 24.84375e5
  SAMPLE_BATCH_SIZE = 100000
  N_GROUPS = 4
  GROUP_DELAYS = (0., 0., 0., 0.)

  class GPS(object):

    class L1(object):
      INTERMEDIATE_FREQUENCY_HZ = freq_profile_low_rate['GPS_L1_IF']
      INDEX = 0
      NAME = GPS_L1_NAME

    class L2(object):
      INTERMEDIATE_FREQUENCY_HZ = freq_profile_low_rate['GPS_L2_IF']
      INDEX = 1
      NAME = GPS_L2_NAME

  class GLONASS(object):

    class L1(object):
      INTERMEDIATE_FREQUENCIES_HZ = \
          [float(1200000 + b * 562500) for b in range(7)] + \
          [float(1200000 + b * 562500) for b in range(-7, 0)]
      INDEX = 2
      NAME = GLONASS_L1_NAME

    class L2(object):
      INTERMEDIATE_FREQUENCIES_HZ = \
          [float(1100000 + b * 437500) for b in range(7)] + \
          [float(1100000 + b * 437500) for b in range(-7, 0)]
      INDEX = 3
      NAME = GLONASS_L2_NAME

  class Galileo(object):

    class E1(object):
      INTERMEDIATE_FREQUENCY_HZ = 14.58e5
      INDEX = 0
      NAME = GALILEO_E1_NAME

    class E6(object):
      INTERMEDIATE_FREQUENCY_HZ = 43.75e5
      INDEX = 2
      NAME = GALILEO_E6_NAME

    class E5b(object):
      INTERMEDIATE_FREQUENCY_HZ = 27.86e5
      INDEX = 3
      NAME = GALILEO_E5B_NAME

  class Beidou(object):

    class B1(object):
      INTERMEDIATE_FREQUENCY_HZ = 28.902e5
      INDEX = 0
      NAME = BEIDOU_B1_NAME

    class B2:
      INTERMEDIATE_FREQUENCY_HZ = 27.86e5
      INDEX = 3
      NAME = BEIDOU_B2_NAME

    class B3(object):
      INTERMEDIATE_FREQUENCY_HZ = 33.52e5
      INDEX = 2
      NAME = BEIDOU_B3_NAME
peregrine/iqgen/bits/encoder_gps.py
class GPSL1BitEncoder(BandBitEncoder):
  '''
  Generic single bit encoder for GPS L1 C/A signal
  '''

  def __init__(self, outputConfig):
    '''
    Constructs GPS L1 C/A band single bit encoder object.

    Parameters
    ----------
    outputConfig : object
      Output parameters object.
    '''
    super(GPSL1BitEncoder, self).__init__(outputConfig.GPS.L1.INDEX)
peregrine/iqgen/if_iface.py
class HighRateConfig(object):
  '''
  Output control configuration for high data rate tests.

  Attributes
  ----------
  NAME : string
    Configuration name
  SAMPLE_RATE_HZ : float
    Sample rate in hertz for data generation.
  SAMPLE_BATCH_SIZE : int
    Size of the sample batch in samples.
  N_GROUPS : int
    Number of groups in the configuration
  GROUP_DELAYS: tuple(float * 4)
    Group delays for the configuration
  GPS : object
    GPS band information
  Galileo : object
    Galileo band information
  Beidou : object
    Beidou band information
  GLONASS : object
    Glonass band information
  '''
  NAME = "High rate configuration equivalent to full rate data output"
  SAMPLE_RATE_HZ = 99.375e6
  SAMPLE_BATCH_SIZE = 100000
  N_GROUPS = NormalRateConfig.N_GROUPS
  GROUP_DELAYS = NormalRateConfig.GROUP_DELAYS

  class GPS(object):
    '''
    Parameters for GPS bands data generation.
    '''
    class L1(object):
      INTERMEDIATE_FREQUENCY_HZ = freq_profile_high_rate['GPS_L1_IF']
      INDEX = 0
      NAME = GPS_L1_NAME

    class L2(object):
      INTERMEDIATE_FREQUENCY_HZ = freq_profile_high_rate['GPS_L2_IF']
      INDEX = 1
      NAME = GPS_L2_NAME

  GLONASS = NormalRateConfig.GLONASS
  Galileo = NormalRateConfig.Galileo
  Beidou = NormalRateConfig.Beidou
peregrine/iqgen/bits/doppler_poly.py
class Doppler(DopplerBase):
  '''
  Doppler control for an object that has constant acceleration. Such signal has
  constant doppler value with a possible sign invert.
  '''

  TWO_PI = scipy.constants.pi * 2

  def __init__(self, distance0_m, tec_epm2, coeffs):
    '''
    Constructs doppler control object for linear acceleration.

    Parameters
    ----------
    distance0_m : float
      Distance to object in meters at time 0.
    tec_epm2 : float
      Total free electron content integrated along line of sight to the object
      in electrons per m^2.
    coeffs : array-like
      Phase shift coefficients. Phase chift will be computed as:
      C_n*t^n + C_(n-1)^(n-1) + ... + C_2*t^2 + C_1*t + C_0
      C_n..C_0 - values for speed of light
    '''
    super(Doppler, self).__init__(distance0_m=distance0_m,
                                  tec_epm2=tec_epm2)
    self.coeffs = tuple([x for x in coeffs])
    self.n_coeffs = len(coeffs)
    self.speedPoly = None
    self.distancePoly = None
    if self.n_coeffs > 0:
      new_coeffs = []
      self.n_coeffs += 1
      for idx, c in enumerate(coeffs):
        order = self.n_coeffs - idx - 1
        new_coeffs.append(c / order)
      new_coeffs.append(0.)
      self.distancePoly = numpy.poly1d(new_coeffs)
      self.distanceCoeffs = new_coeffs
      if self.n_coeffs > 1:
        self.speedPoly = numpy.poly1d(coeffs)
    else:
      self.distanceCoeffs = None

  def __str__(self):
    '''
    Constructs literal presentation of object.

    Returns
    -------
    string
      Literal presentation of object
    '''
    return "DopplerPoly(coeffs={}, distance0_m={}," \
           " tec_epm2={} codeDopplerIgnored={})". \
           format(self.coeffs, self.distance0_m,
                  self.tec_epm2, self.codeDopplerIgnored)

  def computeDistanceM(self, svTime_s):
    '''
    Computes doppler shift in meters.

    Parameters
    ----------
    svTime_s : float
      Time in seconds at which distance is computed. Please note that  is not
      a time of the observer.

    Returns
    -------
    float
      Distance to satellite in meters.
    '''
    poly = self.distancePoly
    if poly is not None:
      return poly(svTime_s)  # self.coeffs[cnt - 1]
    else:
      return 0.

  def computeSpeedMps(self, svTime_s):
    '''
    Computes speed along the vector to satellite in meters per second.

    Parameters
    ----------
    svTime_s : float
      Time in seconds at which speed is computed. Please note that  is not
      a time of the observer.

    Returns
    -------
    float
      Speed of satellite in meters per second.
    '''
    poly = self.speedPoly
    if poly is not None:
      return poly(svTime_s)
    else:
      return 0.

  def computeDopplerShiftM(self, userTimeAll_s):
    '''
    Method to compute metric doppler shift

    Parameters
    ----------
    userTimeAll_s : numpy.ndarray(shape=(1, nSamples), dtype=numpy.float)
      Time vector for sample timestamps in seconds

    Returns
    -------
    numpy.ndarray(shape=(1, nSamples), dtype=numpy.float)
      Computed doppler shift in meters
    '''
    distancePoly = self.distancePoly
    if distancePoly is not None:
      # Slower, but simple
      doppler_m = distancePoly(userTimeAll_s)
    else:
      # No phase shift
      doppler_m = numpy.zeros_like(userTimeAll_s)
    return doppler_m

  def computeDopplerShiftHz(self, userTimeAll_s, carrierSignal):
    '''
    Method to compute doppler shift in Hz.

    Parameters
    ----------
    userTimeAll_s : numpy.ndarray(shape=(1, nSamples), dtype=numpy.float)
      Time vector for sample timestamps in seconds
    carrierSignal : object
      Carrier signal parameters

    Returns
    -------
    numpy.ndarray(shape=(1, nSamples), dtype=numpy.float)
      Computed doppler frquency shift in hertz
    '''
    speedPoly = self.speedPoly
    if speedPoly is not None:
      # Slower, but simple
      c0 = -float(carrierSignal.CENTER_FREQUENCY_HZ) / scipy.constants.c
      doppler_hz = speedPoly(userTimeAll_s) * c0
    else:
      # No phase shift
      doppler_hz = numpy.zeros_like(userTimeAll_s)
    return doppler_hz
peregrine/iqgen/bits/encoder_glo.py
class GLONASSL1L2BitEncoder(TwoBandsBitEncoder):
  '''
  Generic single bit encoder for GLONASS L1 and L2 signals
  '''

  def __init__(self, outputConfig):
    '''
    Constructs GLONASS L1 and L2 dual band single bit encoder object.

    Parameters
    ----------
    outputConfig : object
      Output parameters object.
    '''
    super(GLONASSL1L2BitEncoder, self).__init__(outputConfig.GLONASS.L1.INDEX,
                                                outputConfig.GLONASS.L2.INDEX)
peregrine/iqgen/bits/encoder_glo.py
class GLONASSL1TwoBitsEncoder(BandTwoBitsEncoder):
  '''
  Generic single bit encoder for GLONASS L1 signal
  '''

  def __init__(self, outputConfig):
    '''
    Constructs GLONASS L1 band single bit encoder object.

    Parameters
    ----------
    outputConfig : object
      Output parameters object.
    '''
    super(GLONASSL1TwoBitsEncoder, self).__init__(
        outputConfig.GLONASS.L1.INDEX)
peregrine/iqgen/bits/amplitude_poly.py
class AmplitudePoly(AmplitudeBase):
  '''
  Amplitude control with polynomial dependency over time.
  '''

  def __init__(self, units, coeffs):
    '''
    Constructs polynomial amplitude control object.

    Parameters
    coeffs : array-like
      Polynomial coefficients
    '''
    super(AmplitudePoly, self).__init__(units)

    self.coeffs = tuple([x for x in coeffs])
    if len(coeffs) > 0:
      self.poly = numpy.poly1d(coeffs)
    else:
      self.poly = None

  def __str__(self):
    '''
    Constructs literal presentation of object.

    Returns
    -------
    string
      Literal presentation of object
    '''
    return "AmplitudePoly(units={}, c={})".format(self.units, self.coeffs)

  def applyAmplitude(self, signal, userTimeAll_s, noiseParams):
    '''
    Applies amplitude modulation to signal.

    This method applies polynomial modulation.

    Parameters
    ----------
    signal : numpy.ndarray
      Signal sample vector. Each element defines signal amplitude in range
      [-1; +1]. This vector is modified in place.
    userTimeAll_s : numpy.ndarray
      Sample time vector. Each element defines sample time in seconds.
    noiseParams : NoiseParameters
      Noise parameters to adjust signal amplitude level.

    Returns
    -------
    numpy.ndarray
      Array with output samples
    '''

    poly = self.poly
    if poly is not None:
      amplitudeVector = poly(userTimeAll_s)
      amplitudeVector = AmplitudeBase.convertUnits2Amp(amplitudeVector,
                                                       self.units,
                                                       noiseParams)
      signal *= amplitudeVector
    else:
      amplitude = AmplitudeBase.convertUnits2Amp(1.,
                                                 self.units,
                                                 noiseParams)
      signal *= amplitude

    return signal

  def computeSNR(self, noiseParams):
    '''
    Computes signal to noise ratio in dB.

    noiseParams : NoiseParameters
      Noise parameter container

    Returns
    -------
    float
      SNR in dB
    '''
    poly = self.poly
    if poly is not None:
      value = poly(0.)
    else:
      value = 1.

    return AmplitudeBase.convertUnits2SNR(value, self.units, noiseParams)
peregrine/iqgen/if_iface.py
class CustomRateConfig(object):
  '''
  Output control configuration for comparison tests.

  Attributes
  ----------
  NAME : string
    Configuration name
  SAMPLE_RATE_HZ : float
    Sample rate in hertz for data generation.
  SAMPLE_BATCH_SIZE : int
    Size of the sample batch in samples.
  N_GROUPS : int
    Number of groups in the configuration
  GROUP_DELAYS: tuple(float * 4)
    Group delays for the configuration
  GPS : object
    GPS band information
  '''
  NAME = "Custom configuration for fast tests"
  SAMPLE_RATE_HZ = freq_profile_peregrine['sampling_freq']
  SAMPLE_BATCH_SIZE = 100000
  N_GROUPS = NormalRateConfig.N_GROUPS
  GROUP_DELAYS = NormalRateConfig.GROUP_DELAYS

  class GPS(object):

    class L1(object):
      INTERMEDIATE_FREQUENCY_HZ = freq_profile_peregrine['GPS_L1_IF']
      INDEX = 0
      NAME = GPS_L1_NAME

    class L2(object):
      INTERMEDIATE_FREQUENCY_HZ = freq_profile_peregrine['GPS_L2_IF']
      INDEX = 1
      NAME = GPS_L2_NAME

  class GLONASS(object):

    class L1(object):
      INTERMEDIATE_FREQUENCIES_HZ = \
          [float(6000000l + b * 562500l) for b in range(7)] + \
          [float(6000000l + b * 562500l) for b in range(-7, 0)]
      INDEX = 2
      NAME = GLONASS_L1_NAME

    class L2(object):
      INTERMEDIATE_FREQUENCIES_HZ = \
          [float(6000000l + b * 437500l) for b in range(7)] + \
          [float(6000000l + b * 437500l) for b in range(-7, 0)]
      INDEX = 3
      NAME = GLONASS_L2_NAME
peregrine/iqgen/bits/message_glo.py
class Message(object):
  '''
  GLONASS message generator
  '''

  def __init__(self, prn, tow0=1, n_msg=0, n_prefixBits=50):
    '''
    Constructs message object.

    Parameters
    ----------
    prn : int
      Satellite PRN
    tow0 : int
      Time of week in 6-second units for the first message
    n_msg : int, optional
      Number of messages to generate for output
    n_prefixBits : int, optional
      Number of bits to issue before the first message
    '''
    super(Message, self).__init__()
    self.prn = prn
    self.n_prefixBits = n_prefixBits
    self.n_msg0 = n_msg
    self.tow0 = tow0
    self.messageCount = 0
    self.messageLen = n_prefixBits
    self.nextTk_h = tow0 / (60 * 60) % (60 * 60 * 24)
    self.nextTk_m = tow0 / 60 % 60
    self.nextTk_30s = 1 if tow0 / 30 % 2 else 0

    self.nextMsgId = 1
    self.messageBits = numpy.zeros(n_prefixBits, dtype=numpy.uint8)
    self.messageBits[1::2] = 1
    self.a8 = numpy.ndarray(1, dtype=numpy.uint8)
    self.addMessages(n_msg)

  def __str__(self, *args, **kwargs):
    '''
    Formats object as string literal

    Returns
    -------
    string
      String representation of the object
    '''
    return "GLONASSS: prn=%d pref=%d tod=%02d:%02d:%02d" % \
           (self.prn, self.n_prefixBits, self.nextTk_h, self.nextTk_m,
            30 if self.nextTk_30s else 0)

  def getDataBits(self, dataAll_idx):
    '''
    Generates vector of data bits corresponding to input index

    Parameters
    ----------
    dataAll_idx : numpy.ndarray(dtype=numpy.int64)
      Vector of bit indexes

    Returns
    -------
    numpy.ndarray(dtype=numpy.uint8)
      Vector of data bits
    '''

    lastIdx = dataAll_idx[-1]
    if lastIdx >= self.messageLen:
      # Grow data bits
      delta = lastIdx - self.messageLen + 1
      newMsgCount = delta / 200
      if delta % 200:
        newMsgCount += 1
      self.addMessages(newMsgCount)

    # numpy.take degrades performance a lot over time.
    # return numpy.take(self.symbolData, dataAll_idx , mode='wrap')
    return self.messageBits[dataAll_idx]

  def addMessages(self, newMsgCount):
    '''
    Generate additional GLONASS messages

    This method generates and encodes additional LNAV messages. The message
    contents is added to the internal buffer.

    Parameters
    ----------
    newMsgCount : int
      Number of messages to generate
    '''
    newMessageLen = newMsgCount * 200 + self.messageLen
    newMessageData = numpy.ndarray(newMessageLen, dtype=numpy.uint8)
    newMessageData[:self.messageLen] = self.messageBits
    for i in range(self.messageLen, newMessageLen, 200):
      if self.nextMsgId == 1:
        logger.info("Starting new GLONASS frame: prn=%d frame tod=%02d:%02d:%02d" %
                    (self.prn,
                     self.nextTk_h, self.nextTk_m,
                     30 if self.nextTk_30s == 1 else 0))
      logger.debug("Generating GLONASS string: prn=%d msg=%d" %
                   (self.prn, self.nextMsgId))
      glo_msg = self.generateGloMessage()
      # First 170 symbols are 85 bits of message
      # Meander sequence: as per ICD, each data bit is added to 1/0 sequence
      newMessageData[i:i + 85 * 2:2] = glo_msg ^ 1
      newMessageData[i + 1:i + 85 * 2:2] = glo_msg
      # Last 30 symbols is the time mark
      newMessageData[i + 170:i + 200] = _TIME_MARK
    self.messageLen = newMessageLen
    self.messageBits = newMessageData
    self.messageCount += newMsgCount

  def generateGloMessage(self):
    '''
    Produces additional GLONASS message.
    Currently the method generates only type 1 GLONASS strings with ToD.

    Returns
    -------
    numpy.ndarray(shape=85, dtype=numpy.uint8)
      Message bits.
    '''
    msgData = numpy.zeros(85, dtype=numpy.uint8)

    if self.nextMsgId == 1:
      self.fillString1(msgData)
    else:
      self.fillString2_15(msgData)

    self.nextMsgId += 1
    if self.nextMsgId == 16:
      self.nextMsgId = 1

      # Frame has changed - the frame length is 30 seconds
      self.nextTk_30s += 1
      while self.nextTk_30s >= 2:
        self.nextTk_30s -= 2
        self.nextTk_m += 1
      while self.nextTk_m >= 60:
        self.nextTk_m -= 60
        self.nextTk_h += 1
      while self.nextTk_h >= 24:
        self.nextTk_h -= 24

    self.updateParity(msgData)

    return msgData

  def fillString1(self, msgData):
    msgData[0] = 0                                     # idle chip
    msgData[1:5] = self.getBits(0b0001, 4)             # m[4]
    # [2] - Reserved
    msgData[7:9] = self.getBits(0b00, 2)               # P1[2]

    msgData[9:14] = self.getBits(self.nextTk_h, 5)     # Tk[12]
    msgData[14:20] = self.getBits(self.nextTk_m, 6)    # Tk[12]
    msgData[26:27] = self.getBits(self.nextTk_30s, 1)  # Tk[12]
    msgData[28::2] = 1  # Zero + one everywhere

  def fillString2_15(self, msgData):
    msgData[1::2] = 1  # Zero + one everywhere

  def getBits(self, value, nBits):
    '''
    Converts integer into bit array

    Parameters
    ----------
    value : int
      Integer value
    nBits : number of bits to produce

    Returns
    -------
    numpy.ndarray(shape=(`nBits`), dtype=numpy.uint8)
      Parameter `value` represented as a bit array
    '''
    if nBits <= 8:
      self.a8[0] = value
      result = numpy.unpackbits(self.a8)
    else:  # pragma: no cover
      assert False
    return result[-nBits:]

  def updateParity(self, dataBits):
    '''
    Updates data bits and computes parity.

    When 85 bits are provided, they are used for parity computation and for
    bit inversion.

    Parameters
    ----------
    dataBits : numpy.ndarray(dtype=numpy.uint8)
      85 element array
    '''
    packed = numpy.packbits(dataBits)
    assert len(packed) == 11

    hc = _HAMMING_COEFFS
    for bIdx in range(8):
      p = 0
      for i in range(11):
        p ^= parity(packed[i] & hc[bIdx][i])
      dataBits[-(bIdx + 1)] = p
      packed[10] |= p << bIdx
peregrine/iqgen/bits/encoder_glo.py
class GLONASSL2BitEncoder(BandBitEncoder):
  '''
  Generic single bit encoder for GLONASS L2 signal
  '''

  def __init__(self, outputConfig):
    '''
    Constructs GLONASS L2 band single bit encoder object.

    Parameters
    ----------
    outputConfig : object
      Output parameters object.
    '''
    super(GLONASSL2BitEncoder, self).__init__(outputConfig.GLONASS.L2.INDEX)
peregrine/iqgen/bits/tcxo_sine.py
class TCXOSine(TCXOBase):
  '''
  Sine TCXO control class.
  '''

  def __init__(self, initial_ppm, amplitude_ppm, period_s):
    '''
    Constructs TCXO control object.

    Parameters
    ----------
    initial_ppm : float
      Initial drift in ppm
    amplitude_ppm : float
      Drift amplitude in ppm
    period_s : float
      Drift period in seconds
    '''
    super(TCXOSine, self).__init__()

    self.initial_ppm = initial_ppm
    self.amplitude_ppm = amplitude_ppm
    self.period_s = period_s
    self.c0 = -amplitude_ppm * 1e-6 * self.period_s / (2. * scipy.constants.pi)
    self.c1 = 2. * scipy.constants.pi / period_s
    self.c2 = initial_ppm * 1e-6

  def __str__(self, *args, **kwargs):
    '''
    Provides string representation of the object
    '''
    return "TCXOSine: initial_ppm=%f amplitude_ppm=%f period_s=%f" % \
           (self.initial_ppm, self.amplitude_ppm, self.period_s)

  def computeTcxoTime(self, fromSample, toSample, outputConfig):
    '''
    Method generates time vector for the given sample index range depending on
    TCXO behaviour.

    Parameters
    ----------
    fromSample : int
      Index of the first sample.
    toSample: int
      Index of the last sample plus 1.
    outputConfig : object
      Output configuration

    Returns
    -------
    numpy.ndarray(shape=(toSample - fromSample), dtype=numpy.float)
      Vector of the shifted time stamps for the given TCXO controller.
    '''
    c0 = self.c0
    c1 = self.c1
    c2 = self.c2
    time0_s = fromSample / outputConfig.SAMPLE_RATE_HZ
    timeX_s = toSample / outputConfig.SAMPLE_RATE_HZ

    timeAll_s = numpy.linspace(time0_s,
                               timeX_s,
                               toSample - fromSample,
                               endpoint=False,
                               dtype=numpy.float)

    result = numpy.cos(timeAll_s * c1)
    result += -1.
    result *= c0
    if c2:
      result += timeAll_s * c2

    return result
peregrine/iqgen/bits/satellite_gps.py
class GPSSatellite(Satellite):
  '''
  GPS satellite object.
  '''

  def __init__(self, prnNo):
    '''
    Constructs satellite object

    Parameters
    ----------
    prnNo : int
      GPS satellite number for selecting PRN.
    '''
    super(GPSSatellite, self).__init__("GPS{}".format(prnNo))
    self.prn = prnNo
    self.l2clCodeType = '01'
    self.l1caCode = GPS_L1CA_Code(prnNo)
    self.l2cCode = GPS_L2C_Code(prnNo, self.l2clCodeType)
    self.l1caEnabled = False
    self.l2cEnabled = False
    self.l1caMessage = DEFAULT_MESSAGE
    self.l2cMessage = DEFAULT_MESSAGE
    self.time0S = 0.
    self.pr0M = 0.
    self.phaseShift = 0.

  def setL1CAEnabled(self, enableFlag):
    '''
    Enables or disable GPS L1 C/A sample generation

    Parameters
    ----------
    enableFlag : boolean
      Flag to enable (True) or disable (False) GPS L1 C/A samples
    '''
    self.l1caEnabled = enableFlag

  def isL1CAEnabled(self):
    '''
    Tests if L1 C/A signal generation is enabled

    Returns
    -------
    bool
      True, when L1 C/A signal generation is enabled, False otherwise
    '''
    return self.l1caEnabled

  def setL2CEnabled(self, enableFlag):
    '''
    Enables or disable GPS L2 C sample generation

    Parameters
    ----------
    enableFlag : boolean
      Flag to enable (True) or disable (False) GPS L2 C samples
    '''
    self.l2cEnabled = enableFlag

  def isL2CEnabled(self):
    '''
    Tests if L2 C signal generation is enabled

    Returns
    -------
    bool
      True, when L2 C signal generation is enabled, False otherwise
    '''
    return self.l2cEnabled

  def setL2CLCodeType(self, clCodeType):
    '''
    Change L1 CL code type if needed

    Parameters
    ----------
    clCodeType : string
      L2 CL code type: '00', '01' or '11'
    '''
    if self.l2clCodeType != clCodeType:
      self.l2cCode = GPS_L2C_Code(self.prn, clCodeType)
      self.l2clCodeType = clCodeType

  def getL2CLCodeType(self):
    '''
    Get L2 CL code type

    Returns
    -------
    string
      L2 CL code type: '00', '01' or '11'
    '''
    return self.l2clCodeType

  def setL1CAMessage(self, message):
    '''
    Configures data source for L1 C/A signal.

    Parameters
    ----------
    message : object
      Message object that will provide symbols for L1 C/A signal.
    '''
    self.l1caMessage = message

  def setL2CMessage(self, message):
    '''
    Configures data source for L2 C signal.

    Parameters
    ----------
    message : object
      Message object that will provide symbols for L2 C signal.
    '''
    self.l2cMessage = message

  def getL1CAMessage(self):
    '''
    Returns configured message object for GPS L1 C/A band

    Returns
    -------
    object
      L1 C/A message object
    '''
    return self.l1caMessage

  def getL2CMessage(self):
    '''
    Returns configured message object for GPS L2 C band

    Returns
    -------
    object
      L2 C message object
    '''
    return self.l2cMessage

  def getBatchSignals(self,
                      userTimeAll_s,
                      samples,
                      outputConfig,
                      noiseParams,
                      band,
                      debug):
    '''
    Generates signal samples.

    Parameters
    ----------
    userTimeAll_s : numpy.ndarray(n_samples, dtype=numpy.float64)
      Vector of observer's timestamps in seconds for the interval start.
    samples : numpy.ndarray((4, n_samples))
      Array to which samples are added.
    outputConfig : object
      Output configuration object.
    noiseParams : NoiseParameters
      Noise parameters object
    band : Band
      Band description object.
    debug : bool
      Debug flag

    Returns
    -------
    list
      Debug information
    '''
    result = []
    if (self.l1caEnabled and band == outputConfig.GPS.L1):
      intermediateFrequency_hz = band.INTERMEDIATE_FREQUENCY_HZ
      values = self.doppler.computeBatch(userTimeAll_s,
                                         self.amplitude,
                                         noiseParams,
                                         signals.GPS.L1CA,
                                         intermediateFrequency_hz,
                                         self.l1caMessage,
                                         self.l1caCode,
                                         outputConfig,
                                         debug)
      numpy.add(samples[band.INDEX],
                values[0],
                out=samples[band.INDEX])
      debugData = {'type': "GPSL1", 'doppler': values[1]}
      result.append(debugData)
    if (self.l2cEnabled and band == outputConfig.GPS.L2):
      intermediateFrequency_hz = band.INTERMEDIATE_FREQUENCY_HZ
      values = self.doppler.computeBatch(userTimeAll_s,
                                         self.amplitude,
                                         noiseParams,
                                         signals.GPS.L2C,
                                         intermediateFrequency_hz,
                                         self.l2cMessage,
                                         self.l2cCode,
                                         outputConfig,
                                         debug)
      numpy.add(samples[band.INDEX],
                values[0],
                out=samples[band.INDEX])
      debugData = {'type': "GPSL2", 'doppler': values[1]}
      result.append(debugData)
    return result

  def isBandEnabled(self, band, outputConfig):
    '''
    Checks if particular band is supported and enabled.

    Parameters
    ----------
    band : Band
      Band description object.
    outputConfig : object
      Output configuration

    Returns:
    bool
      True, if the band is supported and enabled; False otherwise.
    '''
    result = None
    if band == outputConfig.GPS.L1:
      result = self.isL1CAEnabled()
    elif band == outputConfig.GPS.L2:
      result = self.isL2CEnabled()
    else:
      result = False
    return result
peregrine/iqgen/bits/encoder_glo.py
class GLONASSL2TwoBitsEncoder(BandTwoBitsEncoder):
  '''
  Generic single bit encoder for GLONASS L2 Civil signal
  '''

  def __init__(self, outputConfig):
    '''
    Constructs GLONASS L2 C band single bit encoder object.

    Parameters
    ----------
    outputConfig : object
      Output parameters object.
    '''
    super(GLONASSL2TwoBitsEncoder, self).__init__(
        outputConfig.GLONASS.L2.INDEX)
peregrine/iqgen/bits/encoder_gps.py
class GPSL2BitEncoder(BandBitEncoder):
  '''
  Generic single bit encoder for GPS L2 Civil signal
  '''

  def __init__(self, outputConfig):
    '''
    Constructs GPS L2 C band single bit encoder object.

    Parameters
    ----------
    outputConfig : object
      Output parameters object.
    '''
    super(GPSL2BitEncoder, self).__init__(outputConfig.GPS.L2.INDEX)
peregrine/iqgen/bits/amplitude_sine.py
class AmplitudeSine(AmplitudeBase):
  '''
  Amplitude control with sine modulation over time.
  '''

  def __init__(self, units, initial, amplitude, period_s):
    '''
    Constructs sine amplitude control object.

    Parameters
    initial : float
      Initial amplitude value (median)
    amplitude : float
      Amplitude of change
    period_s : float
      Period of change in seconds
    '''
    super(AmplitudeSine, self).__init__(units)
    self.initial = initial
    self.amplitude = amplitude
    self.period_s = period_s
    self.c = 2. * scipy.constants.pi / period_s

  def __str__(self):
    '''
    Constructs literal presentation of object.

    Returns
    -------
    string
      Literal presentation of object
    '''
    return "AmplitudeSine(units={}, base={}, amp={}, p={} s)".\
        format(self.units, self.initial, self.amplitude, self.period_s)

  def applyAmplitude(self, signal, userTimeAll_s, noiseParams):
    '''
    Applies amplitude modulation to signal.

    Parameters
    ----------
    signal : numpy.ndarray
      Signal sample vector. Each element defines signal amplitude in range
      [-1; +1]. This vector is modified in place.
    userTimeAll_s : numpy.ndarray
      Sample time vector. Each element defines sample time in seconds.
    noiseParams : NoiseParameters
      Noise parameters to adjust signal amplitude level.

    Returns
    -------
    numpy.ndarray
      Array with output samples
    '''

    ampAll = numpy.sin(userTimeAll_s * self.c) * self.amplitude + self.initial

    ampAll = AmplitudeBase.convertUnits2Amp(ampAll,
                                            self.units,
                                            noiseParams)
    signal *= ampAll

    return signal

  def computeSNR(self, noiseParams):
    '''
    Computes signal to noise ratio in dB.

    noiseParams : NoiseParameters
      Noise parameter container

    Returns
    -------
    float
      SNR in dB
    '''
    value = self.initial
    return AmplitudeBase.convertUnits2SNR(value, self.units, noiseParams)
peregrine/iqgen/if_iface.py
class NormalRateConfig(object):
  '''
  Output control configuration for normal tests.

  Attributes
  ----------
  NAME : string
    Configuration name
  SAMPLE_RATE_HZ : float
    Sample rate in hertz for data generation.
  SAMPLE_BATCH_SIZE : int
    Size of the sample batch in samples.
  N_GROUPS : int
    Number of groups in the configuration
  GROUP_DELAYS: tuple(float * 4)
    Group delays for the configuration
  GPS : object
    GPS band information
  Galileo : object
    Galileo band information
  Beidou : object
    Beidou band information
  GLONASS : object
    Glonass band information
  '''
  NAME = "Normal rate configuration equivalent to decimated data output"
  SAMPLE_RATE_HZ = 24.84375e6
  SAMPLE_BATCH_SIZE = 100000
  N_GROUPS = LowRateConfig.N_GROUPS
  GROUP_DELAYS = LowRateConfig.GROUP_DELAYS

  class GPS(object):
    '''
    Parameters for GPS bands data generation.
    '''
    class L1(object):
      INTERMEDIATE_FREQUENCY_HZ = freq_profile_normal_rate['GPS_L1_IF']
      INDEX = 0
      NAME = GPS_L1_NAME

    class L2(object):
      INTERMEDIATE_FREQUENCY_HZ = freq_profile_normal_rate['GPS_L2_IF']
      INDEX = 1
      NAME = GPS_L2_NAME

  class GLONASS(object):

    class L1(object):
      INTERMEDIATE_FREQUENCIES_HZ = \
          [float(12000000l + b * 562500l) for b in range(7)] + \
          [float(12000000l + b * 562500l) for b in range(-7, 0)]
      INDEX = 2
      NAME = GLONASS_L1_NAME

    class L2(object):
      INTERMEDIATE_FREQUENCIES_HZ = \
          [float(11000000l + b * 437500l) for b in range(7)] + \
          [float(11000000l + b * 437500l) for b in range(-7, 0)]
      INDEX = 3
      NAME = GLONASS_L2_NAME

  class Galileo(object):

    class E1(object):
      INTERMEDIATE_FREQUENCY_HZ = 14.58e6
      INDEX = 0
      NAME = GALILEO_E1_NAME

    class E6(object):
      INTERMEDIATE_FREQUENCY_HZ = 43.75e6
      INDEX = 2
      NAME = GALILEO_E6_NAME

    class E5b(object):
      INTERMEDIATE_FREQUENCY_HZ = 27.86e6
      INDEX = 3
      NAME = GALILEO_E5B_NAME

  class Beidou(object):

    class B1(object):
      INTERMEDIATE_FREQUENCY_HZ = 28.902e6
      INDEX = 0
      NAME = BEIDOU_B1_NAME

    class B2:
      INTERMEDIATE_FREQUENCY_HZ = 27.86e6
      INDEX = 3
      NAME = BEIDOU_B2_NAME

    class B3(object):
      INTERMEDIATE_FREQUENCY_HZ = 33.52e6
      INDEX = 2
      NAME = BEIDOU_B3_NAME
peregrine/iqgen/bits/satellite_glo.py
class GLOSatellite(Satellite):
  '''
  GLONASS satellite object.
  '''

  def __init__(self, prnNo):
    '''
    Constructs satellite object

    Parameters
    ----------
    prnNo : int
      GPS satellite number for selecting PRN.
    '''
    super(GLOSatellite, self).__init__("GLONASS{}".format(prnNo))
    self.prn = prnNo
    self.caCode = GLO_CA_Code(prnNo)
    self.l1Enabled = False
    self.l2Enabled = False
    self.l1Message = DEFAULT_MESSAGE
    self.l2Message = DEFAULT_MESSAGE
    self.time0S = 0.
    self.pr0M = 0.
    self.phaseShift = 0.

  def setL1Enabled(self, enableFlag):
    '''
    Enables or disable GLONASS L1 C/A sample generation

    Parameters
    ----------
    enableFlag : boolean
      Flag to enable (True) or disable (False) GPS L1 C/A samples
    '''
    self.l1Enabled = enableFlag

  def isL1Enabled(self):
    '''
    Tests if L1 C/A signal generation is enabled

    Returns
    -------
    bool
      True, when L1 C/A signal generation is enabled, False otherwise
    '''
    return self.l1Enabled

  def setL2Enabled(self, enableFlag):
    '''
    Enables or disable GLONASS L2 C sample generation

    Parameters
    ----------
    enableFlag : boolean
      Flag to enable (True) or disable (False) GPS L2 C samples
    '''
    self.l2Enabled = enableFlag

  def isL2Enabled(self):
    '''
    Tests if L2 C signal generation is enabled

    Returns
    -------
    bool
      True, when L2 C signal generation is enabled, False otherwise
    '''
    return self.l2Enabled

  def setL1Message(self, message):
    '''
    Configures data source for L1 C/A signal.

    Parameters
    ----------
    message : object
      Message object that will provide symbols for L1 C/A signal.
    '''
    self.l1Message = message
    self.l2Message = message

  def setL2Message(self, message):
    '''
    Configures data source for L2 C signal.

    Parameters
    ----------
    message : object
      Message object that will provide symbols for L2 C signal.
    '''
    pass

  def getL1Message(self):
    '''
    Returns configured message object for GPS L1 C/A band

    Returns
    -------
    object
      L1 C/A message object
    '''
    return self.l1Message

  def getL2Message(self):
    '''
    Returns configured message object for GPS L2 C band

    Returns
    -------
    object
      L2 C message object
    '''
    return self.l1Message

  def getBatchSignals(self,
                      userTimeAll_s,
                      samples,
                      outputConfig,
                      noiseParams,
                      band,
                      debug):
    '''
    Generates signal samples.

    Parameters
    ----------
    userTimeAll_s : numpy.ndarray(n_samples, dtype=numpy.float64)
      Vector of observer's timestamps in seconds for the interval start.
    samples : numpy.ndarray((4, n_samples))
      Array to which samples are added.
    outputConfig : object
      Output configuration object.
    noiseParams : NoiseParameters
      Noise parameters object
    band : Band
      Band description object.
    debug : bool
      Debug flag

    Returns
    -------
    list
      Debug information
    '''
    result = []
    if (self.l1Enabled and band == outputConfig.GLONASS.L1):
      intermediateFrequency_hz = band.INTERMEDIATE_FREQUENCIES_HZ[self.prn]
      values = self.doppler.computeBatch(userTimeAll_s,
                                         self.amplitude,
                                         noiseParams,
                                         signals.GLONASS.L1S[self.prn],
                                         intermediateFrequency_hz,
                                         self.l1Message,
                                         self.caCode,
                                         outputConfig,
                                         debug)
      numpy.add(samples[band.INDEX],
                values[0],
                out=samples[band.INDEX])
      debugData = {'type': "GLOL1", 'doppler': values[1]}
      result.append(debugData)
    if (self.l2Enabled and band == outputConfig.GLONASS.L2):
      intermediateFrequency_hz = band.INTERMEDIATE_FREQUENCIES_HZ[self.prn]
      values = self.doppler.computeBatch(userTimeAll_s,
                                         self.amplitude,
                                         noiseParams,
                                         signals.GLONASS.L2S[self.prn],
                                         intermediateFrequency_hz,
                                         self.l2Message,
                                         self.caCode,
                                         outputConfig,
                                         debug)
      numpy.add(samples[band.INDEX],
                values[0],
                out=samples[band.INDEX])
      debugData = {'type': "GLOL2", 'doppler': values[1]}
      result.append(debugData)
    return result

  def isBandEnabled(self, band, outputConfig):
    '''
    Checks if particular band is supported and enabled.

    Parameters
    ----------
    band : Band
      Band description object.
    outputConfig : object
      Output configuration

    Returns:
    bool
      True, if the band is supported and enabled; False otherwise.
    '''
    result = None
    if band == outputConfig.GLONASS.L1:
      result = self.isL1Enabled()
    elif band == outputConfig.GLONASS.L2:
      result = self.isL2Enabled()
    else:
      result = False
    return result
peregrine/iqgen/bits/message_lnav.py
class Message(object):
  '''
  GPS LNAV message generator
  '''

  def __init__(self, prn, tow0=1, n_msg=0, n_prefixBits=50):
    '''
    Constructs message object.

    Parameters
    ----------
    prn : int
      Satellite PRN
    tow0 : int
      Time of week in 6-second units for the first message
    n_msg : int, optional
      Number of messages to pre-generate for output
    n_prefixBits : int, optional
      Number of bits to issue before the first message
    '''
    super(Message, self).__init__()
    self.prn = prn
    self.n_prefixBits = n_prefixBits
    self.n_msg0 = n_msg
    self.tow0 = tow0
    self.messageCount = 0
    self.messageLen = n_prefixBits
    self.nextTow = tow0
    self.nextMsgId = 0
    self.messageBits = numpy.zeros(n_prefixBits, dtype=numpy.uint8)
    words = (n_prefixBits + 29) / 30
    if words:
      tmp = numpy.zeros(words * 30, dtype=numpy.uint8)
      tmp[1::2] = 1
      if words > 1:
        self.updateParity(tmp[0:30])
        for i in range(1, words - 1):
          self.updateParity(tmp[i * 30 - 2: i * 30 + 30])
        self.updateParity(tmp[words * 30 - 32: words * 30], True)
      else:
        self.updateParity(tmp[0: 30], True)
      self.messageBits[:] = tmp[-n_prefixBits:]
    self.a8 = numpy.ndarray(1, dtype=numpy.uint8)
    self.a32 = numpy.ndarray(1, dtype=numpy.dtype('>u4'))
    self.addMessages(n_msg)

  def __str__(self, *args, **kwargs):
    '''
    Formats object as string literal

    Returns
    -------
    string
      String representation of the object
    '''
    return "GPS LNAV: prn=%d pref=%d tow=%d" % \
           (self.prn, self.n_prefixBits, self.nextTow)

  def getDataBits(self, dataAll_idx):
    '''
    Generates vector of data bits corresponding to input index

    Parameters
    ----------
    dataAll_idx : numpy.ndarray(dtype=numpy.int64)
      Vector of bit indexes

    Returns
    -------
    numpy.ndarray(dtype=numpy.uint8)
      Vector of data bits
    '''

    lastIdx = dataAll_idx[-1]
    if lastIdx >= self.messageLen:
      # Grow data bits
      delta = lastIdx - self.messageLen + 1
      newMsgCount = delta / 300
      if delta % 300:
        newMsgCount += 1
      self.addMessages(newMsgCount)

    # numpy.take degrades performance a lot over time.
    # return numpy.take(self.symbolData, dataAll_idx , mode='wrap')
    return self.messageBits[dataAll_idx]

  def addMessages(self, newMsgCount):
    '''
    Generate additional LNAV messages

    This method generates and encodes additional LNAV messages. The message
    contents is added to the internal buffer.

    Parameters
    ----------
    newMsgCount : int
      Number of messages to generate
    '''
    if newMsgCount == 0:
      return

    newMessageLen = newMsgCount * 300 + self.messageLen
    newMessageData = numpy.ndarray(newMessageLen, dtype=numpy.uint8)
    newMessageData[:self.messageLen] = self.messageBits
    for i in range(self.messageLen, newMessageLen, 300):
      logger.info("Generating LNAV message: prn=%d tow=%d msg_id=%d" %
                  (self.prn, self.nextTow, self.nextMsgId))
      lnav_msg = self.generateLNavMessage()
      newMessageData[i:i + 300] = lnav_msg
    self.messageLen = newMessageLen
    self.messageBits = newMessageData
    self.messageCount += newMsgCount

    return

  def generateLNavMessage(self):
    '''
    Produces additional GPS LNAV message.

    Returns
    -------
    numpy.ndarray(shape=300, dtype=numpy.uint8)
      Message bits.
    '''
    msgData = numpy.zeros(300, dtype=numpy.uint8)
    msgData[1::2] = 1  # Zero + one everywhere

    # TLM word
    self.fillTlmWord(msgData[0:30], self.nextMsgId)
    self.nextMsgId += 1
    self.updateParity(msgData[0:30])
    # logger.debug("TLM: %s" % msgData[0:30])

    # TOW word
    self.fillTowWord(msgData[30:60], self.nextTow)
    self.nextTow += 1
    if self.nextTow == 7 * 24 * 60 * 10:
      self.nextTow = 0
    self.updateParity(msgData[28:60], True)
    # logger.debug("TOW: %s" % msgData[30:60])

    self.updateParity(msgData[58:90])
    self.updateParity(msgData[88:120])
    self.updateParity(msgData[118:150])
    self.updateParity(msgData[148:180])
    self.updateParity(msgData[178:210])
    self.updateParity(msgData[208:240])
    self.updateParity(msgData[238:270])
    self.updateParity(msgData[268:300], True)

    return msgData

  def getBits(self, value, nBits):
    '''
    Converts integer into bit array

    Parameters
    ----------
    value : int
      Integer value
    nBits : number of bits to produce

    Returns
    -------
    numpy.ndarray(shape=(`nBits`), dtype=numpy.uint8)
      Parameter `value` represented as a bit array
    '''
    if nBits <= 8:
      self.a8[0] = value
      result = numpy.unpackbits(self.a8)
    else:
      self.a32[0] = value
      result = numpy.unpackbits(self.a32.view(dtype=numpy.uint8))
    return result[-nBits:]

  def fillTlmWord(self, wordBits, msgId=0):
    '''
    Fills in TLM word contents.

    Parameters
    ----------
    wordBits : numpy.ndarray(shape=30, type=numpy.uint8)
      Destination array
    '''
    wordBits[0:8] = self.getBits(0b10001011, 8)  # Preamble
    wordBits[8:22] = self.getBits(msgId, 14)  # TML message
    wordBits[22] = 0  # Reserved
    wordBits[23] = 0  # Integrity
    return

  def fillTowWord(self, wordBits, tow):
    '''
    Fills in TOW word contents.

    Parameters
    ----------
    wordBits : numpy.ndarray(shape=30, type=numpy.uint8)
      Destination array
    '''
    wordBits[0:17] = self.getBits(tow, 17)  # TOW count in 6 second units
    wordBits[17] = 0  # Alert Flag
    wordBits[18] = 0  # Anti-Spoof flag
    wordBits[19:22] = self.getBits(0, 3)  # Sub-frame ID
    return

  def updateParity(self, dataBits, resolve=False):
    '''
    Updates data bits and computes parity.

    When 32 bits are provided, they are used for parity computation and for
    bit inversion.

    Parameters
    ----------
    dataBits : numpy.ndarray(dtype=numpy.uint8)
      30 or 32 element array
    resolve: bool, optional
      When specified, bits d23 and d24 of the GPS word are updated to ensure
      that parity bits d29 and d30 are zeros.
    '''
    packed = numpy.packbits(dataBits)
    acc = (packed[0] << 24) | (packed[1] << 16) | \
          (packed[2] << 8) | packed[3]
    if len(dataBits) == 30:
      acc >>= 2
    elif acc & 0x40000000:
      acc ^= 0x3FFFFFC0
      dataBits[-30:-6] ^= 1

    # D29 = D30*^d1^d3^d5^d6^d7^d9^d10^d14^d15^d16^d17^d18^d21^d22^d24
    d29 = parity(acc & 0b01101011101100011111001101000000)
    # D30 = D29*^d3^d5^d6^d8^d9^d10^d11^d13^d15^d19^d22^d23^d24
    d30 = parity(acc & 0b10001011011110101000100111000000)

    if resolve:
      if d29:
        acc ^= 0x80
        d29 = False
        d30 = not d30
        dataBits[-8] ^= 1
      if d30:
        acc ^= 0x40
        d30 = False
        dataBits[-7] ^= 1

    # D25 = D29*^d1^d2^d3^d5^d6^d10^d11^d12^d13^d14^d17^d18^d20^d23
    dataBits[-6] = parity(acc & 0b10111011000111110011010010000000)
    # D26 = D30*^d2^d3^d4^d6^d7^d11^d12^d13^d14^d15^d18^d19^d21^d24
    dataBits[-5] = parity(acc & 0b01011101100011111001101001000000)
    # D27 = D29*^d1^d3^d4^d5^d7^d8^d12^d13^d14^d15^d16^d19^d20^d22
    dataBits[-4] = parity(acc & 0b10101110110001111100111000000000)
    # D28 = D30*^d2^d4^d5^d6^d8^d9^d13^d14^d15^d16^d17^d20^d21^d23
    dataBits[-3] = parity(acc & 0b01010111011000111110011010000000)
    # D29 = D30*^d1^d3^d5^d6^d7^d9^d10^d14^d15^d16^d17^d18^d21^d22^d24
    dataBits[-2] = d29
    # D30 = D29*^d3^d5^d6^d8^d9^d10^d11^d13^d15^d19^d22^d23^d24
    dataBits[-1] = d30

    return
peregrine/iqgen/bits/encoder_gps.py
class GPSL1TwoBitsEncoder(BandTwoBitsEncoder):
  '''
  Generic single bit encoder for GPS L1 C/A signal
  '''

  def __init__(self, outputConfig):
    '''
    Constructs GPS L1 C/A band single bit encoder object.

    Parameters
    ----------
    outputConfig : object
      Output parameters object.
    '''
    super(GPSL1TwoBitsEncoder, self).__init__(outputConfig.GPS.L1.INDEX)
peregrine/iqgen/bits/message_const.py
class Message(object):
  '''
  Message consisting of same bits
  '''

  def __init__(self, bitValue):
    '''
    Initializes object.

    Parameters
    ----------
    bitValue : int
      Value for the bits. 1 for 0 bits, -1 for 1 bits.
    '''
    super(Message, self).__init__()
    self.bitValue = bitValue
    self.binValue = 1 if bitValue < 0 else 0

  def __str__(self, *args, **kwargs):
    '''
    Formats object as string literal

    Returns
    -------
    string
      String representation of the object
    '''
    return "Const: bit value=%d" % self.binValue

  def getDataBits(self, dataAll_idx):
    '''
    Generates vector of data bits corresponding to input index

    Parameters
    ----------
    dataAll_idx : numpy.ndarray(dtype=numpy.int64)
      Vector of bit indexes

    Returns
    -------
    numpy.ndarray(dtype=numpy.uint8)
      Vector of data bits
    '''
    result = numpy.ndarray(len(dataAll_idx), dtype=numpy.uint8)
    result.fill(self.binValue)
    return result
peregrine/iqgen/bits/encoder_gps.py
class GPSL1L2TwoBitsEncoder(TwoBandsTwoBitsEncoder):
  '''
  Generic single bit encoder for GPS L1 C/A and L2 Civil signals
  '''

  def __init__(self, outputConfig):
    '''
    Constructs GPS L1 C/A and L2 C dual band single bit encoder object.

    Parameters
    ----------
    outputConfig : object
      Output parameters object.
    '''
    super(GPSL1L2TwoBitsEncoder, self).__init__(outputConfig.GPS.L1.INDEX,
                                                outputConfig.GPS.L2.INDEX)
peregrine/iqgen/bits/message_cnav.py
class Message(object):
  '''
  GPS LNAV message block.

  The object provides proper-formatted CNAV messages with random contents.
  '''

  def __init__(self, prn, tow0=2, n_msg=0, n_prefixBits=50):
    '''
    Constructs message object.

    Parameters
    ----------
    prn : int
      Satellite PRN
    tow0 : int
      Time of week in 6-second units for the first message
    n_msg : int, optional
      Number of messages to generate for output
    n_prefixBits : int, optional
      Number of bits to issue before the first message
    '''
    super(Message, self).__init__()

    if tow0 & 1:
      logger.error("Initial ToW is not multiple of 2")

    self.prn = prn
    self.tow0 = tow0
    self.n_msg0 = n_msg
    self.n_prefixBits = n_prefixBits

    self.encoder = ConvEncoder27()
    self.messageCount = 0
    self.messageLen = n_prefixBits * 2
    self.symbolData = numpy.zeros(self.messageLen, dtype=numpy.uint8)

    prefixBits = numpy.zeros(self.n_prefixBits, dtype=numpy.uint8)
    prefixBits[0::2] = 1
    self.symbolData[:] = self.encoder.encode(prefixBits)
    self.nextTow = tow0
    self.addMessages(n_msg)

  def __str__(self, *args, **kwargs):
    '''
    Formats object as string literal

    Returns
    -------
    string
      String representation of the object
    '''
    return "GPS CNAV: prn=%d pref=%d tow=%d" % \
           (self.prn, self.n_prefixBits, self.nextTow)

  def getDataBits(self, dataAll_idx):
    '''
    Generates vector of data bits corresponding to input index

    Parameters
    ----------
    dataAll_idx : numpy.ndarray(dtype=numpy.int64)
      Vector of bit indexes

    Returns
    -------
    numpy.ndarray(dtype=numpy.uint8)
      Vector of data bits
    '''

    lastIdx = dataAll_idx[-1]
    if lastIdx >= self.messageLen:
      # Grow data bits
      delta = lastIdx - self.messageLen + 1
      newMsgCount = delta / 600
      if delta % 600:
        newMsgCount += 1
      self.addMessages(newMsgCount)

    # numpy.take degrades performance a lot over time.
    # return numpy.take(self.symbolData, dataAll_idx , mode='wrap')
    return self.symbolData[dataAll_idx]

  def addMessages(self, newMsgCount):
    '''
    Generate additional CNAV messages

    This method generates and encodes additional CNAV messages. The message
    contents is encoded using 2-7 convolution encoder and added to the internal
    buffer.

    Parameters
    ----------
    newMsgCount : int
      Number of messages to generate
    '''
    newMessageLen = newMsgCount * 600 + self.messageLen
    newSymbolData = numpy.ndarray(newMessageLen, dtype=numpy.uint8)
    newSymbolData[:self.messageLen] = self.symbolData
    for i in range(self.messageLen, newMessageLen, 600):
      logger.info("Generating CNAV message: prn=%d tow=%d msg_id=%d" %
                  (self.prn, self.nextTow, 0))
      cnav_msg = CNavRawMsg.generate(self.prn, 0, self.nextTow)
      self.nextTow += 2
      if self.nextTow == 7 * 24 * 60 * 10:
        self.nextTow = 0
      encoded = self.encoder.encode(cnav_msg)
      newSymbolData[i:i + 600] = encoded
    self.messageLen = newMessageLen
    self.symbolData = newSymbolData
    self.messageCount += newMsgCount
peregrine/iqgen/bits/encoder_gps.py
class GPSL2TwoBitsEncoder(BandTwoBitsEncoder):
  '''
  Generic single bit encoder for GPS L2 Civil signal
  '''

  def __init__(self, outputConfig):
    '''
    Constructs GPS L2 C band single bit encoder object.

    Parameters
    ----------
    outputConfig : object
      Output parameters object.
    '''
    super(GPSL2TwoBitsEncoder, self).__init__(outputConfig.GPS.L2.INDEX)
peregrine/iqgen/bits/message_zeroone.py
class Message(object):
  '''
  Message that contains zeros and ones
  '''

  def __init__(self):
    '''
    Constructs object.
    '''
    super(Message, self).__init__()
    self.bits = numpy.asarray([0, 1], dtype=numpy.uint8)

  def __str__(self, *args, **kwargs):
    '''
    Formats object as string literal

    Returns
    -------
    string
      String representation of the object
    '''
    return "ZeroOne"

  def getDataBits(self, dataAll_idx):
    '''
    Generates vector of data bits corresponding to input index

    Parameters
    ----------
    dataAll_idx : numpy.ndarray(dtype=numpy.int64)
      Vector of bit indexes

    Returns
    -------
    numpy.ndarray(dtype=numpy.uint8)
      Vector of data bits
    '''
    # numpy.take degrades performance a lot over time.
    # return numpy.take(self.bits, dataAll_idx , mode='wrap')
    return self.bits[dataAll_idx & 1]


Question: import sys
import argparse
from peregrine.iqgen.bits.satellite_gps import GPSSatellite
from peregrine.iqgen.bits.satellite_glo import GLOSatellite
from peregrine.iqgen.if_iface import LowRateConfig
from peregrine.iqgen.if_iface import NormalRateConfig
from peregrine.iqgen.if_iface import HighRateConfig
from peregrine.iqgen.if_iface import CustomRateConfig
from peregrine.iqgen.iqgen_main import computeDistanceDelay
from peregrine.iqgen.iqgen_main import prepareArgsParser
from peregrine.iqgen.iqgen_main import selectOutputConfig
from peregrine.iqgen.iqgen_main import printOutputConfig
from peregrine.iqgen.iqgen_main import computeEnabledBands
from peregrine.iqgen.iqgen_main import selectEncoder
from peregrine.iqgen.iqgen_main import makeProgressBar
from peregrine.iqgen.bits.doppler_poly import Doppler as DopplerPoly
from peregrine.iqgen.bits.doppler_sine import Doppler as DopplerSine
from peregrine.iqgen.bits.signals import GPS
from peregrine.iqgen.bits.message_const import Message as ConstMessage
from peregrine.iqgen.bits.message_zeroone import Message as ZeroOneMessage
from peregrine.iqgen.bits.message_cnav import Message as CNavMessage
from peregrine.iqgen.bits.message_lnav import Message as LNavMessage
from peregrine.iqgen.bits.message_glo import Message as GLOMessage
from peregrine.iqgen.bits.amplitude_poly import AmplitudePoly
from peregrine.iqgen.bits.amplitude_sine import AmplitudeSine
from peregrine.iqgen.bits.encoder_gps import GPSL1BitEncoder
from peregrine.iqgen.bits.encoder_gps import GPSL2BitEncoder
from peregrine.iqgen.bits.encoder_gps import GPSL1L2BitEncoder
from peregrine.iqgen.bits.encoder_gps import GPSL1TwoBitsEncoder
from peregrine.iqgen.bits.encoder_gps import GPSL2TwoBitsEncoder
from peregrine.iqgen.bits.encoder_gps import GPSL1L2TwoBitsEncoder
from peregrine.iqgen.bits.encoder_glo import GLONASSL1BitEncoder
from peregrine.iqgen.bits.encoder_glo import GLONASSL2BitEncoder
from peregrine.iqgen.bits.encoder_glo import GLONASSL1L2BitEncoder
from peregrine.iqgen.bits.encoder_glo import GLONASSL1TwoBitsEncoder
from peregrine.iqgen.bits.encoder_glo import GLONASSL2TwoBitsEncoder
from peregrine.iqgen.bits.encoder_glo import GLONASSL1L2TwoBitsEncoder
from peregrine.iqgen.bits.encoder_other import GPSGLONASSBitEncoder
from peregrine.iqgen.bits.encoder_other import GPSGLONASSTwoBitsEncoder
from peregrine.iqgen.bits.tcxo_sine import TCXOSine
from peregrine.iqgen.bits.tcxo_poly import TCXOPoly
from scipy.constants import c as C
# Copyright (C) 2016 Swift Navigation Inc.
#
# Contact: Valeri Atamaniouk <valeri@swiftnav.com>
# This source is subject to the license found in the file 'LICENSE' which must
# be be distributed together with this source. All other rights reserved.
#
# THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
# EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
'''
Unit tests for IQgen program entry point and utilities
'''

# SV

# Profiles


# Message data


# Bit stream encoders



# TCXO



def test_computeDistanceDelay0():
  '''
  Utility test
  '''
  symbolIndex = 0.
  chipIndex = 0.
  signal = GPS.L1CA
  delay = computeDistanceDelay(symbolIndex, chipIndex, signal)
  assert delay == 0.


def test_computeDistanceDelay1():
  '''
  Utility test
  '''
  symbolIndex = 1.
  chipIndex = 0.
  signal = GPS.L1CA
  delay = computeDistanceDelay(symbolIndex, chipIndex, signal)
  assert delay == 1. / signal.SYMBOL_RATE_HZ * C


def test_argparser_init():
  '''
  Argument parser initialization test
  '''
  parser = prepareArgsParser()
  assert isinstance(parser, argparse.ArgumentParser)


def test_selectOutputConfig0():
  '''
  Output configuration selection test
  '''
  config = selectOutputConfig('low_rate')

Answer:   assert config == LowRateConfig