/* -*- c++ -*- */
/*
 * Copyright 2010-2012,2014 Free Software Foundation, Inc.
 *
 * This file is part of GNU Radio
 *
 * GNU Radio is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3, or (at your option)
 * any later version.
 *
 * GNU Radio is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with GNU Radio; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street,
 * Boston, MA 02110-1301, USA.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <gnuradio/io_signature.h>
#include <gnuradio/digital/constellation.h>
#include <gnuradio/math.h>
#include <gnuradio/gr_complex.h>
#include <cstdlib>
#include <cfloat>
#include <stdexcept>
#include <boost/format.hpp>
#include <iostream>

namespace gr {
  namespace digital {

#define M_TWOPI (2*M_PI)
#define SQRT_TWO 0.707107

    // Base Constellation Class
    constellation::constellation(std::vector<gr_complex> constell,
                                 std::vector<int> pre_diff_code,
                                 unsigned int rotational_symmetry,
                                 unsigned int dimensionality
    ) :
      d_constellation(constell),
      d_pre_diff_code(pre_diff_code),
      d_rotational_symmetry(rotational_symmetry),
      d_dimensionality(dimensionality),
      d_re_min(1e20),
      d_re_max(1e20),
      d_im_min(1e20),
      d_im_max(1e20),
      d_lut_precision(0),
      d_lut_scale(0)
    {
      // Scale constellation points so that average magnitude is 1.
      float summed_mag = 0;
      unsigned int constsize = d_constellation.size();
      for (unsigned int i=0; i<constsize; i++) {
        gr_complex c = d_constellation[i];
        summed_mag += sqrt(c.real()*c.real() + c.imag()*c.imag());
      }
      d_scalefactor = constsize/summed_mag;
      for (unsigned int i=0; i<constsize; i++) {
        d_constellation[i] = d_constellation[i]*d_scalefactor;
      }
      if(pre_diff_code.size() == 0)
        d_apply_pre_diff_code = false;
      else if(pre_diff_code.size() != constsize)
        throw std::runtime_error(
          "The constellation and pre-diff code must be of the same length.");
      else
        d_apply_pre_diff_code = true;
      calc_arity();
    }

    constellation::constellation() :
      d_apply_pre_diff_code(false),
      d_rotational_symmetry(0),
      d_dimensionality(1),
      d_scalefactor(1.0),
      d_re_min(1e20),
      d_re_max(1e20),
      d_im_min(1e20),
      d_im_max(1e20),
      d_lut_precision(0.0),
      d_lut_scale(0.0)
    {
      calc_arity();
    }

    constellation::~constellation()
    {
    }

    //! Returns the constellation points for a symbol value
    void
    constellation::map_to_points(unsigned int value, gr_complex *points)
    {
      for(unsigned int i=0; i<d_dimensionality; i++)
        points[i] = d_constellation[value*d_dimensionality + i];
    }

    std::vector<gr_complex>
    constellation::map_to_points_v(unsigned int value)
    {
      std::vector<gr_complex> points_v;
      points_v.resize(d_dimensionality);
      map_to_points(value, &(points_v[0]));
      return points_v;
    }

    float
    constellation::get_distance(unsigned int index, const gr_complex *sample)
    {
      float dist = 0;
      for(unsigned int i=0; i<d_dimensionality; i++) {
        dist += norm(sample[i] - d_constellation[index*d_dimensionality + i]);
      }
      return dist;
    }

    unsigned int
    constellation::get_closest_point(const gr_complex *sample)
    {
      unsigned int min_index = 0;
      float min_euclid_dist;
      float euclid_dist;

      min_euclid_dist = get_distance(0, sample);
      min_index = 0;
      for(unsigned int j = 1; j < d_arity; j++){
        euclid_dist = get_distance(j, sample);
        if(euclid_dist < min_euclid_dist){
          min_euclid_dist = euclid_dist;
          min_index = j;
        }
      }
      return min_index;
    }

    unsigned int
    constellation::decision_maker_pe(const gr_complex *sample,
                                     float *phase_error)
    {
      unsigned int index = decision_maker(sample);
      *phase_error = 0;
      for(unsigned int d=0; d<d_dimensionality; d++)
        *phase_error += -arg(sample[d]*conj(d_constellation[index+d]));
      return index;
    }

    std::vector<gr_complex> constellation::s_points()
    {
      if(d_dimensionality != 1)
        throw std::runtime_error(
          "s_points only works for dimensionality 1 constellations.");
      else
        return d_constellation;
    }

    std::vector<std::vector<gr_complex> >
    constellation::v_points()
    {
      std::vector<std::vector<gr_complex> > vv_const;
      vv_const.resize(d_arity);
      for(unsigned int p=0; p<d_arity; p++) {
        std::vector<gr_complex> v_const;
        v_const.resize(d_dimensionality);
        for(unsigned int d=0; d<d_dimensionality; d++) {
          v_const[d] = d_constellation[p*d_dimensionality+d];
        }
        vv_const[p] = v_const;
      }
      return vv_const;
    }

    void
    constellation::calc_metric(const gr_complex *sample, float *metric,
                               trellis_metric_type_t type)
    {
      switch(type){
      case TRELLIS_EUCLIDEAN:
        calc_euclidean_metric(sample, metric);
        break;
      case TRELLIS_HARD_SYMBOL:
        calc_hard_symbol_metric(sample, metric);
        break;
      case TRELLIS_HARD_BIT:
        throw std::runtime_error("Invalid metric type (not yet implemented).");
        break;
      default:
        throw std::runtime_error("Invalid metric type.");
      }
    }

    void
    constellation::calc_euclidean_metric(const gr_complex *sample,
                                         float *metric)
    {
      for(unsigned int o=0; o<d_arity; o++) {
        metric[o] = get_distance(o, sample);
      }
    }

    void
    constellation::calc_hard_symbol_metric(const gr_complex *sample,
                                           float *metric)
    {
      float minm = FLT_MAX;
      unsigned int minmi = 0;
      for(unsigned int o=0; o<d_arity; o++) {
        float dist = get_distance(o, sample);
        if(dist < minm) {
          minm = dist;
          minmi = o;
        }
      }
      for(unsigned int o=0; o<d_arity; o++) {
        metric[o] = (o==minmi?0.0:1.0);
      }
    }

    void
    constellation::calc_arity()
    {
      if(d_constellation.size() % d_dimensionality != 0)
        throw std::runtime_error(
          "Constellation vector size must be a multiple of the dimensionality.");
      d_arity = d_constellation.size()/d_dimensionality;
    }

    unsigned int
    constellation::decision_maker_v(std::vector<gr_complex> sample)
    {
      assert(sample.size() == d_dimensionality);
      return decision_maker(&(sample[0]));
    }

    void
    constellation::gen_soft_dec_lut(int precision, float npwr)
    {
      d_soft_dec_lut.clear();
      d_lut_scale = powf(2.0f, static_cast<float>(precision));

      // We know we've normalized the constellation, so the min/max
      // dimensions in either direction are scaled to +/-1.
      float maxd = 1.0f;
      float step = (2.0f*maxd) / (d_lut_scale-1);
      float y = -maxd;
      while(y < maxd+step) {
        float x = -maxd;
        while(x < maxd+step) {
          gr_complex pt = gr_complex(x, y);
          d_soft_dec_lut.push_back(calc_soft_dec(pt, npwr));
          x += step;
        }
        y += step;
      }

      d_lut_precision = precision;
    }

    std::vector<float>
    constellation::calc_soft_dec(gr_complex sample, float npwr)
    {
      int v;
      int M = static_cast<int>(d_constellation.size());
      int k = static_cast<int>(log(static_cast<double>(M))/log(2.0));
      std::vector<float> tmp(2*k, 0);
      std::vector<float> s(k, 0);

      for(int i = 0; i < M; i++) {
        // Calculate the distance between the sample and the current
        // constellation point.
        float dist = std::abs(sample - d_constellation[i]);
        // Calculate the probability factor from the distance and
        // the scaled noise power.
        float d = expf(-dist/npwr);

        if(d_apply_pre_diff_code)
          v = d_pre_diff_code[i];
        else
          v = i;

        for(int j = 0; j < k; j++) {
          // Get the bit at the jth index
          int mask = 1 << j;
          int bit = (v & mask) >> j;

          // If the bit is a 0, add to the probability of a zero
          if(bit == 0)
            tmp[2*j+0] += d;
          // else, add to the probability of a one
          else
            tmp[2*j+1] += d;
        }
      }

      // Calculate the log-likelihood ratio for all bits based on the
      // probability of ones (tmp[2*i+1]) over the probability of a zero
      // (tmp[2*i+0]).
      for(int i = 0; i < k; i++) {
        s[k-1-i] = (logf(tmp[2*i+1]) - logf(tmp[2*i+0]));
      }

      return s;
    }

    void
    constellation::set_soft_dec_lut(const std::vector< std::vector<float> > &soft_dec_lut,
                                    int precision)
    {
      max_min_axes();

      d_soft_dec_lut = soft_dec_lut;
      d_lut_precision = precision;
      d_lut_scale = powf(2.0, static_cast<float>(precision));
    }

    bool
    constellation::has_soft_dec_lut()
    {
      return d_soft_dec_lut.size() > 0;
    }

    std::vector< std::vector<float> >
    constellation::soft_dec_lut()
    {
      return d_soft_dec_lut;
    }

    std::vector<float>
    constellation::soft_decision_maker(gr_complex sample)
    {
      if(has_soft_dec_lut()) {
        // Clip to just below 1 --> at 1, we can overflow the index
        // that will put us in the next row of the 2D LUT.
        float xre = branchless_clip(sample.real(), 0.99);
        float xim = branchless_clip(sample.imag(), 0.99);

        // We normalize the constellation in the ctor, so we know that
        // the maximum dimensions go from -1 to +1. We can infer the x
        // and y scale directly.
        float scale = d_lut_scale / (2.0f);

        // Convert the clipped x and y samples to nearest index offset
        xre = floorf((1.0f + xre) * scale);
        xim = floorf((1.0f + xim) * scale);
        int index = static_cast<int>(d_lut_scale*xim + xre);

        int max_index = d_lut_scale*d_lut_scale;

        // Make sure we are in bounds of the index
        while(index >= max_index) {
          index -= d_lut_scale;
        }
        while(index < 0) {
          index += d_lut_scale;
        }

        return d_soft_dec_lut[index];
      }
      else {
        return calc_soft_dec(sample);
      }
    }

    void
    constellation::max_min_axes()
    {
      // Find min/max of constellation for both real and imag axes.
      d_re_min = 1e20;
      d_im_min = 1e20;
      d_re_max = -1e20;
      d_im_max = -1e20;
      for(size_t i = 0; i < d_constellation.size(); i++) {
        if(d_constellation[i].real() > d_re_max)
          d_re_max = d_constellation[i].real();
        if(d_constellation[i].imag() > d_im_max)
          d_im_max = d_constellation[i].imag();

        if(d_constellation[i].real() < d_re_min)
          d_re_min = d_constellation[i].real();
        if(d_constellation[i].imag() < d_im_min)
          d_im_min = d_constellation[i].imag();
      }
      if(d_im_min == 0)
        d_im_min = d_re_min;
      if(d_im_max == 0)
        d_im_max = d_re_max;
      if(d_re_min == 0)
        d_re_min = d_im_min;
      if(d_re_max == 0)
        d_re_max = d_im_max;
    }

    /********************************************************************/

    constellation_calcdist::sptr
    constellation_calcdist::make(std::vector<gr_complex> constell,
                                 std::vector<int> pre_diff_code,
                                 unsigned int rotational_symmetry,
                                 unsigned int dimensionality)
    {
      return constellation_calcdist::sptr(
        new constellation_calcdist(constell, pre_diff_code,
                                   rotational_symmetry, dimensionality));
    }

    constellation_calcdist::constellation_calcdist(
      std::vector<gr_complex> constell,
      std::vector<int> pre_diff_code,
      unsigned int rotational_symmetry,
      unsigned int dimensionality)
      : constellation(constell, pre_diff_code, rotational_symmetry, dimensionality)
    {}

    // Chooses points base on shortest distance.
    // Inefficient.
    unsigned int
    constellation_calcdist::decision_maker(const gr_complex *sample)
    {
      return get_closest_point(sample);
    }

    /********************************************************************/

    constellation_sector::constellation_sector(std::vector<gr_complex> constell,
                                               std::vector<int> pre_diff_code,
                                               unsigned int rotational_symmetry,
                                               unsigned int dimensionality,
                                               unsigned int n_sectors) :
      constellation(constell, pre_diff_code, rotational_symmetry,
                    dimensionality),
      n_sectors(n_sectors)
    {
    }

    constellation_sector::~constellation_sector()
    {
    }

    unsigned int
    constellation_sector::decision_maker(const gr_complex *sample)
    {
      unsigned int sector;
      sector = get_sector(sample);
      return sector_values[sector];
    }

    void
    constellation_sector::find_sector_values()
    {
      unsigned int i;
      sector_values.clear();
      for(i=0; i<n_sectors; i++) {
        sector_values.push_back(calc_sector_value(i));
      }
    }

    /********************************************************************/

    constellation_rect::sptr
    constellation_rect::make(std::vector<gr_complex> constell,
                             std::vector<int> pre_diff_code,
                             unsigned int rotational_symmetry,
                             unsigned int real_sectors,
                             unsigned int imag_sectors,
                             float width_real_sectors,
                             float width_imag_sectors)
    {
      return constellation_rect::sptr(new constellation_rect
                                      (constell, pre_diff_code,
                                       rotational_symmetry,
                                       real_sectors, imag_sectors,
                                       width_real_sectors,
                                       width_imag_sectors));
    }

    constellation_rect::constellation_rect(
      std::vector<gr_complex> constell,
      std::vector<int> pre_diff_code,
      unsigned int rotational_symmetry,
      unsigned int real_sectors, unsigned int imag_sectors,
      float width_real_sectors, float width_imag_sectors) :
      constellation_sector(constell, pre_diff_code, rotational_symmetry,
                           1, real_sectors * imag_sectors),
      n_real_sectors(real_sectors), n_imag_sectors(imag_sectors),
      d_width_real_sectors(width_real_sectors),
      d_width_imag_sectors(width_imag_sectors)
    {
      d_width_real_sectors *= d_scalefactor;
      d_width_imag_sectors *= d_scalefactor;
      find_sector_values();
    }

    constellation_rect::~constellation_rect()
    {
    }

    unsigned int
    constellation_rect::get_sector(const gr_complex *sample)
    {
      int real_sector, imag_sector;
      unsigned int sector;

      real_sector = int(real(*sample)/d_width_real_sectors
                        + n_real_sectors/2.0);
      if(real_sector < 0)
        real_sector = 0;
      if(real_sector >= (int)n_real_sectors)
        real_sector = n_real_sectors-1;

      imag_sector = int(imag(*sample)/d_width_imag_sectors
                        + n_imag_sectors/2.0);
      if(imag_sector < 0)
        imag_sector = 0;
      if(imag_sector >= (int)n_imag_sectors)
        imag_sector = n_imag_sectors-1;

      sector = real_sector * n_imag_sectors + imag_sector;
      return sector;
    }

    gr_complex
    constellation_rect::calc_sector_center(unsigned int sector)
    {
      unsigned int real_sector, imag_sector;
      gr_complex sector_center;
      real_sector = float(sector)/n_imag_sectors;
      imag_sector = sector - real_sector * n_imag_sectors;
      sector_center = gr_complex(
        (real_sector + 0.5 - n_real_sectors/2.0) * d_width_real_sectors,
        (imag_sector + 0.5 - n_imag_sectors/2.0) * d_width_imag_sectors);
      return sector_center;
    }

    unsigned int
    constellation_rect::calc_sector_value(unsigned int sector)
    {
      gr_complex sector_center = calc_sector_center(sector);
      unsigned int closest_point;
      closest_point = get_closest_point(&sector_center);
      return closest_point;
    }

    /********************************************************************/

    constellation_expl_rect::sptr
    constellation_expl_rect::make(std::vector<gr_complex> constellation,
                                  std::vector<int> pre_diff_code,
                                  unsigned int rotational_symmetry,
                                  unsigned int real_sectors,
                                  unsigned int imag_sectors,
                                  float width_real_sectors,
                                  float width_imag_sectors,
                                  std::vector<unsigned int> sector_values)
    {
      return constellation_expl_rect::sptr
        (new constellation_expl_rect(constellation, pre_diff_code,
                                     rotational_symmetry,
                                     real_sectors, imag_sectors,
                                     width_real_sectors, width_imag_sectors,
                                     sector_values));
    }

    constellation_expl_rect::constellation_expl_rect(
      std::vector<gr_complex> constellation,
      std::vector<int> pre_diff_code,
      unsigned int rotational_symmetry,
      unsigned int real_sectors,
      unsigned int imag_sectors,
      float width_real_sectors,
      float width_imag_sectors,
      std::vector<unsigned int> sector_values)
      : constellation_rect(constellation, pre_diff_code, rotational_symmetry,
                           real_sectors, imag_sectors, width_real_sectors, width_imag_sectors),
        d_sector_values(sector_values)
    {
    }

    constellation_expl_rect::~constellation_expl_rect()
    {
    }

    /********************************************************************/

    constellation_psk::sptr
    constellation_psk::make(std::vector<gr_complex> constell,
                            std::vector<int> pre_diff_code,
                            unsigned int n_sectors)
    {
      return constellation_psk::sptr(new constellation_psk
                                     (constell, pre_diff_code,
                                      n_sectors));
    }

    constellation_psk::constellation_psk(std::vector<gr_complex> constell,
                                         std::vector<int> pre_diff_code,
                                         unsigned int n_sectors) :
      constellation_sector(constell, pre_diff_code, constell.size(),
                           1, n_sectors)
    {
      find_sector_values();
    }

    constellation_psk::~constellation_psk()
    {
    }

    unsigned int
    constellation_psk::get_sector(const gr_complex *sample)
    {
      float phase = arg(*sample);
      float width = M_TWOPI / n_sectors;
      int sector = floor(phase/width + 0.5);
      if(sector < 0)
        sector += n_sectors;
      return sector;
    }

    unsigned int
    constellation_psk::calc_sector_value(unsigned int sector)
    {
      float phase = sector * M_TWOPI / n_sectors;
      gr_complex sector_center = gr_complex(cos(phase), sin(phase));
      unsigned int closest_point = get_closest_point(&sector_center);
      return closest_point;
    }

    /********************************************************************/

    constellation_bpsk::sptr
    constellation_bpsk::make()
    {
      return constellation_bpsk::sptr(new constellation_bpsk());
    }

    constellation_bpsk::constellation_bpsk()
    {
      d_constellation.resize(2);
      d_constellation[0] = gr_complex(-1, 0);
      d_constellation[1] = gr_complex(1, 0);
      d_rotational_symmetry = 2;
      d_dimensionality = 1;
      calc_arity();
    }

    constellation_bpsk::~constellation_bpsk()
    {
    }

    unsigned int
    constellation_bpsk::decision_maker(const gr_complex *sample)
    {
      return (real(*sample) > 0);
    }

    /********************************************************************/

    constellation_qpsk::sptr
    constellation_qpsk::make()
    {
      return constellation_qpsk::sptr(new constellation_qpsk());
    }

    constellation_qpsk::constellation_qpsk()
    {
      d_constellation.resize(4);
      // Gray-coded
      d_constellation[0] = gr_complex(-SQRT_TWO, -SQRT_TWO);
      d_constellation[1] = gr_complex(SQRT_TWO, -SQRT_TWO);
      d_constellation[2] = gr_complex(-SQRT_TWO, SQRT_TWO);
      d_constellation[3] = gr_complex(SQRT_TWO, SQRT_TWO);

      /*
        d_constellation[0] = gr_complex(SQRT_TWO, SQRT_TWO);
        d_constellation[1] = gr_complex(-SQRT_TWO, SQRT_TWO);
        d_constellation[2] = gr_complex(SQRT_TWO, -SQRT_TWO);
        d_constellation[3] = gr_complex(SQRT_TWO, -SQRT_TWO);
      */

      d_pre_diff_code.resize(4);
      d_pre_diff_code[0] = 0x0;
      d_pre_diff_code[1] = 0x2;
      d_pre_diff_code[2] = 0x3;
      d_pre_diff_code[3] = 0x1;

      d_rotational_symmetry = 4;
      d_dimensionality = 1;
      calc_arity();
    }

    constellation_qpsk::~constellation_qpsk()
    {
    }

    unsigned int
    constellation_qpsk::decision_maker(const gr_complex *sample)
    {
      // Real component determines small bit.
      // Imag component determines big bit.
      return 2*(imag(*sample)>0) + (real(*sample)>0);

      /*
        bool a = real(*sample) > 0;
        bool b = imag(*sample) > 0;
        if(a) {
        if(b)
        return 0x0;
        else
        return 0x1;
        }
        else {
        if(b)
        return 0x2;
        else
        return 0x3;
        }
      */
    }

    /********************************************************************/

    constellation_dqpsk::sptr
    constellation_dqpsk::make()
    {
      return constellation_dqpsk::sptr(new constellation_dqpsk());
    }

    constellation_dqpsk::constellation_dqpsk()
    {
      // This constellation is not gray coded, which allows
      // us to use differential encodings (through diff_encode and
      // diff_decode) on the symbols.
      d_constellation.resize(4);
      d_constellation[0] = gr_complex(+SQRT_TWO, +SQRT_TWO);
      d_constellation[1] = gr_complex(-SQRT_TWO, +SQRT_TWO);
      d_constellation[2] = gr_complex(-SQRT_TWO, -SQRT_TWO);
      d_constellation[3] = gr_complex(+SQRT_TWO, -SQRT_TWO);

      // Use this mapping to convert to gray code before diff enc.
      d_pre_diff_code.resize(4);
      d_pre_diff_code[0] = 0x0;
      d_pre_diff_code[1] = 0x1;
      d_pre_diff_code[2] = 0x3;
      d_pre_diff_code[3] = 0x2;
      d_apply_pre_diff_code = true;

      d_rotational_symmetry = 4;
      d_dimensionality = 1;
      calc_arity();
    }

    constellation_dqpsk::~constellation_dqpsk()
    {
    }

    unsigned int
    constellation_dqpsk::decision_maker(const gr_complex *sample)
    {
      // Slower deicison maker as we can't slice along one axis.
      // Maybe there's a better way to do this, still.

      bool a = real(*sample) > 0;
      bool b = imag(*sample) > 0;
      if(a) {
        if(b)
          return 0x0;
        else
          return 0x3;
      }
      else {
        if(b)
          return 0x1;
        else
          return 0x2;
      }
    }

    /********************************************************************/

    constellation_8psk::sptr
    constellation_8psk::make()
    {
      return constellation_8psk::sptr(new constellation_8psk());
    }

    constellation_8psk::constellation_8psk()
    {
      float angle = M_PI/8.0;
      d_constellation.resize(8);
      // Gray-coded
      d_constellation[0] = gr_complex(cos( 1*angle), sin( 1*angle));
      d_constellation[1] = gr_complex(cos( 7*angle), sin( 7*angle));
      d_constellation[2] = gr_complex(cos(15*angle), sin(15*angle));
      d_constellation[3] = gr_complex(cos( 9*angle), sin( 9*angle));
      d_constellation[4] = gr_complex(cos( 3*angle), sin( 3*angle));
      d_constellation[5] = gr_complex(cos( 5*angle), sin( 5*angle));
      d_constellation[6] = gr_complex(cos(13*angle), sin(13*angle));
      d_constellation[7] = gr_complex(cos(11*angle), sin(11*angle));
      d_rotational_symmetry = 8;
      d_dimensionality = 1;
      calc_arity();
    }

    constellation_8psk::~constellation_8psk()
    {
    }

    unsigned int
    constellation_8psk::decision_maker(const gr_complex *sample)
    {
      unsigned int ret = 0;

      float re = sample->real();
      float im = sample->imag();

      if(fabsf(re) <= fabsf(im))
        ret  = 4;
      if(re <= 0)
        ret |= 1;
      if(im <= 0)
        ret |= 2;

      return ret;
    }

    /********************************************************************/

    constellation_8psk_natural::sptr
    constellation_8psk_natural::make()
    {
      return constellation_8psk_natural::sptr(new constellation_8psk_natural());
    }

    constellation_8psk_natural::constellation_8psk_natural()
    {
      float angle = M_PI/8.0;
      d_constellation.resize(8);
      // Natural-mapping
      d_constellation[0] = gr_complex(cos( 15*angle), sin( 15*angle));
      d_constellation[1] = gr_complex(cos( 1*angle), sin( 1*angle));
      d_constellation[2] = gr_complex(cos(3*angle), sin(3*angle));
      d_constellation[3] = gr_complex(cos( 5*angle), sin( 5*angle));
      d_constellation[4] = gr_complex(cos( 7*angle), sin( 7*angle));
      d_constellation[5] = gr_complex(cos( 9*angle), sin( 9*angle));
      d_constellation[6] = gr_complex(cos(11*angle), sin(11*angle));
      d_constellation[7] = gr_complex(cos(13*angle), sin(13*angle));
      d_rotational_symmetry = 8;
      d_dimensionality = 1;
      calc_arity();
    }

    constellation_8psk_natural::~constellation_8psk_natural()
    {
    }

    unsigned int
    constellation_8psk_natural::decision_maker(const gr_complex *sample)
    {
      unsigned int ret = 0;

      float re = sample->real();
      float im = sample->imag();

      if((re+im) < 0)
        ret  = 4;
      if(fabsf(im) > fabsf(re)){
        ret |= 2;
	if(re*im < 0)
          ret |= 1;
	}
      if(fabsf(im) < fabsf(re) && re*im > 0)
        ret |= 1;

      return ret;
    }

    /********************************************************************/

    constellation_16qam::sptr
    constellation_16qam::make()
    {
      return constellation_16qam::sptr(new constellation_16qam());
    }

    constellation_16qam::constellation_16qam()
    {
      const float level = sqrt(float(0.1));
      d_constellation.resize(16);
      // The mapping used in 16qam set partition
      d_constellation[0] = gr_complex(1*level,-1*level);
      d_constellation[1] = gr_complex(-1*level,-1*level);
      d_constellation[2] = gr_complex(3*level,-3*level);
      d_constellation[3] = gr_complex(-3*level,-3*level);
      d_constellation[4] = gr_complex(-3*level,-1*level);
      d_constellation[5] = gr_complex(3*level,-1*level);
      d_constellation[6] = gr_complex(-1*level,-3*level);
      d_constellation[7] = gr_complex(1*level,-3*level);
      d_constellation[8] = gr_complex(-3*level,3*level);
      d_constellation[9] = gr_complex(3*level,3*level);
      d_constellation[10] = gr_complex(-1*level,1*level);
      d_constellation[11] = gr_complex(1*level,1*level);
      d_constellation[12] = gr_complex(1*level,3*level);
      d_constellation[13] = gr_complex(-1*level,3*level);
      d_constellation[14] = gr_complex(3*level,1*level);
      d_constellation[15] = gr_complex(-3*level,1*level);
      d_rotational_symmetry = 4;
      d_dimensionality = 1;
      calc_arity();
    }

    constellation_16qam::~constellation_16qam()
    {
    }

    unsigned int
    constellation_16qam::decision_maker(const gr_complex *sample)
    {
      unsigned int ret = 0;
      const float level = sqrt(float(0.1));
      float re = sample->real();
      float im = sample->imag();

      if(im <= 0 && im >= -2*level && re >= 0 && re <= 2*level)
	ret = 0;
      else if(im <= 0 && im >= -2*level && re <= 0 && re >= -2*level)
	ret = 1;
      else if(im <= -2*level && re >= 2*level)
	ret = 2;
      else if(im <= -2*level && re <= -2*level)
	ret = 3;
      else if(im <= 0 && im >= -2*level && re <= -2*level)
	ret = 4;
      else if(im <= 0 && im >= -2*level && re >= 2*level)
	ret = 5;
      else if(im <= -2*level && re <= 0 && re >= -2*level)
	ret = 6;
      else if(im <= -2*level && re >= 0 && re <= 2*level)
	ret = 7;
      else if(im >= 2*level && re <= -2*level)
	ret = 8;
      else if(im >= 2*level && re >= 2*level)
	ret = 9;
      else if(im >= 0 && im <= 2*level && re <= 0 && re >= -2*level)
	ret = 10;
      else if(im >= 0 && im <= 2*level && re >= 0 && re <= 2*level)
	ret = 11;
      else if(im >= 2*level && re >= 0 && re <= 2*level)
	ret = 12;
      else if(im >= 2*level && re <= 0 && re >= -2*level)
	ret = 13;
      else if(im >= 0 && im <= 2*level && re >= 2*level)
	ret = 14;
      else if(im >= 0 && im <= 2*level && re <= -2*level)
	ret = 15;

      return ret;
    }

  } /* namespace digital */
} /* namespace gr */


///

#include <iostream>
#include <algorithm>
#include <vector>
#include <unordered_map>
#include <map>
#include <iterator>
// BGL includes
// #include <boost/graph/adjacency_list.hpp>

// // BGL graph definitions
// // =====================
// // Graph Type with nested interior edge properties for Flow Algorithms
// // =====================
// // Graph Type, OutEdgeList Type, VertexList Type, (un)directedS
// typedef boost::adjacency_list<boost::vecS, boost::vecS, boost::directedS     // Use vecS for the VertexList! Choosing setS for the OutEdgeList disallows parallel edges.
//     >          graph;
// typedef boost::graph_traits<graph>::vertex_descriptor    vertex_desc;    // Vertex Descriptor: with vecS vertex list, this is really just an int in the range [0, num_vertices(G)).
// typedef boost::graph_traits<graph>::edge_iterator    edge_it;    // to iterate over all edges
// typedef  boost::graph_traits<graph>::out_edge_iterator      out_edge_it;
// // Custom Edge Adder Class, that holds the references
// // to the graph, capacity map and reverse edge map
// // ===================================================

std::vector<int> age;
std::vector<int> solution;
std::vector<std::pair<int,int>> curr_path;

struct second_greater {
  bool operator() (const std::pair<int, int>& t0, const std::pair<int,int>& t1) {
    return t0.second > t1.second;
  }
};

void solve_for(int idx, std::vector<std::vector<int>> &children,
  std::vector<std::vector<std::pair<int,int>>> &queries) {

  curr_path.push_back({idx, age[idx]});
  std::vector<std::pair<int,int>> queries_for_idx = queries[idx];
  for(auto p : queries_for_idx) {
    int sol_idx = p.first;
    int b = p.second;
    auto j = std::lower_bound(curr_path.begin(), curr_path.end(), std::make_pair(0, b), second_greater());
    solution[sol_idx] = j->first;
  }
  for(const int i : children[idx]) {
    solve_for(i, children, queries);
  }

  curr_path.pop_back();

}

// Main
void testcase() {
  // build graph
  int n, q;
  std::cin >> n >> q;

  std::unordered_map<std::string, int> name_to_ind;
  name_to_ind.clear();

  std::vector<std::vector<int>> children(n);

  age.clear();
  age.reserve(n);

  solution.clear();
  solution = std::vector<int>(q);

  curr_path.clear();

  std::vector<std::string> ind_to_name;
  ind_to_name.reserve(n);

  int root = -1;
  int max_age =  std::numeric_limits<int>::min();

  for(int i = 0; i < n; i++) {
    std::string s; int a;
    std::cin >> s; std::cin >> a;
    name_to_ind.insert({s, i});
    ind_to_name.push_back(s);
    age.push_back(a);
    if(a > max_age) {
      root = i; max_age = a;
    }
  }

  for(int i = 0; i < n - 1; i++) {
    std::string s, p;
    std::cin >> s; std::cin >> p;
    children[name_to_ind.at(p)].push_back(name_to_ind.at(s));
  }

  std::vector<std::vector<std::pair<int,int>>> queries(n);

  for(int k = 0; k < q; k++) {
    std::string s; int b;
    std::cin >> s; std::cin >> b;
    int idx = name_to_ind.at(s);
    queries[idx].push_back({k, b});
  }

  solve_for(root, children, queries);

  for(int k = 0; k < q; k++) {
    std::cout << ind_to_name[solution[k]] << " ";
  }
  std::cout << std::endl;
}

int main() {
  std::ios_base::sync_with_stdio(false);
  std::size_t t;
  for (std::cin >> t; t > 0; --t) testcase();
  return 0;
}


// Boost.Maral library (Molecular Archiving, Retrieval & Algorithm Library)
//
// Copyright (C) 2014 Armin Madadkar Sobhani
//
// Distributed under the Boost Software License Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//
// $Id$

#ifndef MARAL_UNITS_HPP
#define MARAL_UNITS_HPP

#include <boost/units/systems/si/plane_angle.hpp>
#include <boost/units/systems/angle/degrees.hpp>
#include <boost/units/systems/angle/gradians.hpp>
#include <boost/units/systems/si/length.hpp>
#include <boost/units/base_units/metric/angstrom.hpp>
#include <boost/units/systems/si/io.hpp>

namespace boost { namespace units
{
    typedef metric::angstrom_base_unit::unit_type angstrom_unit;
    BOOST_UNITS_STATIC_CONSTANT(angstrom, angstrom_unit);
    BOOST_UNITS_STATIC_CONSTANT(angstroms, angstrom_unit);

    typedef scaled_base_unit<si::meter_base_unit,
            scale<10, static_rational<-9> > > nanometer_base_unit;
    typedef nanometer_base_unit::unit_type nanometer_unit;
    BOOST_UNITS_STATIC_CONSTANT(nanometer, nanometer_unit);
    BOOST_UNITS_STATIC_CONSTANT(nanometers, nanometer_unit);

}}  // namespace boost::units

namespace maral {

////////////////////////////////////////////////////////////////////////////////
/// \brief Namespace for units, quantities and unit system support.
///
/// Namespace for supporting units, quantities, unit system and dimensional
/// analysis. It is based on Boost.Units. Some terminology from Boost.Units:
///
/// \li \b Unit: A set of base units raised to rational exponents, e.g. m^1,
/// kg^1, m^1/s^2.
/// \li \b Quantity: A quantity represents a concrete amount of a unit. Thus,
/// while the meter is the base unit of length in the SI system, 5.5 meters is a
/// quantity of length in that system.
/// \li \b System: A unit system is a collection of base units representing all
/// the measurable entities of interest for a specific problem.
///
/// Maral uses SI unit system by default.

namespace units {

////////////////////////////////////////////////////////////////////////////////
// Units for Angle

////////////////////////////////////////////////////////////////////////////////
/// \brief Specialization of boost::units::quantity for storing angles in
/// radians.
///
/// \param Type Value type of the angle (e.g. int, float, double, ...).
/// \remarks
/// \par
/// A partial specialization of boost::units::quantity for storing angle values
/// in radians.
/// \see radians, to_radians

template <typename Type>
using angle_in_radians =
    boost::units::quantity<boost::units::si::plane_angle, Type>;

////////////////////////////////////////////////////////////////////////////////
/// \brief Specialization of boost::units::quantity for storing angles in
/// degrees.
///
/// \param Type Value type of the angle (e.g. int, float, double, ...).
/// \remarks
/// \par
/// A partial specialization of boost::units::quantity for storing angle values
/// in degrees.
/// \see degrees, to_degrees

template <typename Type>
using angle_in_degrees =
    boost::units::quantity<boost::units::degree::plane_angle, Type>;

////////////////////////////////////////////////////////////////////////////////
/// \brief Specialization of boost::units::quantity for storing angles in
/// gradians.
///
/// \param Type Value type of the angle (e.g. int, float, double, ...).
/// \remarks
/// \par
/// A partial specialization of boost::units::quantity for storing angle values
/// in gradians.
/// \see degrees, to_degrees

template <typename Type>
using angle_in_gradians =
    boost::units::quantity<boost::units::gradian::plane_angle, Type>;

////////////////////////////////////////////////////////////////////////////////
/// \return Created angle in raidans as a boost::units::quantity object.
/// \param rad Angle value in radians.
/// \remarks
/// Type-safe factory method for creating an angle in radians from a value
/// specified.
/// \see to_radians

template <typename Type>
inline
angle_in_radians<Type> radians(Type rad)
{
    return angle_in_radians<Type>(rad * boost::units::si::radians);
}

////////////////////////////////////////////////////////////////////////////////
/// \return Created angle in degrees as a boost::units::quantity object.
/// \param deg Angle value in degrees.
/// \remarks
/// Type-safe factory method for creating an angle in degrees from a value
/// specified.
/// \see to_degrees

template <typename Type>
inline
angle_in_degrees<Type> degrees(Type deg)
{
    return angle_in_degrees<Type>(deg * boost::units::degree::degrees);
}

////////////////////////////////////////////////////////////////////////////////
/// \return Created angle in gradians as a boost::units::quantity object.
/// \param grd Angle value in gradians.
/// \remarks
/// Type-safe factory method for creating an angle in gradians from a value
/// specified.
/// \see to_gradians

template <typename Type>
inline
angle_in_gradians<Type> gradians(Type grd)
{
    return angle_in_degrees<Type>(grd * boost::units::gradian::gradians);
}

////////////////////////////////////////////////////////////////////////////////
/// \return Angle in radians.
/// \param ang Reference to an angle quantity object.
/// \remarks
/// Conversion method for getting angle quantity in radians.
/// \see radians

template <typename Unit, typename Type>
inline
angle_in_radians<Type> to_radians(
    const boost::units::quantity<Unit, Type>& ang)
{
    return static_cast< angle_in_radians<Type> > (ang);
}

////////////////////////////////////////////////////////////////////////////////
/// \return Angle in degrees.
/// \param ang Reference to an angle quantity object.
/// \remarks
/// Conversion method for getting angle quantity in degrees.
/// \see degrees

template <typename Unit, typename Type>
inline
angle_in_degrees<Type> to_degrees(
    const boost::units::quantity<Unit, Type>& ang)
{
    return static_cast< angle_in_degrees<Type> > (ang);
}

////////////////////////////////////////////////////////////////////////////////
/// \return Angle in gradians.
/// \param ang Reference to an angle quantity object.
/// \remarks
/// Conversion method for getting angle quantity in gradians.
/// \see gradians

template <typename Unit, typename Type>
inline
angle_in_gradians<Type> to_gradians(
    const boost::units::quantity<Unit, Type>& ang)
{
    return static_cast< angle_in_gradians<Type> > (ang);
}

////////////////////////////////////////////////////////////////////////////////
// Units for Length

////////////////////////////////////////////////////////////////////////////////
/// \brief Specialization of boost::units::quantity for storing length in
/// Angstroms.
///
/// \param Type Value type of the length (e.g. int, float, double, ...).
/// \remarks
/// \par
/// An alias of boost::units::quantity for storing length values in Angstroms.
/// \see angstroms, to_angstroms

template <typename Type>
using length_in_angstroms =
    boost::units::quantity<boost::units::angstrom_unit, Type>;

////////////////////////////////////////////////////////////////////////////////
/// \brief Specialization of boost::units::quantity for storing length in
/// nanometers.
///
/// \param Type Value type of the length (e.g. int, float, double, ...).
/// \remarks
/// \par
/// An alias of boost::units::quantity for storing length values in nanometers.
/// \see nanometers, to_nanometers

template <typename Type>
using length_in_nanometers =
    boost::units::quantity<boost::units::nanometer_unit, Type>;

////////////////////////////////////////////////////////////////////////////////
/// \brief Specialization of boost::units::quantity for storing angles in
/// gradians.
///
/// \param Type Value type of the angle (e.g. int, float, double, ...).
/// \remarks
/// \par
/// A partial specialization of boost::units::quantity for storing angle values
/// in gradians.
/// \see degrees, to_degrees

//template <typename Type>
//using angle_in_gradians =
//    boost::units::quantity<boost::units::gradian::plane_angle, Type>;

////////////////////////////////////////////////////////////////////////////////
/// \return Created length in Angstroms as a boost::units::quantity object.
/// \param ang Length value in Angstroms.
/// \remarks
/// Type-safe factory method for creating a length in Angstroms from a value
/// specified.
/// \see to_angstroms

template <typename Type>
inline
length_in_angstroms<Type> angstroms(Type ang)
{
    return length_in_angstroms<Type>(ang * boost::units::angstroms);
}

////////////////////////////////////////////////////////////////////////////////
/// \return Created length in nanometers as a boost::units::quantity object.
/// \param len Length value in nanometers.
/// \remarks
/// Type-safe factory method for creating a length in nanometers from a value
/// specified.
/// \see to_nanometers

template <typename Type>
inline
length_in_nanometers<Type> nanometers(Type len)
{
    return length_in_nanometers<Type>(len * boost::units::nanometers);
}

////////////////////////////////////////////////////////////////////////////////
/// \return Created angle in gradians as a boost::units::quantity object.
/// \param grd Angle value in gradians.
/// \remarks
/// Type-safe factory method for creating an angle in gradians from a value
/// specified.
/// \see to_gradians

//template <typename Type>
//inline
//angle_in_gradians<Type> gradians(Type grd)
//{
//    return angle_in_degrees<Type>(grd * boost::units::gradian::gradians);
//}

////////////////////////////////////////////////////////////////////////////////
/// \return Length in Angstroms.
/// \param len Reference to a length quantity object.
/// \remarks
/// Conversion method for getting length quantity in Angstroms.
/// \see angstroms

template <typename Unit, typename Type>
inline
length_in_angstroms<Type> to_angstroms(
    const boost::units::quantity<Unit, Type>& len)
{
    return static_cast< length_in_angstroms<Type> > (len);
}

////////////////////////////////////////////////////////////////////////////////
/// \return Length in nanometers.
/// \param len Reference to a length value.
/// \remarks
/// Conversion method for getting a length quantity in nanometers.
/// \see nanometers

template <typename Unit, typename Type>
inline
length_in_nanometers<Type> to_nanometers(
    const boost::units::quantity<Unit, Type>& len)
{
    return static_cast< length_in_nanometers<Type> > (len);
}

////////////////////////////////////////////////////////////////////////////////
/// \return Angle in gradians.
/// \param ang Reference to an angle quantity object.
/// \remarks
/// Conversion method for getting angle quantity in gradians.
/// \see gradians

//template <typename Unit, typename Type>
//inline
//angle_in_gradians<Type> to_gradians(
//    const boost::units::quantity<Unit, Type>& ang)
//{
//    return static_cast< angle_in_gradians<Type> > (ang);
//}

}}    // namespace maral::units

#endif    // MARAL_UNITS_HPP


/* CirKit: A circuit toolkit
 * Copyright (C) 2009-2015  University of Bremen
 * Copyright (C) 2015-2017  EPFL
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

#include "reed_muller_synthesis.hpp"

#include <math.h>

#include <boost/assign/std/vector.hpp>
#include <boost/dynamic_bitset.hpp>
#include <boost/range/adaptor/reversed.hpp>
#include <boost/range/adaptor/transformed.hpp>
#include <boost/range/algorithm/find_if.hpp>
#include <boost/range/algorithm/for_each.hpp>
#include <boost/range/irange.hpp>
#include <boost/range/iterator_range.hpp>
#include <boost/variant.hpp>

#include <core/functor.hpp>
#include <core/utils/timer.hpp>

#include <reversible/functions/add_gates.hpp>
#include <reversible/functions/clear_circuit.hpp>
#include <reversible/functions/copy_metadata.hpp>
#include <reversible/functions/fully_specified.hpp>
#include <reversible/io/print_circuit.hpp>

#include "synthesis_utils_p.hpp"

using namespace boost::assign;

namespace cirkit
{

  using spectra_t = std::vector<boost::dynamic_bitset<>>;

  struct to_value
  {
    using result_type = bool;

    bool operator()( const boost::optional<bool>& b ) const
    {
      return *b;
    }
  };

  template<typename CubeIterator>
  unsigned long cube_to_value( CubeIterator first, CubeIterator second )
  {
    boost::dynamic_bitset<> input;

    using boost::adaptors::transformed;
    boost::for_each( boost::make_iterator_range( first, second ) | transformed( to_value() ), [&input]( bool b ) { input.push_back( b ); } );
    return input.to_ulong();
  }

  void apply_cnot( spectra_t& f, unsigned c, unsigned t )
  {
    for ( auto& row : f )
    {
      row[t] = row[t] ^ row[c];
    }
  }

  boost::dynamic_bitset<> multiply_columns( const spectra_t& f, const std::vector<unsigned>& columns )
  {
    boost::dynamic_bitset<> m( f.size() );

    // Initial
    for ( unsigned r = 0u; r < f.size(); ++r )
    {
      m.set( r, f[r].test( columns.at( 0u ) ) );
    }

    for ( unsigned i = 1u; i < columns.size(); ++i )
    {
      boost::dynamic_bitset<> mnew( f.size(), 0 );

      for ( unsigned r = 0u; r < f.size(); ++r )
      {
        if ( m.test( r ) )
        {
          for ( unsigned r2 = 0u; r2 < f.size(); ++r2 )
          {
            if ( f[r2].test( columns.at( i ) ) )
            {
              mnew.flip( r | r2 );
            }
          }
        }
      }

      m = mnew;
    }

    return m;
  }

  void apply_toffoli( spectra_t& f, const std::vector<unsigned>& controls, unsigned t )
  {
    boost::dynamic_bitset<> c = multiply_columns( f, controls );

    for ( unsigned r = 0u; r < f.size(); ++r )
    {
      f[r][t] = f[r][t] ^ c[r];
    }
  }

  void apply_toffoli_front( spectra_t& f, const std::vector<unsigned>& controls, unsigned t )
  {
    if ( f.empty() ) return;

    // Control Mask
    unsigned cmask = 0u;
    for ( unsigned c : controls )
    {
      cmask |= 1u << c;
    }

    // Target Mask
    unsigned tmask = 1u << t;

    // for each column
    for ( unsigned j : boost::irange( 0u, (unsigned)f[0u].size() ) )
    {
      // for each row
      for ( unsigned r : boost::irange( 0u, (unsigned)f.size() ) )
      {
        // match?
        if ( ( r & tmask ) && f[r].test( j ) )
        {
          // Clear the bit
          unsigned mask = r & ~tmask;
          f[mask | cmask].flip( j );
        }
      }
    }
  }

  void apply_gate( circuit& circ, const std::vector<spectra_t*>& funcs, unsigned offset, unsigned& insert_at, const boost::variant<std::vector<unsigned>, unsigned>& controls, unsigned t )
  {
    std::vector<unsigned> _controls;

    if ( const std::vector<unsigned>* op = boost::get<std::vector<unsigned> >( &controls ) )
    {
      _controls.assign( op->begin(), op->end() );
    }
    else if ( const unsigned* op = boost::get<unsigned>( &controls ) )
    {
      _controls += *op;
    }

    switch ( _controls.size() )
    {
    case 0u:
      insert_not( circ, insert_at, t );
      insert_at += offset;
      funcs[offset]->at( 0u ).reset( t );
      apply_toffoli_front( *funcs[1u - offset], _controls, t );
      break;

    case 1u:
      insert_cnot( circ, insert_at, _controls.at( 0u ), t );
      insert_at += offset;
      apply_cnot( *funcs[offset], _controls.at( 0u ), t );
      apply_toffoli_front( *funcs[1u - offset], _controls, t );
      break;

    default:
      insert_toffoli( circ, insert_at, _controls, t );
      insert_at += offset;
      apply_toffoli( *funcs[offset], _controls, t );
      apply_toffoli_front( *funcs[1u - offset], _controls, t );
      break;
    }
  }

  void print_spectra( const spectra_t& f )
  {
    for ( unsigned i = 0u; i < f.size(); ++i )
    {
      std::cout << f[i] << std::endl;
    }
  }

  bool reed_muller_synthesis( circuit& circ, const binary_truth_table& spec, properties::ptr settings, properties::ptr statistics )
  {

    // Settings parsing
    const auto bidirectional = get( settings, "bidirectional", true );

    // Run-time measuring
    properties_timer t( statistics );

    // circuit has to be empty
    clear_circuit( circ );

    // truth table has to be fully specified
    if ( !fully_specified( spec ) )
    {
      set_error_message( statistics, "truth table `spec` is not fully specified." );
      return false;
    }

    // Determine Function Vectors from Specification
    unsigned n = spec.num_outputs();
    spectra_t func( 1u << n, boost::dynamic_bitset<>( n ) );
    spectra_t ifunc( 1u << n, boost::dynamic_bitset<>( n ) );

    for ( binary_truth_table::const_iterator it = spec.begin(); it != spec.end(); ++it )
    {
      unsigned long ipos = cube_to_value( it->first.first, it->first.second );
      binary_truth_table::cube_type output( it->second.first, it->second.second );

      for ( unsigned i = 0u; i < n; ++i )
      {
        func[ipos].set( i, *output.at( i ) );
      }

      if ( bidirectional )
      {
        unsigned long opos = cube_to_value( it->second.first, it->second.second );
        binary_truth_table::cube_type input( it->first.first, it->first.second );
        for ( unsigned i = 0u; i < n; ++i )
        {
          ifunc[opos].set( i, *input.at( i ) );
        }
      }
    }

    // Determine Reed Muller Spectra fom Function Vectors
    {
      unsigned i, j, k, m, p;
      for ( m = 1u; m < ( 1u << n ); m = 2 * m )
      {
        for ( i = 0u; i < ( 1u << n ); i = i + 2 * m )
        {
          for ( j = i, p = k = i + m; j < p; j = j + 1, k = k + 1 )
          {
            func[k] = func[k] ^ func[j];
            if ( bidirectional )
            {
              ifunc[k] = ifunc[k] ^ ifunc[j];
            }
          }
        }
      }
    }

    // Synthesis
    // copy metadata
    circ.set_lines( n );
    copy_metadata( spec, circ );

    std::vector<spectra_t*> funcs;
    funcs += &func,&ifunc;
    unsigned insert_at = 0u;

    // Step A (i = 0)
    for ( unsigned j = 0u; j < n; ++j )
    {
      unsigned offset = bidirectional && ( ifunc[0u].count() < func[0u].count() ) ? 1u : 0u;

      if ( funcs[offset]->at( 0u ).test( j ) )
      {
        apply_gate( circ, funcs, offset, insert_at, std::vector<unsigned>(), j );
      }
    }

    for ( unsigned i = 1u; i < ( 1u << n ) - 1; ++i )
    {
      // Step B (i = 2^(k-1), variable rows)
      if ( ( log( i ) / log( 2.0 ) ) == ceil( ( log( i ) / log( 2.0 ) ) ) )
      {
        unsigned k = (unsigned)ceil( ( log( i ) / log( 2.0 ) ) );
        unsigned offset = bidirectional && ( hamming_distance( i, ifunc[0u].to_ulong() ) < hamming_distance( i, func[0u].to_ulong() ) ) ? 1u : 0u;
        const boost::dynamic_bitset<>& func_offset = funcs[offset]->at( i );
        if ( !func_offset.test( k ) )
        {
          using boost::adaptors::reversed;

          unsigned s = *boost::find_if( boost::irange( 0u, n ) | reversed, [&func_offset]( unsigned j ) { return func_offset.test( j ); } );
          apply_gate( circ, funcs, offset, insert_at, s, k );
        }

        for ( unsigned j = 0u; j < n; ++j )
        {
          if ( j != k && func_offset.test( j ) )
          {
            apply_gate( circ, funcs, offset, insert_at, k, j );
          }
        }
      }
      // Step C (i != 2^(k-1), non-variable rows)
      else
      {
        unsigned offset = bidirectional && ( hamming_distance( i, ifunc[0u].to_ulong() ) < hamming_distance( i, func[0u].to_ulong() ) ) ? 1u : 0u;

        // already empty?
        if ( funcs[offset]->at( i ).none() ) continue;

        // Find s?
        using boost::adaptors::reversed;
        unsigned s = n;
        for ( unsigned j : boost::irange( 0u, n ) | reversed )
        {
          if ( funcs[offset]->at( i ).test( j ) && ( i & ( 1u << j ) ) == 0u )
          {
            s = j;
            break;
          }
        }

        assert( s != n );

        // Before CNOTs
        std::vector<unsigned> targets;
        for ( unsigned j = 0u; j < n; ++j )
        {
          if ( j != s && funcs[offset]->at( i ).test( j ) )
          {
            apply_gate( circ, funcs, offset, insert_at, s, j );
            targets += j;
          }
        }

        // Toffoli Gate
        std::vector<unsigned> controls;
        for ( unsigned j = 0u; j < n; ++j )
        {
          if ( i & ( 1u << j ) )
          {
            controls += j;
          }
        }
        apply_gate( circ, funcs, offset, insert_at, controls, s );

        // After CNOTs
        for ( unsigned j : targets )
        {
          apply_gate( circ, funcs, offset, insert_at, s, j );
        }
      }
    }

    return true;
  }

  truth_table_synthesis_func reed_muller_synthesis_func( properties::ptr settings, properties::ptr statistics )
  {
    truth_table_synthesis_func f = [&settings, &statistics]( circuit& circ, const binary_truth_table& spec ) {
      return reed_muller_synthesis( circ, spec, settings, statistics );
    };
    f.init( settings, statistics );
    return f;
  }

}

// Local Variables:
// c-basic-offset: 2
// eval: (c-set-offset 'substatement-open 0)
// eval: (c-set-offset 'innamespace 0)
// End:


//
// Copyright (c) 2002--2010
// Toon Knapen, Karl Meerbergen, Kresimir Fresl,
// Thomas Klimpel and Rutger ter Borg
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//
// THIS FILE IS AUTOMATICALLY GENERATED
// PLEASE DO NOT EDIT!
//

#ifndef BOOST_NUMERIC_BINDINGS_LAPACK_DRIVER_GESDD_HPP
#define BOOST_NUMERIC_BINDINGS_LAPACK_DRIVER_GESDD_HPP

#include <boost/assert.hpp>
#include <boost/numeric/bindings/begin.hpp>
#include <boost/numeric/bindings/detail/array.hpp>
#include <boost/numeric/bindings/is_column_major.hpp>
#include <boost/numeric/bindings/is_complex.hpp>
#include <boost/numeric/bindings/is_mutable.hpp>
#include <boost/numeric/bindings/is_real.hpp>
#include <boost/numeric/bindings/lapack/workspace.hpp>
#include <boost/numeric/bindings/remove_imaginary.hpp>
#include <boost/numeric/bindings/size.hpp>
#include <boost/numeric/bindings/stride.hpp>
#include <boost/numeric/bindings/traits/detail/utils.hpp>
#include <boost/numeric/bindings/value_type.hpp>
#include <boost/static_assert.hpp>
#include <boost/type_traits/is_same.hpp>
#include <boost/type_traits/remove_const.hpp>
#include <boost/utility/enable_if.hpp>

//
// The LAPACK-backend for gesdd is the netlib-compatible backend.
//
#include <boost/numeric/bindings/lapack/detail/lapack.h>
#include <boost/numeric/bindings/lapack/detail/lapack_option.hpp>

namespace boost {
namespace numeric {
namespace bindings {
namespace lapack {

//
// The detail namespace contains value-type-overloaded functions that
// dispatch to the appropriate back-end LAPACK-routine.
//
namespace detail {

//
// Overloaded function for dispatching to
// * netlib-compatible LAPACK backend (the default), and
// * float value-type.
//
inline std::ptrdiff_t gesdd( const char jobz, const fortran_int_t m,
        const fortran_int_t n, float* a, const fortran_int_t lda, float* s,
        float* u, const fortran_int_t ldu, float* vt,
        const fortran_int_t ldvt, float* work, const fortran_int_t lwork,
        fortran_int_t* iwork ) {
    fortran_int_t info(0);
    LAPACK_SGESDD( &jobz, &m, &n, a, &lda, s, u, &ldu, vt, &ldvt, work,
            &lwork, iwork, &info );
    return info;
}

//
// Overloaded function for dispatching to
// * netlib-compatible LAPACK backend (the default), and
// * double value-type.
//
inline std::ptrdiff_t gesdd( const char jobz, const fortran_int_t m,
        const fortran_int_t n, double* a, const fortran_int_t lda, double* s,
        double* u, const fortran_int_t ldu, double* vt,
        const fortran_int_t ldvt, double* work, const fortran_int_t lwork,
        fortran_int_t* iwork ) {
    fortran_int_t info(0);
    LAPACK_DGESDD( &jobz, &m, &n, a, &lda, s, u, &ldu, vt, &ldvt, work,
            &lwork, iwork, &info );
    return info;
}

//
// Overloaded function for dispatching to
// * netlib-compatible LAPACK backend (the default), and
// * complex<float> value-type.
//
inline std::ptrdiff_t gesdd( const char jobz, const fortran_int_t m,
        const fortran_int_t n, std::complex<float>* a,
        const fortran_int_t lda, float* s, std::complex<float>* u,
        const fortran_int_t ldu, std::complex<float>* vt,
        const fortran_int_t ldvt, std::complex<float>* work,
        const fortran_int_t lwork, float* rwork, fortran_int_t* iwork ) {
    fortran_int_t info(0);
    LAPACK_CGESDD( &jobz, &m, &n, a, &lda, s, u, &ldu, vt, &ldvt, work,
            &lwork, rwork, iwork, &info );
    return info;
}

//
// Overloaded function for dispatching to
// * netlib-compatible LAPACK backend (the default), and
// * complex<double> value-type.
//
inline std::ptrdiff_t gesdd( const char jobz, const fortran_int_t m,
        const fortran_int_t n, std::complex<double>* a,
        const fortran_int_t lda, double* s, std::complex<double>* u,
        const fortran_int_t ldu, std::complex<double>* vt,
        const fortran_int_t ldvt, std::complex<double>* work,
        const fortran_int_t lwork, double* rwork, fortran_int_t* iwork ) {
    fortran_int_t info(0);
    LAPACK_ZGESDD( &jobz, &m, &n, a, &lda, s, u, &ldu, vt, &ldvt, work,
            &lwork, rwork, iwork, &info );
    return info;
}

} // namespace detail

//
// Value-type based template class. Use this class if you need a type
// for dispatching to gesdd.
//
template< typename Value, typename Enable = void >
struct gesdd_impl {};

//
// This implementation is enabled if Value is a real type.
//
template< typename Value >
struct gesdd_impl< Value, typename boost::enable_if< is_real< Value > >::type > {

    typedef Value value_type;
    typedef typename remove_imaginary< Value >::type real_type;

    //
    // Static member function for user-defined workspaces, that
    // * Deduces the required arguments for dispatching to LAPACK, and
    // * Asserts that most arguments make sense.
    //
    template< typename MatrixA, typename VectorS, typename MatrixU,
            typename MatrixVT, typename WORK, typename IWORK >
    static std::ptrdiff_t invoke( const char jobz, MatrixA& a, VectorS& s,
            MatrixU& u, MatrixVT& vt, detail::workspace2< WORK,
            IWORK > work ) {
        namespace bindings = ::boost::numeric::bindings;
        BOOST_STATIC_ASSERT( (bindings::is_column_major< MatrixA >::value) );
        BOOST_STATIC_ASSERT( (bindings::is_column_major< MatrixU >::value) );
        BOOST_STATIC_ASSERT( (bindings::is_column_major< MatrixVT >::value) );
        BOOST_STATIC_ASSERT( (boost::is_same< typename remove_const<
                typename bindings::value_type< MatrixA >::type >::type,
                typename remove_const< typename bindings::value_type<
                VectorS >::type >::type >::value) );
        BOOST_STATIC_ASSERT( (boost::is_same< typename remove_const<
                typename bindings::value_type< MatrixA >::type >::type,
                typename remove_const< typename bindings::value_type<
                MatrixU >::type >::type >::value) );
        BOOST_STATIC_ASSERT( (boost::is_same< typename remove_const<
                typename bindings::value_type< MatrixA >::type >::type,
                typename remove_const< typename bindings::value_type<
                MatrixVT >::type >::type >::value) );
        BOOST_STATIC_ASSERT( (bindings::is_mutable< MatrixA >::value) );
        BOOST_STATIC_ASSERT( (bindings::is_mutable< VectorS >::value) );
        BOOST_STATIC_ASSERT( (bindings::is_mutable< MatrixU >::value) );
        BOOST_STATIC_ASSERT( (bindings::is_mutable< MatrixVT >::value) );
        std::ptrdiff_t minmn = std::min< std::ptrdiff_t >( size_row(a),
                size_column(a) );
        BOOST_ASSERT( bindings::size(s) >= std::min<
                std::ptrdiff_t >(bindings::size_row(a),
                bindings::size_column(a)) );
        BOOST_ASSERT( bindings::size(work.select(fortran_int_t())) >=
                min_size_iwork( minmn ));
        BOOST_ASSERT( bindings::size(work.select(real_type())) >=
                min_size_work( bindings::size_row(a),
                bindings::size_column(a), jobz, minmn ));
        BOOST_ASSERT( bindings::size_column(a) >= 0 );
        BOOST_ASSERT( bindings::size_minor(a) == 1 ||
                bindings::stride_minor(a) == 1 );
        BOOST_ASSERT( bindings::size_minor(u) == 1 ||
                bindings::stride_minor(u) == 1 );
        BOOST_ASSERT( bindings::size_minor(vt) == 1 ||
                bindings::stride_minor(vt) == 1 );
        BOOST_ASSERT( bindings::size_row(a) >= 0 );
        BOOST_ASSERT( bindings::stride_major(a) >= std::max< std::ptrdiff_t >(1,
                bindings::size_row(a)) );
        BOOST_ASSERT( jobz == 'A' || jobz == 'S' || jobz == 'O' ||
                jobz == 'N' );
        return detail::gesdd( jobz, bindings::size_row(a),
                bindings::size_column(a), bindings::begin_value(a),
                bindings::stride_major(a), bindings::begin_value(s),
                bindings::begin_value(u), bindings::stride_major(u),
                bindings::begin_value(vt), bindings::stride_major(vt),
                bindings::begin_value(work.select(real_type())),
                bindings::size(work.select(real_type())),
                bindings::begin_value(work.select(fortran_int_t())) );
    }

    //
    // Static member function that
    // * Figures out the minimal workspace requirements, and passes
    //   the results to the user-defined workspace overload of the
    //   invoke static member function
    // * Enables the unblocked algorithm (BLAS level 2)
    //
    template< typename MatrixA, typename VectorS, typename MatrixU,
            typename MatrixVT >
    static std::ptrdiff_t invoke( const char jobz, MatrixA& a, VectorS& s,
            MatrixU& u, MatrixVT& vt, minimal_workspace ) {
        namespace bindings = ::boost::numeric::bindings;
        std::ptrdiff_t minmn = std::min< std::ptrdiff_t >( size_row(a),
                size_column(a) );
        bindings::detail::array< real_type > tmp_work( min_size_work(
                bindings::size_row(a), bindings::size_column(a), jobz,
                minmn ) );
        bindings::detail::array< fortran_int_t > tmp_iwork(
                min_size_iwork( minmn ) );
        return invoke( jobz, a, s, u, vt, workspace( tmp_work, tmp_iwork ) );
    }

    //
    // Static member function that
    // * Figures out the optimal workspace requirements, and passes
    //   the results to the user-defined workspace overload of the
    //   invoke static member
    // * Enables the blocked algorithm (BLAS level 3)
    //
    template< typename MatrixA, typename VectorS, typename MatrixU,
            typename MatrixVT >
    static std::ptrdiff_t invoke( const char jobz, MatrixA& a, VectorS& s,
            MatrixU& u, MatrixVT& vt, optimal_workspace ) {
        namespace bindings = ::boost::numeric::bindings;
        return invoke( jobz, a, s, u, vt, minimal_workspace() );
    }

    //
    // Static member function that returns the minimum size of
    // workspace-array work.
    //
    static std::ptrdiff_t min_size_work( const std::ptrdiff_t m,
            const std::ptrdiff_t n, const char jobz,
            const std::ptrdiff_t minmn ) {
        if ( n == 0 ) return 1;
        if ( jobz == 'N' ) return 3*minmn + std::max<
                std::ptrdiff_t >( std::max< std::ptrdiff_t >(m,n), 7*minmn );
        if ( jobz == 'O' ) return 3*minmn*minmn + std::max<
                std::ptrdiff_t >( std::max< std::ptrdiff_t >( m,n ),
                5*minmn*minmn + 4*minmn );
        return 3*minmn*minmn + std::max< std::ptrdiff_t >( std::max<
                std::ptrdiff_t >( m,n ), 4*minmn*minmn + 4*minmn );
    }

    //
    // Static member function that returns the minimum size of
    // workspace-array iwork.
    //
    static std::ptrdiff_t min_size_iwork( const std::ptrdiff_t minmn ) {
            return 8*minmn;
    }
};

//
// This implementation is enabled if Value is a complex type.
//
template< typename Value >
struct gesdd_impl< Value, typename boost::enable_if< is_complex< Value > >::type > {

    typedef Value value_type;
    typedef typename remove_imaginary< Value >::type real_type;

    //
    // Static member function for user-defined workspaces, that
    // * Deduces the required arguments for dispatching to LAPACK, and
    // * Asserts that most arguments make sense.
    //
    template< typename MatrixA, typename VectorS, typename MatrixU,
            typename MatrixVT, typename WORK, typename RWORK, typename IWORK >
    static std::ptrdiff_t invoke( const char jobz, MatrixA& a, VectorS& s,
            MatrixU& u, MatrixVT& vt, detail::workspace3< WORK, RWORK,
            IWORK > work ) {
        namespace bindings = ::boost::numeric::bindings;
        BOOST_STATIC_ASSERT( (bindings::is_column_major< MatrixA >::value) );
        BOOST_STATIC_ASSERT( (bindings::is_column_major< MatrixU >::value) );
        BOOST_STATIC_ASSERT( (bindings::is_column_major< MatrixVT >::value) );
        BOOST_STATIC_ASSERT( (boost::is_same< typename remove_const<
                typename bindings::value_type< MatrixA >::type >::type,
                typename remove_const< typename bindings::value_type<
                MatrixU >::type >::type >::value) );
        BOOST_STATIC_ASSERT( (boost::is_same< typename remove_const<
                typename bindings::value_type< MatrixA >::type >::type,
                typename remove_const< typename bindings::value_type<
                MatrixVT >::type >::type >::value) );
        BOOST_STATIC_ASSERT( (bindings::is_mutable< MatrixA >::value) );
        BOOST_STATIC_ASSERT( (bindings::is_mutable< VectorS >::value) );
        BOOST_STATIC_ASSERT( (bindings::is_mutable< MatrixU >::value) );
        BOOST_STATIC_ASSERT( (bindings::is_mutable< MatrixVT >::value) );
        std::ptrdiff_t minmn = std::min< std::ptrdiff_t >( size_row(a),
                size_column(a) );
        BOOST_ASSERT( bindings::size(s) >= std::min<
                std::ptrdiff_t >(bindings::size_row(a),
                bindings::size_column(a)) );
        BOOST_ASSERT( bindings::size(work.select(fortran_int_t())) >=
                min_size_iwork( minmn ));
        BOOST_ASSERT( bindings::size(work.select(real_type())) >=
                min_size_rwork( minmn, jobz ));
        BOOST_ASSERT( bindings::size(work.select(value_type())) >=
                min_size_work( bindings::size_row(a),
                bindings::size_column(a), jobz, minmn ));
        BOOST_ASSERT( bindings::size_column(a) >= 0 );
        BOOST_ASSERT( bindings::size_minor(a) == 1 ||
                bindings::stride_minor(a) == 1 );
        BOOST_ASSERT( bindings::size_minor(u) == 1 ||
                bindings::stride_minor(u) == 1 );
        BOOST_ASSERT( bindings::size_minor(vt) == 1 ||
                bindings::stride_minor(vt) == 1 );
        BOOST_ASSERT( bindings::size_row(a) >= 0 );
        BOOST_ASSERT( bindings::stride_major(a) >= std::max< std::ptrdiff_t >(1,
                bindings::size_row(a)) );
        BOOST_ASSERT( jobz == 'A' || jobz == 'S' || jobz == 'O' ||
                jobz == 'N' );
        return detail::gesdd( jobz, bindings::size_row(a),
                bindings::size_column(a), bindings::begin_value(a),
                bindings::stride_major(a), bindings::begin_value(s),
                bindings::begin_value(u), bindings::stride_major(u),
                bindings::begin_value(vt), bindings::stride_major(vt),
                bindings::begin_value(work.select(value_type())),
                bindings::size(work.select(value_type())),
                bindings::begin_value(work.select(real_type())),
                bindings::begin_value(work.select(fortran_int_t())) );
    }

    //
    // Static member function that
    // * Figures out the minimal workspace requirements, and passes
    //   the results to the user-defined workspace overload of the
    //   invoke static member function
    // * Enables the unblocked algorithm (BLAS level 2)
    //
    template< typename MatrixA, typename VectorS, typename MatrixU,
            typename MatrixVT >
    static std::ptrdiff_t invoke( const char jobz, MatrixA& a, VectorS& s,
            MatrixU& u, MatrixVT& vt, minimal_workspace ) {
        namespace bindings = ::boost::numeric::bindings;
        std::ptrdiff_t minmn = std::min< std::ptrdiff_t >( size_row(a),
                size_column(a) );
        bindings::detail::array< value_type > tmp_work( min_size_work(
                bindings::size_row(a), bindings::size_column(a), jobz,
                minmn ) );
        bindings::detail::array< real_type > tmp_rwork( min_size_rwork( minmn,
                jobz ) );
        bindings::detail::array< fortran_int_t > tmp_iwork(
                min_size_iwork( minmn ) );
        return invoke( jobz, a, s, u, vt, workspace( tmp_work, tmp_rwork,
                tmp_iwork ) );
    }

    //
    // Static member function that
    // * Figures out the optimal workspace requirements, and passes
    //   the results to the user-defined workspace overload of the
    //   invoke static member
    // * Enables the blocked algorithm (BLAS level 3)
    //
    template< typename MatrixA, typename VectorS, typename MatrixU,
            typename MatrixVT >
    static std::ptrdiff_t invoke( const char jobz, MatrixA& a, VectorS& s,
            MatrixU& u, MatrixVT& vt, optimal_workspace ) {
        namespace bindings = ::boost::numeric::bindings;
        std::ptrdiff_t minmn = std::min< std::ptrdiff_t >( size_row(a),
                size_column(a) );
        value_type opt_size_work;
        bindings::detail::array< real_type > tmp_rwork( min_size_rwork( minmn,
                jobz ) );
        bindings::detail::array< fortran_int_t > tmp_iwork(
                min_size_iwork( minmn ) );
        detail::gesdd( jobz, bindings::size_row(a),
                bindings::size_column(a), bindings::begin_value(a),
                bindings::stride_major(a), bindings::begin_value(s),
                bindings::begin_value(u), bindings::stride_major(u),
                bindings::begin_value(vt), bindings::stride_major(vt),
                &opt_size_work, -1, bindings::begin_value(tmp_rwork),
                bindings::begin_value(tmp_iwork) );
        bindings::detail::array< value_type > tmp_work(
                traits::detail::to_int( opt_size_work ) );
        return invoke( jobz, a, s, u, vt, workspace( tmp_work, tmp_rwork,
                tmp_iwork ) );
    }

    //
    // Static member function that returns the minimum size of
    // workspace-array work.
    //
    static std::ptrdiff_t min_size_work( const std::ptrdiff_t m,
            const std::ptrdiff_t n, const char jobz,
            const std::ptrdiff_t minmn ) {
        if ( n == 0 ) return 1;
        if ( jobz == 'N' ) return 2*minmn + std::max< std::ptrdiff_t >( m,n );
        if ( jobz == 'O' ) return 2*(minmn*minmn + minmn) + std::max<
                std::ptrdiff_t >( m, n );
        return minmn*minmn + 2*minmn + std::max< std::ptrdiff_t >( m, n );
    }

    //
    // Static member function that returns the minimum size of
    // workspace-array rwork.
    //
    static std::ptrdiff_t min_size_rwork( const std::ptrdiff_t minmn,
            const char jobz ) {
        if ( jobz == 'N' ) return 5*minmn;
        return 5*minmn*minmn + 7*minmn;
    }

    //
    // Static member function that returns the minimum size of
    // workspace-array iwork.
    //
    static std::ptrdiff_t min_size_iwork( const std::ptrdiff_t minmn ) {
            return 8*minmn;
    }
};

//
// Functions for direct use. These functions are overloaded for temporaries,
// so that wrapped types can still be passed and used for write-access. In
// addition, if applicable, they are overloaded for user-defined workspaces.
// Calls to these functions are passed to the gesdd_impl classes. In the
// documentation, most overloads are collapsed to avoid a large number of
// prototypes which are very similar.
//

//
// Overloaded function for gesdd. Its overload differs for
// * User-defined workspace
//
template< typename MatrixA, typename VectorS, typename MatrixU,
        typename MatrixVT, typename Workspace >
inline typename boost::enable_if< detail::is_workspace< Workspace >,
        std::ptrdiff_t >::type
gesdd( const char jobz, MatrixA& a, VectorS& s, MatrixU& u, MatrixVT& vt,
        Workspace work ) {
    return gesdd_impl< typename bindings::value_type<
            MatrixA >::type >::invoke( jobz, a, s, u, vt, work );
}

//
// Overloaded function for gesdd. Its overload differs for
// * Default workspace-type (optimal)
//
template< typename MatrixA, typename VectorS, typename MatrixU,
        typename MatrixVT >
inline typename boost::disable_if< detail::is_workspace< MatrixVT >,
        std::ptrdiff_t >::type
gesdd( const char jobz, MatrixA& a, VectorS& s, MatrixU& u,
        MatrixVT& vt ) {
    return gesdd_impl< typename bindings::value_type<
            MatrixA >::type >::invoke( jobz, a, s, u, vt,
            optimal_workspace() );
}

} // namespace lapack
} // namespace bindings
} // namespace numeric
} // namespace boost

#endif