#ifndef FOURTOP_LEPTON
#define FOURTOP_LEPTON

//#include <boost>
#include <cstring>
#include <stdio.h>
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>
//#include <TObject.h>
#include <TMath.h>
#include "ROOT/RDF/RInterface.hxx"
#include "ROOT/RVec.hxx"
#include "Math/GenVector/PtEtaPhiM4D.h"

//const and by reference means no copies, no pointer handling, etc.
using VecF_t = const ROOT::RVec<float>&;
using VecI_t = const ROOT::RVec<int>&;
using VecUC_t = const ROOT::RVec<UChar_t>&;
using VecB_t = const ROOT::RVec<bool>&;
// using VecP4_t = const ROOT::RVec<ROOT::Math::PtEtaPhiM4D<double>>&;
using VecP4_t = const ROOT::VecOps::RVec< ROOT::Math::LorentzVector< ROOT::Math::PtEtaPhiM4D<double> > >&;
// class myFunctorClass
// {
// public:
//   myFunctorClass (int x) : _x( x ) {}
//   int operator() (int y) { return _x + y; }
// private:
//   int _id;
//   bool _inviso
// };
class preselectElectrons
{
public:
  preselectElectrons(int ElectronID, bool invertIsolation) : _ElectronID(ElectronID), _invertIsolation(invertIsolation) {}
  ROOT::RVec<int> operator()(VecF_t Electron_pt, VecF_t Electron_eta, VecF_t Electron_phi,
			     VecF_t Electron_ip3d, VecF_t Electron_dz,  VecI_t Electron_charge,
			     VecI_t Electron_cutBased){
    ROOT::RVec<int> Electron_premask = Electron_pt > 15 &&
      (
       (abs(Electron_eta) < 1.4442 && abs(Electron_ip3d) < 0.05 && abs(Electron_dz) < 0.1)
       ||
       (abs(Electron_eta) > 1.5660 && abs(Electron_eta) < 2.5 && abs(Electron_ip3d) < 0.10 && abs(Electron_dz) < 0.2)
       );
    if(_invertIsolation){
      std::cout << "iso inversion not implemented" << std::endl;
    }
    else{
      Electron_premask = Electron_premask && Electron_cutBased >= _ElectronID;
    }
    return Electron_premask;
  }
private:
  int _ElectronID;
  bool _invertIsolation;
};
class preselectMuons
{
public:
  preselectMuons (int MuonID, bool invertIsolation) : _MuonID(MuonID), _invertIsolation(invertIsolation) {}
  ROOT::RVec<int> operator()(VecF_t Muon_pt, VecF_t Muon_eta, VecF_t Muon_phi,
			     VecF_t Muon_ip3d, VecF_t Muon_dz, VecI_t Muon_charge,
			     VecB_t Muon_looseId, VecB_t Muon_mediumId, VecB_t Muon_tightId, VecUC_t Muon_pfIsoId){
    ROOT::RVec<int> Muon_premask = Muon_pt > 15 && abs(Muon_eta) < 2.4 && abs(Muon_ip3d) < 0.10 && abs(Muon_dz) < 0.02;
    if(_MuonID == 2){//loose, match Electrons for this number
      Muon_premask = Muon_premask && Muon_looseId == true;
    }
    else if(_MuonID == 3){
      Muon_premask = Muon_premask && Muon_mediumId == true;
    }
    else if(_MuonID == 4){
      Muon_premask = Muon_premask && Muon_tightId == true;
    }
    if(_invertIsolation){
      Muon_premask = Muon_premask && Muon_pfIsoId < 4;
    }
    else{
      Muon_premask = Muon_premask && Muon_pfIsoId >= 4;
    }
    return Muon_premask;
  }
private:
  int _MuonID;
  bool _invertIsolation;
};

std::pair<ROOT::RDF::RNode, ROOT::VecOps::RVec<int>> applyTriggerSelection(ROOT::RDF::RNode df, std::string era, std::string triggerChannel, std::string decayChannel,
									   bool isData, std::string subera, std::string ElectronID, std::string MuonID,
									   bool applyLowMassResonanceCut=true, bool invertZWindow=false, bool verbose=false){
  std::map< std::string, std::vector<int> > triggerIDs;
  std::map< std::string, ROOT::VecOps::RVec<double> > triggers;
  std::map< std::string, int> triggerBit;
  std::vector<std::string> vetoTriggers;
  ROOT::VecOps::RVec<int> decayChannelIDs = {};
  bool cutLowMassResonances = false;
  bool cutZResonance = false;
  ROOT::VecOps::RVec<int> channelTriggerBits = {};
  if(decayChannel == "ElMu"){
    decayChannelIDs = {13, 11};
  }
  else if(decayChannel == "ElEl"){
    decayChannelIDs = {11, 11};
    cutLowMassResonances = applyLowMassResonanceCut;
    cutZResonance = true;
  }
  else if(decayChannel == "MuMu"){
    decayChannelIDs = {13, 13};
    cutLowMassResonances = applyLowMassResonanceCut;
    cutZResonance = true;
  }
  std::string stringCutLowMassResonances = cutLowMassResonances ? "true" : "false";
  std::string stringCutZResonance = cutZResonance ? "true" : "false";
  std::string stringInvertZWindow = invertZWindow ? "true": "false";
  if(isData){
    assert (subera == "A" || subera == "B" || subera == "C" || subera == "D" || subera == "E" || subera == "F");
  }
  if(era == "2017"){
    //ElMu
    triggerBit["HLT_Mu23_TrkIsoVVL_Ele12_CaloIdL_TrackIdL_IsoVL_DZ"] = 14;
    triggerBit["HLT_Mu12_TrkIsoVVL_Ele23_CaloIdL_TrackIdL_IsoVL_DZ"] = 13;
    if(triggerChannel == "ElMu") channelTriggerBits = {14, 13};
    //MuMu
    triggerBit["HLT_Mu17_TrkIsoVVL_Mu8_TrkIsoVVL_DZ"] = 12;
    triggerBit["HLT_Mu17_TrkIsoVVL_Mu8_TrkIsoVVL_DZ_Mass3p8"] = 11;
    if(triggerChannel == "MuMu") channelTriggerBits = {12, 11};
    //ElEl
    triggerBit["HLT_Ele23_Ele12_CaloIdL_TrackIdL_IsoVL"] = 9;
    if(triggerChannel == "ElEl") channelTriggerBits = {9};
    //Mu
    triggerBit["HLT_IsoMu27"] = 7;
    if(triggerChannel == "Mu") channelTriggerBits = {7};
    //El
    triggerBit["HLT_Ele35_WPTight_Gsf"] = 6;
    if(triggerChannel == "El") channelTriggerBits = {6};
    //MET
    triggerBit["HLT_PFMET200_NotCleaned"] = 3;
    triggerBit["HLT_PFMET200_HBHECleaned"] = 2;
    triggerBit["HLT_PFMETTypeOne200_HBHE_BeamHaloCleaned"] = 1;
    if(triggerChannel == "MET") channelTriggerBits = {3, 2, 1};
  }
  else if(era == "2018"){
    //ElMu
    triggerBit["HLT_Mu12_TrkIsoVVL_Ele23_CaloIdL_TrackIdL_IsoVL_DZ"] = 14; // why the reversal? Maa ika
    triggerBit["HLT_Mu23_TrkIsoVVL_Ele12_CaloIdL_TrackIdL_IsoVL_DZ"] = 12;
    if(triggerChannel == "ElMu") channelTriggerBits = {14, 12};
    //MuMu
    triggerBit["HLT_Mu17_TrkIsoVVL_Mu8_TrkIsoVVL_DZ_Mass3p8"] = 11;
    if(triggerChannel == "MuMu") channelTriggerBits = {11};
    //ElEl
    triggerBit["HLT_Ele23_Ele12_CaloIdL_TrackIdL_IsoVL"] = 9;
    if(triggerChannel == "ElEl") channelTriggerBits = {9};
    //Mu
    triggerBit["HLT_IsoMu24"] = 8;
    if(triggerChannel == "Mu") channelTriggerBits = {8};
    //El
    triggerBit["HLT_Ele32_WPTight_Gsf"] = 4;
    if(triggerChannel == "El") channelTriggerBits = {4};
    //MET
    triggerBit["HLT_PFMET200_NotCleaned"] = 3;
    triggerBit["HLT_PFMET200_HBHECleaned"] = 2;
    triggerBit["HLT_PFMETTypeOne200_HBHE_BeamHaloCleaned"] = 1;
    if(triggerChannel == "MET") channelTriggerBits = {3, 2, 1};
  }
  if(decayChannel == "ElMu"){
    if(era == "2017"){
      if(triggerChannel == "ElMu"){
	//DONE
	vetoTriggers = {}; //ElMu has highest trigger precedence
	triggerIDs["HLT_Mu23_TrkIsoVVL_Ele12_CaloIdL_TrackIdL_IsoVL_DZ"] = {13, 11}; //BCDEF
	triggers["HLT_Mu23_TrkIsoVVL_Ele12_CaloIdL_TrackIdL_IsoVL_DZ"] = {25.0, 15.0}; //BCDEF
	triggerIDs["HLT_Mu12_TrkIsoVVL_Ele23_CaloIdL_TrackIdL_IsoVL_DZ"] = {13, 11}; //BCDEF
	triggers["HLT_Mu12_TrkIsoVVL_Ele23_CaloIdL_TrackIdL_IsoVL_DZ"] = {15.0, 25.0}; //BCDEF
      }
      else if(triggerChannel == "Mu"){
	//DONE
	vetoTriggers = {"HLT_Mu23_TrkIsoVVL_Ele12_CaloIdL_TrackIdL_IsoVL_DZ", "HLT_Mu12_TrkIsoVVL_Ele23_CaloIdL_TrackIdL_IsoVL_DZ",
			"HLT_Ele23_Ele12_CaloIdL_TrackIdL_IsoVL"}; //Mu has 4th highest trigger precedence
	if(!isData || subera == "B") vetoTriggers.push_back("HLT_Mu17_TrkIsoVVL_Mu8_TrkIsoVVL_DZ");
	if(!isData || subera == "C" || subera == "D" || subera == "E" || subera == "F") vetoTriggers.push_back("HLT_Mu17_TrkIsoVVL_Mu8_TrkIsoVVL_DZ_Mass3p8");
	triggerIDs["HLT_IsoMu27"] = {13};
	triggers["HLT_IsoMu27"] = {30.0, 15.0}; //BCDEF
      }
      else if(triggerChannel == "El"){
	//DONE
	vetoTriggers = {"HLT_Mu23_TrkIsoVVL_Ele12_CaloIdL_TrackIdL_IsoVL_DZ", "HLT_Mu12_TrkIsoVVL_Ele23_CaloIdL_TrackIdL_IsoVL_DZ",
			"HLT_Ele23_Ele12_CaloIdL_TrackIdL_IsoVL", "HLT_IsoMu27"}; //El has 5th highest trigger precedence
	if(!isData || subera == "B") vetoTriggers.push_back("HLT_Mu17_TrkIsoVVL_Mu8_TrkIsoVVL_DZ");
	if(!isData || subera == "C" || subera == "D" || subera == "E" || subera == "F") vetoTriggers.push_back("HLT_Mu17_TrkIsoVVL_Mu8_TrkIsoVVL_DZ_Mass3p8");
	triggerIDs["HLT_Ele35_WPTight_Gsf"] = {11};
	triggers["HLT_Ele35_WPTight_Gsf"] = {15.0, 38.0}; //BCDEF
      }
      else if(triggerChannel == "MET"){
	//DONE
	vetoTriggers = {}; //Don't veto events for trigger studies, but should only pull from the MET datastream...
	triggerIDs["HLT_PFMET200_NotCleaned"] = {};
	triggers["HLT_PFMET200_NotCleaned"] = {15.0, 15.0}; //impose asymmetric cut somewhere else
	triggerIDs["HLT_PFMET200_HBHECleaned"] = {};
	triggers["HLT_PFMET200_HBHECleaned"] = {15.0, 15.0}; //impose asymmetric cut somewhere else
	triggerIDs["HLT_PFMETTypeOne200_HBHE_BeamHaloCleaned"] = {};
	triggers["HLT_PFMETTypeOne200_HBHE_BeamHaloCleaned"] = {15.0, 15.0}; //impose asymmetric cut somewhere else
      }
      else{
	vetoTriggers = {};
	triggerIDs["#LOGICERROR"] = {};
	triggers["#LOGICERROR"] = {};
      }
    }
    else if(era == "2018"){
      if(triggerChannel == "ElMu"){
	//DONE
	vetoTriggers = {}; //ElMu has highest trigger precedence
	triggers["HLT_Mu12_TrkIsoVVL_Ele23_CaloIdL_TrackIdL_IsoVL_DZ"] = {15.0, 25.0}; //ABCD
	triggerIDs["HLT_Mu12_TrkIsoVVL_Ele23_CaloIdL_TrackIdL_IsoVL_DZ"] = {13, 11}; //ABCD
	triggers["HLT_Mu23_TrkIsoVVL_Ele12_CaloIdL_TrackIdL_IsoVL_DZ"] = {25.0, 15.0}; //ABCD
	triggerIDs["HLT_Mu23_TrkIsoVVL_Ele12_CaloIdL_TrackIdL_IsoVL_DZ"] = {13, 11}; //ABCD
      }
      else if(triggerChannel == "Mu"){
	//DONE
	vetoTriggers = {"HLT_Mu12_TrkIsoVVL_Ele23_CaloIdL_TrackIdL_IsoVL_DZ", "HLT_Mu23_TrkIsoVVL_Ele12_CaloIdL_TrackIdL_IsoVL_DZ",
			"HLT_Mu17_TrkIsoVVL_Mu8_TrkIsoVVL_DZ_Mass3p8", "HLT_Ele23_Ele12_CaloIdL_TrackIdL_IsoVL"}; //Mu has 4th highest trigger precedence
	triggerIDs["HLT_IsoMu24"] = {13}; //ABCD
	triggers["HLT_IsoMu24"] = {27.0, 15.0}; //ABCD
      }
      else if(triggerChannel == "El"){
	//DONE
	vetoTriggers = {"HLT_Mu12_TrkIsoVVL_Ele23_CaloIdL_TrackIdL_IsoVL_DZ", "HLT_Mu23_TrkIsoVVL_Ele12_CaloIdL_TrackIdL_IsoVL_DZ",
			"HLT_Mu17_TrkIsoVVL_Mu8_TrkIsoVVL_DZ_Mass3p8", "HLT_Ele23_Ele12_CaloIdL_TrackIdL_IsoVL", "HLT_IsoMu24"}; //El has 5th highest trigger precedence
	triggerIDs["HLT_Ele32_WPTight_Gsf"] = {11};
	triggers["HLT_Ele32_WPTight_Gsf"] = {15.0, 35.0}; // ABCD
      }
      else if(triggerChannel == "MET"){
	//DONE
	vetoTriggers = {}; //Don't veto events for trigger studies, but should only pull from the MET datastream...
	triggerIDs["HLT_PFMET200_NotCleaned"] = {};
	triggers["HLT_PFMET200_NotCleaned"] = {15.0, 15.0}; //impose asymmetric cut somewhere else
	triggerIDs["HLT_PFMET200_HBHECleaned"] = {};
	triggers["HLT_PFMET200_HBHECleaned"] = {15.0, 15.0}; //impose asymmetric cut somewhere else
	triggerIDs["HLT_PFMETTypeOne200_HBHE_BeamHaloCleaned"] = {};
	triggers["HLT_PFMETTypeOne200_HBHE_BeamHaloCleaned"] = {15.0, 15.0}; //impose asymmetric cut somewhere else
      }
      else{
	vetoTriggers = {};
	triggerIDs["#LOGICERROR"] = {};
	triggers["#LOGICERROR"] = {};
      }
    }
  }
  else if (decayChannel == "MuMu"){
    if(era == "2017"){
      if(triggerChannel == "MuMu"){
	//DONE
	vetoTriggers = {"HLT_Mu23_TrkIsoVVL_Ele12_CaloIdL_TrackIdL_IsoVL_DZ", "HLT_Mu12_TrkIsoVVL_Ele23_CaloIdL_TrackIdL_IsoVL_DZ"}; //MuMu has 2nd highest trigger precedence
	if(!isData || subera == "B"){
	  triggerIDs["HLT_Mu17_TrkIsoVVL_Mu8_TrkIsoVVL_DZ"] = {13, 13}; //B
	  triggers["HLT_Mu17_TrkIsoVVL_Mu8_TrkIsoVVL_DZ"] = {25.0, 15.0}; //B
	}
	if(!isData || subera == "C" || subera == "D" || subera == "E" || subera == "F"){ //NO ELSE IF, must have both for MC
	  triggerIDs["HLT_Mu17_TrkIsoVVL_Mu8_TrkIsoVVL_DZ_Mass3p8"] = {13, 13}; //CDEF
	  triggers["HLT_Mu17_TrkIsoVVL_Mu8_TrkIsoVVL_DZ_Mass3p8"] = {25.0, 15.0}; //CDEF
	}
      }
      else if(triggerChannel == "Mu"){
	//DONE
	vetoTriggers = {"HLT_Mu23_TrkIsoVVL_Ele12_CaloIdL_TrackIdL_IsoVL_DZ", "HLT_Mu12_TrkIsoVVL_Ele23_CaloIdL_TrackIdL_IsoVL_DZ"}; //Mu has 4th highest trigger precedence
	if(!isData || subera == "B") vetoTriggers.push_back("HLT_Mu17_TrkIsoVVL_Mu8_TrkIsoVVL_DZ");
	if(!isData || subera == "C" || subera == "D" || subera == "E" || subera == "F") vetoTriggers.push_back("HLT_Mu17_TrkIsoVVL_Mu8_TrkIsoVVL_DZ_Mass3p8");
	triggerIDs["HLT_IsoMu27"] = {13};
	triggers["HLT_IsoMu27"] = {30.0, 15.0}; //BCDEF
      }
      else if(triggerChannel == "MET"){
	//DONE
	vetoTriggers = {}; //Don't veto events for trigger studies, but should only pull from the MET datastream...
	triggerIDs["HLT_PFMET200_NotCleaned"] = {};
	triggers["HLT_PFMET200_NotCleaned"] = {15.0, 15.0}; //impose asymmetric cut somewhere else
	triggerIDs["HLT_PFMET200_HBHECleaned"] = {};
	triggers["HLT_PFMET200_HBHECleaned"] = {15.0, 15.0}; //impose asymmetric cut somewhere else
	triggerIDs["HLT_PFMETTypeOne200_HBHE_BeamHaloCleaned"] = {};
	triggers["HLT_PFMETTypeOne200_HBHE_BeamHaloCleaned"] = {15.0, 15.0}; //impose asymmetric cut somewhere else
      }
      else{
	//DONE
	vetoTriggers = {};
	triggerIDs["#LOGICERROR"] = {};
	triggers["#LOGICERROR"] = {};
      }
    }
    else if(era == "2018"){
      if(triggerChannel == "MuMu"){
	//FIXME
	vetoTriggers = {"HLT_Mu12_TrkIsoVVL_Ele23_CaloIdL_TrackIdL_IsoVL_DZ", "HLT_Mu23_TrkIsoVVL_Ele12_CaloIdL_TrackIdL_IsoVL_DZ"}; //Mu has 4th highest trigger precedence
	triggers["HLT_Mu17_TrkIsoVVL_Mu8_TrkIsoVVL_DZ_Mass3p8"] = {25.0, 15.0}; //ABCD
      }
      else if(triggerChannel == "Mu"){
	//DONE
	vetoTriggers = {"HLT_Mu12_TrkIsoVVL_Ele23_CaloIdL_TrackIdL_IsoVL_DZ", "HLT_Mu23_TrkIsoVVL_Ele12_CaloIdL_TrackIdL_IsoVL_DZ",
			"HLT_Mu17_TrkIsoVVL_Mu8_TrkIsoVVL_DZ_Mass3p8", "HLT_Ele23_Ele12_CaloIdL_TrackIdL_IsoVL"}; //Mu has 4th highest trigger precedence
	triggerIDs["HLT_IsoMu24"] = {13}; //ABCD
	triggers["HLT_IsoMu24"] = {27.0, 15.0}; //ABCD
      }
      else if(triggerChannel == "MET"){
	//DONE
	vetoTriggers = {}; //Don't veto events for trigger studies, but should only pull from the MET datastream...
	triggerIDs["HLT_PFMET200_NotCleaned"] = {};
	triggers["HLT_PFMET200_NotCleaned"] = {15.0, 15.0}; //impose asymmetric cut somewhere else
	triggerIDs["HLT_PFMET200_HBHECleaned"] = {};
	triggers["HLT_PFMET200_HBHECleaned"] = {15.0, 15.0}; //impose asymmetric cut somewhere else
	triggerIDs["HLT_PFMETTypeOne200_HBHE_BeamHaloCleaned"] = {};
	triggers["HLT_PFMETTypeOne200_HBHE_BeamHaloCleaned"] = {15.0, 15.0}; //impose asymmetric cut somewhere else
      }
      else{
	vetoTriggers = {};
	triggerIDs["#LOGICERROR"] = {};
	triggers["#LOGICERROR"] = {};
      }
    }
  }
  else if (decayChannel == "ElEl"){
    if(era == "2017"){
      if(triggerChannel == "ElEl"){
	//DONE
	vetoTriggers = {"HLT_Mu23_TrkIsoVVL_Ele12_CaloIdL_TrackIdL_IsoVL_DZ", "HLT_Mu12_TrkIsoVVL_Ele23_CaloIdL_TrackIdL_IsoVL_DZ"}; //Mu has 4th highest trigger precedence
	if(!isData || subera == "B") vetoTriggers.push_back("HLT_Mu17_TrkIsoVVL_Mu8_TrkIsoVVL_DZ");
	if(!isData || subera == "C" || subera == "D" || subera == "E" || subera == "F") vetoTriggers.push_back("HLT_Mu17_TrkIsoVVL_Mu8_TrkIsoVVL_DZ_Mass3p8");
	triggerIDs["HLT_Ele23_Ele12_CaloIdL_TrackIdL_IsoVL"] = {11, 11};
	triggers["HLT_Ele23_Ele12_CaloIdL_TrackIdL_IsoVL"] = {25.0, 15.0}; //BCDEF
      }
      else if(triggerChannel == "El"){
	//DONE
	vetoTriggers = {"HLT_Mu23_TrkIsoVVL_Ele12_CaloIdL_TrackIdL_IsoVL_DZ", "HLT_Mu12_TrkIsoVVL_Ele23_CaloIdL_TrackIdL_IsoVL_DZ",
			"HLT_Ele23_Ele12_CaloIdL_TrackIdL_IsoVL", "HLT_IsoMu27"}; //El has 5th highest trigger precedence
	if(!isData || subera == "B") vetoTriggers.push_back("HLT_Mu17_TrkIsoVVL_Mu8_TrkIsoVVL_DZ");
	if(!isData || subera == "C" || subera == "D" || subera == "E" || subera == "F") vetoTriggers.push_back("HLT_Mu17_TrkIsoVVL_Mu8_TrkIsoVVL_DZ_Mass3p8");
	triggerIDs["HLT_Ele35_WPTight_Gsf"] = {11};
	triggers["HLT_Ele35_WPTight_Gsf"] = {38.0, 15.0}; //BCDEF

      }
      else if(triggerChannel == "MET"){
	//DONE
	vetoTriggers = {}; //Don't veto events for trigger studies, but should only pull from the MET datastream...
	triggerIDs["HLT_PFMET200_NotCleaned"] = {};
	triggers["HLT_PFMET200_NotCleaned"] = {15.0, 15.0}; //impose asymmetric cut somewhere else
	triggerIDs["HLT_PFMET200_HBHECleaned"] = {};
	triggers["HLT_PFMET200_HBHECleaned"] = {15.0, 15.0}; //impose asymmetric cut somewhere else
	triggerIDs["HLT_PFMETTypeOne200_HBHE_BeamHaloCleaned"] = {};
	triggers["HLT_PFMETTypeOne200_HBHE_BeamHaloCleaned"] = {15.0, 15.0}; //impose asymmetric cut somewhere else
      }
      else{
	vetoTriggers = {};
	triggerIDs["#LOGICERROR"] = {};
	triggers["#LOGICERROR"] = {};
      }
    }
    else if(era == "2018"){
      if(triggerChannel == "ElEl"){
	//DONE
	vetoTriggers = {"HLT_Mu12_TrkIsoVVL_Ele23_CaloIdL_TrackIdL_IsoVL_DZ", "HLT_Mu23_TrkIsoVVL_Ele12_CaloIdL_TrackIdL_IsoVL_DZ",
			"HLT_Mu17_TrkIsoVVL_Mu8_TrkIsoVVL_DZ_Mass3p8"}; //ElEl has 3rd highest trigger precedence
	triggerIDs["HLT_Ele23_Ele12_CaloIdL_TrackIdL_IsoVL"] = {11, 11};
	triggers["HLT_Ele23_Ele12_CaloIdL_TrackIdL_IsoVL"] = {25.0, 15.0}; // ABCD
      }
      else if(triggerChannel == "El"){
	//DONE
	vetoTriggers = {"HLT_Mu12_TrkIsoVVL_Ele23_CaloIdL_TrackIdL_IsoVL_DZ", "HLT_Mu23_TrkIsoVVL_Ele12_CaloIdL_TrackIdL_IsoVL_DZ",
			"HLT_Mu17_TrkIsoVVL_Mu8_TrkIsoVVL_DZ_Mass3p8", "HLT_Ele23_Ele12_CaloIdL_TrackIdL_IsoVL", "HLT_IsoMu24"}; //El has 5th highest trigger precedence
	triggerIDs["HLT_Ele32_WPTight_Gsf"] = {11};
	triggers["HLT_Ele32_WPTight_Gsf"] = {35.0, 15.0}; // ABCD
      }
      else if(triggerChannel == "MET"){
	//DONE
	vetoTriggers = {}; //Don't veto events for trigger studies, but should only pull from the MET datastream...
	triggerIDs["HLT_PFMET200_NotCleaned"] = {};
	triggers["HLT_PFMET200_NotCleaned"] = {15.0, 15.0}; //impose asymmetric cut somewhere else
	triggerIDs["HLT_PFMET200_HBHECleaned"] = {};
	triggers["HLT_PFMET200_HBHECleaned"] = {15.0, 15.0}; //impose asymmetric cut somewhere else
	triggerIDs["HLT_PFMETTypeOne200_HBHE_BeamHaloCleaned"] = {};
	triggers["HLT_PFMETTypeOne200_HBHE_BeamHaloCleaned"] = {15.0, 15.0}; //impose asymmetric cut somewhere else
      }
      else{
	vetoTriggers = {};
	triggerIDs["#LOGICERROR"] = {};
	triggers["#LOGICERROR"] = {};
      }
    }
  }

  std::cout << "Era: " << era << "   Subera: " << subera << "   isData: " << isData << "   Channel: " << decayChannel << "   triggerChannel: " << triggerChannel << std::endl;
  for(std::map< std::string, std::vector<int> >::iterator id_iter = triggerIDs.begin(); id_iter != triggerIDs.end(); ++id_iter){
    // std::cout << id_iter->first << "   " << id_iter->second << std::endl;
    std::cout << id_iter->first << "   ";
    for(int i = 0; i < triggers[id_iter->first].size(); ++i){
      std::cout << triggerIDs[id_iter->first].at(i) << "  ";
    }
    for(int i = 0; i < triggers[id_iter->first].size(); ++i){
      std::cout << triggers[id_iter->first].at(i) << "  ";
    }
    std::cout << std::endl;
  }
  // for(std::map< std::string, std::vector<double> >::iterator th_iter = triggers.begin(); th_iter != triggers.end(); ++th_iter){
  // }
  ROOT::RDF::RNode ret = df;

  int ElectronIDEnum;
  int MuonIDEnum;

  if(ElectronID == "Veto") ElectronIDEnum = 1;
  if(ElectronID == "Loose") ElectronIDEnum = 2;
  if(ElectronID == "Medium") ElectronIDEnum = 3;
  if(ElectronID == "Tight") ElectronIDEnum = 4;

  if(MuonID == "Loose") MuonIDEnum = 2;
  if(MuonID == "Medium") MuonIDEnum = 3;
  if(MuonID == "Tight") MuonIDEnum = 4;

  auto constructP4 = [](VecF_t pts, VecF_t etas, VecF_t phis, VecF_t masses){
    auto fourVecs = ROOT::VecOps::Construct<ROOT::Math::PtEtaPhiMVector>(pts, etas, phis, masses);
    return fourVecs;
  };
  preselectElectrons pIsoElectrons(ElectronIDEnum, false);
  preselectMuons pIsoMuons(MuonIDEnum, false);
  ret = ret.Define("Electron_p4", constructP4, {"Electron_pt", "Electron_phi", "Electron_eta", "Electron_mass"});
  ret = ret.Define("Electron_preselection", pIsoElectrons, {"Electron_pt", "Electron_eta", "Electron_phi",
	"Electron_ip3d", "Electron_dz", "Electron_charge",
	"Electron_cutBased"});
  ret = ret.Define("Muon_p4", constructP4, {"Muon_pt", "Muon_phi", "Muon_eta", "Muon_mass"});
  ret = ret.Define("Muon_preselection", pIsoMuons, {"Muon_pt", "Muon_eta", "Muon_phi",
	"Muon_ip3d", "Muon_dz", "Muon_charge",
	"Muon_looseId", "Muon_mediumId", "Muon_tightId", "Muon_pfIsoId"});
  ret = ret.Define("junkPrinterE", "if(ROOT::VecOps::Any(e_mask != Electron_preselection)){"
		   "auto diff_mask = (e_mask != Electron_preselection);"
		   "std::cout << \"Difference found\" << std::endl;"
		   "std::cout << \"Eleectron pt  \" << Electron_pt[diff_mask] << std::endl;"
		   "std::cout << \"Eleectron eta \" << Electron_eta[diff_mask] << std::endl;"
		   "std::cout << \"Eleectron phi \" << Electron_phi[diff_mask] << std::endl;"
		   "std::cout << \"Eleectron ip3d\" << Electron_ip3d[diff_mask] << std::endl;"
		   "std::cout << \"Eleectron dz  \" << Electron_dz[diff_mask] << std::endl;"
		   "std::cout << \"Eleectron chg \" << Electron_charge[diff_mask] << std::endl;"
		   "std::cout << \"Eleectron ID  \" << Electron_cutBased[diff_mask] << std::endl << std::endl;}"
		   "return false;");
  ret = ret.Define("junkPrinterM", "if(ROOT::VecOps::Any(mu_mask != Muon_preselection)){"
		   "auto diff_mask = (mu_mask != Muon_preselection);"
		   "std::cout << \"Difference found\" << std::endl;"
		   "std::cout << \"Muon pt  \" << Muon_pt[diff_mask] << std::endl;"
		   "std::cout << \"Muon eta \" << Muon_eta[diff_mask] << std::endl;"
		   "std::cout << \"Muon phi \" << Muon_phi[diff_mask] << std::endl;"
		   "std::cout << \"Muon ip3d\" << Muon_ip3d[diff_mask] << std::endl;"
		   "std::cout << \"Muon dz  \" << Muon_dz[diff_mask] << std::endl;"
		   "std::cout << \"Muon chg \" << Muon_charge[diff_mask] << std::endl;"
		   "std::cout << \"Muon L   \" << Muon_looseId[diff_mask] << std::endl;"
		   "std::cout << \"Muon M   \" << Muon_mediumId[diff_mask] << std::endl;"
		   "std::cout << \"Muon T   \" << Muon_tightId[diff_mask] << std::endl;"
		   "std::cout << \"Muon iso \" << Muon_pfIsoId[diff_mask] << \"Old \" << Muon_pfIsoId[mu_mask] << \"New \" << Muon_pfIsoId[Muon_preselection] << std::endl << std::endl;}"
		   "return false;");
// \"old v new \" << Muon_pfIsoId[mu_mask] << \" \" << Muon_pfIsoId[Muon_preselection] << std::end;}"

  //filter out tertiary leptons and match the decayChannel expectations, leaving only the pt thresholds to ensure correctness
  //Need to capture by value. If we capture by reference, no copy is made, but the local variable decayChannelIDs can go out of scope. Then we're referencing a variable that
  //no longer exists. Voila.
  // ret = ret.Filter([decayChannelIDs](VecI_t Muon_pdgId, VecI_t Muon_charge, VecI_t Muon_preselection,
  // 				      VecI_t Electron_pdgId, VecI_t Electron_charge, VecI_t Electron_preselection, ULong64_t rdfentry_){
  // 		     // std::cout << rdfentry_ << "  -->  ";
  // 		     bool twoLeptons = (Sum(Muon_preselection) + Sum(Electron_preselection) == 2);
  // 		     auto Lepton_absflavor = ROOT::VecOps::abs(ROOT::VecOps::Concatenate(Muon_pdgId[Muon_preselection], Electron_pdgId[Electron_preselection]));
  // 		     auto Lepton_charge = ROOT::VecOps::Concatenate(Muon_charge[Muon_preselection], Electron_charge[Electron_preselection]);
  // 		     // std::cout << Lepton_absflavor.size() << "   " << Lepton_charge.size() << "   " << decayChannelIDs.size() << "   ";
  // 		     if(twoLeptons){
  // 		       bool correctFlavors = ROOT::VecOps::All(Lepton_absflavor == decayChannelIDs);
  // 		       if(Lepton_charge.at(0) * Lepton_charge.at(1) < 0){
  // 			 if(correctFlavors){
  // 			   // std::cout << rdfentry_ << " Selected IDs: " << Lepton_absflavor
  // 			   // 	     << "  Decay IDs: (" << decayChannelIDs.size() << ") " << decayChannelIDs
  // 			   // 	     << " Decision 2L: " << twoLeptons;
  // 			   // std::cout << " Decision CF: " << correctFlavors << std::endl;
  // 			   return true;
  // 			 }
  // 			 //failed correct flavors?
  // 			 // else{
  // 			 //   std::cout << "Flav mismatch: " << Lepton_absflavor << " " << decayChannelIDs << " " << correctFlavors << std::endl;
  // 			 // }
  // 		       }
  // 		       //failed opposite charge
  // 		       // else{
  // 		       // 	 std::cout << "Charge mismatch: " << Lepton_charge << std::endl;
  // 		       // }
  // 		     }
  // 		     //failed two leptons
  // 		     // else{
  // 		     //   std::cout << "Dilepton mismatch: " << Muon_preselection << " " << Electron_preselection << " " << twoLeptons << std::endl;
  // 		     // }
  // 		     return false;
  // 		   },
  // 		   {"Muon_pdgId", "Muon_charge", "Muon_preselection", "Electron_pdgId", "Electron_charge", "Electron_preselection", "rdfentry_"},
  // 		   "Exactly 2 isolated, opposite-sign leptons of correct flavor");
  ret = ret.Filter([decayChannelIDs](VecI_t Muon_pdgId, VecI_t Muon_charge, VecI_t Muon_preselection,
				      VecI_t Electron_pdgId, VecI_t Electron_charge, VecI_t Electron_preselection, ULong64_t rdfentry_){
		     // std::cout << rdfentry_ << "  -->  ";
		     bool twoLeptons = (Sum(Muon_preselection) + Sum(Electron_preselection) == 2);
		     auto Lepton_absflavor = ROOT::VecOps::abs(ROOT::VecOps::Concatenate(Muon_pdgId[Muon_preselection], Electron_pdgId[Electron_preselection]));
		     auto Lepton_charge = ROOT::VecOps::Concatenate(Muon_charge[Muon_preselection], Electron_charge[Electron_preselection]);
		     // std::cout << Lepton_absflavor.size() << "   " << Lepton_charge.size() << "   " << decayChannelIDs.size() << "   ";
		     if(twoLeptons){
		       return true;
		     }
		     //failed two leptons
		     return false;
		   },
		   {"Muon_pdgId", "Muon_charge", "Muon_preselection", "Electron_pdgId", "Electron_charge", "Electron_preselection", "rdfentry_"},
		   "Exactly 2 isolated leptons");
  ret = ret.Filter([decayChannelIDs](VecI_t Muon_pdgId, VecI_t Muon_charge, VecI_t Muon_preselection,
				      VecI_t Electron_pdgId, VecI_t Electron_charge, VecI_t Electron_preselection, ULong64_t rdfentry_){
		     // std::cout << rdfentry_ << "  -->  ";
		     bool twoLeptons = (Sum(Muon_preselection) + Sum(Electron_preselection) == 2);
		     auto Lepton_absflavor = ROOT::VecOps::abs(ROOT::VecOps::Concatenate(Muon_pdgId[Muon_preselection], Electron_pdgId[Electron_preselection]));
		     auto Lepton_charge = ROOT::VecOps::Concatenate(Muon_charge[Muon_preselection], Electron_charge[Electron_preselection]);
		     // std::cout << Lepton_absflavor.size() << "   " << Lepton_charge.size() << "   " << decayChannelIDs.size() << "   ";
		     if(twoLeptons){
		       bool correctFlavors = ROOT::VecOps::All(Lepton_absflavor == decayChannelIDs);
		       if(Lepton_charge.at(0) * Lepton_charge.at(1) < 0){
			   return true;
		       }
		       //failed opposite charge
		     }
		     return false;
		   },
		   {"Muon_pdgId", "Muon_charge", "Muon_preselection", "Electron_pdgId", "Electron_charge", "Electron_preselection", "rdfentry_"},
		   "Opposite-sign leptons");
  ret = ret.Filter([decayChannelIDs](VecI_t Muon_pdgId, VecI_t Muon_charge, VecI_t Muon_preselection,
				      VecI_t Electron_pdgId, VecI_t Electron_charge, VecI_t Electron_preselection, ULong64_t rdfentry_){
		     // std::cout << rdfentry_ << "  -->  ";
		     bool twoLeptons = (Sum(Muon_preselection) + Sum(Electron_preselection) == 2);
		     auto Lepton_absflavor = ROOT::VecOps::abs(ROOT::VecOps::Concatenate(Muon_pdgId[Muon_preselection], Electron_pdgId[Electron_preselection]));
		     auto Lepton_charge = ROOT::VecOps::Concatenate(Muon_charge[Muon_preselection], Electron_charge[Electron_preselection]);
		     // std::cout << Lepton_absflavor.size() << "   " << Lepton_charge.size() << "   " << decayChannelIDs.size() << "   ";
		     if(twoLeptons){
		       bool correctFlavors = ROOT::VecOps::All(Lepton_absflavor == decayChannelIDs);
		       if(Lepton_charge.at(0) * Lepton_charge.at(1) < 0){
			 if(correctFlavors){
			   // std::cout << rdfentry_ << " Selected IDs: " << Lepton_absflavor
			   // 	     << "  Decay IDs: (" << decayChannelIDs.size() << ") " << decayChannelIDs
			   // 	     << " Decision 2L: " << twoLeptons;
			   // std::cout << " Decision CF: " << correctFlavors << std::endl;
			   return true;
			 }
			 //failed correct flavors?
			 // else{
			 //   std::cout << "Flav mismatch: " << Lepton_absflavor << " " << decayChannelIDs << " " << correctFlavors << std::endl;
			 // }
		       }
		       //failed opposite charge
		       // else{
		       // 	 std::cout << "Charge mismatch: " << Lepton_charge << std::endl;
		       // }
		     }
		     //failed two leptons
		     // else{
		     //   std::cout << "Dilepton mismatch: " << Muon_preselection << " " << Electron_preselection << " " << twoLeptons << std::endl;
		     // }
		     return false;
		   },
		   {"Muon_pdgId", "Muon_charge", "Muon_preselection", "Electron_pdgId", "Electron_charge", "Electron_preselection", "rdfentry_"},
		   "Correct lepton flavor");
  ret = ret.Filter([decayChannelIDs, cutLowMassResonances](VecI_t Muon_pdgId, VecI_t Muon_charge, VecP4_t Muon_p4, VecI_t Muon_preselection,
							   VecI_t Electron_pdgId, VecI_t Electron_charge, VecP4_t Electron_p4, VecI_t Electron_preselection,
							   ULong64_t rdfentry_){
		     // std::cout << rdfentry_ << "  -->  ";
		     bool twoLeptons = (Sum(Muon_preselection) + Sum(Electron_preselection) == 2);
		     auto Lepton_absflavor = ROOT::VecOps::abs(ROOT::VecOps::Concatenate(Muon_pdgId[Muon_preselection], Electron_pdgId[Electron_preselection]));
		     auto Lepton_charge = ROOT::VecOps::Concatenate(Muon_charge[Muon_preselection], Electron_charge[Electron_preselection]);
		     // std::cout << Lepton_absflavor.size() << "   " << Lepton_charge.size() << "   " << decayChannelIDs.size() << "   ";
		     if(twoLeptons){
		       bool correctFlavors = ROOT::VecOps::All(Lepton_absflavor == decayChannelIDs);
		       if(Lepton_charge.at(0) * Lepton_charge.at(1) < 0){
			 if(correctFlavors){
			   // std::cout << rdfentry_ << " Selected IDs: " << Lepton_absflavor
			   // 	     << "  Decay IDs: (" << decayChannelIDs.size() << ") " << decayChannelIDs
			   // 	     << " Decision 2L: " << twoLeptons;
			   // std::cout << " Decision CF: " << correctFlavors << std::endl;
			   if(cutLowMassResonances){
			     if(Muon_p4[Muon_preselection].size() == 2 &&
			     	(Muon_p4[Muon_preselection].at(0) + Muon_p4[Muon_preselection].at(1)).M() >= 20.0)
			       return true;
			     else if(Electron_p4[Electron_preselection].size() == 2 &&
			     	     (Electron_p4[Electron_preselection].at(0) + Electron_p4[Electron_preselection].at(1)).M() >= 20.0)
			       return true;
			     //figure resonance cut
			     else
			       return false;
			   }
			   else
			     return true;

			 }
		       }
		     }
		     return false;
		   },
		   {"Muon_pdgId", "Muon_charge", "Muon_p4", "Muon_preselection", "Electron_pdgId", "Electron_charge", "Electron_p4", "Electron_preselection", "rdfentry_"},
		   "Pass low mass resonance cut (applied = " + stringCutLowMassResonances + ")" );
  ret = ret.Filter([decayChannelIDs, cutZResonance, invertZWindow](VecI_t Muon_pdgId, VecI_t Muon_charge, VecP4_t Muon_p4, VecI_t Muon_preselection,
								   VecI_t Electron_pdgId, VecI_t Electron_charge, VecP4_t Electron_p4, VecI_t Electron_preselection,
								   ULong64_t rdfentry_){
		     // std::cout << rdfentry_ << "  -->  ";
		     bool twoLeptons = (Sum(Muon_preselection) + Sum(Electron_preselection) == 2);
		     auto Lepton_absflavor = ROOT::VecOps::abs(ROOT::VecOps::Concatenate(Muon_pdgId[Muon_preselection], Electron_pdgId[Electron_preselection]));
		     auto Lepton_charge = ROOT::VecOps::Concatenate(Muon_charge[Muon_preselection], Electron_charge[Electron_preselection]);
		     // std::cout << Lepton_absflavor.size() << "   " << Lepton_charge.size() << "   " << decayChannelIDs.size() << "   ";
		     if(twoLeptons){
		       bool correctFlavors = ROOT::VecOps::All(Lepton_absflavor == decayChannelIDs);
		       if(Lepton_charge.at(0) * Lepton_charge.at(1) < 0){
			 if(correctFlavors){
			   // std::cout << rdfentry_ << " Selected IDs: " << Lepton_absflavor
			   // 	     << "  Decay IDs: (" << decayChannelIDs.size() << ") " << decayChannelIDs
			   // 	     << " Decision 2L: " << twoLeptons;
			   // std::cout << " Decision CF: " << correctFlavors << std::endl;
			   if(cutZResonance){
			     if(invertZWindow){
			       //mass within 15GeV of the Z boson
			       if(Muon_p4[Muon_preselection].size() == 2 &&
				  std::fabs( (Muon_p4[Muon_preselection].at(0) + Muon_p4[Muon_preselection].at(1)).M() - 91.0 ) <= 15.0)
				 return true;
			       else if(Electron_p4[Electron_preselection].size() == 2 &&
				       std::fabs( (Electron_p4[Electron_preselection].at(0) + Electron_p4[Electron_preselection].at(1)).M() - 91.0 ) <= 15.0)
				 return true;
			       else
				 return false;
			     }
			     else{
			       //mass outside 15GeV of the Z boson
			       if(Muon_p4[Muon_preselection].size() == 2 &&
				  std::fabs( (Muon_p4[Muon_preselection].at(0) + Muon_p4[Muon_preselection].at(1)).M() - 91.0 ) > 15.0)
				 return true;
			       else if(Electron_p4[Electron_preselection].size() == 2 &&
				       std::fabs( (Electron_p4[Electron_preselection].at(0) + Electron_p4[Electron_preselection].at(1)).M() - 91.0 ) > 15.0)
				 return true;
			       else
				 return false;
			     }
			   }
			   else
			     //no Z cut, pass through
			     return true;
			 }
		       }
		     }
		     return false;
		   },
		   {"Muon_pdgId", "Muon_charge", "Muon_p4", "Muon_preselection", "Electron_pdgId", "Electron_charge", "Electron_p4", "Electron_preselection", "rdfentry_"},
		   "Z mass window (applied = " + stringCutZResonance + ", inverted = " + stringInvertZWindow + ")" );
  // ret = ret.Filter([](VecI_t Muon_pdgId, VecI_t Muon_pdgId, VecI_t Muon_preselection, VecI_t Electron_charge, VecI_t Electron_preselection){
  //Goal: For events that at least have 2 isolated leptons, then check the valid trigger paths, first vetoing higher tier triggers with a filter,
  //then only using a Define to store the concurrent trigger path decisions in an int value (-1 vetoed, 0 failed, 1 passed) and the matching lepton selection masks in two additional arrays

  //for a quick QCD test, just filter on the valid triggers from this path
  //build common filter code based on triggerChannel
  std::string filter_code = "return";
  auto v_first = vetoTriggers.begin();
  auto v_next_to_last = vetoTriggers.empty() ? vetoTriggers.end() : std::prev(vetoTriggers.end());
  auto v_last = vetoTriggers.end();
  if(v_first != v_last){
    filter_code += " (";
    for(std::vector<std::string>::iterator vt_iter = v_first; vt_iter != v_last; ++vt_iter){
      filter_code += *vt_iter + " == false";
      if(vt_iter != v_next_to_last){
	filter_code += " && ";
      }
    }
    filter_code += ") &&"; //We had a veto section, do an AND with the passing trigger
  }

  //build the filter expressions per trigger, using common vetoTrigger code
  std::map<std::string, std::string> trigger_code;
  auto first = triggers.begin();
  auto next_to_last = triggers.empty() ? triggers.end() : std::prev(triggers.end()); // in case s is empty
  auto last = triggers.end();
  for(std::map< std::string, ROOT::VecOps::RVec<double> >::iterator th_iter = first; th_iter != last; ++th_iter){
    trigger_code[th_iter->first] = filter_code + " (" + th_iter->first + " == true";
    if( decayChannelIDs.at(0, -1) == 13 && decayChannelIDs.at(1, -1) == 13)//MuMu
      trigger_code[th_iter->first] += " && Muon_pt[Muon_preselection].at(0, -1) >= " + std::to_string(th_iter->second.at(0, 15)) +
	" && Muon_pt[Muon_preselection].at(1, -1) >= " + std::to_string(th_iter->second.at(1, 15));
    else if( decayChannelIDs.at(0, -1) == 13 && decayChannelIDs.at(1, -1) == 11)
      trigger_code[th_iter->first] += " && Muon_pt[Muon_preselection].at(0, -1) >= " + std::to_string(th_iter->second.at(0, 15)) +
	" && Electron_pt[Electron_preselection].at(0, -1) >= " + std::to_string(th_iter->second.at(1, 15));
    else if( decayChannelIDs.at(0, -1) == 11 && decayChannelIDs.at(1, -1) == 11)
      trigger_code[th_iter->first] += " && Electron_pt[Electron_preselection].at(0, -1) >= " + std::to_string(th_iter->second.at(0, 15)) +
	" && Electron_pt[Electron_preselection].at(1, -1) >= " + std::to_string(th_iter->second.at(1, 15));
    trigger_code[th_iter->first] += ");";
    std::cout << th_iter->first << " --> " << trigger_code[th_iter->first] << std::endl;
    auto thresh = triggers[th_iter->first]; //the pt thresholds
    ret = ret.Define("triggerPath_" + std::to_string(triggerBit[th_iter->first]),
		     [thresh](bool trigger_path, VecF_t Muon_pt, VecI_t Muon_preselection,
			       VecF_t Electron_pt, VecI_t Electron_preselection, ULong64_t rdfentry_){
		       auto Lepton_pt = ROOT::VecOps::Concatenate(Muon_pt[Muon_preselection], Electron_pt[Electron_preselection]);
		       if(thresh.size() == 2){
			 return 1 * ROOT::VecOps::All(Lepton_pt > thresh);
		       }
		       else if(thresh.size() == 0){
			 ROOT::VecOps::RVec<double> generic = {25.0, 15.0};
			 return 1 * ROOT::VecOps::All(ROOT::VecOps::Reverse(ROOT::VecOps::Sort(Lepton_pt)) > generic);
		       }
		       else
			 throw runtime_error("Thresholds must either have 2 double values representing the Muon and Electron or highest then lowest pt thresholds, or have length 0");
		     },
		     {th_iter->first, "Muon_pt", "Muon_preselection", "Electron_pt", "Electron_preselection", "rdfentry_"});

  }

  //Now save filter decisions
  auto tb_first = triggerBit.begin();
  auto tb_last = triggerBit.end();
  for(std::map< std::string, int>::iterator tb_iter = tb_first; tb_iter != tb_last; ++tb_iter){
    if(triggers.find(tb_iter->first) == triggers.end()){
      //all trigger paths not being checked, set to -1
      ret = ret.Define("triggerPath_" + std::to_string(tb_iter->second), [](){int null_val = -1; return null_val;}, {});
      ret = ret.Define("triggerPath_" + std::to_string(tb_iter->second) + "_Muon_selection", [](){ROOT::VecOps::RVec<int> null_val = {};return null_val;}, {});
      ret = ret.Define("triggerPath_" + std::to_string(tb_iter->second) + "_Electrons_selection", [](){ROOT::VecOps::RVec<int> null_val = {};return null_val;}, {});
    }
    else{
      //a trigger is being checked
      // ret = ret.Define("triggerPath_" + std::to_string(tb_iter->second), );
      ret = ret.Define("triggerPath_" + std::to_string(tb_iter->second) + "_Muons_selection",
		       [](bool trigger_path, ROOT::VecOps::RVec<int> Muon_preselection){
			 if(trigger_path)
			   return Muon_preselection;
			 else{
			   ROOT::VecOps::RVec<int> null_val = {};
			   return null_val;
			 }
		       },
		       {tb_iter->first, "Muon_preselection"});
      ret = ret.Define("triggerPath_" + std::to_string(tb_iter->second) + "_Electrons_selection",
		       [](bool trigger_path, ROOT::VecOps::RVec<int> Electron_preselection){
			 if(trigger_path)
			   return Electron_preselection;
			 else{
			   ROOT::VecOps::RVec<int> null_val = {};
			   return null_val;
			 }
		       },
		       {tb_iter->first, "Electron_preselection"});
    }
  }//end iteration over triggerBits
  return std::pair<ROOT::RDF::RNode, ROOT::VecOps::RVec<int>>(ret, channelTriggerBits);
  // ROOT::Detail::RDF::ColumnNames_t flags = {};
}

// CMuon, CElectron = Cutbased lepton selection
// vector< vector<int> > findCMuonCElectronPair(VecF_t Muon_pt, VecF_t Muon_eta, VecF_t Muon_phi,
// 					     VecF_t Muon_ip3d, VecF_t Muon_dz, VecI_t Muon_charge,
// 					     VecI_t Muon_passId,
// 					     VecF_t Electron_pt, VecF_t Electron_eta, VecF_t Electron_phi,
// 					     VecF_t Electron_ip3d, VecF_t Electron_dz,  VecI_t Electron_charge,
// 					     VecI_t Electron_passId,
// 					     int LeadThresh, int SubleadThresh, int VetoThresh, int LeadId){
//   using namespace ROOT::VecOps;
//   auto Muon_premask = Muon_pt > 15 && abs(Muon_eta) < 2.4 && Muon_passId == true && Muon_pfIsoId >= 4 && abs(Muon_ip3d) < 0.10 && abs(Muon_dz) < 0.02;
//   auto Muon_idx = Argsort(Muon_pt)[Muon_premask]; // max to min
//   auto Electron_premask = Electron_pt > 15 && Electron_passId == true &&
//     (
//      (abs(Electron_eta) < 1.4442 && abs(Electron_ip3d) < 0.05 && abs(Electron_dz) < 0.1)
//      ||
//      (abs(Electron_eta) > 1.5660 && abs(Electron_eta) < 2.5 && abs(Electron_ip3d) < 0.10 && abs(Electron_dz) < 0.2)
//      );
//CMuon, CElectron = Cutbased lepton selection
// vector< vector<int> > findCMuonCElectronPair(VecF_t Muon_pt, VecF_t Muon_eta, VecF_t Muon_phi,
// 					     VecF_t Muon_ip3d, VecF_t Muon_dz, VecI_t Muon_charge,
// 					     VecI_t Muon_passId,
// 					     VecF_t Electron_pt, VecF_t Electron_eta, VecF_t Electron_phi,
// 					     VecF_t Electron_ip3d, VecF_t Electron_dz,  VecI_t Electron_charge,
// 					     VecI_t Electron_passId,
// 					     int LeadThresh, int SubleadThresh, int VetoThresh, int LeadId){
//   using namespace ROOT::VecOps;
//   auto Muon_premask = Muon_pt > 15 && abs(Muon_eta) < 2.4 && Muon_passId == true && Muon_pfIsoId >= 4 && abs(Muon_ip3d) < 0.10 && abs(Muon_dz) < 0.02;
//   auto Muon_idx = Argsort(Muon_pt)[Muon_premask]; // max to min
//   auto Electron_premask = Electron_pt > 15 && Electron_passId == true &&
//     (
//      (abs(Electron_eta) < 1.4442 && abs(Electron_ip3d) < 0.05 && abs(Electron_dz) < 0.1)
//      ||
//      (abs(Electron_eta) > 1.5660 && abs(Electron_eta) < 2.5 && abs(Electron_ip3d) < 0.10 && abs(Electron_dz) < 0.2)
//      );
//   auto Electron_idx = Argsort(Electron_pt)[Electron_premask]; // min to max
//   for(int Muon_i:Muon_idx){
//     for(int Tau_i:Tau_idx){
//       if(DeltaR(Muon_eta[Muon_i], Tau_eta[Tau_i], Muon_phi[Muon_i], Tau_phi[Tau_i]) > 0.5 && Muon_charge[Muon_i] * Tau_charge[Tau_i] < 0){
// 	return {Muon_i,Tau_i};
//       }
//     }
//   }
//   return {}; // Invalid event!
// }

  // electron_options = {"Veto", "Loose", "Medium", "Tight"}; //, "MVA80noiso", "MVA80iso", "MVA90noiso", "MVA90iso"};
  // muon_options = {"LooseID", "MediumID", "TightID"}; //"TOPMVAVeryLoose", "TOPMVALoose", "TOPMVAMedium", "TOPMVATight"};

  // 	//Electrons: Very Loose (> -0.55, S=99%, B=8%), Loose (> 0.00, S=98%, B=4%), Medium (> 0.60, S=95%, B=2%), Tight (> 0.90, S=90%, B=1%) branch: Electron_mvaTOP
  // 	//Muons: Very Loose (> -0.45, S=99%, B=9%), Loose (> 0.05, S=98%, B=5%), Medium (> 0.65, S=95%, B=2%), Tight (> 0.90, S=90%, B=1%) branch: Muon_mvaTOP
  // 	if(arg_list[0] == "Muon_eta" || arg_list[0] == "Muon_pt"){
  // 	  auto slottedLookup = [veclut, branch_and_key](int slot, ROOT::VecOps::RVec<float> X, ROOT::VecOps::RVec<float> Y){
  // 	    ROOT::VecOps::RVec<float> rvec_return = {};
  // 	    for(int li=0; li < X.size(); ++li) {
  // 	      rvec_return.push_back((*veclut)[slot]->TH2Lookup(branch_and_key, fabs(X[li]), fabs(Y[li])));
  // 	    }
  // 	    return rvec_return;
  // 	  };
  // 	  ret = ret.DefineSlot(branch_and_key, slottedLookup, arg_list);
  // 	}
#endif


// Boost.Geometry (aka GGL, Generic Geometry Library)

// Copyright (c) 2007-2015 Barend Gehrels, Amsterdam, the Netherlands.
// Copyright (c) 2008-2015 Bruno Lalande, Paris, France.
// Copyright (c) 2009-2015 Mateusz Loskot, London, UK.
// Copyright (c) 2014-2015 Samuel Debionne, Grenoble, France.

// This file was modified by Oracle on 2015, 2016, 2017, 2018.
// Modifications copyright (c) 2015-2018, Oracle and/or its affiliates.

// Contributed and/or modified by Vissarion Fysikopoulos, on behalf of Oracle
// Contributed and/or modified by Menelaos Karavelas, on behalf of Oracle
// Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle

// 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)

#ifndef BOOST_GEOMETRY_STRATEGY_GEOGRAPHIC_EXPAND_SEGMENT_HPP
#define BOOST_GEOMETRY_STRATEGY_GEOGRAPHIC_EXPAND_SEGMENT_HPP

#include <cstddef>
#include <functional>

#include <boost/geometry/core/access.hpp>
#include <boost/geometry/core/tags.hpp>

#include <boost/geometry/algorithms/detail/envelope/box.hpp>
#include <boost/geometry/algorithms/detail/envelope/range_of_boxes.hpp>
#include <boost/geometry/algorithms/detail/envelope/segment.hpp>

#include <boost/geometry/srs/spheroid.hpp>

#include <boost/geometry/strategy/expand.hpp>
#include <boost/geometry/strategy/geographic/envelope_segment.hpp>
#include <boost/geometry/strategies/geographic/parameters.hpp>
#include <boost/geometry/strategy/spherical/expand_segment.hpp>

namespace boost { namespace geometry
{

namespace strategy { namespace expand
{

template
<
    typename FormulaPolicy = strategy::andoyer,
    typename Spheroid = geometry::srs::spheroid<double>,
    typename CalculationType = void
>
class geographic_segment
{
public:
    inline geographic_segment()
        : m_envelope_strategy()
    {}

    explicit inline geographic_segment(Spheroid const& spheroid)
        : m_envelope_strategy(spheroid)
    {}

    template <typename Box, typename Segment>
    inline void apply(Box& box, Segment const& segment) const
    {
        detail::segment_on_spheroid::apply(box, segment, m_envelope_strategy);
    }

    Spheroid model() const
    {
        return m_envelope_strategy.model();
    }

private:
    strategy::envelope::geographic_segment
        <
            FormulaPolicy, Spheroid, CalculationType
        > m_envelope_strategy;
};

#ifndef DOXYGEN_NO_STRATEGY_SPECIALIZATIONS

namespace services
{

template <typename CalculationType>
struct default_strategy<segment_tag, geographic_tag, CalculationType>
{
    typedef geographic_segment
        <
            strategy::andoyer,
            geometry::srs::spheroid<double>,
            CalculationType
        > type;
};

} // namespace services

#endif // DOXYGEN_NO_STRATEGY_SPECIALIZATIONS

}} // namespace strategy::expand

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_STRATEGY_GEOGRAPHIC_EXPAND_SEGMENT_HPP


//==============================================================================
//         Copyright 2003 - 2011   LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2011   LRI    UMR 8623 CNRS/Univ Paris Sud XI
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
#ifndef BOOST_SIMD_SDK_SIMD_DETAILS_MAKE_SOA_HPP_INCLUDED
#define BOOST_SIMD_SDK_SIMD_DETAILS_MAKE_SOA_HPP_INCLUDED

#include <boost/simd/sdk/simd/meta/vector_of.hpp>
#include <boost/simd/sdk/meta/cardinal_as.hpp>

namespace boost { namespace simd
{
  namespace details
  {
    // Compute maximum cardinal in a sequence
    template<class T, class X>
    struct  max_cardinal_
          : meta::cardinal_as< typename max_value< T, meta::cardinal_as<mpl::_, X> >::type, X >::type
    {};

    // Build the sequence of proper native
    template<class T, class X>
    struct make_soa
    {
      template<class Sig>
      struct result;

      template<class This, class U>
      struct result<This(U)>
      {
        typedef typename meta::vector_of<U, max_cardinal_<T, X>::value>::type type;
      };
    };
  }
} }

#endif


// Copyright (c) 2021 Graphcore Ltd. All rights reserved.

#include <boost/program_options.hpp>
#include <poplar/Engine.hpp>
#include <poplar/OptionFlags.hpp>
#include <poplibs_support/Algorithm.hpp>
#include <poplibs_support/TestDevice.hpp>
#include <poplibs_test/Util.hpp>
#include <poplin/codelets.hpp>
#include <popops/ElementWise.hpp>
#include <popops/Expr.hpp>
#include <popops/codelets.hpp>
#include <poputil/TileMapping.hpp>
#include <poputil/exceptions.hpp>

#include <sstream>
#include <utility>
#include <vector>

using namespace poplar;
using namespace poplar::program;
using namespace poplibs_support;
using namespace popops;
using namespace popops::expr;
using namespace poplibs_test::util;

std::pair<std::vector<double>, std::vector<double>>
executeExpr(const DeviceType &deviceType, const Expr &expression,
            const Type &dType, bool inPlace, unsigned length, unsigned slices,
            bool profile) {
  auto device = createTestDevice(deviceType, 1, 1);
  const auto &target = device.getTarget();
  Graph g(device.getTarget());
  addCodelets(g);

  // Padding to use to either check for overwrites or create a gap between
  // slices. Padding will result in later slices being aligned to 4 element
  // (At least 8 byte) boundaries to avoid copies and leave in place ops where
  // they are allocated
  const auto paddingSize = (1 << ceilLog2(std::max(length + 1, 3u))) - length;
  const auto dataLength = (length + paddingSize) * slices;
  std::vector<double> hostInA(dataLength);
  std::vector<double> hostInC(dataLength);
  std::vector<double> hostInB(dataLength);
  std::vector<double> hostOutOpt(inPlace ? dataLength : length * slices);
  std::vector<double> hostOutNoOpt(inPlace ? dataLength : length * slices);

  for (unsigned i = 0; i < hostInA.size(); i++) {
    double value = dType != UNSIGNED_INT && (i % 2) ? -1.0 * i : i;
    hostInA[i] = value;
    hostInB[i] = 2 * (hostInA.size() - i);
    hostInC[i] = value;
  }

  auto a = g.addVariable(dType, {dataLength}, "inputa");
  g.setTileMapping(a, 0);
  auto b = g.addVariable(dType, {dataLength}, "inputb");
  g.setTileMapping(b, 0);
  auto c = g.addVariable(dType, {dataLength}, "inputc");
  g.setTileMapping(c, 0);

  std::vector<Interval> sliceIntervals(slices);
  for (unsigned i = 0; i < slices; i++) {
    sliceIntervals[i] = {i * (length + paddingSize),
                         i * (length + paddingSize) + length};
  }
  auto aSlices = concat(a.slices(sliceIntervals));
  auto bSlices = concat(b.slices(sliceIntervals));
  auto cSlices = concat(c.slices(sliceIntervals));

  std::vector<std::pair<std::string, char *>> tmap;
  std::unique_ptr<char[]> rawA, rawB, rawC, rawOutOpt, rawOutNoOpt;
  program::Sequence uploadProg, downloadProg;
  rawA =
      allocateHostMemoryForTensor(a, "rawA", g, uploadProg, downloadProg, tmap);
  rawB =
      allocateHostMemoryForTensor(b, "rawB", g, uploadProg, downloadProg, tmap);
  rawC =
      allocateHostMemoryForTensor(c, "rawC", g, uploadProg, downloadProg, tmap);

  copy(target, hostInA, dType, rawA.get());
  copy(target, hostInB, dType, rawB.get());
  copy(target, hostInC, dType, rawC.get());
  OptionFlags enableGen = {{"enableGenerateCodelet", "true"}};
  OptionFlags disableGen = {{"enableGenerateCodelet", "false"}};

  Sequence prog;
  Tensor tOpt;
  Tensor tNoOpt;
  if (!inPlace) {
    tOpt = popops::map(g, expression, {aSlices, bSlices}, prog, "testEnable",
                       enableGen);
    tNoOpt = popops::map(g, expression, {cSlices, bSlices}, prog, "testDisable",
                         disableGen);
  } else {
    popops::mapInPlace(g, expression, {aSlices, bSlices}, prog, "testEnable",
                       enableGen);
    popops::mapInPlace(g, expression, {cSlices, bSlices}, prog, "testDisable",
                       disableGen);
  }
  if (!inPlace) {
    rawOutOpt = allocateHostMemoryForTensor(tOpt, "rawOpt", g, uploadProg,
                                            downloadProg, tmap);
    rawOutNoOpt = allocateHostMemoryForTensor(tNoOpt, "rawNoOpt", g, uploadProg,
                                              downloadProg, tmap);
  }
  Sequence controlProg({uploadProg, prog, downloadProg});

  OptionFlags engineOptions;
  if (profile) {
    engineOptions.set("debug.instrumentCompute", "true");
  }
  Engine e(g, controlProg, engineOptions);
  attachStreams(e, tmap);
  device.bind([&](const Device &d) {
    e.load(d);
    e.run();
  });

  if (deviceType != DeviceType::Cpu && profile) {
    e.printProfileSummary(std::cout,
                          OptionFlags{{"showExecutionSteps", "true"}});
  }
  if (inPlace) {
    copy(target, dType, rawA.get(), hostOutOpt);
    copy(target, dType, rawC.get(), hostOutNoOpt);
  } else {
    copy(target, dType, rawOutOpt.get(), hostOutOpt);
    copy(target, dType, rawOutNoOpt.get(), hostOutNoOpt);
  }
  return std::make_pair(hostOutOpt, hostOutNoOpt);
}

int main(int argc, char **argv) {
  namespace po = boost::program_options;

  poplar::Type dataType;
  bool inPlace;
  bool profile = false;
  unsigned length, slices = 1;
  bool vectorizableExpression = true;
  DeviceType deviceType;

  po::options_description desc("Options");
  // clang-format off
  desc.add_options() ("help", "Print help")
    ("device-type",
     po::value<DeviceType>(&deviceType)->required(),
     "Device Type")
    ("length",
     po::value<unsigned>(&length)->required(),
     "Data length")
    ("slices",
     po::value<unsigned>(&slices)->default_value(slices),
     "Number of slices to operate on")
    ("data-type",
     po::value<poplar::Type>(&dataType)->required(),
     "Data Type: unsigned, int")
    ("in-place",
     po::value<bool>(&inPlace)->required(),
     "operation is inplace")
    ("vectorizable-expression",
     po::value<bool>(&vectorizableExpression)
        ->default_value(vectorizableExpression),
     "Use a vectorizable expression that should produce a MultiVertex")
   ("profile",
     po::value<bool>(&profile)->default_value(profile),
     "provide profile output")
    ;
  // clang-format on

  po::variables_map vm;
  try {
    po::store(po::parse_command_line(argc, argv, desc), vm);
    if (vm.count("help")) {
      std::cout << desc << "\n\n";
      return 1;
    }
    po::notify(vm);
  } catch (std::exception &e) {
    std::cerr << "error: " << e.what() << "\n";
    return 1;
  }

  const auto vExpr = Add(Sub(Add(_1, _2), _1), _2);
  // Due to bool as a
  const auto expr = Select(Mul(Const(0.0f), _1), _1, Lt(_1, Const(0.0f)));

  auto [opt, noOpt] = vectorizableExpression
                          ? executeExpr(deviceType, vExpr, dataType, inPlace,
                                        length, slices, profile)
                          : executeExpr(deviceType, expr, dataType, inPlace,
                                        length, slices, profile);
  for (unsigned i = 0; i != opt.size(); ++i) {
    if (opt[i] != noOpt[i]) {
      std::cerr << "mismatch at position " << i << " expected " << noOpt[i];
      std::cerr << " actual " << opt[i];
      return 1;
    }
  }
  return 0;
}


//
//  Slip_Weakening_reg.hpp
//  hybrid_fem_bie
//
//  Created by Max on 2/8/18.
//
//

#ifndef Slip_Weakening_reg_hpp
#define Slip_Weakening_reg_hpp

#include <stdio.h>
#include <iostream>
#include <Eigen/Eigen>
#include "maplocal.hpp"

void Slip_Weakening_reg(VectorXd &M_global_vec, ArrayXi &top_surf_index, ArrayXi &bot_surf_index, VectorXd &fe_global, double dt, double dx,double dy, int nx, ArrayXd &delt_v_n, ArrayXd &delt_u_n, ArrayXd &T_0, ArrayXd &tau_s, double mu_s, double mu_d, double Dc, int Ndofn, double M, VectorXd &F_fault, ArrayXd &T_c);
#endif /* Slip_Weakening_reg_hpp */


#include <merely3d/app.hpp>
#include <merely3d/window.hpp>
#include <merely3d/camera_controller.hpp>

#include <Eigen/Geometry>

using merely3d::Window;
using merely3d::WindowBuilder;
using merely3d::Frame;
using merely3d::Material;
using merely3d::Color;
using merely3d::Line;
using merely3d::EventHandler;
using merely3d::CameraController;

using merely3d::renderable;
using merely3d::Rectangle;
using merely3d::Box;
using merely3d::Sphere;
using merely3d::Particle;

using Eigen::Vector2f;
using Eigen::Vector3f;
using Eigen::Quaternionf;
using Eigen::AngleAxisf;

int main()
{
    // Constructing the app first is essential: it makes sure that
    // GLFW is set up properly. Note that as an alternative, you can call
    // glfw init/terminate yourself directly, but you must be careful that
    // any windows are destroyed before calling terminate(). App automatically
    // takes care of this as long as it outlives any windows.
    merely3d::App app;

    {
        auto window = WindowBuilder()
                .dimensions(1024, 768)
                .title("Hello merely3d!")
                .multisampling(8)
                .build();

        window.camera().look_in(Vector3f(1.0, 0.0, -1), Vector3f(0.0, 0.0, 1.0));
        window.camera().set_position(Vector3f(-1.0, 0.0, 3.0));

        window.add_event_handler(std::shared_ptr<EventHandler>(new CameraController));

        const size_t num_particles_x = 10;
        const size_t num_particles_y = 10;
        const size_t num_particles_z = 10;

        while (!window.should_close())
        {
            window.render_frame([&] (Frame & frame)
            {
                for (size_t i = 0; i < num_particles_x; ++i)
                {
                    for (size_t j = 0; j < num_particles_y; ++j)
                    {
                        for (size_t k = 0; k < num_particles_z; ++k)
                        {
                            const auto x = static_cast<float>(i) / 2.0;
                            const auto y = static_cast<float>(j) / 2.0;
                            const auto z = static_cast<float>(k) / 2.0;
                            frame.draw_particle(Particle()
                                                        .with_position(Vector3f(x, y, z))
                                                        .with_radius(0.2)
                                                        .with_color(Color(105.0/255.0, 155.0/255.0, 234/255.0)));
                        }
                    }
                }

                frame.draw_line(Line(Eigen::Vector3f(0.0, 0.0, 0.0), Eigen::Vector3f(0.0, 0.0, 1.0)));
            });
        }
    }

    return 0;
}


/* -*- 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 */


//////////////////////////////////////////////////////////////////////////////
//
// (C) Copyright Alejandro Cabrera 2011.
// 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)
//
// See http://www.boost.org/libs/bloom_filter for documentation.
//
//////////////////////////////////////////////////////////////////////////////

#define BOOST_TEST_DYN_LINK 1
#define BOOST_TEST_MODULE "Boost Dynamic Bloom Filter" 1
#include <iostream>

#include <boost/bloom_filter/dynamic_bloom_filter.hpp>
#include <boost/test/unit_test.hpp>
#include <boost/test/floating_point_comparison.hpp>

using boost::bloom_filters::dynamic_bloom_filter;
using boost::bloom_filters::boost_hash;
using boost::bloom_filters::detail::incompatible_size_exception;

BOOST_AUTO_TEST_CASE(defaultConstructor) {
  typedef boost::mpl::vector<
    boost_hash<int, 13>,
    boost_hash<int, 17>,
    boost_hash<int, 19> > BoostHashFunctions;

  dynamic_bloom_filter<int> bloom1;
  dynamic_bloom_filter<int, BoostHashFunctions> bloom2;
}

BOOST_AUTO_TEST_CASE(rangeConstructor) {
  int elems[5] = {1,2,3,4,5};
  dynamic_bloom_filter<int> bloom(elems, elems+5);

  BOOST_CHECK_EQUAL(bloom.count(), 5ul);
}

BOOST_AUTO_TEST_CASE(copyConstructor) {
  int elems[5] = {1,2,3,4,5};
  dynamic_bloom_filter<int> bloom1(elems, elems+5);
  dynamic_bloom_filter<int> bloom2(bloom1);

  BOOST_CHECK_EQUAL(bloom1.count(), bloom2.count());
}

BOOST_AUTO_TEST_CASE(assignment)
{
  dynamic_bloom_filter<int> bloom1(200);
  dynamic_bloom_filter<int> bloom2(200);

  for (size_t i = 0; i < 200; ++i) {
    bloom1.insert(i);
    BOOST_CHECK_EQUAL(bloom1.probably_contains(i), true);
  }

  bloom2 = bloom1;

  for (size_t i = 0; i < 200; ++i) {
    BOOST_CHECK_EQUAL(bloom2.probably_contains(i), true);
  }
}

BOOST_AUTO_TEST_CASE(bit_capacity) {
  dynamic_bloom_filter<size_t> bloom_8(8);
  dynamic_bloom_filter<size_t> bloom_256(256);
  dynamic_bloom_filter<size_t> bloom_2048(2048);

  BOOST_CHECK_EQUAL(bloom_8.bit_capacity(), 8ul);
  BOOST_CHECK_EQUAL(bloom_256.bit_capacity(), 256ul);
  BOOST_CHECK_EQUAL(bloom_2048.bit_capacity(), 2048ul);
}

BOOST_AUTO_TEST_CASE(empty) {
  dynamic_bloom_filter<size_t> bloom(8);

  BOOST_CHECK_EQUAL(bloom.empty(), true);
  bloom.insert(1);
  BOOST_CHECK_EQUAL(bloom.empty(), false);
  bloom.clear();
  BOOST_CHECK_EQUAL(bloom.empty(), true);
}

BOOST_AUTO_TEST_CASE(numHashFunctions) {
  dynamic_bloom_filter<size_t> bloom_1;
  dynamic_bloom_filter<size_t, boost::mpl::vector<
    boost_hash<size_t, 1>,
    boost_hash<size_t, 2> > > bloom_2;
  dynamic_bloom_filter<size_t, boost::mpl::vector<
    boost_hash<size_t, 1>,
    boost_hash<size_t, 2>,
    boost_hash<size_t, 3>,
    boost_hash<size_t, 4>,
    boost_hash<size_t, 5>,
    boost_hash<size_t, 6>,
    boost_hash<size_t, 7> > > bloom_7;

  BOOST_CHECK_EQUAL(bloom_1.num_hash_functions(), 1ul);
  BOOST_CHECK_EQUAL(bloom_2.num_hash_functions(), 2ul);
  BOOST_CHECK_EQUAL(bloom_7.num_hash_functions(), 7ul);
}

BOOST_AUTO_TEST_CASE(falsePositiveRate) {
  dynamic_bloom_filter<size_t> bloom(64);

  BOOST_CHECK_EQUAL(bloom.false_positive_rate(), 0.0);

  bloom.insert(1);
  BOOST_CHECK_CLOSE(bloom.false_positive_rate(), 0.015504, .01);

  bloom.insert(2);
  BOOST_CHECK_CLOSE(bloom.false_positive_rate(), 0.030768, .01);

  bloom.insert(3);
  BOOST_CHECK_CLOSE(bloom.false_positive_rate(), 0.045794, .01);

  bloom.insert(4);
  BOOST_CHECK_CLOSE(bloom.false_positive_rate(), 0.060588, .01);

  bloom.insert(5);
  BOOST_CHECK_CLOSE(bloom.false_positive_rate(), 0.075151, .01);

  bloom.insert(6);
  BOOST_CHECK_CLOSE(bloom.false_positive_rate(), 0.089491, .01);

  for (size_t i = 7; i < 5000; ++i)
    bloom.insert(i);

  BOOST_CHECK_GE(bloom.false_positive_rate(), 0.6);
  BOOST_CHECK_LE(bloom.false_positive_rate(), 1.0);
}

BOOST_AUTO_TEST_CASE(probably_contains) {
  dynamic_bloom_filter<size_t> bloom(8);

  bloom.insert(1);
  BOOST_CHECK_EQUAL(bloom.probably_contains(1), true);
  BOOST_CHECK_LE(bloom.count(), 3ul);
  BOOST_CHECK_GE(bloom.count(), 1ul);
}

BOOST_AUTO_TEST_CASE(doesNotContain) {
  dynamic_bloom_filter<size_t> bloom(8);

  BOOST_CHECK_EQUAL(bloom.probably_contains(1), false);
}

BOOST_AUTO_TEST_CASE(insertNoFalseNegatives) {
  dynamic_bloom_filter<size_t> bloom(2048);

  for (size_t i = 0; i < 100; ++i) {
    bloom.insert(i);
    BOOST_CHECK_EQUAL(bloom.probably_contains(i), true);
  }
}

BOOST_AUTO_TEST_CASE(rangeInsert) {
  int elems[5] = {1,2,3,4,5};
  dynamic_bloom_filter<size_t> bloom(8);

  bloom.insert(elems, elems+5);
  BOOST_CHECK_EQUAL(bloom.count(), 5ul);
}

BOOST_AUTO_TEST_CASE(clear) {
  dynamic_bloom_filter<size_t> bloom(8);

  for (size_t i = 0; i < 1000; ++i)
    bloom.insert(i);

  bloom.clear();
  BOOST_CHECK_EQUAL(bloom.probably_contains(1), false);
  BOOST_CHECK_EQUAL(bloom.count(), 0ul);
}

struct SwapFixture {
  SwapFixture()
    : bloom1(10), bloom2(20)
  {
    for (size_t i = 0; i < 5; ++i)
      elems[i] = i+1;

    bloom1.insert(elems, elems+2);
    bloom2.insert(elems+2, elems+5);
  }

  dynamic_bloom_filter<size_t> bloom1;
  dynamic_bloom_filter<size_t> bloom2;
  size_t elems[5];
};

BOOST_FIXTURE_TEST_CASE(memberSwap, SwapFixture) {
  bloom1.swap(bloom2);

  BOOST_CHECK_EQUAL(bloom1.count(), 3ul);
  BOOST_CHECK_EQUAL(bloom1.bit_capacity(), 20ul);
  BOOST_CHECK_EQUAL(bloom2.count(), 2ul);
  BOOST_CHECK_EQUAL(bloom2.bit_capacity(), 10ul);
}

BOOST_FIXTURE_TEST_CASE(globalSwap, SwapFixture) {
  swap(bloom1, bloom2);

  BOOST_CHECK_EQUAL(bloom1.count(), 3ul);
  BOOST_CHECK_EQUAL(bloom2.count(), 2ul);
}

struct PairwiseOpsFixture {
  PairwiseOpsFixture() :
    bloom1(100), bloom2(100), bloom_result(100)
  {
  }

  dynamic_bloom_filter<size_t> bloom1;
  dynamic_bloom_filter<size_t> bloom2;
  dynamic_bloom_filter<size_t> bloom_result;
};

BOOST_FIXTURE_TEST_CASE(testUnion, PairwiseOpsFixture) {
  for (size_t i = 0; i < 50; ++i)
    bloom1.insert(i);

  for (size_t i = 50; i < 100; ++i)
    bloom2.insert(i);

  bloom_result = bloom1 | bloom2;

  for (size_t i = 0; i < 100; ++i)
    BOOST_CHECK_EQUAL(bloom_result.probably_contains(i), true);

  BOOST_CHECK_GE(bloom_result.count(), bloom1.count());
  BOOST_CHECK_GE(bloom_result.count(), bloom2.count());
}

BOOST_FIXTURE_TEST_CASE(testUnionAssign, PairwiseOpsFixture) {
  for (size_t i = 0; i < 100; ++i)
    bloom1.insert(i);

  bloom_result |= bloom1;

  for (size_t i = 0; i < 100; ++i)
    BOOST_CHECK_EQUAL(bloom_result.probably_contains(i), true);

  BOOST_CHECK_EQUAL(bloom_result.count(), bloom1.count());
}

BOOST_FIXTURE_TEST_CASE(testIntersect, PairwiseOpsFixture) {
  // overlap at 50
  for (size_t i = 0; i < 51; ++i)
    bloom1.insert(i);

  for (size_t i = 50; i < 100; ++i)
    bloom2.insert(i);

  bloom_result = bloom1 & bloom2;

  BOOST_CHECK_LE(bloom_result.count(), bloom1.count());
  BOOST_CHECK_LE(bloom_result.count(), bloom2.count());
  BOOST_CHECK_LE(bloom_result.count(), 1ul);
  BOOST_CHECK_EQUAL(bloom_result.probably_contains(50), true);
}

BOOST_FIXTURE_TEST_CASE(testIntersectAssign, PairwiseOpsFixture) {
  for (size_t i = 0; i < 100; ++i)
    bloom1.insert(i);

  bloom_result &= bloom1;

  for (size_t i = 0; i < 100; ++i)
    BOOST_CHECK_EQUAL(bloom_result.probably_contains(i), false);
}

BOOST_FIXTURE_TEST_CASE(equalityOperator, PairwiseOpsFixture) {
  BOOST_CHECK_EQUAL(bloom1 == bloom2, true);
  bloom1.insert(1);
  BOOST_CHECK_EQUAL(bloom1 == bloom2, false);
  bloom2.insert(1);
  BOOST_CHECK_EQUAL(bloom1 == bloom2, true);
}

BOOST_FIXTURE_TEST_CASE(inequalityOperator, PairwiseOpsFixture) {
  BOOST_CHECK_EQUAL(bloom1 != bloom2, false);
  bloom1.insert(1);
  BOOST_CHECK_EQUAL(bloom1 != bloom2, true);
  bloom2.insert(1);
  BOOST_CHECK_EQUAL(bloom1 != bloom2, false);
}

struct IncompatibleSizeFixture {
  IncompatibleSizeFixture() :
    bloom1(100), bloom2(101), exception_thrown(false)
  {}

  dynamic_bloom_filter<size_t> bloom1;
  dynamic_bloom_filter<size_t> bloom2;
  dynamic_bloom_filter<size_t> bloom3;
  bool exception_thrown;
};

BOOST_FIXTURE_TEST_CASE(_intersectException, IncompatibleSizeFixture) {
  try {
    bloom3 = bloom1 & bloom2;
  }
  catch (incompatible_size_exception e) {
    exception_thrown = true;
  }

  BOOST_CHECK_EQUAL(exception_thrown, true);
}

BOOST_FIXTURE_TEST_CASE(_unionException, IncompatibleSizeFixture) {
  try {
    bloom3 = bloom1 | bloom2;
  }
  catch (incompatible_size_exception e) {
    exception_thrown = true;
  }

  BOOST_CHECK_EQUAL(exception_thrown, true);
}

BOOST_FIXTURE_TEST_CASE(_equalityException, IncompatibleSizeFixture) {
  try {
    if (bloom1 == bloom2)
      exception_thrown = false;
  }
  catch (incompatible_size_exception e) {
    exception_thrown = true;
  }

  BOOST_CHECK_EQUAL(exception_thrown, true);
}