def test_plot_mesh(self): mesh = geometry.mesh_planar_sensor(n_pixel=5, width=50., thickness=100., resolution=100., filename='planar_mesh_tmp_3.msh') plot.plot_mesh(mesh)
def test_mesh_planar(self): ''' Check the mesh generation for planar sensor. Likely due to random pertubation (on purpose) the result is never exactly constant. Thus only numbers of points checked. ''' n_pixel = 5 width = 50 thickness = 200 geometry.mesh_planar_sensor(n_pixel, width, thickness=thickness, resolution=100, filename='planar_mesh_tmp.msh') points, _, _, _, _ = meshio.read('planar_mesh_tmp.msh') self.assertGreater(points.shape[0], 14000)
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))))
def get_mesh(n_pixel, width, thickness, resolution): try: return tools.load( os.path.join( DATAFOLDER, 'mesh_%d_%d_%d_%d' % (n_pixel, int(width), int(thickness), int(resolution)))) except IOError: mesh = geometry.mesh_planar_sensor(n_pixel=n_pixel, width=width, thickness=thickness, resolution=resolution) tools.save( mesh, os.path.join( DATAFOLDER, 'mesh_%d_%d_%d_%d' % (n_pixel, int(width), int(thickness), int(resolution)))) return mesh
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
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))
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))