Example #1
0
    def calculate_potential(depletion_mask=None, y_dep_new=None):
        potential = fipy.CellVariable(mesh=mesh, name='potential', value=0.)
        electrons = fipy.CellVariable(mesh=mesh, name='e-')
        electrons.valence = -1
        charge = electrons * electrons.valence
        charge.name = "charge"

        # Uniform charge distribution by setting a uniform concentration of
        # electrons = 1
        electrons.setValue(rho_scale)

        # A depletion zone within the bulk requires an internal boundary
        # condition. Internal boundary conditions seem to challenge fipy, see:
        # http://www.ctcms.nist.gov/fipy/documentation/USAGE.html#applying-internal-boundary-conditions

        large_value = 1e+10  # Hack for optimizer

        if depletion_mask is not None:
            # FIXME: Generic depletion_mask not working
            # Is overwritten here with a simple 1D depletion mask
            depletion_mask = np.logical_and(potential.mesh.y > y_dep_new[0],
                                            potential.mesh.y > y_dep_new[0])
            potential.equation = (fipy.DiffusionTerm(coeff=epsilon_scaled) +
                                  charge == fipy.ImplicitSourceTerm(
                                      depletion_mask * large_value) -
                                  depletion_mask * large_value * V_bias)
        else:
            potential.equation = (fipy.DiffusionTerm(coeff=epsilon_scaled) +
                                  charge == 0.)

        # Calculate boundaries
        backplane = mesh.getFacesTop()
        readout_plane = mesh.getFacesBottom()

        electrodes = readout_plane
        bcs = [fipy.FixedValue(value=V_bias, faces=backplane)]
        X, _ = mesh.getFaceCenters()
        for pixel in range(n_pixel):
            pixel_position = width * (pixel + 1. / 2.) - width * n_pixel / 2.
            bcs.append(
                fipy.FixedValue(
                    value=V_readout if pixel_position == 0. else 0.,
                    faces=electrodes & (X > pixel_position - pitch / 2.) &
                    (X < pixel_position + pitch / 2.)))

        solver.solve(potential,
                     equation=potential.equation,
                     boundaryConditions=bcs)
        return potential
Example #2
0
def calculate_planar_sensor_w_potential(mesh, width, pitch, n_pixel,
                                        thickness):
    ''' Calculates the weighting field of a planar sensor.
    '''
    _LOGGER.info('Calculating weighting potential')
    # Mesh validity check
    mesh_width = mesh.getFaceCenters()[0, :].max() - mesh.getFaceCenters()[
        0, :].min()

    if mesh_width != width * n_pixel:
        raise ValueError(
            'The provided mesh width does not correspond to the sensor width')

    if mesh.getFaceCenters()[1, :].min() != 0:
        raise ValueError('The provided mesh does not start at 0.')

    if mesh.getFaceCenters()[1, :].max() != thickness:
        raise ValueError('The provided mesh does not end at sensor thickness.')

    potential = fipy.CellVariable(mesh=mesh, name='potential', value=0.)
    permittivity = 1.
    potential.equation = (fipy.DiffusionTerm(coeff=permittivity) == 0.)

    # Calculate boundaries
    backplane = mesh.getFacesTop()
    readout_plane = mesh.getFacesBottom()

    electrodes = readout_plane
    bcs = [fipy.FixedValue(value=0., faces=backplane)]
    X, _ = mesh.getFaceCenters()
    for pixel in range(n_pixel):
        pixel_position = width * (pixel + 1. / 2.) - width * n_pixel / 2.
        bcs.append(
            fipy.FixedValue(value=1.0 if pixel_position == 0. else 0.,
                            faces=electrodes &
                            (X > pixel_position - pitch / 2.) &
                            (X < pixel_position + pitch / 2.)))

    solver.solve(potential,
                 equation=potential.equation,
                 boundaryConditions=bcs)
    return potential
Example #3
0
def calculate_potential(mesh, rho, epsilon, V_read, V_bias, x_dep):
    r''' Calculate the potential with a given space charge distribution.

    If the depletion width is too large the resulting potential will have
    a minimum < bias voltage. This is unphysical.
    '''

    # The field scales with rho / epsilon, thus scale to proper value to
    # counteract numerical instabilities
    epsilon_scaled = 1.
    rho_scale = rho / epsilon

    potential = fipy.CellVariable(mesh=mesh, name='potential', value=0.)

    electrons = fipy.CellVariable(mesh=mesh, name='e-')
    electrons.valence = -1

    electrons.setValue(rho_scale)

    charge = electrons * electrons.valence
    charge.name = "charge"

    # A depletion zone within the bulk requires an internal boundary condition
    # Internal boundary conditions seem to challenge fipy, see:
    # http://www.ctcms.nist.gov/fipy/documentation/USAGE.html#applying-internal-boundary-conditions

    large_value = 1e+15  # Hack for optimizer

    mask = mesh.x > x_dep
    potential.equation = (fipy.DiffusionTerm(coeff=epsilon_scaled) -
                          fipy.ImplicitSourceTerm(mask * large_value) +
                          mask * large_value * V_bias + charge == 0)

    potential.constrain(V_read, mesh.facesLeft)
    potential.constrain(V_bias, mesh.facesRight)

    solver.solve(potential, equation=potential.equation)

    return potential
Example #4
0
def calculate_3D_sensor_w_potential(mesh,
                                    width_x,
                                    width_y,
                                    n_pixel_x,
                                    n_pixel_y,
                                    radius,
                                    nD=2):
    _LOGGER.info('Calculating weighting potential')

    # Mesh validity check
    mesh_width = mesh.getFaceCenters()[0, :].max() - mesh.getFaceCenters()[
        0, :].min()
    mesh_height = mesh.getFaceCenters()[1, :].max() - mesh.getFaceCenters()[
        1, :].min()

    desc = geometry.SensorDescription3D(width_x, width_y, n_pixel_x, n_pixel_y,
                                        radius, nD)
    min_x, max_x, min_y, max_y = desc.get_array_corners()

    if mesh_width != max_x - min_x:
        raise ValueError(
            'Provided mesh width does not correspond to the sensor width')
    if mesh_height != max_y - min_y:
        raise ValueError(
            'Provided mesh height does not correspond to the sensor height')
    if (mesh.getFaceCenters()[0, :].min() != min_x
            or mesh.getFaceCenters()[0, :].max() != max_x):
        raise ValueError('The provided mesh has a wrong x position')
    if (mesh.getFaceCenters()[1, :].min() != min_y
            or mesh.getFaceCenters()[1, :].max() != max_y):
        raise ValueError('The provided mesh has a wrong y position')

    potential = fipy.CellVariable(mesh=mesh, name='potential', value=0.)
    permittivity = 1.
    potential.equation = (fipy.DiffusionTerm(coeff=permittivity) == 0.)

    bcs = []
    allfaces = mesh.getExteriorFaces()
    X, Y = mesh.getFaceCenters()

    # Set boundary conditions
    # Set readout pillars potentials
    for pos_x, pos_y in desc.get_ro_col_offsets():
        ring = allfaces & ((X - pos_x)**2 + (Y - pos_y)**2 < (radius)**2)
        # Center pixel, phi = 1
        if desc.position_in_center_pixel(pos_x, pos_y):
            bcs.append(fipy.FixedValue(value=1., faces=ring))
        else:  # Other pixel, phi = 0
            bcs.append(fipy.FixedValue(value=0., faces=ring))

    # Full bias pillars potentials = 0
    for pos_x, pos_y in desc.get_center_bias_col_offsets():
        ring = allfaces & ((X - pos_x)**2 + (Y - pos_y)**2 < (radius)**2)
        bcs.append(fipy.FixedValue(value=0., faces=ring))

    # Side bias pillars potentials = 0
    for pos_x, pos_y in desc.get_side_bias_col_offsets():
        ring = allfaces & ((X - pos_x)**2 + (Y - pos_y)**2 < (radius)**2)
        bcs.append(fipy.FixedValue(value=0., faces=ring))

    # Edge bias pillars potentials = 0
    for pos_x, pos_y in desc.get_edge_bias_col_offsets():
        ring = allfaces & ((X - pos_x)**2 + (Y - pos_y)**2 < (radius)**2)
        bcs.append(fipy.FixedValue(value=0., faces=ring))

    solver.solve(potential,
                 equation=potential.equation,
                 boundaryConditions=bcs)
    return potential
Example #5
0
    def get_potential(max_iter=10):
        ''' Calculates the potential with boundary conditions.

        Can have a larger bias column radius to simulate a not fully depleted
        sensor
        '''

        r_bias = radius  # Start with full depletion assumption

        for i in range(max_iter):
            # Set boundary condition
            bcs = []
            allfaces = mesh.getExteriorFaces()
            X, Y = mesh.getFaceCenters()

            # Set boundary conditions
            # Set readout pillars potentials
            for pos_x, pos_y in desc.get_ro_col_offsets():
                ring = allfaces & ((X - pos_x)**2 + (Y - pos_y)**2 <
                                   (radius * 2)**2)
                bcs.append(fipy.FixedValue(value=V_readout, faces=ring))

            depletion_mask = None

            # Full bias pillars potentials = V_bias
            for pos_x, pos_y in desc.get_center_bias_col_offsets():
                ring = allfaces & ((X - pos_x)**2 + (Y - pos_y)**2 <
                                   (radius)**2)
                bcs.append(fipy.FixedValue(value=V_bias, faces=ring))
                if not np.any(depletion_mask):
                    depletion_mask = (potential.mesh.x - pos_x)**2 + \
                        (potential.mesh.y - pos_y)**2 < r_bias ** 2
                else:
                    depletion_mask |= (potential.mesh.x - pos_x)**2 + \
                        (potential.mesh.y - pos_y)**2 < (r_bias) ** 2

            # Side bias pillars potentials = V_bias
            for pos_x, pos_y in desc.get_side_bias_col_offsets():
                ring = allfaces & ((X - pos_x)**2 + (Y - pos_y)**2 <
                                   (radius)**2)
                bcs.append(fipy.FixedValue(value=V_bias, faces=ring))
                if not np.any(depletion_mask):
                    depletion_mask = (potential.mesh.x - pos_x)**2 + \
                        (potential.mesh.y - pos_y)**2 < r_bias ** 2
                else:
                    depletion_mask |= (potential.mesh.x - pos_x)**2 + \
                        (potential.mesh.y - pos_y)**2 < (r_bias) ** 2

            # Edge bias pillars potentials = V_bias
            for pos_x, pos_y in desc.get_edge_bias_col_offsets():
                ring = allfaces & ((X - pos_x)**2 + (Y - pos_y)**2 <
                                   (radius)**2)
                bcs.append(fipy.FixedValue(value=V_bias, faces=ring))
                if not np.any(depletion_mask):
                    depletion_mask = (potential.mesh.x - pos_x)**2 + \
                        (potential.mesh.y - pos_y)**2 < r_bias ** 2
                else:
                    depletion_mask |= (potential.mesh.x - pos_x)**2 + \
                        (potential.mesh.y - pos_y)**2 < (r_bias) ** 2

            # A depletion zone within the bulk requires an internal boundary
            # condition. Internal boundary conditions seem to challenge fipy
            # http://www.ctcms.nist.gov/fipy/documentation/USAGE.html#applying-internal-boundary-conditions

            large_value = 1e+10  # Hack for optimizer

            potential.equation = (fipy.DiffusionTerm(coeff=epsilon_scaled) +
                                  charge == fipy.ImplicitSourceTerm(
                                      depletion_mask * large_value) -
                                  depletion_mask * large_value * V_bias)

            solver.solve(potential,
                         equation=potential.equation,
                         boundaryConditions=bcs)

            # Check if fully depleted
            if not np.isclose(potential.arithmeticFaceValue().min(),
                              V_bias,
                              rtol=0.05,
                              atol=0.01):
                if i == 0:
                    logging.warning('Sensor is not fully depleted. '
                                    'Try to find depletion region. ')
            else:
                return potential

            # Get line between readout and bias column to check for full
            # depletion
            for x, y in desc.get_ro_col_offsets():
                if desc.position_in_center_pixel(x, y):
                    x_ro, y_ro = x, y
                    break
            for x, y in list(desc.get_center_bias_col_offsets()
                             ) + desc.get_edge_bias_col_offsets():
                if desc.position_in_center_pixel(x, y):
                    x_bias, y_bias = x, y
                    break

            pot_descr = Description(potential,
                                    min_x=min_x,
                                    max_x=max_x,
                                    min_y=min_y,
                                    max_y=max_y,
                                    nx=width_x * n_pixel_x,
                                    ny=width_y * n_pixel_y)

            N = 1000
            x = np.linspace(x_ro, x_bias, N)
            y = np.linspace(y_ro, y_bias, N)
            # Deselect position that is within the columns
            sel = ~desc.position_in_column(x, y)
            x, y = x[sel], y[sel]
            position = np.sqrt(x**2 + y**2)  # [um]
            phi = pot_descr.get_potential(x, y)
            x_r = position.max()
            x_min = position[np.atleast_1d(np.argmin(phi))[0]]
            #             import matplotlib.pyplot as plt
            #             plt.plot(position, phi, color='blue', linewidth=2,
            #                      label='Potential')
            #             plt.plot([x_r, x_r], plt.ylim())
            #             plt.plot([x_min, x_min], plt.ylim())
            #             plt.show()

            # Increase bias radius boundary to simulate not depleted
            # region sourrounding bias column
            r_bias += x_r - x_min

            logging.info('Depletion region error: %d um', x_r - x_min)

        raise RuntimeError('Unable to find the depletion region')