WKF!? - What the Kinematic Fit
Introduction and Background
This started as a side project to implement kinematic fitting while learning some modern template meta programming techniques. It uses strong types to define and identify variables and detector resolutions. The idea is to focus the user on the main challenge for the experimentalist: defining the detector resolutions and correlations in the covariance matrix.
Clearly defining the kinematic fitting (KF) problem in a direct and clear way was the main challenge of the interface. The genearl proceedure is as follows:
- Identify all the measured variables, N_m.
- Define the resolution (functions) of each measured variable.
- Idenitify the unmeasured variables, N_u, if there are any. (Together the
measured
- unmeasured variables furnish the set of independent variables.)
- Provide the functions to calculate each unmeasured variables. (Care must be taken if here, more on this later.)
- Define the constraints, N_c, and provide functions to calculate their values.
Let's take a toy example of 3 momentum conservation in a 2D NR elastic collision.
A1+B1(at rest) -> A2 + B2
P_A1= P_A2 + P_B2
Say we know P_A to 1%, the recoil momenta to 5%, and their angles to 10%. All momenta are measured plus the two recoil angles (in the same plane) there are N_u=0 unmeasured variables and N_m=5.
using namespace std;
using namespace wkf;
// Identify the measured variables
using P_A1_var = kinfit::Var<struct P_A1_tag>;
using P_A2_var = kinfit::Var<struct P_A2_tag>;
using Theta_A2_var = kinfit::Var<struct Theta_A2_tag>;
using P_B2_var = kinfit::Var<struct P_B2_tag>;
using Theta_B2_var = kinfit::Var<struct Theta_B2_tag>;
auto fitter = wkf::ProblemBuilder()
// Define their resolutions
.build_unmeasured([](const auto& args) { return 0.01 * get<P_A1_var>(args); },
[](const auto& args) { return 0.05 * get<P_A2_var>(args); },
[](const auto& args) { return 0.05 * get<Theta_A2_var>(args); },
[](const auto& args) { return 0.10 * get<P_B2_var>(args); },
[](const auto& args) { return 0.10 * get<Theta_B2_var>(args); })
// There are no unmeasured variables
.build_unmeasured()
// In 2 dimensions (x-z plane), Px and Pz are conserved.
.build_constraints(
[](const auto& args) {
const double Px_A2 = get<P_A2_var>(args) * sin(get<Theta_A2_var>(args));
const double Px_B2 = get<P_B2_var>(args) * sin(get<Theta_B2_var>(args));
return Px_A2 + Px_B2; // should equal 0
},
[](const auto& args) {
const double Pz_A1 = get<P_A1_var>(args);
const double Pz_A2 = get<P_A2_var>(args) * cos(get<Theta_A2_var>(args));
const double Pz_B2 = get<P_B2_var>(args) * cos(get<Theta_B2_var>(args));
return Pz_A1 - (Pz_A2 + Pz_B2); // should equal 0
});
// fitter is now fully defined!