def get_w_potential():
    try:
        return tools.load(
            os.path.join(DATAFOLDER, 'pot_w_descr_%d_3D' % RESOLUTION))
    except IOError:

        mesh = get_mesh()

        min_x, max_x, min_y, max_y = GEOM_DESCR.get_array_corners()
        nx = WIDTH_X * NPIXEL_X * 4
        ny = WIDTH_Y * NPIXEL_Y * 4

        w_potential = fields.calculate_3D_sensor_w_potential(mesh,
                                                             WIDTH_X,
                                                             WIDTH_Y,
                                                             NPIXEL_X,
                                                             NPIXEL_Y,
                                                             RADIUS,
                                                             nD=ND)
        pot_w_descr = fields.Description(w_potential,
                                         min_x=min_x,
                                         max_x=max_x,
                                         min_y=min_y,
                                         max_y=max_y,
                                         nx=nx,
                                         ny=ny,
                                         smoothing=SMOOTHING)

        tools.save(pot_w_descr,
                   os.path.join(DATAFOLDER, 'pot_w_descr_%d_3D' % RESOLUTION))

    return pot_w_descr
Exemple #2
0
def get_w_potential(thickness):
    try:
        return tools.load(
            os.path.join(DATAFOLDER, 'pot_w_descr_%d' % thickness))
    except IOError:
        mesh = get_mesh(n_pixel=NPIXEL,
                        width=WIDTH,
                        thickness=thickness,
                        resolution=RESOLUTION_1
                        if thickness == THICKNESS_1 else RESOLUTION_2)

        w_potential = fields.calculate_planar_sensor_w_potential(
            mesh=mesh,
            width=WIDTH,
            pitch=PITCH,
            n_pixel=NPIXEL,
            thickness=thickness)

        pot_w_descr = fields.Description(
            w_potential,
            min_x=float(mesh.getFaceCenters()[0, :].min()),
            max_x=float(mesh.getFaceCenters()[0, :].max()),
            min_y=0,
            max_y=thickness,
            nx=WIDTH * NPIXEL,
            ny=thickness,
            smoothing=SMOOTHING)

        tools.save(pot_w_descr,
                   os.path.join(DATAFOLDER, 'pot_w_descr_%d' % thickness))

    return pot_w_descr
def get_potential(V_bias, n_eff):
    try:
        return tools.load(
            os.path.join(DATAFOLDER, 'pot_descr_%d_%d_%d_3D' %
                         (V_bias, n_eff, RESOLUTION)))
    except IOError:
        V_readout = 0.
        V_bi = -silicon.get_diffusion_potential(n_eff / 1e12, temperature=TEMP)

        mesh = get_mesh()
        potential = fields.calculate_3D_sensor_potential(
            mesh, WIDTH_X, WIDTH_Y, NPIXEL_X, NPIXEL_Y, RADIUS, ND, n_eff,
            V_bias, V_readout, V_bi)

        min_x, max_x, min_y, max_y = GEOM_DESCR.get_array_corners()
        nx = WIDTH_X * NPIXEL_X * 4
        ny = WIDTH_Y * NPIXEL_Y * 4

        pot_descr = fields.Description(
            potential,
            min_x=min_x,
            max_x=max_x,
            min_y=min_y,
            max_y=max_y,
            nx=nx,  # um res.
            ny=ny,  # um res.
            smoothing=SMOOTHING)

        tools.save(
            pot_descr,
            os.path.join(DATAFOLDER, 'pot_descr_%d_%d_%d_3D' %
                         (V_bias, n_eff, RESOLUTION)))

    return pot_descr
Exemple #4
0
def get_potential(V_bias, n_eff, thickness):
    try:
        return tools.load(
            os.path.join(DATAFOLDER,
                         'pot_descr_%d_%d_%d' % (V_bias, n_eff, thickness)))
    except IOError:
        V_readout = 0.
        V_bi = -silicon.get_diffusion_potential(n_eff / 1e12, temperature=TEMP)

        #         mesh = MESH_1 if thickness == THICKNESS_1 else MESH_2
        mesh = get_mesh(n_pixel=NPIXEL,
                        width=WIDTH,
                        thickness=thickness,
                        resolution=RESOLUTION_1
                        if thickness == THICKNESS_1 else RESOLUTION_2)

        potential = fields.calculate_planar_sensor_potential(
            mesh=mesh,
            width=WIDTH,
            pitch=PITCH,
            n_pixel=NPIXEL,
            thickness=thickness,
            n_eff=n_eff,
            V_bias=V_bias,
            V_readout=V_readout,
            V_bi=V_bi)

        min_x = float(mesh.getFaceCenters()[0, :].min())
        max_x = float(mesh.getFaceCenters()[0, :].max())
        nx = WIDTH * NPIXEL
        ny = thickness

        pot_descr = fields.Description(potential,
                                       min_x=min_x,
                                       max_x=max_x,
                                       min_y=0,
                                       max_y=thickness,
                                       nx=nx,
                                       ny=ny,
                                       smoothing=SMOOTHING)

        tools.save(
            pot_descr,
            os.path.join(DATAFOLDER,
                         'pot_descr_%d_%d_%d' % (V_bias, n_eff, thickness)))

    return pot_descr
Exemple #5
0
    def test_save_and_load(self):
        ''' Check the saving and loading to disk.
        '''

        # Create data
        mesh = geometry.mesh_planar_sensor(n_pixel=9,
                                           width=50.,
                                           thickness=300.,
                                           resolution=50.,
                                           filename='planar_mesh_example.msh')
        potential = fields.calculate_planar_sensor_potential(mesh=mesh,
                                                             width=50.,
                                                             pitch=45.,
                                                             n_pixel=9,
                                                             thickness=300.,
                                                             n_eff=5e12,
                                                             V_bias=-160.,
                                                             V_readout=0.,
                                                             V_bi=1.5)
        min_x = float(mesh.getFaceCenters()[0, :].min())
        max_x = float(mesh.getFaceCenters()[0, :].max())
        description = fields.Description(potential,
                                         min_x=min_x,
                                         max_x=max_x,
                                         min_y=0,
                                         max_y=300.,
                                         nx=202,
                                         ny=200)

        # Force the creation of the potential and field functions
        description.get_field(0, 0)

        # Store and reload object
        tools.save(description, 'tmp.sc')
        description_2 = tools.load('tmp.sc')

        self.assertTrue(np.all(description.pot_data == description_2.pot_data))
        self.assertTrue(
            np.all(description.potential_grid == description_2.potential_grid))
        self.assertTrue(
            np.all(
                np.array(
                    description.get_field(description._xx, description._yy)) ==
                np.array(
                    description_2.get_field(description_2._xx,
                                            description_2._yy))))
Exemple #6
0
def planar_sensor(n_eff,
                  V_bias,
                  V_readout=0.,
                  temperature=300,
                  n_pixel=9,
                  width=50.,
                  pitch=45.,
                  thickness=200.,
                  selection=None,
                  resolution=300.,
                  nx=None,
                  ny=None,
                  smoothing=0.05,
                  mesh_file='planar_mesh.msh'):
    ''' Create a planar_sensor sensor pixel array.

        Parameters
        ----------
        n_eff : number
            Effective doping concentration in :math:`\mathrm{\frac{1}{cm^3}}`
        V_bias : number
            Bias voltage in Volt
        V_readout : number
            Readout voltage in Volt
        temperature : float
            Temperature in Kelvin
        n_pixel : int
            Number of pixels
        width : number
            Width of one pixel in :math:`\mathrm{\mu m}`
        pitch : number
            Pitch (redout implant width) of one pixel in :math:`\mathrm{\mu m}`
        thickness : number
            Thickness of the sensor in :math:`\mathrm{\mu m}`
        selection : string
            Selects if the weighting potential / potentials or both are
            calculated.
            If not set: calculate weighting potential and drift potential
            If drift: calculate drift potential only
            If weighting: calculate weighting potential only
        resolution : number
            Mesh resolution. Should lead to > 200000 mesh points for exact
            results.
        nx : number
            Interpolation points in x for the potentials and fields
        ny : number
            Interpolation points in y for the potentials and fields
        smoothing : number
            Smoothing parameter for the potential. Higher number leads to
            more smooth looking potential, but be aware too much smoothing
            leads to wrong results!
        mesh_file : str
            File name of the created mesh file

        Returns
        -----
        Two scarce.fields.Description objects for the weighting potential and
        potential if no specified selection.
    '''

    # Create mesh of the sensor and stores the result
    # The created file can be viewed with any mesh viewer (e.g. gmsh)
    mesh = geometry.mesh_planar_sensor(n_pixel=n_pixel,
                                       width=width,
                                       thickness=thickness,
                                       resolution=resolution,
                                       filename=mesh_file)

    min_x = float(mesh.getFaceCenters()[0, :].min())
    max_x = float(mesh.getFaceCenters()[0, :].max())

    # Set um resolution grid
    if not nx:
        nx = width * n_pixel
    if not ny:
        ny = thickness

    if not selection or 'drift' in selection:
        V_bi = -silicon.get_diffusion_potential(n_eff, temperature)
        # Numerically solve the Laplace equation on the mesh
        potential = fields.calculate_planar_sensor_potential(
            mesh=mesh,
            width=width,
            pitch=pitch,
            n_pixel=n_pixel,
            thickness=thickness,
            n_eff=n_eff,
            V_bias=V_bias,
            V_readout=V_readout,
            V_bi=V_bi)
        pot_descr = fields.Description(potential,
                                       min_x=min_x,
                                       max_x=max_x,
                                       min_y=0,
                                       max_y=thickness,
                                       nx=nx,
                                       ny=ny,
                                       smoothing=smoothing)
        if selection and 'drift' in selection:
            return pot_descr

    if not selection or 'weighting' in selection:
        # Numerically solve the Poisson equation on the mesh
        w_potential = fields.calculate_planar_sensor_w_potential(
            mesh=mesh,
            width=width,
            pitch=pitch,
            n_pixel=n_pixel,
            thickness=thickness)
        pot_w_descr = fields.Description(w_potential,
                                         min_x=min_x,
                                         max_x=max_x,
                                         min_y=0,
                                         max_y=thickness,
                                         nx=nx,
                                         ny=ny,
                                         smoothing=smoothing)
        if selection and 'weighting' in selection:
            return pot_w_descr

    return pot_w_descr, pot_descr
Exemple #7
0
def sensor_3D(n_eff,
              V_bias,
              V_readout=0.,
              temperature=300,
              n_pixel_x=3,
              n_pixel_y=3,
              width_x=250.,
              width_y=50.,
              radius=6.,
              nD=2,
              selection=None,
              resolution=80.,
              nx=None,
              ny=None,
              smoothing=0.1,
              mesh_file='3D_mesh.msh'):
    ''' Create a 3D sensor pixel array.

        Parameters
        ----------
        n_eff : number
            Effective doping concentration in :math:`\mathrm{\frac{1}{cm^3}}`
        V_bias : number
            Bias voltage in Volt
        V_readout : number
            Readout voltage in Volt
        temperature : float
            Temperature in Kelvin
        n_pixel_x : int
            Number of pixels in x
        n_pixel_y : int
            Number of pixels in y
        width_x : number
            Width of one pixel in x in :math:`\mathrm{\mu m}`
        width_y : number
            Width of one pixel in y in :math:`\mathrm{\mu m}`
        radius : number
            Radius of readout and biasing columns in :math:`\mathrm{\mu m}`
        nD : int
            Number of readout columns per pixel
        selection : string
            Selects if the weighting potential / potentials or both are
            calculated.
            If not set: calculate weighting potential and drift potential
            If drift: calculate drift potential only
            If weighting: calculate weighting potential only
        resolution : number
            Mesh resolution. Should lead to > 300000 mesh points for exact
            results.
        nx : number
            Interpolation points in x for the potentials and fields
        ny : number
            Interpolation points in y for the potentials and fields
        smoothing : number
            Smoothing parameter for the potential. Higher number leads to
            more smooth looking potential, but be aware too much smoothing
            leads to wrong results!
        mesh_file : str
            File name of the created mesh file

        Returns
        -----
        Two scarce.fields.Description objects for the weighting potential and
        potential if not specified selection and a geometry desciption object.
    '''

    if not nx:
        nx = width_x * n_pixel_x * 4
    if not ny:
        ny = width_y * n_pixel_y * 4

    mesh = geometry.mesh_3D_sensor(width_x=width_x,
                                   width_y=width_y,
                                   n_pixel_x=n_pixel_x,
                                   n_pixel_y=n_pixel_y,
                                   radius=radius,
                                   nD=nD,
                                   resolution=resolution,
                                   filename=mesh_file)

    # Describe the 3D sensor array
    geom_descr = geometry.SensorDescription3D(width_x, width_y, n_pixel_x,
                                              n_pixel_y, radius, nD)
    min_x, max_x, min_y, max_y = geom_descr.get_array_corners()

    if not selection or 'drift' in selection:
        V_bi = -silicon.get_diffusion_potential(n_eff, temperature)
        potential = fields.calculate_3D_sensor_potential(
            mesh, width_x, width_y, n_pixel_x, n_pixel_y, radius, nD, n_eff,
            V_bias, V_readout, V_bi)
        pot_descr = fields.Description(
            potential,
            min_x=min_x,
            max_x=max_x,
            min_y=min_y,
            max_y=max_y,
            nx=nx,  # um res.
            ny=ny,  # um res.
            smoothing=smoothing)
        if selection and 'drift' in selection:
            return pot_descr, geom_descr

    if not selection or 'weighting' in selection:
        w_potential = fields.calculate_3D_sensor_w_potential(mesh,
                                                             width_x,
                                                             width_y,
                                                             n_pixel_x,
                                                             n_pixel_y,
                                                             radius,
                                                             nD=nD)
        pot_w_descr = fields.Description(w_potential,
                                         min_x=min_x,
                                         max_x=max_x,
                                         min_y=min_y,
                                         max_y=max_y,
                                         nx=nx,
                                         ny=ny,
                                         smoothing=smoothing)
        if selection and 'weighting' in selection:
            return pot_w_descr, geom_descr

    return pot_w_descr, pot_descr, geom_descr
Exemple #8
0
    def test_weighting_potential_planar(self):
        '''  Compares estimated weighting potential to analytical solution.
        '''

        # Influences how correct the field for the center pixel(s) is
        # due to more far away infinite boundary condition
        n_pixel = 11

        for i, width in enumerate([50., 200.]):
            # FIXME: 50 um thichness does not work
            for j, thickness in enumerate([50., 250.]):
                # Analytical solution only existing for pixel width = readout
                # pitch (100% fill factor)
                pitch = width

                # Tune resolution properly for time/accuracy trade off
                if i == 0 and j == 0:
                    resolution = 200
                    continue
                elif i == 0 and j == 1:
                    resolution = 100
                    continue
                elif i == 1 and j == 0:
                    resolution = 600
                    continue  # FIXME: 50 thichness / 200 width does not work
                elif i == 1 and j == 1:
                    resolution = 200
                else:
                    raise RuntimeError('Loop index unknown')

                mesh = geometry.mesh_planar_sensor(
                    n_pixel=n_pixel,
                    width=width,
                    thickness=thickness,
                    resolution=resolution,
                    filename='planar_mesh_tmp_2.msh')

                potential = fields.calculate_planar_sensor_w_potential(
                    mesh=mesh,
                    width=width,
                    pitch=pitch,
                    n_pixel=n_pixel,
                    thickness=thickness)

                min_x, max_x = -width * float(n_pixel), width * float(n_pixel)
                min_y, max_y = 0., thickness
                nx, ny = 1000, 1000

                potential_description = fields.Description(potential,
                                                           min_x=min_x,
                                                           max_x=max_x,
                                                           min_y=min_y,
                                                           max_y=max_y,
                                                           nx=nx,
                                                           ny=ny,
                                                           smoothing=0.1)

                def potential_analytic(x, y):
                    return fields.get_weighting_potential_analytic(
                        x, y,
                        D=thickness,
                        S=width,
                        is_planar=True)

                # Create x,y grid
                x = np.linspace(min_x, max_x, nx)
                y = np.linspace(min_y, max_y, ny)
                xx, yy = np.meshgrid(x, y, sparse=True)

                # Evaluate potential on a grid
                pot_analytic = potential_analytic(xx, yy)
                pot_numeric = potential_description.get_potential(xx, yy)

#                 import matplotlib.pyplot as plt
#                 for pos_x in [0, 10, 15, 30, 45]:
#                     plt.plot(y, pot_analytic.T[nx / 2 + pos_x, :],
#                              label='Analytic')
#                 for pos_x in [0, 10, 15, 30, 45]:
#                     plt.plot(y, pot_numeric.T[nx / 2 + pos_x, :],
#                              label='Numeric')
#                 plt.legend(loc=0)
#                 plt.show()

                # Check only at center pixel, edge pixel are not interessting
                for pos_x in [-45, -30, -15, -10, 0, 10, 15, 30, 45]:
                    sel = pot_analytic.T[nx / 2 + pos_x, :] > 0.01
                    # Check with very tiny and tuned error allowance
                    self.assertTrue(np.allclose(
                        pot_analytic.T[nx / 2 + pos_x, sel],
                        pot_numeric.T[nx / 2 + pos_x, sel],
                        rtol=0.01, atol=0.005))
Exemple #9
0
    def test_potential_smoothing(self):
        '''  Checks the smoothing of the potential to be independent of the
             potential values.
        '''

        n_pixel = 11
        width = 50.
        thickness = 50.

        # Create x,y grid
        min_x, max_x = -width * float(n_pixel), width * float(n_pixel)
        min_y, max_y = 0., thickness
        nx, ny = 1000, 1000
        x = np.linspace(min_x, max_x, nx)
        y = np.linspace(min_y, max_y, ny)
        xx, yy = np.meshgrid(x, y, sparse=True)

        # Load potential solution to save time
        potential = dump.read(
            filename=os.path.join(constant.FIXTURE_FOLDER, 'potential.sc'))

        def upcale_potential(potential, V_readout, V_bias):
            ''' Scales potential to [V_bias, V_readout] to simulate other bias settings
            '''
            return ((potential - np.nanmin(potential)) /
                    (np.nanmax(potential) - np.nanmin(potential))) * \
                (V_readout - V_bias) + V_readout

        def downscale_potential(potential):
            ''' Scales potential to [0, 1] to make the smoothing result comparible
            '''
            return (potential - np.nanmin(potential)) / (np.nanmax(potential) -
                                                         np.nanmin(potential))

        potential_descr = fields.Description(potential,
                                             min_x=min_x,
                                             max_x=max_x,
                                             min_y=min_y,
                                             max_y=max_y,
                                             nx=nx,
                                             ny=ny)

        # Expected result for the std. smoothing value and a potential between
        # 0 and 1
        pot_numeric = downscale_potential(
            potential_descr.get_potential_smooth(xx, yy))

        for V_bias in [-100, -1000]:
            for V_readout in [50, 0, -50]:
                # Create fake data with different bias by upscaling
                potential_scaled = upcale_potential(
                    potential, V_readout, V_bias)
                # Describe upscaled data
                potential_descr_scaled = fields.Description(potential_scaled,
                                                            min_x=min_x,
                                                            max_x=max_x,
                                                            min_y=min_y,
                                                            max_y=max_y,
                                                            nx=nx,
                                                            ny=ny)
                # Downscale smoothed potential for comparison
                pot_numeric_2 = downscale_potential(
                    potential_descr_scaled.get_potential_smooth(xx, yy))

                self.assertTrue(
                    np.allclose(pot_numeric, pot_numeric_2, equal_nan=True))
Exemple #10
0
    def test_weighting_field_planar(self):
        '''  Compare weighting field to numerical solution.
        '''

        width = 50.
        # Analytical solution only existing for pixel width = readout pitch
        # (100 % fill factor)
        pitch = width
        thickness = 200.
        n_pixel = 11

        mesh = geometry.mesh_planar_sensor(
            n_pixel=n_pixel,
            width=width,
            thickness=thickness,
            resolution=200,
            filename='planar_mesh_tmp_2.msh')

        potential = fields.calculate_planar_sensor_w_potential(
            mesh=mesh,
            width=width,
            pitch=pitch,
            n_pixel=n_pixel,
            thickness=thickness)

        # Define field/potential domain
        min_x, max_x = -width * float(n_pixel), width * float(n_pixel)
        min_y, max_y = 0., thickness

        # Create x,y grid
        nx, ny = 1000, 1000
        x = np.linspace(min_x, max_x, nx)
        y = np.linspace(min_y, max_y, ny)
        xx, yy = np.meshgrid(x, y, sparse=True)

        field_description = fields.Description(potential,
                                               min_x=min_x,
                                               max_x=max_x,
                                               min_y=min_y,
                                               max_y=max_y,
                                               nx=nx,
                                               ny=ny,
                                               smoothing=0.2)

        def field_analytic(x, y):
            return fields.get_weighting_field_analytic(x, y, D=thickness,
                                                       S=width, is_planar=True)

        # Evaluate field on a grid
        f_analytic_x, f_analytic_y = field_analytic(xx, yy)
        f_numeric_x, f_numeric_y = field_description.get_field(xx, yy)

        # Check only at center pixel, edge pixel are not interessting
        for pox_x in [-45, -30, -15, -10, 0, 10, 15, 30, 45]:
            self.assertTrue(np.allclose(
                f_analytic_x.T[
                    nx / 2 + pox_x, :], f_numeric_x.T[nx / 2 + pox_x, :],
                rtol=0.01, atol=0.01))
            self.assertTrue(np.allclose(
                f_analytic_y.T[
                    nx / 2 + pox_x, :], f_numeric_y.T[nx / 2 + pox_x, :],
                rtol=0.01, atol=0.01))