// EnergyCorrelator Package
// Questions/Comments? Email the authors:
// larkoski@mit.edu, lnecib@mit.edu,
// gavin.salam@cern.ch jthaler@jthaler.net
//
// Copyright (c) 2013-2016
// Andrew Larkoski, Lina Necib, Gavin Salam, and Jesse Thaler
//
// $Id$
//----------------------------------------------------------------------
// This file is part of FastJet contrib.
//
// It 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 2 of the License, or (at
// your option) any later version.
//
// It 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 this code. If not, see .
//----------------------------------------------------------------------
#include "EnergyCorrelator.hh"
#include
#include
using namespace std;
FASTJET_BEGIN_NAMESPACE // defined in fastjet/internal/base.hh
namespace contrib {
double EnergyCorrelator::result(const PseudoJet& jet) const {
// if jet does not have constituents, throw error
if (!jet.has_constituents()) throw Error("EnergyCorrelator called on jet with no constituents.");
// get N = 0 case out of the way
if (_N == 0) return 1.0;
// find constituents
std::vector particles = jet.constituents();
// return zero if the number of constituents is less than _N
if (particles.size() < _N) return 0.0 ;
double answer = 0.0;
// take care of N = 1 case.
if (_N == 1) {
for (unsigned int i = 0; i < particles.size(); i++) {
answer += energy(particles[i]);
}
return answer;
}
double half_beta = _beta/2.0;
// take care of N = 2 case.
if (_N == 2) {
for (unsigned int i = 0; i < particles.size(); i++) {
for (unsigned int j = i + 1; j < particles.size(); j++) { //note offset by one so that angle is never called on identical pairs
answer += energy(particles[i])
* energy(particles[j])
* pow(angleSquared(particles[i],particles[j]), half_beta);
}
}
return answer;
}
// if N > 5, then throw error
if (_N > 5) {
throw Error("EnergyCorrelator is only hard coded for N = 0,1,2,3,4,5");
}
// Now deal with N = 3,4,5. Different options if storage array is used or not.
if (_strategy == storage_array) {
// For N > 2, fill static storage array to save computation time.
unsigned int nC = particles.size();
// Make energy storage
double *energyStore = new double[nC];
// Make angular storage
double **angleStore = new double*[nC];
precompute_energies_and_angles(particles, energyStore, angleStore);
// Define n_angles so it is the same function for ECFs and ECFGs
unsigned int n_angles = _N * (_N - 1) / 2;
// now do recursion
if (_N == 3) {
answer = evaluate_n3(nC, n_angles, energyStore, angleStore);
} else if (_N == 4) {
answer = evaluate_n4(nC, n_angles, energyStore, angleStore);
} else if (_N == 5) {
answer = evaluate_n5(nC, n_angles, energyStore, angleStore);
} else {
assert(_N <= 5);
}
// Deleting arrays
delete[] energyStore;
for (unsigned int i = 0; i < particles.size(); i++) {
delete[] angleStore[i];
}
delete[] angleStore;
} else if (_strategy == slow) {
if (_N == 3) {
for (unsigned int i = 0; i < particles.size(); i++) {
for (unsigned int j = i + 1; j < particles.size(); j++) {
double ans_ij = energy(particles[i])
* energy(particles[j])
* pow(angleSquared(particles[i],particles[j]), half_beta);
for (unsigned int k = j + 1; k < particles.size(); k++) {
answer += ans_ij
* energy(particles[k])
* pow(angleSquared(particles[i],particles[k]), half_beta)
* pow(angleSquared(particles[j],particles[k]), half_beta);
}
}
}
} else if (_N == 4) {
for (unsigned int i = 0; i < particles.size(); i++) {
for (unsigned int j = i + 1; j < particles.size(); j++) {
double ans_ij = energy(particles[i])
* energy(particles[j])
* pow(angleSquared(particles[i],particles[j]), half_beta);
for (unsigned int k = j + 1; k < particles.size(); k++) {
double ans_ijk = ans_ij
* energy(particles[k])
* pow(angleSquared(particles[i],particles[k]), half_beta)
* pow(angleSquared(particles[j],particles[k]), half_beta);
for (unsigned int l = k + 1; l < particles.size(); l++) {
answer += ans_ijk
* energy(particles[l])
* pow(angleSquared(particles[i],particles[l]), half_beta)
* pow(angleSquared(particles[j],particles[l]), half_beta)
* pow(angleSquared(particles[k],particles[l]), half_beta);
}
}
}
}
} else if (_N == 5) {
for (unsigned int i = 0; i < particles.size(); i++) {
for (unsigned int j = i + 1; j < particles.size(); j++) {
double ans_ij = energy(particles[i])
* energy(particles[j])
* pow(angleSquared(particles[i],particles[j]), half_beta);
for (unsigned int k = j + 1; k < particles.size(); k++) {
double ans_ijk = ans_ij
* energy(particles[k])
* pow(angleSquared(particles[i],particles[k]), half_beta)
* pow(angleSquared(particles[j],particles[k]), half_beta);
for (unsigned int l = k + 1; l < particles.size(); l++) {
double ans_ijkl = ans_ijk
* energy(particles[l])
* pow(angleSquared(particles[i],particles[l]), half_beta)
* pow(angleSquared(particles[j],particles[l]), half_beta)
* pow(angleSquared(particles[k],particles[l]), half_beta);
for (unsigned int m = l + 1; m < particles.size(); m++) {
answer += ans_ijkl
* energy(particles[m])
* pow(angleSquared(particles[i],particles[m]), half_beta)
* pow(angleSquared(particles[j],particles[m]), half_beta)
* pow(angleSquared(particles[k],particles[m]), half_beta)
* pow(angleSquared(particles[l],particles[m]), half_beta);
}
}
}
}
}
} else {
assert(_N <= 5);
}
} else {
assert(_strategy == slow || _strategy == storage_array);
}
return answer;
}
double EnergyCorrelator::energy(const PseudoJet& jet) const {
if (_measure == pt_R) {
return jet.perp();
} else if (_measure == E_theta || _measure == E_inv) {
return jet.e();
} else {
assert(_measure==pt_R || _measure==E_theta || _measure==E_inv);
return std::numeric_limits::quiet_NaN();
}
}
double EnergyCorrelator::angleSquared(const PseudoJet& jet1, const PseudoJet& jet2) const {
if (_measure == pt_R) {
return jet1.squared_distance(jet2);
} else if (_measure == E_theta) {
// doesn't seem to be a fastjet built in for this
double dot = jet1.px()*jet2.px() + jet1.py()*jet2.py() + jet1.pz()*jet2.pz();
double norm1 = jet1.px()*jet1.px() + jet1.py()*jet1.py() + jet1.pz()*jet1.pz();
double norm2 = jet2.px()*jet2.px() + jet2.py()*jet2.py() + jet2.pz()*jet2.pz();
double costheta = dot/(sqrt(norm1 * norm2));
if (costheta > 1.0) costheta = 1.0; // Need to handle case of numerical overflow
double theta = acos(costheta);
return theta*theta;
} else if (_measure == E_inv) {
if (jet1.E() < 0.0000001 || jet2.E() < 0.0000001) return 0.0;
else {
double dot4 = max(jet1.E()*jet2.E() - jet1.px()*jet2.px() - jet1.py()*jet2.py() - jet1.pz()*jet2.pz(),0.0);
return 2.0 * dot4 / jet1.E() / jet2.E();
}
} else {
assert(_measure==pt_R || _measure==E_theta || _measure==E_inv);
return std::numeric_limits::quiet_NaN();
}
}
double EnergyCorrelator::multiply_angles(double angle_list[], int n_angles, unsigned int N_total) const {
// Compute the product of the n_angles smallest angles.
// std::partial_sort could also work, but since angle_list contains
// less than 10 elements, this way is usually faster.
double product = 1;
for (int a = 0; a < n_angles; a++) {
double cur_min = angle_list[0];
int cur_min_pos = 0;
for (unsigned int b = 1; b < N_total; b++) {
if (angle_list[b] < cur_min) {
cur_min = angle_list[b];
cur_min_pos = b;
}
}
// multiply it by the next smallest
product *= cur_min;
angle_list[cur_min_pos] = INT_MAX;
}
return product;
}
void EnergyCorrelator::precompute_energies_and_angles(std::vector const &particles, double* energyStore, double** angleStore) const {
// Fill storage with energy/angle information
unsigned int nC = particles.size();
for (unsigned int i = 0; i < nC; i++) {
angleStore[i] = new double[i];
}
double half_beta = _beta/2.0;
for (unsigned int i = 0; i < particles.size(); i++) {
energyStore[i] = energy(particles[i]);
for (unsigned int j = 0; j < i; j++) {
if (half_beta == 1.0){
angleStore[i][j] = angleSquared(particles[i], particles[j]);
} else {
angleStore[i][j] = pow(angleSquared(particles[i], particles[j]), half_beta);
}
}
}
}
double EnergyCorrelator::evaluate_n3(unsigned int nC, unsigned int n_angles, double* energyStore, double** angleStore) const {
unsigned int N_total = 3;
double angle1, angle2, angle3;
double angle;
double answer = 0;
for (unsigned int i = 2; i < nC; i++) {
for (unsigned int j = 1; j < i; j++) {
double mult_energy_i_j = energyStore[i] * energyStore[j];
for (unsigned int k = 0; k < j; k++) {
angle1 = angleStore[i][j];
angle2 = angleStore[i][k];
angle3 = angleStore[j][k];
double angle_list[] = {angle1, angle2, angle3};
if (n_angles == N_total) {
angle = angle1 * angle2 * angle3;
} else {
angle = multiply_angles(angle_list, n_angles, N_total);
}
answer += mult_energy_i_j
* energyStore[k]
* angle;
}
}
}
return answer;
}
double EnergyCorrelator::evaluate_n4(unsigned int nC, unsigned int n_angles, double* energyStore, double** angleStore) const {
double answer = 0;
double angle1, angle2, angle3, angle4, angle5, angle6;
unsigned int N_total = 6;
double angle;
for (unsigned int i = 3; i < nC; i++) {
for (unsigned int j = 2; j < i; j++) {
for (unsigned int k = 1; k < j; k++) {
for (unsigned int l = 0; l < k; l++) {
angle1 = angleStore[i][j];
angle2 = angleStore[i][k];
angle3 = angleStore[i][l];
angle4 = angleStore[j][k];
angle5 = angleStore[j][l];
angle6 = angleStore[k][l];
double angle_list[] = {angle1, angle2, angle3, angle4, angle5, angle6};
if (n_angles == N_total) {
angle = angle1 * angle2 * angle3 * angle4 * angle5 * angle6;
} else {
angle = multiply_angles(angle_list, n_angles, N_total);
}
answer += energyStore[i]
* energyStore[j]
* energyStore[k]
* energyStore[l]
* angle;
}
}
}
}
return answer;
}
double EnergyCorrelator::evaluate_n5(unsigned int nC, unsigned int n_angles, double* energyStore, double** angleStore) const {
double answer = 0;
double angle1, angle2, angle3, angle4, angle5, angle6, angle7, angle8, angle9, angle10;
unsigned int N_total = 10;
double angle;
for (unsigned int i = 4; i < nC; i++) {
for (unsigned int j = 3; j < i; j++) {
for (unsigned int k = 2; k < j; k++) {
for (unsigned int l = 1; l < k; l++) {
for (unsigned int m = 0; m < l; m++) {
angle1 = angleStore[i][j];
angle2 = angleStore[i][k];
angle3 = angleStore[i][l];
angle4 = angleStore[i][m];
angle5 = angleStore[j][k];
angle6 = angleStore[j][l];
angle7 = angleStore[j][m];
angle8 = angleStore[k][l];
angle9 = angleStore[k][m];
angle10 = angleStore[l][m];
double angle_list[] = {angle1, angle2, angle3, angle4, angle5, angle6, angle7, angle8,
angle9, angle10};
angle = multiply_angles(angle_list, n_angles, N_total);
answer += energyStore[i]
* energyStore[j]
* energyStore[k]
* energyStore[l]
* energyStore[m]
* angle;
}
}
}
}
}
return answer;
}
double EnergyCorrelatorGeneralized::multiply_angles(double angle_list[], int n_angles, unsigned int N_total) const {
return _helper_correlator.multiply_angles(angle_list, n_angles, N_total);
}
void EnergyCorrelatorGeneralized::precompute_energies_and_angles(std::vector const &particles, double* energyStore, double** angleStore) const {
return _helper_correlator.precompute_energies_and_angles(particles, energyStore, angleStore);
}
double EnergyCorrelatorGeneralized::evaluate_n3(unsigned int nC, unsigned int n_angles, double* energyStore, double** angleStore) const {
return _helper_correlator.evaluate_n3(nC, n_angles, energyStore, angleStore);
}
double EnergyCorrelatorGeneralized::evaluate_n4(unsigned int nC, unsigned int n_angles, double* energyStore, double** angleStore) const {
return _helper_correlator.evaluate_n4(nC, n_angles, energyStore, angleStore);
}
double EnergyCorrelatorGeneralized::evaluate_n5(unsigned int nC, unsigned int n_angles, double* energyStore, double** angleStore) const {
return _helper_correlator.evaluate_n5(nC, n_angles, energyStore, angleStore);
}
double EnergyCorrelatorGeneralized::result(const PseudoJet& jet) const {
// if jet does not have constituents, throw error
if (!jet.has_constituents()) throw Error("EnergyCorrelator called on jet with no constituents.");
// Throw an error if N < 0
// Not needed if N is unsigned integer
//if (_N < 0 ) throw Error("N cannot be negative");
// get N = 0 case out of the way
if (_N == 0) return 1.0;
// take care of N = 1 case.
if (_N == 1) return 1.0;
// find constituents
std::vector particles = jet.constituents();
double answer = 0.0;
// return zero if the number of constituents is less than _N for the ECFG
if (particles.size() < _N) return 0.0 ;
// The normalization is the energy or pt of the jet, which is also ECF(1, beta)
double EJ = _helper_correlator.result(jet);
// The overall normalization
double norm = pow(EJ, _N);
// Find the max number of angles and throw an error if unsuitable
int N_total = int(_N*(_N-1)/2);
if (_angles > N_total) throw Error("Requested number of angles for EnergyCorrelatorGeneralized is larger than number of angles available");
if (_angles < -1) throw Error("Negative number of angles called for EnergyCorrelatorGeneralized");
double half_beta = _beta/2.0;
// take care of N = 2 case.
if (_N == 2) {
for (unsigned int i = 0; i < particles.size(); i++) {
for (unsigned int j = i + 1; j < particles.size(); j++) { //note offset by one so that angle is never called on identical pairs
answer += energy(particles[i])
* energy(particles[j])
* pow(angleSquared(particles[i],particles[j]), half_beta)/norm;
}
}
return answer;
}
// if N > 4, then throw error
if (_N > 5) {
throw Error("EnergyCorrelatorGeneralized is only hard coded for N = 0,1,2,3,4,5");
}
// Now deal with N = 3,4,5. Different options if storage array is used or not.
if (_strategy == EnergyCorrelator::storage_array) {
// For N > 2, fill static storage array to save computation time.
unsigned int nC = particles.size();
// Make energy storage
// double energyStore[nC];
double *energyStore = new double[nC];
// Make angular storage
// double angleStore[nC][nC];
double **angleStore = new double*[nC];
precompute_energies_and_angles(particles, energyStore, angleStore);
unsigned int n_angles = _angles;
if (_angles < 0) {
n_angles = N_total;
}
// now do recursion
if (_N == 3) {
answer = evaluate_n3(nC, n_angles, energyStore, angleStore) / norm;
} else if (_N == 4) {
answer = evaluate_n4(nC, n_angles, energyStore, angleStore) / norm;
} else if (_N == 5) {
answer = evaluate_n5(nC, n_angles, energyStore, angleStore) / norm;
} else {
assert(_N <= 5);
}
// Deleting arrays
delete[] energyStore;
for (unsigned int i = 0; i < particles.size(); i++) {
delete[] angleStore[i];
}
delete[] angleStore;
} else if (_strategy == EnergyCorrelator::slow) {
if (_N == 3) {
unsigned int N_total = 3;
double angle1, angle2, angle3;
double angle;
for (unsigned int i = 0; i < particles.size(); i++) {
for (unsigned int j = i + 1; j < particles.size(); j++) {
for (unsigned int k = j + 1; k < particles.size(); k++) {
angle1 = angleSquared(particles[i], particles[j]);
angle2 = angleSquared(particles[i], particles[k]);
angle3 = angleSquared(particles[j], particles[k]);
if (_angles == -1){
angle = angle1*angle2*angle3;
} else {
double angle_list[] = {angle1, angle2, angle3};
std::vector angle_vector(angle_list, angle_list + N_total);
std::sort(angle_vector.begin(), angle_vector.begin() + N_total);
angle = angle_vector[0];
for ( int l = 1; l < _angles; l++) { angle = angle * angle_vector[l]; }
}
answer += energy(particles[i])
* energy(particles[j])
* energy(particles[k])
* pow(angle, half_beta) /norm;
}
}
}
} else if (_N == 4) {
double angle1, angle2, angle3, angle4, angle5, angle6;
unsigned int N_total = 6;
double angle;
for (unsigned int i = 0; i < particles.size(); i++) {
for (unsigned int j = i + 1; j < particles.size(); j++) {
for (unsigned int k = j + 1; k < particles.size(); k++) {
for (unsigned int l = k + 1; l < particles.size(); l++) {
angle1 = angleSquared(particles[i], particles[j]);
angle2 = angleSquared(particles[i], particles[k]);
angle3 = angleSquared(particles[i], particles[l]);
angle4 = angleSquared(particles[j], particles[k]);
angle5 = angleSquared(particles[j], particles[l]);
angle6 = angleSquared(particles[k], particles[l]);
if(_angles == -1) {
angle = angle1*angle2*angle3*angle4*angle5*angle6;
} else {
double angle_list[] = {angle1, angle2, angle3, angle4, angle5, angle6};
std::vector angle_vector(angle_list, angle_list + N_total);
std::sort(angle_vector.begin(), angle_vector.begin() + N_total);
angle = angle_vector[0];
for ( int s = 1; s < _angles; s++) { angle = angle * angle_vector[s]; }
}
answer += energy(particles[i])
* energy(particles[j])
* energy(particles[k])
* energy(particles[l])
* pow(angle, half_beta)/norm;
}
}
}
}
} else if (_N == 5) {
double angle1, angle2, angle3, angle4, angle5, angle6, angle7, angle8, angle9, angle10;
unsigned int N_total = 10;
double angle;
for (unsigned int i = 0; i < particles.size(); i++) {
for (unsigned int j = i + 1; j < particles.size(); j++) {
for (unsigned int k = j + 1; k < particles.size(); k++) {
for (unsigned int l = k + 1; l < particles.size(); l++) {
for (unsigned int m = l + 1; m < particles.size(); m++) {
angle1 = angleSquared(particles[i], particles[j]);
angle2 = angleSquared(particles[i], particles[k]);
angle3 = angleSquared(particles[i], particles[l]);
angle4 = angleSquared(particles[j], particles[k]);
angle5 = angleSquared(particles[j], particles[l]);
angle6 = angleSquared(particles[k], particles[l]);
angle7 = angleSquared(particles[m], particles[i]);
angle8 = angleSquared(particles[m], particles[j]);
angle9 = angleSquared(particles[m], particles[k]);
angle10 = angleSquared(particles[m], particles[l]);
if (_angles == -1){
angle = angle1*angle2*angle3*angle4*angle5*angle6*angle7*angle8*angle9*angle10;
} else {
double angle_list[] = {angle1, angle2, angle3, angle4, angle5, angle6,
angle7, angle8, angle9, angle10};
std::vector angle_vector(angle_list, angle_list + N_total);
std::sort(angle_vector.begin(), angle_vector.begin() + N_total);
angle = angle_vector[0];
for ( int s = 1; s < _angles; s++) { angle = angle * angle_vector[s]; }
}
answer += energy(particles[i])
* energy(particles[j])
* energy(particles[k])
* energy(particles[l])
* energy(particles[m])
* pow(angle, half_beta) /norm;
}
}
}
}
}
} else {
assert(_N <= 5);
}
} else {
assert(_strategy == EnergyCorrelator::slow || _strategy == EnergyCorrelator::storage_array);
}
return answer;
}
std::vector EnergyCorrelatorGeneralized::result_all_angles(const PseudoJet& jet) const {
// if jet does not have constituents, throw error
if (!jet.has_constituents()) throw Error("EnergyCorrelator called on jet with no constituents.");
// Throw an error if N < 1
if (_N < 1 ) throw Error("N cannot be negative or zero");
// get the N = 1 case out of the way
if (_N == 1) {
std::vector ans (1, 1.0);
return ans;
}
// find constituents
std::vector particles = jet.constituents();
// return zero if the number of constituents is less than _N for the ECFG
if (particles.size() < _N) {
std::vector ans (_N, 0.0);
return ans;
}
// The normalization is the energy or pt of the jet, which is also ECF(1, beta)
double EJ = _helper_correlator.result(jet);
// The overall normalization
double norm = pow(EJ, _N);
// Find the max number of angles and throw an error if it unsuitable
int N_total = _N * (_N - 1)/2;
double half_beta = _beta/2.0;
// take care of N = 2 case.
if (_N == 2) {
double answer = 0.0;
for (unsigned int i = 0; i < particles.size(); i++) {
for (unsigned int j = i + 1; j < particles.size(); j++) { //note offset by one so that angle is never called on identical pairs
answer += energy(particles[i])
* energy(particles[j])
* pow(angleSquared(particles[i],particles[j]), half_beta)/norm;
}
}
std::vector ans(N_total, answer);
return ans;
}
// Prepare the answer vector
std::vector ans (N_total, 0.0);
// if N > 4, then throw error
if (_N > 5) {
throw Error("EnergyCorrelatorGeneralized is only hard coded for N = 0,1,2,3,4,5");
}
// Now deal with N = 3,4,5. Different options if storage array is used or not.
if (_strategy == EnergyCorrelator::storage_array) {
// For N > 2, fill static storage array to save computation time.
// Make energy storage
std::vector energyStore;
energyStore.resize(particles.size());
// Make angular storage
std::vector < std::vector > angleStore;
angleStore.resize(particles.size());
for (unsigned int i = 0; i < angleStore.size(); i++) {
angleStore[i].resize(i);
}
// Fill storage with energy/angle information
for (unsigned int i = 0; i < particles.size(); i++) {
energyStore[i] = energy(particles[i]);
for (unsigned int j = 0; j < i; j++) {
if (half_beta == 1){
angleStore[i][j] = angleSquared(particles[i], particles[j]);
} else {
angleStore[i][j] = pow(angleSquared(particles[i], particles[j]), half_beta);
}
}
}
// now do recursion
if (_N == 3) {
double angle1, angle2, angle3;
for (unsigned int i = 2; i < particles.size(); i++) {
for (unsigned int j = 1; j < i; j++) {
for (unsigned int k = 0; k < j; k++) {
angle1 = angleStore[i][j];
angle2 = angleStore[i][k];
angle3 = angleStore[j][k];
double angle_list[] = {angle1, angle2, angle3};
std::vector angle_vector(angle_list, angle_list + N_total);
std::sort(angle_vector.begin(), angle_vector.begin() + N_total);
std::vector final_angles (N_total, angle_vector[0]);
double z_product = energyStore[i] * energyStore[j] * energyStore[k]/norm;
ans[0] += z_product * final_angles[0];
for ( int s=1 ; s angle_vector(angle_list, angle_list + N_total);
std::sort(angle_vector.begin(), angle_vector.begin() + N_total);
std::vector final_angles (N_total, angle_vector[0]);
double z_product = energyStore[i] * energyStore[j] * energyStore[k] * energyStore [l]/norm;
ans[0] += z_product * final_angles[0];
for ( int s=1 ; s angle_vector(angle_list, angle_list + N_total);
std::sort(angle_vector.begin(), angle_vector.begin() + N_total);
std::vector final_angles (N_total, angle_vector[0]);
double z_product = energyStore[i] * energyStore[j] * energyStore[k]
* energyStore[l] * energyStore[m]/norm;
ans[0] += z_product * final_angles[0];
for ( int s=1 ; s angle_vector(angle_list, angle_list + N_total);
std::sort(angle_vector.begin(), angle_vector.begin() + N_total);
std::vector final_angles (N_total, angle_vector[0]);
double z_product = energy(particles[i])
* energy(particles[j])
* energy(particles[k])/norm;
ans[0] += z_product * final_angles[0];
for ( int s=1 ; s angle_vector(angle_list, angle_list + N_total);
std::sort(angle_vector.begin(), angle_vector.begin() + N_total);
std::vector final_angles (N_total, angle_vector[0]);
double z_product = energy(particles[i])
* energy(particles[j])
* energy(particles[k])
* energy(particles[l])/norm;
ans[0] += z_product * final_angles[0];
for ( int s=1 ; s angle_vector(angle_list, angle_list + N_total);
std::sort(angle_vector.begin(), angle_vector.begin() + N_total);
std::vector final_angles (N_total, angle_vector[0]);
double z_product = energy(particles[i])
* energy(particles[j])
* energy(particles[k])
* energy(particles[l])
* energy(particles[m])/norm;
ans[0] += z_product * final_angles[0];
for ( int s=1 ; s