コード例 #1
0
def LCD_by_rejection(pos, vel, sf_par, sf_per, st, en, jj):
    '''
    Takes in a Maxwellian or pseudo-maxwellian distribution. Outputs the number
    and indexes of any particle inside the loss cone
    '''
    B0x    = fields.eval_B0x(pos[st: en])
    N_loss = 1

    while N_loss > 0:
        v_perp      = np.sqrt(vel[1, st: en] ** 2 + vel[2, st: en] ** 2)
        
        N_loss, loss_idx = calc_losses(vel[0, st: en], v_perp, B0x, st=st)

        # Catch for a particle on the boundary : Set 90 degree pitch angle (gyrophase shouldn't overly matter)
        if N_loss == 1:
            if abs(pos[loss_idx[0]]) == const.xmax:
                ww = loss_idx[0]
                vel[0, loss_idx[0]] = 0.
                vel[1, loss_idx[0]] = np.sqrt(vel[0, ww] ** 2 + vel[1, ww] ** 2 + vel[2, ww] ** 2)
                vel[2, loss_idx[0]] = 0.
                N_loss = 0

        if N_loss != 0:   
            new_vx = np.random.normal(0., sf_par, N_loss)             
            new_vy = np.random.normal(0., sf_per, N_loss)
            new_vz = np.random.normal(0., sf_per, N_loss)
            
            for ii in range(N_loss):
                vel[0, loss_idx[ii]] = new_vx[ii]
                vel[1, loss_idx[ii]] = new_vy[ii]
                vel[2, loss_idx[ii]] = new_vz[ii]
    return
コード例 #2
0
def init_totally_random():
    pos = np.zeros((3, N), dtype=np.float64)
    vel = np.zeros((3, N), dtype=np.float64)
    idx = np.ones(N, dtype=np.int8) * -1
    np.random.seed(seed)

    for jj in range(Nj):
        idx[idx_start[jj]:idx_end[jj]] = jj  # Set particle idx

        # Particle index ranges
        st = idx_start[jj]
        en = idx_end[jj]

        pos[0, st:en] = np.random.uniform(xmin, xmax, en - st)
        vel[0, st:en] = np.random.normal(0, vth_par[jj], en - st) + drift_v[jj]
        vel[1, st:en] = np.random.normal(0, vth_perp[jj], en - st)
        vel[2, st:en] = np.random.normal(0, vth_perp[jj], en - st)

    print('Initializing particles off-axis')
    B0x = fields.eval_B0x(pos[0, :en])
    v_perp = np.sqrt(vel[1, :en]**2 + vel[2, :en]**2)
    gyangle = get_gyroangle_array(vel[:, :en])
    rL = v_perp / (qm_ratios[idx[:en]] * B0x)
    pos[1, :en] = rL * np.cos(gyangle)
    pos[2, :en] = rL * np.sin(gyangle)
    return pos, vel, idx
コード例 #3
0
ファイル: particles_1D.py プロジェクト: cycle13/hybrid
def eval_B0_particle(pos, Bp):
    '''
    Calculates the B0 magnetic field at the position of a particle. B0x is
    non-uniform in space, and B0r (split into y,z components) is the required
    value to keep div(B) = 0
    
    These values are added onto the existing value of B at the particle location,
    Bp. B0x is simply equated since we never expect a non-zero wave field in x.
    '''
    constant = -a * B_eq
    Bp[0] = eval_B0x(pos[0])
    Bp[1] += constant * pos[0] * pos[1]
    Bp[2] += constant * pos[0] * pos[2]
    return
コード例 #4
0
def eval_B0_exact(x, v, qmi, approx):
    '''
    DIAGNOSTIC FUNCTION
    Solves (maybe?) the exact solution for By,Bz radial (coupled quadratic
    equations of their squares)
    
    Need to code this manually to separate out the two real solutions.
    
    The +det term of the quadratic always seems to be nan? Probably some 
    mathematical reason for this (b < 4ac? Check later)
    
    Just return positive results for each for now, but don't assume this will work
    generally - just for this particle.
    
    How to work out if it should be the positive or negative solution 
    (of the sqrt) taken? Use v_perp cross B?
    
    Currently using approximation as guide - not the best solution, but it works
    '''
    B0_particle = np.zeros(3)
    B0_x        = eval_B0x(x)
    K2          = (a * B_eq * x / qmi) ** 2     # Constant for calculation
    
    # Quadratic coefficients of R = By ** 2 :: 4 solutions
    ay = (v[1] ** 2 + v[2] ** 2) * (-K2)
    by = - v[2] ** 2 * K2 * B0_x ** 2
    cy = (v[2] ** 2 * K2) ** 2
    
    B0_y = np.sqrt(solve_quadratic(ay, by, cy))
    
    # Quadratic coefficients of Q = Bz ** 2 :: 4 solutions
    az = (v[1] ** 2 + v[2] ** 2) * (-K2)
    bz = - v[1] ** 2 * K2 * B0_x ** 2
    cz = (v[1] ** 2 * K2) ** 2
    
    B0_z = np.sqrt(solve_quadratic(az, bz, cz))
    
    # All three of these will always be positive
    # Need a way to work out how to set +/- status
    B0_particle[0] = B0_x
    B0_particle[1] = B0_y
    B0_particle[2] = B0_z
    
    if approx[1] < 0:
        B0_particle[1] *= -1.0
        
    if approx[2] < 0:
        B0_particle[2] *= -1.0
    
    return B0_particle
コード例 #5
0
ファイル: auxilliary_1D.py プロジェクト: cycle13/hybrid
def interpolate_edges_to_center(B, interp, zero_boundaries=False):
    ''' 
    Used for interpolating values on the B-grid to the E-grid (for E-field calculation)
    with a 3D array (e.g. B). Second derivative y2 is calculated on the B-grid, with
    forwards/backwards difference used for endpoints.
    
    interp has one more gridpoint than required just because of the array used. interp[-1]
    should remain zero.
    
    This might be able to be done without the intermediate y2 array since the interpolated
    points don't require previous point values.
    
    ADDS B0 TO X-AXIS ON TOP OF INTERPOLATION
    '''
    y2 = np.zeros(B.shape, dtype=nb.float64)
    interp *= 0.

    # Calculate second derivative
    for jj in range(1, B.shape[1]):

        # Interior B-nodes, Centered difference
        for ii in range(1, NC):
            y2[ii, jj] = B[ii + 1, jj] - 2 * B[ii, jj] + B[ii - 1, jj]

        # Edge B-nodes, Forwards/Backwards difference
        if zero_boundaries == True:
            y2[0, jj] = 0.
            y2[NC, jj] = 0.
        else:
            y2[0, jj] = 2 * B[0, jj] - 5 * B[1, jj] + 4 * B[2, jj] - B[3, jj]
            y2[NC,
               jj] = 2 * B[NC, jj] - 5 * B[NC - 1, jj] + 4 * B[NC - 2, jj] - B[
                   NC - 3, jj]

    # Do spline interpolation: E[ii] is bracketed by B[ii], B[ii + 1]
    for jj in range(1, B.shape[1]):
        for ii in range(NC):
            interp[ii, jj] = 0.5 * (B[ii, jj] + B[ii + 1, jj] + (1 / 6) *
                                    (y2[ii, jj] + y2[ii + 1, jj]))

    # Add B0x to interpolated array
    for ii in range(NC):
        interp[ii, 0] = fields.eval_B0x(E_nodes[ii])

    # This bit could be removed to allow B0x to vary in green cells naturally
    # interp[:ND,      0] = interp[ND,    0]
    # interp[ND+NX+1:, 0] = interp[ND+NX, 0]
    return
コード例 #6
0
ファイル: particles_1D.py プロジェクト: cycle13/hybrid
def eval_B0_particle_1D(pos, vel, Bp, qm):
    '''
    Calculates the B0 magnetic field at the position of a particle. B0x is
    non-uniform in space, and B0r (split into y,z components) is the required
    value to keep div(B) = 0
    
    These values are added onto the existing value of B at the particle location,
    Bp. B0x is simply equated since we never expect a non-zero wave field in x.
        
    Could totally vectorise this. Would have to change to give a particle_temp
    array for memory allocation or something
    '''
    Bp[0] = eval_B0x(pos[0])
    cyc_fac = a * B_eq * pos[0] / (qm * Bp[0])

    Bp[1] += vel[2] * cyc_fac
    Bp[2] -= vel[1] * cyc_fac
    return
コード例 #7
0
def eval_B0_particle(x, v, qmi, b1):
    '''
    Calculates the B0 magnetic field at the position of a particle. Neglects B0_r
    and thus local cyclotron depends only on B0_x. Includes b1 in cyclotron, but
    since b1 < B0_r, maybe don't?
    
    Also, how accurate is this near the equator?
    '''
    B0_xp    = np.zeros(3)
    B0_xp[0] = eval_B0x(x)    
    
    b1t      = np.sqrt(b1[0] ** 2 + b1[1] ** 2 + b1[2] ** 2)
    l_cyc    = qmi * (B0_xp[0] + b1t)
    fac      = a * B_eq * x / l_cyc
    
    B0_xp[1] =  v[2] * fac
    B0_xp[2] = -v[1] * fac
    return B0_xp
コード例 #8
0
ファイル: particles_1D.py プロジェクト: cycle13/hybrid
def eval_B0_particle(pos, Bp):
    '''
    Calculates the B0 magnetic field at the position of a particle. B0x is
    non-uniform in space, and B0r (split into y,z components) is the required
    value to keep div(B) = 0
    
    These values are added onto the existing value of B at the particle location,
    Bp. B0x is simply equated since we never expect a non-zero wave field in x.
        
    Could totally vectorise this. Would have to change to give a particle_temp
    array for memory allocation or something
    '''
    rL = np.sqrt(pos[1]**2 + pos[2]**2)

    B0_r = -a * B_eq * pos[0] * rL
    Bp[0] = eval_B0x(pos[0])
    Bp[1] += B0_r * pos[1] / rL
    Bp[2] += B0_r * pos[2] / rL
    return
コード例 #9
0
ファイル: particles_1D.py プロジェクト: cycle13/hybrid
def eval_B0_particle_1D(pos, vel, idx, Bp):
    '''
    Calculates the B0 magnetic field at the position of a particle. B0x is
    non-uniform in space, and B0r (split into y,z components) is the required
    value to keep div(B) = 0
    
    These values are added onto the existing value of B at the particle location,
    Bp. B0x is simply equated since we never expect a non-zero wave field in x.
        
    Could totally vectorise this. Would have to change to give a particle_temp
    array for memory allocation or something
    '''
    Bp[0] = eval_B0x(pos[0])
    constant = a * B_eq
    for ii in range(idx.shape[0]):
        l_cyc = qm_ratios[idx[ii]] * Bp[0, ii]

        Bp[1, ii] += constant * pos[0, ii] * vel[2, ii] / l_cyc
        Bp[2, ii] -= constant * pos[0, ii] * vel[1, ii] / l_cyc
    return
コード例 #10
0
ファイル: particles_1D.py プロジェクト: cycle13/hybrid
def position_update(pos, vel, idx, DT, Ie, W_elec):
    '''
    Updates the position of the particles using x = x0 + vt. 
    Also updates particle nearest node and weighting.

    INPUT:
        pos    -- Particle position array (Also output) 
        vel    -- Particle velocity array (Also output for reflection)
        idx    -- Particle index    array (Also output for reflection)
        DT     -- Simulation time step
        Ie     -- Particle leftmost to nearest node array (Also output)
        W_elec -- Particle weighting array (Also output)
        
    Note: This function also controls what happens when a particle leaves the 
    simulation boundary.
    
    NOTE :: This reinitialization thing is super unoptimized and inefficient.
            Maybe initialize array of n_ppc samples for each species, to at least vectorize it
            Count each one being used, start generating new ones if it runs out (but it shouldn't)
            
    # 28/05/2020 :: Removed np.abs() and -np.sign() factors from v_x calculation
    # See if that helps better simulate the distro function (will "lose" more
    # particles at boundaries, but that'll just slow things down a bit - should
    # still be valid)
    '''
    pos[0, :] += vel[0, :] * DT
    pos[1, :] += vel[1, :] * DT
    pos[2, :] += vel[2, :] * DT

    # Check Particle boundary conditions: Re-initialize if at edges
    for ii in nb.prange(pos.shape[1]):
        if (pos[0, ii] < xmin or pos[0, ii] > xmax):
            if particle_reinit == 1:

                # Fix position
                if pos[0, ii] > xmax:
                    pos[0, ii] = 2 * xmax - pos[0, ii]
                elif pos[0, ii] < xmin:
                    pos[0, ii] = 2 * xmin - pos[0, ii]

                # Re-initialize velocity: Vel_x sign so it doesn't go back into boundary
                sf_per = np.sqrt(kB * Tper[idx[ii]] / mass[idx[ii]])
                sf_par = np.sqrt(kB * Tpar[idx[ii]] / mass[idx[ii]])

                if temp_type[idx[ii]] == 0:
                    vel[0, ii] = np.random.normal(0, sf_par)
                    vel[1, ii] = np.random.normal(0, sf_per)
                    vel[2, ii] = np.random.normal(0, sf_per)
                    v_perp = np.sqrt(vel[1, ii]**2 + vel[2, ii]**2)
                else:
                    particle_PA = 0.0
                    while np.abs(particle_PA) < loss_cone_xmax:
                        vel[0, ii] = (np.random.normal(
                            0, sf_par)) * (-np.sign(pos[0, ii]))
                        vel[1, ii] = np.random.normal(0, sf_per)
                        vel[2, ii] = np.random.normal(0, sf_per)
                        v_perp = np.sqrt(vel[1, ii]**2 + vel[2, ii]**2)

                        particle_PA = np.arctan(
                            v_perp / vel[0, ii])  # Calculate particle PA's

                # Don't foget : Also need to reinitialize position gyrophase (pos[1:2])
                B0x = eval_B0x(pos[0, ii])
                gyangle = init.get_gyroangle_single(vel[:, ii])
                rL = v_perp / (qm_ratios[idx[ii]] * B0x)
                pos[1, ii] = rL * np.cos(gyangle)
                pos[2, ii] = rL * np.sin(gyangle)

            elif particle_periodic == 1:
                # Mario (Periodic)
                if pos[0, ii] > xmax:
                    pos[0, ii] += xmin - xmax
                elif pos[0, ii] < xmin:
                    pos[0, ii] += xmax - xmin

                # Randomise gyrophase: Prevent bunching at initialization
                if randomise_gyrophase == True:
                    v_perp = np.sqrt(vel[1, ii]**2 + vel[2, ii]**2)
                    theta = np.random.uniform(0, 2 * np.pi)

                    vel[1, ii] = v_perp * np.sin(theta)
                    vel[2, ii] = v_perp * np.cos(theta)

            elif particle_reflect == 1:
                # Reflect
                if pos[0, ii] > xmax:
                    pos[0, ii] = 2 * xmax - pos[0, ii]
                elif pos[0, ii] < xmin:
                    pos[0, ii] = 2 * xmin - pos[0, ii]

                vel[0, ii] *= -1.0

            else:
                # DEACTIVATE PARTICLE (Negative index means they're not pushed or counted in sources)
                idx[ii] -= 128
                vel[:, ii] *= 0.0

    assign_weighting_TSC(pos, Ie, W_elec)
    return
コード例 #11
0
def position_update(pos, vel, idx, DT, Ie, W_elec):
    '''
    Updates the position of the particles using x = x0 + vt. 
    Also updates particle nearest node and weighting.

    INPUT:
        pos    -- Particle position array (Also output) 
        vel    -- Particle velocity array (Also output for reflection)
        idx    -- Particle index    array (Also output for reflection)
        DT     -- Simulation time step
        Ie     -- Particle leftmost to nearest node array (Also output)
        W_elec -- Particle weighting array (Also output)
        
    Note: This function also controls what happens when a particle leaves the 
    simulation boundary. As per Daughton et al. (2006).
    '''
    N_lost = np.zeros((2, Nj), dtype=np.int64)

    pos[0, :] += vel[0, :] * DT
    pos[1, :] += vel[1, :] * DT
    pos[2, :] += vel[2, :] * DT

    # Check Particle boundary conditions: Re-initialize if at edges
    for ii in nb.prange(pos.shape[1]):
        if idx[ii] >= 0:
            if (pos[0, ii] < xmin or pos[0, ii] > xmax):

                if pos[0, ii] < xmin:
                    N_lost[0, idx[ii]] += 1
                else:
                    N_lost[1, idx[ii]] += 1

                # Move particle to opposite boundary (Periodic)
                if particle_periodic == 1:
                    if pos[0, ii] > xmax:
                        pos[0, ii] += xmin - xmax
                    elif pos[0, ii] < xmin:
                        pos[0, ii] += xmax - xmin

                # Random flux initialization at boundary (Open)
                elif particle_reinit == 1:
                    if pos[0, ii] > xmax:
                        pos[0, ii] = 2 * xmax - pos[0, ii]
                    elif pos[0, ii] < xmin:
                        pos[0, ii] = 2 * xmin - pos[0, ii]

                    if temp_type[idx[ii]] == 0:
                        vel[0, ii] = np.random.normal(0, vth_par[idx[ii]])
                        vel[1, ii] = np.random.normal(0, vth_perp[idx[ii]])
                        vel[2, ii] = np.random.normal(0, vth_perp[idx[ii]])
                        v_perp = np.sqrt(vel[1, ii]**2 + vel[2, ii]**2)
                    else:
                        particle_PA = 0.0
                        while np.abs(particle_PA) < loss_cone_xmax:
                            vel[0, ii] = np.random.normal(0, vth_par[
                                idx[ii]])  # * (-1.0) * np.sign(pos[0, ii])
                            vel[1, ii] = np.random.normal(0, vth_perp[idx[ii]])
                            vel[2, ii] = np.random.normal(0, vth_perp[idx[ii]])
                            v_perp = np.sqrt(vel[1, ii]**2 + vel[2, ii]**2)

                            particle_PA = np.arctan(
                                v_perp / vel[0, ii])  # Calculate particle PA's

                    # Don't foget : Also need to reinitialize position gyrophase (pos[1:2])
                    B0x = eval_B0x(pos[0, ii])
                    gyangle = init.get_gyroangle_single(vel[:, ii])
                    rL = v_perp / (qm_ratios[idx[ii]] * B0x)
                    pos[1, ii] = rL * np.cos(gyangle)
                    pos[2, ii] = rL * np.sin(gyangle)

                # Deactivate particle (Open, default)
                else:
                    pos[:, ii] *= 0.0
                    vel[:, ii] *= 0.0
                    idx[ii] -= 128

    print(N_lost[0, :], N_lost[1, :])
    assign_weighting_CIC(pos, idx, Ie, W_elec)
    return
コード例 #12
0
def uniform_config_reverseradix_velocity():
    '''
    Creates an N-sampled normal distribution across all particle species within each simulation cell

    OUTPUT:
        pos -- 3xN array of particle positions. Pos[0] is uniformly distributed with boundaries depending on its temperature type
        vel -- 3xN array of particle velocities. Each component initialized as a Gaussian with a scale factor determined by the species perp/para temperature
        idx -- N   array of particle indexes, indicating which species it belongs to. Coded as an 8-bit signed integer, allowing values between +/-128
    
    New function using analytic loadings and reverse-radix shuffling.
    TO DO:
        -- Check with particle plots, is this random enough?
        -- Do a run to see if it fixes boundaries
        -- At some point, have to load loss cone distribution
    '''
    pos = np.zeros((3, const.N), dtype=np.float64)
    vel = np.zeros((3, const.N), dtype=np.float64)
    idx = np.ones(const.N, dtype=np.int8) * -1

    for jj in range(const.Nj):
        half_n = const.N_species[
            jj] // 2  # Half particles of species - doubled later

        st = const.idx_start[jj]
        en = const.idx_start[jj] + half_n

        # Set position
        for kk in range(half_n):
            pos[0, st + kk] = 2 * const.xmax * (float(kk) / (half_n - 1))
        pos[0, st:en] -= const.xmax

        # Set velocity for half: Randomly Maxwellian
        arr = np.arange(half_n)

        R_vr = rkbr_uniform_set(arr + 1, base=2)
        R_theta = rkbr_uniform_set(arr, base=3)
        R_vrx = rkbr_uniform_set(arr + 1, base=5)

        vr = const.vth_perp[jj] * np.sqrt(-2 * np.log(R_vr))
        vrx = const.vth_par[jj] * np.sqrt(-2 * np.log(R_vrx))
        theta = R_theta * np.pi * 2

        vel[0, st:en] = vrx * np.sin(theta) + const.drift_v[jj]
        vel[1, st:en] = vr * np.sin(theta)
        vel[2, st:en] = vr * np.cos(theta)
        idx[st:en] = jj

        pos[0, en:en + half_n] = pos[0, st:en]  # Other half, same position
        vel[0, en:en + half_n] = vel[0, st:en] * 1.0  # Set parallel
        vel[1, en:en +
            half_n] = vel[1, st:en] * -1.0  # Invert perp velocities (v2 = -v1)
        vel[2, en:en + half_n] = vel[2, st:en] * -1.0

        idx[st:const.idx_end[jj]] = jj

    # Set initial Larmor radius - rL from v_perp, distributed to y,z based on velocity gyroangle
    print('Initializing particles off-axis')
    B0x = fields.eval_B0x(pos[0, :en])
    v_perp = np.sqrt(vel[1, :en]**2 + vel[2, :en]**2)
    gyangle = get_gyroangle_array(vel[:, :en])
    rL = v_perp / (const.qm_ratios[idx[:en]] * B0x)
    pos[1, :en] = rL * np.cos(gyangle)
    pos[2, :en] = rL * np.sin(gyangle)
    return pos, vel, idx
コード例 #13
0
def uniform_gaussian_distribution_ultra_quiet():
    '''Creates an N-sampled normal distribution across all particle species within each simulation cell

    OUTPUT:
        pos -- 3xN array of particle positions. Pos[0] is uniformly distributed with boundaries depending on its temperature type
        vel -- 3xN array of particle velocities. Each component initialized as a Gaussian with a scale factor determined by the species perp/para temperature
        idx -- N   array of particle indexes, indicating which species it belongs to. Coded as an 8-bit signed integer, allowing values between +/-128
    
    Same as UGD_Q() but with 4 particles at each spatial point instead of two. This balances it in 
    vx as well as vy (at least initially) and should allow for flux to be more equal?
    '''
    pos = np.zeros((3, const.N), dtype=np.float64)
    vel = np.zeros((3, const.N), dtype=np.float64)
    idx = np.ones(
        const.N,
        dtype=np.int8) * -1  # Start all particles as disabled (idx < 0)
    np.random.seed(const.seed)

    for jj in range(const.Nj):
        quart_n = const.nsp_ppc[
            jj] // 4  # Quarter of particles per cell - quaded later
        if const.temp_type[
                jj] == 0:  # Change how many cells are loaded between cold/warm populations
            NC_load = const.NX
        else:
            if const.rc_hwidth == 0 or const.rc_hwidth > const.NX // 2:  # Need to change this to be something like the FWHM or something
                NC_load = const.NX
            else:
                NC_load = 2 * const.rc_hwidth

        # Load particles in each applicable cell
        acc = 0
        offset = 0
        for ii in range(NC_load):
            # Add particle if last cell (for symmetry)
            if ii == NC_load - 1:
                quart_n += 1
                offset = 1

            # Particle index ranges
            st = const.idx_start[jj] + acc
            en = const.idx_start[jj] + acc + quart_n

            # Set position for half: Analytically uniform
            for kk in range(quart_n):
                pos[0,
                    st + kk] = const.dx * (float(kk) / (quart_n - offset) + ii)

            # Turn [0, NC] distro into +/- NC/2 distro
            pos[0, st:en] -= NC_load * const.dx / 2

            # Set velocity for half: Randomly Maxwellian
            vel[0, st:en] = np.random.normal(0, const.vth_par[jj],
                                             quart_n) + const.drift_v[jj]
            vel[1, st:en] = np.random.normal(0, const.vth_perp[jj], quart_n)
            vel[2, st:en] = np.random.normal(0, const.vth_perp[jj], quart_n)
            idx[st:en] = jj  # Turn particle on

            # Set Loss Cone Distribution: Reinitialize particles in loss cone (move to a function)
            if const.homogenous == False and const.temp_type[jj] == 1:
                LCD_by_rejection(pos, vel, st, en, jj)

            # Quiet start : Initialize second half of v_perp
            vel[0, en:en + quart_n] = vel[0, st:en] * 1.0  # Set parallel
            pos[0, en:en + quart_n] = pos[0,
                                          st:en]  # Other half, same position
            vel[1, en:en + quart_n] = vel[
                1, st:en] * -1.0  # Invert perp velocities (v2 = -v1)
            vel[2, en:en + quart_n] = vel[2, st:en] * -1.0
            idx[en:en + quart_n] = jj  # Turn particle on

            # Quieter start : Initialize second half of v_para
            en2 = en + quart_n
            vel[0, en2:en2 +
                2 * quart_n] = vel[0, st:en2] * -1.0  # Set anti-parallel

            pos[0, en2:en2 + 2 * quart_n] = pos[0, st:en2]  # Same positions
            vel[1, en2:en2 +
                2 * quart_n] = vel[1, st:en2]  # Same perpendicular velocities
            vel[2, en2:en2 + 2 * quart_n] = vel[2, st:en2]
            idx[en2:en2 + 2 * quart_n] = jj  # Turn particle on

            acc += quart_n * 4

    # Set initial Larmor radius - rL from v_perp, distributed to y,z based on velocity gyroangle
    print('Initializing particles off-axis')
    B0x = fields.eval_B0x(pos[0, :acc])
    v_perp = np.sqrt(vel[1, :acc]**2 + vel[2, :acc]**2)
    gyangle = get_gyroangle_array(vel[:, :acc])
    rL = v_perp / (const.qm_ratios[idx[:acc]] * B0x)
    pos[1, :acc] = rL * np.cos(gyangle)
    pos[2, :acc] = rL * np.sin(gyangle)

    return pos, vel, idx
コード例 #14
0
def uniform_config_random_velocity_gaussian_T():
    '''Creates an N-sampled normal distribution across all particle species within each simulation cell

    OUTPUT:
        pos -- 3xN array of particle positions. Pos[0] is uniformly distributed with boundaries depending on its temperature type
        vel -- 3xN array of particle velocities. Each component initialized as a Gaussian with a scale factor determined by the species perp/para temperature
        idx -- N   array of particle indexes, indicating which species it belongs to. Coded as an 8-bit signed integer, allowing values between +/-128
    
    This one varies temperature by position as a gaussian - i.e. every particle is loaded from a 
    slightly different normal distribution. Because of this, don't bother loading cellwise.
    
    Also, Gaussian only applied to hot component. Cold components remain homogenous and isotropic
    '''
    pos = np.zeros((3, const.N), dtype=np.float64)
    vel = np.zeros((3, const.N), dtype=np.float64)
    idx = np.ones(
        const.N,
        dtype=np.int8) * -1  # Start all particles as disabled (idx < 0)
    np.random.seed(const.seed)

    for jj in range(const.Nj):
        half_n = const.N_species[
            jj] // 2  # Half particles of species - doubled later

        st = const.idx_start[jj]
        en = const.idx_start[jj] + half_n

        # Set position
        for kk in range(half_n):
            pos[0, st + kk] = 2 * const.xmax * (float(kk) / (half_n - 1))
        pos[0, st:en] -= const.xmax
        idx[st:en] = jj

        if const.temp_type[jj] == 1:
            # Set velocity: Position varying temperature (Gaussian)
            vth_par_gauss, vth_perp_gauss = get_vth_at_x(pos[0, st:en], jj)
            mu = np.zeros(const.N_species[jj] // 2)

            # Set velocity for half: Randomly Maxwellian but with varying vth in space
            vel[0, st:en] = np.random.normal(
                mu, vth_par_gauss,
                const.N_species[jj] // 2) + const.drift_v[jj]
            vel[1, st:en] = np.random.normal(mu, vth_perp_gauss,
                                             const.N_species[jj] // 2)
            vel[2, st:en] = np.random.normal(mu, vth_perp_gauss,
                                             const.N_species[jj] // 2)

            # Set Loss Cone Distribution: Reinitialize particles in loss cone (move to a function)
            if const.homogenous == False:
                LCD_by_rejection_varying_vth(pos, vel, st, en, jj,
                                             vth_par_gauss, vth_perp_gauss)
        else:
            # Set velocity for half: Randomly Maxwellian, isotropic and homogenous
            vel[0, st:en] = np.random.normal(
                0.0, const.vth_par[jj],
                const.N_species[jj] // 2) + const.drift_v[jj]
            vel[1, st:en] = np.random.normal(0.0, const.vth_perp[jj],
                                             const.N_species[jj] // 2)
            vel[2, st:en] = np.random.normal(0.0, const.vth_perp[jj],
                                             const.N_species[jj] // 2)

        pos[0, en:en + half_n] = pos[0, st:en]  # Other half, same position
        vel[0, en:en + half_n] = vel[0, st:en] * 1.0  # Set parallel
        vel[1, en:en +
            half_n] = vel[1, st:en] * -1.0  # Invert perp velocities (v2 = -v1)
        vel[2, en:en + half_n] = vel[2, st:en] * -1.0

        idx[st:const.idx_end[jj]] = jj

    # Set initial Larmor radius - rL from v_perp, distributed to y,z based on velocity gyroangle
    print('Initializing particles off-axis')
    B0x = fields.eval_B0x(pos[0, :en])
    v_perp = np.sqrt(vel[1, :en]**2 + vel[2, :en]**2)
    gyangle = get_gyroangle_array(vel[:, :en])
    rL = v_perp / (const.qm_ratios[idx[:en]] * B0x)
    pos[1, :en] = rL * np.cos(gyangle)
    pos[2, :en] = rL * np.sin(gyangle)

    return pos, vel, idx
コード例 #15
0
def uniform_config_random_velocity():
    '''Creates an N-sampled normal distribution across all particle species within each simulation cell

    OUTPUT:
        pos -- 3xN array of particle positions. Pos[0] is uniformly distributed with boundaries depending on its temperature type
        vel -- 3xN array of particle velocities. Each component initialized as a Gaussian with a scale factor determined by the species perp/para temperature
        idx -- N   array of particle indexes, indicating which species it belongs to. Coded as an 8-bit signed integer, allowing values between +/-128
    
    Note: y,z components of particle positions intialized with identical gyrophases, since only the projection
    onto the x-axis interacts with the simulation fields. pos y,z are kept ONLY to calculate/track the Larmor radius 
    of each particle. This initial position suffers the same issue as trying to update the radial field using 
    B0r = 0 for the Larmor radius approximation, however because this is only an initial condition, at worst this
    will just cause a variation in the Larmor radius with position in x, but this will at least be conserved 
    throughout the simulation, and not drift with time.
    
    CHECK THIS LATER: BUT ITS ONLY AN INITIAL CONDITION SO IT SHOULD BE OK FOR NOW

    # Could use temp_type[jj] == 1 for RC LCD only
    '''
    pos = np.zeros((3, const.N), dtype=np.float64)
    vel = np.zeros((3, const.N), dtype=np.float64)
    idx = np.ones(
        const.N,
        dtype=np.int8) * -1  # Start all particles as disabled (idx < 0)
    np.random.seed(const.seed)

    for jj in range(const.Nj):
        half_n = const.nsp_ppc[
            jj] // 2  # Half particles per cell - doubled later
        if const.temp_type[
                jj] == 0:  # Change how many cells are loaded between cold/warm populations
            NC_load = const.NX
        else:
            if const.rc_hwidth == 0 or const.rc_hwidth > const.NX // 2:  # Need to change this to be something like the FWHM or something
                NC_load = const.NX
            else:
                NC_load = 2 * const.rc_hwidth

        # Load particles in each applicable cell
        acc = 0
        offset = 0
        for ii in range(NC_load):
            # Add particle if last cell (for symmetry)
            if ii == NC_load - 1:
                half_n += 1
                offset = 1

            # Particle index ranges
            st = const.idx_start[jj] + acc
            en = const.idx_start[jj] + acc + half_n

            # Set position for half: Analytically uniform
            for kk in range(half_n):
                pos[0,
                    st + kk] = const.dx * (float(kk) / (half_n - offset) + ii)

            # Turn [0, NC] distro into +/- NC/2 distro
            pos[0, st:en] -= NC_load * const.dx / 2

            # Set velocity for half: Randomly Maxwellian
            vel[0, st:en] = np.random.normal(0, const.vth_par[jj],
                                             half_n) + const.drift_v[jj]
            vel[1, st:en] = np.random.normal(0, const.vth_perp[jj], half_n)
            vel[2, st:en] = np.random.normal(0, const.vth_perp[jj], half_n)
            idx[st:en] = jj  # Turn particle on

            # Set Loss Cone Distribution: Reinitialize particles in loss cone (move to a function)
            if const.homogenous == False and const.temp_type[jj] == 1:
                LCD_by_rejection(pos, vel, st, en, jj)

            # Quiet start : Initialize second half
            if const.quiet_start == True:
                vel[0, en:en + half_n] = vel[0, st:en] * 1.0  # Set parallel
            else:
                vel[0,
                    en:en + half_n] = vel[0, st:en] * -1.0  # Set anti-parallel

            pos[0, en:en + half_n] = pos[0, st:en]  # Other half, same position
            vel[1, en:en + half_n] = vel[
                1, st:en] * -1.0  # Invert perp velocities (v2 = -v1)
            vel[2, en:en + half_n] = vel[2, st:en] * -1.0
            idx[en:en + half_n] = jj  # Turn particle on

            acc += half_n * 2

    # Set initial Larmor radius - rL from v_perp, distributed to y,z based on velocity gyroangle
    print('Initializing particles off-axis')
    B0x = fields.eval_B0x(pos[0, :en])
    v_perp = np.sqrt(vel[1, :en]**2 + vel[2, :en]**2)
    gyangle = get_gyroangle_array(vel[:, :en])
    rL = v_perp / (const.qm_ratios[idx[:en]] * B0x)
    pos[1, :en] = rL * np.cos(gyangle)
    pos[2, :en] = rL * np.sin(gyangle)

    return pos, vel, idx
コード例 #16
0
def uniform_gaussian_distribution_quiet():
    '''Creates an N-sampled normal distribution across all particle species within each simulation cell

    OUTPUT:
        pos -- 3xN array of particle positions. Pos[0] is uniformly distributed with boundaries depending on its temperature type
        vel -- 3xN array of particle velocities. Each component initialized as a Gaussian with a scale factor determined by the species perp/para temperature
        idx -- N   array of particle indexes, indicating which species it belongs to. Coded as an 8-bit signed integer, allowing values between +/-128
    
    Note: y,z components of particle positions intialized with identical gyrophases, since only the projection
    onto the x-axis interacts with the simulation fields. pos y,z are kept ONLY to calculate/track the Larmor radius 
    of each particle. This initial position suffers the same issue as trying to update the radial field using 
    B0r = 0 for the Larmor radius approximation, however because this is only an initial condition, at worst this
    will just cause a variation in the Larmor radius with position in x, but this will at least be conserved 
    throughout the simulation, and not drift with time.
    
    CHECK THIS LATER: BUT ITS ONLY AN INITIAL CONDITION SO IT SHOULD BE OK FOR NOW

    # Could use temp_type[jj] == 1 for RC LCD only
    '''
    pos = np.zeros((3, N), dtype=np.float64)
    vel = np.zeros((3, N), dtype=np.float64)
    idx = np.zeros(N, dtype=np.int8)
    np.random.seed(seed)

    for jj in range(Nj):
        idx[idx_start[jj]:idx_end[jj]] = jj  # Set particle idx

        half_n = nsp_ppc[jj] // 2  # Half particles per cell - doubled later
        sf_par = np.sqrt(kB * Tpar[jj] /
                         mass[jj])  # Scale factors for velocity initialization
        sf_per = np.sqrt(kB * Tper[jj] / mass[jj])

        if temp_type[
                jj] == 0:  # Change how many cells are loaded between cold/warm populations
            NC_load = NX
        else:
            if rc_hwidth == 0 or rc_hwidth > NX // 2:
                NC_load = NX
            else:
                NC_load = 2 * rc_hwidth

        # Load particles in each applicable cell
        acc = 0
        offset = 0
        for ii in range(NC_load):
            # Add particle if last cell (for symmetry)
            if ii == NC_load - 1:
                half_n += 1
                offset = 1

            # Particle index ranges
            st = idx_start[jj] + acc
            en = idx_start[jj] + acc + half_n

            # Set position for half: Analytically uniform
            for kk in range(half_n):
                pos[0, st + kk] = dx * (float(kk) / (half_n - offset) + ii)

            # Turn [0, NC] distro into +/- NC/2 distro
            pos[0, st:en] -= NC_load * dx / 2

            # Set velocity for half: Randomly Maxwellian
            vel[0, st:en] = np.random.normal(0, sf_par, half_n) + drift_v[jj]
            vel[1, st:en] = np.random.normal(0, sf_per, half_n)
            vel[2, st:en] = np.random.normal(0, sf_per, half_n)

            # Set Loss Cone Distribution: Reinitialize particles in loss cone
            B0x = fields.eval_B0x(pos[0, st:en])
            if const.homogenous == False:
                N_loss = const.N_species[jj]

                while N_loss > 0:
                    v_perp = np.sqrt(vel[1, st:en]**2 + vel[2, st:en]**2)

                    N_loss, loss_idx = calc_losses(vel[0, st:en],
                                                   v_perp,
                                                   B0x,
                                                   st=st)

                    # Catch for a particle on the boundary : Set 90 degree pitch angle (gyrophase shouldn't overly matter)
                    if N_loss == 1:
                        if abs(pos[0, loss_idx[0]]) == const.xmax:
                            ww = loss_idx[0]
                            vel[0, loss_idx[0]] = 0.
                            vel[1, loss_idx[0]] = np.sqrt(vel[0, ww]**2 +
                                                          vel[1, ww]**2 +
                                                          vel[2, ww]**2)
                            vel[2, loss_idx[0]] = 0.
                            N_loss = 0

                    if N_loss != 0:
                        vel[0, loss_idx] = np.random.normal(0., sf_par, N_loss)
                        vel[1, loss_idx] = np.random.normal(0., sf_per, N_loss)
                        vel[2, loss_idx] = np.random.normal(0., sf_per, N_loss)
            else:
                v_perp = np.sqrt(vel[1, st:en]**2 + vel[2, st:en]**2)

            vel[0, en:en +
                half_n] = vel[0, st:en] * -1.0  # Invert velocities (v2 = -v1)
            vel[1, en:en + half_n] = vel[1, st:en] * -1.0
            vel[2, en:en + half_n] = vel[2, st:en] * -1.0

            pos[0, en:en + half_n] = pos[0, st:en]  # Other half, same position

            acc += half_n * 2

    # Set initial Larmor radius - rL from v_perp, distributed to y,z based on velocity gyroangle
    print('Initializing particles off-axis')
    B0x = fields.eval_B0x(pos[0])
    v_perp = np.sqrt(vel[1]**2 + vel[2]**2)
    gyangle = get_gyroangle_from_velocity(vel)
    rL = v_perp / (qm_ratios[idx] * B0x)
    pos[1] = rL * np.cos(gyangle)
    pos[2] = rL * np.sin(gyangle)

    return pos, vel, idx