GInX
Geodesics Integrator tool for particles in GRMHD using Adaptive Mesh Refinement using AMReX.
ParticlesContainer.hxx
Go to the documentation of this file.
1
11#ifndef PHOTONSCONTAINER_HXX
12#define PHOTONSCONTAINER_HXX
13
14// Import libraries
15#include <cctk.h>
16
17#include "AMReX_Array.H"
18#include "AMReX_CTOParallelForImpl.H"
19#include "AMReX_ParallelDescriptor.H"
21#include "Interpolator.hxx"
22#include "Utilities.hxx"
23#include "cctk_Types.h"
24#include "cctk_core.h"
25#include <AMReX_AmrParticles.H>
26#include <AMReX_MultiFab.H>
27#include <AMReX_MultiFabUtil.H>
28#include <AMReX_Particles.H>
29#include <AMReX_REAL.H>
30#include <cassert>
31#include <cctk_Arguments.h>
32#include <cctk_Parameters.h>
33#include <cmath>
34#include <fstream>
35#include <iostream>
36#include <sstream>
37#include <string>
38
39namespace GInX {
40
41// #############################################################################
42// ParticlesContainer::CLASS INITIALIZATION
43// #############################################################################
44
51template <typename StructType>
53 : public BaseParticleContainer<ParticlesContainer<StructType>,
54 StructType> {
55protected:
56 CCTK_REAL mass = 0.;
57
58public:
62 using Base =
64 StructType>;
65 using Base::Base;
66
67 ParticlesContainer(amrex::AmrCore *amr_core, const CCTK_REAL m)
68 : Base(amr_core), mass{m} {};
69
71
72 // Evolving all the particles on each container
73 void evolve(const amrex::MultiFab &lapse, const amrex::MultiFab &shift,
74 const amrex::MultiFab &metric, const amrex::MultiFab &curv,
75 const CCTK_REAL &dt, const int &lev) override;
76
77 // Given differential equation dU/dt = f(U, dU/dx; t) computes f(U, dU/dx;t)
78 AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE CCTK_ATTRIBUTE_ALWAYS_INLINE
79 amrex::GpuArray<CCTK_REAL, 7>
80 compute_rhs(const amrex::GpuArray<CCTK_REAL, 7> &u, const CCTK_REAL &t,
81 amrex::Array4<CCTK_REAL const> const &lapse,
82 amrex::Array4<CCTK_REAL const> const &shift,
83 amrex::Array4<CCTK_REAL const> const &metric,
84 amrex::Array4<CCTK_REAL const> const &K, const CCTK_REAL dt,
85 const amrex::GpuArray<double, 3> &dx, const int lev,
86 const amrex::GpuArray<double, 3> &plo);
87
88 // Computes and normalize the velocity
89 void normalize_velocity(const amrex::MultiFab &metric, const int level);
90
91 // Get the mass value
92 CCTK_INT get_mass() { return this->mass; }
93
95}; // ParticlesContainer class
96
97// ##############################################################################
98// ParticlesContainer::METHODS DECLARATION
99// ##############################################################################
100
147template <typename StructType>
148AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE CCTK_ATTRIBUTE_ALWAYS_INLINE
149 amrex::GpuArray<CCTK_REAL, 7>
151 const amrex::GpuArray<CCTK_REAL, 7> &u, const CCTK_REAL &t,
152 amrex::Array4<CCTK_REAL const> const &lapse,
153 const amrex::Array4<CCTK_REAL const> &shift,
154 const amrex::Array4<CCTK_REAL const> &metric,
155 const amrex::Array4<CCTK_REAL const> &curv, const CCTK_REAL dt,
156 const amrex::GpuArray<double, 3> &dx, const int lev,
157 const amrex::GpuArray<double, 3> &plo) {
158
159 amrex::GpuArray<CCTK_REAL, 7> rhs = {0., 0., 0., 0., 0., 0., 0.};
160
161 const long int i0 = amrex::Math::floor((u[0] - plo[0]) / dx[0]);
162 const long int j0 = amrex::Math::floor((u[1] - plo[1]) / dx[1]);
163 const long int k0 = amrex::Math::floor((u[2] - plo[2]) / dx[2]);
164
165 // Interpolate lapse & partial lapse at \vect{x}
166 CCTK_REAL lapse_x;
167 amrex::GpuArray<CCTK_REAL, 3> d_lapse_x;
168 d_interpolate_array<5>(lapse_x, d_lapse_x, lapse, i0, j0, k0, u[0], u[1],
169 u[2], dx, plo);
170
171 // Interpolate shift & partial shift at \vect{x}
172 amrex::GpuArray<CCTK_REAL, 3> shift_x;
173 amrex::GpuArray<amrex::GpuArray<CCTK_REAL, 3>, 3> d_shift_x;
174 d_interpolate_array<5>(shift_x, d_shift_x, shift, i0, j0, k0, u[0], u[1],
175 u[2], dx, plo);
176
177 // Interpolate metric & partial metric at \vect{x}
178 amrex::GpuArray<CCTK_REAL, 6> gamma_x;
179 amrex::GpuArray<amrex::GpuArray<CCTK_REAL, 6>, 3> d_gamma_x;
180 d_interpolate_array<5>(gamma_x, d_gamma_x, metric, i0, j0, k0, u[0], u[1],
181 u[2], dx, plo);
182
183 // Interpolate Curvature at \vect{x}
184 amrex::GpuArray<CCTK_REAL, 6> curv_x;
185 interpolate_array<5>(curv_x, curv, i0, j0, k0, u[0], u[1], u[2], dx, plo);
186
187 // Compute the inverse of the metric.
188 const CCTK_REAL inv_det_gamma =
189 1.0 / (gamma_x[0] * gamma_x[3] * gamma_x[5] +
190 2. * gamma_x[1] * gamma_x[2] * gamma_x[4] -
191 gamma_x[2] * gamma_x[2] * gamma_x[3] -
192 gamma_x[4] * gamma_x[4] * gamma_x[0] -
193 gamma_x[1] * gamma_x[1] * gamma_x[5]);
194
195 const amrex::GpuArray<CCTK_REAL, 6> gamma_inv_x = {
196 (gamma_x[3] * gamma_x[5] - gamma_x[4] * gamma_x[4]) * inv_det_gamma,
197 (gamma_x[4] * gamma_x[2] - gamma_x[1] * gamma_x[5]) * inv_det_gamma,
198 (gamma_x[1] * gamma_x[4] - gamma_x[2] * gamma_x[3]) * inv_det_gamma,
199 (gamma_x[0] * gamma_x[5] - gamma_x[2] * gamma_x[2]) * inv_det_gamma,
200 (gamma_x[2] * gamma_x[1] - gamma_x[0] * gamma_x[4]) * inv_det_gamma,
201 (gamma_x[0] * gamma_x[3] - gamma_x[1] * gamma_x[1]) * inv_det_gamma};
202
203 const amrex::GpuArray<CCTK_REAL, 3> V_down = {u[3], u[4], u[5]};
204
205 // Compute the upper index velocity terms.
206 const amrex::GpuArray<CCTK_REAL, 3> V_up = {
207 gamma_inv_x[0] * u[3] + gamma_inv_x[1] * u[4] + gamma_inv_x[2] * u[5],
208 gamma_inv_x[1] * u[3] + gamma_inv_x[3] * u[4] + gamma_inv_x[4] * u[5],
209 gamma_inv_x[2] * u[3] + gamma_inv_x[4] * u[4] + gamma_inv_x[5] * u[5]};
210
211 // Compute the rhs for position
212 rhs[0] = lapse_x * V_up[0] - shift_x[0];
213 rhs[1] = lapse_x * V_up[1] - shift_x[1];
214 rhs[2] = lapse_x * V_up[2] - shift_x[2];
215
216 // Compute the rhs for velocity
217 for (int i = 0; i < 3; i++) {
218 rhs[3 + i] =
219 -d_lapse_x[i] +
220 (VecVecMul(d_lapse_x, V_up) -
221 lapse_x * VecVecMul(SMatVecMul(curv_x, V_up), V_up)) *
222 V_down[i] +
223 0.5 * lapse_x * VecVecMul(SMatVecMul(d_gamma_x[i], V_up), V_up) +
224 VecVecMul(V_down, d_shift_x[i]);
225 }
226
227 // Compute the rhs for energy
228 rhs[3 + StructType::ln_E] =
229 lapse_x * VecVecMul(SMatVecMul(curv_x, V_up), V_up) -
230 VecVecMul(V_up, d_lapse_x);
231
232 return rhs;
233
234} // ParticlesContainer::compute_rhs
235
265template <typename StructType>
266void ParticlesContainer<StructType>::evolve(const amrex::MultiFab &lapse,
267 const amrex::MultiFab &shift,
268 const amrex::MultiFab &metric,
269 const amrex::MultiFab &curv,
270 const CCTK_REAL &dt, const int &lev) {
271
272 const auto plo0 = this->Geom(0).ProbLoArray();
273 const auto phi0 = this->Geom(0).ProbHiArray();
274
275 const auto dx = this->Geom(lev).CellSizeArray();
276 const auto plo = this->Geom(lev).ProbLoArray();
277
278 const CCTK_REAL boundarie_hx = phi0[0] - 0.0 * dx[0];
279 const CCTK_REAL boundarie_lx = plo0[0] + 0.0 * dx[0];
280 const CCTK_REAL boundarie_hy = phi0[1] - 0.0 * dx[1];
281 const CCTK_REAL boundarie_ly = plo0[1] + 0.0 * dx[1];
282 const CCTK_REAL boundarie_hz = phi0[2] - 0.0 * dx[2];
283 const CCTK_REAL boundarie_lz = plo0[2] + 0.0 * dx[2];
284
285 for (ParticleIterator<StructType> pti(*this, lev); pti.isValid();
286 ++pti) {
287
288 const int np = pti.numParticles();
289
290 // Get the information relate to the velocities and energy.
291 auto &attribs = pti.GetAttributes();
292 CCTK_REAL *AMREX_RESTRICT vels_x = attribs[StructType::vx].data();
293 CCTK_REAL *AMREX_RESTRICT vels_y = attribs[StructType::vy].data();
294 CCTK_REAL *AMREX_RESTRICT vels_z = attribs[StructType::vz].data();
295 CCTK_REAL *AMREX_RESTRICT ln_energy = attribs[StructType::ln_E].data();
296 auto *AMREX_RESTRICT particles = &(pti.GetArrayOfStructs()[0]);
297
298 // Get the array of each parameter.
299 auto const lapse_array = lapse.array(pti);
300 auto const shift_array = shift.array(pti);
301 auto const metric_array = metric.array(pti);
302 auto const curv_array = curv.array(pti);
303
304 // Needed for GPU
305 auto self = this;
306
307 amrex::ParallelFor(np, [=] AMREX_GPU_DEVICE(int i) noexcept {
308 const amrex::GpuArray<CCTK_REAL, 7> U = {
309 particles[i].pos(0), particles[i].pos(1), particles[i].pos(2),
310 vels_x[i], vels_y[i], vels_z[i],
311 ln_energy[i]};
312
313 bool out_of_bounds = false;
314
315 amrex::GpuArray<CCTK_REAL, 7> U_tmp = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
316
317 // f1 = rhs(u , t) for the runge kutta 4 step
318 auto k_odd =
319 self->compute_rhs(U, 0.0, lapse_array, shift_array, metric_array,
320 curv_array, dt, dx, lev, plo0);
321
322 U_tmp[0] = U[0] + 0.5 * dt * k_odd[0];
323 U_tmp[1] = U[1] + 0.5 * dt * k_odd[1];
324 U_tmp[2] = U[2] + 0.5 * dt * k_odd[2];
325 U_tmp[3] = U[3] + 0.5 * dt * k_odd[3];
326 U_tmp[4] = U[4] + 0.5 * dt * k_odd[4];
327 U_tmp[5] = U[5] + 0.5 * dt * k_odd[5];
328 U_tmp[6] = U[6] + 0.5 * dt * k_odd[6];
329
330 out_of_bounds |= (U_tmp[0] > boundarie_hx) || (U_tmp[0] < boundarie_lx);
331 out_of_bounds |= (U_tmp[1] > boundarie_hy) || (U_tmp[1] < boundarie_ly);
332 out_of_bounds |= (U_tmp[2] > boundarie_hz) || (U_tmp[2] < boundarie_lz);
333
334 if (out_of_bounds) {
335 particles[i].id() = -1;
336 return;
337 }
338
339 // f2 = rhs(u + 0.5 * dt * f1, t) for the runge kutta 4 step
340 auto k_even =
341 self->compute_rhs(U_tmp, 0.5 * dt, lapse_array, shift_array,
342 metric_array, curv_array, dt, dx, lev, plo0);
343
344 // Update particles with the f1 and f2 from RK4
345 U_tmp[0] = U[0] + 0.5 * dt * k_even[0];
346 U_tmp[1] = U[1] + 0.5 * dt * k_even[1];
347 U_tmp[2] = U[2] + 0.5 * dt * k_even[2];
348 U_tmp[3] = U[3] + 0.5 * dt * k_even[3];
349 U_tmp[4] = U[4] + 0.5 * dt * k_even[4];
350 U_tmp[5] = U[5] + 0.5 * dt * k_even[5];
351 U_tmp[6] = U[6] + 0.5 * dt * k_even[6];
352
353 particles[i].pos(0) += (1. / 6.) * dt * (k_odd[0] + 2. * k_even[0]);
354 particles[i].pos(1) += (1. / 6.) * dt * (k_odd[1] + 2. * k_even[1]);
355 particles[i].pos(2) += (1. / 6.) * dt * (k_odd[2] + 2. * k_even[2]);
356 vels_x[i] += (1. / 6.) * dt * (k_odd[3] + 2. * k_even[3]);
357 vels_y[i] += (1. / 6.) * dt * (k_odd[4] + 2. * k_even[4]);
358 vels_z[i] += (1. / 6.) * dt * (k_odd[5] + 2. * k_even[5]);
359 ln_energy[i] += (1. / 6.) * dt * (k_odd[6] + 2. * k_even[6]);
360
361 out_of_bounds |= (U_tmp[0] > boundarie_hx) || (U_tmp[0] < boundarie_lx);
362 out_of_bounds |= (U_tmp[1] > boundarie_hy) || (U_tmp[1] < boundarie_ly);
363 out_of_bounds |= (U_tmp[2] > boundarie_hz) || (U_tmp[2] < boundarie_lz);
364
365 if (out_of_bounds) {
366 particles[i].id() = -1;
367 return;
368 }
369
370 // f3 = rhs(u + 0.5 * dt * f2, t) for the runge kutta 4 step
371 k_odd = self->compute_rhs(U_tmp, 0.5 * dt, lapse_array, shift_array,
372 metric_array, curv_array, dt, dx, lev, plo0);
373
374 U_tmp[0] = U[0] + dt * k_odd[0];
375 U_tmp[1] = U[1] + dt * k_odd[1];
376 U_tmp[2] = U[2] + dt * k_odd[2];
377 U_tmp[3] = U[3] + dt * k_odd[3];
378 U_tmp[4] = U[4] + dt * k_odd[4];
379 U_tmp[5] = U[5] + dt * k_odd[5];
380 U_tmp[6] = U[6] + dt * k_odd[6];
381
382 out_of_bounds |= (U_tmp[0] > boundarie_hx) || (U_tmp[0] < boundarie_lx);
383 out_of_bounds |= (U_tmp[1] > boundarie_hy) || (U_tmp[1] < boundarie_ly);
384 out_of_bounds |= (U_tmp[2] > boundarie_hz) || (U_tmp[2] < boundarie_lz);
385
386 if (out_of_bounds) {
387 particles[i].id() = -1;
388 return;
389 }
390
391 // f4 = rhs(u + dt * f3, t) for the runge kutta 4 step
392 k_even = self->compute_rhs(U_tmp, dt, lapse_array, shift_array,
393 metric_array, curv_array, dt, dx, lev, plo0);
394
395 // Update particles with the f3 and f4 from RK4
396 particles[i].pos(0) += (1. / 6.) * dt * (2. * k_odd[0] + k_even[0]);
397 particles[i].pos(1) += (1. / 6.) * dt * (2. * k_odd[1] + k_even[1]);
398 particles[i].pos(2) += (1. / 6.) * dt * (2. * k_odd[2] + k_even[2]);
399 vels_x[i] += (1. / 6.) * dt * (2. * k_odd[3] + k_even[3]);
400 vels_y[i] += (1. / 6.) * dt * (2. * k_odd[4] + k_even[4]);
401 vels_z[i] += (1. / 6.) * dt * (2. * k_odd[5] + k_even[5]);
402 ln_energy[i] += (1. / 6.) * dt * (2. * k_odd[6] + k_even[6]);
403
404 out_of_bounds |= (particles[i].pos(0) > boundarie_hx) ||
405 (particles[i].pos(0) < boundarie_lx);
406 out_of_bounds |= (particles[i].pos(1) > boundarie_hy) ||
407 (particles[i].pos(1) < boundarie_ly);
408 out_of_bounds |= (particles[i].pos(2) > boundarie_hz) ||
409 (particles[i].pos(2) < boundarie_lz);
410
411 if (out_of_bounds) {
412 particles[i].id() = -1;
413 return;
414 }
415 });
416 }
417} // ParticlesContainer::evolve
418
455template <typename StructType>
457 const amrex::MultiFab &metric, const int level) {
458
459 // Get the with of the discretization on each direction.
460 const auto dx = this->Geom(level).CellSizeArray();
461 // Get the lower and higher value over the ParticleContainer Geometry
462 const auto p_lo = this->Geom(level).ProbLoArray();
463 const auto p_hi = this->Geom(level).ProbHiArray();
464
465 for (amrex::MFIter mfi = this->MakeMFIter(level); mfi.isValid(); ++mfi) {
466
467 // Get a reference to the particles
468 auto &particle_tile = this->DefineAndReturnParticleTile(level, mfi);
469
470 // Determines the current size and the required new size
471 const auto current_size = particle_tile.GetArrayOfStructs().size();
472
473 // Gets raw pointers to the two different ways particle data is stored for
474 // performance reasons: Array of Struct (AoS) and Struct of Arrays (SoA)
475 auto *p_struct = particle_tile.GetArrayOfStructs()().data();
476 auto arrdata = particle_tile.GetStructOfArrays().realarray();
477
478 // get the current process id
479 const auto metric_array = metric.array(mfi);
480 const CCTK_REAL m = this->mass;
481
482 amrex::ParallelFor(current_size, [=] AMREX_GPU_DEVICE(int i) noexcept {
483 // Start a for loop with Random Number evolution for the velocity
484 const CCTK_REAL ratio[3] = {arrdata[StructType::vx][i],
485 arrdata[StructType::vy][i],
486 arrdata[StructType::vz][i]};
487 const CCTK_REAL E = std::exp(arrdata[StructType::ln_E][i]);
488
489 // Generate a random position
490 const auto &p = p_struct[i];
491
492 const int i0 = amrex::Math::floor((p.pos(0) - p_lo[0]) / dx[0]);
493 const int j0 = amrex::Math::floor((p.pos(1) - p_lo[1]) / dx[1]);
494 const int k0 = amrex::Math::floor((p.pos(2) - p_lo[2]) / dx[2]);
495
496 // Interpolate metric
497 amrex::GpuArray<CCTK_REAL, 6> gamma_x;
498 interpolate_array<5>(gamma_x, metric_array, i0, j0, k0, p.pos(0),
499 p.pos(1), p.pos(2), dx, p_lo);
500
501 const CCTK_REAL inv_det_gamma =
502 1.0 / (gamma_x[0] * gamma_x[3] * gamma_x[5] +
503 2. * gamma_x[1] * gamma_x[2] * gamma_x[4] -
504 gamma_x[2] * gamma_x[2] * gamma_x[3] -
505 gamma_x[4] * gamma_x[4] * gamma_x[0] -
506 gamma_x[1] * gamma_x[1] * gamma_x[5]);
507
508 const amrex::GpuArray<CCTK_REAL, 6> gamma_inv_x = {
509 (gamma_x[3] * gamma_x[5] - gamma_x[4] * gamma_x[4]) * inv_det_gamma,
510 (gamma_x[4] * gamma_x[2] - gamma_x[1] * gamma_x[5]) * inv_det_gamma,
511 (gamma_x[1] * gamma_x[4] - gamma_x[2] * gamma_x[3]) * inv_det_gamma,
512 (gamma_x[0] * gamma_x[5] - gamma_x[2] * gamma_x[2]) * inv_det_gamma,
513 (gamma_x[2] * gamma_x[1] - gamma_x[0] * gamma_x[4]) * inv_det_gamma,
514 (gamma_x[0] * gamma_x[3] - gamma_x[1] * gamma_x[1]) * inv_det_gamma};
515
516 // Normalizing the velocity.
517 const CCTK_REAL v_squared = ratio[0] * ratio[0] * gamma_inv_x[0] +
518 ratio[1] * ratio[1] * gamma_inv_x[3] +
519 ratio[2] * ratio[2] * gamma_inv_x[5] +
520 2.0 * ratio[0] * ratio[1] * gamma_inv_x[1] +
521 2.0 * ratio[0] * ratio[2] * gamma_inv_x[2] +
522 2.0 * ratio[1] * ratio[2] * gamma_inv_x[4];
523
524 const CCTK_REAL v = std::sqrt(v_squared);
525 const CCTK_REAL alpha = std::sqrt(1. - m * m / (E * E));
526
527 arrdata[StructType::vx][i] = ratio[0] * alpha / v;
528 arrdata[StructType::vy][i] = ratio[1] * alpha / v;
529 arrdata[StructType::vz][i] = ratio[2] * alpha / v;
530 });
531 }
532} // ParticlesContainer::normalize_velocity
533
534template <typename StructType>
536 CCTK_INFO("Redistributing particles");
537} // ParticlesContainer::redistribute_particles
538
539} // namespace GInX
540
541#endif // !PHOTONSCONTAINER_HXX
It Contains the BaseParticlesContainer and ParticleIterator classes inside of the GInX namespace.
Barycentric Lagrange interpolation related functions definitions.
Code utilities such as matrix and vector multiplications.
AMREX_GPU_DEVICE AMREX_GPU_HOST AMREX_FORCE_INLINE CCTK_ATTRIBUTE_ALWAYS_INLINE amrex::GpuArray< CCTK_REAL, 3 > SMatVecMul(amrex::GpuArray< CCTK_REAL, 6 > A, amrex::GpuArray< CCTK_REAL, 3 > V)
Definition: Utilities.hxx:30
AMREX_GPU_DEVICE AMREX_GPU_HOST AMREX_FORCE_INLINE CCTK_ATTRIBUTE_ALWAYS_INLINE CCTK_REAL VecVecMul(amrex::GpuArray< CCTK_REAL, 3 > U, amrex::GpuArray< CCTK_REAL, 3 > V)
Definition: Utilities.hxx:51
BaseParticleContainer abstract class definition.
Definition: BaseParticleContainer.hxx:67
Definition: BaseParticleContainer.hxx:31
ParticlesContainer class definition.
Definition: ParticlesContainer.hxx:54
CCTK_REAL mass
Definition: ParticlesContainer.hxx:56
void normalize_velocity(const amrex::MultiFab &metric, const int level)
Normalize the velocity accordingly to the metric on each particle position.
Definition: ParticlesContainer.hxx:456
void evolve(const amrex::MultiFab &lapse, const amrex::MultiFab &shift, const amrex::MultiFab &metric, const amrex::MultiFab &curv, const CCTK_REAL &dt, const int &lev) override
Evolving using Runge-Kutta 4.
Definition: ParticlesContainer.hxx:266
void redistribute_particles()
Definition: ParticlesContainer.hxx:535
ParticlesContainer(amrex::AmrCore *amr_core, const CCTK_REAL m)
Definition: ParticlesContainer.hxx:67
CCTK_INT get_mass()
Definition: ParticlesContainer.hxx:92
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE CCTK_ATTRIBUTE_ALWAYS_INLINE amrex::GpuArray< CCTK_REAL, 7 > compute_rhs(const amrex::GpuArray< CCTK_REAL, 7 > &u, const CCTK_REAL &t, amrex::Array4< CCTK_REAL const > const &lapse, amrex::Array4< CCTK_REAL const > const &shift, amrex::Array4< CCTK_REAL const > const &metric, amrex::Array4< CCTK_REAL const > const &K, const CCTK_REAL dt, const amrex::GpuArray< double, 3 > &dx, const int lev, const amrex::GpuArray< double, 3 > &plo)
Computes the right hand side of the geodesic differential equation.
Definition: ParticlesContainer.hxx:150
Interpolators namespace.
Definition: ParticlesContainer.hxx:39