Ejemplo n.º 1
0
 def test_phi_at_x(self):
     energy = np.array([0.1, 0.2, 0.3, 0.4, 0.5])
     coordinates = np.array([-2.0, -1.0, 0.0, 1.0, 2.0])
     with patch(
             'pyscses.grid.index_of_grid_at_x') as mock_index_of_grid_at_x:
         mock_index_of_grid_at_x.side_effect = [0, 2, 3]
         self.assertEqual(
             phi_at_x(phi=energy, coordinates=coordinates, x=-1.5), 0.1)
         self.assertEqual(
             phi_at_x(phi=energy, coordinates=coordinates, x=-0.1), 0.3)
         self.assertEqual(
             phi_at_x(phi=energy, coordinates=coordinates, x=0.6), 0.4)
Ejemplo n.º 2
0
    def create_space_charge_region(self, grid, pos_or_neg_scr, scr_limit):
        """Calculate the space charge region. The space charge region is defined as the region when the electrostatic potential is greater than a predefined limit.

	Args:
	    grid (:obj:`pyscses.Grid`): Grid object - contains properties of the grid including the x coordinates and the volumes. Used to access the x coordinates.
	    pos_or_neg_scr (str): 'positive' - for a positive space charge potential.
				  'negative' - for a negative space charge potential.
	    scr_limit (float): The minimum electrostatic potential that the electrostatic potential must exceed to be included in the space charge region.

	Returns:
	    list: List of x coordinates for sites within the space charge region.

	"""
        space_charge_region = []
        self.phi_on_mobile_defect_grid = [
            phi_at_x(self.phi, self.grid.x, x) for x in grid.x
        ]
        x_and_phi = np.column_stack((grid.x, self.phi_on_mobile_defect_grid))
        for i in range(len(x_and_phi)):
            if pos_or_neg_scr == 'positive':
                if x_and_phi[i, 1] - x_and_phi[0, 1] > scr_limit:
                    space_charge_region.append(x_and_phi[i, 0])
            if pos_or_neg_scr == 'negative':
                if x_and_phi[i, 1] - x_and_phi[0, 1] < scr_limit:
                    space_charge_region.append(x_and_phi[i, 0])
        return space_charge_region
Ejemplo n.º 3
0
    def calculate_probabilities(self,
                                grid: Grid,
                                phi: np.ndarray,
                                temp: float) -> np.ndarray:
        """
        Calculates the probability of a site being occupied by its corresponding defect.

        Args:
            grid (Grid): Grid object - contains properties of the grid including the x coordinates and the volumes. Used to access the x coordinates.
            phi (array): electrostatic potential on a one-dimensional grid.
            temp (float): Absolute temperature.

        Returns:
            array: probabilities of defects occupying each site using their grid points

        """
        # TODO: This is Jacob's fixed code, but it is inefficient and slow.
        probability = np.zeros_like(grid.x)
        for i,j in enumerate(grid.x):
            prob = []
            for site in self.sites:
                if j == site.x:
                    prob.append(site.probabilities_as_list(phi_at_x(phi, grid.x, site.x), temp))
            if len(prob) == 0:
                probability[i] = 0
            else:
                probability[i] = np.mean(prob)
        return probability
Ejemplo n.º 4
0
    def solve(self, approximation):
        """
        Self-consistent solving of the Poisson-Boltzmann equation. Iterates until the convergence is less than the convergence limit. The outputs are stored as Calculation attributes.
        Calculation.phi (array): Electrostatic potential on a one-dimensional grid. 
        Calculation.rho (array): Charge density on a one-dimensional grid.
        Calculation.niter (int): Number of iterations performed to reach convergence.


        Args:
            approximation (str): Approximation used for the defect behaviour.
                                 'mott-schottky' - Some defects immobile / fixed to bulk mole fractions.
                                 'gouy-chapman' - All defects mobile / able to redistribute.

        """
        poisson_solver = MatrixSolver(
            self.grid,
            self.dielectric,
            self.temp,
            boundary_conditions=self.boundary_conditions)

        phi = np.zeros_like(self.grid.x)
        rho = np.zeros_like(self.grid.x)

        conv = 1
        niter = 0
        while conv > self.convergence:
            predicted_phi, rho = poisson_solver.solve(phi)
            if approximation == 'gouy-chapman':
                average_phi = self.calculate_average(self.grid,
                                                     self.bulk_x_min,
                                                     self.bulk_x_max,
                                                     predicted_phi)
                predicted_phi -= average_phi
            if approximation == 'mott-schottky':
                vo_predicted_phi = [
                    phi_at_x(predicted_phi, self.grid.x, x)
                    for x in self.grid.subgrid(self.site_labels[0]).x
                ]
                average_vo_predicted_phi = self.calculate_average(
                    self.grid.subgrid(self.site_labels[0]), self.bulk_x_min,
                    self.bulk_x_max, vo_predicted_phi)
                predicted_phi -= average_vo_predicted_phi
            phi = self.alpha * predicted_phi + (1.0 - self.alpha) * phi
            conv = sum((predicted_phi - phi)**2) / len(self.grid.x)
            prob = self.grid.set_of_sites.calculate_probabilities(
                self.grid, phi, self.temp)
            niter += 1
#            if niter % 500 == 0.0:
#            if niter == 1:
#                print(conv)
#                print(phi, rho)
#                stop
        self.phi = phi
        self.rho = self.grid.rho(phi, self.temp)
        self.niter = niter
Ejemplo n.º 5
0
    def calculate_defect_density( self, grid, phi, temp ):
        """ 
        Calculates the defect density at each site.
    
        Args: 
            grid (object): Grid object - contains properties of the grid including the x coordinates and the volumes. Used to access the x coordinates.
            phi (array): electrostatic potential on a one-dimensional grid. 
            temp (float): Absolute temperature.

        Returns:
            array: defect density for each site using their grid points
 
        """
        defect_density = np.zeros_like( grid.x )
        for site in self.sites:
            i = index_of_grid_at_x( grid.x, site.x )
            defect_density[ i ] += np.asarray( site.probabilities( phi_at_x( phi, grid.x, site.x ), temp ) ) / grid.volumes[ i ]
        return defect_density  
Ejemplo n.º 6
0
    def calculate_probabilities(self, grid, phi, temp):
        """ 
        Calculates the probability of a site being occupied by its corresponding defect.
    
        Args: 
            grid (object): Grid object - contains properties of the grid including the x coordinates and the volumes. Used to access the x coordinates.
            phi (array): electrostatic potential on a one-dimensional grid. 
            temp (float): Absolute temperature.

        Returns:
            probability (array): probabilities of defects occupying each site using their grid points
 
        """
        probability = np.zeros_like(grid.x)
        for site in self.sites:
            probability[index_of_grid_at_x(grid.x, site.x)] = np.asarray(
                site.probabilities(phi_at_x(phi, grid.x, site.x), temp))
        return probability
Ejemplo n.º 7
0
    def subgrid_calculate_defect_density(self,
                                         sub_grid: Grid,
                                         full_grid: Grid,
                                         phi: np.ndarray,
                                         temp: float) -> np.ndarray:
        """
        Calculates the defect density at each site for a given subset of sites.

        Args:
            subgrid (Grid): Grid object - contains properties of the grid including the x coordinates and the volumes. Used to access the x coordinates. For a given subset of sites.
            full_grid (Grid): Grid object - contains properties of the grid including the x coordinates and the volumes. Used to access the x coordinates. For all sites.
            phi (array): electrostatic potential on a one-dimensional grid.
            temp (float): Absolute temperature.

        Returns:
            array: defect density for each site using their grid points

        """
        defect_density = np.zeros_like( sub_grid.x )
        for site in self.sites:
            i = index_of_grid_at_x( sub_grid.x, site.x )
            defect_density[ i ] += np.asarray( site.probabilities_as_list( phi_at_x( phi, full_grid.x, site.x ), temp ) ) / sub_grid.volumes[ i ]
        return defect_density
Ejemplo n.º 8
0
    def solve(self, approximation: str, verbose: bool = False) -> None:
        """
        Self-consistent solving of the Poisson-Boltzmann equation. Iterates until the convergence is less than the convergence limit. The outputs are stored as Calculation attributes.
        Calculation.phi (array): Electrostatic potential on a one-dimensional grid.
        Calculation.rho (array): Charge density on a one-dimensional grid.
        Calculation.niter (int): Number of iterations performed to reach convergence.


        Args:
            approximation (str): Approximation used for the defect behaviour.
                                 'mott-schottky' - Some defects immobile / fixed to bulk mole fractions.
                                 'gouy-chapman' - All defects mobile / able to redistribute.
            verbose (optional, bool): Verbose output. Default is False.

        """
        poisson_solver = MatrixSolver(
            grid=self.grid,
            dielectric=self.dielectric,
            temp=self.temp,
            boundary_conditions=self.boundary_conditions)

        phi = np.zeros_like(self.grid.x)
        rho = np.zeros_like(self.grid.x)

        conv = 1.0
        niter = 0
        while conv > self.convergence:
            predicted_phi, rho = poisson_solver.solve(phi)
            if approximation == 'gouy-chapman':
                average_phi = self.calculate_average(
                    grid=self.grid,
                    min_cutoff=self.bulk_x_min,
                    max_cutoff=self.bulk_x_max,
                    sc_property=predicted_phi)
                predicted_phi -= average_phi
            elif approximation == 'mott-schottky':
                subgrid = self.grid.subgrid(self.site_labels[0])
                predicted_phi_subgrid = np.array([
                    phi_at_x(phi=predicted_phi, coordinates=self.grid.x, x=x)
                    for x in subgrid.x
                ])
                average_predicted_phi = self.calculate_average(
                    grid=subgrid,
                    min_cutoff=self.bulk_x_min,
                    max_cutoff=self.bulk_x_max,
                    sc_property=predicted_phi_subgrid)
                predicted_phi -= average_predicted_phi
            # TODO: This is inefficient. Jacob has some ideas for how to improve things.
            # TODO: This definition of convergence should not be averaged over all sites.
            phi = self.alpha * predicted_phi + (1.0 - self.alpha) * phi
            conv = sum((predicted_phi - phi)**2) / len(self.grid.x)
            # prob = self.grid.set_of_sites.calculate_probabilities( self.grid, phi, self.temp) # Jacob: Does this do anything?
            niter += 1
            if verbose:
                if niter % 500 == 0:
                    print(
                        f'Iteration: {niter} -> Convergence: {conv} / {self.convergence}'
                    )
        if verbose:
            print(
                f'Converged at iteration {niter} -> Convergence: {conv} / {self.convergence}'
            )
        self.phi = phi
        self.rho = self.grid.rho(phi, self.temp)
        self.niter = niter