Пример #1
0
def _extract_current_results(data, curr, data_time):
    grid = data['models']['simulationGrid']
    plate_spacing = _meters(grid['plate_spacing'])
    zmesh = np.linspace(0, plate_spacing, grid['num_z'] + 1) #holds the z-axis grid points in an array
    beam = data['models']['beam']
    if data.models.simulationGrid.simulation_mode == '3d':
        cathode_area = _meters(grid['channel_width']) * _meters(grid['channel_height'])
    else:
        cathode_area = _meters(grid['channel_width'])
    RD_ideal = sources.j_rd(beam['cathode_temperature'], beam['cathode_work_function']) * cathode_area
    JCL_ideal = sources.cl_limit(beam['cathode_work_function'], beam['anode_work_function'], beam['anode_voltage'], plate_spacing) * cathode_area

    if beam['currentMode'] == '2' or (beam['currentMode'] == '1' and beam['beam_current'] >= JCL_ideal):
        curr2 = np.full_like(zmesh, JCL_ideal)
        y2_title = 'Child-Langmuir cold limit'
    else:
        curr2 = np.full_like(zmesh, RD_ideal)
        y2_title = 'Richardson-Dushman'
    return {
        'title': 'Current for Time: {:.4e}s'.format(data_time),
        'x_range': [0, plate_spacing],
        'y_label': 'Current [A]',
        'x_label': 'Z [m]',
        'points': [
            curr.tolist(),
            curr2.tolist(),
        ],
        'x_points': zmesh.tolist(),
        'y_range': [min(np.min(curr), np.min(curr2)), max(np.max(curr), np.max(curr2))],
        'y1_title': 'Current',
        'y2_title': y2_title,
    }
Пример #2
0
def _extract_current_results(data, curr, data_time):
    grid = data['models']['simulationGrid']
    plate_spacing = _meters(grid['plate_spacing'])
    zmesh = np.linspace(0, plate_spacing, grid['num_z'] + 1) #holds the z-axis grid points in an array
    beam = data['models']['beam']
    if _SIM_DATA.warpvnd_is_3d(data):
        cathode_area = _meters(grid['channel_width']) * _meters(grid['channel_height'])
    else:
        cathode_area = _meters(grid['channel_width'])
    RD_ideal = sources.j_rd(beam['cathode_temperature'], beam['cathode_work_function']) * cathode_area
    JCL_ideal = sources.cl_limit(beam['cathode_work_function'], beam['anode_work_function'], beam['anode_voltage'], plate_spacing) * cathode_area

    if beam['currentMode'] == '2' or (beam['currentMode'] == '1' and beam['beam_current'] >= JCL_ideal):
        curr2 = np.full_like(zmesh, JCL_ideal)
        y2_title = 'Child-Langmuir cold limit'
    else:
        curr2 = np.full_like(zmesh, RD_ideal)
        y2_title = 'Richardson-Dushman'
    return {
        'title': 'Current for Time: {:.4e}s'.format(data_time),
        'x_range': [0, plate_spacing],
        'y_label': 'Current [A]',
        'x_label': 'Z [m]',
        'points': [
            curr.tolist(),
            curr2.tolist(),
        ],
        'x_points': zmesh.tolist(),
        'y_range': [min(np.min(curr), np.min(curr2)), max(np.max(curr), np.max(curr2))],
        'y1_title': 'Current',
        'y2_title': y2_title,
    }
Пример #3
0
def main(x_struts, y_struts, V_grid, grid_height, strut_width, strut_height,
         rho_ew, T_em, phi_em, T_coll, phi_coll, rho_cw, gap_distance, rho_load,
         run_id, channel_width=100e-9,
         injection_type=2, magnetic_field=0.0, random_seed=True, install_grid=True, max_wall_time=1e9,
         particle_diagnostic_switch=False, field_diagnostic_switch=False, lost_diagnostic_switch=False):
    """
    Run a simulation of a gridded TEC.
    Args:
        x_struts: Number of struts that intercept the x-axis.
        y_struts: Number of struts that intercept the y-axis
        V_grid: Voltage to place on the grid in Volts.
        grid_height: Distance from the emitter to the grid normalized by gap_distance, unitless.
        strut_width: Transverse extent of the struts in meters.
        strut_height: Longitudinal extent of the struts in meters.
        rho_ew: Emitter side wiring resistivity, ohms*cm.
        T_em: Emitter temperature, kelvin.
        phi_em: Emitter work function, eV.
        T_coll: Collector termperature, kelvin.
        phi_coll: Collector work function, eV.
        rho_cw: Collector side wiring resistivity, ohms*cm.
        gap_distance: Distance from emitter to collector, meters.
        rho_load: Load resistivity, ohms*cm.
        run_id: Run ID. Mainly used for parallel optimization.
        injection_type: 1: For constant current emission with only thermal velocity spread in z and CL limited emission.
                        2: For true thermionic emission. Velocity spread along all axes.
        random_seed: True/False. If True, will force a random seed to be used for emission positions.
        install_grid: True/False. If False the grid will not be installed. Results in simple parallel plate setup.
                                  If False then phi_em - phi_coll specifies the voltage on the collector.
        max_wall_time: Wall time to allow simulation to run for. Simulation is periodically checked and will halt if it
                        appears the next segment of the simulation will exceed max_wall_time. This is not guaranteed to
                        work since the guess is based on the run time up to that point.
                        Intended to be used when running on system with job manager.
        particle_diagnostic_switch: True/False. Use openPMD compliant .h5 particle diagnostics.
        field_diagnostic_switch: True/False. Use rswarp electrostatic .h5 field diagnostics (Maybe openPMD compliant?).
        lost_diagnostic_switch: True/False. Enable collection of lost particle coordinates
                        with rswarp.diagnostics.parallel.save_lost_particles.

    """
    # record inputs and set parameters
    run_attributes = deepcopy(locals())

    for key in run_attributes:
        if key in efficiency.tec_parameters:
            efficiency.tec_parameters[key][0] = run_attributes[key]

    # set new random seed
    if random_seed:
        top.seedranf(randint(1, 1e9))

    # Control for printing in parallel
    if comm_world.size != 1:
        synchronizeQueuedOutput_mpi4py(out=True, error=True)

    if particle_diagnostic_switch or field_diagnostic_switch:
        # Directory paths
        diagDir = 'diags_id{}/hdf5/'.format(run_id)
        field_base_path = 'diags_id{}/fields/'.format(run_id)
        diagFDir = {'magnetic': 'diags_id{}/fields/magnetic'.format(run_id),
                    'electric': 'diags_id{}/fields/electric'.format(run_id)}

        # Cleanup command if directories already exist
        if comm_world.rank == 0:
            cleanupPrevious(diagDir, diagFDir)

    load_balance = LoadBalancer()
    ######################
    # DOMAIN/GEOMETRY/MESH
    ######################

    # Dimensions
    X_MAX = +channel_width / 2.
    X_MIN = -X_MAX
    Y_MAX = +channel_width / 2.
    Y_MIN = -Y_MAX
    Z_MAX = gap_distance
    Z_MIN = 0.

    # TODO: cells in all dimensions reduced by 10x for testing, will need to verify if this is reasonable (TEMP)
    # Grid parameters
    dx_want = 5e-9
    dy_want = 5e-9
    dz_want = 5e-9

    NUM_X = int(round(channel_width / dx_want))  # 20 #128 #10
    NUM_Y = int(round(channel_width / dy_want))  # 20 #128 #10
    NUM_Z = int(round(gap_distance / dz_want))

    # mesh spacing
    dz = (Z_MAX - Z_MIN) / NUM_Z
    dx = channel_width / NUM_X
    dy = channel_width / NUM_Y

    print "Channel width: {}, DX = {}".format(channel_width, dx)
    print "Channel width: {}, DY = {}".format(channel_width, dy)
    print "Channel length: {}, DZ = {}".format(gap_distance, dz)

    # Solver Geometry and Boundaries

    # Specify solver geometry
    w3d.solvergeom = w3d.XYZgeom

    # Set field boundary conditions
    w3d.bound0 = neumann
    w3d.boundnz = dirichlet
    w3d.boundxy = periodic
    # Particles boundary conditions
    top.pbound0 = absorb
    top.pboundnz = absorb
    top.pboundxy = periodic

    # Set mesh boundaries
    w3d.xmmin = X_MIN
    w3d.xmmax = X_MAX
    w3d.ymmin = Y_MIN
    w3d.ymmax = Y_MAX
    w3d.zmmin = 0.
    w3d.zmmax = Z_MAX

    # Set mesh cell counts
    w3d.nx = NUM_X
    w3d.ny = NUM_Y
    w3d.nz = NUM_Z

    #############################
    # PARTICLE INJECTION SETTINGS
    #############################

    # Cathode and anode settings
    EMITTER_TEMP = T_em
    EMITTER_PHI = phi_em # work function in eV
    COLLECTOR_PHI = phi_coll  # Can be used if vacuum level is being set
    ACCEL_VOLTS = V_grid  # ACCEL_VOLTS used for velocity and CL calculations
    collector_voltage = phi_em - phi_coll

    # Emitted species
    background_beam = Species(type=Electron, name='background')
    measurement_beam = Species(type=Electron, name='measurement')

    # Emitter area and position
    SOURCE_RADIUS_1 = 0.5 * channel_width  # a0 parameter - X plane
    SOURCE_RADIUS_2 = 0.5 * channel_width  # b0 parameter - Y plane
    Z_PART_MIN = dz / 1000.  # starting particle z value

    # Compute cathode area for geomtry-specific current calculations
    if (w3d.solvergeom == w3d.XYZgeom):
        # For 3D cartesion geometry only
        cathode_area = 4. * SOURCE_RADIUS_1 * SOURCE_RADIUS_2
    else:
        # Assume 2D XZ geometry
        cathode_area = 2. * SOURCE_RADIUS_1 * 1.

    # If using the XZ geometry, set so injection uses the same geometry
    top.linj_rectangle = (w3d.solvergeom == w3d.XZgeom or w3d.solvergeom == w3d.XYZgeom)

    # Returns velocity beam_beta (in units of beta) for which frac of emitted particles have v < beam_beta * c
    beam_beta = sources.compute_cutoff_beta(EMITTER_TEMP, frac=0.99)

    PTCL_PER_STEP = 100

    if injection_type == 1:
        CURRENT_MODIFIER = 0.5  # Factor to multiply CL current by when setting beam current
        # Constant current density - beam transverse velocity fixed to zero, very small longitduinal velocity

        # Set injection flag
        top.inject = 1  # 1 means constant; 2 means space-charge limited injection;# 6 means user-specified
        top.npinject = PTCL_PER_STEP
        beam_current = 4. / 9. * eps0 * sqrt(2. * echarge / background_beam.mass) \
                       * ACCEL_VOLTS ** 1.5 / gap_distance ** 2 * cathode_area

        background_beam.ibeam = beam_current * CURRENT_MODIFIER

        background_beam.a0 = SOURCE_RADIUS_1
        background_beam.b0 = SOURCE_RADIUS_2
        background_beam.ap0 = .0e0
        background_beam.bp0 = .0e0

        w3d.l_inj_exact = True

        # Initial velocity settings (5% of c)
        vrms = np.sqrt(1 - 1 / (0.05 / 511e3 + 1) ** 2) * 3e8
        top.vzinject = vrms

    if injection_type == 2:
        # True Thermionic injection
        top.inject = 1

        # Set both beams to same npinject to keep weights the same
        background_beam.npinject = PTCL_PER_STEP
        measurement_beam.npinject = PTCL_PER_STEP

        w3d.l_inj_exact = True

        # Specify thermal properties
        background_beam.vthz = measurement_beam.vthz = np.sqrt(EMITTER_TEMP * kb_J / background_beam.mass)
        background_beam.vthperp = measurement_beam.vthperp = np.sqrt(EMITTER_TEMP * kb_J / background_beam.mass)
        top.lhalfmaxwellinject = 1  # inject z velocities as half Maxwellian

        beam_current = sources.j_rd(EMITTER_TEMP, EMITTER_PHI) * cathode_area  # steady state current in Amps
        print('beam current expected: {}, current density {}'.format(beam_current, beam_current / cathode_area))
        jcl = 4. / 9. * eps0 * sqrt(2. * echarge / background_beam.mass) \
                       * ACCEL_VOLTS ** 1.5 / gap_distance ** 2 * cathode_area
        print('child-langmuir  limit: {}, current density {}'.format(jcl, jcl / cathode_area))
        background_beam.ibeam = measurement_beam.ibeam = beam_current
        background_beam.a0 = measurement_beam.a0 = SOURCE_RADIUS_1
        background_beam.b0 = measurement_beam.b0 = SOURCE_RADIUS_2
        background_beam.ap0 = measurement_beam.ap0 = .0e0
        background_beam.bp0 = measurement_beam.bp0 = .0e0

    derivqty()

    ##############
    # FIELD SOLVER
    ##############
    # Add Uniform B_z field if turned on
    if magnetic_field:
        bz = np.zeros([w3d.nx, w3d.ny, w3d.nz])
        bz[:, :, :] = magnetic_field
        z_start = w3d.zmmin
        z_stop = w3d.zmmax
        top.ibpush = 2
        addnewbgrd(z_start, z_stop, xs=w3d.xmmin, dx=(w3d.xmmax - w3d.xmmin), ys=w3d.ymmin, dy=(w3d.ymmax - w3d.ymmin),
                   nx=w3d.nx, ny=w3d.ny, nz=w3d.nz, bz=bz)

    # Set up fieldsolver
    f3d.mgtol = 1e-6
    solverE = MultiGrid3D()
    registersolver(solverE)

    ########################
    # CONDUCTOR INSTALLATION
    ########################

    if install_grid:
        accel_grid, gl = create_grid(x_struts, y_struts, V_grid,
                                     grid_height * gap_distance, strut_width, strut_height,
                                     channel_width)
        accel_grid.voltage = V_grid

    # --- Anode Location
    zplate = Z_MAX

    # Create source conductors
    if install_grid:
        source = ZPlane(zcent=w3d.zmmin, zsign=-1., voltage=0., condid=2)
    else:
        source = ZPlane(zcent=w3d.zmmin, zsign=-1., voltage=0.)

    # Create ground plate
    total_rho = efficiency.tec_parameters['rho_load'][0]
    if install_grid:
        plate = ZPlane(zcent=zplate, condid=3)
        circuit = ExternalCircuit(top, solverE, total_rho, collector_voltage, cathode_area * 1e4, plate, debug=False)
        plate.voltage = circuit
        # plate.voltage = collector_voltage
    else:
        plate = ZPlane(zcent=zplate)
        circuit = ExternalCircuit(top, solverE, total_rho, collector_voltage, cathode_area * 1e4, plate, debug=False)
        plate.voltage = circuit
        # plate.voltage = collector_voltage

    if install_grid:
        installconductor(accel_grid)
        installconductor(source, dfill=largepos)
        installconductor(plate, dfill=largepos)
        scraper = ParticleScraper([accel_grid, source, plate],
                                  lcollectlpdata=True,
                                  lsaveintercept=True)
        scraper_dictionary = {'grid': 1, 'source': 2, 'collector': 3}
    else:
        installconductor(source, dfill=largepos)
        installconductor(plate, dfill=largepos)
        scraper = ParticleScraper([source, plate],
                                  lcollectlpdata=True,
                                  lsaveintercept=True)
        scraper_dictionary = {'source': 1, 'collector': 2}

    #############
    # DIAGNOSTICS
    #############

    # Particle/Field diagnostic options
    if particle_diagnostic_switch:
        particleperiod = 250  # TEMP
        particle_diagnostic_0 = ParticleDiagnostic(period=particleperiod, top=top, w3d=w3d,
                                                   species={species.name: species
                                                            for species in listofallspecies},
                                                            # if species.name == 'measurement'}, # TEMP
                                                   comm_world=comm_world, lparallel_output=False,
                                                   write_dir=diagDir[:-5])
        installafterstep(particle_diagnostic_0.write)

    if field_diagnostic_switch:
        fieldperiod = 1000
        efield_diagnostic_0 = FieldDiagnostic.ElectrostaticFields(solver=solverE, top=top, w3d=w3d,
                                                                  comm_world=comm_world,
                                                                  period=fieldperiod)
        installafterstep(efield_diagnostic_0.write)

    # Set externally derived parameters for efficiency calculation
    efficiency.tec_parameters['A_em'][0] = cathode_area * 1e4  # cm**2
    if install_grid:
        efficiency.tec_parameters['occlusion'][0] = efficiency.calculate_occlusion(**efficiency.tec_parameters)
    else:
        efficiency.tec_parameters['occlusion'][0] = 0.0

    ##########################
    # SOLVER SETTINGS/GENERATE
    ##########################

    # prevent gist from starting upon setup
    top.lprntpara = false
    top.lpsplots = false

    top.verbosity = -1  # Reduce solver verbosity
    solverE.mgverbose = -1  # further reduce output upon stepping - prevents websocket timeouts in Jupyter notebook

    init_iters = 20000
    regular_iters = 200

    init_tol = 1e-6
    regular_tol = 1e-6

    # Time Step

    # Determine an appropriate time step based upon estimated final velocity
    if install_grid:
        vz_accel = sqrt(2. * abs(V_grid) * np.abs(background_beam.charge) / background_beam.mass)
    else:
        vz_accel = sqrt(2. * abs(collector_voltage) * np.abs(background_beam.charge) / background_beam.mass)
    vzfinal = vz_accel + beam_beta * c
    dt = dz / vzfinal
    top.dt = dt

    solverE.mgmaxiters = init_iters
    solverE.mgtol = init_tol
    package("w3d")
    generate()
    solverE.mgtol = regular_tol
    solverE.mgmaxiters = regular_iters

    print("weights (background) (measurement): {}, {}".format(background_beam.sw, measurement_beam.sw))

    # Use rnpinject to set number of macroparticles emitted
    background_beam.rnpinject = PTCL_PER_STEP
    measurement_beam.rnpinject = 0  # measurement beam is off at start

    ##################
    # CONTROL SEQUENCE
    ##################
    # Run until steady state is achieved (flat current profile at collector) (measurement species turned on)
    # Record data for effiency calculation
    # Switch off measurement species and wait for simulation to clear (background species is switched on)

    early_abort = 0  # If true will flag output data to notify
    startup_time = 4 * gap_distance / vz_accel  # ~4 crossing times to approach steady-state with external circuit
    crossing_measurements = 10  # Number of crossing times to record for
    steps_per_crossing = int(gap_distance / vz_accel / dt)
    ss_check_interval = int(steps_per_crossing / 2.)
    ss_max_checks = 8  # Maximum number of of times to run steady-state check procedure before aborting
    times = []  # Write out timing of cycle steps to file
    clock = 0  # clock tracks the current, total simulation-runtime

    # Run initial block of steps
    record_time(stept, times, startup_time)
    clock += times[-1]
    stop_initialization = top.it  # for diag file

    print("Completed Initialization on Step {}\nInitialization run time: {}".format(top.it, times[-1]))

    # Start checking for Steady State Operation
    tol = 0.01
    ss_flag = 0
    check_count = 0  # Track number of times steady-state check performed

    while ss_flag != 1 and check_count < ss_max_checks:
        if (max_wall_time - clock) < times[-1]:
            early_abort = 1
            break

        record_time(step, times, ss_check_interval*4)
        clock += times[-1]

        tstart = (top.it - ss_check_interval) * top.dt
        _, current1 = plate.get_current_history(js=None, l_lost=1, l_emit=0,
                                               l_image=0, tmin=tstart, tmax=None, nt=1)
        current = np.sum(current1)

        if np.abs(current) < 0.5 * efficiency.tec_parameters['occlusion'][0] * beam_current:
            # If too little current is getting through run another check cycle
            check_count += 1
            print("Completed check {}, insufficient current, running again for {} steps".format(check_count,
                                                                                                ss_check_interval))
            continue

        ss_flag = 1
        # print np.abs(current), 0.5 * efficiency.tec_parameters['occlusion'][0] * beam_current
        # try:
        #     # If steady_state check initialized no need to do it again
        #     steady_state
        # except NameError:
        #     # If this is the first pass with sufficient current then initialize the check
        #     if check_count == 0:
        #         # If the initial period was long enough to get current on collector then use that
        #         steady_state = SteadyState(top, plate, steps_per_crossing)
        #     else:
        #         # If we had to run several steady state checks with no current then just use the period with current
        #         steady_state = SteadyState(top, plate, ss_check_interval)
        #
        # ss_flag = steady_state(steps_per_crossing)
        check_count += 1

    stop_ss_check = top.it  # For diag file

    # If there was a failure to reach steady state after specified number of checks then pass directly end
    if check_count == ss_max_checks:
        early_abort = -1
        crossing_measurements = 0
        print("Failed to reach steady state. Aborting simulation.")
    else:
        # Start Steady State Operation
        print(" Steady State Reached.\nStarting efficiency "
              "recording for {} crossing times.\nThis will be {} steps".format(crossing_measurements,
                                                                               steps_per_crossing * crossing_measurements))

    # particle_diagnostic_0.period = steps_per_crossing #TEMP commented out
    # Switch to measurement beam species
    measurement_beam.rnpinject = PTCL_PER_STEP
    background_beam.rnpinject = 0

    # Install Zcrossing Diagnostic
    ZCross = ZCrossingParticles(zz=grid_height * gap_distance / 200., laccumulate=1)
    emitter_flux = []

    crossing_wall_time = times[-1] * steps_per_crossing / ss_check_interval  # Estimate wall time for one crossing
    print('crossing_wall_time estimate: {}, for {} steps'.format(crossing_wall_time, steps_per_crossing))
    print('wind-down loop time estimate: {}, for {} steps'.format(crossing_wall_time *
                                                                  steps_per_crossing / ss_check_interval,
                                                                  ss_check_interval))
    for sint in range(crossing_measurements):
        # Kill the loop and proceed to writeout if we don't have time to complete the loop
        if (max_wall_time - clock) < crossing_wall_time:
            early_abort = 2
            break

        record_time(step, times, steps_per_crossing)
        clock += times[-1]

        # Re-evaluate time for next loop
        crossing_wall_time = times[-1]

        # Record velocities of emitted particles for later KE calculation
        velocity_array = np.array([ZCross.getvx(js=measurement_beam.js),
                                   ZCross.getvy(js=measurement_beam.js),
                                   ZCross.getvz(js=measurement_beam.js)]).transpose()
        # velocity_array = velocity_array[velocity_array[:, 2] >= 0.]  # Filter particles moving to emitter
        emitter_flux.append(velocity_array)

        ZCross.clear()  # Clear ZcrossingParticles memory

        print("Measurement: {} of {} intervals completed. Interval run time: {} s".format(sint + 1,
                                                                                          crossing_measurements,
                                                                                          times[-1]))
    stop_eff_calc = top.it  # For diag file
    # Run wind-down until measurement particles have cleared
    measurement_beam.rnpinject = 0
    background_beam.rnpinject = PTCL_PER_STEP

    initial_population = measurement_beam.npsim[0]
    measurement_tol = 0.03
    # if particle_diagnostic_switch:
    #     particle_diagnostic_0.period = ss_check_interval
    while measurement_beam.npsim[0] > measurement_tol * initial_population:
        # Kill the loop and proceed to writeout if we don't have time to complete the loop
        if (max_wall_time - clock) < crossing_wall_time * ss_check_interval / steps_per_crossing :
            early_abort = 3
            break

        record_time(step, times, ss_check_interval)
        clock += times[-1]

        # Record velocities of emitted particles for later KE calculation
        # Check is required here as measurement_beam particles will not always be passing through
        if ZCross.getvx(js=measurement_beam.js).shape[0] > 0:
            velocity_array = np.array([ZCross.getvx(js=measurement_beam.js),
                                       ZCross.getvy(js=measurement_beam.js),
                                       ZCross.getvz(js=measurement_beam.js)]).transpose()
            print "Backwards particles: {}".format(np.where(velocity_array[:, 2] < 0.)[0].shape[0])
            # velocity_array = velocity_array[velocity_array[:, 2] >= 0.]  # Filter particles moving to emitter
            emitter_flux.append(velocity_array)
            ZCross.clear()  # Clear ZcrossingParticles memory
        print(" Wind-down: Taking {} steps, On Step: {}, {} Particles Left".format(ss_check_interval, top.it,
                                                                                   measurement_beam.npsim[0]))

    stop_winddown = top.it  # For diag file

    ######################
    # CALCULATE EFFICIENCY
    ######################
    try:
        emitter_flux = np.vstack(emitter_flux)
    except ValueError:
        # If this triggered then measurement emission never took place
        # Run took too long probably and abort took place
        emitter_flux = np.array([[0., 0., 0.]])

    # Find integrated charge on each conductor
    surface_charge = analyze_scraped_particles(top, measurement_beam, solverE)
    measured_charge = {}

    for key in surface_charge:
        # We can abuse the fact that js=0 for background species to filter it from the sum
        measured_charge[key] = np.sum(surface_charge[key][:, 1] * surface_charge[key][:, 3])

    # Set derived parameters from simulation
    efficiency.tec_parameters['run_time'][0] = crossing_measurements * steps_per_crossing * dt
    if crossing_measurements == 0:
        # Set to large value to force all powers and currents to zero
        efficiency.tec_parameters['run_time'][0] = 1e20

    # Find total number of measurement particles that were emitted
    total_macroparticles = measurement_beam.npsim[0] + np.sum([measured_charge[key] for key in surface_charge])
    efficiency.tec_parameters['J_em'][0] = e * (total_macroparticles - measured_charge[scraper_dictionary['source']]) \
                                        * measurement_beam.sw / \
                                        efficiency.tec_parameters['run_time'][0] / efficiency.tec_parameters['A_em'][0]

    # If grid isn't being used then J_grid will not be in scraper dict
    try:
        efficiency.tec_parameters['J_grid'][0] = e * measured_charge[scraper_dictionary['grid']] * measurement_beam.sw / \
                                            efficiency.tec_parameters['run_time'][0] / \
                                            (efficiency.tec_parameters['occlusion'][0] *
                                             efficiency.tec_parameters['A_em'][0])
    except KeyError:
        efficiency.tec_parameters['J_grid'][0] = 0.0

    efficiency.tec_parameters['J_ec'][0] = e * measured_charge[scraper_dictionary['collector']] * measurement_beam.sw / \
                                        efficiency.tec_parameters['run_time'][0] / efficiency.tec_parameters['A_em'][0]

    efficiency.tec_parameters['P_em'][0] = efficiency.calculate_power_flux(emitter_flux, measurement_beam.sw,
                                                                        efficiency.tec_parameters['phi_em'][0],
                                                                        **efficiency.tec_parameters)

    # Efficiency calculation
    print("Efficiency")
    efficiency_result = efficiency.calculate_efficiency(**efficiency.tec_parameters)
    print("Overall Efficiency: {}".format(efficiency_result['eta']))
    print("Total steps: {}".format(top.it))
    ######################
    # FINAL RUN STATISTICS
    ######################

    if comm_world.rank == 0:
        if not os.path.exists('diags_id{}'.format(run_id)):
            os.makedirs('diags_id{}'.format(run_id))

        np.save('iv_data.npy', np.array([circuit.current_history, circuit.voltage_history]))

        write_parameter_file(run_attributes, filename='diags_id{}/'.format(run_id))

        filename = 'efficiency_id{}.h5'.format(str(run_id))
        with h5.File(os.path.join('diags_id{}'.format(run_id), filename), 'w') as h5file:
            # TODO: Add current history
            eff_group = h5file.create_group('/efficiency')
            run_group = h5file.create_group('/attributes')
            scrap_group = h5file.create_group('/scraper')
            h5file.attrs['complete'] = early_abort
            for key in efficiency_result:
                eff_group.attrs[key] = efficiency_result[key]
            for key in efficiency.tec_parameters:
                eff_group.attrs[key] = efficiency.tec_parameters[key]
            for key in run_attributes:
                run_group.attrs[key] = run_attributes[key]
            run_group.attrs['dt'] = top.dt
            run_group.attrs['stop_initialization'] = stop_initialization
            run_group.attrs['stop_ss_check'] = stop_ss_check
            run_group.attrs['stop_eff_calc'] = stop_eff_calc
            run_group.attrs['stop_winddown'] = stop_winddown
            # for key, value in scraper_dictionary.iteritems():
            #     scrap_group.attrs[key] = measured_charge[value]
            #
            inv_scraper_dict = {value: key for key, value in scraper_dictionary.iteritems()}
            for cond in solverE.conductordatalist:
                cond_objs = cond[0]
                scrap_group.attrs[inv_scraper_dict[cond_objs.condid]] = measured_charge[cond_objs.condid]
                _, bckgrnd_current = cond_objs.get_current_history(js=0, l_lost=1, l_emit=0,
                                                                   l_image=0, tmin=None, tmax=None, nt=top.it)
                _, msrmnt_current = cond_objs.get_current_history(js=1, l_lost=1, l_emit=0,
                                                                  l_image=0, tmin=None, tmax=None, nt=top.it)
                scrap_group.create_dataset('{}_background'.format(inv_scraper_dict[cond_objs.condid]),
                                           data=bckgrnd_current)
                scrap_group.create_dataset('{}_measurement'.format(inv_scraper_dict[cond_objs.condid]),
                                           data=msrmnt_current)

            h5file.create_dataset('times', data=times)
    beam_current = sources.cl_limit(CATHODE_PHI, ANODE_WF, GRID_BIAS,
                                    PLATE_SPACING) * cathode_area

    beam.ibeam = beam_current
    beam.a0 = SOURCE_RADIUS_1
    beam.b0 = SOURCE_RADIUS_2
    w3d.l_inj_exact = True

elif USER_INJECT == 3:
    #Thermionic injection

    #Set injection flag
    top.inject = 6  # 1 means constant; 2 means space-charge limited injection;# 6 means user-specified

    beam_current = sources.j_rd(
        CATHODE_TEMP,
        CATHODE_PHI) * cathode_area  #steady state current in Amps
    beam.ibeam = beam_current
    beam.a0 = SOURCE_RADIUS_1
    beam.b0 = SOURCE_RADIUS_2

    myInjector = injectors.injectorUserDefined(beam, CATHODE_TEMP,
                                               CHANNEL_WIDTH, Z_PART_MIN,
                                               PTCL_PER_STEP)
    installuserinjection(myInjector.inject_thermionic)

    # These must be set for user injection
    top.ainject = 1.0
    top.binject = 1.0

derivqty()
Пример #5
0
def main(injection_type,
         cathode_temperature,
         cathode_workfunction,
         anode_workfunction,
         anode_voltage,
         gate_voltage,
         lambdaR,
         beta,
         srefprob,
         drefprob,
         reflection_scheme,
         gap_voltage,
         dgap,
         dt,
         nsteps,
         particles_per_step,
         file_path,
         fieldperiod=100,
         particleperiod=1000,
         reflections=True):
    settings = deepcopy(locals())
    ############################
    # Domain / Geometry / Mesh #
    ############################

    # Grid geometry

    w = 1.6e-3  # full hexagon width
    a = w / 2.0  # inner wall width
    b = 80e-6  # thickness
    r = np.sqrt(3) / 2. * w  # distance between two opposite walls
    d = np.sqrt(b**2 / 4. + b**2)  #
    h = 0.2e-3  # height of grid (length in z)

    PLATE_SPACING = dgap
    CHANNEL_WIDTH_X = w + 2 * d + a
    CHANNEL_WIDTH_Y = 2 * r + 2 * b

    # Dimensions
    X_MAX = +CHANNEL_WIDTH_X / 2.
    X_MIN = -X_MAX
    Y_MAX = +CHANNEL_WIDTH_Y / 2.
    Y_MIN = -Y_MAX
    Z_MAX = PLATE_SPACING
    Z_MIN = 0.

    # Grid parameters
    NUM_X = 100
    NUM_Y = 100
    NUM_Z = 25

    # z step size
    dx = (X_MAX - X_MIN) / NUM_X
    dy = (Y_MAX - Y_MIN) / NUM_Y
    dz = (Z_MAX - Z_MIN) / NUM_Z

    print(" --- (xmin, ymin, zmin) = ({}, {}, {})".format(X_MIN, Y_MIN, Z_MIN))
    print(" --- (xmax, ymax, zmax) = ({}, {}, {})".format(X_MAX, Y_MAX, Z_MAX))
    print(" --- (dx, dy, dz) = ({}, {}, {})".format(dx, dy, dz))

    # Solver Geometry and Boundaries

    # Specify solver geometry
    w3d.solvergeom = w3d.XYZgeom

    # Set field boundary conditions
    w3d.bound0 = dirichlet
    w3d.boundnz = dirichlet
    w3d.boundxy = periodic

    # Particles boundary conditions
    top.pbound0 = absorb
    top.pboundnz = absorb
    top.pboundxy = periodic

    # Set mesh boundaries
    w3d.xmmin = X_MIN
    w3d.xmmax = X_MAX
    w3d.ymmin = Y_MIN
    w3d.ymmax = Y_MAX
    w3d.zmmin = Z_MIN
    w3d.zmmax = Z_MAX

    # Set mesh cell counts
    w3d.nx = NUM_X
    w3d.ny = NUM_Y
    w3d.nz = NUM_Z

    ################
    # FIELD SOLVER #
    ################
    magnetic_field = True
    if magnetic_field:
        bz = np.zeros([w3d.nx, w3d.ny, w3d.nz])
        bz[:, :, :] = 200e-3
        z_start = w3d.zmmin
        z_stop = w3d.zmmax
        top.ibpush = 2
        addnewbgrd(z_start,
                   z_stop,
                   xs=w3d.xmmin,
                   dx=(w3d.xmmax - w3d.xmmin),
                   ys=w3d.ymmin,
                   dy=(w3d.ymmax - w3d.ymmin),
                   nx=w3d.nx,
                   ny=w3d.ny,
                   nz=w3d.nz,
                   bz=bz)

    # Set up fieldsolver
    f3d.mgtol = 1e-6
    solverE = MultiGrid3D()
    registersolver(solverE)

    ###############################
    # PARTICLE INJECTION SETTINGS #
    ###############################
    volts_on_conductor = gap_voltage

    # INJECTION SPECIFICATION
    USER_INJECT = injection_type

    # Cathode and anode settings
    CATHODE_TEMP = cathode_temperature
    CATHODE_PHI = cathode_workfunction
    ANODE_WF = anode_workfunction  # Can be used if vacuum level is being set
    CONDUCTOR_VOLTS = volts_on_conductor  # ACCEL_VOLTS used for velocity and CL calculations

    # Emitted species
    background_beam = Species(type=Electron, name='background')
    # Reflected species
    reflected_electrons = Species(type=Electron, name='reflected')

    # Emitter area and position
    SOURCE_RADIUS_1 = 0.5 * CHANNEL_WIDTH_X  # a0 parameter - X plane
    SOURCE_RADIUS_2 = 0.5 * CHANNEL_WIDTH_Y  # b0 parameter - Y plane
    Z_PART_MIN = dz / 1000.  # starting particle z value

    # Compute cathode area for geomtry-specific current calculations
    if (w3d.solvergeom == w3d.XYZgeom):
        # For 3D cartesion geometry only
        cathode_area = 4. * SOURCE_RADIUS_1 * SOURCE_RADIUS_2
    else:
        # Assume 2D XZ geometry
        cathode_area = 2. * SOURCE_RADIUS_1 * 1.

    # If using the XZ geometry, set so injection uses the same geometry
    top.linj_rectangle = (w3d.solvergeom == w3d.XZgeom
                          or w3d.solvergeom == w3d.XYZgeom)

    # Returns velocity beam_beta (in units of beta) for which frac of emitted particles have v < beam_beta * c
    beam_beta = sources.compute_cutoff_beta(CATHODE_TEMP, frac=0.99)

    PTCL_PER_STEP = particles_per_step
    if USER_INJECT == 1:
        CURRENT_MODIFIER = 0.5  # Factor to multiply CL current by when setting beam current
        # Constant current density - beam transverse velocity fixed to zero, very small longitduinal velocity

        # Set injection flag
        top.inject = 1  # 1 means constant; 2 means space-charge limited injection;# 6 means user-specified
        top.npinject = PTCL_PER_STEP
        beam_current = 4. / 9. * eps0 * sqrt(2. * echarge / background_beam.mass) \
                           * CONDUCTOR_VOLTS ** 1.5 / PLATE_SPACING ** 2 * cathode_area

        background_beam.ibeam = beam_current * CURRENT_MODIFIER

        background_beam.a0 = SOURCE_RADIUS_1
        background_beam.b0 = SOURCE_RADIUS_2
        background_beam.ap0 = .0e0
        background_beam.bp0 = .0e0

        w3d.l_inj_exact = True

        # Initial velocity settings (5% of c)
        vrms = np.sqrt(1 - 1 / (0.05 / 511e3 + 1)**2) * 3e8
        top.vzinject = vrms

    if USER_INJECT == 2:
        # SC limited Thermionic injection
        top.inject = 2

        # Set both beams to same npinject to keep weights the same
        background_beam.npinject = PTCL_PER_STEP
        top.finject = [1.0, 0.0]
        w3d.l_inj_exact = True

        # Specify thermal properties
        background_beam.vthz = np.sqrt(CATHODE_TEMP * kb_J /
                                       background_beam.mass)
        background_beam.vthperp = np.sqrt(CATHODE_TEMP * kb_J /
                                          background_beam.mass)
        top.lhalfmaxwellinject = 1  # inject z velocities as half Maxwellian

        beam_current = sources.j_rd(
            CATHODE_TEMP,
            CATHODE_PHI) * cathode_area  # steady state current in Amps
        print('beam current expected: {}, current density {}'.format(
            beam_current, beam_current / cathode_area))
        jcl = 0.
        if gap_voltage > 0.:
            jcl = 4. / 9. * eps0 * sqrt(2. * echarge / background_beam.mass) \
                * CONDUCTOR_VOLTS ** 1.5 / PLATE_SPACING ** 2 * cathode_area
        print('child-langmuir  limit: {}, current density {}'.format(
            jcl, jcl / cathode_area))
        background_beam.ibeam = beam_current
        background_beam.a0 = SOURCE_RADIUS_1
        background_beam.b0 = SOURCE_RADIUS_2
        background_beam.ap0 = .0e0
        background_beam.bp0 = .0e0

    if USER_INJECT == 4:
        w3d.l_inj_exact = True
        w3d.l_inj_user_particles_v = True

        # Schottky model
        top.inject = 1
        top.ninject = 1
        top.lhalfmaxwellinject = 1  # inject z velocities as half Maxwellian
        top.zinject = np.asarray([Z_PART_MIN])
        top.ainject = np.asarray([SOURCE_RADIUS_1])
        top.binject = np.asarray([SOURCE_RADIUS_2])
        top.finject = np.asarray([[1.0, 0.0]])

        electric_field = 0
        delta_w = np.sqrt(e**3 * electric_field / (4 * np.pi * eps0))
        A0 = 1.20173e6
        AR = A0 * lambdaR

        background_beam.a0 = SOURCE_RADIUS_1
        background_beam.b0 = SOURCE_RADIUS_2
        background_beam.ap0 = .0e0
        background_beam.bp0 = .0e0
        background_beam.vthz = np.sqrt(CATHODE_TEMP * kb_J /
                                       background_beam.mass)
        background_beam.vthperp = np.sqrt(CATHODE_TEMP * kb_J /
                                          background_beam.mass)

        # use Richardson current to estimate particle weight
        rd_current = AR * CATHODE_TEMP**2 * np.exp(
            -(CATHODE_PHI * e) / (CATHODE_TEMP * k)) * cathode_area
        electrons_per_second = rd_current / e
        electrons_per_step = electrons_per_second * dt
        background_beam.sw = electrons_per_step / PTCL_PER_STEP

        def schottky_emission():
            # schottky emission at cathode side

            global num_particles_res

            Ez = solverE.getez()
            Ez_mean = np.mean(Ez[:, :, 0])

            if w3d.inj_js == background_beam.js:
                delta_w = 0.
                if Ez_mean < 0.:
                    delta_w = np.sqrt(beta * e**3 * np.abs(Ez_mean) /
                                      (4 * np.pi * eps0))

                rd_current = AR * CATHODE_TEMP**2 * np.exp(
                    -(CATHODE_PHI * e - delta_w) /
                    (CATHODE_TEMP * k)) * cathode_area
                electrons_per_second = rd_current / e
                electrons_per_step = electrons_per_second * top.dt
                float_num_particles = electrons_per_step / background_beam.sw
                num_particles = int(float_num_particles + num_particles_res +
                                    np.random.rand())
                num_particles_res += float_num_particles - num_particles

                # --- inject np particles of species electrons1
                # --- Create the particles on the surface
                x = -background_beam.a0 + 2 * background_beam.a0 * np.random.rand(
                    num_particles)
                y = -background_beam.b0 + 2 * background_beam.b0 * np.random.rand(
                    num_particles)
                vz = np.random.rand(num_particles)
                vz = np.maximum(1e-14 * np.ones_like(vz), vz)
                vz = background_beam.vthz * np.sqrt(-2.0 * np.log(vz))

                vrf = np.random.rand(num_particles)
                vrf = np.maximum(1e-14 * np.ones_like(vrf), vrf)
                vrf = background_beam.vthz * np.sqrt(-2.0 * np.log(vrf))
                trf = 2 * np.pi * np.random.rand(num_particles)
                vx = vrf * np.cos(trf)
                vy = vrf * np.sin(trf)

                # --- Setup the injection arrays
                w3d.npgrp = num_particles
                gchange('Setpwork3d')
                # --- Fill in the data. All have the same z-velocity, vz1.
                w3d.xt[:] = x
                w3d.yt[:] = y
                w3d.uxt[:] = vx
                w3d.uyt[:] = vy
                w3d.uzt[:] = vz

        installuserparticlesinjection(schottky_emission)

    derivqty()

    print("weight:", background_beam.sw)

    ##########################
    # CONDUCTOR INSTALLATION #
    ##########################
    install_conductor = True

    # --- Anode Location
    zplate = Z_MAX

    # Create source conductors
    if install_conductor:
        emitter = ZPlane(zcent=w3d.zmmin, zsign=-1., voltage=0., condid=2)
    else:
        emitter = ZPlane(zcent=w3d.zmmin, zsign=-1., voltage=0.)

    # Create collector
    if install_conductor:
        collector = ZPlane(voltage=gap_voltage, zcent=zplate, condid=3)
    else:
        collector = ZPlane(voltage=gap_voltage, zcent=zplate)

    # Create grid

    if install_conductor:
        # Offsets to make sure grid is centered after install
        # Because case089 was not made with a hexagon at (0., 0.) it must be moved depending on STL file used and
        # version of STLconductor
        cxmin, cymin, czmin = -0.00260785012506, -0.00312974047847, 0.000135000009323
        cxmax, cymax, czmax = 0.00339215015993, 0.00287025980651, 0.000335000018822
        conductor = STLconductor(
            "honeycomb_case0.89t_xycenter_zcen470.stl",
            xcent=cxmin + (cxmax - cxmin) / 2.,
            ycent=cymin + (cymax - cymin) /
            2.,  #zcent=czmin + (czmax - czmin) / 2., disp=(0.,0.,1e-6),
            verbose="on",
            voltage=gate_voltage,
            normalization_factor=dz,
            condid=1)

    if install_conductor:
        installconductor(conductor, dfill=largepos)
        installconductor(emitter, dfill=largepos)
        installconductor(collector, dfill=largepos)
        scraper_diode = ParticleScraper([emitter, collector],
                                        lcollectlpdata=True,
                                        lsaveintercept=True)
        scraper_gate = ParticleScraper([conductor],
                                       lcollectlpdata=True,
                                       lsaveintercept=False)

        scraper_dictionary = {1: 'grid', 2: 'emitter', 3: 'collector'}
    else:
        installconductor(emitter, dfill=largepos)
        installconductor(collector, dfill=largepos)
        scraper = ParticleScraper([emitter, collector],
                                  lcollectlpdata=True,
                                  lsaveintercept=True)
        scraper_dictionary = {1: 'emitter', 2: 'collector'}

    ########################
    # Hacked Grid Scraping #
    ########################
    # Implements boxes with a reflection probability based on transparency attribute
    # used to crudely emulate particles reflected from the STLconductor honeycomb which cannot provide
    # scraper positions needed for reflection calculation
    grid_reflections = True

    if grid_reflections:
        grid_front = Box(xcent=0.,
                         ycent=0.,
                         zcent=0.135e-3 - dz / 2.,
                         xsize=2 * (X_MAX - X_MIN),
                         ysize=2 * (Y_MAX - Y_MIN),
                         zsize=dz)
        grid_back = Box(xcent=0.,
                        ycent=0.,
                        zcent=0.335e-3 + dz / 2,
                        xsize=2 * (X_MAX - X_MIN),
                        ysize=2 * (Y_MAX - Y_MIN),
                        zsize=dz)

        scraper_front = ParticleScraperGrid(grid_front,
                                            lcollectlpdata=True,
                                            lsaveintercept=True)
        scraper_back = ParticleScraperGrid(grid_back,
                                           lcollectlpdata=True,
                                           lsaveintercept=True)

        # Fraction of particles expected to pass through the conductor (assuming they cross it in a single step)
        scraper_front.transparency = 0.80
        scraper_back.transparency = 0.80

        # Directional scraper to prevent particles from being scraped coming out of honeycomb interior
        scraper_front.directional_scraper = -1
        scraper_back.directional_scraper = +1

    #####################
    # Diagnostics Setup #
    #####################

    efield_diagnostic_0 = FieldDiagnostic.ElectrostaticFields(
        solver=solverE,
        top=top,
        w3d=w3d,
        comm_world=comm_world,
        period=fieldperiod,
        write_dir=os.path.join(file_path, 'fields'))
    installafterstep(efield_diagnostic_0.write)

    particle_diagnostic_0 = ParticleDiagnostic(
        period=particleperiod,
        top=top,
        w3d=w3d,
        species={species.name: species
                 for species in listofallspecies},
        comm_world=comm_world,
        lparallel_output=False,
        write_dir=file_path)
    installafterstep(particle_diagnostic_0.write)

    ####################
    # CONTROL SEQUENCE #
    ####################

    # prevent gist from starting upon setup
    top.lprntpara = false
    top.lpsplots = false

    top.verbosity = 1  # Reduce solver verbosity
    solverE.mgverbose = 1  # further reduce output upon stepping - prevents websocket timeouts in Jupyter notebook

    init_iters = 2000
    regular_iters = 50

    init_tol = 1e-5
    regular_tol = 1e-6

    # Time Step
    top.dt = dt

    # Define and install particle reflector
    if reflections:
        collector_reflector = ParticleReflector(scraper=scraper_diode,
                                                conductor=collector,
                                                spref=reflected_electrons,
                                                srefprob=srefprob,
                                                drefprob=drefprob,
                                                refscheme=reflection_scheme)
        installparticlereflector(collector_reflector)
        print("reflection_scheme = " + reflection_scheme)
    else:
        print("reflections: off")

    if grid_reflections:
        reflector_front = ParticleReflector(scraper=scraper_front,
                                            conductor=grid_front,
                                            spref=reflected_electrons,
                                            srefprob=srefprob,
                                            drefprob=0.75,
                                            refscheme=reflection_scheme)
        installparticlereflector(reflector_front)

        reflector_back = ParticleReflector(scraper=scraper_back,
                                           conductor=grid_back,
                                           spref=reflected_electrons,
                                           srefprob=srefprob,
                                           drefprob=0.75,
                                           refscheme=reflection_scheme)
        installparticlereflector(reflector_back)

    # initialize field solver and potential field
    solverE.mgmaxiters = init_iters
    solverE.mgtol = init_tol

    package("w3d")
    generate()

    # Specify particle weight for reflected_electrons
    reflected_electrons.sw = background_beam.sw
    print("weight: background_beam = {}, reflected = {}".format(
        background_beam.sw, reflected_electrons.sw))

    solverE.mgmaxiters = regular_iters
    solverE.mgtol = regular_tol
    step(nsteps)

    ####################
    # Final Output     #
    ####################

    surface_charge = analyze_collected_charge(top, solverE)
    if reflections:
        reflected_charge = analyze_reflected_charge(top, [collector_reflector],
                                                    comm_world=comm_world)

    if comm_world.rank == 0:
        filename = os.path.join(
            file_path, "all_charge_anodeV_{}.h5".format(anode_voltage))
        diag_file = h5.File(filename, 'w')

        # Write simulation parameters
        for key, val in settings.items():
            diag_file.attrs[key] = val
        for dom_attr in [
                'xmmin', 'xmmax', 'ymmin', 'ymmax', 'zmmin', 'zmmax', 'nx',
                'ny', 'nz'
        ]:
            diag_file.attrs[dom_attr] = eval('w3d.' + dom_attr)

        # Record scraped particles into scraper group of file
        scraper_data = diag_file.create_group('scraper')
        for key, val in scraper_dictionary.items():
            scraper_data.attrs[val] = key

        for condid, cond_data in surface_charge.items():
            cond_group = scraper_data.create_group('{}'.format(
                scraper_dictionary[condid]))
            for i, spec_dat in enumerate(cond_data):
                cond_group.create_dataset(listofallspecies[i].name,
                                          data=spec_dat)

        # Record reflected particles into reflector group
        if reflections:
            reflector_data = diag_file.create_group('reflector')
            for key in reflected_charge:
                reflector_data.attrs[scraper_dictionary[key]] = key

            for condid, ref_data in reflected_charge.items():
                refl_group = reflector_data.create_group('{}'.format(
                    scraper_dictionary[condid]))
                refl_group.create_dataset('reflected', data=ref_data)

        diag_file.close()
Пример #6
0
def main(x_struts,
         y_struts,
         V_grid,
         grid_height,
         strut_width,
         strut_height,
         rho_ew,
         T_em,
         phi_em,
         T_coll,
         phi_coll,
         rho_cw,
         gap_distance,
         rho_load,
         run_id,
         channel_width=100e-9,
         injection_type=2,
         magnetic_field=0.0,
         random_seed=True,
         install_grid=True,
         install_circuit=True,
         max_wall_time=1e9,
         particle_diagnostic_switch=False,
         field_diagnostic_switch=False,
         lost_diagnostic_switch=False):
    """
    Run a simulation of a gridded TEC.
    Args:
        x_struts: Number of struts that intercept the x-axis.
        y_struts: Number of struts that intercept the y-axis
        V_grid: Voltage to place on the grid in Volts.
        grid_height: Distance from the emitter to the grid normalized by gap_distance, unitless.
        strut_width: Transverse extent of the struts in meters.
        strut_height: Longitudinal extent of the struts in meters.
        rho_ew: Emitter side wiring resistivity, ohms*cm.
        T_em: Emitter temperature, kelvin.
        phi_em: Emitter work function, eV.
        T_coll: Collector termperature, kelvin.
        phi_coll: Collector work function, eV.
        rho_cw: Collector side wiring resistivity, ohms*cm.
        gap_distance: Distance from emitter to collector, meters.
        rho_load: Load resistivity, ohms*cm.
        run_id: Run ID. Mainly used for parallel optimization.
        injection_type: 1: For constant current emission with only thermal velocity spread in z and CL limited emission.
                        2: For true thermionic emission. Velocity spread along all axes.
        random_seed: True/False. If True, will force a random seed to be used for emission positions.
        install_grid: True/False. If False the grid will not be installed. Results in simple parallel plate setup.
                                  If False then phi_em - phi_coll specifies the voltage on the collector.
        install_circuit: True/False. Include external circuit that will modulate gap voltage based on
                                     current flow and contact potential between cathode/anode.
        max_wall_time: Wall time to allow simulation to run for. Simulation is periodically checked and will halt if it
                        appears the next segment of the simulation will exceed max_wall_time. This is not guaranteed to
                        work since the guess is based on the run time up to that point.
                        Intended to be used when running on system with job manager.
        particle_diagnostic_switch: True/False. Use openPMD compliant .h5 particle diagnostics.
        field_diagnostic_switch: True/False. Use rswarp electrostatic .h5 field diagnostics (Maybe openPMD compliant?).
        lost_diagnostic_switch: True/False. Enable collection of lost particle coordinates
                        with rswarp.diagnostics.parallel.save_lost_particles.

    """
    # record inputs and set parameters
    run_attributes = deepcopy(locals())

    for key in run_attributes:
        if key in efficiency.tec_parameters:
            efficiency.tec_parameters[key][0] = run_attributes[key]

    # set new random seed
    if random_seed:
        top.seedranf(randint(1, 1e9))

    # Control for printing in parallel
    if comm_world.size != 1:
        synchronizeQueuedOutput_mpi4py(out=True, error=True)

    if particle_diagnostic_switch or field_diagnostic_switch:
        # Directory paths
        diagDir = 'diags_id{}/hdf5/'.format(run_id)
        field_base_path = 'diags_id{}/fields/'.format(run_id)
        diagFDir = {
            'magnetic': 'diags_id{}/fields/magnetic'.format(run_id),
            'electric': 'diags_id{}/fields/electric'.format(run_id)
        }

        # Cleanup command if directories already exist
        if comm_world.rank == 0:
            cleanupPrevious(diagDir, diagFDir)

    load_balance = LoadBalancer()
    ######################
    # DOMAIN/GEOMETRY/MESH
    ######################

    # Dimensions
    X_MAX = +channel_width / 2.
    X_MIN = -X_MAX
    Y_MAX = +channel_width / 2.
    Y_MIN = -Y_MAX
    Z_MAX = gap_distance
    Z_MIN = 0.

    # TODO: cells in all dimensions reduced by 10x for testing, will need to verify if this is reasonable (TEMP)
    # Grid parameters
    dx_want = 5e-9
    dy_want = 5e-9
    dz_want = 5e-9

    NUM_X = int(round(channel_width / dx_want))  # 20 #128 #10
    NUM_Y = int(round(channel_width / dy_want))  # 20 #128 #10
    NUM_Z = int(round(gap_distance / dz_want))

    # mesh spacing
    dz = (Z_MAX - Z_MIN) / NUM_Z
    dx = channel_width / NUM_X
    dy = channel_width / NUM_Y

    print "Channel width: {}, DX = {}".format(channel_width, dx)
    print "Channel width: {}, DY = {}".format(channel_width, dy)
    print "Channel length: {}, DZ = {}".format(gap_distance, dz)

    # Solver Geometry and Boundaries

    # Specify solver geometry
    w3d.solvergeom = w3d.XYZgeom

    # Set field boundary conditions
    w3d.bound0 = neumann
    w3d.boundnz = dirichlet
    w3d.boundxy = periodic
    # Particles boundary conditions
    top.pbound0 = absorb
    top.pboundnz = absorb
    top.pboundxy = periodic

    # Set mesh boundaries
    w3d.xmmin = X_MIN
    w3d.xmmax = X_MAX
    w3d.ymmin = Y_MIN
    w3d.ymmax = Y_MAX
    w3d.zmmin = 0.
    w3d.zmmax = Z_MAX

    # Set mesh cell counts
    w3d.nx = NUM_X
    w3d.ny = NUM_Y
    w3d.nz = NUM_Z

    #############################
    # PARTICLE INJECTION SETTINGS
    #############################

    # Cathode and anode settings
    EMITTER_TEMP = T_em
    EMITTER_PHI = phi_em  # work function in eV
    COLLECTOR_PHI = phi_coll  # Can be used if vacuum level is being set
    ACCEL_VOLTS = V_grid  # ACCEL_VOLTS used for velocity and CL calculations
    collector_voltage = phi_em - phi_coll

    # Emitted species
    background_beam = Species(type=Electron, name='background')
    measurement_beam = Species(type=Electron, name='measurement')

    # Emitter area and position
    SOURCE_RADIUS_1 = 0.5 * channel_width  # a0 parameter - X plane
    SOURCE_RADIUS_2 = 0.5 * channel_width  # b0 parameter - Y plane
    Z_PART_MIN = dz / 1000.  # starting particle z value

    # Compute cathode area for geomtry-specific current calculations
    if (w3d.solvergeom == w3d.XYZgeom):
        # For 3D cartesion geometry only
        cathode_area = 4. * SOURCE_RADIUS_1 * SOURCE_RADIUS_2
    else:
        # Assume 2D XZ geometry
        cathode_area = 2. * SOURCE_RADIUS_1 * 1.

    # If using the XZ geometry, set so injection uses the same geometry
    top.linj_rectangle = (w3d.solvergeom == w3d.XZgeom
                          or w3d.solvergeom == w3d.XYZgeom)

    # Returns velocity beam_beta (in units of beta) for which frac of emitted particles have v < beam_beta * c
    beam_beta = sources.compute_cutoff_beta(EMITTER_TEMP, frac=0.99)

    PTCL_PER_STEP = 100

    if injection_type == 1:
        CURRENT_MODIFIER = 0.5  # Factor to multiply CL current by when setting beam current
        # Constant current density - beam transverse velocity fixed to zero, very small longitduinal velocity

        # Set injection flag
        top.inject = 1  # 1 means constant; 2 means space-charge limited injection;# 6 means user-specified
        top.npinject = PTCL_PER_STEP
        beam_current = 4. / 9. * eps0 * sqrt(2. * echarge / background_beam.mass) \
                       * ACCEL_VOLTS ** 1.5 / gap_distance ** 2 * cathode_area

        background_beam.ibeam = beam_current * CURRENT_MODIFIER

        background_beam.a0 = SOURCE_RADIUS_1
        background_beam.b0 = SOURCE_RADIUS_2
        background_beam.ap0 = .0e0
        background_beam.bp0 = .0e0

        w3d.l_inj_exact = True

        # Initial velocity settings (5% of c)
        vrms = np.sqrt(1 - 1 / (0.05 / 511e3 + 1)**2) * 3e8
        top.vzinject = vrms

    if injection_type == 2:
        # True Thermionic injection
        top.inject = 1

        # Set both beams to same npinject to keep weights the same
        background_beam.npinject = PTCL_PER_STEP
        measurement_beam.npinject = PTCL_PER_STEP

        w3d.l_inj_exact = True

        # Specify thermal properties
        background_beam.vthz = measurement_beam.vthz = np.sqrt(
            EMITTER_TEMP * kb_J / background_beam.mass)
        background_beam.vthperp = measurement_beam.vthperp = np.sqrt(
            EMITTER_TEMP * kb_J / background_beam.mass)
        top.lhalfmaxwellinject = 1  # inject z velocities as half Maxwellian

        beam_current = sources.j_rd(
            EMITTER_TEMP,
            EMITTER_PHI) * cathode_area  # steady state current in Amps
        print('beam current expected: {}, current density {}'.format(
            beam_current, beam_current / cathode_area))
        jcl = 4. / 9. * eps0 * sqrt(2. * echarge / background_beam.mass) \
                       * ACCEL_VOLTS ** 1.5 / gap_distance ** 2 * cathode_area
        print('child-langmuir  limit: {}, current density {}'.format(
            jcl, jcl / cathode_area))
        background_beam.ibeam = measurement_beam.ibeam = beam_current
        background_beam.a0 = measurement_beam.a0 = SOURCE_RADIUS_1
        background_beam.b0 = measurement_beam.b0 = SOURCE_RADIUS_2
        background_beam.ap0 = measurement_beam.ap0 = .0e0
        background_beam.bp0 = measurement_beam.bp0 = .0e0

    derivqty()

    ##############
    # FIELD SOLVER
    ##############
    # Add Uniform B_z field if turned on
    if magnetic_field:
        bz = np.zeros([w3d.nx, w3d.ny, w3d.nz])
        bz[:, :, :] = magnetic_field
        z_start = w3d.zmmin
        z_stop = w3d.zmmax
        top.ibpush = 2
        addnewbgrd(z_start,
                   z_stop,
                   xs=w3d.xmmin,
                   dx=(w3d.xmmax - w3d.xmmin),
                   ys=w3d.ymmin,
                   dy=(w3d.ymmax - w3d.ymmin),
                   nx=w3d.nx,
                   ny=w3d.ny,
                   nz=w3d.nz,
                   bz=bz)

    # Set up fieldsolver
    f3d.mgtol = 1e-6
    solverE = MultiGrid3D()
    registersolver(solverE)

    ########################
    # CONDUCTOR INSTALLATION
    ########################

    if install_grid:
        accel_grid, gl = create_grid(x_struts, y_struts, V_grid,
                                     grid_height * gap_distance, strut_width,
                                     strut_height, channel_width)
        accel_grid.voltage = V_grid

    # --- Anode Location
    zplate = Z_MAX

    # Create source conductors
    if install_grid:
        source = ZPlane(zcent=w3d.zmmin, zsign=-1., voltage=0., condid=2)
    else:
        source = ZPlane(zcent=w3d.zmmin, zsign=-1., voltage=0.)

    # Create ground plate
    total_rho = efficiency.tec_parameters['rho_load'][0]
    if install_grid:
        plate = ZPlane(zcent=zplate, condid=3)
        if install_circuit:
            circuit = ExternalCircuit(top,
                                      solverE,
                                      total_rho,
                                      collector_voltage,
                                      cathode_area * 1e4,
                                      plate,
                                      debug=False)
        plate.voltage = circuit
        # plate.voltage = collector_voltage
    else:
        plate = ZPlane(zcent=zplate)
        if install_circuit:
            circuit = ExternalCircuit(top,
                                      solverE,
                                      total_rho,
                                      collector_voltage,
                                      cathode_area * 1e4,
                                      plate,
                                      debug=False)
        plate.voltage = circuit
        # plate.voltage = collector_voltage

    if install_grid:
        installconductor(accel_grid)
        installconductor(source, dfill=largepos)
        installconductor(plate, dfill=largepos)
        scraper = ParticleScraper([accel_grid, source, plate],
                                  lcollectlpdata=True,
                                  lsaveintercept=True)
        scraper_dictionary = {'grid': 1, 'source': 2, 'collector': 3}
    else:
        installconductor(source, dfill=largepos)
        installconductor(plate, dfill=largepos)
        scraper = ParticleScraper([source, plate],
                                  lcollectlpdata=True,
                                  lsaveintercept=True)
        scraper_dictionary = {'source': 1, 'collector': 2}

    #############
    # DIAGNOSTICS
    #############

    # Particle/Field diagnostic options
    if particle_diagnostic_switch:
        particleperiod = 250  # TEMP
        particle_diagnostic_0 = ParticleDiagnostic(
            period=particleperiod,
            top=top,
            w3d=w3d,
            species={species.name: species
                     for species in listofallspecies},
            # if species.name == 'measurement'}, # TEMP
            comm_world=comm_world,
            lparallel_output=False,
            write_dir=diagDir[:-5])
        installafterstep(particle_diagnostic_0.write)

    if field_diagnostic_switch:
        fieldperiod = 1000
        efield_diagnostic_0 = FieldDiagnostic.ElectrostaticFields(
            solver=solverE,
            top=top,
            w3d=w3d,
            comm_world=comm_world,
            period=fieldperiod)
        installafterstep(efield_diagnostic_0.write)

    # Set externally derived parameters for efficiency calculation
    efficiency.tec_parameters['A_em'][0] = cathode_area * 1e4  # cm**2
    if install_grid:
        efficiency.tec_parameters['occlusion'][
            0] = efficiency.calculate_occlusion(**efficiency.tec_parameters)
    else:
        efficiency.tec_parameters['occlusion'][0] = 0.0

    ##########################
    # SOLVER SETTINGS/GENERATE
    ##########################

    # prevent gist from starting upon setup
    top.lprntpara = false
    top.lpsplots = false

    top.verbosity = -1  # Reduce solver verbosity
    solverE.mgverbose = -1  # further reduce output upon stepping - prevents websocket timeouts in Jupyter notebook

    init_iters = 20000
    regular_iters = 200

    init_tol = 1e-6
    regular_tol = 1e-6

    # Time Step

    # Determine an appropriate time step based upon estimated final velocity
    if install_grid:
        vz_accel = sqrt(2. * abs(V_grid) * np.abs(background_beam.charge) /
                        background_beam.mass)
    else:
        vz_accel = sqrt(2. * abs(collector_voltage) *
                        np.abs(background_beam.charge) / background_beam.mass)
    vzfinal = vz_accel + beam_beta * c
    dt = dz / vzfinal
    top.dt = dt

    solverE.mgmaxiters = init_iters
    solverE.mgtol = init_tol
    package("w3d")
    generate()
    solverE.mgtol = regular_tol
    solverE.mgmaxiters = regular_iters

    print("weights (background) (measurement): {}, {}".format(
        background_beam.sw, measurement_beam.sw))

    # Use rnpinject to set number of macroparticles emitted
    background_beam.rnpinject = PTCL_PER_STEP
    measurement_beam.rnpinject = 0  # measurement beam is off at start

    ##################
    # CONTROL SEQUENCE
    ##################
    # Run until steady state is achieved (flat current profile at collector) (measurement species turned on)
    # Record data for effiency calculation
    # Switch off measurement species and wait for simulation to clear (background species is switched on)

    early_abort = 0  # If true will flag output data to notify
    startup_time = 4 * gap_distance / vz_accel  # ~4 crossing times to approach steady-state with external circuit
    crossing_measurements = 10  # Number of crossing times to record for
    steps_per_crossing = int(gap_distance / vz_accel / dt)
    ss_check_interval = int(steps_per_crossing / 2.)
    ss_max_checks = 8  # Maximum number of of times to run steady-state check procedure before aborting
    times = []  # Write out timing of cycle steps to file
    clock = 0  # clock tracks the current, total simulation-runtime

    # Run initial block of steps
    record_time(stept, times, startup_time)
    clock += times[-1]
    stop_initialization = top.it  # for diag file

    print("Completed Initialization on Step {}\nInitialization run time: {}".
          format(top.it, times[-1]))

    # Start checking for Steady State Operation
    tol = 0.01
    ss_flag = 0
    check_count = 0  # Track number of times steady-state check performed

    while ss_flag != 1 and check_count < ss_max_checks:
        if (max_wall_time - clock) < times[-1]:
            early_abort = 1
            break

        record_time(step, times, ss_check_interval * 4)
        clock += times[-1]

        tstart = (top.it - ss_check_interval) * top.dt
        _, current1 = plate.get_current_history(js=None,
                                                l_lost=1,
                                                l_emit=0,
                                                l_image=0,
                                                tmin=tstart,
                                                tmax=None,
                                                nt=1)
        current = np.sum(current1)

        if np.abs(
                current
        ) < 0.5 * efficiency.tec_parameters['occlusion'][0] * beam_current:
            # If too little current is getting through run another check cycle
            check_count += 1
            print(
                "Completed check {}, insufficient current, running again for {} steps"
                .format(check_count, ss_check_interval))
            continue

        ss_flag = 1
        # print np.abs(current), 0.5 * efficiency.tec_parameters['occlusion'][0] * beam_current
        # try:
        #     # If steady_state check initialized no need to do it again
        #     steady_state
        # except NameError:
        #     # If this is the first pass with sufficient current then initialize the check
        #     if check_count == 0:
        #         # If the initial period was long enough to get current on collector then use that
        #         steady_state = SteadyState(top, plate, steps_per_crossing)
        #     else:
        #         # If we had to run several steady state checks with no current then just use the period with current
        #         steady_state = SteadyState(top, plate, ss_check_interval)
        #
        # ss_flag = steady_state(steps_per_crossing)
        check_count += 1

    stop_ss_check = top.it  # For diag file

    # If there was a failure to reach steady state after specified number of checks then pass directly end
    if check_count == ss_max_checks:
        early_abort = -1
        crossing_measurements = 0
        print("Failed to reach steady state. Aborting simulation.")
    else:
        # Start Steady State Operation
        print(
            " Steady State Reached.\nStarting efficiency "
            "recording for {} crossing times.\nThis will be {} steps".format(
                crossing_measurements,
                steps_per_crossing * crossing_measurements))

    # particle_diagnostic_0.period = steps_per_crossing #TEMP commented out
    # Switch to measurement beam species
    measurement_beam.rnpinject = PTCL_PER_STEP
    background_beam.rnpinject = 0

    # Install Zcrossing Diagnostic
    ZCross = ZCrossingParticles(zz=grid_height * gap_distance / 200.,
                                laccumulate=1)
    emitter_flux = []

    crossing_wall_time = times[
        -1] * steps_per_crossing / ss_check_interval  # Estimate wall time for one crossing
    print('crossing_wall_time estimate: {}, for {} steps'.format(
        crossing_wall_time, steps_per_crossing))
    print('wind-down loop time estimate: {}, for {} steps'.format(
        crossing_wall_time * steps_per_crossing / ss_check_interval,
        ss_check_interval))
    for sint in range(crossing_measurements):
        # Kill the loop and proceed to writeout if we don't have time to complete the loop
        if (max_wall_time - clock) < crossing_wall_time:
            early_abort = 2
            break

        record_time(step, times, steps_per_crossing)
        clock += times[-1]

        # Re-evaluate time for next loop
        crossing_wall_time = times[-1]

        # Record velocities of emitted particles for later KE calculation
        velocity_array = np.array([
            ZCross.getvx(js=measurement_beam.js),
            ZCross.getvy(js=measurement_beam.js),
            ZCross.getvz(js=measurement_beam.js)
        ]).transpose()
        # velocity_array = velocity_array[velocity_array[:, 2] >= 0.]  # Filter particles moving to emitter
        emitter_flux.append(velocity_array)

        ZCross.clear()  # Clear ZcrossingParticles memory

        print(
            "Measurement: {} of {} intervals completed. Interval run time: {} s"
            .format(sint + 1, crossing_measurements, times[-1]))
    stop_eff_calc = top.it  # For diag file
    # Run wind-down until measurement particles have cleared
    measurement_beam.rnpinject = 0
    background_beam.rnpinject = PTCL_PER_STEP

    initial_population = measurement_beam.npsim[0]
    measurement_tol = 0.03
    # if particle_diagnostic_switch:
    #     particle_diagnostic_0.period = ss_check_interval
    while measurement_beam.npsim[0] > measurement_tol * initial_population:
        # Kill the loop and proceed to writeout if we don't have time to complete the loop
        if (max_wall_time - clock
            ) < crossing_wall_time * ss_check_interval / steps_per_crossing:
            early_abort = 3
            break

        record_time(step, times, ss_check_interval)
        clock += times[-1]

        # Record velocities of emitted particles for later KE calculation
        # Check is required here as measurement_beam particles will not always be passing through
        if ZCross.getvx(js=measurement_beam.js).shape[0] > 0:
            velocity_array = np.array([
                ZCross.getvx(js=measurement_beam.js),
                ZCross.getvy(js=measurement_beam.js),
                ZCross.getvz(js=measurement_beam.js)
            ]).transpose()
            print "Backwards particles: {}".format(
                np.where(velocity_array[:, 2] < 0.)[0].shape[0])
            # velocity_array = velocity_array[velocity_array[:, 2] >= 0.]  # Filter particles moving to emitter
            emitter_flux.append(velocity_array)
            ZCross.clear()  # Clear ZcrossingParticles memory
        print(" Wind-down: Taking {} steps, On Step: {}, {} Particles Left".
              format(ss_check_interval, top.it, measurement_beam.npsim[0]))

    stop_winddown = top.it  # For diag file

    ######################
    # CALCULATE EFFICIENCY
    ######################
    try:
        emitter_flux = np.vstack(emitter_flux)
    except ValueError:
        # If this triggered then measurement emission never took place
        # Run took too long probably and abort took place
        emitter_flux = np.array([[0., 0., 0.]])

    # Find integrated charge on each conductor
    surface_charge = analyze_scraped_particles(top, measurement_beam, solverE)
    measured_charge = {}

    for key in surface_charge:
        # We can abuse the fact that js=0 for background species to filter it from the sum
        measured_charge[key] = np.sum(surface_charge[key][:, 1] *
                                      surface_charge[key][:, 3])

    # Set derived parameters from simulation
    efficiency.tec_parameters['run_time'][
        0] = crossing_measurements * steps_per_crossing * dt
    if crossing_measurements == 0:
        # Set to large value to force all powers and currents to zero
        efficiency.tec_parameters['run_time'][0] = 1e20

    # Find total number of measurement particles that were emitted
    total_macroparticles = measurement_beam.npsim[0] + np.sum(
        [measured_charge[key] for key in surface_charge])
    efficiency.tec_parameters['J_em'][0] = e * (total_macroparticles - measured_charge[scraper_dictionary['source']]) \
                                        * measurement_beam.sw / \
                                        efficiency.tec_parameters['run_time'][0] / efficiency.tec_parameters['A_em'][0]

    # If grid isn't being used then J_grid will not be in scraper dict
    try:
        efficiency.tec_parameters['J_grid'][0] = e * measured_charge[scraper_dictionary['grid']] * measurement_beam.sw / \
                                            efficiency.tec_parameters['run_time'][0] / \
                                            (efficiency.tec_parameters['occlusion'][0] *
                                             efficiency.tec_parameters['A_em'][0])
    except KeyError:
        efficiency.tec_parameters['J_grid'][0] = 0.0

    efficiency.tec_parameters['J_ec'][0] = e * measured_charge[scraper_dictionary['collector']] * measurement_beam.sw / \
                                        efficiency.tec_parameters['run_time'][0] / efficiency.tec_parameters['A_em'][0]

    efficiency.tec_parameters['P_em'][0] = efficiency.calculate_power_flux(
        emitter_flux, measurement_beam.sw,
        efficiency.tec_parameters['phi_em'][0], **efficiency.tec_parameters)

    # Efficiency calculation
    print("Efficiency")
    efficiency_result = efficiency.calculate_efficiency(
        **efficiency.tec_parameters)
    print("Overall Efficiency: {}".format(efficiency_result['eta']))
    print("Total steps: {}".format(top.it))
    ######################
    # FINAL RUN STATISTICS
    ######################

    if comm_world.rank == 0:
        if not os.path.exists('diags_id{}'.format(run_id)):
            os.makedirs('diags_id{}'.format(run_id))

        np.save('iv_data.npy',
                np.array([circuit.current_history, circuit.voltage_history]))

        write_parameter_file(run_attributes,
                             filename='diags_id{}/'.format(run_id))

        filename = 'efficiency_id{}.h5'.format(str(run_id))
        with h5.File(os.path.join('diags_id{}'.format(run_id), filename),
                     'w') as h5file:
            # TODO: Add current history
            eff_group = h5file.create_group('/efficiency')
            run_group = h5file.create_group('/attributes')
            scrap_group = h5file.create_group('/scraper')
            h5file.attrs['complete'] = early_abort
            for key in efficiency_result:
                eff_group.attrs[key] = efficiency_result[key]
            for key in efficiency.tec_parameters:
                eff_group.attrs[key] = efficiency.tec_parameters[key]
            for key in run_attributes:
                run_group.attrs[key] = run_attributes[key]
            run_group.attrs['dt'] = top.dt
            run_group.attrs['stop_initialization'] = stop_initialization
            run_group.attrs['stop_ss_check'] = stop_ss_check
            run_group.attrs['stop_eff_calc'] = stop_eff_calc
            run_group.attrs['stop_winddown'] = stop_winddown
            # for key, value in scraper_dictionary.iteritems():
            #     scrap_group.attrs[key] = measured_charge[value]
            #
            inv_scraper_dict = {
                value: key
                for key, value in scraper_dictionary.iteritems()
            }
            for cond in solverE.conductordatalist:
                cond_objs = cond[0]
                scrap_group.attrs[inv_scraper_dict[
                    cond_objs.condid]] = measured_charge[cond_objs.condid]
                _, bckgrnd_current = cond_objs.get_current_history(js=0,
                                                                   l_lost=1,
                                                                   l_emit=0,
                                                                   l_image=0,
                                                                   tmin=None,
                                                                   tmax=None,
                                                                   nt=top.it)
                _, msrmnt_current = cond_objs.get_current_history(js=1,
                                                                  l_lost=1,
                                                                  l_emit=0,
                                                                  l_image=0,
                                                                  tmin=None,
                                                                  tmax=None,
                                                                  nt=top.it)
                scrap_group.create_dataset('{}_background'.format(
                    inv_scraper_dict[cond_objs.condid]),
                                           data=bckgrnd_current)
                scrap_group.create_dataset('{}_measurement'.format(
                    inv_scraper_dict[cond_objs.condid]),
                                           data=msrmnt_current)

            h5file.create_dataset('times', data=times)
    beam_current = sources.cl_limit(CATHODE_PHI, ANODE_WF, GRID_BIAS, PLATE_SPACING)*cathode_area
    
    
    beam.ibeam = beam_current
    beam.a0     = SOURCE_RADIUS_1
    beam.b0     = SOURCE_RADIUS_2    
    w3d.l_inj_exact = True

    
elif USER_INJECT == 3:
    #Thermionic injection
    
    #Set injection flag
    top.inject = 6               # 1 means constant; 2 means space-charge limited injection;# 6 means user-specified   

    beam_current = sources.j_rd(CATHODE_TEMP,CATHODE_PHI)*cathode_area #steady state current in Amps
    beam.ibeam = beam_current
    beam.a0     = SOURCE_RADIUS_1
    beam.b0     = SOURCE_RADIUS_2
    
    myInjector = injectors.injectorUserDefined(beam, CATHODE_TEMP, CHANNEL_WIDTH, Z_PART_MIN, PTCL_PER_STEP)
    installuserinjection(myInjector.inject_thermionic)
    
    # These must be set for user injection
    top.ainject = 1.0          
    top.binject = 1.0


derivqty()