Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
281 changes: 281 additions & 0 deletions src/ParticleBC/BoundaryConditionType.cpp
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
#include <cmath>
#include <cstdlib>

#include <string>
#include <algorithm>
#include <cctype>

#include "BoundaryConditionType.h"
#include "Params.h"
#include "userFunctions.h"
Expand Down Expand Up @@ -920,3 +924,280 @@ void thermalize_particle_wall( Species *species, int imin, int imax, int directi
}
}
}


// ==== angle_threshold particle boundary condition ====

namespace {

// Trim whitespace
inline std::string strip_spaces( std::string s )
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inline will likely not be used here

{
s.erase(
std::remove_if(
s.begin(), s.end(),
[]( unsigned char c ) { return std::isspace( c ); }
),
s.end()
);
return s;
}

// Parse boundary condition string:
// "angle_threshold:3deg" -> 3 degrees in radians
// "angle_threshold:0.05" -> 0.05 radians (default unit)
// "angle_threshold:0.05rad" -> 0.05 radians
// If no parameter is provided ("angle_threshold"), defaults to pi/2 (≈ always thermalize).
inline double parse_angle0_rad_from_bc( const std::string &bc )
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same

{
const double pi = std::acos( -1.0 );
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use M_PI, i believe, for pi. To be checked

const double half_pi = 0.5 * pi;

auto pos = bc.find( ':' );
if( pos == std::string::npos ) {
return half_pi;
}

std::string s = strip_spaces( bc.substr( pos + 1 ) );
bool is_deg = false;
bool is_rad = false;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest not giving the choice of degrees. This makes the input too difficult to benchmark when there are too many possibilities. Users can easily switch from the python script

if( s.size() >= 3 && s.compare( s.size() - 3, 3, "deg" ) == 0 ) {
is_deg = true;
s = s.substr( 0, s.size() - 3 );
} else if( s.size() >= 3 && s.compare( s.size() - 3, 3, "rad" ) == 0 ) {
is_rad = true;
s = s.substr( 0, s.size() - 3 );
}

double val = std::atof( s.c_str() );
if( val < 0.0 ) {
val = -val;
}

if( is_deg ) {
val *= pi / 180.0;
} else if( is_rad ) {
// already radians
} else {
// default: radians
}

// Clamp to [0, pi/2] for sanity
if( val < 0.0 ) val = 0.0;
if( val > half_pi ) val = half_pi;

return val;
}

// Compute alpha = atan2(|p_perp|, |p_par|), where p_par is the momentum component along "direction"
// and p_perp is the magnitude of the momentum component(s) perpendicular to it.
#ifdef SMILEI_ACCELERATOR_GPU_OACC
#pragma acc routine seq
#endif
#ifdef SMILEI_ACCELERATOR_GPU_OMP
#pragma omp declare target
#endif

inline double compute_alpha( int direction,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again I am not sure you can defined inline functions here. If needed on gpu, this might cost something

const double *px, const double *py, const double *pz,
int ipart )
{
// Use full 3-velocity (3V) information even in 1D/2D geometries (e.g., 1D3V, 2D3V).
// direction is the boundary normal axis (0:x, 1:y, 2:z).
const double p_par =
( direction == 0 ) ? px[ipart] :
( direction == 1 ) ? py[ipart] :
pz[ipart];

double p_perp2 = 0.0;
if( direction != 0 ) p_perp2 += px[ipart]*px[ipart];
if( direction != 1 ) p_perp2 += py[ipart]*py[ipart];
if( direction != 2 ) p_perp2 += pz[ipart]*pz[ipart];

return std::atan2( std::sqrt( p_perp2 ), std::fabs( p_par ) );
}
#ifdef SMILEI_ACCELERATOR_GPU_OMP
#pragma omp end declare target
#endif


} // namespace

// angle_threshold BC (domain lower boundary):
// - if alpha < alpha0 : apply "thermalize" BC
// - else : apply "reflective" BC
void angle_threshold_particle_inf( Species *species, int imin, int imax, int direction,
double limit_inf, double dt, std::vector<double> &invgf,
Random * rand, double &energy_change )
{
// Decide alpha0 from the BC string on this side
const std::string &bc = species->boundary_conditions_[direction][0];
const double alpha0 = parse_angle0_rad_from_bc( bc );
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests should not be done here. Parse the string art the initialization, otherwise this will be to slow

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mccoys Thanks for the comment. I was able to address the other comments rather straightforwardly, but this one seemed a bit more involved, so I just wanted to check if I understood your suggestion correctly.

Would you recommend parsing the boundary-condition string only once during initialization, then storing the parsed angle-threshold value as a parameter to be reused at runtime, instead of parsing it inside BoundaryConditionType.cpp?

If that is what you mean, I was thinking it might require a small refactor on the initialization side, for example by storing the value in Species and setting it from SpeciesFactory. Would that be the right direction?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that sounds exactly right

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mccoys Thanks again for the suggestion. I updated the implementation accordingly, and there have been several revisions to the code. I would be very grateful if you could review it again when you have time.

I agree that optimization is important. At the same time, my main focus here is to implement the intended particle boundary-condition mechanism, where the particle behavior at the boundary is determined by the incidence angle with respect to the boundary normal. I would also appreciate your feedback on whether this mechanism is represented appropriately in the current implementation.

The threshold angle is now parsed once during initialization, stored as a species parameter, and reused at runtime instead of being parsed inside BoundaryConditionType.cpp.

I also updated the PR description and rechecked the behavior with 1D and 2D test runs.


const int nDim = species->nDim_particle;

double *pos_dir = species->particles->getPtrPosition( direction );
double *p_dir = species->particles->getPtrMomentum( direction );
double *px = species->particles->getPtrMomentum( 0 );
double *py = species->particles->getPtrMomentum( 1 );
double *pz = species->particles->getPtrMomentum( 2 );

// First pass: reflect particles that are outside AND alpha >= alpha0
#ifdef SMILEI_ACCELERATOR_GPU_OACC
#pragma acc parallel deviceptr(pos_dir,p_dir,px,py,pz)
#pragma acc loop gang worker vector
#elif defined( SMILEI_ACCELERATOR_GPU_OMP )
#pragma omp target is_device_ptr(pos_dir,p_dir,px,py,pz)
#pragma omp teams distribute parallel for
#endif
for( int ipart=imin; ipart<imax; ipart++ ) {
if( pos_dir[ipart] < limit_inf ) {
const double alpha = compute_alpha( direction, px, py, pz, ipart );
if( alpha >= alpha0 ) {
// Reflect
pos_dir[ipart] = 2.0 * limit_inf - pos_dir[ipart];
p_dir[ipart] = -p_dir[ipart];
}
}
}

// Second pass: remaining outside particles get the standard thermalize BC
double thermal_energy_change = 0.0;
thermalize_particle_inf( species, imin, imax, direction, limit_inf, dt, invgf, rand, thermal_energy_change );
energy_change = thermal_energy_change;
}

// angle_threshold BC (domain upper boundary):
// - if alpha < alpha0 : apply "thermalize" BC
// - else : apply "reflective" BC
void angle_threshold_particle_sup( Species *species, int imin, int imax, int direction,
double limit_sup, double dt, std::vector<double> &invgf,
Random * rand, double &energy_change )
{
// Decide alpha0 from the BC string on this side
const std::string &bc = species->boundary_conditions_[direction][1];
const double alpha0 = parse_angle0_rad_from_bc( bc );

const int nDim = species->nDim_particle;

double *pos_dir = species->particles->getPtrPosition( direction );
double *p_dir = species->particles->getPtrMomentum( direction );
double *px = species->particles->getPtrMomentum( 0 );
double *py = species->particles->getPtrMomentum( 1 );
double *pz = species->particles->getPtrMomentum( 2 );

// First pass: reflect particles that are outside AND alpha >= alpha0
#ifdef SMILEI_ACCELERATOR_GPU_OACC
#pragma acc parallel deviceptr(pos_dir,p_dir,px,py,pz)
#pragma acc loop gang worker vector
#elif defined( SMILEI_ACCELERATOR_GPU_OMP )
#pragma omp target is_device_ptr(pos_dir,p_dir,px,py,pz)
#pragma omp teams distribute parallel for
#endif
for( int ipart=imin; ipart<imax; ipart++ ) {
if( pos_dir[ipart] >= limit_sup ) {
const double alpha = compute_alpha( direction, px, py, pz, ipart );
if( alpha >= alpha0 ) {
// Reflect (upper boundary is outside the domain -> reflect just before it)
pos_dir[ipart] = 2.0 * std::nextafter( limit_sup, 0.0 ) - pos_dir[ipart];
p_dir[ipart] = -p_dir[ipart];
}
}
}

// Second pass: remaining outside particles get the standard thermalize BC
double thermal_energy_change = 0.0;
thermalize_particle_sup( species, imin, imax, direction, limit_sup, dt, invgf, rand, thermal_energy_change );
energy_change = thermal_energy_change;
}

// angle_threshold BC for internal walls.
// Implemented for completeness: it behaves like thermalize_particle_wall for alpha < alpha0,
// and like reflect_particle_wall for alpha >= alpha0.
void angle_threshold_particle_wall( Species *species, int imin, int imax, int direction,
double wall_position, double dt, std::vector<double> &invgf,
Random * rand, double &energy_change )
{
const int nDim = species->nDim_particle;

// Try to find an "angle_threshold:..." string on either side; if absent, default pi/2.
const std::string &bc0 = species->boundary_conditions_[direction][0];
const std::string &bc1 = species->boundary_conditions_[direction][1];
double alpha0 = parse_angle0_rad_from_bc( bc0 );
if( bc0.rfind( "angle_threshold", 0 ) != 0 ) {
alpha0 = parse_angle0_rad_from_bc( bc1 );
}

double *pos_dir = species->particles->getPtrPosition( direction );
double *p_dir = species->particles->getPtrMomentum( direction );

double *px = species->particles->getPtrMomentum( 0 );
double *py = species->particles->getPtrMomentum( 1 );
double *pz = species->particles->getPtrMomentum( 2 );
double *weight = species->particles->getPtrWeight();

energy_change = 0.0;

for( int ipart=imin; ipart<imax; ipart++ ) {

const double particle_position = pos_dir[ipart];
const double particle_position_old = particle_position - dt * invgf[ipart] * species->particles->Momentum[direction][ipart];

// crossing test (same as reflect_particle_wall / thermalize_particle_wall)
if( ( wall_position - particle_position_old ) * ( wall_position - particle_position ) < 0.0 ) {

const double alpha = compute_alpha( direction, px, py, pz, ipart );

if( alpha >= alpha0 ) {
// Pure reflection (no energy loss)
pos_dir[ipart] = 2.0 * wall_position - pos_dir[ipart];
p_dir[ipart] = -p_dir[ipart];
continue;
}

// Otherwise: use the existing thermalize wall behavior (copied from thermalize_particle_wall),
// which may thermalize or reflect depending on v compared to 3*v_th.
// --- begin adapted block ---
double p2 = px[ipart] * px[ipart] + py[ipart] * py[ipart] + pz[ipart] * pz[ipart];
double LorentzFactor = std::sqrt( 1.0 + p2 );
double v = std::sqrt( p2 ) / LorentzFactor;

double initial_energy = LorentzFactor - 1.0;

if( v > 3.0 * species->thermal_velocity_[0] ) {

double sign_vel = -1.0;
if( std::abs( p_dir[ipart] ) > 0.0 ) {
sign_vel = -p_dir[ipart] / std::abs( p_dir[ipart] );
}
p_dir[ipart] = sign_vel * species->thermal_momentum_[direction]
* std::sqrt( -std::log( 1.0 - rand->uniform1() ) );

// tangential component(s)
if( nDim > 1 ) {
// choose transverse index as in original code
int i2 = (direction + 1) % nDim;
double *p_t1 = species->particles->getPtrMomentum( i2 );
p_t1[ipart] = species->thermal_momentum_[i2] * perp_rand( rand );

if( nDim > 2 ) {
int i3 = (direction + 2) % nDim;
double *p_t2 = species->particles->getPtrMomentum( i3 );
p_t2[ipart] = species->thermal_momentum_[i3] * perp_rand( rand );
}
}

} else {
// low velocity: reflect
p_dir[ipart] = -p_dir[ipart];
}

pos_dir[ipart] = 2.0 * wall_position - pos_dir[ipart];

LorentzFactor = std::sqrt( 1.0 + px[ipart] * px[ipart] + py[ipart] * py[ipart] + pz[ipart] * pz[ipart] );
energy_change += weight[ipart] * ( initial_energy - LorentzFactor + 1.0 );
// --- end adapted block ---
}
}
}
18 changes: 18 additions & 0 deletions src/ParticleBC/BoundaryConditionType.h
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

#include <cmath>
#include <cstdlib>
#include <vector>

#include "Particles.h"
#include "Species.h"
Expand Down Expand Up @@ -70,5 +71,22 @@ void thermalize_particle_sup( Species *species, int imin, int imax, int directio

void thermalize_particle_wall( Species *species, int imin, int imax, int direction, double limit_pos, double dt, std::vector<double> &invgf, Random * rand, double &energy_change );

// angle_threshold BC (direction-general, 2D example):
// alpha = atan2(|p_perp|, |p_par|) where
// p_par = momentum[direction] (normal component)
// p_perp = momentum[1-direction] (tangential component) // 2D only
// if alpha < alpha0 -> thermalize
// else -> reflect
void angle_threshold_particle_inf( Species *species, int imin, int imax, int direction,
double limit_inf, double dt, std::vector<double> &invgf,
Random * rand, double &energy_change );

void angle_threshold_particle_sup( Species *species, int imin, int imax, int direction,
double limit_sup, double dt, std::vector<double> &invgf,
Random * rand, double &energy_change );

void angle_threshold_particle_wall( Species *species, int imin, int imax, int direction,
double limit_pos, double dt, std::vector<double> &invgf,
Random * rand, double &energy_change );

#endif
23 changes: 20 additions & 3 deletions src/ParticleBC/PartBoundCond.cpp
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,14 @@ PartBoundCond::PartBoundCond( Params &params, Species *species, Patch *patch )
}
} else if( species->boundary_conditions_[0][0] == "periodic" ) {
// Nothing to do
} else {
} else if( species->boundary_conditions_[0][0].rfind("angle_threshold", 0) == 0 ) {
if( patch->isXmin() ) {
bc_xmin = &angle_threshold_particle_inf;
}
} else {
ERROR( "Xmin boundary condition `"<<species->boundary_conditions_[0][0]<<"` unknown" );
}


// Xmax
bc_xmax = &internal_sup;
Expand All @@ -140,6 +145,10 @@ PartBoundCond::PartBoundCond( Params &params, Species *species, Patch *patch )
}
} else if( species->boundary_conditions_[0][1] == "periodic" ) {
// Nothing to do
} else if( species->boundary_conditions_[0][1].rfind("angle_threshold", 0) == 0 ) {
if( patch->isXmax() ) {
bc_xmax = &angle_threshold_particle_sup;
}
} else {
ERROR( "Xmax boundary condition `"<<species->boundary_conditions_[0][1]<<"` unknown" );
}
Expand All @@ -166,7 +175,11 @@ PartBoundCond::PartBoundCond( Params &params, Species *species, Patch *patch )
}
} else if( species->boundary_conditions_[1][0] == "periodic" ) {
// Nothing to do
} else {
} else if( species->boundary_conditions_[1][0].rfind("angle_threshold", 0) == 0 ) {
if( patch->isYmin() ) {
bc_ymin = &angle_threshold_particle_inf;
}
} else {
ERROR( "Ymin boundary condition `"<< species->boundary_conditions_[1][0] << "` unknown" );
}

Expand All @@ -190,7 +203,11 @@ PartBoundCond::PartBoundCond( Params &params, Species *species, Patch *patch )
}
} else if( species->boundary_conditions_[1][1] == "periodic" ) {
// Nothing to do
} else {
} else if( species->boundary_conditions_[1][1].rfind("angle_threshold", 0) == 0 ) {
if( patch->isYmax() ) {
bc_ymax = &angle_threshold_particle_sup;
}
} else {
ERROR( "Ymax boundary condition `"<< species->boundary_conditions_[1][1] <<"` undefined" );
}

Expand Down
4 changes: 3 additions & 1 deletion src/Species/SpeciesFactory.h
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -647,7 +647,9 @@ class SpeciesFactory
);
}
for( unsigned int ii=0; ii<2; ii++ ) {
if( this_species->boundary_conditions_[iDim][ii] == "thermalize" ) {
const std::string &bc = this_species->boundary_conditions_[iDim][ii];
const bool is_thermalize_like = ( bc == "thermalize" ) || ( bc.rfind( "angle_threshold", 0 ) == 0 );
if( is_thermalize_like ) {
has_thermalize = true;
if( this_species->mass_ == 0 ) {
ERROR_NAMELIST(
Expand Down