#ifndef RIVET_PARTICLEUTILS_HH
#define RIVET_PARTICLEUTILS_HH

#include "Rivet/Particle.hh"
#include "Rivet/Tools/ParticleBaseUtils.hh"
#include "Rivet/Tools/ParticleIdUtils.hh"

// Macros to map Rivet::Particle functions to PID:: functions of the same name
#define PARTICLE_TO_PID_BOOLFN(fname) inline bool fname (const Particle& p) { return PID:: fname (p.pid()); }
#define PARTICLE_TO_PID_INTFN(fname) inline int fname (const Particle& p) { return PID:: fname (p.pid()); }
#define PARTICLE_TO_PID_DBLFN(fname) inline double fname (const Particle& p) { return PID:: fname (p.pid()); }

namespace Rivet {


  /// @defgroup particleutils Functions for Particles
  /// @{

  /// @defgroup particleutils_class Particle classifier functions
  ///@{

  /// Unbound function access to PID code
  inline int pid(const Particle& p) { return p.pid(); }

  /// Unbound function access to abs PID code
  inline int abspid(const Particle& p) { return p.abspid(); }


  /// Is this particle species charged?
  PARTICLE_TO_PID_BOOLFN(isCharged)

  /// Is this particle species neutral?
  PARTICLE_TO_PID_BOOLFN(isNeutral)


  /// Is this a neutrino?
  PARTICLE_TO_PID_BOOLFN(isNeutrino)

  /// Determine if the PID is that of a charged lepton
  PARTICLE_TO_PID_BOOLFN(isChargedLepton)
  PARTICLE_TO_PID_BOOLFN(isChLepton)

  /// Determine if the PID is that of a lepton (charged or neutral)
  PARTICLE_TO_PID_BOOLFN(isLepton)

  /// Determine if the PID is that of a photon
  PARTICLE_TO_PID_BOOLFN(isPhoton)

  /// Determine if the PID is that of an electron or positron
  PARTICLE_TO_PID_BOOLFN(isElectron)

  /// Determine if the PID is that of an muon or antimuon
  PARTICLE_TO_PID_BOOLFN(isMuon)

  /// Determine if the PID is that of an tau or antitau
  PARTICLE_TO_PID_BOOLFN(isTau)

  /// Determine if the PID is that of a hadron
  PARTICLE_TO_PID_BOOLFN(isHadron)

  /// Determine if the PID is that of a meson
  PARTICLE_TO_PID_BOOLFN(isMeson)

  /// Determine if the PID is that of a baryon
  PARTICLE_TO_PID_BOOLFN(isBaryon)

  /// Determine if the PID is that of a quark
  PARTICLE_TO_PID_BOOLFN(isQuark)

  /// Determine if the PID is that of a parton (quark or gluon)
  PARTICLE_TO_PID_BOOLFN(isParton)



  /// Determine if the PID is that of a W+
  PARTICLE_TO_PID_BOOLFN(isWplus)

  /// Determine if the PID is that of a W-
  PARTICLE_TO_PID_BOOLFN(isWminus)

  /// Determine if the PID is that of a W+-
  PARTICLE_TO_PID_BOOLFN(isW)

  /// Determine if the PID is that of a Z0
  PARTICLE_TO_PID_BOOLFN(isZ)

  /// Determine if the PID is that of an SM/lightest SUSY Higgs
  PARTICLE_TO_PID_BOOLFN(isHiggs)

  /// Determine if the PID is that of an s/sbar
  PARTICLE_TO_PID_BOOLFN(isStrange)

  /// Determine if the PID is that of a c/cbar
  PARTICLE_TO_PID_BOOLFN(isCharm)

  /// Determine if the PID is that of a b/bbar
  PARTICLE_TO_PID_BOOLFN(isBottom)

  /// Determine if the PID is that of a t/tbar
  PARTICLE_TO_PID_BOOLFN(isTop)


  /// Determine if the particle is a heavy flavour hadron or parton
  PARTICLE_TO_PID_BOOLFN(isHeavyFlavour)

  /// Determine if the PID is that of a heavy parton (c,b,t)
  PARTICLE_TO_PID_BOOLFN(isHeavyParton)

  /// Determine if the PID is that of a light parton (u,d,s)
  PARTICLE_TO_PID_BOOLFN(isLightParton)


  /// Determine if the PID is that of a heavy flavour (b or c) meson
  PARTICLE_TO_PID_BOOLFN(isHeavyMeson)

  /// Determine if the PID is that of a heavy flavour (b or c) baryon
  PARTICLE_TO_PID_BOOLFN(isHeavyBaryon)

  /// Determine if the PID is that of a heavy flavour (b or c) hadron
  PARTICLE_TO_PID_BOOLFN(isHeavyHadron)


  /// Determine if the PID is that of a light flavour (not b or c) meson
  PARTICLE_TO_PID_BOOLFN(isLightMeson)

  /// Determine if the PID is that of a light flavour (not b or c) baryon
  PARTICLE_TO_PID_BOOLFN(isLightBaryon)

  /// Determine if the PID is that of a light flavour (not b or c) hadron
  PARTICLE_TO_PID_BOOLFN(isLightHadron)


  /// Determine if the PID is that of a b-meson.
  PARTICLE_TO_PID_BOOLFN(isBottomMeson)

  /// Determine if the PID is that of a b-baryon.
  PARTICLE_TO_PID_BOOLFN(isBottomBaryon)

  /// Determine if the PID is that of a b-hadron.
  PARTICLE_TO_PID_BOOLFN(isBottomHadron)


  /// @brief Determine if the PID is that of a c-meson.
  ///
  /// Specifically, the _heaviest_ quark is a c: a B_c is a b-meson and NOT a c-meson.
  /// Charmonia (closed charm) are counted as c-mesons here.
  PARTICLE_TO_PID_BOOLFN(isCharmMeson)

  /// @brief Determine if the PID is that of a c-baryon.
  ///
  /// Specifically, the _heaviest_ quark is a c: a baryon containing a b & c
  /// is a b-baryon and NOT a c-baryon. To test for the simpler case, just use
  /// a combination of hasCharm() and isBaryon().
  PARTICLE_TO_PID_BOOLFN(isCharmBaryon)

  /// Determine if the PID is that of a c-hadron.
  PARTICLE_TO_PID_BOOLFN(isCharmHadron)


  // /// Determine if the PID is that of a strange meson
  // PARTICLE_TO_PID_BOOLFN(isStrangeMeson)

  // /// Determine if the PID is that of a strange baryon
  // PARTICLE_TO_PID_BOOLFN(isStrangeBaryon)

  // /// Determine if the PID is that of a strange hadron
  // PARTICLE_TO_PID_BOOLFN(isStrangeHadron)



  /// Is this a pomeron, odderon, or generic reggeon?
  PARTICLE_TO_PID_BOOLFN(isReggeon)

  /// Determine if the PID is that of a diquark (used in hadronization models)
  PARTICLE_TO_PID_BOOLFN(isDiquark)

  /// Determine if the PID is that of a pentaquark (hypothetical hadron)
  PARTICLE_TO_PID_BOOLFN(isPentaquark)

  /// Is this a fundamental SUSY particle?
  PARTICLE_TO_PID_BOOLFN(isSUSY)

  /// Is this an R-hadron?
  PARTICLE_TO_PID_BOOLFN(isRhadron)

  /// Is this a technicolor particle?
  PARTICLE_TO_PID_BOOLFN(isTechnicolor)

  /// Is this an excited (composite) quark or lepton?
  PARTICLE_TO_PID_BOOLFN(isExcited)

  /// Is this a Kaluza-Klein excitation?
  PARTICLE_TO_PID_BOOLFN(isKK)

  /// Is this a graviton?
  PARTICLE_TO_PID_BOOLFN(isGraviton)

  /// Is this a BSM particle (including graviton)?
  PARTICLE_TO_PID_BOOLFN(isBSM)



  /// Determine if the PID is in the generator-specific range
  PARTICLE_TO_PID_BOOLFN(isGenSpecific)

  /// Determine if the PID is that of an EW scale resonance
  PARTICLE_TO_PID_BOOLFN(isResonance)

  /// Check the PID for usability in transport codes like Geant4
  PARTICLE_TO_PID_BOOLFN(isTransportable)



  /// Does this particle contain an up quark?
  PARTICLE_TO_PID_BOOLFN(hasUp)

  /// Does this particle contain a down quark?
  PARTICLE_TO_PID_BOOLFN(hasDown)

  /// Does this particle contain a strange quark?
  PARTICLE_TO_PID_BOOLFN(hasStrange)

  /// Does this particle contain a charm quark?
  PARTICLE_TO_PID_BOOLFN(hasCharm)

  /// Does this particle contain a bottom quark?
  PARTICLE_TO_PID_BOOLFN(hasBottom)

  /// Does this particle contain a top quark?
  PARTICLE_TO_PID_BOOLFN(hasTop)



  /// jSpin returns 2J+1, where J is the total spin
  PARTICLE_TO_PID_INTFN(jSpin)

  /// sSpin returns 2S+1, where S is the spin
  PARTICLE_TO_PID_INTFN(sSpin)

  /// lSpin returns 2L+1, where L is the orbital angular momentum
  PARTICLE_TO_PID_INTFN(lSpin)


  /// Return the charge
  PARTICLE_TO_PID_DBLFN(charge)

  /// Return 3 times the charge (3 x quark charge is an int)
  PARTICLE_TO_PID_INTFN(charge3)

  /// Return the absolute charge
  PARTICLE_TO_PID_DBLFN(abscharge)

  /// Return 3 times the abs charge (3 x quark charge is an int)
  PARTICLE_TO_PID_INTFN(abscharge3)

  /// Get the atomic number (number of protons) in a nucleus/ion
  PARTICLE_TO_PID_INTFN(nuclZ)

  /// Get the atomic weight (number of nucleons) in a nucleus/ion
  PARTICLE_TO_PID_INTFN(nuclA)

  /// If this is a nucleus (ion), get nLambda
  PARTICLE_TO_PID_INTFN(nuclNlambda)

  /// @}


  /// @defgroup particleutils_pairclass Particle pair classifiers
  /// @brief
  /// @todo Make versions that work on ParticlePair?
  /// @{

  inline bool isSameSign(const Particle& a, const Particle& b) { return PID::isSameSign(a.pid(), b.pid()); }
  inline bool isOppSign(const Particle& a, const Particle& b) { return PID::isOppSign(a.pid(), b.pid()); }
  inline bool isSameFlav(const Particle& a, const Particle& b) { return PID::isSameFlav(a.pid(), b.pid()); }
  inline bool isOppFlav(const Particle& a, const Particle& b) { return PID::isOppFlav(a.pid(), b.pid()); }

  inline bool isOSSF(const Particle& a, const Particle& b) { return PID::isOSSF(a.pid(), b.pid()); }
  inline bool isSSSF(const Particle& a, const Particle& b) { return PID::isSSSF(a.pid(), b.pid()); }
  inline bool isOSOF(const Particle& a, const Particle& b) { return PID::isOSOF(a.pid(), b.pid()); }
  inline bool isSSOF(const Particle& a, const Particle& b) { return PID::isSSOF(a.pid(), b.pid()); }

  /// @}


  /// @defgroup particleutils_charge Particle charge/sign comparison functions
  /// @{

  /// @brief Return true if Particles @a a and @a b have the opposite charge sign
  /// @note Two neutrals returns false
  inline bool oppSign(const Particle& a, const Particle& b) {
    return sign(a.charge3()) == -sign(b.charge3()) && sign(a.charge3()) != ZERO;
  }

  /// Return true if Particles @a a and @a b have the same charge sign
  /// @note Two neutrals returns true
  inline bool sameSign(const Particle& a, const Particle& b) {
    return sign(a.charge3()) == sign(b.charge3());
  }

  /// Return true if Particles @a a and @a b have the exactly opposite charge
  /// @note Two neutrals returns false
  inline bool oppCharge(const Particle& a, const Particle& b) {
    return a.charge3() == -b.charge3() && a.charge3() != 0;
  }

  /// Return true if Particles @a a and @a b have the same charge (including neutral)
  /// @note Two neutrals returns true
  inline bool sameCharge(const Particle& a, const Particle& b) {
    return a.charge3() == b.charge3();
  }

  /// Return true if Particles @a a and @a b have a different (not necessarily opposite) charge
  inline bool diffCharge(const Particle& a, const Particle& b) {
    return a.charge3() != b.charge3();
  }

  /// @}



  //////////////////////////////////////



  /// @defgroup particleutils_nonpid Non-PID particle properties, via unbound functions
  /// @{

  /// @brief Determine whether a particle is the first in a decay chain to meet the function requirement
  inline bool isFirstWith(const Particle& p, const ParticleSelector& f) {
    return p.isFirstWith(f);
  }

  /// @brief Determine whether a particle is the first in a decay chain not to meet the function requirement
  inline bool isFirstWithout(const Particle& p, const ParticleSelector& f) {
    return p.isFirstWithout(f);
  }


  /// @brief Determine whether a particle is the last in a decay chain to meet the function requirement
  inline bool isLastWith(const Particle& p, const ParticleSelector& f) {
    return p.isLastWith(f);
  }

  /// @brief Determine whether a particle is the last in a decay chain not to meet the function requirement
  inline bool isLastWithout(const Particle& p, const ParticleSelector& f) {
    return p.isLastWithout(f);
  }



  /// @brief Determine whether a particle has an ancestor which meets the function requirement
  inline bool hasAncestorWith(const Particle& p, const ParticleSelector& f, bool only_physical=true) {
    return p.hasAncestorWith(f, only_physical);
  }

  /// @brief Determine whether a particle has an ancestor which doesn't meet the function requirement
  inline bool hasAncestorWithout(const Particle& p, const ParticleSelector& f, bool only_physical=true) {
    return p.hasAncestorWithout(f, only_physical);
  }


  /// @brief Determine whether a particle has a parent which meets the function requirement
  inline bool hasParentWith(const Particle& p, const ParticleSelector& f) {
    return p.hasParentWith(f);
  }

  /// @brief Determine whether a particle has a parent which doesn't meet the function requirement
  inline bool hasParentWithout(const Particle& p, const ParticleSelector& f) {
    return p.hasParentWithout(f);
  }


  /// @brief Determine whether a particle has a child which meets the function requirement
  inline bool hasChildWith(const Particle& p, const ParticleSelector& f) {
    return p.hasChildWith(f);
  }

  /// @brief Determine whether a particle has a child which doesn't meet the function requirement
  inline bool hasChildWithout(const Particle& p, const ParticleSelector& f) {
    return p.hasChildWithout(f);
  }


  /// @brief Determine whether a particle has a descendant which meets the function requirement
  inline bool hasDescendantWith(const Particle& p, const ParticleSelector& f, bool remove_duplicates=true) {
    return p.hasDescendantWith(f, remove_duplicates);
  }

  /// @brief Determine whether a particle has a descendant which doesn't meet the function requirement
  inline bool hasDescendantWithout(const Particle& p, const ParticleSelector& f, bool remove_duplicates=true) {
    return p.hasDescendantWithout(f, remove_duplicates);
  }


  /// @brief Determine whether a particle has a stable descendant which meets the function requirement
  inline bool hasStableDescendantWith(const Particle& p, const ParticleSelector& f) {
    return p.hasStableDescendantWith(f);
  }

  /// @brief Determine whether a particle has a stable descendant which doesn't meet the function requirement
  inline bool hasStableDescendantWithout(const Particle& p, const ParticleSelector& f) {
    return p.hasStableDescendantWithout(f);
  }



  /// Is this particle potentially visible in a detector?
  inline bool isVisible(const Particle& p) { return p.isVisible(); }

  /// @brief Decide if a given particle is direct, via Particle::isDirect()
  ///
  /// A "direct" particle is one directly connected to the hard process. It is a
  /// preferred alias for "prompt", since it has no confusing implications about
  /// distinguishability by timing information.
  ///
  /// The boolean arguments allow a decay lepton to be considered direct if
  /// its parent was a "real" direct lepton.
  inline bool isDirect(const Particle& p, bool allow_from_direct_tau=false, bool allow_from_direct_mu=false) {
    return p.isDirect(allow_from_direct_tau, allow_from_direct_mu);
  }

  /// @brief Decide if a given particle is prompt, via Particle::isPrompt()
  ///
  /// The boolean arguments allow a decay lepton to be considered prompt if
  /// its parent was a "real" prompt lepton.
  inline bool isPrompt(const Particle& p, bool allow_from_prompt_tau=false, bool allow_from_prompt_mu=false) {
    return p.isPrompt(allow_from_prompt_tau, allow_from_prompt_mu);
  }


  /// Decide if a given particle is stable, via Particle::isStable()
  inline bool isStable(const Particle& p) { return p.isStable(); }

  /// Decide if a given particle decays hadronically
  inline bool hasHadronicDecay(const Particle& p) {
    if (p.isStable()) return false;
    if (p.hasChildWith(isHadron)) return true;
    return false;
  }

  /// Decide if a given particle decays leptonically (decays, and no hadrons)
  inline bool hasLeptonicDecay(const Particle& p) {
    if (p.isStable()) return false;
    if (p.hasChildWith(isHadron)) return false;
    return true;
  }


  /// Check whether a given PID is found in the particle's ancestor list
  /// @deprecated Prefer hasAncestorWith
  inline bool hasAncestor(const Particle& p, PdgId pid)  { return p.hasAncestor(pid); }

  /// Determine whether the particle is from a b-hadron decay
  inline bool fromBottom(const Particle& p) { return p.fromBottom(); }

  /// @brief Determine whether the particle is from a c-hadron decay
  inline bool fromCharm(const Particle& p) { return p.fromCharm(); }

  /// @brief Determine whether the particle is from a hadron decay
  inline bool fromHadron(const Particle& p) { return p.fromHadron(); }

  /// @brief Determine whether the particle is from a tau decay
  inline bool fromTau(const Particle& p, bool prompt_taus_only=false) {
    return p.fromTau(prompt_taus_only);
  }

  /// @brief Determine whether the particle is from a prompt tau decay
  inline bool fromPromptTau(const Particle& p) { return p.fromPromptTau(); }

  // /// @brief Determine whether the particle is from a hadron or tau decay
  // DEPRECATED("Too vague: use fromHadron or fromHadronicTau")
  // inline bool fromDecay(const Particle& p) { return p.fromDecay(); }

  /// @}


  /// @defgroup particleutils_p2bool Particle classifier -> bool functors
  ///
  /// To be passed to any() or all() e.g. any(p.children(), HasPID(PID::MUON))
  /// @{

  /// Base type for Particle -> bool functors
  struct BoolParticleFunctor {
    virtual bool operator()(const Particle& p) const = 0;
    virtual ~BoolParticleFunctor() {}
  };

  /// Functor for and-combination of selector logic
  struct BoolParticleAND : public BoolParticleFunctor {
    BoolParticleAND(const std::vector<ParticleSelector>& sels) : selectors(sels) {}
    BoolParticleAND(const ParticleSelector& a, const ParticleSelector& b) : selectors({a,b}) {}
    BoolParticleAND(const ParticleSelector& a, const ParticleSelector& b, const ParticleSelector& c) : selectors({a,b,c}) {}
    bool operator()(const Particle& p) const {
      for (const ParticleSelector& sel : selectors) if (!sel(p)) return false;
      return true;
    }
    std::vector<ParticleSelector> selectors;
  };
  /// Operator syntactic sugar for AND construction
  inline BoolParticleAND operator && (const ParticleSelector& a, const ParticleSelector& b) {
    return BoolParticleAND(a, b);
  }


  /// Functor for or-combination of selector logic
  struct BoolParticleOR : public BoolParticleFunctor {
    BoolParticleOR(const std::vector<ParticleSelector>& sels) : selectors(sels) {}
    BoolParticleOR(const ParticleSelector& a, const ParticleSelector& b) : selectors({a,b}) {}
    BoolParticleOR(const ParticleSelector& a, const ParticleSelector& b, const ParticleSelector& c) : selectors({a,b,c}) {}
    bool operator()(const Particle& p) const {
      for (const ParticleSelector& sel : selectors) if (sel(p)) return true;
      return false;
    }
    std::vector<ParticleSelector> selectors;
  };
  /// Operator syntactic sugar for OR construction
  inline BoolParticleOR operator || (const ParticleSelector& a, const ParticleSelector& b) {
    return BoolParticleOR(a, b);
  }

  /// Functor for inverting selector logic
  struct BoolParticleNOT : public BoolParticleFunctor {
    BoolParticleNOT(const ParticleSelector& sel) : selector(sel) {}
    bool operator()(const Particle& p) const { return !selector(p); }
    ParticleSelector selector;
  };
  /// Operator syntactic sugar for NOT construction
  inline BoolParticleNOT operator ! (const ParticleSelector& a) {
    return BoolParticleNOT(a);
  }


  /// PID matching functor
  struct HasPID : public BoolParticleFunctor {
    HasPID(PdgId pid) : targetpids{pid} { }
    HasPID(vector<PdgId> pids) : targetpids{pids} { }
    HasPID(initializer_list<PdgId> pids) : targetpids{pids} { }
    bool operator()(const Particle& p) const { return contains(targetpids, p.pid()); }
    vector<PdgId> targetpids;
  };
  using hasPID = HasPID;

  /// |PID| matching functor
  struct HasAbsPID : public BoolParticleFunctor {
    HasAbsPID(PdgId pid) : targetapids{abs(pid)} { }
    HasAbsPID(vector<PdgId> pids) { for (PdgId pid : pids) targetapids.push_back(abs(pid)); }
    HasAbsPID(initializer_list<PdgId> pids) { for (PdgId pid : pids) targetapids.push_back(abs(pid)); }
    bool operator()(const Particle& p) const { return contains(targetapids, p.abspid()); }
    vector<PdgId> targetapids;
  };
  using hasAbsPID = HasAbsPID;


  /// Determine whether a particle is the first in a decay chain to meet the cut/function
  struct FirstParticleWith : public BoolParticleFunctor {
    FirstParticleWith(const ParticleSelector& f) : fn(f) { }
    FirstParticleWith(const Cut& c);
    bool operator()(const Particle& p) const { return isFirstWith(p, fn); }
    ParticleSelector fn;
  };
  using firstParticleWith = FirstParticleWith;

  /// Determine whether a particle is the first in a decay chain not to meet the cut/function
  struct FirstParticleWithout : public BoolParticleFunctor {
    FirstParticleWithout(const ParticleSelector& f) : fn(f) { }
    FirstParticleWithout(const Cut& c);
    bool operator()(const Particle& p) const { return isFirstWithout(p, fn); }
    ParticleSelector fn;
  };
  using firstParticleWithout = FirstParticleWithout;


  /// Determine whether a particle is the last in a decay chain to meet the cut/function
  struct LastParticleWith : public BoolParticleFunctor {
    template <typename FN>
    LastParticleWith(const FN& f) : fn(f) { }
    LastParticleWith(const Cut& c);
    bool operator()(const Particle& p) const { return isLastWith(p, fn); }
    std::function<bool(const Particle&)> fn;
  };
  using lastParticleWith = LastParticleWith;

  /// Determine whether a particle is the last in a decay chain not to meet the cut/function
  struct LastParticleWithout : public BoolParticleFunctor {
    LastParticleWithout(const ParticleSelector& f) : fn(f) { }
    LastParticleWithout(const Cut& c);
    bool operator()(const Particle& p) const { return isLastWithout(p, fn); }
    ParticleSelector fn;
  };
  using lastParticleWithout = LastParticleWithout;


  /// Determine whether a particle has an ancestor which meets the cut/function
  struct HasParticleAncestorWith : public BoolParticleFunctor {
    HasParticleAncestorWith(const ParticleSelector& f, bool only_physical=true) : fn(f), onlyphysical(only_physical) { }
    HasParticleAncestorWith(const Cut& c, bool only_physical=true);
    bool operator()(const Particle& p) const { return hasAncestorWith(p, fn, onlyphysical); }
    ParticleSelector fn;
    bool onlyphysical;
  };
  using hasParticleAncestorWith = HasParticleAncestorWith;

  /// Determine whether a particle has an ancestor which doesn't meet the cut/function
  struct HasParticleAncestorWithout : public BoolParticleFunctor {
    HasParticleAncestorWithout(const ParticleSelector& f, bool only_physical=true) : fn(f), onlyphysical(only_physical) { }
    HasParticleAncestorWithout(const Cut& c, bool only_physical=true);
    bool operator()(const Particle& p) const { return hasAncestorWithout(p, fn, onlyphysical); }
    ParticleSelector fn;
    bool onlyphysical;
  };
  using hasParticleAncestorWithout = HasParticleAncestorWithout;


  /// Determine whether a particle has an parent which meets the cut/function
  struct HasParticleParentWith : public BoolParticleFunctor {
    HasParticleParentWith(const ParticleSelector& f) : fn(f) { }
    HasParticleParentWith(const Cut& c);
    bool operator()(const Particle& p) const { return hasParentWith(p, fn); }
    ParticleSelector fn;
  };
  using hasParticleParentWith = HasParticleParentWith;

  /// Determine whether a particle has an parent which doesn't meet the cut/function
  struct HasParticleParentWithout : public BoolParticleFunctor {
    HasParticleParentWithout(const ParticleSelector& f) : fn(f) { }
    HasParticleParentWithout(const Cut& c);
    bool operator()(const Particle& p) const { return hasParentWithout(p, fn); }
    ParticleSelector fn;
  };
  using hasParticleParentWithout = HasParticleParentWithout;


  /// Determine whether a particle has a child which meets the cut/function
  struct HasParticleChildWith : public BoolParticleFunctor {
    HasParticleChildWith(const ParticleSelector& f) : fn(f) { }
    HasParticleChildWith(const Cut& c);
    bool operator()(const Particle& p) const { return hasChildWith(p, fn); }
    ParticleSelector fn;
  };
  using hasParticleChildWith = HasParticleChildWith;

  /// Determine whether a particle has a child which doesn't meet the cut/function
  struct HasParticleChildWithout : public BoolParticleFunctor {
    HasParticleChildWithout(const ParticleSelector& f) : fn(f) { }
    HasParticleChildWithout(const Cut& c);
    bool operator()(const Particle& p) const { return hasChildWithout(p, fn); }
    ParticleSelector fn;
  };
  using hasParticleChildWithout = HasParticleChildWithout;


  /// Determine whether a particle has a descendant which meets the cut/function
  struct HasParticleDescendantWith : public BoolParticleFunctor {
    HasParticleDescendantWith(const ParticleSelector& f, bool remove_duplicates=true) : fn(f), rmduplicates(remove_duplicates) { }
    HasParticleDescendantWith(const Cut& c, bool remove_duplicates=true);
    bool operator()(const Particle& p) const { return hasDescendantWith(p, fn, rmduplicates); }
    ParticleSelector fn;
    bool rmduplicates;
  };
  using hasParticleDescendantWith = HasParticleDescendantWith;

  /// Determine whether a particle has a descendant which doesn't meet the cut/function
  struct HasParticleDescendantWithout : public BoolParticleFunctor {
    HasParticleDescendantWithout(const ParticleSelector& f, bool remove_duplicates=true) : fn(f), rmduplicates(remove_duplicates) { }
    HasParticleDescendantWithout(const Cut& c, bool remove_duplicates=true);
    bool operator()(const Particle& p) const { return hasDescendantWithout(p, fn, rmduplicates); }
    ParticleSelector fn;
    bool rmduplicates;
  };
  using hasParticleDescendantWithout = HasParticleDescendantWithout;

  /// @}


  /// @defgroup particleutils_filt Unbound functions for filtering particles
  /// @{

  /// Filter a particle collection in-place to the subset that passes the supplied Cut
  Particles& ifilter_select(Particles& particles, const Cut& c);
  /// Alias for ifilter_select
  /// @deprecated Use ifilter_select
  inline Particles& ifilterBy(Particles& particles, const Cut& c) { return ifilter_select(particles, c); }
  /// New alias for ifilter_select
  inline Particles& iselect(Particles& particles, const Cut& c) { return ifilter_select(particles, c); }

  /// Filter a particle collection in-place to the subset that passes the supplied Cut
  inline Particles filter_select(const Particles& particles, const Cut& c) {
    Particles rtn = particles;
    return ifilter_select(rtn, c);
  }
  /// Alias for ifilter_select
  /// @deprecated Use filter_select
  inline Particles filterBy(const Particles& particles, const Cut& c) { return filter_select(particles, c); }
  /// New alias for ifilter_select
  inline Particles select(const Particles& particles, const Cut& c) { return filter_select(particles, c); }

  /// Filter a particle collection in-place to the subset that passes the supplied Cut
  inline Particles filter_select(const Particles& particles, const Cut& c, Particles& out) {
    out = filter_select(particles, c);
    return out;
  }
  /// Alias for ifilter_select
  /// @deprecated Use filter_select
  inline Particles filterBy(const Particles& particles, const Cut& c, Particles& out) { return filter_select(particles, c, out); }
  /// New alias for ifilter_select
  inline Particles select(const Particles& particles, const Cut& c, Particles& out) { return filter_select(particles, c, out); }


  /// Filter a particle collection in-place to the subset that fails the supplied Cut
  Particles& ifilter_discard(Particles& particles, const Cut& c);
  /// New alias for ifilter_discard
  inline Particles& idiscard(Particles& particles, const Cut& c) { return ifilter_discard(particles, c); }

  /// Filter a particle collection in-place to the subset that fails the supplied Cut
  inline Particles filter_discard(const Particles& particles, const Cut& c) {
    Particles rtn = particles;
    return ifilter_discard(rtn, c);
  }
  /// New alias for filter_discard
  inline Particles discard(const Particles& particles, const Cut& c) { return filter_discard(particles, c); }

  /// Filter a particle collection in-place to the subset that fails the supplied Cut
  inline Particles filter_discard(const Particles& particles, const Cut& c, Particles& out) {
    out = filter_discard(particles, c);
    return out;
  }
  /// New alias for filter_discard
  inline Particles discard(const Particles& particles, const Cut& c, Particles& out) { return filter_discard(particles, c, out); }


  // inline void ifilterIsolateDeltaR(Particles& particles, const FourMomenta& vecs) {
  //   ifilter_discard(particles,
  // }


  // inline Particles filterIsolateDeltaR(const Particles& particles, const FourMomenta& vecs) {
  // }

  /// @}



  /// @defgroup particleutils_pair Particle pair functions
  /// @{

  /// Get the PDG ID codes of a ParticlePair
  /// @todo Make ParticlePair a custom class instead?
  inline PdgIdPair pids(const ParticlePair& pp) {
    return make_pair(pp.first.pid(), pp.second.pid());
  }

  /// @}



  /// @defgroup particleutils_kin Operations on collections of Particle
  ///
  /// @note This can't be done on generic collections of ParticleBase -- thanks, C++ :-/
  ///@{
  namespace Kin {

    inline double sumPt(const Particles& ps) {
      return sum(ps, pT, 0.0);
    }

    inline FourMomentum sumP4(const Particles& ps) {
      return sum(ps, p4, FourMomentum());
    }

    inline Vector3 sumP3(const Particles& ps) {
      return sum(ps, p3, Vector3());
    }

    /// @todo Min dPhi, min dR?
    /// @todo Isolation routines?

  }

  // Import Kin namespace into Rivet
  using namespace Kin;


  /// Check Particle equivalence
  inline bool isSame(const Particle& a, const Particle& b) {
    return a.isSame(b);
  }

  /// @}


  /// @brief Check for pid membership in a list of particles
  /// @note if abs is true, then only abs pids are checked.
  inline bool containsPID(const Particles& parts, int id, bool abs=false) {
    if (abs) return any(parts, HasAbsPID(id));
    return any(parts, HasPID(id));
  }



  /// @brief Check whether a particle is radiative.
  /// @note A particle is considered radiative if (1) it has only one mother particle and (2) one of its siblings has the same pid as the mother particle.
  inline bool isRadiative(const Particle& part) {
    const Particles& parents = part.parents();
    if (parents.size() != 1)
      return false;

    const Particle& mother = parents[0];
    return ( part.pid() != mother.pid() ) && ( containsPID(mother.children(), mother.pid()) );
  }


  /// @brief Check whether a set of particles' decay chains can contain the requested list of pids.
  /// @note if absolute is true, then only the absolute values of pids are compared.
  /// @note if ignorephoton is true, then photons are ignored when searching for the set of particles.
  bool cascadeContains
    ( const Particles& parts
    , const vector<int>& pids
    , bool absolute
    , bool ignorephoton
    );

  /// @}

}

#endif
