|
new file 100644
|
|
|
/** @file poisson.c
|
|
|
* @brief Poisson/Helmholtz solver, including the routines for
|
|
|
* inhomogeneous (point-plane) configurations.
|
|
|
*/
|
|
|
#include <stdlib.h>
|
|
|
#include <stdio.h>
|
|
|
#include <math.h>
|
|
|
|
|
|
#include "cdr.h"
|
|
|
#include "cstream.h"
|
|
|
#include "fishpack.h"
|
|
|
#include "grid.h"
|
|
|
#include "interpol2.h"
|
|
|
#include "mapper.h"
|
|
|
#include "parameters.h"
|
|
|
#include "poisson.h"
|
|
|
#include "proto.h"
|
|
|
#include "rz_array.h"
|
|
|
#include "species.h"
|
|
|
|
|
|
static void get_bound_cond (pois_grid_t *grid, pois_problem_t *prob,
|
|
|
int *r_bound_cond, int *z_bound_cond,
|
|
|
double lambda);
|
|
|
static int needs_refinement (pois_grid_t *grid, int ir, int iz,
|
|
|
double threshold);
|
|
|
static int refine_in (pois_grid_t *grid, int cr0, int cz0, int cr1, int cz1);
|
|
|
|
|
|
decl_mapper_funcs(er);
|
|
|
decl_mapper_funcs(ez);
|
|
|
decl_mapper_funcs(etheta);
|
|
|
decl_mapper_funcs(charge);
|
|
|
/*!< Here we set three mapper_t objects that specify how we should do
|
|
|
* the mapping between the potential (once it is calculated) and the three
|
|
|
* components of the electric field.
|
|
|
*/
|
|
|
|
|
|
/* Global variables for the computations of inhomogeneous fields:
|
|
|
* initialized in pois_inhom_init. */
|
|
|
|
|
|
int pois_gridpoints_z, pois_gridpoints_r;
|
|
|
/*!< Number of gridpoints at level 0 of the poisson solver
|
|
|
*
|
|
|
* if !pois_inhom, this has to be equal to the parameter gridpoins_z.
|
|
|
* We also allow a different extension in r, albeit this is not yet used.
|
|
|
*/
|
|
|
|
|
|
double pois_inhom_L; /*!< Total length of the poisson domain. */
|
|
|
double pois_inhom_z; /*!< Location of the floating charge. */
|
|
|
double pois_inhom_dphi;
|
|
|
/*!< Potential difference between the electrodes created by a unit charge
|
|
|
* located at (0, pois_inhom_z)
|
|
|
*/
|
|
|
|
|
|
double pois_inhom_fixed_q_t;
|
|
|
/*!< Time-dependent fixed charge for the inhomogeneous poisson code
|
|
|
* It is updated in cstream_set_field_at_time acording to the parameter
|
|
|
* rise_time.
|
|
|
*/
|
|
|
|
|
|
extern double E_x, E_y, E_z;
|
|
|
|
|
|
static double inhom_phi_term (double r, double z, double b);
|
|
|
static double inhom_e_term (double r, double z, double b);
|
|
|
static double inhom_er_term (double r, double z, double b);
|
|
|
static double inhom_ez_term (double r, double z, double b);
|
|
|
/*!< To calculate the potential and electric fields created by a unit charge
|
|
|
* between two planar electrodes, we use the "mirror charges method"
|
|
|
* the contribution of each mirror charge is given by these functions.
|
|
|
*/
|
|
|
|
|
|
mapper_t er_mapper = mk_mapper_staggered (er, &interpol_bilin, 0, -1);
|
|
|
mapper_t ez_mapper = mk_mapper_staggered (ez, &interpol_bilin, -1, 0);
|
|
|
mapper_t etheta_mapper = mk_mapper (etheta, &interpol_quadratic);
|
|
|
mapper_t *e_mappers[] = {&er_mapper, &ez_mapper, ðeta_mapper, NULL};
|
|
|
/*!< These are the mapper structures to translate between cdr and poisson
|
|
|
* grids. See mapper.c and mapper.h for more info.
|
|
|
*/
|
|
|
|
|
|
mapper_t charge_mapper = mk_mapper_down(charge, &interpol_wackers);
|
|
|
mapper_t *charge_mappers[] = {&charge_mapper, NULL};
|
|
|
|
|
|
pois_problem_t *pois_electrostatic;
|
|
|
|
|
|
/** @brief All initialization of the Poisson solver has to be here. */
|
|
|
void
|
|
|
pois_init (void)
|
|
|
{
|
|
|
if (pois_inhom) {
|
|
|
pois_inhom_init ();
|
|
|
}
|
|
|
else {
|
|
|
pois_gridpoints_z = gridpoints_z;
|
|
|
}
|
|
|
pois_gridpoints_r = gridpoints_r;
|
|
|
|
|
|
pois_electrostatic = xmalloc (sizeof (pois_problem_t));
|
|
|
|
|
|
pois_electrostatic->max_level = pois_max_level;
|
|
|
pois_electrostatic->extra_levels = extra_pois_levels;
|
|
|
pois_electrostatic->max_error = pois_max_error;
|
|
|
|
|
|
pois_electrostatic->bnd_right = pois_bnd_right;
|
|
|
pois_electrostatic->bnd_top = pois_bnd_top;
|
|
|
pois_electrostatic->bnd_bottom = pois_bnd_bottom;
|
|
|
}
|
|
|
|
|
|
/** @brief Creates a 2D Poisson grid. */
|
|
|
pois_grid_t*
|
|
|
pois_new_a (int r0, int z0, int r1, int z1)
|
|
|
{
|
|
|
pois_grid_t *grid;
|
|
|
|
|
|
grid = pois_new_3d_a (r0, z0, r1, z1, 1);
|
|
|
|
|
|
return grid;
|
|
|
}
|
|
|
|
|
|
/** @brief Creates a new 3D Poisson grid.
|
|
|
*
|
|
|
* @a r0, @a z0, @a r1, @a z1 are in GRID units
|
|
|
*/
|
|
|
pois_grid_t*
|
|
|
pois_new_3d_a (int r0, int z0, int r1, int z1, int ntheta)
|
|
|
{
|
|
|
rz_array_t *phi, *charge, *error;
|
|
|
pois_grid_t *grid;
|
|
|
|
|
|
debug (2, "pois_new_a (r0 = %d, z0 = %d, r1 = %d, z1 = %d)\n",
|
|
|
r0, z0, r1, z1);
|
|
|
|
|
|
phi = rz_new_3d_a (r0, z0, r1, z1, ntheta);
|
|
|
error = rz_new_3d_a (r0, z0, r1, z1, ntheta);
|
|
|
|
|
|
#ifdef F_PHI_SAME_AS_CHARGE
|
|
|
charge = phi;
|
|
|
#else
|
|
|
charge = rz_new_3d_a (r0, z0, r1, z1, ntheta);
|
|
|
#endif /* F_PHI_SAME_AS_CHARGE */
|
|
|
|
|
|
grid = (pois_grid_t *) xmalloc (sizeof (pois_grid_t));
|
|
|
|
|
|
grid->r0 = r0;
|
|
|
grid->r1 = r1;
|
|
|
grid->z0 = z0;
|
|
|
grid->z1 = z1;
|
|
|
grid->ntheta = ntheta;
|
|
|
|
|
|
grid->phi = phi;
|
|
|
grid->charge = charge;
|
|
|
grid->error = error;
|
|
|
|
|
|
/* By default, the array does not have any external boundary. */
|
|
|
grid->ext_bound = BND_NONE;
|
|
|
|
|
|
/* Initially, a grid does not have any relatives. */
|
|
|
init_leaf (grid);
|
|
|
|
|
|
return grid;
|
|
|
}
|
|
|
|
|
|
/** @brief Frees the memory allocated by pois_new_a */
|
|
|
void
|
|
|
pois_free (pois_grid_t *grid)
|
|
|
{
|
|
|
debug (3, "pois_free (...)\n");
|
|
|
|
|
|
/* The decission of whether to use the same memory for the charge and
|
|
|
* the potential can be made elsewhere.
|
|
|
*/
|
|
|
if (grid->phi != grid->charge)
|
|
|
rz_free (grid->charge);
|
|
|
rz_free (grid->phi);
|
|
|
rz_free (grid->error);
|
|
|
|
|
|
free (grid);
|
|
|
}
|
|
|
|
|
|
/** @brief Recursively frees a tree of Poisson grids. */
|
|
|
void
|
|
|
pois_free_r (pois_grid_t *grid)
|
|
|
{
|
|
|
pois_grid_t *leaf;
|
|
|
|
|
|
debug (3, "pois_free_r (" grid_printf_str ")\n", grid_printf_args (grid));
|
|
|
|
|
|
free_childs (grid, leaf, pois_free_r);
|
|
|
|
|
|
pois_free (grid);
|
|
|
}
|
|
|
|
|
|
/** @brief Creates a new Poisson grid.
|
|
|
*
|
|
|
* @a r0, @a r1... are in global units (i.e. independent of the grid level).
|
|
|
*/
|
|
|
pois_grid_t*
|
|
|
pois_new_glob_a (int r0, int z0, int r1, int z1, int level)
|
|
|
{
|
|
|
debug (3, "pois_new_glob_a (%d, %d, %d, %d)\n", r0, r1, z0, z1);
|
|
|
pois_grid_t *grid;
|
|
|
|
|
|
if (level >= 0) {
|
|
|
grid = pois_new_a (r0 << level, z0 << level,
|
|
|
r1 << level, z1 << level);
|
|
|
} else {
|
|
|
level = -level;
|
|
|
grid = pois_new_a (r0 >> level, z0 >> level,
|
|
|
r1 >> level, z1 >> level);
|
|
|
}
|
|
|
return grid;
|
|
|
}
|
|
|
|
|
|
/** @brief Starts the tree of Poisson grids with the two coarsest ones.
|
|
|
*
|
|
|
* Receives the grid dimensions at level 0.
|
|
|
*/
|
|
|
pois_grid_t*
|
|
|
pois_init_tree_a (int r0, int z0, int r1, int z1)
|
|
|
{
|
|
|
pois_grid_t *coarsest, *sub_coarsest;
|
|
|
int level;
|
|
|
|
|
|
debug (3, "pois_init_tree_a (%d, %d, %d, %d)\n", r0, z0, r1, z1);
|
|
|
|
|
|
level = -extra_pois_levels;
|
|
|
|
|
|
coarsest = pois_new_glob_a (r0, z0, r1, z1, level);
|
|
|
coarsest->ext_bound = BND_MASK_ALL;
|
|
|
coarsest->level = level;
|
|
|
|
|
|
sub_coarsest = pois_new_glob_a (r0, z0, r1, z1, level + 1);
|
|
|
|
|
|
add_child (coarsest, sub_coarsest);
|
|
|
grid_inherit_ext_bound ((grid_t *) sub_coarsest);
|
|
|
|
|
|
return coarsest;
|
|
|
}
|
|
|
|
|
|
/** @brief Is it possible to use a single function to handle all four
|
|
|
* boundaries?
|
|
|
*
|
|
|
* Maybe this should be rewritten to use the interpol2 module?
|
|
|
*/
|
|
|
REAL*
|
|
|
pois_boundary_a (pois_grid_t *grid, int boundary)
|
|
|
{
|
|
|
pois_grid_t *parent;
|
|
|
|
|
|
/* Forget the 0s, they are there to shut up the compiler. */
|
|
|
int stride = 0, ortho_stride = 0, icells, invert, n;
|
|
|
int rb, zb;
|
|
|
double interp_array[3][3] = {{-4.0, 1.0, 18.0},
|
|
|
{-2.0, 78.0, 14.0},
|
|
|
{-6.0, -7.0, 4.0}};
|
|
|
|
|
|
const double prefactor = 1 / 96.0;
|
|
|
REAL *bnd, *p, *lstart;
|
|
|
|
|
|
debug (3, "pois_boundary_a (..., %d)\n", boundary);
|
|
|
|
|
|
parent = grid->parent;
|
|
|
|
|
|
/* First we check if the boundary is in r or in z. */
|
|
|
if (boundary & BND_AT_Z) {
|
|
|
icells = grid->r1 - grid->r0;
|
|
|
zb = 1; rb = 0;
|
|
|
if (NULL != parent) {
|
|
|
stride = parent->phi->strides[R_INDX];
|
|
|
ortho_stride = parent->phi->strides[Z_INDX];
|
|
|
}
|
|
|
} else {
|
|
|
zb = 0; rb = 1;
|
|
|
icells = grid->z1 - grid->z0;
|
|
|
if (NULL != parent) {
|
|
|
stride = parent->phi->strides[Z_INDX];
|
|
|
ortho_stride = parent->phi->strides[R_INDX];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
debug (3, " grid->ext_bound = %d\n", grid->ext_bound);
|
|
|
|
|
|
if (grid->ext_bound & BND_MASK (boundary)) {
|
|
|
/* If the boundary is external, we apply homogeneous Dirichlet/Neumann. */
|
|
|
bnd = (REAL *) xcalloc (sizeof (REAL), icells);
|
|
|
debug (3, "pois_boundary_a (...) -> [0.0] * %d\n", icells);
|
|
|
|
|
|
assert (grid->r1 != 0);
|
|
|
return bnd;
|
|
|
} else {
|
|
|
bnd = (REAL *) xmalloc (sizeof (REAL) * icells);
|
|
|
}
|
|
|
/* If the grid is root it should have returned in the previous if */
|
|
|
assert (NULL != parent);
|
|
|
|
|
|
/* Now we check if the boundary is at {r,z} minimum or {r,z} maximum */
|
|
|
if (boundary & BND_MAX) {
|
|
|
lstart = RZP (parent->phi,
|
|
|
(grid->r1 >> 1) - 1 + rb,
|
|
|
(grid->z1 >> 1) - 1 + zb);
|
|
|
stride = -stride;
|
|
|
ortho_stride = -ortho_stride;
|
|
|
invert = TRUE;
|
|
|
} else {
|
|
|
lstart = RZP (parent->phi, (grid->r0 >> 1) - rb, (grid->z0 >> 1) - zb);
|
|
|
invert = FALSE;
|
|
|
}
|
|
|
|
|
|
for (p = lstart, n = 0; n < icells; p += stride, n += 2) {
|
|
|
/* p points to the cell in the parent grid and p + stride points to
|
|
|
an adjacent cell. */
|
|
|
double s = 0.0, s2 = 0.0;
|
|
|
int i, j;
|
|
|
for (i = -1; i <= 1; i++) {
|
|
|
for (j = -1; j <= 1; j++) {
|
|
|
s += *(p + i * stride + j * ortho_stride) * interp_array[i + 1][j + 1];
|
|
|
s2 += *(p - i * stride + j * ortho_stride) * interp_array[i + 1][j + 1];
|
|
|
}
|
|
|
}
|
|
|
if (!invert) {
|
|
|
bnd[n] = prefactor * s;
|
|
|
if (n < icells - 1) bnd[n + 1] = prefactor * s2;
|
|
|
} else {
|
|
|
bnd[icells - n - 1] = prefactor * s;
|
|
|
if (n < icells - 1) bnd[icells - n - 2] = prefactor * s2;
|
|
|
}
|
|
|
}
|
|
|
return bnd;
|
|
|
}
|
|
|
|
|
|
/** @brief Solves the Poisson equation, calling the FISHPACK routine.
|
|
|
*
|
|
|
* Assumes that grid->charge is already set.
|
|
|
*/
|
|
|
void
|
|
|
pois_solve_grid (pois_grid_t *grid, pois_problem_t *prob,
|
|
|
double lambda, double s)
|
|
|
{
|
|
|
REAL *boundaries[4];
|
|
|
int i, z_bound_cond, r_bound_cond;
|
|
|
double rmin, rmax, zmin, zmax;
|
|
|
|
|
|
debug (3, "pois_solve_grid (..., lambda = %f, s = %f)\n", lambda, s);
|
|
|
|
|
|
for (i = 0; i < 4; i++) {
|
|
|
boundaries[i] = pois_boundary_a (grid, i);
|
|
|
debug (3, "boundaries[%d] = %p\n", i, boundaries[i]);
|
|
|
}
|
|
|
|
|
|
#ifndef F_PHI_SAME_AS_CHARGE
|
|
|
rz_copy (grid->charge, grid->r0, grid->z0,
|
|
|
grid->phi, grid->r0, grid->z0,
|
|
|
grid->r1 - grid->r0, grid->z1 - grid->z0);
|
|
|
#endif
|
|
|
|
|
|
rmin = grid->r0 * dr[grid->level];
|
|
|
rmax = grid->r1 * dr[grid->level];
|
|
|
zmin = grid->z0 * dz[grid->level];
|
|
|
zmax = grid->z1 * dz[grid->level];
|
|
|
|
|
|
debug (3, "HSTCYL/HSTCRT in a grid %d x %d\n", grid->r1 - grid->r0,
|
|
|
grid->z1 - grid->z0);
|
|
|
|
|
|
get_bound_cond (grid, prob, &r_bound_cond, &z_bound_cond, lambda);
|
|
|
|
|
|
#ifndef TRUE2D
|
|
|
fish_hstcyl (rmin, rmax, grid->r1 - grid->r0,
|
|
|
r_bound_cond, boundaries[BND_LEFT], boundaries[BND_RIGHT],
|
|
|
zmin, zmax, grid->z1 - grid->z0,
|
|
|
z_bound_cond, boundaries[BND_BOTTOM], boundaries[BND_TOP],
|
|
|
lambda, s,
|
|
|
RZP (grid->phi, grid->r0, grid->z0),
|
|
|
grid->phi->strides[Z_INDX]);
|
|
|
#else
|
|
|
fish_hstcrt (rmin, rmax, grid->r1 - grid->r0,
|
|
|
r_bound_cond, boundaries[BND_LEFT], boundaries[BND_RIGHT],
|
|
|
zmin, zmax, grid->z1 - grid->z0,
|
|
|
z_bound_cond, boundaries[BND_BOTTOM], boundaries[BND_TOP],
|
|
|
lambda,
|
|
|
RZP (grid->phi, grid->r0, grid->z0),
|
|
|
grid->phi->strides[Z_INDX]);
|
|
|
#endif
|
|
|
debug (3, "Finished HSTCYL/HSTCRT in a grid %d x %d\n", grid->r1 - grid->r0,
|
|
|
grid->z1 - grid->z0);
|
|
|
|
|
|
pois_set_phi_boundaries (grid, boundaries,
|
|
|
(grid->ext_bound & BND_MASK (BND_LEFT))
|
|
|
&& 0. == lambda,
|
|
|
|
|
|
prob->bnd_right == BND_CND_HNEUMANN
|
|
|
&& (grid->ext_bound & BND_MASK (BND_RIGHT)),
|
|
|
|
|
|
prob->bnd_bottom == BND_CND_HNEUMANN
|
|
|
&& (grid->ext_bound & BND_MASK (BND_BOTTOM)),
|
|
|
|
|
|
prob->bnd_top == BND_CND_HNEUMANN
|
|
|
&& (grid->ext_bound & BND_MASK (BND_TOP)));
|
|
|
|
|
|
for (i = 0; i < 4; i++) {
|
|
|
debug (3, "boundaries[%d] = %p\n", i, boundaries[i]);
|
|
|
free (boundaries[i]);
|
|
|
}
|
|
|
debug (3, " <- pos_solve_grid (...)\n");
|
|
|
}
|
|
|
|
|
|
/** @brief Gets the boundary conditions that we pass to FISHPACK.
|
|
|
*
|
|
|
* Note that we use the same conditions for the Poisson equation
|
|
|
* and for the Helmholtz equation used to find the photoionization
|
|
|
* source. From my point of view, it doesn't make sense to use different
|
|
|
* conditions, so that will simply add an unneccesary complication.
|
|
|
*/
|
|
|
static void
|
|
|
get_bound_cond (pois_grid_t *grid, pois_problem_t *prob,
|
|
|
int *r_bound_cond, int *z_bound_cond, double lambda)
|
|
|
{
|
|
|
static int right_inside[] = {FISH_UNS_DIR, FISH_NEU_DIR, FISH_DIR_DIR};
|
|
|
static int right_ext_neu[] = {FISH_UNS_NEU, FISH_NEU_NEU, FISH_DIR_NEU};
|
|
|
static int *right_ext_dir = right_inside;
|
|
|
static int fish_order[4] = { FISH_DIR_DIR, FISH_DIR_NEU,
|
|
|
FISH_NEU_DIR, FISH_NEU_NEU};
|
|
|
|
|
|
int bottom, top;
|
|
|
int *right_sel;
|
|
|
|
|
|
if ((grid->ext_bound & BND_MASK (BND_TOP))
|
|
|
&& prob->bnd_top == BND_CND_HNEUMANN)
|
|
|
top = 1;
|
|
|
else top = 0;
|
|
|
|
|
|
if ((grid->ext_bound & BND_MASK (BND_BOTTOM))
|
|
|
&& prob->bnd_bottom == BND_CND_HNEUMANN)
|
|
|
bottom = 1;
|
|
|
else bottom = 0;
|
|
|
|
|
|
*z_bound_cond = fish_order[(bottom << 1) + top];
|
|
|
|
|
|
if ((grid->ext_bound & BND_MASK (BND_RIGHT))) {
|
|
|
if (prob->bnd_right == BND_CND_HNEUMANN)
|
|
|
right_sel = right_ext_neu;
|
|
|
else
|
|
|
right_sel = right_ext_dir;
|
|
|
} else {
|
|
|
right_sel = right_inside;
|
|
|
}
|
|
|
|
|
|
#ifndef TRUE2D
|
|
|
*r_bound_cond = ((grid->ext_bound & BND_MASK (BND_LEFT)) && 0. == lambda)?
|
|
|
right_sel[0]: right_sel[2];
|
|
|
#else
|
|
|
*r_bound_cond = ((grid->ext_bound & BND_MASK (BND_LEFT))?
|
|
|
right_sel[1]: right_sel[2]);
|
|
|
#endif
|
|
|
|
|
|
}
|
|
|
|
|
|
/** @brief Uses the @a boundaries[] vectors to set the boundaries by
|
|
|
* interpolation for the phi array of a grid.
|
|
|
*
|
|
|
* (in the extra space we allocated when we created the array).
|
|
|
* xxx_neu tells us if the xxx boundary has Neumann b.c.
|
|
|
* (or unspecified, as FISHPACK calls them if they are applied in the axis)
|
|
|
*
|
|
|
* TODO: Please rewrite this mess.
|
|
|
*/
|
|
|
void
|
|
|
pois_set_phi_boundaries (pois_grid_t *grid, REAL *boundaries[],
|
|
|
int left_neu, int right_neu, int bottom_neu,
|
|
|
int top_neu)
|
|
|
{
|
|
|
int i;
|
|
|
|
|
|
debug (3, "pois_set_phi_boundaries (...)\n");
|
|
|
|
|
|
if ((grid->ext_bound & BND_MASK(BND_BOTTOM)) && bottom_neu) {
|
|
|
for (i = grid->r0; i < grid->r1; i++) {
|
|
|
RZ (grid->phi, i, grid->z0 - 1) = RZ (grid->phi, i, grid->z0);
|
|
|
}
|
|
|
} else {
|
|
|
for (i = grid->r0; i < grid->r1; i++) {
|
|
|
RZ (grid->phi, i, grid->z0 - 1) =
|
|
|
2 * boundaries[BND_BOTTOM][i - grid->r0] - RZ (grid->phi, i, grid->z0);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if ((grid->ext_bound & BND_MASK(BND_TOP)) && top_neu) {
|
|
|
for (i = grid->r0; i < grid->r1; i++) {
|
|
|
RZ (grid->phi, i, grid->z1) = RZ (grid->phi, i, grid->z1 - 1);
|
|
|
}
|
|
|
} else {
|
|
|
for (i = grid->r0; i < grid->r1; i++) {
|
|
|
RZ (grid->phi, i, grid->z1) = 2 * boundaries[BND_TOP][i - grid->r0]
|
|
|
- RZ(grid->phi, i, grid->z1 - 1);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (left_neu) {
|
|
|
for (i = grid->z0; i < grid->z1; i++) {
|
|
|
RZ (grid->phi, grid->r0 - 1, i) = RZ(grid->phi, grid->r0, i);
|
|
|
}
|
|
|
} else {
|
|
|
for (i = grid->z0; i < grid->z1; i++) {
|
|
|
RZ(grid->phi, grid->r0 - 1, i) = 2 * boundaries[BND_LEFT][i - grid->z0]
|
|
|
- RZ(grid->phi, grid->r0, i);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (right_neu) {
|
|
|
for (i = grid->z0; i < grid->z1; i++) {
|
|
|
RZ (grid->phi, grid->r1, i) = RZ(grid->phi, grid->r1 - 1, i);
|
|
|
}
|
|
|
} else {
|
|
|
for (i = grid->z0; i < grid->z1; i++) {
|
|
|
RZ(grid->phi, grid->r1, i) = 2 * boundaries[BND_RIGHT][i - grid->z0]
|
|
|
- RZ(grid->phi, grid->r1 - 1, i);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/* This is quite nightmarish. Think about rewritting. */
|
|
|
if ((grid->ext_bound & BND_MASK(BND_RIGHT)) && right_neu) {
|
|
|
RZ(grid->phi, grid->r1, grid->z0 - 1) = RZ(grid->phi, grid->r1 - 1,
|
|
|
grid->z0 - 1);
|
|
|
RZ(grid->phi, grid->r1, grid->z1) = RZ(grid->phi, grid->r1 - 1,
|
|
|
grid->z1);
|
|
|
} else {
|
|
|
RZ(grid->phi, grid->r1, grid->z0 - 1) =
|
|
|
RZ(grid->phi, grid->r1 - 1, grid->z0 - 1)
|
|
|
+ RZ(grid->phi, grid->r1, grid->z0)
|
|
|
- 0.5 * (RZ(grid->phi, grid->r1 - 2, grid->z0 - 1)
|
|
|
+ RZ(grid->phi, grid->r1, grid->z0 + 1));
|
|
|
|
|
|
RZ(grid->phi, grid->r1, grid->z1) =
|
|
|
RZ(grid->phi, grid->r1 - 1, grid->z1)
|
|
|
+ RZ(grid->phi, grid->r1, grid->z1 - 1)
|
|
|
- 0.5 * (RZ(grid->phi, grid->r1 - 2, grid->z1)
|
|
|
+ RZ(grid->phi, grid->r1, grid->z1 - 2));
|
|
|
}
|
|
|
|
|
|
if ((grid->ext_bound & BND_MASK(BND_LEFT)) && left_neu) {
|
|
|
RZ(grid->phi, grid->r0 - 1, grid->z0 - 1) = RZ(grid->phi, grid->r0,
|
|
|
grid->z0 - 1);
|
|
|
RZ(grid->phi, grid->r0 - 1, grid->z1) = RZ(grid->phi, grid->r0,
|
|
|
grid->z1);
|
|
|
} else {
|
|
|
RZ(grid->phi, grid->r0 - 1, grid->z0 - 1) =
|
|
|
RZ(grid->phi, grid->r0, grid->z0 - 1)
|
|
|
+ RZ(grid->phi, grid->r0 - 1, grid->z0)
|
|
|
- 0.5 * (RZ(grid->phi, grid->r0 + 1, grid->z0 - 1)
|
|
|
+ RZ(grid->phi, grid->r0 - 1, grid->z0 + 1));
|
|
|
|
|
|
RZ(grid->phi, grid->r0 - 1, grid->z1) =
|
|
|
RZ(grid->phi, grid->r0, grid->z1)
|
|
|
+ RZ(grid->phi, grid->r0 - 1, grid->z1 - 1)
|
|
|
- 0.5 * (RZ(grid->phi, grid->r0 + 1, grid->z1)
|
|
|
+ RZ(grid->phi, grid->r0 - 1, grid->z1 - 2));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/** @brief Estimates the error on this grid by comparing with the calculations
|
|
|
* for his parent and performing a Richardson extrapolation.
|
|
|
*/
|
|
|
void
|
|
|
pois_set_error (pois_grid_t *grid)
|
|
|
{
|
|
|
pois_grid_t *parent;
|
|
|
interpol_t *interpol;
|
|
|
int pr, pz;
|
|
|
|
|
|
debug (3, "pois_set_error(...)\n");
|
|
|
|
|
|
parent = grid->parent;
|
|
|
assert (NULL != parent);
|
|
|
|
|
|
interpol = interpol_new_a (2.0, 2.0, &interpol_wackers);
|
|
|
|
|
|
iter_grid_parent (grid, pr, pz) {
|
|
|
int i, j;
|
|
|
|
|
|
interpol_set_stencil_at ((grid_t *) grid, interpol,
|
|
|
pr * 2.0 + 1, pz * 2.0 + 1,
|
|
|
parent->phi, pr, pz, 0);
|
|
|
for (i = 0; i < 2; i++)
|
|
|
for (j = 0; j < 2; j++) {
|
|
|
int cr, cz;
|
|
|
double v;
|
|
|
|
|
|
cr = (pr << 1) + i;
|
|
|
cz = (pz << 1) + j;
|
|
|
|
|
|
v = interpol_apply (interpol, (double) cr + 0.5, (double) cz + 0.5);
|
|
|
|
|
|
/* The error will only be used as its abs value. Therefore
|
|
|
we calculate it here and forever. */
|
|
|
RZ (grid->error, cr, cz) = fabs (RZ (grid->phi, cr, cz) - v);
|
|
|
|
|
|
}
|
|
|
}
|
|
|
interpol_free (interpol);
|
|
|
}
|
|
|
|
|
|
/** @brief The refinement routine.
|
|
|
*
|
|
|
* Threshold is initially pois_max_error, but if we get too large refinement
|
|
|
* it can be increased (after warning the user that we are not reaching
|
|
|
* the accuracy that he asked for).
|
|
|
*/
|
|
|
int
|
|
|
pois_refine (pois_grid_t *grid, double threshold)
|
|
|
{
|
|
|
int ir, iz;
|
|
|
int zmin, zmax, rmin, rmax;
|
|
|
/* Be careful: cz1 and cr1 are inclusive: when the child is actually
|
|
|
created, one has to increse them by 1. */
|
|
|
int cr0, cr1, cz0, cz1;
|
|
|
int all_ok = TRUE;
|
|
|
|
|
|
int tainted_line = FALSE;
|
|
|
|
|
|
debug (3, "pois_refine(...)\n");
|
|
|
|
|
|
/* Initially we set values out of the grid, so the first comparisons
|
|
|
* always change the values.
|
|
|
*/
|
|
|
cr0 = grid->r1; cr1 = grid->r0 - 1;
|
|
|
cz0 = grid->z1; cz1 = grid->z0 - 1;
|
|
|
|
|
|
/* The boundary can only coincide with the parent's boundary if this
|
|
|
* is an external boundary.
|
|
|
*/
|
|
|
rmin = (grid->ext_bound & BND_MASK(BND_LEFT))? grid->r0: (grid->r0 + 1);
|
|
|
rmax = (grid->ext_bound & BND_MASK(BND_RIGHT))? grid->r1: (grid->r1 - 1);
|
|
|
zmin = (grid->ext_bound & BND_MASK(BND_BOTTOM))? grid->z0: (grid->z0 + 1);
|
|
|
zmax = (grid->ext_bound & BND_MASK(BND_TOP))? grid->z1: (grid->z1 - 1);
|
|
|
|
|
|
for (iz = zmin; iz < zmax && all_ok; iz++) {
|
|
|
tainted_line = FALSE;
|
|
|
for (ir = rmin; ir < rmax; ir++) {
|
|
|
if (needs_refinement (grid, ir, iz, threshold)) {
|
|
|
tainted_line = TRUE;
|
|
|
if (ir < cr0) cr0 = ir;
|
|
|
if (ir > cr1) cr1 = ir;
|
|
|
}
|
|
|
}
|
|
|
if (tainted_line) {
|
|
|
if (iz < cz0) cz0 = iz;
|
|
|
if (iz > cz1) cz1 = iz;
|
|
|
} else if (cr1 >= cr0 && cz1 >= cz0) {
|
|
|
all_ok = all_ok && refine_in (grid, cr0, cz0, cr1, cz1);
|
|
|
cr0 = grid->r1; cr1 = grid->r0 - 1;
|
|
|
cz0 = grid->z1; cz1 = grid->z0 - 1;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (cr1 >= cr0 && cz1 >= cz0) {
|
|
|
all_ok = all_ok && refine_in (grid, cr0, cz0, cr1, cz1);
|
|
|
}
|
|
|
|
|
|
/* If there was some error with some refinement, we delete the created
|
|
|
* sub-grids and go back to the caller, which has to increase the refinement
|
|
|
* threshold.
|
|
|
*/
|
|
|
if (!all_ok) {
|
|
|
pois_grid_t *leaf;
|
|
|
free_childs (grid, leaf, pois_free_r);
|
|
|
set_childless (grid);
|
|
|
}
|
|
|
return all_ok;
|
|
|
}
|
|
|
|
|
|
#define REFINE_IN_MAX_WARN_CNT 20;
|
|
|
|
|
|
/** @brief Refines and creates a new grid according to the coordinates given.
|
|
|
*
|
|
|
* (if the FISHPACK limit allows it). Returns TRUE if the refinement
|
|
|
* succeeded, FALSE otherwise (if the FISHPACK limit was exceeded).
|
|
|
*/
|
|
|
static int
|
|
|
refine_in (pois_grid_t *grid, int cr0, int cz0, int cr1, int cz1)
|
|
|
{
|
|
|
pois_grid_t *child;
|
|
|
int nr0, nr1, nz0, nz1;
|
|
|
static int warn_cnt = REFINE_IN_MAX_WARN_CNT;
|
|
|
|
|
|
nr0 = cr0 << 1;
|
|
|
nz0 = cz0 << 1;
|
|
|
nr1 = (++cr1) << 1;
|
|
|
nz1 = (++cz1) << 1;
|
|
|
|
|
|
if ((nr1 - nr0) > FISH_MAX_GRIDPOINTS ||
|
|
|
(nz1 - nz0) > FISH_MAX_GRIDPOINTS) {
|
|
|
if (warn_cnt > 0) {
|
|
|
warning ("FISHPACK limit exceeded. Not refining grid "
|
|
|
grid_printf_str ".\n", nr0, nz0, nr1, nz1, grid->level + 1);
|
|
|
warn_cnt --;
|
|
|
}
|
|
|
if (warn_cnt == 0) {
|
|
|
warning ("No more warnings about FISHPACK limit.\n");
|
|
|
warn_cnt --;
|
|
|
}
|
|
|
return FALSE;
|
|
|
}
|
|
|
|
|
|
child = pois_new_a (nr0, nz0, nr1, nz1);
|
|
|
|
|
|
add_child (grid, child);
|
|
|
grid_inherit_ext_bound ((grid_t *) child);
|
|
|
debug (3, "new child created {r0 = %d, z0 = %d, r1 = %d, z1 = %d, "
|
|
|
"level = %d}\n", child->r0, child->z0, child->r1, child->z1,
|
|
|
child->level);
|
|
|
|
|
|
return TRUE;
|
|
|
}
|
|
|
|
|
|
/** @brief Calculates the error measure of this grid.
|
|
|
*
|
|
|
* For debug/testing purposes
|
|
|
*/
|
|
|
void
|
|
|
pois_error_measures (pois_grid_t *grid, double *L1, double *L2, double *Lmax)
|
|
|
{
|
|
|
int ir, iz, i;
|
|
|
double err;
|
|
|
*L2 = 0.0;
|
|
|
*L1 = 0.0;
|
|
|
*Lmax = 0.0;
|
|
|
i = 0;
|
|
|
|
|
|
iter_grid (grid, ir, iz) {
|
|
|
i++;
|
|
|
err = RZ (grid->error, ir, iz);
|
|
|
if (err > *Lmax) *Lmax = err;
|
|
|
*L1 += err;
|
|
|
*L2 += err * err;
|
|
|
}
|
|
|
|
|
|
*L1 /= i;
|
|
|
*L2 = sqrt (*L2 / i);
|
|
|
|
|
|
}
|
|
|
|
|
|
/** @brief Writes some error measures of this grid and its descendants
|
|
|
* into @a fp
|
|
|
*/
|
|
|
void
|
|
|
pois_write_error_r (pois_grid_t *grid, FILE *fp)
|
|
|
{
|
|
|
double L1, L2, Lmax;
|
|
|
pois_grid_t *child;
|
|
|
|
|
|
pois_error_measures (grid, &L1, &L2, &Lmax);
|
|
|
|
|
|
fprintf (fp, "%d %g %g %g %g %g\n", grid->level, dr[grid->level],
|
|
|
dz[grid->level], L1, L2, Lmax);
|
|
|
|
|
|
iter_childs (grid, child) {
|
|
|
pois_write_error_r (child, fp);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/** @brief Takes a @a cdr tree and solves the Poisson equation for it.
|
|
|
*
|
|
|
* Returns a poisson tree.
|
|
|
*/
|
|
|
pois_grid_t **
|
|
|
pois_solve_a (cdr_grid_t *cdr, pois_problem_t *prob)
|
|
|
{
|
|
|
/* Call to the generic Poisson/Helmholtz solver */
|
|
|
return pois_gen_solve_a (cdr, prob, e_mappers, 0.0);
|
|
|
}
|
|
|
|
|
|
/** @brief Solves a Poisson/Helmholtz equation with FISHPACK and maps
|
|
|
* the result using a given set of mapper.
|
|
|
*
|
|
|
* From this method downwards (in the sense of calling order, not file
|
|
|
* organization), all the code is shared by the Poisson solver and the
|
|
|
* Photoionization (Helmholtz) solver.
|
|
|
*/
|
|
|
pois_grid_t **
|
|
|
pois_gen_solve_a (cdr_grid_t *cdr, pois_problem_t *prob,
|
|
|
mapper_t **mappers, double es)
|
|
|
{
|
|
|
cdr_grid_t *tree;
|
|
|
pois_grid_t **pois_modes;
|
|
|
int mode;
|
|
|
|
|
|
pois_modes = (pois_grid_t**) xmalloc (sizeof(pois_grid_t*) * max_ntheta);
|
|
|
assert (0 == cdr->level);
|
|
|
tree = cdr_add_coarser_grids_a (cdr, prob->extra_levels);
|
|
|
|
|
|
/* This is the high-level parallelizable loop! */
|
|
|
#pragma omp parallel
|
|
|
{
|
|
|
#pragma omp for schedule(dynamic)
|
|
|
for (mode = 0; mode < max_ntheta; mode++) {
|
|
|
pois_modes[mode] = pois_solve_mode (tree, cdr, prob, mode, es);
|
|
|
|
|
|
if (pois_inhom && es == 0.0 && mode == 0) {
|
|
|
/* Add the inhomogeneous laplacian potential to phi.
|
|
|
* It has only to be added to the zero mode.
|
|
|
*/
|
|
|
double q_factor;
|
|
|
q_factor = pois_inhom_q_factor (pois_modes[0]);
|
|
|
|
|
|
/* Was:
|
|
|
pois_add_inhom_phi_r (pois_modes[0], q_factor);
|
|
|
*/
|
|
|
pois_add_inhom_phi_r (pois_modes[0], -q_factor);
|
|
|
}
|
|
|
|
|
|
/* For the interpolation, we require an extra buffer cell
|
|
|
* in some boundaries and we check for overflows inside the
|
|
|
* mapper methods.
|
|
|
*/
|
|
|
map_trees_r (mappers, (grid_t*) pois_modes[mode], (grid_t*) cdr,
|
|
|
mode, FALSE, TRUE, FALSE,
|
|
|
/*s_buf = */ 2,
|
|
|
/*t_buf = */ 4);
|
|
|
map_trees_r (mappers, (grid_t*) pois_modes[mode], (grid_t*) cdr,
|
|
|
mode, TRUE, FALSE, TRUE,
|
|
|
/*s_buf = */ 1,
|
|
|
/*t_buf = */ 2);
|
|
|
}
|
|
|
}
|
|
|
cdr_free_coarser_grids (tree, extra_pois_levels);
|
|
|
|
|
|
return pois_modes;
|
|
|
}
|
|
|
|
|
|
/** @brief Solves a single Fourier mode.
|
|
|
*
|
|
|
* @a tree is the root of the @a cdr tree with extra_pois_levels added.
|
|
|
* @a cdr is the cdr grid at level 0, contained in tree:
|
|
|
*
|
|
|
* cdr = tree->first_child->firts_child...->first_child
|
|
|
* \_________ extra_pois_levels __________/
|
|
|
*/
|
|
|
pois_grid_t *
|
|
|
pois_solve_mode (cdr_grid_t *tree, cdr_grid_t *cdr, pois_problem_t *prob,
|
|
|
int mode, double es)
|
|
|
{
|
|
|
pois_grid_t *pois;
|
|
|
|
|
|
pois = pois_init_tree_a (0, 0, pois_gridpoints_r, pois_gridpoints_z);
|
|
|
|
|
|
/* Solves the coarsest Poisson grid. */
|
|
|
/* Changed Wed Mar 19 13:50:23 2008: was s_buf = 1. */
|
|
|
map_trees_r (charge_mappers, (grid_t *) tree, (grid_t *) pois,
|
|
|
mode, TRUE, TRUE, FALSE, 0, 2);
|
|
|
pois_solve_grid (pois, prob, -w2k[mode], es);
|
|
|
|
|
|
/* Wed May 14 11:00:30 2008:
|
|
|
* I divide pois_max_error by abs(wk[mode]) because in the calculation
|
|
|
* of e_theta, the errors get multiplied by wk[mode]. Hence if we
|
|
|
* want to get similar errors in all modes, we have to remove it from the
|
|
|
* threshold. The 1 is because wk[0] = 0, but we do not want to have
|
|
|
* a \inf threshold.
|
|
|
*/
|
|
|
pois_solve_r (pois->first_child, tree, prob, mode, es,
|
|
|
prob->max_error / (1 + abs(wk[mode])));
|
|
|
|
|
|
return pois;
|
|
|
}
|
|
|
|
|
|
#define POIS_SOLVE_R_MAX_WARN 50
|
|
|
|
|
|
/** @brief Recursively solves one Poisson grid. */
|
|
|
void
|
|
|
pois_solve_r (pois_grid_t *pois, cdr_grid_t *cdr, pois_problem_t *prob,
|
|
|
int mode, double es, double threshold)
|
|
|
{
|
|
|
pois_grid_t *child;
|
|
|
int succeeded = FALSE;
|
|
|
static int warn_cnt = POIS_SOLVE_R_MAX_WARN;
|
|
|
|
|
|
/* Changed Wed Mar 19 13:51:00 2008: was s_buf = 1 */
|
|
|
map_grid_r (charge_mappers, (grid_t *) cdr, (grid_t *) pois,
|
|
|
mode, TRUE, TRUE, FALSE, 0, 2);
|
|
|
debug(3, "w2k[mode = %d] = %f\n", mode, w2k[mode]);
|
|
|
|
|
|
pois_solve_grid (pois, prob, -w2k[mode], es);
|
|
|
|
|
|
if (pois->level >= prob->max_level) return;
|
|
|
|
|
|
pois_set_error (pois);
|
|
|
|
|
|
while (!succeeded) {
|
|
|
succeeded = pois_refine (pois, threshold);
|
|
|
|
|
|
if (!succeeded) {
|
|
|
threshold *= 2;
|
|
|
if (warn_cnt > 0) {
|
|
|
warning ("Poisson error threshold was too small: trying %g.\
|
|
|
Consider increasing pois_max_error\n",
|
|
|
threshold);
|
|
|
if (warn_cnt >= 0) warn_cnt --;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
iter_childs (pois, child) {
|
|
|
pois_solve_r (child, cdr, prob, mode, es, threshold);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/** @brief Returns 1 if the given cell has to be refined.
|
|
|
*
|
|
|
* which means that it satisfies at least one of the following:
|
|
|
* a. The error in the cell is larger than the threshold.
|
|
|
* b. The cell has a neightbour with an error larger than the threshold
|
|
|
*/
|
|
|
static int
|
|
|
needs_refinement (pois_grid_t *grid, int ir, int iz, double threshold)
|
|
|
{
|
|
|
int i, j;
|
|
|
int r = FALSE;
|
|
|
int irmin, irmax, izmin, izmax;
|
|
|
|
|
|
irmin = ir > grid->r0 + 3? ir - 1: grid->r0;
|
|
|
irmax = ir < grid->r1 - 4? ir + 1: grid->r1 - 1;
|
|
|
izmin = iz > grid->z0 + 3? iz - 1: grid->z0;
|
|
|
izmax = iz < grid->z1 - 4? iz + 1: grid->z1 - 1;
|
|
|
|
|
|
for (i = irmin; i <= irmax && !r; i++)
|
|
|
for (j = izmin; j <= izmax; j++)
|
|
|
if (RZ(grid->error, i, j) > threshold) {
|
|
|
r = TRUE;
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
return r;
|
|
|
}
|
|
|
|
|
|
/*****************************************************************************
|
|
|
* Functions for the implemetation of a needle-plate discharge. *
|
|
|
* The method used here is that of the "floating charge": *
|
|
|
* A charge is located at pois_inhom_z, which is used to keep the potential *
|
|
|
* at L_z constant. *
|
|
|
****************************************************************************/
|
|
|
|
|
|
/** @brief pois_inhom_init QQQQ */
|
|
|
void
|
|
|
pois_inhom_init (void)
|
|
|
{
|
|
|
/* The domain of solution of the Poisson equation will be different
|
|
|
* from that of the CDR equation. The difference between them is
|
|
|
* given by the parameter needle_length, but has to be rounded
|
|
|
* to a multiple of the grid size at level 0.
|
|
|
*/
|
|
|
pois_gridpoints_z = (gridpoints_z
|
|
|
+ (int) nearbyint (needle_length / dz[0]));
|
|
|
pois_inhom_L = pois_gridpoints_z * dz[0];
|
|
|
|
|
|
pois_inhom_z = L_z + needle_radius;
|
|
|
pois_inhom_dphi = pois_inhom_phi (0, pois_inhom_L) - pois_inhom_phi (0, L_z);
|
|
|
|
|
|
}
|
|
|
|
|
|
/* We use the following functions to calculate the fields and potential
|
|
|
* created by a unit charge located at (r = 0, z = b).
|
|
|
* These are created by the method of mirror charges, with a total of
|
|
|
* pois_inhom_reflections charges.
|
|
|
*/
|
|
|
|
|
|
/* Single term of phi. */
|
|
|
static double
|
|
|
inhom_phi_term (double r, double z, double b)
|
|
|
{
|
|
|
double insqrt;
|
|
|
|
|
|
insqrt = (SQ(z - b) + SQ(r));
|
|
|
|
|
|
return invfourpi / sqrt (insqrt);
|
|
|
}
|
|
|
|
|
|
/** @brief Common decay factor for the electric fields. */
|
|
|
static double
|
|
|
inhom_e_term (double r, double z, double b)
|
|
|
{
|
|
|
double insqrt;
|
|
|
|
|
|
insqrt = (SQ(z - b) + SQ(r));
|
|
|
insqrt = insqrt * insqrt * insqrt;
|
|
|
|
|
|
return invfourpi / sqrt (insqrt);
|
|
|
}
|
|
|
|
|
|
/** @brief Single term of E_r. */
|
|
|
static double
|
|
|
inhom_er_term (double r, double z, double b)
|
|
|
{
|
|
|
return r * inhom_e_term (r, z, b);
|
|
|
}
|
|
|
|
|
|
/** @brief Single term of E_z */
|
|
|
static double
|
|
|
inhom_ez_term (double r, double z, double b)
|
|
|
{
|
|
|
return (z - b) * inhom_e_term (r, z, b);
|
|
|
}
|
|
|
|
|
|
/* Since we have to sum over the same locations of the mirror charges
|
|
|
* but with different "field functions", we use the following macro.
|
|
|
*
|
|
|
* Note that using a single function for inhom_phi, inhom_er, and inhom_ez
|
|
|
* would not be a good idea since they are evaluated at different points.
|
|
|
* Passing that function a pointer to another function would have been somewhat
|
|
|
* slower and, I think, not so readable.
|
|
|
*/
|
|
|
#define sum_reflections(total_, field_, r_, z_) \
|
|
|
do { \
|
|
|
int i__; \
|
|
|
total_ = (field_ (r_, z_, pois_inhom_z) - \
|
|
|
field_ (r_, z_, -pois_inhom_z)); \
|
|
|
\
|
|
|
for (i__ = 2; i__ <= pois_inhom_reflections; i__ += 2) { \
|
|
|
total_ += (field_ (r_, z_, pois_inhom_z + i__ * pois_inhom_L) + \
|
|
|
field_ (r_, z_, pois_inhom_z - i__ * pois_inhom_L)); \
|
|
|
\
|
|
|
total_ -= (field_ (r_, z_, -pois_inhom_z + i__ * pois_inhom_L) + \
|
|
|
field_ (r_, z_, -pois_inhom_z - i__ * pois_inhom_L)); \
|
|
|
} \
|
|
|
} while (0)
|
|
|
|
|
|
/** @brief Returns the potential created by a unit charge located at pois_inhom_z
|
|
|
* between an electrode at 0 and a second one at pois_inhom_L.
|
|
|
*/
|
|
|
double
|
|
|
pois_inhom_phi (double r, double z)
|
|
|
{
|
|
|
double res;
|
|
|
|
|
|
sum_reflections (res, inhom_phi_term, r, z);
|
|
|
|
|
|
return res;
|
|
|
}
|
|
|
|
|
|
/** @brief Radial component of the field. */
|
|
|
double
|
|
|
pois_inhom_er (double r, double z)
|
|
|
{
|
|
|
double res;
|
|
|
|
|
|
sum_reflections (res, inhom_er_term, r, z);
|
|
|
|
|
|
return res;
|
|
|
}
|
|
|
|
|
|
/** @brief Axial component of the field. */
|
|
|
double
|
|
|
pois_inhom_ez (double r, double z)
|
|
|
{
|
|
|
double res;
|
|
|
|
|
|
sum_reflections (res, inhom_ez_term, r, z);
|
|
|
|
|
|
return res;
|
|
|
}
|
|
|
|
|
|
/** @brief Once we have the electrostatic potential created by the space charges,
|
|
|
* we use this function to compute the multiplying factor of the floating
|
|
|
* charge.
|
|
|
*
|
|
|
* One can also forget about keeping the potential fixed somewhere and impose
|
|
|
* a constant (non-floating, but may depend on time) charge pois_inhom_fixed_q,
|
|
|
* which is used if nonzero. The use of that is to simulate a charged cloud
|
|
|
* close to the earth, which acts as an electrode: usually you would also combine
|
|
|
* that with the sprite module, that allows varing neutral densities.
|
|
|
*/
|
|
|
double
|
|
|
pois_inhom_q_factor (pois_grid_t *pois)
|
|
|
{
|
|
|
double u;
|
|
|
/* Note that the signs preceding the numerical potentials are inverted since
|
|
|
* in our notation, we use the opposite of the physical one, in order
|
|
|
* to use the charge as the source of the Poisson equation.
|
|
|
*/
|
|
|
|
|
|
if (pois_inhom_fixed_q != 0.0) {
|
|
|
return pois_inhom_fixed_q_t;
|
|
|
}
|
|
|
|
|
|
/* Was:
|
|
|
u = - E_z * (pois_inhom_L - L_z) + pois_phi_at (pois, 0.0, L_z, 0.0);
|
|
|
*/
|
|
|
|
|
|
u = E_z * (L_z - pois_inhom_L) + pois_phi_at (pois, 0.0, L_z, 0.0);
|
|
|
|
|
|
return -u / pois_inhom_dphi;
|
|
|
}
|
|
|
|
|
|
/** @brief Adds the potential created by the needle to a Poisson grid and their
|
|
|
* descendants.
|
|
|
*
|
|
|
* Note that \f$\phi\f$ here is in Fourier space and the Laplacian
|
|
|
* potential is always axi-symmetric, so you only have to call this function
|
|
|
* with the zero-mode of the Poisson grids.
|
|
|
*
|
|
|
* The reason that it is better to add the inhomogeneous field to the potential
|
|
|
* and not later to the electric fields is that if we do that, we will
|
|
|
* have to substract two large and very curved functions that give something
|
|
|
* close to zero: the error we make then will be comparable to the result.
|
|
|
*/
|
|
|
void
|
|
|
pois_add_inhom_phi_r (pois_grid_t *grid, double q)
|
|
|
{
|
|
|
int ir, iz;
|
|
|
pois_grid_t *child;
|
|
|
double res;
|
|
|
|
|
|
debug (2, "pois_add_inhom_phi_r (" grid_printf_str ", q = %f)\n",
|
|
|
grid_printf_args(grid), q);
|
|
|
|
|
|
/* Note that there is no factor grid->ntheta coming from thr FFT
|
|
|
un-normalization. The reason is that actually q is sqrt(N) smaller
|
|
|
than it has to be and later, in the inverse FFT we will multiply by
|
|
|
sqrt(N). So everything is correct, I believe. */
|
|
|
iter_grid_n (grid, ir, iz, 2) {
|
|
|
|
|
|
sum_reflections (res, inhom_phi_term, r_at(ir, grid->level), z_at(iz, grid->level));
|
|
|
|
|
|
|
|
|
RZ(grid->phi, ir, iz) += q * res;
|
|
|
}
|
|
|
|
|
|
iter_childs (grid, child) {
|
|
|
pois_add_inhom_phi_r (child, q);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/***************************************************************
|
|
|
* Mapper functions for the components of the electric field. *
|
|
|
* See mapper.h for an explanation of each of these methods. *
|
|
|
***************************************************************/
|
|
|
|
|
|
/** @brief er_copy QQQQ */
|
|
|
void
|
|
|
er_copy (mapper_t *mapper, grid_t *source, grid_t *target,
|
|
|
int ir, int iz, int itheta)
|
|
|
{
|
|
|
cdr_grid_t *cdr;
|
|
|
pois_grid_t *pois;
|
|
|
|
|
|
cdr = (cdr_grid_t*) target;
|
|
|
pois = (pois_grid_t*) source;
|
|
|
|
|
|
if (ir < pois->r1)
|
|
|
RZT (cdr->er, ir, iz, itheta) = ER_RZ (pois, ir, iz);
|
|
|
}
|
|
|
|
|
|
/** @brief ez_copy QQQQ */
|
|
|
void
|
|
|
ez_copy (mapper_t *mapper, grid_t *source, grid_t *target,
|
|
|
int ir, int iz, int itheta)
|
|
|
{
|
|
|
cdr_grid_t *cdr;
|
|
|
pois_grid_t *pois;
|
|
|
|
|
|
cdr = (cdr_grid_t*) target;
|
|
|
pois = (pois_grid_t*) source;
|
|
|
|
|
|
if (iz < pois->z1)
|
|
|
RZT (cdr->ez, ir, iz, itheta) = EZ_RZ (pois, ir, iz);
|
|
|
}
|
|
|
|
|
|
/** @brief etheta_copy QQQQ */
|
|
|
void
|
|
|
etheta_copy (mapper_t *mapper, grid_t *source, grid_t *target,
|
|
|
int ir, int iz, int itheta)
|
|
|
{
|
|
|
cdr_grid_t *cdr;
|
|
|
pois_grid_t *pois;
|
|
|
|
|
|
cdr = (cdr_grid_t*) target;
|
|
|
pois = (pois_grid_t*) source;
|
|
|
|
|
|
RZT (cdr->etheta, ir, iz, itheta) = RZ (pois->phi, ir, iz);
|
|
|
}
|
|
|
|
|
|
/** @brief er_coarsen QQQQ */
|
|
|
void
|
|
|
er_coarsen (mapper_t *mapper, grid_t *source, grid_t *target,
|
|
|
int ir, int iz, int itheta)
|
|
|
{
|
|
|
cdr_grid_t *cdr;
|
|
|
pois_grid_t *pois;
|
|
|
int level_diff, er_ir, er_iz;
|
|
|
|
|
|
cdr = (cdr_grid_t*) target;
|
|
|
pois = (pois_grid_t*) source;
|
|
|
|
|
|
level_diff = pois->level - cdr->level;
|
|
|
|
|
|
er_iz = (iz << level_diff) + (1 << (level_diff - 1));
|
|
|
er_ir = ((ir + 1) << level_diff) - 1;
|
|
|
|
|
|
if (grid_contains (source, er_ir, er_iz - 1, GRID_INSIDE) &&
|
|
|
grid_contains (source, er_ir, er_iz, GRID_INSIDE)) {
|
|
|
|
|
|
RZT (cdr->er, ir, iz, itheta) = 0.5 * (ER_RZ(pois, er_ir, er_iz) +
|
|
|
ER_RZ(pois, er_ir, er_iz - 1));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/** @brief ez_coarsen QQQQ */
|
|
|
void
|
|
|
ez_coarsen (mapper_t *mapper, grid_t *source, grid_t *target,
|
|
|
int ir, int iz, int itheta)
|
|
|
{
|
|
|
cdr_grid_t *cdr;
|
|
|
pois_grid_t *pois;
|
|
|
int level_diff, ez_ir, ez_iz;
|
|
|
|
|
|
cdr = (cdr_grid_t*) target;
|
|
|
pois = (pois_grid_t*) source;
|
|
|
|
|
|
level_diff = pois->level - cdr->level;
|
|
|
|
|
|
ez_iz = ((iz + 1) << level_diff) - 1;
|
|
|
ez_ir = (ir << level_diff) + (1 << (level_diff - 1));
|
|
|
|
|
|
if (grid_contains (source, ez_ir - 1, ez_iz, GRID_INSIDE) &&
|
|
|
grid_contains (source, ez_ir, ez_iz, GRID_INSIDE)) {
|
|
|
|
|
|
RZT(cdr->ez, ir, iz, itheta) = 0.5 * (EZ_RZ(pois, ez_ir, ez_iz) +
|
|
|
EZ_RZ(pois, ez_ir - 1, ez_iz));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/** @brief etheta_coarsen QQQQ */
|
|
|
void
|
|
|
etheta_coarsen (mapper_t *mapper, grid_t *source, grid_t *target,
|
|
|
int ir, int iz, int itheta)
|
|
|
{
|
|
|
cdr_grid_t *cdr;
|
|
|
pois_grid_t *pois;
|
|
|
int level_diff, ez_ir, er_iz;
|
|
|
|
|
|
cdr = (cdr_grid_t*) target;
|
|
|
pois = (pois_grid_t*) source;
|
|
|
|
|
|
level_diff = pois->level - cdr->level;
|
|
|
|
|
|
er_iz = (iz << level_diff) + (1 << (level_diff - 1));
|
|
|
ez_ir = (ir << level_diff) + (1 << (level_diff - 1));
|
|
|
|
|
|
if (grid_contains (source, ez_ir, er_iz, GRID_INSIDE) &&
|
|
|
grid_contains (source, ez_ir - 1, er_iz, GRID_INSIDE) &&
|
|
|
grid_contains (source, ez_ir, er_iz - 1, GRID_INSIDE) &&
|
|
|
grid_contains (source, ez_ir - 1, er_iz - 1, GRID_INSIDE)) {
|
|
|
|
|
|
RZT (cdr->etheta, ir, iz, itheta) = 0.25 *
|
|
|
(RZ (pois->phi, ez_ir, er_iz)
|
|
|
+ RZ (pois->phi, ez_ir - 1, er_iz)
|
|
|
+ RZ (pois->phi, ez_ir, er_iz - 1)
|
|
|
+ RZ (pois->phi, ez_ir - 1, er_iz - 1));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/** @brief er_interpol QQQQ */
|
|
|
int
|
|
|
er_interpol_set (mapper_t *mapper, grid_t *source, interpol_t *interpol,
|
|
|
int pr, int pz, int itheta)
|
|
|
{
|
|
|
pois_grid_t *pois;
|
|
|
|
|
|
pois = (pois_grid_t*) source;
|
|
|
|
|
|
/* When we set Neumann b.c. in one boundary and Dirichlet in the opposite
|
|
|
* a larger-than-usual error appears in the boundaries because we are
|
|
|
* matching potetntials in one side and fields in the other. The only way
|
|
|
* I see of avoiding this is to forget about the extrapolated values of
|
|
|
* the grids. But we still need those values on the external boundaries.
|
|
|
*/
|
|
|
if (pr < pois->r0 || pz < pois->z0
|
|
|
|| pr > pois->r1 - 1 || pz > pois->z1)
|
|
|
return FALSE;
|
|
|
|
|
|
interpol_set_stencil (interpol,
|
|
|
er_r_at (pr - 1, pois->level),
|
|
|
er_z_at (pz - 1, pois->level),
|
|
|
ER_RZ (pois, pr - 1, pz - 1),
|
|
|
ER_RZ (pois, pr - 1, pz),
|
|
|
ER_RZ (pois, pr, pz - 1),
|
|
|
ER_RZ (pois, pr, pz));
|
|
|
|
|
|
return TRUE;
|
|
|
}
|
|
|
|
|
|
/** @brief ez_interpol_set QQQQ */
|
|
|
int
|
|
|
ez_interpol_set (mapper_t *mapper, grid_t *source, interpol_t *interpol,
|
|
|
int pr, int pz, int itheta)
|
|
|
{
|
|
|
pois_grid_t *pois;
|
|
|
|
|
|
pois = (pois_grid_t*) source;
|
|
|
|
|
|
if (pr < pois->r0 || pz < pois->z0
|
|
|
|| pr > pois->r1 || pz > pois->z1 - 1)
|
|
|
return FALSE;
|
|
|
|
|
|
interpol_set_stencil (interpol,
|
|
|
ez_r_at (pr - 1, pois->level),
|
|
|
ez_z_at (pz - 1, pois->level),
|
|
|
EZ_RZ (pois, pr - 1, pz - 1),
|
|
|
EZ_RZ (pois, pr - 1, pz),
|
|
|
EZ_RZ (pois, pr, pz - 1),
|
|
|
EZ_RZ (pois, pr, pz));
|
|
|
return TRUE;
|
|
|
}
|
|
|
|
|
|
/** @brief etheta_interpol_set QQQQ */
|
|
|
int
|
|
|
etheta_interpol_set (mapper_t *mapper, grid_t *source, interpol_t *interpol,
|
|
|
int pr, int pz, int itheta)
|
|
|
{
|
|
|
pois_grid_t *pois;
|
|
|
|
|
|
pois = (pois_grid_t*) source;
|
|
|
|
|
|
if (pr < pois->r0 || pz < pois->z0
|
|
|
|| pr > pois->r1 - 1|| pz > pois->z1 - 1)
|
|
|
return FALSE;
|
|
|
|
|
|
interpol_set_stencil_at (source, interpol,
|
|
|
r_at (pr, pois->level),
|
|
|
z_at (pz, pois->level),
|
|
|
pois->phi, pr, pz, 0);
|
|
|
return TRUE;
|
|
|
}
|
|
|
|
|
|
/** @brief er_interpol QQQQ */
|
|
|
void
|
|
|
er_interpol (mapper_t *mapper, grid_t *source, grid_t *target,
|
|
|
interpol_t *interpol, int ir, int iz, int itheta)
|
|
|
{
|
|
|
double er_r, er_z;
|
|
|
cdr_grid_t *cdr;
|
|
|
pois_grid_t *pois;
|
|
|
int level_diff;
|
|
|
|
|
|
cdr = (cdr_grid_t *) target;
|
|
|
pois = (pois_grid_t *) source;
|
|
|
|
|
|
level_diff = cdr->level - pois->level;
|
|
|
|
|
|
/* Note that when pr = pois->r1, the electric field is calculated
|
|
|
* from the extrapolation of phi and it is hence less accurate.
|
|
|
* The same applies below to pz and pois->z1.
|
|
|
*/
|
|
|
if ((ir >> level_diff) >= pois->r1) return;
|
|
|
|
|
|
er_r = er_r_at (ir, cdr->level);
|
|
|
er_z = er_z_at (iz, cdr->level);
|
|
|
|
|
|
RZT (cdr->er, ir, iz, itheta) = interpol_apply (interpol, er_r, er_z);
|
|
|
}
|
|
|
|
|
|
/** @brief ez_interpol QQQQ */
|
|
|
void
|
|
|
ez_interpol (mapper_t *mapper, grid_t *source, grid_t *target,
|
|
|
interpol_t *interpol, int ir, int iz, int itheta)
|
|
|
{
|
|
|
double ez_r, ez_z;
|
|
|
cdr_grid_t *cdr;
|
|
|
pois_grid_t *pois;
|
|
|
int level_diff;
|
|
|
|
|
|
cdr = (cdr_grid_t *) target;
|
|
|
pois = (pois_grid_t *) source;
|
|
|
|
|
|
level_diff = cdr->level - pois->level;
|
|
|
|
|
|
if ((iz >> level_diff) >= pois->z1) return;
|
|
|
|
|
|
ez_r = ez_r_at (ir, cdr->level);
|
|
|
ez_z = ez_z_at (iz, cdr->level);
|
|
|
|
|
|
RZT (cdr->ez, ir, iz, itheta) = interpol_apply (interpol, ez_r, ez_z);
|
|
|
}
|
|
|
|
|
|
/** @brief etheta_interpol QQQQ */
|
|
|
void
|
|
|
etheta_interpol (mapper_t *mapper, grid_t *source, grid_t *target,
|
|
|
interpol_t *interpol, int ir, int iz, int itheta)
|
|
|
{
|
|
|
double eth_r, eth_z;
|
|
|
cdr_grid_t *cdr;
|
|
|
|
|
|
cdr = (cdr_grid_t *) target;
|
|
|
|
|
|
eth_r = r_at (ir, cdr->level);
|
|
|
eth_z = z_at (iz, cdr->level);
|
|
|
|
|
|
RZT (cdr->etheta, ir, iz, itheta) =
|
|
|
interpol_apply (interpol, eth_r, eth_z);
|
|
|
}
|
|
|
|
|
|
/** @brief Mapping of the charge. */
|
|
|
void
|
|
|
charge_copy (mapper_t *mapper, grid_t *source, grid_t *target,
|
|
|
int ir, int iz, int itheta)
|
|
|
{
|
|
|
cdr_grid_t *cdr;
|
|
|
pois_grid_t *pois;
|
|
|
|
|
|
pois = (pois_grid_t*) target;
|
|
|
cdr = (cdr_grid_t*) source;
|
|
|
|
|
|
RZ (pois->charge, ir, iz) = RZT (cdr->charge, ir, iz, itheta);
|
|
|
}
|
|
|
|
|
|
/** @brief charge_interpol_set QQQQ. */
|
|
|
int
|
|
|
charge_interpol_set (mapper_t *mapper, grid_t *source, interpol_t *interpol,
|
|
|
int pr, int pz, int itheta)
|
|
|
{
|
|
|
cdr_grid_t *cdr;
|
|
|
|
|
|
cdr = (cdr_grid_t*) source;
|
|
|
|
|
|
if (pr < cdr->r0 || pz < cdr->z0
|
|
|
|| pr > cdr->r1 - 1|| pz > cdr->z1 - 1)
|
|
|
return FALSE;
|
|
|
|
|
|
interpol_set_stencil_at (source, interpol,
|
|
|
r_at (pr, cdr->level),
|
|
|
z_at (pz, cdr->level),
|
|
|
cdr->charge, pr, pz, itheta);
|
|
|
return TRUE;
|
|
|
}
|
|
|
|
|
|
/** @brief charge_interpol QQQQ. */
|
|
|
void
|
|
|
charge_interpol (mapper_t *mapper, grid_t *source, grid_t *target,
|
|
|
interpol_t *interpol,
|
|
|
int ir, int iz, int itheta)
|
|
|
{
|
|
|
double r, z;
|
|
|
pois_grid_t *pois;
|
|
|
|
|
|
pois = (pois_grid_t *) target;
|
|
|
|
|
|
r = r_at (ir, pois->level);
|
|
|
z = z_at (iz, pois->level);
|
|
|
|
|
|
RZT (pois->charge, ir, iz, 0) = interpol_apply (interpol, r, z);
|
|
|
}
|
|
|
|
|
|
/** @brief Finds the best-possible approximation for the potential at a given
|
|
|
* point (@a r, @a z, @a theta) by interpolating from the finest grid that covers
|
|
|
* that point.
|
|
|
*/
|
|
|
double
|
|
|
pois_phi_at (pois_grid_t *grid, double r, double z, double theta)
|
|
|
{
|
|
|
int ir, iz, itheta, it, itn;
|
|
|
REAL phi_it[2];
|
|
|
interpol_t *phi_at_interpol = NULL;
|
|
|
|
|
|
debug (3, "pois_phi_at (" grid_printf_str ", r = %g, z = %g, theta = %g)\n",
|
|
|
grid_printf_args (grid), r, z, theta);
|
|
|
|
|
|
grid = (pois_grid_t *) grid_finest_containing_r ((grid_t*) grid, r, z);
|
|
|
|
|
|
if (NULL == grid) {
|
|
|
fatal ("In pois_phi_at: potential outside the grid");
|
|
|
}
|
|
|
|
|
|
phi_at_interpol = interpol_new_a (dr[grid->level], dz[grid->level],
|
|
|
&interpol_quadratic);
|
|
|
|
|
|
ir = (int) (r / dr[grid->level]);
|
|
|
iz = (int) (z / dz[grid->level]);
|
|
|
|
|
|
/* If r == 0, it doesn't matter which theta we use, so we use 0.
|
|
|
* also if we are in a 2D simulation.
|
|
|
*/
|
|
|
if (grid->ntheta > 1 && r > 0.0) {
|
|
|
itheta = (int) (theta / dtheta);
|
|
|
itn = 2;
|
|
|
} else {
|
|
|
itheta = 0;
|
|
|
itn = 1;
|
|
|
}
|
|
|
|
|
|
for (it = 0; it < itn; it++) {
|
|
|
interpol_set_stencil_at ((grid_t *) grid, phi_at_interpol,
|
|
|
r_at (ir, grid->level),
|
|
|
z_at (iz, grid->level),
|
|
|
grid->phi, ir, iz, itheta + it);
|
|
|
|
|
|
phi_it[it] = interpol_apply (phi_at_interpol, r, z);
|
|
|
}
|
|
|
|
|
|
interpol_free (phi_at_interpol);
|
|
|
|
|
|
if (grid->ntheta > 1 && r > 0.0) {
|
|
|
return (phi_it[0] * (theta - theta_at (itheta)) +
|
|
|
phi_it[1] * (theta_at (itheta + 1) - theta)) / dtheta;
|
|
|
} else {
|
|
|
return phi_it[0];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/** @brief Dumps all the contents of the given grid into filenames given by
|
|
|
* prefix and name
|
|
|
*/
|
|
|
void
|
|
|
pois_dump (pois_grid_t *grid, const char *prefix, const char *name)
|
|
|
{
|
|
|
char *fname;
|
|
|
int m = pois_output_margin;
|
|
|
|
|
|
asprintf (&fname, "%s/r.%s.tsv", prefix, name);
|
|
|
rz_axis_dump (fname, grid->r0 - m, grid->r1 + m, dr[grid->level]);
|
|
|
free (fname);
|
|
|
|
|
|
asprintf (&fname, "%s/z.%s.tsv", prefix, name);
|
|
|
rz_axis_dump (fname, grid->z0 - m, grid->z1 + m, dz[grid->level]);
|
|
|
free (fname);
|
|
|
|
|
|
asprintf (&fname, "%s/phi.%s.tsv", prefix, name);
|
|
|
rz_dump (grid->phi, fname, "w", grid->r0 - m, grid->z0 - m,
|
|
|
grid->r1 + m, grid->z1 + m);
|
|
|
free (fname);
|
|
|
|
|
|
asprintf (&fname, "%s/charge.%s.tsv", prefix, name);
|
|
|
rz_dump (grid->charge, fname, "w", grid->r0 - m, grid->z0 - m,
|
|
|
grid->r1 + m, grid->z1 + m);
|
|
|
free (fname);
|
|
|
|
|
|
asprintf (&fname, "%s/error.%s.tsv", prefix, name);
|
|
|
rz_dump (grid->error, fname, "w", grid->r0 - m, grid->z0 - m,
|
|
|
grid->r1 + m, grid->z1 + m);
|
|
|
free (fname);
|
|
|
}
|
|
|
|
|
|
/** @brief Dumps all the contents of the given grid into filenames given by
|
|
|
* prefix and name
|
|
|
*/
|
|
|
void
|
|
|
pois_dump_r (pois_grid_t *grid, const char *prefix, const char *name)
|
|
|
{
|
|
|
pois_grid_t *child;
|
|
|
char *cname;
|
|
|
int i = 0, nchilds;
|
|
|
char codes[] = "abcdefghijklmnopqrstuvwxyz";
|
|
|
|
|
|
pois_dump (grid, prefix, name);
|
|
|
|
|
|
nchilds = grid_howmany_children ((grid_t *) grid);
|
|
|
|
|
|
for (i = 0; i < nchilds; i++) {
|
|
|
child = (pois_grid_t *) grid_get_child ((grid_t*) grid, nchilds - i - 1);
|
|
|
|
|
|
assert (NULL != child);
|
|
|
|
|
|
if (i > sizeof(codes) - 2)
|
|
|
asprintf (&cname, "%s{%d}", name, i);
|
|
|
else
|
|
|
asprintf (&cname, "%s%c", name, codes[i]);
|
|
|
|
|
|
pois_dump_r (child, prefix, cname);
|
|
|
free (cname);
|
|
|
}
|
|
|
}
|