Version: SMASH-2.0
collidermodus.cc
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2014-2020
3  * SMASH Team
4  *
5  * GNU General Public License (GPLv3 or later)
6  */
7 
8 #include "smash/collidermodus.h"
9 
10 #include <algorithm>
11 #include <cmath>
12 #include <cstdlib>
13 #include <cstring>
14 #include <memory>
15 #include <stdexcept>
16 #include <string>
17 #include <tuple>
18 #include <utility>
19 #include <vector>
20 
21 #include "smash/configuration.h"
22 #include "smash/customnucleus.h"
23 #include "smash/cxx14compat.h"
25 #include "smash/fourvector.h"
26 #include "smash/logging.h"
27 #include "smash/random.h"
28 
29 namespace smash {
30 static constexpr int LCollider = LogArea::Collider::id;
267  const ExperimentParameters &params) {
268  Configuration modus_cfg = modus_config["Collider"];
269  // Get the reference frame for the collision calculation.
270  if (modus_cfg.has_value({"Calculation_Frame"})) {
271  frame_ = modus_cfg.take({"Calculation_Frame"});
272  }
273 
274  // Determine whether to allow the first collisions within the same nucleus
275  if (modus_cfg.has_value({"Collisions_Within_Nucleus"})) {
276  cll_in_nucleus_ = modus_cfg.take({"Collisions_Within_Nucleus"});
277  }
278  Configuration proj_cfg = modus_cfg["Projectile"];
279  Configuration targ_cfg = modus_cfg["Target"];
280  /* Needed to check if projectile and target in customnucleus are read from
281  * the same input file.*/
282  bool same_file = false;
283  // Set up the projectile nucleus
284  if (proj_cfg.has_value({"Deformed"})) {
285  projectile_ =
286  create_deformed_nucleus(proj_cfg, params.testparticles, "projectile");
287  } else if (proj_cfg.has_value({"Custom"})) {
288  same_file = same_inputfile(proj_cfg, targ_cfg);
289  projectile_ =
290  make_unique<CustomNucleus>(proj_cfg, params.testparticles, same_file);
291  } else {
292  projectile_ = make_unique<Nucleus>(proj_cfg, params.testparticles);
293  }
294  if (projectile_->size() < 1) {
295  throw ColliderEmpty("Input Error: Projectile nucleus is empty.");
296  }
297 
298  // Set up the target nucleus
299  if (targ_cfg.has_value({"Deformed"})) {
300  target_ = create_deformed_nucleus(targ_cfg, params.testparticles, "target");
301  } else if (targ_cfg.has_value({"Custom"})) {
302  target_ =
303  make_unique<CustomNucleus>(targ_cfg, params.testparticles, same_file);
304  } else {
305  target_ = make_unique<Nucleus>(targ_cfg, params.testparticles);
306  }
307  if (target_->size() < 1) {
308  throw ColliderEmpty("Input Error: Target nucleus is empty.");
309  }
310 
311  // Get the Fermi-Motion input (off, on, frozen)
312  if (modus_cfg.has_value({"Fermi_Motion"})) {
313  // We only read the value, because it is still required by the experiment
314  // class to make sure we don't use frozen Fermi momenta with potentials.
315  fermi_motion_ = modus_cfg.read({"Fermi_Motion"});
316  }
317 
318  // Get the total nucleus-nucleus collision energy. Since there is
319  // no meaningful choice for a default energy, we require the user to
320  // give one (and only one) energy input from the available options.
321  int energy_input = 0;
322  const double mass_projec = projectile_->mass();
323  const double mass_target = target_->mass();
324  // average mass of a particle in that nucleus
325  const double mass_a =
326  projectile_->mass() / projectile_->number_of_particles();
327  const double mass_b = target_->mass() / target_->number_of_particles();
328  // Option 1: Center of mass energy.
329  if (modus_cfg.has_value({"Sqrtsnn"})) {
330  sqrt_s_NN_ = modus_cfg.take({"Sqrtsnn"});
331  // Check that input satisfies the lower bound (everything at rest).
332  if (sqrt_s_NN_ <= mass_a + mass_b) {
334  "Input Error: sqrt(s_NN) is not larger than masses:\n" +
335  std::to_string(sqrt_s_NN_) + " GeV <= " + std::to_string(mass_a) +
336  " GeV + " + std::to_string(mass_b) + " GeV.");
337  }
338  // Set the total nucleus-nucleus collision energy.
339  total_s_ = (sqrt_s_NN_ * sqrt_s_NN_ - mass_a * mass_a - mass_b * mass_b) *
340  mass_projec * mass_target / (mass_a * mass_b) +
341  mass_projec * mass_projec + mass_target * mass_target;
342  energy_input++;
343  }
344  /* Option 2: Kinetic energy per nucleon of the projectile nucleus
345  * (target at rest). */
346  if (modus_cfg.has_value({"E_Kin"})) {
347  const double e_kin = modus_cfg.take({"E_Kin"});
348  if (e_kin < 0) {
350  "Input Error: "
351  "E_Kin must be nonnegative.");
352  }
353  // Set the total nucleus-nucleus collision energy.
354  total_s_ = s_from_Ekin(e_kin * projectile_->number_of_particles(),
355  mass_projec, mass_target);
356  sqrt_s_NN_ = std::sqrt(s_from_Ekin(e_kin, mass_a, mass_b));
357  energy_input++;
358  }
359  // Option 3: Momentum of the projectile nucleus (target at rest).
360  if (modus_cfg.has_value({"P_Lab"})) {
361  const double p_lab = modus_cfg.take({"P_Lab"});
362  if (p_lab < 0) {
364  "Input Error: "
365  "P_Lab must be nonnegative.");
366  }
367  // Set the total nucleus-nucleus collision energy.
368  total_s_ = s_from_plab(p_lab * projectile_->number_of_particles(),
369  mass_projec, mass_target);
370  sqrt_s_NN_ = std::sqrt(s_from_plab(p_lab, mass_a, mass_b));
371  energy_input++;
372  }
373  if (energy_input == 0) {
374  throw std::domain_error(
375  "Input Error: Non-existent collision energy. "
376  "Please provide one of Sqrtsnn/E_Kin/P_Lab.");
377  }
378  if (energy_input > 1) {
379  throw std::domain_error(
380  "Input Error: Redundant collision energy. "
381  "Please provide only one of Sqrtsnn/E_Kin/P_Lab.");
382  }
383 
384  /* Impact parameter setting: Either "Value", "Range", "Max" or "Sample".
385  * Unspecified means 0 impact parameter.*/
386  if (modus_cfg.has_value({"Impact", "Value"})) {
387  impact_ = modus_cfg.take({"Impact", "Value"});
388  imp_min_ = impact_;
389  imp_max_ = impact_;
390  } else {
391  // If impact is not supplied by value, inspect sampling parameters:
392  if (modus_cfg.has_value({"Impact", "Sample"})) {
393  sampling_ = modus_cfg.take({"Impact", "Sample"});
394  if (sampling_ == Sampling::Custom) {
395  if (!(modus_cfg.has_value({"Impact", "Values"}) ||
396  modus_cfg.has_value({"Impact", "Yields"}))) {
397  throw std::domain_error(
398  "Input Error: Need impact parameter spectrum for custom "
399  "sampling. "
400  "Please provide Values and Yields.");
401  }
402  const std::vector<double> impacts =
403  modus_cfg.take({"Impact", "Values"});
404  const std::vector<double> yields = modus_cfg.take({"Impact", "Yields"});
405  if (impacts.size() != yields.size()) {
406  throw std::domain_error(
407  "Input Error: Need as many impact parameter values as yields. "
408  "Please make sure that Values and Yields have the same length.");
409  }
410  impact_interpolation_ = make_unique<InterpolateDataLinear<double>>(
411  InterpolateDataLinear<double>(impacts, yields));
412 
413  const auto imp_minmax =
414  std::minmax_element(impacts.begin(), impacts.end());
415  imp_min_ = *imp_minmax.first;
416  imp_max_ = *imp_minmax.second;
417  yield_max_ = *std::max_element(yields.begin(), yields.end());
418  }
419  }
420  if (modus_cfg.has_value({"Impact", "Range"})) {
421  const std::array<double, 2> range = modus_cfg.take({"Impact", "Range"});
422  imp_min_ = range[0];
423  imp_max_ = range[1];
424  }
425  if (modus_cfg.has_value({"Impact", "Max"})) {
426  imp_min_ = 0.0;
427  imp_max_ = modus_cfg.take({"Impact", "Max"});
428  }
429  }
431  // whether the direction of separation should be ramdomly smapled
433  modus_cfg.take({"Impact", "Random_Reaction_Plane"}, false);
434  // Look for user-defined initial separation between nuclei.
435  if (modus_cfg.has_value({"Initial_Distance"})) {
436  initial_z_displacement_ = modus_cfg.take({"Initial_Distance"});
437  // the displacement is half the distance (both nuclei are shifted
438  // initial_z_displacement_ away from origin)
440  }
441 }
442 
443 std::ostream &operator<<(std::ostream &out, const ColliderModus &m) {
444  return out << "-- Collider Modus:\n"
445  << "sqrt(S) (nucleus-nucleus) = "
446  << format(std::sqrt(m.total_s_), "GeV\n")
447  << "sqrt(S) (nucleon-nucleon) = " << format(m.sqrt_s_NN_, "GeV\n")
448  << "Projectile:\n"
449  << *m.projectile_ << "\nTarget:\n"
450  << *m.target_;
451 }
452 
453 std::unique_ptr<DeformedNucleus> ColliderModus::create_deformed_nucleus(
454  Configuration &nucleus_cfg, int ntest, const std::string &nucleus_type) {
455  bool auto_deform = nucleus_cfg.take({"Deformed", "Automatic"});
456  bool is_beta2 = nucleus_cfg.has_value({"Deformed", "Beta_2"}) ? true : false;
457  bool is_beta4 = nucleus_cfg.has_value({"Deformed", "Beta_4"}) ? true : false;
458  std::unique_ptr<DeformedNucleus> nucleus;
459 
460  if ((auto_deform && (!is_beta2 && !is_beta4)) ||
461  (!auto_deform && (is_beta2 && is_beta4))) {
462  nucleus = make_unique<DeformedNucleus>(nucleus_cfg, ntest, auto_deform);
463  return nucleus;
464  } else {
465  throw std::domain_error("Deformation of " + nucleus_type +
466  " nucleus not configured "
467  "properly, please check whether all necessary "
468  "parameters are set.");
469  }
470 }
471 
473  const ExperimentParameters &) {
474  sample_impact();
475 
476  logg[LCollider].info() << "Impact parameter = " << format(impact_, "fm");
477  // Populate the nuclei with appropriately distributed nucleons.
478  // If deformed, this includes rotating the nucleus.
479  projectile_->arrange_nucleons();
480  target_->arrange_nucleons();
481 
482  // Use the total mandelstam variable to get the frame-dependent velocity for
483  // each nucleus. Position a is projectile, position b is target.
484  double v_a, v_b;
485  std::tie(v_a, v_b) =
486  get_velocities(total_s_, projectile_->mass(), target_->mass());
487 
488  // If velocities are larger or equal to 1, throw an exception.
489  if (v_a >= 1.0 || v_b >= 1.0) {
490  throw std::domain_error(
491  "Found velocity equal to or larger than 1 in "
492  "ColliderModus::initial_conditions.\nConsider using "
493  "the center of velocity reference frame.");
494  }
495 
496  // Calculate the beam velocity of the projectile and the target, which will be
497  // used to calculate the beam momenta in experiment.cc
499  velocity_projectile_ = v_a;
500  velocity_target_ = v_b;
501  }
502 
503  // Generate Fermi momenta if necessary
506  // Frozen: Fermi momenta will be ignored during the propagation to
507  // avoid that the nuclei will fly apart.
508  projectile_->generate_fermi_momenta();
509  target_->generate_fermi_momenta();
511  logg[LCollider].info() << "Fermi motion is ON.";
512  } else {
513  logg[LCollider].info() << "FROZEN Fermi motion is on.";
514  }
515  } else if (fermi_motion_ == FermiMotion::Off) {
516  // No Fermi-momenta are generated in this case
517  logg[LCollider].info() << "Fermi motion is OFF.";
518  } else {
519  throw std::domain_error("Invalid Fermi_Motion input.");
520  }
521 
522  // Boost the nuclei to the appropriate velocity.
523  projectile_->boost(v_a);
524  target_->boost(v_b);
525 
526  // Shift the nuclei into starting positions. Contracted spheres with
527  // nuclear radii should touch exactly at t=0. Modus starts at negative
528  // time corresponding to additional initial displacement.
529  const double d_a = std::max(0., projectile_->get_diffusiveness());
530  const double d_b = std::max(0., target_->get_diffusiveness());
531  const double r_a = projectile_->get_nuclear_radius();
532  const double r_b = target_->get_nuclear_radius();
533  const double dz = initial_z_displacement_;
534 
535  const double simulation_time = -dz / std::abs(v_a);
536  const double proj_z = -dz - std::sqrt(1.0 - v_a * v_a) * (r_a + d_a);
537  const double targ_z =
538  +dz * std::abs(v_b / v_a) + std::sqrt(1.0 - v_b * v_b) * (r_b + d_b);
539  // rotation angle in the transverse plane
540  const double phi =
541  random_reaction_plane_ ? random::uniform(0.0, 2.0 * M_PI) : 0.0;
542 
543  projectile_->shift(proj_z, +impact_ / 2.0, simulation_time);
544  target_->shift(targ_z, -impact_ / 2.0, simulation_time);
545 
546  // Put the particles in the nuclei into code particles.
547  projectile_->copy_particles(particles);
548  target_->copy_particles(particles);
549  rotate_reaction_plane(phi, particles);
550  return simulation_time;
551 }
552 
553 void ColliderModus::rotate_reaction_plane(double phi, Particles *particles) {
554  for (ParticleData &p : *particles) {
555  ThreeVector pos = p.position().threevec();
556  ThreeVector mom = p.momentum().threevec();
557  pos.rotate_around_z(phi);
558  mom.rotate_around_z(phi);
559  p.set_3position(pos);
560  p.set_3momentum(mom);
561  }
562 }
563 
565  switch (sampling_) {
566  case Sampling::Quadratic: {
567  // quadratic sampling: Note that for bmin > bmax, this still yields
568  // the correct distribution (however canonical() = 0 is then the
569  // upper end, not the lower).
570  impact_ = std::sqrt(imp_min_ * imp_min_ +
573  } break;
574  case Sampling::Custom: {
575  // rejection sampling based on given distribution
576  assert(impact_interpolation_ != nullptr);
577  double probability_random = 1;
578  double probability = 0;
579  double b;
580  while (probability_random > probability) {
582  probability = (*impact_interpolation_)(b) / yield_max_;
583  assert(probability < 1.);
584  probability_random = random::uniform(0., 1.);
585  }
586  impact_ = b;
587  } break;
588  case Sampling::Uniform: {
589  // linear sampling. Still, min > max works fine.
591  }
592  }
593 }
594 
595 std::pair<double, double> ColliderModus::get_velocities(double s, double m_a,
596  double m_b) {
597  double v_a = 0.0;
598  double v_b = 0.0;
599  // Frame dependent calculations of velocities. Assume v_a >= 0, v_b <= 0.
600  switch (frame_) {
602  v_a = center_of_velocity_v(s, m_a, m_b);
603  v_b = -v_a;
604  break;
606  // Compute center of mass momentum.
607  double pCM = pCM_from_s(s, m_a, m_b);
608  v_a = pCM / std::sqrt(m_a * m_a + pCM * pCM);
609  v_b = -pCM / std::sqrt(m_b * m_b + pCM * pCM);
610  } break;
612  v_a = fixed_target_projectile_v(s, m_a, m_b);
613  break;
614  default:
615  throw std::domain_error(
616  "Invalid reference frame in "
617  "ColliderModus::get_velocities.");
618  }
619  return std::make_pair(v_a, v_b);
620 }
621 
622 std::string ColliderModus::custom_file_path(const std::string &file_directory,
623  const std::string &file_name) {
624  // make sure that path is correct even if the / at the end is missing
625  if (file_directory.back() == '/') {
626  return file_directory + file_name;
627  } else {
628  return file_directory + '/' + file_name;
629  }
630 }
631 
633  Configuration &targ_config) {
634  /* Check if both nuclei are custom
635  * Only check target as function is called after if statement for projectile.
636  */
637  if (!targ_config.has_value({"Custom"})) {
638  return false;
639  }
640  std::string projectile_file_directory =
641  proj_config.read({"Custom", "File_Directory"});
642  std::string target_file_directory =
643  targ_config.read({"Custom", "File_Directory"});
644  std::string projectile_file_name = proj_config.read({"Custom", "File_Name"});
645  std::string target_file_name = targ_config.read({"Custom", "File_Name"});
646  // Check if files are the same for projectile and target
647  std::string proj_path =
648  custom_file_path(projectile_file_directory, projectile_file_name);
649  std::string targ_path =
650  custom_file_path(target_file_directory, target_file_name);
651  if (proj_path == targ_path) {
652  return true;
653  } else {
654  return false;
655  }
656 }
657 
658 } // namespace smash
smash::ColliderModus::impact_interpolation_
std::unique_ptr< InterpolateDataLinear< double > > impact_interpolation_
Pointer to the impact parameter interpolation.
Definition: collidermodus.h:205
smash::ColliderModus::initial_z_displacement_
double initial_z_displacement_
Initial z-displacement of nuclei.
Definition: collidermodus.h:233
smash
Definition: action.h:24
smash::ColliderModus::imp_min_
double imp_min_
Minimum value of impact parameter.
Definition: collidermodus.h:199
smash::ColliderModus::custom_file_path
std::string custom_file_path(const std::string &file_directory, const std::string &file_name)
Creates full path string consisting of file_directory and file_name Needed to initialize a customnucl...
Definition: collidermodus.cc:622
customnucleus.h
Sampling::Quadratic
Sample from areal / quadratic distribution.
CalculationFrame::CenterOfVelocity
smash::ColliderModus::cll_in_nucleus_
bool cll_in_nucleus_
An option to accept first collisions within the same nucleus.
Definition: collidermodus.h:245
smash::ColliderModus::fermi_motion_
FermiMotion fermi_motion_
An option to include Fermi motion ("off", "on", "frozen")
Definition: collidermodus.h:241
smash::ParticleData
Definition: particledata.h:52
smash::ColliderModus::random_reaction_plane_
bool random_reaction_plane_
Whether the reaction plane should be randomized.
Definition: collidermodus.h:195
smash::Configuration::read
Value read(std::initializer_list< const char * > keys) const
Additional interface for SMASH to read configuration values without removing them.
Definition: configuration.cc:158
smash::ColliderModus::sampling_
Sampling sampling_
Method used for sampling of impact parameter.
Definition: collidermodus.h:197
smash::ColliderModus::sqrt_s_NN_
double sqrt_s_NN_
Center-of-mass energy of a nucleon-nucleon collision.
Definition: collidermodus.h:163
smash::s_from_plab
double s_from_plab(double plab, double m_P, double m_T)
Convert p_lab to Mandelstam-s for a fixed-target setup, with a projectile of mass m_P and momentum pl...
Definition: kinematics.h:224
cxx14compat.h
smash::ColliderModus::target_
std::unique_ptr< Nucleus > target_
Target.
Definition: collidermodus.h:151
smash::fixed_target_projectile_v
double fixed_target_projectile_v(double s, double ma, double mb)
Definition: kinematics.h:39
FermiMotion::Off
Don't use fermi motion.
smash::ColliderModus::same_inputfile
bool same_inputfile(Configuration &proj_config, Configuration &targ_config)
Checks if target and projectile are read from the same external file if they are both initialized as ...
Definition: collidermodus.cc:632
smash::operator<<
std::ostream & operator<<(std::ostream &out, const ActionPtr &action)
Definition: action.h:518
smash::ColliderModus::sample_impact
void sample_impact()
Sample impact parameter.
Definition: collidermodus.cc:564
experimentparameters.h
smash::s_from_Ekin
double s_from_Ekin(double e_kin, double m_P, double m_T)
Convert E_kin to Mandelstam-s for a fixed-target setup, with a projectile of mass m_P and a kinetic e...
Definition: kinematics.h:211
smash::Configuration::has_value
bool has_value(std::initializer_list< const char * > keys) const
Returns whether there is a non-empty value behind the requested keys.
Definition: configuration.cc:181
smash::ColliderModus::initial_conditions
double initial_conditions(Particles *particles, const ExperimentParameters &parameters)
Generates initial state of the particles in the system.
Definition: collidermodus.cc:472
fourvector.h
smash::ThreeVector::rotate_around_z
void rotate_around_z(double theta)
Rotate the vector around the z axis by the given angle theta.
Definition: threevector.h:308
FermiMotion::Frozen
Use fermi motion without potentials.
smash::logg
std::array< einhard::Logger<>, std::tuple_size< LogArea::AreaTuple >::value > logg
An array that stores all pre-configured Logger objects.
Definition: logging.cc:39
Sampling::Custom
Sample from custom, user-defined distribution.
smash::ColliderModus::velocity_projectile_
double velocity_projectile_
Beam velocity of the projectile.
Definition: collidermodus.h:249
smash::pCM
T pCM(const T sqrts, const T mass_a, const T mass_b) noexcept
Definition: kinematics.h:79
random.h
smash::center_of_velocity_v
double center_of_velocity_v(double s, double ma, double mb)
Definition: kinematics.h:26
smash::Configuration
Interface to the SMASH configuration files.
Definition: configuration.h:464
smash::ColliderModus::imp_max_
double imp_max_
Maximum value of impact parameter.
Definition: collidermodus.h:201
smash::ThreeVector
Definition: threevector.h:31
smash::format
FormattingHelper< T > format(const T &value, const char *unit, int width=-1, int precision=-1)
Acts as a stream modifier for std::ostream to output an object with an optional suffix string and wit...
Definition: logging.h:307
smash::ColliderModus::total_s_
double total_s_
Center-of-mass energy squared of the nucleus-nucleus collision.
Definition: collidermodus.h:157
smash::ColliderModus::frame_
CalculationFrame frame_
Reference frame for the system, as specified from config.
Definition: collidermodus.h:237
smash::LCollider
static constexpr int LCollider
Definition: collidermodus.cc:30
collidermodus.h
smash::InterpolateDataLinear< double >
CalculationFrame::FixedTarget
smash::ColliderModus::yield_max_
double yield_max_
Maximum value of yield. Needed for custom impact parameter sampling.
Definition: collidermodus.h:203
smash::Particles
Definition: particles.h:33
smash::ColliderModus::impact_
double impact_
Impact parameter.
Definition: collidermodus.h:193
smash::ColliderModus
Definition: collidermodus.h:43
smash::ColliderModus::ColliderEmpty
Definition: collidermodus.h:132
Sampling::Uniform
Sample from uniform distribution.
FermiMotion::On
Use fermi motion in combination with potentials.
smash::ColliderModus::projectile_
std::unique_ptr< Nucleus > projectile_
Projectile.
Definition: collidermodus.h:143
logging.h
configuration.h
smash::ColliderModus::velocity_target_
double velocity_target_
Beam velocity of the target.
Definition: collidermodus.h:253
smash::Configuration::take
Value take(std::initializer_list< const char * > keys)
The default interface for SMASH to read configuration values.
Definition: configuration.cc:140
smash::ExperimentParameters
Helper structure for Experiment.
Definition: experimentparameters.h:24
smash::pdg::p
constexpr int p
Proton.
Definition: pdgcode_constants.h:28
smash::random::uniform
T uniform(T min, T max)
Definition: random.h:88
CalculationFrame::CenterOfMass
smash::ColliderModus::create_deformed_nucleus
static std::unique_ptr< DeformedNucleus > create_deformed_nucleus(Configuration &nucleus_cfg, const int ntest, const std::string &nucleus_type)
Configure Deformed Nucleus.
Definition: collidermodus.cc:453
smash::ExperimentParameters::testparticles
int testparticles
Number of test particle.
Definition: experimentparameters.h:32
smash::ColliderModus::rotate_reaction_plane
void rotate_reaction_plane(double phi, Particles *particles)
Rotate the reaction plane about the angle phi.
Definition: collidermodus.cc:553
smash::ColliderModus::get_velocities
std::pair< double, double > get_velocities(double mandelstam_s, double m_a, double m_b)
Get the frame dependent velocity for each nucleus, using the current reference frame.
Definition: collidermodus.cc:595
smash::random::canonical
T canonical()
Definition: random.h:113
smash::ColliderModus::ColliderModus
ColliderModus(Configuration modus_config, const ExperimentParameters &parameters)
Constructor.
Definition: collidermodus.cc:266
smash::ModusDefault::InvalidEnergy
Definition: modusdefault.h:191
smash::pCM_from_s
T pCM_from_s(const T s, const T mass_a, const T mass_b) noexcept
Definition: kinematics.h:66