@@ -163,47 +163,60 @@ def solve(self, h_container, s_container, kpoints: Union[list, torch.Tensor, np
163163 eigvals_list = []
164164 eigvecs_list = []
165165
166- for k in kpoints :
167- hk = h_container .sample_k (k , symm = True )
168-
169- if s_container is not None :
170- sk = s_container .sample_k (k , symm = True )
171- hk -= self .sigma * sk
172- A = hk .to_scipy (format = "csr" )
173- M = sk
174- else :
175- hk .shift (- self .sigma )
176- A = hk .to_scipy (format = "csr" )
177- M = None
178-
179- A .sort_indices ()
180- A .sum_duplicates ()
181- N = A .shape [0 ]
182-
183- # Try PARDISO first, fall back to scipy SuperLU if PARDISO fails
184- # (MKL PARDISO has a known bug with certain block-structured patterns)
185- solver = PyPardisoSolver (mtype = 13 )
186- solver .factorize (A )
187-
188- def matvec (b ):
189- return solver .solve (A , b )
166+ # Create a single solver instance and reuse it across k-points.
167+ # PARDISO stores LU factors in opaque `pt` handles; creating a new
168+ # solver per k-point without calling free_memory() leaks all that
169+ # internal MKL memory.
170+ solver = PyPardisoSolver (mtype = 13 )
171+
172+ try :
173+ for k in kpoints :
174+ hk = h_container .sample_k (k , symm = True )
190175
191- Op = LinearOperator ((N , N ), matvec = matvec , dtype = A .dtype )
192-
193- try :
194- # Use larger NCV to help convergence, especially for clustered eigenvalues
195- ncv = max (2 * self .neig + 1 , 20 )
196- vals , vecs = eigsh (A = hk , M = M , k = self .neig , sigma = 0.0 , OPinv = Op , mode = self .mode , which = "LM" , ncv = ncv )
197- except Exception :
198- # Retry with larger NCV if ARPACK fails (e.g. error 3: No shifts could be applied)
199- # This often happens when eigenvalues are clustered near the shift
200- ncv = max (5 * self .neig , 50 )
201- vals , vecs = eigsh (A = hk , M = M , k = self .neig , sigma = 0.0 , OPinv = Op , mode = self .mode , which = "LM" , ncv = ncv )
202-
203- eigvals_list .append (vals + self .sigma )
204- if return_eigenvectors :
205- eigvecs_list .append (vecs )
206-
176+ if s_container is not None :
177+ sk = s_container .sample_k (k , symm = True )
178+ hk -= self .sigma * sk
179+ A = hk .to_scipy (format = "csr" )
180+ M = sk
181+ else :
182+ hk .shift (- self .sigma )
183+ A = hk .to_scipy (format = "csr" )
184+ M = None
185+
186+ A .sort_indices ()
187+ A .sum_duplicates ()
188+ N = A .shape [0 ]
189+
190+ solver .factorize (A )
191+
192+ def matvec (b ):
193+ return solver .solve (A , b )
194+
195+ Op = LinearOperator ((N , N ), matvec = matvec , dtype = A .dtype )
196+
197+ try :
198+ # Use larger NCV to help convergence, especially for clustered eigenvalues
199+ ncv = max (2 * self .neig + 1 , 20 )
200+ vals , vecs = eigsh (A = hk , M = M , k = self .neig , sigma = 0.0 , OPinv = Op , mode = self .mode , which = "LM" , ncv = ncv )
201+ except Exception :
202+ # Retry with larger NCV if ARPACK fails (e.g. error 3: No shifts could be applied)
203+ # This often happens when eigenvalues are clustered near the shift
204+ ncv = max (5 * self .neig , 50 )
205+ vals , vecs = eigsh (A = hk , M = M , k = self .neig , sigma = 0.0 , OPinv = Op , mode = self .mode , which = "LM" , ncv = ncv )
206+
207+ # Release PARDISO's internal LU factorization memory for this
208+ # k-point before moving to the next one. Without this, the
209+ # factorization buffers from *every* k-point accumulate.
210+ solver .free_memory ()
211+ del Op
212+
213+ eigvals_list .append (vals + self .sigma )
214+ if return_eigenvectors :
215+ eigvecs_list .append (vecs )
216+ finally :
217+ # Guarantee all internal PARDISO memory is released even on error.
218+ solver .free_memory (everything = True )
219+
207220 if return_eigenvectors :
208221 return eigvals_list , eigvecs_list
209222 else :
0 commit comments