Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
230 changes: 230 additions & 0 deletions docs/hands_on/tutorial2_2d_mat.ipynb

Large diffs are not rendered by default.

293 changes: 233 additions & 60 deletions dpnegf/negf/lead_property.py

Large diffs are not rendered by default.

92 changes: 55 additions & 37 deletions dpnegf/runner/NEGF.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from dpnegf.utils.elec_struc_cal import ElecStruCal
from dpnegf.negf.density import Ozaki,Fiori
from dpnegf.negf.device_property import DeviceProperty
from dpnegf.negf.lead_property import LeadProperty, compute_all_self_energy
from dpnegf.negf.lead_property import LeadProperty, compute_all_self_energy, _has_saved_self_energy
from dpnegf.negf.negf_utils import is_fully_covered
import ase
from dpnegf.utils.constants import Boltzmann, eV2J
Expand All @@ -21,7 +21,6 @@
from pyinstrument import Profiler
import os


log = logging.getLogger(__name__)


Expand Down Expand Up @@ -49,7 +48,7 @@ def __init__(self,
sgf_solver: str,
e_fermi: float=None,
use_saved_HS: bool=False, saved_HS_path: str=None,
self_energy_save: bool=False, self_energy_save_path: str=None, se_info_display: bool=False,
use_saved_se: bool=False, self_energy_save_path: str=None, se_info_display: bool=False,
out_tc: bool=False,out_dos: bool=False,out_density: bool=False,out_potential: bool=False,
out_current: bool=False,out_current_nscf: bool=False,out_ldos: bool=False,out_lcurrent: bool=False,
results_path: Optional[str]=None,
Expand Down Expand Up @@ -80,9 +79,9 @@ def __init__(self,
self.saved_HS_path = saved_HS_path

self.sgf_solver = sgf_solver
self.self_energy_save = self_energy_save
self.self_energy_save_path = self_energy_save_path
self.se_info_display = se_info_display
self.use_saved_se = use_saved_se # whether to use the saved self-energy or not
self.self_energy_save_path = self_energy_save_path # The directory to save the self-energy or for saved self-energy
self.se_info_display = se_info_display # whether to display the self-energy information after calculation
self.pbc = self.stru_options["pbc"]

if self.stru_options["lead_L"]["useBloch"] or self.stru_options["lead_R"]["useBloch"]:
Expand Down Expand Up @@ -403,13 +402,13 @@ def compute(self):
self.negf_compute(scf_require=False,Vbias=self.potential_at_orb)

else:
# profiler = Profiler()
# profiler.start()
profiler = Profiler()
profiler.start()
self.negf_compute(scf_require=False,Vbias=None)
# profiler.stop()
# output_path = os.path.join(self.results_path, "profile_report.html")
# with open(output_path, 'w') as report_file:
# report_file.write(profiler.output_html())
profiler.stop()
output_path = os.path.join(self.results_path, "profile_report.html")
with open(output_path, 'w') as report_file:
report_file.write(profiler.output_html())

Comment on lines +405 to 412
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Make profiling optional; avoid hard dependency on pyinstrument.

Importing pyinstrument unconditionally can break environments. Guard it and fall back gracefully.

Apply this diff:

-            profiler = Profiler()
-            profiler.start() 
-            self.negf_compute(scf_require=False,Vbias=None)
-            profiler.stop()
-            output_path = os.path.join(self.results_path, "profile_report.html")
-            with open(output_path, 'w') as report_file:
-                report_file.write(profiler.output_html())
+            try:
+                from pyinstrument import Profiler
+                profiler = Profiler()
+                profiler.start()
+                self.negf_compute(scf_require=False, Vbias=None)
+                profiler.stop()
+                output_path = os.path.join(self.results_path, "profile_report.html")
+                with open(output_path, 'w') as report_file:
+                    report_file.write(profiler.output_html())
+            except Exception as ex:
+                log.debug("Profiling disabled (%s). Running without profiler.", ex)
+                self.negf_compute(scf_require=False, Vbias=None)

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In dpnegf/runner/NEGF.py around lines 405 to 412, make pyinstrument optional by
wrapping the Profiler import in a try/except and only using profiling when
import succeeds: attempt to import Profiler from pyinstrument, set profiler =
None on ImportError, and then guard profiler.start(), profiler.stop(), and
writing profiler.output_html() with an if profiler check so the code falls back
silently (or logs a debug message) instead of raising when pyinstrument is not
installed; ensure output_path handling is unchanged and no profiler-related
calls run when profiler is None.

def poisson_negf_scf(self,interface_poisson,atom_gridpoint_index,err=1e-6,max_iter=1000,
mix_method:str='linear', mix_rate:float=0.3, tolerance:float=1e-7,Gaussian_sigma:float=3.0):
Expand Down Expand Up @@ -513,35 +512,57 @@ def poisson_negf_scf(self,interface_poisson,atom_gridpoint_index,err=1e-6,max_it
# if iter_count <= max_iter:
# profiler.stop()
# with open('profile_report.html', 'w') as report_file:
# report_file.write(profiler.output_html())

# report_file.write(profiler.output_html())、

def prepare_self_energy(self, scf_require: bool) -> None:
"""
Prepares the self-energy for the NEGF calculation.

Depending on the calculation settings, this method either loads previously saved self-energy data
or computes and saves new self-energy values for the device leads. The computation method varies
based on whether self-consistent field (SCF) calculations are required and whether Dirichlet boundary
conditions are applied to the leads.

Parameters:
----------
scf_require (bool): Indicates whether SCF calculations are required.
"""
# self energy calculation
log.info(msg="------Self-energy calculation------")
if self.self_energy_save_path is None:
self.self_energy_save_path = os.path.join(self.results_path, "self_energy")
os.makedirs(self.self_energy_save_path, exist_ok=True)

if self.use_saved_se:
assert _has_saved_self_energy(self.self_energy_save_path), "No saved self-energy found in {}".format(self.self_energy_save_path)
log.info(msg="Using saved self-energy from {}".format(self.self_energy_save_path))
log.info(msg="Ensure the saved self-energy is consistent with the current calculation setting!")
else:
log.info(msg="Calculating self-energy and saving to {}".format(self.self_energy_save_path))
if scf_require and self.poisson_options["with_Dirichlet_leads"]:
# For the Dirichlet leads, the self-energy of the leads is only calculated once and saved.
# In each iteration, the self-energy of the leads is not updated.
# for ik, k in enumerate(self.kpoints):
# for e in self.density.integrate_range:
# self.deviceprop.lead_L.self_energy(kpoint=k, energy=e, eta_lead=self.eta_lead, save=True)
# self.deviceprop.lead_R.self_energy(kpoint=k, energy=e, eta_lead=self.eta_lead, save=True)
compute_all_self_energy(self.eta_lead, self.deviceprop.lead_L, self.deviceprop.lead_R,
self.kpoints, self.density.integrate_range, self.self_energy_save_path)
elif not self.scf:
# In non-scf case, the self-energy of the leads is calculated for each energy point in the energy grid.
compute_all_self_energy(self.eta_lead, self.deviceprop.lead_L, self.deviceprop.lead_R,
self.kpoints, self.uni_grid, self.self_energy_save_path)
log.info(msg="-----------------------------------\n")



def negf_compute(self,scf_require=False,Vbias=None):

assert scf_require is not None, "scf_require should be set to True or False"
self.out['k']=[];self.out['wk']=[]
if hasattr(self, "uni_grid"): self.out["uni_grid"] = self.uni_grid

# self energy calculation
log.info(msg="------Self-energy calculation------")
selfen_parent_dir = os.path.join(self.results_path,"self_energy")
if not os.path.exists(selfen_parent_dir):
os.makedirs(selfen_parent_dir)
if scf_require and self.poisson_options["with_Dirichlet_leads"]:
# For the Dirichlet leads, the self-energy of the leads is only calculated once and saved.
# In each iteration, the self-energy of the leads is not updated.
# for ik, k in enumerate(self.kpoints):
# for e in self.density.integrate_range:
# self.deviceprop.lead_L.self_energy(kpoint=k, energy=e, eta_lead=self.eta_lead, save=True)
# self.deviceprop.lead_R.self_energy(kpoint=k, energy=e, eta_lead=self.eta_lead, save=True)
compute_all_self_energy(self.eta_lead, self.deviceprop.lead_L, self.deviceprop.lead_R,
self.kpoints, self.density.integrate_range)
elif not self.scf:
# In non-scf case, the self-energy of the leads is calculated for each energy point in the energy grid.
compute_all_self_energy(self.eta_lead, self.deviceprop.lead_L, self.deviceprop.lead_R,
self.kpoints, self.uni_grid)
log.info(msg="-----------------------------------\n")

self.prepare_self_energy(scf_require)

for ik, k in enumerate(self.kpoints):

Expand Down Expand Up @@ -627,7 +648,6 @@ def negf_compute(self,scf_require=False,Vbias=None):
kpoint=k,
eta_lead=self.eta_lead,
method=self.sgf_solver,
save=self.self_energy_save,
save_path=self.self_energy_save_path,
se_info_display=self.se_info_display
)
Expand All @@ -641,7 +661,6 @@ def negf_compute(self,scf_require=False,Vbias=None):
kpoint=k,
eta_lead=self.eta_lead,
method=self.sgf_solver,
save=self.self_energy_save,
save_path=self.self_energy_save_path,
se_info_display=self.se_info_display
)
Expand Down Expand Up @@ -736,7 +755,6 @@ def negf_compute(self,scf_require=False,Vbias=None):
kpoint=k,
eta_lead=self.eta_lead,
method=self.sgf_solver,
save=self.self_energy_save,
save_path=self.self_energy_save_path,
se_info_display=self.se_info_display
)
Expand Down
3 changes: 1 addition & 2 deletions dpnegf/tests/test_negf_negf_hamiltonian_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,7 @@ def test_negf_Hamiltonian(root_directory):
energy=e,
kpoint=kpoints[0],
eta_lead=negf_json['task_options']["eta_lead"],
method=negf_json['task_options']["sgf_solver"],
save=False
method=negf_json['task_options']["sgf_solver"]
)
print("lead_L self energy:",deviceprop.lead_L.se)
print("lead_R self energy:",deviceprop.lead_R.se)
Expand Down
4 changes: 2 additions & 2 deletions dpnegf/utils/argcheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -1047,8 +1047,8 @@ def negf():
Argument("stru_options", dict, optional=False, sub_fields=stru_options(), doc=doc_stru_options),
Argument("poisson_options", dict, optional=True, default={}, sub_fields=[], sub_variants=[poisson_options()], doc=doc_poisson_options),
Argument("sgf_solver", str, optional=True, default="Sancho-Rubio", doc=doc_sgf_solver),
Argument("self_energy_save", bool, optional=True, default=False, doc="whether to save the self energy"),
Argument("self_energy_save_path", str, optional=True, default=None, doc="the path to save the self energy"),
Argument("use_saved_se", bool, optional=True, default=False, doc="whether to use saved self energy"),
Argument("self_energy_save_path", str, optional=True, default=None, doc="the directory to save the self energy or load the self energy"),
Argument("se_info_display", bool, optional=True, default=False, doc="whether to display the self energy information"),
Argument("espacing", [int, float], optional=False, doc=doc_espacing),
Argument("emin", [int, float], optional=False, doc=doc_emin),
Expand Down
18 changes: 18 additions & 0 deletions examples/graphene/band.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"structure":"train/data/POSCAR",
"task_options": {
"task": "band",
"kline_type":"abacus",
"kpath":[
[0, 0, 0, 50],
[0.5, 0, 0, 50],
[0.3333333, 0.3333333, 0, 50],
[0, 0, 0, 1]
],
"nkpoints":151,
"klabels":["G", "M", "K", "G"],
"emin":-20,
"emax":20,
"nel_atom":{"C":4}
}
}
11 changes: 11 additions & 0 deletions examples/graphene/extr_baseline/c_spds.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"common_options": {
"basis": {
"C": [
"2s",
"2p",
"d*"
]
}
}
}
Comment on lines +1 to +11
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

🧩 Analysis chain

Fix directory name: extr_baseline vs extra_baseline

Graphene uses "extr_baseline" while hBN uses "extra_baseline". This inconsistency will break scripts relying on a common pattern. Rename the directory or align all references.

Run to spot all occurrences:


🏁 Script executed:

#!/bin/bash
rg -n 'extra_baseline|extr_baseline' -C2

Length of output: 396


Fix directory name: extr_baseline vs extra_baseline

PR adds examples/graphene/extr_baseline (e.g., examples/graphene/extr_baseline/c_spds.json) while docs reference ../extra_baseline/hbn_spd_model/sktb.json (docs/hands_on/tutorial2_2d_mat.ipynb:29). Align the directory name or update all references to avoid breaking scripts.

🤖 Prompt for AI Agents
In examples/graphene/extr_baseline/c_spds.json (lines 1-11) the new directory is
named "extr_baseline" but docs reference "extra_baseline" (e.g.,
docs/hands_on/tutorial2_2d_mat.ipynb line ~29); fix by making the paths
consistent across the repo: either rename the examples/graphene/extr_baseline
directory to examples/graphene/extra_baseline, or update all documentation and
scripts to use "extr_baseline". Search the repo for both "extra_baseline" and
"extr_baseline" and apply the chosen name consistently, updating notebook
references (docs/hands_on/tutorial2_2d_mat.ipynb) and any CI/scripts that
consume these example paths.

Loading