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
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
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
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
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')