From 626632825e26dd64ab5ebc0ee185eeac0214f49c Mon Sep 17 00:00:00 2001 From: JosePizarro3 Date: Thu, 25 Jan 2024 09:31:23 +0100 Subject: [PATCH 1/7] Adding fermi_level extraction --- src/pyssmf/input.py | 2 + src/pyssmf/runner.py | 73 ++++++++++++++++++++++++++++--------- src/pyssmf/visualization.py | 12 ++++-- 3 files changed, 66 insertions(+), 21 deletions(-) diff --git a/src/pyssmf/input.py b/src/pyssmf/input.py index 9725c69..49c8f10 100644 --- a/src/pyssmf/input.py +++ b/src/pyssmf/input.py @@ -146,6 +146,8 @@ def __init__(self, **kwargs): data['dos'] = kwargs.get('dos', False) data['dos_gaussian_width'] = kwargs.get('dos_gaussian_width', 0.1) data['dos_delta_energy'] = kwargs.get('dos_delta_energy', 0.01) + # Nominal number of electrons + data['n_electrons'] = kwargs.get('n_electrons', 1) self.data = data self.to_json() diff --git a/src/pyssmf/runner.py b/src/pyssmf/runner.py index bb75baf..f430ea5 100644 --- a/src/pyssmf/runner.py +++ b/src/pyssmf/runner.py @@ -73,19 +73,37 @@ def prune_hoppings(self): plot_hopping_matrices(pruner.hopping_matrix_norms / pruner.max_value) self.logger.info('Hopping pruning finished!') - def calculate_band_structure(self): + def calculate_fermi_level(self, eigenvalues, n_k_points): """ - Calculates the band structure of the tight-binding model in a given `n_k_path`. + Calculates the Fermi level by ordering the eigenvalues and selecting the one that + corresponds to the filling of the system. """ - n_k_path = self.data.get('n_k_path', 90) - tb_hamiltonian = TBHamiltonian( - self.model, k_grid_type='bands', n_k_path=n_k_path + n_orbitals = self.model.n_orbitals + if eigenvalues.shape != (n_k_points, n_orbitals): + self.logger.error( + 'Eigenvalues shape does not match (n_k_points, n_orbitals).' + ) + return + n_electrons = self.data.get('n_electrons', 1) + if n_electrons > n_orbitals: + self.logger.error( + 'Found n_electrons > n_orbitals, but n_electrons can only be <= n_orbitals. ' + 'The rule is that n_electrons / n_orbitals should represent the filling. ' + 'Half-filling is n_electrons / n_orbitals = 0.5.' + ) + return + filling = float(n_electrons / n_orbitals) # filling = 0.5 for half-filling + i_fermi = int( + filling * n_k_points * n_orbitals + ) # eigenvalue position of the Fermi level + # We evaluate the histogram for propper Fermi level extraction + _, edges = np.histogram( + eigenvalues, + bins=n_k_points * n_orbitals, + density=True, ) - special_points = tb_hamiltonian.k_path.special_points - kpoints = tb_hamiltonian.kpoints - eigenvalues, _ = tb_hamiltonian.diagonalize(kpoints) - plot_band_structure(eigenvalues, tb_hamiltonian, special_points) - self.logger.info('Band structure calculation finished!') + energies = (edges[:-1] + edges[1:]) / 2 + return energies[i_fermi] def gaussian_convolution( self, @@ -183,28 +201,47 @@ def bz_diagonalization(self): k_grid = self.data.get('k_grid', [1, 1, 1]) tb_hamiltonian = TBHamiltonian(self.model, k_grid_type='full_bz', k_grid=k_grid) kpoints = tb_hamiltonian.kpoints + n_k_points = tb_hamiltonian.n_k_points eigenvalues, eigenvectors = tb_hamiltonian.diagonalize(kpoints) + # Calculate Fermi level + fermi_level = self.calculate_fermi_level(eigenvalues, n_k_points) + # Calculating and plotting DOS - if self.data.get('dos'): - bins = int(np.linalg.norm(k_grid)) + if self.data.get('dos') and fermi_level: width = self.data.get('dos_gaussian_width') delta_energy = self.data.get('dos_delta_energy') energies, orbital_dos, total_dos = self.calculate_dos( - eigenvalues, eigenvectors, bins, width, delta_energy + eigenvalues, eigenvectors, n_k_points, width, delta_energy ) - plot_dos(energies, orbital_dos, total_dos) + plot_dos(energies, fermi_level, orbital_dos, total_dos) self.logger.info('DOS calculation finished!') self.logger.info('BZ diagonalization calculation finished!') - return eigenvalues, eigenvectors + return eigenvalues, eigenvectors, fermi_level + + def calculate_band_structure(self, fermi_level: np.float64 = 0.0): + """ + Calculates the band structure of the tight-binding model in a given `n_k_path`. + """ + n_k_path = self.data.get('n_k_path', 90) + tb_hamiltonian = TBHamiltonian( + self.model, k_grid_type='bands', n_k_path=n_k_path + ) + special_points = tb_hamiltonian.k_path.special_points + kpoints = tb_hamiltonian.kpoints + eigenvalues, _ = tb_hamiltonian.diagonalize(kpoints) + plot_band_structure(eigenvalues, fermi_level, tb_hamiltonian, special_points) + self.logger.info('Band structure calculation finished!') def run(self): self.parse_tb_model() self.prune_hoppings() - if self.data.get('plot_bands'): - self.calculate_band_structure() + eigenvalues, eigenvectors, fermi_level = self.bz_diagonalization() + + if self.data.get('plot_bands') and fermi_level: + self.calculate_band_structure(fermi_level) - self.bz_diagonalization() + print('Finished!') diff --git a/src/pyssmf/visualization.py b/src/pyssmf/visualization.py index e7b4782..978ba93 100644 --- a/src/pyssmf/visualization.py +++ b/src/pyssmf/visualization.py @@ -82,15 +82,16 @@ def update(val): plt.show() -def plot_band_structure(eigenvalues, tb_hamiltonian, special_points=None): +def plot_band_structure(eigenvalues, fermi_level, tb_hamiltonian, special_points=None): """ Plots the band structure of a Hamiltonian. Args: eigenvalues (np.ndarray): Eigenvalues of the Hamiltonian matrix of shape (Nk, Norb). + fermi_level (float): Fermi level. special_points (dict, optional): Dictionary of special points and their labels. """ - + # eigenvalues = eigenvalues - fermi_level num_bands = eigenvalues.shape[1] # Create a figure @@ -120,6 +121,7 @@ def plot_band_structure(eigenvalues, tb_hamiltonian, special_points=None): i += 1 plt.xticks(x_ticks, x_labels) + plt.axhline(y=fermi_level, color='k', linestyle='--') plt.xlim(0, len(eigenvalues) - 1) plt.xlabel('k-points') plt.ylabel('Energy (eV)') @@ -130,24 +132,28 @@ def plot_band_structure(eigenvalues, tb_hamiltonian, special_points=None): plt.show() -def plot_dos(energies, orbital_dos, total_dos): +def plot_dos(energies, fermi_level, orbital_dos, total_dos): """ Plots the density of states (DOS) of a tight-binding Hamiltonian. Args: energies: the energies at which the DOS is evaluated. + fermi_level: the Fermi level. orbital_dos: the orbital-resolved DOS. total_dos: the total DOS. """ # Create a figure plt.figure(figsize=(8, 6)) + # energies = energies - fermi_level plt.plot(energies, total_dos, label='Total DOS', color='k', linewidth=3.5) # Plot orbital-resolved DOS for i, orb_dos in enumerate(orbital_dos): plt.plot(energies, orb_dos, label=f'Orbital {i + 1}') + plt.axvline(x=fermi_level, color='k', linestyle='--') plt.xlabel('Energy (eV)') plt.ylabel('Density of States (DOS)') + plt.grid(True) plt.legend() plt.show() From a19fd9a4162ce665c4541f657c617dd058e5f633 Mon Sep 17 00:00:00 2001 From: JosePizarro3 Date: Mon, 27 May 2024 16:49:41 +0200 Subject: [PATCH 2/7] Changes in ssmf.py module --- src/ssmf.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 src/ssmf.py diff --git a/src/ssmf.py b/src/ssmf.py new file mode 100644 index 0000000..964db57 --- /dev/null +++ b/src/ssmf.py @@ -0,0 +1,25 @@ +# +# Copyright: Dr. José M. Pizarro. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import numpy as np + + +class SlaveSpinCalculation: + def __init__(self): + pass + + def initial_onsite_correction(self): + pass From 89c1b538e7ee819562bd083a2d9d3a1f903a9110 Mon Sep 17 00:00:00 2001 From: JosePizarro3 Date: Tue, 28 May 2024 13:46:08 +0200 Subject: [PATCH 3/7] Moved utils to subfolder and fixed imports to package path Deleted PyQt5 dev package --- src/pyssmf/tb_hamiltonian.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pyssmf/tb_hamiltonian.py b/src/pyssmf/tb_hamiltonian.py index fd703e8..386c605 100644 --- a/src/pyssmf/tb_hamiltonian.py +++ b/src/pyssmf/tb_hamiltonian.py @@ -19,7 +19,6 @@ import ase from ase.spacegroup import get_spacegroup, spacegroup from ase.dft.kpoints import monkhorst_pack, BandPath - from nomad.atomutils import Formula from nomad.units import ureg From 033256c83d769fdbcf080bf733f348864c47cabd Mon Sep 17 00:00:00 2001 From: JosePizarro3 Date: Tue, 28 May 2024 13:49:38 +0200 Subject: [PATCH 4/7] Deleted weirdly generated by VScode module ssmf.py --- src/ssmf.py | 25 ------------------------- 1 file changed, 25 deletions(-) delete mode 100644 src/ssmf.py diff --git a/src/ssmf.py b/src/ssmf.py deleted file mode 100644 index 964db57..0000000 --- a/src/ssmf.py +++ /dev/null @@ -1,25 +0,0 @@ -# -# Copyright: Dr. José M. Pizarro. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -import numpy as np - - -class SlaveSpinCalculation: - def __init__(self): - pass - - def initial_onsite_correction(self): - pass From 538bbea89c31f244927ab38c0a3b288f5fce0c12 Mon Sep 17 00:00:00 2001 From: JosePizarro3 Date: Mon, 10 Jun 2024 11:10:33 +0200 Subject: [PATCH 5/7] Fix mypy --- src/pyssmf/runner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyssmf/runner.py b/src/pyssmf/runner.py index f430ea5..a147fc0 100644 --- a/src/pyssmf/runner.py +++ b/src/pyssmf/runner.py @@ -220,7 +220,7 @@ def bz_diagonalization(self): self.logger.info('BZ diagonalization calculation finished!') return eigenvalues, eigenvectors, fermi_level - def calculate_band_structure(self, fermi_level: np.float64 = 0.0): + def calculate_band_structure(self, fermi_level: float = 0.0): """ Calculates the band structure of the tight-binding model in a given `n_k_path`. """ From 1ad28915362dc47253c44d603156a320a80cea84 Mon Sep 17 00:00:00 2001 From: JosePizarro3 Date: Wed, 12 Jun 2024 15:23:27 +0200 Subject: [PATCH 6/7] Moved initial idea to its own folder to rework on the project --- src/{ => idea0}/pyssmf/__init__.py | 0 src/{ => idea0}/pyssmf/hopping_pruning.py | 0 src/{ => idea0}/pyssmf/input.py | 0 src/{ => idea0}/pyssmf/parsing.py | 0 src/{ => idea0}/pyssmf/runner.py | 0 src/{ => idea0}/pyssmf/schema.py | 0 src/{ => idea0}/pyssmf/tb_hamiltonian.py | 0 src/{ => idea0}/pyssmf/utils/__init__.py | 0 src/{ => idea0}/pyssmf/utils/utils.py | 0 src/{ => idea0}/pyssmf/visualization.py | 0 10 files changed, 0 insertions(+), 0 deletions(-) rename src/{ => idea0}/pyssmf/__init__.py (100%) rename src/{ => idea0}/pyssmf/hopping_pruning.py (100%) rename src/{ => idea0}/pyssmf/input.py (100%) rename src/{ => idea0}/pyssmf/parsing.py (100%) rename src/{ => idea0}/pyssmf/runner.py (100%) rename src/{ => idea0}/pyssmf/schema.py (100%) rename src/{ => idea0}/pyssmf/tb_hamiltonian.py (100%) rename src/{ => idea0}/pyssmf/utils/__init__.py (100%) rename src/{ => idea0}/pyssmf/utils/utils.py (100%) rename src/{ => idea0}/pyssmf/visualization.py (100%) diff --git a/src/pyssmf/__init__.py b/src/idea0/pyssmf/__init__.py similarity index 100% rename from src/pyssmf/__init__.py rename to src/idea0/pyssmf/__init__.py diff --git a/src/pyssmf/hopping_pruning.py b/src/idea0/pyssmf/hopping_pruning.py similarity index 100% rename from src/pyssmf/hopping_pruning.py rename to src/idea0/pyssmf/hopping_pruning.py diff --git a/src/pyssmf/input.py b/src/idea0/pyssmf/input.py similarity index 100% rename from src/pyssmf/input.py rename to src/idea0/pyssmf/input.py diff --git a/src/pyssmf/parsing.py b/src/idea0/pyssmf/parsing.py similarity index 100% rename from src/pyssmf/parsing.py rename to src/idea0/pyssmf/parsing.py diff --git a/src/pyssmf/runner.py b/src/idea0/pyssmf/runner.py similarity index 100% rename from src/pyssmf/runner.py rename to src/idea0/pyssmf/runner.py diff --git a/src/pyssmf/schema.py b/src/idea0/pyssmf/schema.py similarity index 100% rename from src/pyssmf/schema.py rename to src/idea0/pyssmf/schema.py diff --git a/src/pyssmf/tb_hamiltonian.py b/src/idea0/pyssmf/tb_hamiltonian.py similarity index 100% rename from src/pyssmf/tb_hamiltonian.py rename to src/idea0/pyssmf/tb_hamiltonian.py diff --git a/src/pyssmf/utils/__init__.py b/src/idea0/pyssmf/utils/__init__.py similarity index 100% rename from src/pyssmf/utils/__init__.py rename to src/idea0/pyssmf/utils/__init__.py diff --git a/src/pyssmf/utils/utils.py b/src/idea0/pyssmf/utils/utils.py similarity index 100% rename from src/pyssmf/utils/utils.py rename to src/idea0/pyssmf/utils/utils.py diff --git a/src/pyssmf/visualization.py b/src/idea0/pyssmf/visualization.py similarity index 100% rename from src/pyssmf/visualization.py rename to src/idea0/pyssmf/visualization.py From 96db4932a4ea199164783d1af5efac9b7a236cd3 Mon Sep 17 00:00:00 2001 From: JosePizarro3 Date: Thu, 31 Oct 2024 09:23:51 +0100 Subject: [PATCH 7/7] New try --- src/pyssmf/__init__.py | 19 +++++ src/pyssmf/input.py | 171 +++++++++++++++++++++++++++++++++++++++++ src/pyssmf/runner.py | 25 ++++++ 3 files changed, 215 insertions(+) create mode 100644 src/pyssmf/__init__.py create mode 100644 src/pyssmf/input.py create mode 100644 src/pyssmf/runner.py diff --git a/src/pyssmf/__init__.py b/src/pyssmf/__init__.py new file mode 100644 index 0000000..285ea6c --- /dev/null +++ b/src/pyssmf/__init__.py @@ -0,0 +1,19 @@ +# +# Copyright: Dr. José M. Pizarro. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import structlog + +LOGGER = structlog.get_logger(__name__) diff --git a/src/pyssmf/input.py b/src/pyssmf/input.py new file mode 100644 index 0000000..fefbfbf --- /dev/null +++ b/src/pyssmf/input.py @@ -0,0 +1,171 @@ +# +# Copyright: Dr. José M. Pizarro. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from abc import ABC +import logging +import json +import os +import numpy as np + +from . import LOGGER + + +class Input: + def __init__(self, **kwargs): + """ + Reads the input arguments and stores then in a dictionary called `data` and in a + JSON file, `input_ssmf.json` generated in the working_directory. + + Attributes: + data (dict): A dictionary that stores input arguments. The keys and their + corresponding values are as follows: + - 'code' (str): Always set to 'pySSMF'. + - 'working_directory' (str): The directory where the files are located. + - 'input_file' (str): Path to the input JSON file in the working directory. + Only present if 'read_from_input_file' is True. + - 'lattice_model' (str): the specific lattice model to be calculated. See + `valid_lattice_models` for accepted values. + - 'hoppings' (list): List of values for the hoppings in order from nearest to + farthest hopping. Only present if 'lattice_model' is specified. + - 'n_hoppings' (int): Integer specifying how many hoppings are included. + Only supported up to 3. Only present if 'lattice_model' is specified. + - 'n_orbitals' (int): Integer specifying how many orbitals are included. + Only present if 'lattice_model' is specified. + """ + super().__init__() + # List of covered lattice toy models + # TODO extend this list + _valid_lattices = [ + 'linear', + ] + + # Reading input from an `input.json` file + read_input_file = kwargs.get('read_from_input_file', False) + if read_input_file: + input_file = os.path.join( + kwargs.get('working_directory'), kwargs.get('input_file') + ) + data = self.read_from_file(input_file) + data['input_file'] = kwargs.get('input_file') + self.data = data + self.to_json() + return + + # If `input_file` is not specified, we populate `data` with the passed arguments instead + # Initializing `data` + data = {'code': 'pySSMF'} + + # Check working_directory and stores it in data + if not kwargs.get('working_directory'): + raise KeyError( + 'Could not find specified the working_directory in the input.' + ) + data['working_directory'] = kwargs.get('working_directory') + + # Lattice models details + lattice_model_id = kwargs.get('lattice_model', '') + if lattice_model_id not in _valid_lattices: + raise ValueError(f'{lattice_model_id} is not a valid lattice model.') + data['lattice_model'] = lattice_model_id + # We check if 'hoppings' was empty + hoppings = kwargs.get('hoppings', []) + n_hoppings = len(hoppings) + if n_hoppings == 0: + n_hoppings = 1 + n_orbitals = 1 + hoppings = [[1.0]] + LOGGER.warning( + 'Argument `hoppings` was empty, so we consider a nearest neighbor, single-orbital model' + ) + # Only up to 3 neighbors hoppings supported + n_hoppings = len(hoppings) + if n_hoppings > 3: + raise ValueError( + 'Maximum n_hoppings models supported is 3. Please, select ' + 'a smaller number.' + ) + # We check shape for all Wigner-Seitz points hoppings to be (n_orbitals, n_orbitals) + n_orbitals = len(hoppings[0]) + if not all( + np.shape(hop_point) == (n_orbitals, n_orbitals) for hop_point in hoppings + ): + raise ValueError( + 'Dimensions of each hopping matrix do not coincide with' + '(n_orbitals, n_orbitals).', + data={'n_orbitals': n_orbitals}, + ) + # Extracting `onsite_energies`, `hoppings` + onsite_energies = kwargs.get('onsite_energies', []) + if len(onsite_energies) == 0: + onsite_energies = [0.0] * n_orbitals + LOGGER.warning( + 'Attribute `onsite_energies` was empty, so we consider all zeros with the dimensions of `(n_orbitals)`.' + ) + data['hoppings'] = hoppings + data['onsite_energies'] = onsite_energies + data['n_hoppings'] = n_hoppings + data['n_orbitals'] = n_orbitals + + # KGrids + # For band structure calculations + data['n_k_path'] = kwargs.get('n_k_path', 90) + # For full_bz diagonalization + data['k_grid'] = kwargs.get('k_grid', [1, 1, 1]) + + # Plotting arguments + data['plot_hoppings'] = kwargs.get('plot_hoppings', False) + data['plot_bands'] = kwargs.get('plot_bands', False) + # DOS calculation and plotting + data['dos'] = kwargs.get('dos', False) + data['dos_gaussian_width'] = kwargs.get('dos_gaussian_width', 0.1) + data['dos_delta_energy'] = kwargs.get('dos_delta_energy', 0.01) + # Nominal number of electrons + data['n_electrons'] = kwargs.get('n_electrons', 1) + self.data = data + self.to_json() + + def to_json(self) -> None: + """ + Stores the input data in a JSON file in the working directory. + """ + with open(f"{self.data.get('working_directory')}/input_ssmf.json", 'w') as file: + json.dump(self.data, file, indent=4) + + def read_from_file(self, input_file: str) -> dict: + """ + Reads the input data from a JSON file provided it is a pySSMF code input file. + + Args: + input_file (str): path to the input JSON file in the working directory. + + Returns: + (dict): dictionary with the input data read from the JSON input file. + """ + try: + with open(input_file, 'r') as file: + input_data = json.load(file) + except (FileNotFoundError, json.JSONDecodeError): + raise FileNotFoundError( + 'Input file not found or failed to decode JSON input file.', + extra={'input_file': input_file}, + ) + code_name = input_data.get('code', '') + if code_name != 'pySSMF': + raise ValueError( + 'Could not recognize the input JSON file as readable by the pySSMF code.', + extra={'input_file': input_file}, + ) + return input_data diff --git a/src/pyssmf/runner.py b/src/pyssmf/runner.py new file mode 100644 index 0000000..8b7afd8 --- /dev/null +++ b/src/pyssmf/runner.py @@ -0,0 +1,25 @@ +# +# Copyright: Dr. José M. Pizarro. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + + +class Runner: + """ + Class responsible of running a calculation. It reads the input data passed in the JSON + input file and runs the different possible calculations related with pySSMF. + """ + + def __init__(self, **kwargs): + super().__init__()