From 6a1635c6731801ca645f9576a6406340709d270b Mon Sep 17 00:00:00 2001 From: Anouar Moustaj Date: Fri, 12 Jun 2026 14:18:44 +0300 Subject: [PATCH 1/7] updated the include path as the file structure changed. Corrected some bugs as well --- examples/dynamics/time_evolution.ipynb | 36 +- .../manybody/nonhermitian_loss_chain.ipynb | 543 +++++++++++++++++- examples/manybody/scf_examples.ipynb | 4 +- examples/misc/miscellaneous.ipynb | 32 +- examples/spectral/aux_ldos_examples.ipynb | 8 +- .../momentum_spectral_functions.ipynb | 8 +- examples/topology/real_space_topology.ipynb | 10 +- 7 files changed, 577 insertions(+), 64 deletions(-) diff --git a/examples/dynamics/time_evolution.ipynb b/examples/dynamics/time_evolution.ipynb index 1bbae8e..0dd4011 100644 --- a/examples/dynamics/time_evolution.ipynb +++ b/examples/dynamics/time_evolution.ipynb @@ -1,17 +1,4 @@ { - "nbformat": 4, - "nbformat_minor": 5, - "metadata": { - "kernelspec": { - "display_name": "Julia 1.10", - "language": "julia", - "name": "julia-1.10" - }, - "language_info": { - "name": "julia", - "version": "1.10.0" - } - }, "cells": [ { "cell_type": "markdown", @@ -29,7 +16,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "te_imports", "metadata": {}, "outputs": [], @@ -38,7 +25,7 @@ "using Plots\n", "using ITensors\n", "using ITensorMPS\n", - "include(\"../src/TensorBinding.jl\")\n", + "include(\"../../src/TensorBinding.jl\")\n", "using .TensorBinding" ] }, @@ -5015,5 +5002,20 @@ ")" ] } - ] -} \ No newline at end of file + ], + "metadata": { + "kernelspec": { + "display_name": "Julia 1.12", + "language": "julia", + "name": "julia-1.12" + }, + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.12.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/manybody/nonhermitian_loss_chain.ipynb b/examples/manybody/nonhermitian_loss_chain.ipynb index c2c3650..a7e46e6 100644 --- a/examples/manybody/nonhermitian_loss_chain.ipynb +++ b/examples/manybody/nonhermitian_loss_chain.ipynb @@ -16,7 +16,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "794ec812", "metadata": {}, "outputs": [], @@ -26,7 +26,7 @@ "using ITensors\n", "using ITensorMPS\n", "\n", - "include(\"../src/TensorBinding.jl\")\n", + "include(\"../../src/TensorBinding.jl\")\n", "using .TensorBinding" ] }, @@ -42,10 +42,18 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "e496bd95", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "TBHamiltonian | L=3, N=8, scale=auto, maxlinkdim=3 | geometry: 8 sites, 1D | no Tn cache\n" + ] + } + ], "source": [ "L = 3 # N = 2^L sites\n", "t = 1.0\n", @@ -72,9 +80,17 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "z = 0.0 + 0.15im, scale = 4.0\n" + ] + } + ], "source": [ "z = 0.0 + 0.15im\n", "scale = 4.0\n", @@ -95,9 +111,18 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "NonHermitianHamiltonian | z=0.0 + 0.15im, blockdim=2, hermitized maxlinkdim=4\n", + "TBHamiltonian | L=3, N=8, scale=4.0, maxlinkdim=4 | geometry: 8 sites, 1D | no Tn cache\n" + ] + } + ], "source": [ "NH = TensorBinding.hermitize(H; z=z, scale=scale, maxdim=maxdim,\n", " convention=:z_minus_H)\n", @@ -108,14 +133,415 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "id": "9bdf9423", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.6499999999999997\n", + "-0.6500000000000004\n" + ] + }, + { + "data": { + "image/png": "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", + "image/svg+xml": [ + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n" + ], + "text/html": [ + "" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "#check the proper hermitrized Hamiltonian\n", "hermitized_mpo = NH.hermitized\n", - "M = TensorBinding.get_matrix(hermitized_mpo.mpo, hermitrized_mpo.sites)\n", + "M = TensorBinding.get_matrix(hermitized_mpo.mpo, hermitized_mpo.sites)\n", "println(maximum(imag.(M)))\n", "println(minimum(imag.(M)))\n", "heatmap(imag.(M),yflip=true)" @@ -132,9 +558,18 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "total spectral weight at z = -2.309436793446442 + 8.079261817456338e-15im\n", + "computed 40 partial moments\n" + ] + } + ], "source": [ "A_mps, dos_point, partials = TensorBinding.nh_spectral_function(\n", " NH, Ncheb_half;\n", @@ -148,19 +583,95 @@ "println(\"total spectral weight at z = \", dos_point)\n", "println(\"computed \", length(partials), \" partial moments\")" ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "658989aa", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "image/svg+xml": [ + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n" + ], + "text/html": [ + "" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "plot(ldos, label=\"LDOS\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d4b1d899", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { "kernelspec": { - "display_name": "Julia 1.11.6", + "display_name": "Julia 1.12", "language": "julia", - "name": "julia-1.11" + "name": "julia-1.12" }, "language_info": { "file_extension": ".jl", "mimetype": "application/julia", "name": "julia", - "version": "1.11.6" + "version": "1.12.3" } }, "nbformat": 4, diff --git a/examples/manybody/scf_examples.ipynb b/examples/manybody/scf_examples.ipynb index cd72376..00ad31d 100644 --- a/examples/manybody/scf_examples.ipynb +++ b/examples/manybody/scf_examples.ipynb @@ -15,7 +15,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "id": "a002", "metadata": {}, "outputs": [], @@ -25,7 +25,7 @@ "using ITensors\n", "using ITensorMPS\n", "\n", - "include(\"../src/TensorBinding.jl\")\n", + "include(\"../../src/TensorBinding.jl\")\n", "using .TensorBinding" ] }, diff --git a/examples/misc/miscellaneous.ipynb b/examples/misc/miscellaneous.ipynb index 41e9032..64da1ce 100644 --- a/examples/misc/miscellaneous.ipynb +++ b/examples/misc/miscellaneous.ipynb @@ -1,17 +1,4 @@ { - "nbformat": 4, - "nbformat_minor": 5, - "metadata": { - "kernelspec": { - "display_name": "Julia 1.10", - "language": "julia", - "name": "julia-1.10" - }, - "language_info": { - "name": "julia", - "version": "1.10.0" - } - }, "cells": [ { "cell_type": "markdown", @@ -40,7 +27,7 @@ "using Plots\n", "using ITensors\n", "using ITensorMPS\n", - "include(\"../src/TensorBinding.jl\")\n", + "include(\"../../src/TensorBinding.jl\")\n", "using .TensorBinding" ] }, @@ -839,5 +826,18 @@ "vline!([0.0]; ls=:dot, color=:gray, alpha=0.6, label=\"ω = 0\")" ] } - ] -} \ No newline at end of file + ], + "metadata": { + "kernelspec": { + "display_name": "Julia 1.10", + "language": "julia", + "name": "julia-1.10" + }, + "language_info": { + "name": "julia", + "version": "1.10.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/spectral/aux_ldos_examples.ipynb b/examples/spectral/aux_ldos_examples.ipynb index b8fe6a0..0fbaf70 100644 --- a/examples/spectral/aux_ldos_examples.ipynb +++ b/examples/spectral/aux_ldos_examples.ipynb @@ -12,7 +12,7 @@ "using LaTeXStrings\n", "using ITensors\n", "using ITensorMPS\n", - "include(\"../src/TensorBinding.jl\")\n", + "include(\"../../src/TensorBinding.jl\")\n", "using .TensorBinding\n", "using QuanticsTCI\n", "import TensorCrossInterpolation as TCI" @@ -12818,15 +12818,15 @@ ], "metadata": { "kernelspec": { - "display_name": "Julia 1.11.6", + "display_name": "Julia 1.12", "language": "julia", - "name": "julia-1.11" + "name": "julia-1.12" }, "language_info": { "file_extension": ".jl", "mimetype": "application/julia", "name": "julia", - "version": "1.11.6" + "version": "1.12.3" } }, "nbformat": 4, diff --git a/examples/spectral/momentum_spectral_functions.ipynb b/examples/spectral/momentum_spectral_functions.ipynb index 4468b2b..e14e89f 100644 --- a/examples/spectral/momentum_spectral_functions.ipynb +++ b/examples/spectral/momentum_spectral_functions.ipynb @@ -16,7 +16,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 1, "id": "6919ba61", "metadata": {}, "outputs": [], @@ -26,7 +26,7 @@ "using LaTeXStrings\n", "using ITensors\n", "using ITensorMPS\n", - "include(\"../src/TensorBinding.jl\")\n", + "include(\"../../src/TensorBinding.jl\")\n", "using .TensorBinding\n", "using QuanticsTCI\n", "import TensorCrossInterpolation as TCI" @@ -6932,9 +6932,9 @@ ], "metadata": { "kernelspec": { - "display_name": "Julia (18 threads) 1.11.2", + "display_name": "Julia 1.12", "language": "julia", - "name": "julia-_18-threads_-1.11" + "name": "julia-1.12" }, "language_info": { "file_extension": ".jl", diff --git a/examples/topology/real_space_topology.ipynb b/examples/topology/real_space_topology.ipynb index c8b3f2d..1a946f1 100644 --- a/examples/topology/real_space_topology.ipynb +++ b/examples/topology/real_space_topology.ipynb @@ -15,7 +15,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 1, "id": "top001", "metadata": {}, "outputs": [], @@ -24,7 +24,7 @@ "using Plots\n", "using ITensors\n", "using ITensorMPS\n", - "include(\"../src/TensorBinding.jl\")\n", + "include(\"../../src/TensorBinding.jl\")\n", "using .TensorBinding\n", "using QuanticsTCI\n", "import TensorCrossInterpolation as TCI" @@ -6017,15 +6017,15 @@ ], "metadata": { "kernelspec": { - "display_name": "Julia (18 threads) 1.11.2", + "display_name": "Julia 1.12", "language": "julia", - "name": "julia-_18-threads_-1.11" + "name": "julia-1.12" }, "language_info": { "file_extension": ".jl", "mimetype": "application/julia", "name": "julia", - "version": "1.11.2" + "version": "1.12.3" } }, "nbformat": 4, From c5bdc1ef1230e7fda5a8b03655c4d400fbc3a046 Mon Sep 17 00:00:00 2001 From: Anouar Moustaj Date: Fri, 12 Jun 2026 14:19:38 +0300 Subject: [PATCH 2/7] This notebook was broken. The exciton was wrong codes were incorrect. Now corrected. The RPA examples are still broken and need to be looked at. --- examples/manybody/many_body.ipynb | 1026 ++++++++++++++++++++++++----- 1 file changed, 866 insertions(+), 160 deletions(-) diff --git a/examples/manybody/many_body.ipynb b/examples/manybody/many_body.ipynb index ed1fed2..1af9397 100644 --- a/examples/manybody/many_body.ipynb +++ b/examples/manybody/many_body.ipynb @@ -1,17 +1,4 @@ { - "nbformat": 4, - "nbformat_minor": 5, - "metadata": { - "kernelspec": { - "display_name": "Julia 1.10", - "language": "julia", - "name": "julia-1.10" - }, - "language_info": { - "name": "julia", - "version": "1.10.0" - } - }, "cells": [ { "cell_type": "markdown", @@ -30,7 +17,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 16, "id": "mb_imports", "metadata": {}, "outputs": [], @@ -39,7 +26,7 @@ "using Plots\n", "using ITensors\n", "using ITensorMPS\n", - "include(\"../src/TensorBinding.jl\")\n", + "include(\"../../src/TensorBinding.jl\")\n", "using .TensorBinding" ] }, @@ -53,11 +40,11 @@ "\n", "An exciton is a correlated electron-hole pair. In the quantics representation\n", "the two-particle Hilbert space is encoded on a `2L`-qubit chain:\n", - "sites `1..L` for the electron, sites `L+1..2L` for the hole.\n", + "odd sites for the electron position qubits and even sites for the hole position qubits.\n", "\n", "The exciton Hamiltonian is\n", "$$H_{\\rm exc} = H_e \\otimes I_h + I_e \\otimes H_h + V_{eh}$$\n", - "where $V_{eh}(r_e, r_h) = -U/|r_e - r_h|$ is the Coulomb attraction.\n", + "where $V_{eh}(r_e, r_h) = -U(r,r)$ is a Hubbard-type interaction that can be modulated in space.\n", "\n", "The `2L`-site MPO makes the standard MPO-mode Chebyshev list expensive;\n", "the **MPS mode** (`KPM_Tn(H, Ncheb, X; ...)`) propagates a single reference\n", @@ -65,54 +52,752 @@ ] }, { - "cell_type": "markdown", - "id": "sp_exc_intro", + "cell_type": "code", + "execution_count": 18, + "id": "sp_exc_build", "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "TBHamiltonian | L=5, N=32 [exciton, D=1024], scale=10.0, maxlinkdim=11 | geometry: 32 sites, 1D | no Tn cache\n" + ] + } + ], "source": [ - "---\n", - "## 2. Exciton Hamiltonians\n", + "# 1D electron + hole on a chain; APSOS-style modulated type-I confinement\n", + "L_exc = 5 # 2^5 = 32 sites per particle\n", + "t_exc = -1.0\n", + "U_exc = 6.0 # contact attraction strength\n", + "V0_exc = 1.5 # confinement scale\n", + "scale_exc = 10.0\n", + "maxdim_exc = 100\n", "\n", - "An exciton is a correlated electron-hole pair. In the quantics representation\n", - "the two-particle Hilbert space is encoded on a `2L`-qubit chain:\n", - "sites `1..L` for the electron, sites `L+1..2L` for the hole.\n", + "N_sites_exc = 2^L_exc\n", "\n", - "The exciton Hamiltonian is\n", - "$$H_{\\rm exc} = H_e \\otimes I_h + I_e \\otimes H_h + V_{eh}$$\n", - "where $V_{eh}(r_e, r_h) = -U/|r_e - r_h|$ is the Coulomb attraction.\n", + "function Vx_exc(x; V0=V0_exc, N=N_sites_exc)\n", "\n", - "The `2L`-site MPO makes the standard MPO-mode Chebyshev list expensive;\n", - "the **MPS mode** (`KPM_Tn(H, Ncheb, X; ...)`) propagates a single reference\n", - "state $|X, X\\rangle$ and is the recommended approach." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "sp_exc_build", - "metadata": {}, - "outputs": [], - "source": [ - "# 1D electron + hole on a chain; Coulomb attraction\n", - "L_exc = 5 # 2^5 = 32 sites per particle\n", - "t_exc = 1.0\n", - "U_exc = 2.0 # Coulomb strength\n", + " b = sqrt(3)*N/10\n", + " k = 2*pi/b\n", + "\n", + " return V0*(1 + 0.2*(cos(k*x)))\n", + "end\n", "\n", - "H_exc = TensorBinding.get_Hamiltonian(\"exciton_1d\", (t=t_exc, U=U_exc); L=L_exc)\n", + "H_exc = TensorBinding.exciton_hamiltonian(\"chain_1d\", t_exc, x -> U_exc;\n", + " L = L_exc,\n", + " on_site = Vx_exc,\n", + " scale = scale_exc,\n", + " maxdim = maxdim_exc)\n", "println(H_exc)" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 20, "id": "sp_exc_kpm", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "image/png": "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KpcXXnjh2fdn165dRFTB2kREvXr1WrJkiflbP506daoglpKSQkS5ublJSUln35mqKC4upoCVz3QJKS0trUreMIyysjK/pMnAgQO//vrr4uLin376adOmTf/6178yMjLmzZuXl5dnbiyqYu/evRs3buzYsaN5FGwyefLkNWvWLF68+JJLLvFfPH36NFW7Qj/77LM7duyIj493Op0PP/zwW2+9VdOtV445Lz179gy8GB4efv755+fm5u7atatdu3b+6xUcgsxlJj09vcLfJiQkHD9+3H8jBw8ejImJMc8eAsnOziaigwcP9urVa+/evUTUtWvXCjIV4jVNVq9ePW/evJ07dx45csScHZOTJ09WkDRdfFXvroT6jiyEDYKRI0d+8MEHRHTHHXcEan7l5eVutzsuLq6Ci2DtMNcYcz0LxHRtKCoqCrwY6LBqYjqpGoqT3x44cICITMsTEZkerZmZmVXJZ2Vl6bput9v9f+InNjZ25MiRI0eOnDFjxt/+9rf77rvv73//+9NPP33mGu/n7bffJqKmTZu+8MIL/ovmyCxbtmz+/Pn+iYiLiyOikpKSStvZtm3biy++mJycvH79+ssuu2zhwoXjx4+vhV7ocrk8Hk9sbOyZ02HOY4VZi4qKCvzRPCKudCr9eYhMnbu8vHzhwoVndqBx48amZHl5ufnjmQIVrnz44Yc333xzVFTUqFGjJkyYYKrFGzduXLlyZeBZiEmFfY8gVIoshOc+Bw8evOeee6Kjo3Vdnz59+rBhw/wB5pGRkREREUVFRUVFReY379lgtuBXBfyYV0Lhy8gwjFWrVhGR//zt4osvfvvtt02zVqWYv+revXv1e4V777133rx5Bw4cyMjIqGoh9Hq9S5YsIaJ169ZVOAglouLi4mXLlpkuMPRfZSs/P//Mdlwu1+TJkz0ezxtvvJGenv7WW29deeWVd9xxx2+//cbd0ERERISHhxcXF5eWllZYz8zjxLOfNfOpSEpKOvPBCMRc8I4dO1bh+tGjRwN/1HX9kUceCQ8P37Rpk5lJx+SZZ55ZuXLlmc2ainU1J8yCQJJr9JzH6/VOnDjR6XS+9tprc+fOPX369LXXXhuYGNM8yfz555/xNk2d8szdt3mK5Q8u9LNlyxYiCvzasooPP/zwt99+i4mJue6668wr11xzTXx8/K5du7744osz5d1u97x584jIjGGvHjNYMzCKoAKrVq3Kzc296KKLvj2D+fPnE9G7777rFz7//POJaN++fWe2M3PmzB07dowdO9aMPrziiismT5588ODBxx9/vMZOnok5a1u3bg286HK5zDPzs5+15OTkli1b5uXlmbp4VfTo0UPTtPXr17vd7sDrq1evDvwxLy8vJyenW7duFTpW1QNsnrh27969dp0XGgoWO+sItQLPLGMm17juuuvMH83whscee8wv8PrrrxPRJZdc4ncKPZMKXqM+ny88PDwtLa2Co+O2bduIKDEx8dSpU/6L+/btMzOg+gM2/HGEFT5l7NixRPTbb79Vczu19hotLS19+eWXzbWqgo/onDlziCg1NdUfBGLi8XjM9a9Tp07+jKOHDx8+M9WLYRjr1q0zl8D9+/dX1THzBhcuXHjmr3w+X4sWLYho9+7d5hXThyUwfYxJRkaGw+FITk7Ozc31X3Q6nWlpaTabrUIWlcWLFy9YsKD6yIrnnnuOiK644orA2TSfir59+/qvmF6jFZKpfv3110R0xx13VGgzMTExISHB/+NTTz1FRNdee+2ZwZeBieLMMPyXX37Zf+XQoUNmoju/12h5eXlYWFjz5s0DnZw3bdpknquf6R163nnn2Wy2kydPVjMCgiALYb3EXAjj4uLuqQy/M/2qVas0TevQoYPfud/pdFaIoHC73eY54UUXXbR8+fIdO3asWbPmlVdeGTRokP/jKiyEhmH079+fiK6//vpXX311wYIF/u9fc+U4//zzP/vssx07dixdutQ8hn3yySf9f3v2C2HXrl3PvOsHH3zQlDEXwilTpjzyyCOPPPLInXfeOWrUKNNpIjw8/MyIPZ/PN3HiRCKKjo7+05/+9Pnnn//www+vv/666UCUmpr666+/+oVNzaNjx46PPfbYihUrfvjhh+XLlz/44IPmAWBV8e+GYeTm5jocjvDw8Kq+lB988EEievzxx80fnU5nTEzMeeedFyhTXl5uKnBnZmX7/PPPiaht27aBicVN+1lGRkY1Q1pYWGjGel5//fVr16795ZdfZs2aFRERERYWFhjJcDYLYXFxsTmYgwcP/uijjzIyMn766aePPvpo0qRJaWlpfjHT90fTtAkTJrzyyiuPPPJIkyZNTMNnYPiEmSbmmmuuWb9+/e7du998883k5GTTo6fCQpibm2u32y3JBSjUL2QhrJeY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", + "image/svg+xml": [ + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n" + ], + "text/html": [ + "" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "# MPS-mode KPM: propagate |X,X> (electron and hole both at site X)\n", - "X_exc = H_exc.N ÷ 2 # exciton centre\n", - "Ncheb_e = 80\n", - "mdim_e = 60\n", - "TensorBinding.KPM_Tn(H_exc, Ncheb_e, X_exc; maxdim=mdim_e)" + "# Exciton LDOS A(X, omega) = \n", + "X_list_exc = collect(1:H_exc.N)\n", + "Ncheb_exc = 80\n", + "mdim_exc = 100\n", + "cutoff_exc = 1e-6\n", + "m_order_exc = 6\n", + "eta_exc = 1 / (Ncheb_exc + 1)\n", + "omega_exc = range(-8.0, 8.0; length=160)\n", + "\n", + "ldos_exc_mat = TensorBinding.get_exciton_ldos_spatial(H_exc, Ncheb_exc, omega_exc;\n", + " X_list = X_list_exc,\n", + " kernel = :hodc,\n", + " eta = eta_exc,\n", + " m_order = m_order_exc,\n", + " maxdim = mdim_exc,\n", + " cutoff = cutoff_exc)\n", + "\n", + "heatmap(X_list_exc, collect(omega_exc), ldos_exc_mat;\n", + " xlabel=\"X\", ylabel=\"energy\", colorbar_title=\"LDOS\",\n", + " title=\"Exciton LDOS A(X, omega)\", color=:inferno)" ] }, { @@ -120,71 +805,118 @@ "execution_count": null, "id": "sp_exc_ldos", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "image/png": "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IAhWJ5IAo9FolD/OvXv3ir5dhw4dKlasePjw4T/++OPJjezcuXNgYOCff/75119/2R6VChkFQRg6dGhqauqSJUtatGixYsWKhg0brlixgsRyAUCtVr/99tsmk2n27NlPfnfS3sz20fr16wcAX375pfyvmR07dty8ebNGjRpWa4cliCAIe/bs6dSpk16v79OnT5cuXaRDixcvZhgmPDx8x44dVmd9//33P//8s4uLy+effy4Nrlu3zmQyWc28f/9+ZGQkADRp0qSUHgFxIhyctYo8jxCvJTg4eObMmbt27Tpy5Mi2bdsmTJhAEhnCwsKkmcSX8vPzmz179urVqyMiIkjd3u3btz09PSmK+uCDD86ePXvmzJkJEyYAgLe3d0JCgnT6n3/+ybKsWq3+5JNPjh8/fvny5d9++23kyJErVqwgE+y1WOvQoQMASM2+Sf5k/fr1f//999jY2N9+++2VV16pXr06AHTr1o3M2b9/f7Vq1ebMmbNv375r165FR0fPnz+fZdny5ctnZGSQOURsqlevPm/evIiIiIiICLPZLCrVEZIKSK1W++mnn544ceLSpUs7d+4cNmzY2rVri/hg9+/fz7KsRqOZNm3a/v37L1269Mcff3zzzTchISGffvopmUP2k+nWrZvUzcC2giIlJYVodr9+/Q4cOHDlypWDBw9++umnI0eOJBNsyydyc3M9PT1VKtXEiROXL18eERFBqu6MRiNRu+7dux86dOjKlSvh4eGkwd5vv/0mnU4SfLZs2WL1RIGBgVqttohHFvPLJ7p06fLxxx9//PHH48eP79+/v9RFYdCgQTqdzuqUFStW0DRNUdTAgQN//fXXI0eObNq0qWfPngCgUqmszHB3d3/ppZfGjBmzYcOGI0eOREZGLliwgEQd6tatK9XdI88xKIRIyZOYmNinTx/bFHZ3d/dPPvnEZDLJJy9YsECeUi81+Dhz5oxVtufLL78s71NKOHDggFQYQNBoNBs3biRHn1AI8/LyunXrJr9Iq1at/vnnH7kQnjp1yrb/We3atU+dOiVdluf5SZMmydMRi+gss2fPHqt8DRcXlx07dhT92R46dEhqhCZRpUoVUr3+999/MwxTqVIlqx0w1qxZA4Vr6uPj46XW5wSKosaMGUOO2gqhKIqRkZHyNtxSZ5n79+/LHTIAKF++/C+//CI/9+mFUA5N07Vr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", + "image/svg+xml": [ + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n" + ], + "text/html": [ + "" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "# Exciton LDOS at |X,X>\n", - "omega_exc = range(-6.0, 6.0; length=120)\n", - "ldos_exc = [TensorBinding.get_ldos_online(H_exc, Ncheb_e, X_exc, [omega_p]; maxdim=mdim_e)[1]\n", - " for omega_p in omega_exc]\n", + "# Stochastic exciton DOS. The CPU helper supports bound-sector enrichment.\n", + "N_sample_exc = 20\n", + "N_bound_exc = 12\n", + "seed_exc = 42\n", "\n", - "plot(collect(omega_exc), ldos_exc;\n", - " xlabel=\"energy\", ylabel=\"LDOS\", title=\"Exciton LDOS at X=$(X_exc)\",\n", - " legend=false, lw=2)" - ] - }, - { - "cell_type": "markdown", - "id": "mb_sec2", - "metadata": {}, - "source": [ - "---\n", - "## 2. RPA susceptibility via Wynn ε-algorithm\n", + "dos_full_exc = TensorBinding.get_dos_stochastic(H_exc, Ncheb_exc, omega_exc;\n", + " N_sample = N_sample_exc,\n", + " N_bound = 0,\n", + " seed = seed_exc,\n", + " kernel = :hodc,\n", + " eta = eta_exc,\n", + " m_order = m_order_exc,\n", + " normalize = false,\n", + " dos_weighting = :sample,\n", + " maxdim = mdim_exc,\n", + " cutoff = cutoff_exc)\n", "\n", - "The Random Phase Approximation resums the Dyson series for the interacting\n", - "susceptibility:\n", - "$$\\chi = \\chi_0 + \\chi_0 U \\chi_0 + \\cdots = \\frac{\\chi_0}{1 - U\\chi_0}$$\n", + "dos_bound_exc = TensorBinding.get_dos_stochastic(H_exc, Ncheb_exc, omega_exc;\n", + " N_sample = 0,\n", + " N_bound = N_bound_exc,\n", + " seed = seed_exc + 1,\n", + " kernel = :hodc,\n", + " eta = eta_exc,\n", + " m_order = m_order_exc,\n", + " normalize = false,\n", + " dos_weighting = :sample,\n", + " maxdim = mdim_exc,\n", + " cutoff = cutoff_exc)\n", "\n", - "The bare $\\chi_0(q,\\omega)$ is computed from the KPM density matrix and Green's\n", - "function. Wynn's epsilon algorithm accelerates convergence of the partial sums." + "dos_total_exc = dos_full_exc .+ dos_bound_exc\n", + "\n", + "plot(collect(omega_exc), [dos_full_exc dos_bound_exc dos_total_exc];\n", + " xlabel=\"energy\", ylabel=\"sample-weighted DOS\",\n", + " title=\"Stochastic exciton DOS\",\n", + " label=[\"full-space samples\" \"bound |X,X> samples\" \"sum\"], lw=2)" ] }, { "cell_type": "markdown", "id": "68de97f0", "metadata": {}, - "source": [ - "---\n", - "## 5. RPA susceptibility via Wynn ε-algorithm\n", - "\n", - "The Random Phase Approximation resums the Dyson series for the interacting\n", - "susceptibility:\n", - "\n", - "$$\\chi_\\text{RPA} = \\Pi_0 + \\Pi_0 V \\Pi_0 + \\Pi_0 V \\Pi_0 V \\Pi_0 + \\cdots\n", - "= (I - \\Pi_0 V)^{-1} \\Pi_0$$\n", - "\n", - "where $\\Pi_0(\\omega)$ is the non-interacting polarization bubble and $V$ is the\n", - "bare interaction.\n", - "\n", - "**Efficient workflow** (two improvements over the raw MPO approach):\n", - "\n", - "1. **Density matrix once** — $P = \\theta(\\mu - H)$ is computed a single time via\n", - " McWeeny purification and cached in `H._density_cache`. All frequencies share\n", - " the same $P$; no KPM loop is needed.\n", - "\n", - "2. **Wynn ε-algorithm** — instead of solving the large linear system $(I - \\Pi_0 V)\\chi = \\Pi_0$\n", - " at every $\\omega$, we build the Neumann series\n", - " $T_0 = \\Pi_0$, $T_n = T_{n-1} V \\Pi_0$, extract scalars\n", - " $s_n(q) = -\\operatorname{Im}\\langle q | T_n | q \\rangle / \\pi$ via `get_spect_k`,\n", - " and apply the Wynn ε-algorithm to the partial-sum sequence $[S_0, S_1, \\ldots, S_K]$\n", - " per $(q, \\omega)$. With $K = 6$ terms, the Padé estimate $\\varepsilon_6$ typically\n", - " converges as well as hundreds of MPO solves.\n", - "\n", - "All of this is exposed through the single call `get_rpa_susceptibility_wynn`." - ] + "source": "---\n## 2. RPA susceptibility via Wynn ε-algorithm\n\nThe Random Phase Approximation resums the Dyson series for the interacting\nsusceptibility:\n\n$$\\chi_\\text{RPA} = \\Pi_0 + \\Pi_0 V \\Pi_0 + \\Pi_0 V \\Pi_0 V \\Pi_0 + \\cdots\n= (I - \\Pi_0 V)^{-1} \\Pi_0$$\n\nwhere $\\Pi_0(\\omega)$ is the non-interacting polarization bubble and $V$ is the\nbare interaction.\n\n**Efficient workflow** (two improvements over the raw MPO approach):\n\n1. **Density matrix once** — $P = \\theta(\\mu - H)$ is computed a single time via\n McWeeny purification and cached in `H._density_cache`. All frequencies share\n the same $P$; no KPM loop is needed.\n\n2. **Wynn ε-algorithm** — instead of solving the large linear system $(I - \\Pi_0 V)\\chi = \\Pi_0$\n at every $\\omega$, we build the Neumann series\n $T_0 = \\Pi_0$, $T_n = T_{n-1} V \\Pi_0$, extract scalars\n $s_n(q) = -\\operatorname{Im}\\langle q | T_n | q \\rangle / \\pi$ via `get_spect_k`,\n and apply the Wynn ε-algorithm to the partial-sum sequence $[S_0, S_1, \\ldots, S_K]$\n per $(q, \\omega)$. With $K = 6$ terms, the Padé estimate $\\varepsilon_6$ typically\n converges as well as hundreds of MPO solves.\n\nAll of this is exposed through the single call `get_rpa_susceptibility_wynn`." }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 22, "id": "27fc4738", "metadata": {}, "outputs": [ @@ -192,16 +924,9 @@ "name": "stdout", "output_type": "stream", "text": [ - "Pre-computing P via McWeeny purification..." - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "Tr(P) = 15.99996522081863 (target N/2 = 16)\n", - "maxlinkdim(P) = 13\n", + "Pre-computing P via McWeeny purification...\n", + "Tr(P) = 15.99999515968329 (target N/2 = 16)\n", + "maxlinkdim(P) = 12\n", "\n", "Interaction: on-site Hubbard U = 2.0\n" ] @@ -209,7 +934,12 @@ ], "source": [ "# ── System ────────────────────────────────────────────────────────────────────\n", - "# Reuse H1 from section 1 (L=5, N=32, 1D nearest-neighbour chain).\n", + "\n", + "L = 5\n", + "t = -1.0\n", + "\n", + "H1 = TensorBinding.get_Hamiltonian(\"chain_1d\", t; L=L, scale=scale_exc)\n", + "\n", "# The density matrix is computed once via McWeeny purification and\n", "# stored in H1._density_cache. Every call to get_bubble_mpo with\n", "# P_method=:purification will find it there and skip recomputation.\n", @@ -229,58 +959,34 @@ }, { "cell_type": "code", - "execution_count": null, - "id": "6f997278", + "id": "f6c3c099", + "source": "# ── RPA susceptibility via Wynn ε-acceleration ──────────────────────────────\nK_max = 6\nomegalist_rpa = range(-4.0, 4.0; length=80)\n\nchi_partial, chi_wynn = TensorBinding.get_rpa_susceptibility_wynn(H1, MPOV, omegalist_rpa;\n mode = :charge,\n K_max = K_max,\n verbose = false)\n\nn_wynn = K_max ÷ 2\nprintln(\"chi_partial: \", size(chi_partial), \" chi_wynn: \", size(chi_wynn))", "metadata": {}, - "outputs": [], - "source": [ - "q_axis = 0:nq-1\n", - "ω_axis = collect(ωlist_rpa)\n", - "\n", - "# ── Panel 1: Π₀ vs best Wynn estimate ─────────────────────────────────────────\n", - "# chi_partial[1,...] = partial sum at K=0, i.e. bare Π₀(q,ω)\n", - "# chi_wynn[n_wynn,...] = highest Padé estimate, using K_max+1 terms total\n", - "p_pi0 = heatmap(q_axis, ω_axis, chi_partial[1, :, :];\n", - " title=\"Π₀(q,ω) [bare bubble]\",\n", - " xlabel=\"q\", ylabel=\"ω\", color=:inferno)\n", - "\n", - "p_rpa = heatmap(q_axis, ω_axis, chi_wynn[n_wynn, :, :];\n", - " title=\"χ_RPA(q,ω) [Wynn ε_$(2n_wynn), $(K_max+1) terms]\",\n", - " xlabel=\"q\", ylabel=\"ω\", color=:inferno)\n", - "\n", - "display(plot(p_pi0, p_rpa; layout=(1, 2), size=(860, 360),\n", - " plot_title=\"RPA susceptibility (U = $U_hub, N = $(H1.N))\"))\n", - "\n", - "# ── Panel 2: Convergence of partial sums vs Wynn estimates ────────────────────\n", - "# Total spectral weight Σ_{q,ω} |−Im χ| at each approximation level\n", - "spec_partial = [sum(abs.(chi_partial[k+1, :, :])) for k in 0:K_max]\n", - "spec_wynn = [sum(abs.(chi_wynn[m, :, :])) for m in 1:n_wynn]\n", - "\n", - "p_conv = plot(0:K_max, spec_partial;\n", - " label=\"Partial sum K\", lw=2, marker=:circle, color=:crimson,\n", - " xlabel=\"Order K (number of terms = K+1)\",\n", - " ylabel=\"Σ_{q,ω} |−Im χ|\",\n", - " title=\"Convergence: Neumann series vs Wynn ε-acceleration\")\n", - "\n", - "for m in 1:n_wynn\n", - " scatter!(p_conv, [2m], [spec_wynn[m]];\n", - " label=\"Wynn ε_$(2m) ($(2m+1) terms)\",\n", - " markersize=9, markershape=:star5)\n", - "end\n", - "\n", - "hline!(p_conv, [spec_wynn[end]]; lw=1, ls=:dot, color=:gray,\n", - " label=\"Wynn ε_$(2n_wynn) (best estimate)\")\n", - "\n", - "display(plot(p_conv; size=(680, 400)))" - ] + "execution_count": null, + "outputs": [] }, { "cell_type": "code", "execution_count": null, - "id": "b7771ce7", + "id": "6f997278", "metadata": {}, "outputs": [], - "source": [] + "source": "nq = H1.N\nq_axis = 0:nq-1\nomega_axis = collect(omegalist_rpa)\n\n# ── Panel 1: Π₀ vs best Wynn estimate ─────────────────────────────────────────\n# chi_partial[1,...] = partial sum at K=0, i.e. bare Π₀(q,ω)\n# chi_wynn[n_wynn,...] = highest Padé estimate, using K_max+1 terms total\np_pi0 = heatmap(q_axis, omega_axis, chi_partial[1, :, :];\n title=\"Π₀(q,ω) [bare bubble]\",\n xlabel=\"q\", ylabel=\"ω\", color=:inferno)\n\np_rpa = heatmap(q_axis, omega_axis, chi_wynn[n_wynn, :, :];\n title=\"χ_RPA(q,ω) [Wynn ε_$(2n_wynn), $(K_max+1) terms]\",\n xlabel=\"q\", ylabel=\"ω\", color=:inferno)\n\ndisplay(plot(p_pi0, p_rpa; layout=(1, 2), size=(860, 360),\n plot_title=\"RPA susceptibility (U = $U_hub, N = $(H1.N))\"))\n\n# ── Panel 2: Convergence of partial sums vs Wynn estimates ────────────────────\n# Total spectral weight Σ_{q,ω} |−Im χ| at each approximation level\nspec_partial = [sum(abs.(chi_partial[k+1, :, :])) for k in 0:K_max]\nspec_wynn = [sum(abs.(chi_wynn[m, :, :])) for m in 1:n_wynn]\n\np_conv = plot(0:K_max, spec_partial;\n label=\"Partial sum K\", lw=2, marker=:circle, color=:crimson,\n xlabel=\"Order K (number of terms = K+1)\",\n ylabel=\"Σ_{q,ω} |−Im χ|\",\n title=\"Convergence: Neumann series vs Wynn ε-acceleration\")\n\nfor m in 1:n_wynn\n scatter!(p_conv, [2m], [spec_wynn[m]];\n label=\"Wynn ε_$(2m) ($(2m+1) terms)\",\n markersize=9, markershape=:star5)\nend\n\nhline!(p_conv, [spec_wynn[end]]; lw=1, ls=:dot, color=:gray,\n label=\"Wynn ε_$(2n_wynn) (best estimate)\")\n\ndisplay(plot(p_conv; size=(680, 400)))" } - ] + ], + "metadata": { + "kernelspec": { + "display_name": "Julia 1.12", + "language": "julia", + "name": "julia-1.12" + }, + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.12.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 } \ No newline at end of file From 22fb117119aa66f9c214a71278d9346ddd95ecb6 Mon Sep 17 00:00:00 2001 From: Anouar Moustaj Date: Fri, 12 Jun 2026 14:49:46 +0300 Subject: [PATCH 3/7] include GPU_tk.jl --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 1fd0376..0661491 100644 --- a/.gitignore +++ b/.gitignore @@ -13,7 +13,6 @@ src/RSI_tk.jl RhombGraph.ipynb logo_tensorBinding.svg examples/nontracked/ -GPU_tk.jl Conductivity_tk.jl TensorBinding_JOSS_AI.txt docs/src/joss.txt From 3b66bd9fcb38a0d2f1cde4f4868191e4fced2a42 Mon Sep 17 00:00:00 2001 From: Anouar Moustaj Date: Fri, 12 Jun 2026 14:50:07 +0300 Subject: [PATCH 4/7] Add GPU_tk.jl: GPU-accelerated toolkit for large production runs GPU-accelerated counterparts to the CPU solvers, for systems/Ncheb too large to run on CPU. Chebyshev recurrence, MPO/MPS products, Hadamard products, projections, and real-space/QFT sampling run on GPU (NDTensors CUDA backend, ComplexF32); setup, Tucker SVDs, k-/spatial-group bookkeeping, and final scalar accumulation stay on CPU. Entry points: - get_conductivity_ward_gpu / get_conductivity_cheb2d_gpu - sigma(omega) - get_bands_gpu - A(k,omega) band structure - get_ldos_spatial_gpu - A(r,omega) real-space LDOS, with :point/:block sampling and sublattice :average/:resolve modes - get_dos_stochastic_gpu - stochastic-trace DOS - get_exciton_ldos_spatial_gpu - A(X,omega) exciton LDOS - get_C_gpu - real-space Chern marker - scf_magnetic_hubbard_gpu, get_scf_magnetization_gpu, get_scf_bands_gpu - collinear magnetic Hubbard SCF loop and post-hoc magnetization/band diagnostics CUDA stays an optional dependency: CUDA is resolved at runtime via Base.loaded_modules (no `using CUDA` at load time, not in Project.toml), so including this file does not force CUDA on users who never call *_gpu functions. --- src/GPU_tk.jl | 2378 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2378 insertions(+) create mode 100644 src/GPU_tk.jl diff --git a/src/GPU_tk.jl b/src/GPU_tk.jl new file mode 100644 index 0000000..2e7ffc0 --- /dev/null +++ b/src/GPU_tk.jl @@ -0,0 +1,2378 @@ +# ============================================================ +# GPU_tk.jl — GPU production toolkit for TensorBinding +# ============================================================ +# +# This file is the GPU companion to the CPU solvers in src/solvers/ and +# src/core/Utils.jl. Its purpose is to make LARGE PRODUCTION RUNS (big L, +# many Chebyshev moments, fine spatial/k grids — the regime targeted by the +# SLURM scripts in examples/nontracked/APSOS/) tractable by moving the +# dominant Chebyshev-recurrence / MPO-product cost onto a GPU via the +# NDTensors CUDA backend. Every function here mirrors a CPU counterpart with +# a `_gpu` suffix and (unless its own docstring says otherwise) accepts the +# same keyword arguments and returns the same shape of result. +# +# REQUIREMENTS +# using CUDA # must be loaded *before* calling any *_gpu function +# include("TensorBinding.jl"); using .TensorBinding +# +# σ = TensorBinding.get_conductivity_ward_gpu(H, H, ωlist; Ncheb=80, ...) +# +# ENTRY POINTS (each documented in its own docstring below) +# Conductivity (experimental/not working yet) +# get_conductivity_ward_gpu(H1, H2, ωlist; ...) — Ward-identity σ(ω) (Experimental/not working yet) +# get_conductivity_cheb2d_gpu(H1, H2, ωlist; ...) — 2D-Chebyshev σ(ω) (Experimental/not working yet) + +# Spectral / spatial maps +# get_bands_gpu(H, Ncheb, ω; kpath=..., ...) — A(k,ω) bands +# get_ldos_spatial_gpu(H, Ncheb, ω; reduce=..., ...) — A(r,ω) real-space LDOS +# (:point or :block sampling, +# sublattice :average/:resolve) +# get_dos_stochastic_gpu(H, Ncheb, ω; ...) — stochastic-trace DOS +# get_exciton_ldos_spatial_gpu(H, Ncheb, ω; ...) — A(X,ω) exciton LDOS +# Topology +# get_C_gpu(H, xfunc, yfunc; ...) — real-space Chern marker +# Magnetic Hubbard SCF +# scf_magnetic_hubbard_gpu(H0, U; ...) — collinear mean-field loop +# get_scf_magnetization_gpu(res; ...) — post-hoc (r) map +# get_scf_bands_gpu(res, Ncheb, ω; ...) — post-hoc spin-summed bands +# +# GPU/CPU SPLIT (general pattern — see each function's docstring for specifics) +# GPU : Chebyshev recurrence (KPM_Tn_gpu / inline recursions), weighted MPO +# sums (_weighted_mpo_sum_gpu), MPO×MPO and MPO×MPS products + +# truncation, Hadamard products (_hadamard_mpo_gpu), projections +# (_project_aux_gpu), diagonal extraction +# (extract_diagonal_to_mps_gpu) and real-space/QFT sampling. +# CPU : one-time setup (Hamiltonian/operator construction, Tucker SVDs, +# k-/spatial-group bookkeeping, KPM weight matrices, McWeeny initial +# guesses) and the final per-ω scalar accumulation. +# +# PRECISION (WHY F32) +# The NDTensors GPU backend requires Float32 storage, so every MPO/MPS +# moved to GPU via _to_gpu_mpo / _to_gpu_mps is first cast to ComplexF32; +# results moved back via _to_cpu_mpo / _to_cpu_mps are promoted to +# ComplexF64. This is fine for the observables computed here, but +# ComplexF32 eigendecomposition can produce NaN at very tight `cutoff` on +# large systems — functions on this path warn (without altering the value) +# if `cutoff` is below a recommended floor, typically 1e-4 to 1e-6 +# depending on the routine. +# +# REAL-SPACE / BIT-ORDERING CONVENTIONS +# Real-space sampling (_eval_mps_bigendian_gpu, _eval_block_mps_gpu, used by +# get_ldos_spatial_gpu and get_scf_magnetization_gpu) encodes the position +# index MSB-first across the site list, matching the CPU +# eval_mps/binary_to_MPS convention exactly. The QFT/bands path +# (_eval_diag_mps_gpu, used by get_bands_gpu) instead uses the legacy +# LSB-first convention required by the quantics-Fourier MPO. The two are +# not interchangeable — see spatial_sampling_plan in Utils.jl for how the +# shared sampler keeps them straight. +# +# PRODUCTION SCRIPTS +# See examples/nontracked/APSOS/*.jl + *.sh for the SLURM driver scripts +# that exercise this toolkit (magnetic-Hubbard SCF + magnetization/bands +# maps, spatial LDOS with the :point/:block samplers, ...). +# ============================================================ + + +# ── 1. CUDA bridge (no hard dependency) ───────────────────────────────────── + +const _TB_CUDA = Ref{Union{Module,Nothing}}(nothing) + +function _tb_cuda_module() + if _TB_CUDA[] === nothing + id = Base.PkgId(Base.UUID("052768ef-5323-5732-b1bb-66c8b64840ba"), "CUDA") + _TB_CUDA[] = get(Base.loaded_modules, id, nothing) + end + return _TB_CUDA[] +end + +function _check_gpu(caller::String = "") + m = _tb_cuda_module() + if m === nothing + tag = isempty(caller) ? "" : " (called from $caller)" + error(""" +TensorBinding$tag: GPU functions require CUDA.jl. +Load it before calling any *_gpu function: + + using CUDA + +Install once with: ] add CUDA +""") + end +end + +function _gpu_gc!() + m = _tb_cuda_module() + m === nothing && return + m.synchronize() + GC.gc(false) + m.reclaim() +end + + +# ── 2. MPO type conversion ─────────────────────────────────────────────────── + +# CPU F64 → CPU F32 (prerequisite before cu()) +function _mpo_to_f32(mpo::MPO) + return MPO([ + let idx = inds(mpo[i]) + ITensor(ComplexF32.(Array(mpo[i], idx...)), idx) + end + for i in 1:length(mpo) + ]) +end + +# CPU F64 → GPU F32 +function _to_gpu_mpo(mpo::MPO) + _check_gpu("_to_gpu_mpo") + return _tb_cuda_module().cu(_mpo_to_f32(mpo)) +end + +# CPU MPS → GPU F32 MPS +function _to_gpu_mps(mps::MPS) + _check_gpu("_to_gpu_mps") + m = _tb_cuda_module() + result = similar(mps) + for j in 1:length(mps) + idx = inds(mps[j]) + arr = Array(mps[j], idx...) # CPU: typeassert safe + result[j] = ITensors.itensor(m.cu(ComplexF32.(arr)), idx...) + end + return result +end + +function _to_cpu_mps(mps::MPS) + result = similar(mps) + for j in 1:length(mps) + T = mps[j] + s = NDTensors.storage(ITensors.tensor(T)) + arr_cpu = Array(NDTensors.data(s)) + result[j] = ITensors.itensor(ComplexF64.(arr_cpu), inds(T)...) + end + return result +end + +# GPU F32 → CPU F64 (called after Hadamard products) +# Array(::ITensor, inds...) typeasserts the result as Array{T,N}, which fails +# for GPU tensors (CuArray ≠ Array). Go through storage() → raw CuArray → +# Array (bulk copy) → itensor (no typeassert). +function _to_cpu_mpo(mpo::MPO) + result = similar(mpo) + for j in 1:length(mpo) + T = mpo[j] + s = NDTensors.storage(ITensors.tensor(T)) # Dense{F32, CuArray} + arr_cpu = Array(NDTensors.data(s)) # CuArray → Array{F32,1} + result[j] = ITensors.itensor(ComplexF64.(arr_cpu), inds(T)...) + end + return result +end + + +# Build the two QFT operators for the given Hamiltonian and move them to GPU F32. +# Call once before the Tucker pairs loop so the build cost is amortised across +# all r_m × r_n pairs. +function _build_qft_ops_gpu(H::TBHamiltonian) + pos_s = _pos_sites(H) + R = length(pos_s) + FTirev_cpu = _embed_in_full_sites(H, fix_sites( + MPO(TCI.reverse(QuanticsTCI.quanticsfouriermpo(R; sign=-1.0, normalize=true))), pos_s)) + FTrev_cpu = _embed_in_full_sites(H, fix_sites( + MPO(TCI.reverse(QuanticsTCI.quanticsfouriermpo(R; sign=+1.0, normalize=true))), pos_s)) + return _to_gpu_mpo(FTirev_cpu), _to_gpu_mpo(FTrev_cpu) +end + +# Apply the QFT sandwich U·W·U† on GPU using pre-built GPU QFT operators. +# Returns a GPU F32 MPO. +function _apply_qft_conj_gpu(W::MPO, FTirev_gpu::MPO, FTrev_gpu::MPO; + tol::Real = 1e-9, maxdim::Int = 100) + Op1 = apply(W, FTirev_gpu; cutoff=tol, maxdim=maxdim) + Op2 = apply(swapprime(FTrev_gpu, 0 => 1), Op1; cutoff=tol, maxdim=maxdim) + return ITensorMPS.truncate!(Op2; cutoff=tol, maxdim=maxdim) +end + +# ── 3. GPU-safe primitives ─────────────────────────────────────────────────── + +# delta() produces a DiagTensor{Float64} (CPU). When contracted with a +# Dense{ComplexF32} GPU tensor, NDTensors promotes the output to ComplexF64 +# and the _contract! dispatch fails (all three tensors must share El). +# Fix: materialise the delta as a dense F32 GPU tensor. +function _make_delta_gpu(i::Index, j::Index, k::Index) + d_dense = dense(delta(i, j, k)) # DiagStorage → DenseStorage + idx = inds(d_dense) + arr = Array(d_dense, idx...) + return _tb_cuda_module().cu(ITensor(Float32.(arr), idx)) +end + +# GPU-safe Hadamard product: identical logic to _hadamard_mpo but uses +# _make_delta_gpu so all contractions stay within {ComplexF32, GPU}. +function _hadamard_mpo_gpu(A::MPO, B::MPO, out_sites::Vector{<:Index}; + maxdim::Int = typemax(Int), cutoff::Real = 0.0) + L = length(A) + @assert length(B) == L && length(out_sites) == L + sindsA = siteinds(A) + sindsB = siteinds(B) + + links_B_old = Vector{Index}(undef, max(L - 1, 0)) + links_B_new = Vector{Index}(undef, max(L - 1, 0)) + for b in 1:L-1 + lB = only(commoninds(B[b], B[b+1])) + links_B_old[b] = lB + links_B_new[b] = sim(lB) + end + + tens = Vector{ITensor}(undef, L) + for n in 1:L + bra_A, ket_A = _bra_ket(sindsA[n]) + bra_B, ket_B = _bra_ket(sindsB[n]) + bra_out = prime(out_sites[n]) + ket_out = out_sites[n] + bra_B_f = sim(bra_B) + ket_B_f = sim(ket_B) + old_inds = Index[bra_B, ket_B] + new_inds = Index[bra_B_f, ket_B_f] + n > 1 && push!(old_inds, links_B_old[n-1]); n > 1 && push!(new_inds, links_B_new[n-1]) + n < L && push!(old_inds, links_B_old[n]); n < L && push!(new_inds, links_B_new[n]) + B_n = replaceinds(B[n], old_inds, new_inds) + # Contract delta tensors into A *before* multiplying B_n to avoid an + # 8D intermediate. Old order: (A*B)→8D→*δ→6D→*δ→5D. + # New order: (A*δ_bra*δ_ket)→6D→*B_n→6D. + # The 8D path overflows int32 CUDA indexing for maxdim ≳ 115 + # (16·χ⁴ > 2³¹ when χ > ~115), causing ERROR_ILLEGAL_ADDRESS. + W = A[n] * _make_delta_gpu(bra_A, bra_B_f, bra_out) # 4D→5D + W = W * _make_delta_gpu(ket_A, ket_B_f, ket_out) # 5D→6D + W = W * B_n # 6D→6D + tens[n] = W + end + + if L == 1 + mpo = MPO(tens) + (maxdim < typemax(Int) || cutoff > 0.0) && ITensorMPS.truncate!(mpo; maxdim=maxdim, cutoff=cutoff) + return mpo + end + Cs = Vector{ITensor}(undef, L - 1) + for b in 1:L-1 + lA = only(commoninds(A[b], A[b+1])) + lB = links_B_new[b] + Cs[b] = combiner(lA, lB; tags="Link,l=$b") + end + tens[1] = tens[1] * Cs[1] + for n in 2:L-1 + tens[n] = tens[n] * Cs[n-1] * Cs[n] + end + tens[L] = tens[L] * Cs[L-1] + mpo = MPO(tens) + (maxdim < typemax(Int) || cutoff > 0.0) && ITensorMPS.truncate!(mpo; maxdim=maxdim, cutoff=cutoff) + return mpo +end + +# Evaluate an MPS element at bit-index `idx` entirely on GPU using the legacy +# LSB-first convention used by the GPU QFT/bands path. +# +# Basis vectors are built as explicit dense arrays matching the element type of +# A so that the contraction is GPU×GPU with a consistent dtype throughout. +function _eval_diag_mps_gpu(A::MPS, idx::Int) + cuda = _tb_cuda_module() + s = siteinds(A) + ElT = eltype(A[1]) + acc = cuda.cu(ITensor(one(ElT))) + for i in 1:length(s) + b = (idx >> (i - 1)) & 1 + v_arr = zeros(ElT, dim(s[i])) + v_arr[b + 1] = one(real(ElT)) + v = cuda.cu(ITensor(v_arr, s[i])) + acc *= A[i] * v + end + return real(scalar(acc)) +end + +# Real-space MPS element evaluation on GPU, matching binary_to_MPS/eval_mps: +# `idx` is encoded big-endian across the site order. +function _eval_mps_bigendian_gpu(A::MPS, idx::Int) + cuda = _tb_cuda_module() + s = siteinds(A) + ElT = eltype(A[1]) + n = length(s) + acc = cuda.cu(ITensor(one(ElT))) + for i in 1:n + b = (idx >> (n - i)) & 1 + v_arr = zeros(ElT, dim(s[i])) + v_arr[b + 1] = one(real(ElT)) + v = cuda.cu(ITensor(v_arr, s[i])) + acc *= A[i] * v + end + return real(scalar(acc)) +end + +# Block-integrated MPS element on GPU (reduce=:block): sum the profile over one +# coarse block by tracing out the within-block position bits and pinning the +# block to the coarse pixel (ixp, iyp). The big-endian position site order is +# [iy_MSB..iy_LSB, ix_MSB..ix_LSB] (sites 1..Ly carry iy, Ly+1..L carry ix), so +# we keep the top b bits of iy (sites 1..b) and top a bits of ix (sites +# Ly+1..Ly+a) as onehot, and contract every lower bit with [1,1] (a sum). +function _eval_block_mps_gpu(A::MPS, ixp::Int, iyp::Int, + a::Int, b::Int, Lx::Int, Ly::Int) + cuda = _tb_cuda_module() + s = siteinds(A) + ElT = eltype(A[1]) + L = Lx + Ly + acc = cuda.cu(ITensor(one(ElT))) + for i in 1:L + v_arr = zeros(ElT, dim(s[i])) + if i <= b # keep: iy block bit (b - i) + v_arr[((iyp >> (b - i)) & 1) + 1] = one(real(ElT)) + elseif i <= Ly # sum: iy within-block bit + v_arr .= one(real(ElT)) + elseif i <= Ly + a # keep: ix block bit (a - (i - Ly)) + v_arr[((ixp >> (a - (i - Ly))) & 1) + 1] = one(real(ElT)) + else # sum: ix within-block bit + v_arr .= one(real(ElT)) + end + v = cuda.cu(ITensor(v_arr, s[i])) + acc *= A[i] * v + end + return real(scalar(acc)) +end + +""" + extract_diagonal_to_mps_gpu(M::MPO) -> MPS + +GPU-resident analogue of `extract_diagonal_to_mps`. `M` is expected to already +be a GPU ComplexF32 MPO (e.g. a Chebyshev moment from `KPM_Tn_gpu`, after +`_apply_qft_conj_gpu` and/or `_project_aux_gpu`); the returned MPS is also on +GPU (ComplexF32). +""" +# extract_diagonal_to_mps (in RPA_tk.jl) uses plain onehot() which returns a +# CPU DiagBlockSparse tensor. Contracting a GPU ComplexF32 MPO tensor with a +# CPU onehot fails (GPU×CPU mismatch). Here we wrap each onehot call with +# cu() so NDTensors resolves the contraction entirely on the GPU. +# The zero ITensor `res` has no committed storage, so the first `+=` with a +# GPU ComplexF32 result promotes it to the correct GPU type. +function extract_diagonal_to_mps_gpu(M::MPO)::MPS + cuda = _tb_cuda_module() + N = length(M) + new_tensors = Vector{ITensor}(undef, N) + for i in 1:N + t = M[i] + s2, s1 = siteinds(M, i) # s2 = bra (primed), s1 = ket + d_s = dim(s1) + v_inds = uniqueinds(t, s1, s2) + + res = ITensor(v_inds..., s1) # zero tensor; type determined by first += + for v in 1:d_s + slice = t * cuda.cu(onehot(s1 => v)) * cuda.cu(onehot(s2 => v)) + res += slice * cuda.cu(onehot(s1 => v)) + end + new_tensors[i] = res + end + return MPS(new_tensors) +end + +function _is_gpu_tensor(T::ITensor) + storage = NDTensors.storage(ITensors.tensor(T)) + data = try + NDTensors.data(storage) + catch + return false + end + return occursin("CuArray", string(typeof(data))) +end + +function _ensure_gpu_mpo(W::MPO; caller::String = "_ensure_gpu_mpo") + flags = [_is_gpu_tensor(W[i]) for i in 1:length(W)] + all(flags) && return W + any(flags) && error("$caller: mixed CPU/GPU MPO tensors are not supported.") + return _to_gpu_mpo(W) +end + +""" + density_profile_from_dm_gpu(density_mpo, sites=nothing; mode=:direct) -> MPS + +GPU-resident analogue of `density_profile_from_dm`. If `density_mpo` is a CPU +MPO it is uploaded once; if it is already on GPU it is used in place. The +returned profile is a GPU MPS. `mode=:complement` returns `1 - diag(D)` on GPU. +""" +function density_profile_from_dm_gpu(density_mpo::MPO, sites=nothing; + mode::Symbol = :direct, + maxdim::Int = 100, + cutoff::Real = 1e-8) + _check_gpu("density_profile_from_dm_gpu") + dm_gpu = _ensure_gpu_mpo(density_mpo; caller="density_profile_from_dm_gpu") + diag_mps = extract_diagonal_to_mps_gpu(dm_gpu) + mode === :direct && return diag_mps + if mode === :complement + profile_sites = sites === nothing ? collect(siteinds(diag_mps)) : collect(sites) + one_mps = _to_gpu_mps(constant_mps(profile_sites, 1.0)) + return +(one_mps, -diag_mps; maxdim=maxdim, cutoff=cutoff) + end + error("Unsupported density extraction mode :$mode. Use :direct or :complement.") +end + +function _mps_to_diagonal_mpo_gpu(mps::MPS, sites)::MPO + N = length(mps) + mpo_tensors = Vector{ITensor}(undef, N) + for i in 1:N + mps_t = mps[i] + old_s = if N == 1 + only(siteinds(mps)) + elseif i == 1 + uniqueind(mps_t, mps[i+1]) + elseif i == N + uniqueind(mps_t, mps[i-1]) + else + uniqueind(mps_t, mps[i-1], mps[i+1]) + end + s = sites[i] + s_temp = Index(dim(s), "temp") + mpo_tensors[i] = replaceind(mps_t, old_s => s_temp) * + _make_delta_gpu(s_temp, s, s') + end + return MPO(mpo_tensors) +end + +function _rms_error_gpu(a::MPS, b::MPS; cutoff::Real = 1e-12) + diff = +(a, -1.0 * b; cutoff=Float64(cutoff)) + n = prod(dim(s) for s in siteinds(a)) + return sqrt(abs(real(inner(diff', diff))) / n) +end + +function _local_hartree_from_density_gpu(rho::MPS, sites, U::Number, bg::MPS; + maxdim::Int, cutoff::Real) + coeff = +(rho, -1.0 * bg; maxdim=maxdim, cutoff=Float64(cutoff)) + return _mps_to_diagonal_mpo_gpu(U * coeff, sites) +end + +function _hartree_mpo_from_density_gpu(rho::MPS, interaction_op::MPO, sites, bg::MPS; + maxdim::Int, cutoff::Real) + coeff = +(rho, -1.0 * bg; maxdim=maxdim, cutoff=Float64(cutoff)) + coeff_mps = apply(interaction_op, coeff; maxdim=maxdim, cutoff=Float64(cutoff)) + return _mps_to_diagonal_mpo_gpu(coeff_mps, sites) +end + +# Project one auxiliary site out of a GPU MPO. +# Mirrors project_aux (CPU) but builds a dense ComplexF32 projector on GPU so +# every contraction stays on the GPU. The contracted site is absorbed into the +# neighbouring site, returning an MPO with one fewer site. +# +# setelt() produces a DiagBlockSparse ITensor that cu() leaves on CPU — we +# therefore build the |sec> (Tn_list, scale, center) + +GPU version of `KPM_Tn`. Moves the identity and scaled Hamiltonian MPOs to +GPU (ComplexF32) before the recurrence so all Tn tensors stay on GPU. +Requires `using CUDA`. + +If `keep_indices` is provided (a `Set{Int}`, 1-based into the returned vector +where index 1 = T_0, 2 = T_1, …), only those Tns are retained in memory. +All other slots are set to `nothing`. The recurrence itself always runs to +completion — `keep_indices` only controls which results are stored. +""" +function KPM_Tn_gpu(H_mpo::MPO, N::Int, sites; + scale::Union{Real,Nothing} = nothing, + center::Real = 0.0, + maxdim::Int = 40, + dmrg_nsweeps::Int = 5, + dmrg_maxdim = [10, 20, 40], + dmrg_linkdim::Int = 4, + cutoff::Real = 1e-8, + keep_indices::Union{Nothing,AbstractSet{Int}} = nothing, + verbose::Bool = true) + + _check_gpu("KPM_Tn_gpu") + + if isnothing(scale) + scale, center = _estimate_spectral_bounds(H_mpo, sites; + dmrg_nsweeps = dmrg_nsweeps, + dmrg_maxdim = dmrg_maxdim, + dmrg_linkdim = dmrg_linkdim) + end + + I_mpo = MPO(sites, "Id") + Ham_n = (1 / scale) * +(H_mpo, (-center) * I_mpo; cutoff = cutoff) + + I_mpo = _to_gpu_mpo(I_mpo) + Ham_n = _to_gpu_mpo(Ham_n) + + keep = keep_indices + T_k_minus_2 = I_mpo + T_k_minus_1 = Ham_n + Tn_list = Vector{Union{MPO,Nothing}}(undef, N + 1) + Tn_list[1] = (keep === nothing || 1 ∈ keep) ? T_k_minus_2 : nothing + Tn_list[2] = (keep === nothing || 2 ∈ keep) ? T_k_minus_1 : nothing + + for k in 3:N+1 + T_k = +(2 * apply(Ham_n, T_k_minus_1; cutoff = cutoff), + -T_k_minus_2; maxdim = maxdim) + T_k = ITensorMPS.truncate!(T_k; cutoff = cutoff) + Tn_list[k] = (keep === nothing || k ∈ keep) ? T_k : nothing + T_k_minus_2 = T_k_minus_1 + T_k_minus_1 = T_k + _gpu_gc!() + if verbose && (k % 5 == 0 || k == N+1) + println(" [gpu] T_$((k-1)) maxlinkdim=$(ITensorMPS.maxlinkdim(T_k))") + end + end + + return Tn_list, scale, center +end + + +# ── 5. Shared Tucker component builder ────────────────────────────────────── + +# Computes C_tuck, B_tuck, A_tuck, E_tuck fully on GPU. +# All inputs (Tn1, Tn2, P1_gpu, P2_gpu) are expected to be GPU F32 MPOs. +function _build_tucker_components_gpu(Tn1, Tn2, P1_gpu, P2_gpu; + U_m, V_n, r_m, r_n, + maxdim, cutoff) + C_tuck = [_weighted_mpo_sum_gpu(U_m[:, s1], Tn1; maxdim=maxdim, cutoff=cutoff) + for s1 in 1:r_m] + B_tuck = [_weighted_mpo_sum_gpu(conj.(V_n[:, s2]), Tn2; maxdim=maxdim, cutoff=cutoff) + for s2 in 1:r_n] + A_tuck = [isnothing(C_tuck[s1]) ? nothing : + ITensorMPS.truncate!(apply(C_tuck[s1], P1_gpu; maxdim=maxdim, cutoff=cutoff); cutoff=cutoff) + for s1 in 1:r_m] + E_tuck = [isnothing(B_tuck[s2]) ? nothing : + ITensorMPS.truncate!(apply(B_tuck[s2], P2_gpu; maxdim=maxdim, cutoff=cutoff); cutoff=cutoff) + for s2 in 1:r_n] + return C_tuck, B_tuck, A_tuck, E_tuck +end + + +# ── 6. GPU entry points ────────────────────────────────────────────────────── + +""" + get_conductivity_ward_gpu(H1, H2, ωlist; kwargs...) -> Vector{ComplexF64} + +GPU-accelerated version of `get_conductivity_ward`. + +GPU handles: Chebyshev recurrence, density matrix transfer, weighted MPO sums, + MPO×MPO multiplications, Hadamard products. +CPU handles: replace_sites, QFT sandwich, inner products, per-ω accumulation. + +All kwargs are identical to `get_conductivity_ward` (threading is removed — +use julia -t N at the process level instead). +""" +function get_conductivity_ward_gpu(H1::TBHamiltonian, H2::TBHamiltonian, + ωlist::AbstractVector{<:Real}; + q_int::Int = 1, + Lx::Int = H1.L ÷ 2, + q_sq::Union{Real,Nothing} = nothing, + Ncheb::Int = 50, + maxdim::Int = 200, + cutoff::Real = 1e-8, + ϵF::Real = 0.0, + P_method::Symbol = :purification, + purify_method::Symbol = :mcweeny, + purify_maxdim::Int = 40, + purify_maxiters::Int = 30, + purify_tol::Float64 = 1e-5, + η::Real = 1e-3, + coeff_tol::Real = 1e-12, + kernel::Symbol = :jackson, + tucker_tol::Real = 1e-3, + tucker_maxrank::Int = 20, + hooi_iters::Int = 3, + qft_tol::Real = 1e-9, + qft_maxdim::Int = 100, + verbose::Bool = false) + + _check_gpu("get_conductivity_ward_gpu") + H1.geometry === nothing && error("get_conductivity_ward_gpu: H1.geometry is not set") + L1 = H1.L; L2 = H2.L + @assert L1 == L2 "get_conductivity_ward_gpu: H1 and H2 must have the same L" + nω = length(ωlist) + + if q_sq === nothing + q_vec, q_sq_val = _q_int_to_physical(H1, q_int; Lx=Lx) + q_sq_val = Float64(q_sq_val) + verbose && println("ward_gpu: q_int=$q_int → |q|²=$(round(q_sq_val; digits=6))") + else + q_sq_val = Float64(q_sq) + verbose && println("ward_gpu: q_int=$q_int, |q|²=$(round(q_sq_val; digits=6)) (provided)") + end + (isnan(q_sq_val) || q_sq_val < 1e-14) && + error("get_conductivity_ward_gpu: q_sq is NaN or ≈ 0. Provide q_sq explicitly.") + + v_q = _density_vertex_mps(H1, q_int) + + _ensure_scale!(H1); _ensure_scale!(H2) + scale1 = H1.scale; center1 = H1.center + scale2 = H2.scale; center2 = H2.center + + # ── 2D GF coefficients (CPU, small dense matrices) ─────────────────────── + # Computed BEFORE the Tn recurrence so we know which (m,n) pairs matter. + N = Ncheb + 1 + verbose && println("ward_gpu: 2D GF coefficients for $nω frequencies (CPU)...") + C_all = [chebyshev2d_gf_coeffs(ω, scale1, center1, scale2, center2, η, N) + for ω in ωlist] + if kernel == :jackson + g_jk = _jackson_kernel(N) + G_jk = g_jk * g_jk' + C_all = [G_jk .* C for C in C_all] + elseif kernel != :none + error("get_conductivity_ward_gpu: unknown kernel=$kernel") + end + + # Active (m,n) pairs: those where max_ω |C_mn(ω)| exceeds the threshold. + # This determines which Tns we need — no Tucker SVD required. + max_C_abs = reduce((A, B) -> max.(A, abs.(B)), C_all; init=zeros(N, N)) + pair_tol = tucker_tol * maximum(max_C_abs) + active_pairs = [(m, n) for m in 1:N for n in 1:N if max_C_abs[m, n] > pair_tol] + active_m_set = Set(m for (m, _) in active_pairs) + active_n_set = H1 === H2 ? active_m_set : Set(n for (_, n) in active_pairs) + verbose && println("ward_gpu: $(length(active_pairs)) active (m,n) pairs " * + "($(length(active_m_set)) unique m, $(length(active_n_set)) unique n)") + + # ── Chebyshev recurrence on GPU (only active indices stored) ───────────── + verbose && println("ward_gpu: Chebyshev recurrence on GPU (Ncheb=$Ncheb)...") + Tn1, _, _ = KPM_Tn_gpu(H1.mpo, Ncheb, H1.sites; + scale=scale1, center=center1, + maxdim=maxdim, cutoff=cutoff, + keep_indices=active_m_set, verbose=false) + Tn2 = H1 === H2 ? Tn1 : KPM_Tn_gpu(H2.mpo, Ncheb, H2.sites; + scale=scale2, center=center2, + maxdim=maxdim, cutoff=cutoff, + keep_indices=active_n_set, verbose=false)[1] + + # ── Density matrices: purify on CPU, move to GPU ────────────────────────── + verbose && println("ward_gpu: density matrices (CPU purification → GPU)...") + P1_gpu = _get_density_matrix_gpu(H1, ϵF, P_method, Ncheb, maxdim, cutoff, + purify_method, purify_maxdim, purify_maxiters, + purify_tol, verbose) + P2_gpu = H1 === H2 ? P1_gpu : _get_density_matrix_gpu(H2, ϵF, P_method, Ncheb, maxdim, cutoff, + purify_method, purify_maxdim, purify_maxiters, + purify_tol, verbose) + + out_sites = [Index(dim(s), "Bubble,n=$i") for (i, s) in enumerate(H1.sites)] + + # ── Cache T_m·P1 for each active m, T_n·P2 for each active n ──────────── + verbose && println("ward_gpu: caching T_m·P and T_n·P on GPU...") + A_cache = Dict{Int,MPO}() # A_cache[m] = T_m · P1 + for m in active_m_set + isnothing(Tn1[m]) && continue + A_cache[m] = ITensorMPS.truncate!( + apply(Tn1[m], P1_gpu; maxdim=maxdim, cutoff=cutoff); cutoff=cutoff) + _gpu_gc!() + end + E_cache = H1 === H2 ? A_cache : Dict{Int,MPO}() # E_cache[n] = T_n · P2 + if H1 !== H2 + for n in active_n_set + isnothing(Tn2[n]) && continue + E_cache[n] = ITensorMPS.truncate!( + apply(Tn2[n], P2_gpu; maxdim=maxdim, cutoff=cutoff); cutoff=cutoff) + _gpu_gc!() + end + end + + # ── Build QFT operators once ────────────────────────────────────────────── + verbose && println("ward_gpu: building QFT operators (CPU → GPU)...") + FTirev_gpu, FTrev_gpu = _build_qft_ops_gpu(H1) + v_q_gpu = _to_gpu_mps(v_q) + + # ── Per (m,n) pair: two independent Hadamard → QFT → inner products ─────── + # No weighted MPO sums are formed — f_mn = val_A − val_B is a scalar difference. + # This avoids the densitymatrix NaN from accumulating many F32 MPOs. + n_pairs = length(active_pairs) + verbose && println("ward_gpu: $n_pairs (m,n) pairs × 2 inner products (GPU)...") + f_direct = Dict{Tuple{Int,Int},ComplexF64}() + t0_inner = time() + + for (idx, (m, n)) in enumerate(active_pairs) + (isnothing(Tn1[m]) || isnothing(Tn2[n]) || + !haskey(A_cache, m) || !haskey(E_cache, n)) && continue + + # (T_m·P1) ⊙ T_n → QFT sandwich → ⟨v_q|·|v_q⟩ + had_A = _hadamard_mpo_gpu(A_cache[m], Tn2[n], out_sites; maxdim=maxdim, cutoff=cutoff) + D_A = _apply_qft_conj_gpu(replace_sites(had_A, H1.sites), FTirev_gpu, FTrev_gpu; + tol=qft_tol, maxdim=qft_maxdim) + val_A = ComplexF64(inner(v_q_gpu, D_A, v_q_gpu)) + _gpu_gc!() + + # T_m ⊙ (T_n·P2) → QFT sandwich → ⟨v_q|·|v_q⟩ + had_B = _hadamard_mpo_gpu(Tn1[m], E_cache[n], out_sites; maxdim=maxdim, cutoff=cutoff) + D_B = _apply_qft_conj_gpu(replace_sites(had_B, H1.sites), FTirev_gpu, FTrev_gpu; + tol=qft_tol, maxdim=qft_maxdim) + val_B = ComplexF64(inner(v_q_gpu, D_B, v_q_gpu)) + _gpu_gc!() + + f_direct[(m, n)] = val_A - val_B + + if verbose && (idx % 20 == 0 || idx == n_pairs) + println(" ($m,$n) [$idx/$n_pairs, $(round(time()-t0_inner; digits=1))s]") + end + end + + # ── Per-ω accumulation (CPU scalars only) ──────────────────────────────── + σ = zeros(ComplexF64, nω) + for (iω, ω) in enumerate(ωlist) + abs(ω) < 1e-14 && continue + chi_q = sum(C_all[iω][m, n] * get(f_direct, (m, n), zero(ComplexF64)) + for (m, n) in active_pairs; init=zero(ComplexF64)) + σ[iω] = chi_q * ω / q_sq_val + end + + verbose && println("ward_gpu: done.") + return σ +end + + +""" + get_conductivity_cheb2d_gpu(H1, H2, ωlist; kwargs...) -> Vector{Float64} + +GPU-accelerated version of `get_conductivity_cheb2d`. +Same GPU/CPU split as `get_conductivity_ward_gpu`. +""" +function get_conductivity_cheb2d_gpu(H1::TBHamiltonian, H2::TBHamiltonian, + ωlist::AbstractVector{<:Real}; + mu_a::Symbol = :x, + mu_b::Symbol = :x, + Ncheb::Int = 50, + maxdim::Int = 200, + cutoff::Real = 1e-8, + ϵF::Real = 0.0, + P_method::Symbol = :purification, + purify_method::Symbol = :mcweeny, + purify_maxdim::Int = 40, + purify_maxiters::Int = 30, + purify_tol::Float64 = 1e-5, + η::Real = 1e-3, + kernel::Symbol = :jackson, + tucker_tol::Real = 1e-3, + tucker_maxrank::Int = 20, + hooi_iters::Int = 3, + tol_dr::Real = 1e-8, + verbose::Bool = false) + + _check_gpu("get_conductivity_cheb2d_gpu") + H1.geometry === nothing && error("get_conductivity_cheb2d_gpu: H1.geometry is not set") + H2.geometry === nothing && error("get_conductivity_cheb2d_gpu: H2.geometry is not set") + nω = length(ωlist) + + _ensure_scale!(H1); _ensure_scale!(H2) + scale1 = H1.scale; center1 = H1.center + scale2 = H2.scale; center2 = H2.center + + # Current vertex (CPU — built once, not on the hot path) + verbose && println("conductivity_cheb2d_gpu: building current vertex...") + j_b = make_current_operator_mpo(H1, + make_displacement_mpo(H1, mu_b; tol=tol_dr); + maxdim=maxdim, cutoff=cutoff) + j_a = (mu_a == mu_b) ? j_b : + make_current_operator_mpo(H1, + make_displacement_mpo(H1, mu_a; tol=tol_dr); + maxdim=maxdim, cutoff=cutoff) + out_sites_jab = [Index(dim(s), "Jab,n=$i") for (i, s) in enumerate(H1.sites)] + J_ab = _hadamard_mpo(transpose_mpo(j_a), j_b, out_sites_jab; + maxdim=maxdim, cutoff=cutoff) + J_ab = replace_sites(J_ab, H1.sites) + ITensorMPS.truncate!(J_ab; cutoff=cutoff, maxdim=maxdim) + + # 2D GF + Tucker (CPU) — done first to derive active Tn indices + N = Ncheb + 1 + C_all = [chebyshev2d_gf_coeffs(ω, scale1, center1, scale2, center2, η, N) + for ω in ωlist] + if kernel == :jackson + g_jk = _jackson_kernel(N) + G_jk = g_jk * g_jk' + C_all = [G_jk .* C for C in C_all] + elseif kernel != :none + error("get_conductivity_cheb2d_gpu: unknown kernel=$kernel") + end + + T1 = hcat(C_all...); T2 = hcat([transpose(C) for C in C_all]...) + F1 = svd(T1); F2 = svd(T2) + r_m = min(tucker_maxrank, sum(F1.S .> tucker_tol * F1.S[1])) + r_n = min(tucker_maxrank, sum(F2.S .> tucker_tol * F2.S[1])) + U_m = F1.U[:, 1:r_m]; V_n = F2.U[:, 1:r_n] + for _ in 1:hooi_iters + Y = hcat([C * V_n for C in C_all]...); U_m = svd(Y).U[:, 1:r_m] + Z = hcat([C' * U_m for C in C_all]...); V_n = svd(Z).U[:, 1:r_n] + end + verbose && println("conductivity_cheb2d_gpu: Tucker r_m=$r_m, r_n=$r_n → $(r_m*r_n) pairs") + + row_norms_m = [norm(U_m[m, :]) for m in 1:N] + row_norms_n = [norm(V_n[n, :]) for n in 1:N] + active_m = Set(m for m in 1:N if row_norms_m[m] > tucker_tol * maximum(row_norms_m)) + active_n = H1 === H2 ? active_m : + Set(n for n in 1:N if row_norms_n[n] > tucker_tol * maximum(row_norms_n)) + verbose && println("conductivity_cheb2d_gpu: active Tns: $(length(active_m))/$N (H1)" * + (H1 === H2 ? " [reused for H2]" : ", $(length(active_n))/$N (H2)")) + + # Chebyshev moments on GPU (only active indices stored) + verbose && println("conductivity_cheb2d_gpu: Chebyshev recurrence on GPU (Ncheb=$Ncheb)...") + Tn1, _, _ = KPM_Tn_gpu(H1.mpo, Ncheb, H1.sites; + scale=scale1, center=center1, + maxdim=maxdim, cutoff=cutoff, + keep_indices=active_m, verbose=false) + Tn2 = H1 === H2 ? Tn1 : KPM_Tn_gpu(H2.mpo, Ncheb, H2.sites; + scale=scale2, center=center2, + maxdim=maxdim, cutoff=cutoff, + keep_indices=active_n, verbose=false)[1] + + # Density matrices: CPU purification → GPU + verbose && println("conductivity_cheb2d_gpu: density matrices (CPU → GPU)...") + P1_gpu = _get_density_matrix_gpu(H1, ϵF, P_method, Ncheb, maxdim, cutoff, + purify_method, purify_maxdim, purify_maxiters, + purify_tol, verbose) + P2_gpu = H1 === H2 ? P1_gpu : _get_density_matrix_gpu(H2, ϵF, P_method, Ncheb, maxdim, cutoff, + purify_method, purify_maxdim, purify_maxiters, + purify_tol, verbose) + + out_sites = [Index(dim(s), "Bubble,n=$i") for (i, s) in enumerate(H1.sites)] + + A_core = zeros(ComplexF64, r_m, r_n, nω) + for iω in 1:nω; A_core[:, :, iω] = U_m' * C_all[iω] * V_n; end + + # Tucker MPO components on GPU + verbose && println("conductivity_cheb2d_gpu: Tucker components (GPU)...") + C_tuck, B_tuck, A_tuck, E_tuck = _build_tucker_components_gpu( + Tn1, Tn2, P1_gpu, P2_gpu; + U_m=U_m, V_n=V_n, r_m=r_m, r_n=r_n, + maxdim=maxdim, cutoff=cutoff) + + # ω-independent Hadamard (GPU) → back to CPU → store D_tuck + n_pairs = r_m * r_n + verbose && println("conductivity_cheb2d_gpu: $n_pairs Hadamard products (GPU → CPU)...") + D_tuck = Matrix{Union{Nothing,MPO}}(nothing, r_m, r_n) + for s1 in 1:r_m, s2 in 1:r_n + (isnothing(A_tuck[s1]) || isnothing(B_tuck[s2]) || + isnothing(C_tuck[s1]) || isnothing(E_tuck[s2])) && continue + + had_A = _hadamard_mpo_gpu(A_tuck[s1], B_tuck[s2], out_sites; + maxdim=maxdim, cutoff=cutoff) + had_B = _hadamard_mpo_gpu(C_tuck[s1], E_tuck[s2], out_sites; + maxdim=maxdim, cutoff=cutoff) + D_gpu = ITensorMPS.truncate!(+(had_A, -1f0 * had_B; maxdim=maxdim); cutoff=cutoff) + _gpu_gc!() + + D_tuck[s1, s2] = replace_sites(_to_cpu_mpo(D_gpu), H1.sites) + + if verbose + idx = (s1 - 1) * r_n + s2 + (idx % 10 == 0 || idx == n_pairs) && + println(" ($s1,$s2)/($r_m,$r_n) [$idx/$n_pairs]") + end + end + + # Per-ω accumulation (CPU) + coeff_tol_val = 1e-12 + Π = Vector{Union{Nothing,MPO}}(nothing, nω) + for iω in 1:nω + for s1 in 1:r_m, s2 in 1:r_n + g = A_core[s1, s2, iω] + (abs(g) < coeff_tol_val || isnothing(D_tuck[s1, s2])) && continue + if Π[iω] === nothing + Π[iω] = g * D_tuck[s1, s2] + else + Π[iω] = +(Π[iω], g * D_tuck[s1, s2]; maxdim=maxdim) + ITensorMPS.truncate!(Π[iω]; cutoff=cutoff) + end + end + end + + σ = zeros(Float64, nω) + for (iω, (ω, Pi0)) in enumerate(zip(ωlist, Π)) + abs(ω) < 1e-14 && continue + isnothing(Pi0) && continue + σ[iω] = -imag(inner(J_ab, Pi0)) / ω + end + + verbose && println("conductivity_cheb2d_gpu: done.") + return σ +end + + +""" + get_bands_gpu(H, Ncheb, ω_phys_vals; kwargs...) + -> Matrix{Float64} or NamedTuple(Ak, ticks, labels) + +GPU-accelerated version of `get_bands`. + +GPU handles: the full Chebyshev MPO recurrence (the dominant cost) and the + QFT sandwich applied to each Chebyshev moment. +CPU handles: k-group setup, KPM weight matrix, final scalar accumulation. + +All GPU operators (Hamiltonian, identity, QFT pair, per-step aux projectors) +use ComplexF32 via `_to_gpu_mpo`/`_make_delta_gpu`, matching the rest of this +toolkit (see the PRECISION note at the top of GPU_tk.jl). ComplexF32 +eigendecomposition can produce NaN at very tight `cutoff` on large systems — +if `Ak` comes back all-NaN, raise `cutoff` rather than lowering it. + +All keyword arguments are identical to the TBHamiltonian overload of +`get_bands`. The return value is also identical: a plain `Matrix{Float64}` +when no `kpath` is given, or a `NamedTuple(Ak, ticks, labels)` when a +high-symmetry path is requested. + +Usage: +```julia +using CUDA +res = TensorBinding.get_bands_gpu(H, 500, omega; + kpath=[:G, :M, :Kp, :G], kpath_lattice=:honeycomb, + num_x=50, maxdim=200, printinfo=true) +heatmap(1:size(res.Ak,2), omega, res.Ak; xticks=(res.ticks, res.labels)) +``` +""" +function get_bands_gpu(H::TBHamiltonian, Ncheb::Int, ω_phys_vals; + kpath = nothing, + kpath_lattice = nothing, + kpath_Lx = nothing, + spin_proj::Bool = false, + proj_s = nothing, + nambu_proj::Bool = false, + proj_nambu = nothing, + layer_proj::Bool = false, + proj_layer = nothing, + sublattice::Bool = false, + proj_sl = nothing, + sublat_proj::Bool = false, + k_groups_override = nothing, + xmin::Int = 0, + xmax = nothing, + num_x::Int = 60, + num_avg::Int = 1, + ymin::Int = 0, + ymax = nothing, + num_y::Int = 10, + kernel::Symbol = :jackson, + lambda::Real = 4.0, + tol::Real = 1e-9, + maxdim::Int = 100, + cutoff::Real = 1e-10, + printinfo::Bool = false) + + _check_gpu("get_bands_gpu") + + _ensure_scale!(H) + nambu_proj, spin_proj, layer_proj, sublat_proj = + _autoenable_proj(H, nambu_proj, spin_proj, layer_proj, sublat_proj) + + ω_resc = (collect(ω_phys_vals) .- H.center) ./ H.scale + Nω = length(ω_resc) + valid = [abs(ω) < 1.0 for ω in ω_resc] + W_kpm = _kpm_weight_matrix(Ncheb, ω_resc; kernel=kernel, lambda=lambda) + + # ── Auto-detect aux indices (mirrors the CPU TBHamiltonian overload) ──── + nambu_s_det, nambu_side_det = !isnothing(H.nambu_s) ? + aux_site(H, :nambu) : (nothing, :pre) + spin_s_det = H.spin_s + layer_s_det, layer_side_det = !isnothing(H.layer_s) ? + aux_site(H, :layer) : (nothing, :pre) + sublat_s_det, sublat_side_det = !isnothing(H.sublattice_s) ? + aux_site(H, :sublattice) : (nothing, :post) + + # ── L_pos: position qubits only (excluding aux sites) ─────────────────── + isnothing(H.geometry) && error("get_bands_gpu: H.geometry must be set (needed to infer D).") + D = length(H.geometry(1)) + L = H.L + L_pos = L - (spin_proj ? 1 : 0) - (!isnothing(nambu_s_det) ? 1 : 0) - + (!isnothing(layer_s_det) ? 1 : 0) - (!isnothing(sublat_s_det) ? 1 : 0) + + # ── k-path shortcut ────────────────────────────────────────────────────── + kpath_ticks = nothing; kpath_labels = nothing + if !isnothing(kpath) + isnothing(kpath_lattice) && error("get_bands_gpu: kpath requires kpath_lattice.") + Lx_kp = isnothing(kpath_Lx) ? H.L ÷ 2 : Int(kpath_Lx) + Ly_kp = H.L - Lx_kp + k_groups_override, kpath_ticks, kpath_labels = + kpath_setup(kpath_lattice, Lx_kp, Ly_kp, kpath; npts_per_segment=num_x) + end + + # ── k-groups (same logic as low-level CPU get_bands) ──────────────────── + Lx_pos = D == 2 ? div(L_pos, 2) : 0 + N_pos = 2^L_pos + if !isnothing(k_groups_override) + k_groups = k_groups_override + num_x = length(k_groups) + elseif D == 1 + _xmax = xmax === nothing ? N_pos - 1 : Int(xmax) + xcenters = ilinspace(xmin, _xmax, num_x) + half_step = num_x > 1 ? (_xmax - xmin) / (2 * num_x) : 0 + offsets = num_avg > 1 ? round.(Int, range(-half_step, half_step; length=num_avg)) : Int[0] + k_groups = [clamp.(xcenters[i] .+ offsets, 0, N_pos - 1) for i in 1:num_x] + elseif D == 2 + Nx_loc = 2^Lx_pos; Ny_loc = 2^(L_pos - Lx_pos) + num_x = min(num_x, Nx_loc) + _xmax = xmax === nothing ? Nx_loc - 1 : Int(xmax) + _ymax = ymax === nothing ? Ny_loc - 1 : Int(ymax) + xcenters = ilinspace(xmin, _xmax, Nx_loc) + ycenters = ilinspace(ymin, _ymax, Ny_loc) + hsx = num_x > 1 ? (_xmax - xmin) / (2 * num_x) : 0 + hsy = num_y > 1 ? (_ymax - ymin) / (2 * num_y) : 0 + x_offs = num_avg > 1 ? round.(Int, range(-hsx, hsx; length=num_avg)) : Int[0] + y_offs = num_avg > 1 ? round.(Int, range(-hsy, hsy; length=num_avg)) : Int[0] + k_groups = [ + begin + xs = clamp.(xcenters[i] .+ x_offs, 0, Nx_loc - 1) + ys = clamp.(ycenters[i] .+ y_offs, 0, Ny_loc - 1) + [(y << Lx_pos) | x for (x, y) in zip(xs, ys)] + end + for i in 1:num_x + ] + else + error("D must be 1 or 2") + end + + Ak_w = zeros(Float64, Nω, num_x) + + # ── Position sites (used for QFT ops and optional sublattice masks) ────── + aux_to_drop = Set{Index}() + spin_proj && push!(aux_to_drop, + isnothing(spin_s_det) ? H.sites[1] : spin_s_det::Index) + !isnothing(nambu_s_det) && push!(aux_to_drop, nambu_s_det::Index) + !isnothing(layer_s_det) && push!(aux_to_drop, layer_s_det::Index) + !isnothing(sublat_s_det) && push!(aux_to_drop, sublat_s_det::Index) + pos_sites_cpu = filter(s -> s ∉ aux_to_drop, H.sites) + + # ── Legacy sublattice masks — pre-built on CPU, moved to GPU once ──────── + # Built only when `sublattice=true` (legacy models without a sublat aux index). + # For models that use H.sublattice_s (honeycomb, kagome…), sublat_proj=true + # and sublattice=false, so this block is skipped entirely. + if sublattice + if D == 1 + mask_A_gpu = _to_gpu_mpo(_col_select_mpo(L_pos, 0, pos_sites_cpu; keep=:odd)) + mask_B_gpu = _to_gpu_mpo(_col_select_mpo(L_pos, 0, pos_sites_cpu; keep=:even)) + else + Ly_pos = L_pos - Lx_pos + mask_A_gpu = _to_gpu_mpo(_row_checker_mpo(Lx_pos, Ly_pos, pos_sites_cpu)) + mask_B_gpu = _to_gpu_mpo(MPO(pos_sites_cpu, "Id") - + _row_checker_mpo(Lx_pos, Ly_pos, pos_sites_cpu)) + end + end + + # ── GPU Ham and QFT operators ──────────────────────────────────────────── + I_mpo_cpu = MPO(H.sites, "Id") + Ham_n_cpu = (1 / H.scale) * +(H.mpo, (-H.center) * I_mpo_cpu; cutoff=cutoff) + I_mpo_gpu = _to_gpu_mpo(I_mpo_cpu) + Ham_n_gpu = _to_gpu_mpo(Ham_n_cpu) + + # QFT operators sized for pos_sites_cpu (the post-projection site list). + # Calling fix_sites maps the abstract QFT indices onto the actual pos_sites. + R_pos = length(pos_sites_cpu) + FTirev_gpu = _to_gpu_mpo(fix_sites( + MPO(TCI.reverse(QuanticsTCI.quanticsfouriermpo(R_pos; sign=-1.0, normalize=true))), + pos_sites_cpu)) + FTrev_gpu = _to_gpu_mpo(fix_sites( + MPO(TCI.reverse(QuanticsTCI.quanticsfouriermpo(R_pos; sign=+1.0, normalize=true))), + pos_sites_cpu)) + + local _nambu_side = nambu_side_det + local _layer_side = layer_side_det + local _sublat_side = sublat_side_det + local _spin_idx = isnothing(spin_s_det) ? H.sites[1] : spin_s_det + + # ── Online accumulation — fully on GPU ────────────────────────────────── + # Workflow: prebuild everything on CPU (done above), then T_n stays on GPU + # for the entire accumulate step. Only the final scalar() calls transfer + # numbers out of the GPU — no explicit MPO/MPS moves back to CPU. + # + # Per step: + # projection → _project_aux_gpu (dense ComplexF32 projector, GPU throughout) + # QFT → _apply_qft_conj_gpu (pre-built GPU QFT operators) + # diagonal → extract_diagonal_to_mps_gpu (CPU array slice, back to GPU F32) + # sampling → _eval_diag_mps_gpu (scalars pulled out of GPU directly) + function accumulate_Tn_gpu!(ak_accum, Tn_gpu, n) + # Step 0: Nambu (BdG) projection + after_nambu = nambu_proj ? + [_project_aux_gpu(Tn_gpu, nambu_s_det::Index, sec; side=_nambu_side) + for sec in (isnothing(proj_nambu) ? (1:2) : (proj_nambu:proj_nambu))] : + MPO[Tn_gpu] + + # Step 1: spin projection + after_spin = spin_proj ? + [_project_aux_gpu(T, _spin_idx, sec; side=:pre) + for T in after_nambu, sec in (isnothing(proj_s) ? (1:2) : (proj_s:proj_s))] : + after_nambu + + # Step 1c: layer projection + after_layer = if layer_proj + n_lay = dim(layer_s_det::Index) + lay_range = isnothing(proj_layer) ? (1:n_lay) : (proj_layer:proj_layer) + [_project_aux_gpu(T, layer_s_det::Index, sec; side=_layer_side) + for T in after_spin for sec in lay_range] + else + after_spin + end + + # Step 1b: sublattice aux projection + after_sl_aux = if sublat_proj + sl_range = isnothing(proj_sl) ? (1:dim(sublat_s_det::Index)) : (proj_sl:proj_sl) + [_project_aux_gpu(T, sublat_s_det::Index, sec; side=_sublat_side) + for T in after_layer for sec in sl_range] + else + after_layer + end + + # Step 2: legacy sublattice mask sandwich (all GPU — masks pre-built above) + if sublattice + masks = isnothing(proj_sl) ? [mask_A_gpu, mask_B_gpu] : + proj_sl == 1 ? [mask_A_gpu] : [mask_B_gpu] + sl_mpas = MPO[] + for T in after_sl_aux, mask in masks + push!(sl_mpas, apply(apply(mask, T; cutoff=cutoff, maxdim=maxdim), mask; + cutoff=cutoff, maxdim=maxdim)) + end + else + sl_mpas = after_sl_aux + end + + # Step 3: QFT (GPU) → diagonal MPS (GPU) → scalar sampling (GPU) + for T_gpu in sl_mpas + Tn_k_gpu = _apply_qft_conj_gpu(T_gpu, FTirev_gpu, FTrev_gpu; + tol=tol, maxdim=maxdim) + A_mps_gpu = ITensorMPS.truncate!(extract_diagonal_to_mps_gpu(Tn_k_gpu); cutoff=cutoff) + for (ik, xs) in enumerate(k_groups) + s = sum(_eval_diag_mps_gpu(A_mps_gpu, x) for x in xs) / length(xs) + for ie in 1:Nω + ak_accum[ie, ik] += W_kpm[n, ie] * s + end + end + end + + _gpu_gc!() + end + + # ── Chebyshev recurrence (GPU) ─────────────────────────────────────────── + Tkm2 = I_mpo_gpu + Tkm1 = Ham_n_gpu + + accumulate_Tn_gpu!(Ak_w, Tkm2, 1) + accumulate_Tn_gpu!(Ak_w, Tkm1, 2) + + for k in 3:Ncheb + Tk = +(2 * apply(Ham_n_gpu, Tkm1; cutoff=cutoff, maxdim=maxdim), + -Tkm2; cutoff=cutoff, maxdim=maxdim) + ITensorMPS.truncate!(Tk; cutoff=cutoff) + accumulate_Tn_gpu!(Ak_w, Tk, k) + Tkm2 = Tkm1 + Tkm1 = Tk + _gpu_gc!() + printinfo && (k % 10 == 0 || k == Ncheb) && + println(" [gpu] bands step $k/$Ncheb maxlinkdim=$(maxlinkdim(Tkm1))") + end + + # ── KPM normalization: 1 / (π² Ncheb √(1 − ε²)) ──────────────────────── + for iω in 1:Nω + valid[iω] || continue + Ak_w[iω, :] ./= (π^2 * Ncheb * sqrt(1 - ω_resc[iω]^2)) + end + + return isnothing(kpath_ticks) ? Ak_w : + (Ak = Ak_w, ticks = kpath_ticks, labels = kpath_labels) +end + + +""" + get_ldos_spatial_gpu(H, Ncheb, ω_phys_vals; kwargs...) + -> Matrix{Float64} shape (Nω × n_spatial_cols) + +GPU-accelerated version of `get_ldos_spatial` (MPO mode only). + +**Sampling procedures (`reduce`)** — see [`spatial_sampling_plan`](@ref). + +- `:point` (default) — read the LDOS at `num_x[×num_y]` cells / `x_groups` + (optionally box-averaged). Coarse grids alias thin features. +- `:block` — integrate over `num_x × num_y` blocks (powers of two) by tracing out + the within-block bits; gap-free, so thin in-gap edge channels on a large system + cannot be missed. The scalable tool for large-scale edge-state maps. + +**Column layout** + +- No sublattice DOF: `(Nω × ng)`, one column per pixel (group or block). +- Sublattice resolved: `(Nω × ng×n_sub)`, interleaved `[A₀, B₀, A₁, B₁, …]`. +- Sublattice averaged (large scale / `:block`): `(Nω × ng)`, one value per pixel. + +For `:block`, columns are row-major over coarse pixels (`col = ixp + iyp·num_x + 1`). + +**GPU/CPU split** + +GPU: entire Chebyshev MPO recurrence, aux projections, diagonal extraction, + real-space scalar sampling (point eval or block integration). +CPU: KPM weight matrix, output accumulation (scalars only). + +Keyword arguments are identical to `get_ldos_spatial` (`:mps` mode is not +available on GPU; only the single-pass MPO mode is implemented here). + +Usage +----- +```julia +using CUDA +# point map +ldos = TensorBinding.get_ldos_spatial_gpu(H, 200, ωlist; + x_groups = [[uc] for uc in 1:H.N], maxdim=200, printinfo=true) +# block-integrated large-scale edge-state map (num_x, num_y powers of two) +ldos = TensorBinding.get_ldos_spatial_gpu(H, 200, ωlist; + reduce=:block, num_x=128, num_y=128, sublattice=:average, maxdim=200) +``` +""" +function get_ldos_spatial_gpu(H::TBHamiltonian, Ncheb::Int, ω_phys_vals; + x_groups = nothing, + num_x::Int = H.N, + num_y = nothing, + num_avg::Int = 1, + x_start::Int = 1, + x_end::Int = H.N, + grid::Bool = false, + xwin = nothing, + ywin = nothing, + box_half::Int = 0, + reduce::Symbol = :point, + sublattice::Symbol = :auto, + kernel::Symbol = :jackson, + lambda::Real = 4.0, + maxdim::Int = 100, + cutoff::Real = 1e-8, + verbose::Bool = false, + printinfo::Bool = false, + nambu_proj::Bool = false, + proj_nambu = nothing, + spin_proj::Bool = false, + proj_s = nothing, + layer_proj::Bool = false, + proj_layer = nothing, + sublat_proj::Bool = false, + proj_sl = nothing) + + _check_gpu("get_ldos_spatial_gpu") + + # ── Geometry-aware sampling plan (same convention as get_ldos_spatial) ──── + if box_half > 0 || grid || xwin !== nothing || ywin !== nothing || reduce === :block + isnothing(H.geometry) && + error("get_ldos_spatial_gpu: box_half/grid/window/block sampling requires H.geometry to be set.") + length(H.geometry(1)) == 2 || + error("get_ldos_spatial_gpu: box_half/grid/window/block sampling is only supported for 2D systems.") + end + Lx_uc = something(H.Lx, H.L ÷ 2) + Ly_uc = H.L - Lx_uc + n_sub_H = isnothing(H.sublattice_s) ? 1 : dim(H.sublattice_s) + plan = spatial_sampling_plan(H.L; + Lx = Lx_uc, + grid = grid, + reduce = reduce, + n_sub = n_sub_H, + num_x = num_x, num_y = num_y, num_avg = num_avg, + x_start = x_start, x_end = x_end, + xwin = xwin, ywin = ywin, + x_groups = x_groups, box_half = box_half, + sublattice = sublattice) + groups = plan.groups + is_block = plan.reduce === :block + block_a = plan.a + block_b = plan.b + nbx = 2^block_a # coarse pixels along x (block mode) + + _ensure_scale!(H) + nambu_proj, spin_proj, layer_proj, sublat_proj = + _autoenable_proj(H, nambu_proj, spin_proj, layer_proj, sublat_proj) + + # ── Aux site detection ─────────────────────────────────────────────────── + nambu_s_det, nambu_side_det = !isnothing(H.nambu_s) ? aux_site(H, :nambu) : (nothing, :pre) + spin_s_det = H.spin_s + layer_s_det, layer_side_det = !isnothing(H.layer_s) ? aux_site(H, :layer) : (nothing, :pre) + sublat_s_det, sublat_side_det = !isnothing(H.sublattice_s) ? aux_site(H, :sublattice) : (nothing, :post) + + has_sublat = !isnothing(sublat_s_det) + n_sub = has_sublat ? dim(sublat_s_det::Index) : 1 + # Large-scale sampling traces out the sublattice (one value per unit cell); + # atomic-scale / proj_sl=k resolves it into per-atom columns. See plan above. + resolve_sl = has_sublat && (plan.resolve_sublattice || !isnothing(proj_sl)) + average_sl = has_sublat && !resolve_sl + sl_fill = has_sublat ? + (isnothing(proj_sl) ? (1:n_sub) : (proj_sl:proj_sl)) : + (1:1) + + # ── KPM setup ──────────────────────────────────────────────────────────── + ω_vals = (collect(ω_phys_vals) .- H.center) ./ H.scale + Nω = length(ω_vals) + W = _kpm_weight_matrix(Ncheb, ω_vals; kernel=kernel, lambda=lambda) + valid = [abs(ω) < 1.0 for ω in ω_vals] + + ng = length(groups) + n_cols = average_sl ? ng : ng * n_sub + accum = zeros(Float64, Nω, n_cols) + + # ── GPU operators ──────────────────────────────────────────────────────── + I_mpo_cpu = MPO(H.sites, "Id") + Ham_n_cpu = (1 / H.scale) * +(H.mpo, (-H.center) * I_mpo_cpu; cutoff=cutoff) + I_mpo_gpu = _to_gpu_mpo(I_mpo_cpu) + Ham_n_gpu = _to_gpu_mpo(Ham_n_cpu) + + local _nambu_side = nambu_side_det + local _layer_side = layer_side_det + local _sublat_side = sublat_side_det + local _spin_idx = isnothing(spin_s_det) ? H.sites[1] : spin_s_det + + # ── Online accumulation (GPU) ──────────────────────────────────────────── + # No QFT sandwich: positions are real-space, so after projections we extract + # the diagonal MPS and reduce it to per-pixel scalars: + # reduce=:point → evaluate at each group's cells (big-endian) and average, + # reduce=:block → integrate over each coarse block by tracing the within- + # block bits (_eval_block_mps_gpu). Both return (u, value) + # where u is the 1-indexed output pixel (column unit). + function spatial_vals_gpu(diag_mps) + if is_block + return [(ixp + iyp * nbx + 1, + _eval_block_mps_gpu(diag_mps, ixp, iyp, block_a, block_b, Lx_uc, Ly_uc)) + for iyp in 0:(2^block_b - 1) for ixp in 0:(nbx - 1)] + else + return [(ig, sum(_eval_mps_bigendian_gpu(diag_mps, x - 1) for x in grp) / length(grp)) + for (ig, grp) in enumerate(groups)] + end + end + + function accumulate_Tn_ldos_gpu!(ak_accum, Tn_gpu, n) + after_nambu = nambu_proj ? + [_project_aux_gpu(Tn_gpu, nambu_s_det::Index, sec; side=_nambu_side) + for sec in (isnothing(proj_nambu) ? (1:2) : (proj_nambu:proj_nambu))] : + MPO[Tn_gpu] + + after_spin = spin_proj ? + [_project_aux_gpu(T, _spin_idx, sec; side=:pre) + for T in after_nambu, sec in (isnothing(proj_s) ? (1:2) : (proj_s:proj_s))] : + after_nambu + + after_layer = if layer_proj + n_lay = dim(layer_s_det::Index) + lay_range = isnothing(proj_layer) ? (1:n_lay) : (proj_layer:proj_layer) + [_project_aux_gpu(T, layer_s_det::Index, sec; side=_layer_side) + for T in after_spin for sec in lay_range] + else + after_spin + end + + if has_sublat + # Resolved → per-atom column; averaged → fold all atoms into the + # single per-pixel column u (mean over the n_sub atoms). + for Tl in after_layer, s in sl_fill + Tp = _project_aux_gpu(Tl, sublat_s_det::Index, s; side=_sublat_side) + diag_mps = ITensorMPS.truncate!(extract_diagonal_to_mps_gpu(Tp); cutoff=cutoff) + scale = average_sl ? 1.0 / n_sub : 1.0 + for (u, val) in spatial_vals_gpu(diag_mps) + c = average_sl ? u : (u - 1) * n_sub + s + for iω in 1:Nω + valid[iω] || continue + ak_accum[iω, c] += W[n, iω] * val * scale + end + end + end + else + for Tp in after_layer + diag_mps = ITensorMPS.truncate!(extract_diagonal_to_mps_gpu(Tp); cutoff=cutoff) + for (u, val) in spatial_vals_gpu(diag_mps) + for iω in 1:Nω + valid[iω] || continue + ak_accum[iω, u] += W[n, iω] * val + end + end + end + end + + _gpu_gc!() + end + + # ── Chebyshev recurrence (GPU) ─────────────────────────────────────────── + cutoff < 1e-6 && @warn "get_ldos_spatial_gpu: cutoff=$cutoff is below 1e-6; ComplexF32 eigendecomposition may produce NaN on large systems — consider cutoff ≥ 1e-4." + gpu_cutoff = Float64(cutoff) + Tkm2 = I_mpo_gpu + Tkm1 = Ham_n_gpu + + accumulate_Tn_ldos_gpu!(accum, Tkm2, 1) + accumulate_Tn_ldos_gpu!(accum, Tkm1, 2) + + for k in 3:Ncheb + Tk = +(2 * apply(Ham_n_gpu, Tkm1; cutoff=gpu_cutoff, maxdim=maxdim), + -Tkm2; cutoff=gpu_cutoff, maxdim=maxdim) + ITensorMPS.truncate!(Tk; cutoff=gpu_cutoff) + accumulate_Tn_ldos_gpu!(accum, Tk, k) + Tkm2 = Tkm1 + Tkm1 = Tk + _gpu_gc!() + (verbose || printinfo) && (k % 10 == 0 || k == Ncheb) && + println(" [gpu] ldos step $k/$Ncheb maxlinkdim=$(maxlinkdim(Tkm1))") + end + + # ── KPM normalization ──────────────────────────────────────────────────── + result = zeros(Float64, Nω, n_cols) + for iω in 1:Nω + valid[iω] || continue + result[iω, :] = accum[iω, :] ./ (π^2 * Ncheb * sqrt(1 - ω_vals[iω]^2)) + end + + return result +end + + +""" + get_dos_stochastic_gpu(H, Ncheb, ω_phys_vals; kwargs...) + -> Vector{Float64} length Nω + +GPU-accelerated stochastic density of states via MPS Chebyshev KPM. + +For each random sample the scaled Hamiltonian MPO lives on GPU and the product- +state MPS is transferred to GPU once before the recursion starts. +The Chebyshev moments ⟨ψ₀|T_n(H̃)|ψ₀⟩ are scalars pulled to CPU at each step. + +Signature and optional kwargs are identical to `get_dos_stochastic` (CPU). +`N_bound` (exciton bound-sector enrichment) is supported. Use +`dos_weighting=:sample` to return the unweighted sampled signal +`avg_full + avg_bound`, which is useful when visualising exciton peaks that are +otherwise hidden by continuum phase-space factors in the trace DOS. +For exciton Hamiltonians, `continuum_only=true` samples ordered electron-hole +product states with `x_e != x_h` for the `N_sample` branch. + +`kernel=:hodc` selects the Higher-Order Delta Chebyshev reconstruction +(`eta`, `m_order` control the contour); its weights already carry the full KPM +normalisation, so no `√(1−ω²)` denominator is applied. `eta=0` falls back to +`1/(Ncheb+1)`. Otherwise `kernel` is a convolution kernel (`:jackson` default, +`:lorentz` with `lambda`, `:fejer`, `:dirichlet`). +""" +function get_dos_stochastic_gpu(H::TBHamiltonian, Ncheb::Int, ω_phys_vals; + N_sample::Int = 50, + N_bound::Int = 0, + seed::Union{Int,Nothing} = 42, + normalize::Bool = false, + dos_weighting::Symbol = :trace, + kernel::Symbol = :jackson, + lambda::Real = 4.0, + eta::Real = 0.0, + m_order::Int = 4, + maxdim::Int = 100, + cutoff::Real = 1e-8, + verbose::Bool = false, + printinfo::Bool = false, + continuum_only::Bool = false, + nambu_proj::Bool = false, + proj_nambu = nothing, + spin_proj::Bool = false, + proj_s = nothing, + layer_proj::Bool = false, + proj_layer = nothing, + sublat_proj::Bool = false, + proj_sl = nothing) + + _check_gpu("get_dos_stochastic_gpu") + cutoff < 1e-6 && @warn "get_dos_stochastic_gpu: cutoff=$cutoff is below 1e-6; ComplexF32 eigendecomposition may produce NaN on large systems — consider cutoff ≥ 1e-4." + _ensure_scale!(H) + dos_weighting in (:trace, :sample) || + error("get_dos_stochastic_gpu: dos_weighting must be :trace or :sample.") + N_sample >= 0 || error("get_dos_stochastic_gpu: N_sample must be non-negative.") + N_bound >= 0 || error("get_dos_stochastic_gpu: N_bound must be non-negative.") + + I_mpo_cpu = MPO(H.sites, "Id") + Ham_n_cpu = (1 / H.scale) * +(H.mpo, (-H.center) * I_mpo_cpu; cutoff=cutoff) + Ham_n_gpu = _to_gpu_mpo(Ham_n_cpu) + + D = prod(ITensors.dim(s) for s in H.sites) + N_phys = H.N + is_exc = length(H.sites) == 2 * H.L + continuum_only && !is_exc && + error("get_dos_stochastic_gpu: continuum_only=true requires an exciton Hamiltonian.") + continuum_only && N_phys < 2 && + error("get_dos_stochastic_gpu: continuum_only=true requires H.N >= 2.") + + (; nambu_range, spin_range, layer_range, sl_range, any_aux_proj) = + _aux_setup(H, nambu_proj, proj_nambu, spin_proj, proj_s, + layer_proj, proj_layer, sublat_proj, proj_sl) + + ω_vals = (collect(ω_phys_vals) .- H.center) ./ H.scale + Nω = length(ω_vals) + W, denom = _dos_weight_matrix(Ncheb, ω_vals; + kernel=kernel, lambda=lambda, eta=eta, m_order=m_order) + valid = [abs(ω) < 1.0 for ω in ω_vals] + + rng = seed === nothing ? Random.default_rng() : Random.MersenneTwister(seed) + accum_full = zeros(Float64, Nω) + accum_bound = zeros(Float64, Nω) + + function _run_kpm_mps_gpu!(psi0_gpu, accum, weight) + apply_kwargs = (cutoff=Float64(cutoff), maxdim=maxdim) + function kpm_step!(phi, n) + mu = Float64(real(inner(psi0_gpu, phi))) + for iω in 1:Nω + valid[iω] || continue + accum[iω] += W[n, iω] * mu * weight + end + end + phi_km2 = psi0_gpu + phi_km1 = apply(Ham_n_gpu, phi_km2; apply_kwargs...) + kpm_step!(phi_km2, 1) + kpm_step!(phi_km1, 2) + for k in 3:Ncheb + phi_k = +(2 * apply(Ham_n_gpu, phi_km1; apply_kwargs...), + -phi_km2; apply_kwargs...) + kpm_step!(phi_k, k) + phi_km2 = phi_km1 + phi_km1 = phi_k + end + _gpu_gc!() + return maxlinkdim(phi_km1) + end + + function _exciton_pair_mps_gpu_seed(xe::Int, xh::Int) + Lphys = div(length(H.sites), 2) + bits_e = to_binary_vector(xe - 1, Lphys) + bits_h = to_binary_vector(xh - 1, Lphys) + state = Vector{String}(undef, 2 * Lphys) + for b in 1:Lphys + state[2b - 1] = bits_e[b] + state[2b] = bits_h[b] + end + return MPS(H.sites, state) + end + + if any_aux_proj + continuum_only && + error("get_dos_stochastic_gpu: continuum_only is not supported together with auxiliary projections.") + D_eff = N_phys + if N_sample > 0 + xs = rand(rng, 1:N_phys, N_sample) + for (i, x) in enumerate(xs) + for σ_n in nambu_range, σ_s in spin_range, σ_l in layer_range, σ_sl in sl_range + psi0_gpu = _to_gpu_mps(_ldos_make_psi0(H, x, σ_n, σ_s, σ_l, σ_sl)) + χ = _run_kpm_mps_gpu!(psi0_gpu, accum_full, 1.0/N_sample) + (verbose || printinfo) && i % 10 == 0 && + σ_n == first(nambu_range) && σ_s == first(spin_range) && + σ_l == first(layer_range) && σ_sl == first(sl_range) && + println(" [gpu] dos sample $i/$N_sample (projected) maxlinkdim=$χ") + end + end + end + + result = zeros(Float64, Nω) + for iω in 1:Nω + valid[iω] || continue + if dos_weighting == :sample + result[iω] = accum_full[iω] / denom[iω] + else + result[iω] = D_eff * accum_full[iω] / denom[iω] + end + end + normalize && dos_weighting == :trace && (result ./= D_eff) + return result + end + + # ── Full / continuum Hilbert-space sampling ─────────────────────────────── + if N_sample > 0 + if continuum_only + xs_e = rand(rng, 1:N_phys, N_sample) + ys_h = rand(rng, 1:(N_phys - 1), N_sample) + for i in 1:N_sample + xe = xs_e[i] + xh = ys_h[i] < xe ? ys_h[i] : ys_h[i] + 1 + psi0_gpu = _to_gpu_mps(_exciton_pair_mps_gpu_seed(xe, xh)) + χ = _run_kpm_mps_gpu!(psi0_gpu, accum_full, 1.0/N_sample) + (verbose || printinfo) && i % 10 == 0 && + println(" [gpu] dos continuum sample $i/$N_sample (xe=$xe, xh=$xh) maxlinkdim=$χ") + end + else + samples = rand(rng, 0:(D - 1), N_sample) + for (i, k) in enumerate(samples) + psi0_gpu = _to_gpu_mps(_basis_state_mps(k, H.sites)) + χ = _run_kpm_mps_gpu!(psi0_gpu, accum_full, 1.0/N_sample) + (verbose || printinfo) && i % 10 == 0 && + println(" [gpu] dos sample $i/$N_sample maxlinkdim=$χ") + end + end + end + + # ── Bound-sector enrichment (exciton) ───────────────────────────────────── + if N_bound > 0 && is_exc + xs = rand(rng, 1:N_phys, N_bound) + for (i, x) in enumerate(xs) + psi0_gpu = _to_gpu_mps(mpsexciton(x, H.sites)) + χ = _run_kpm_mps_gpu!(psi0_gpu, accum_bound, 1.0/N_bound) + (verbose || printinfo) && i % 10 == 0 && + println(" [gpu] dos bound sample $i/$N_bound (x=$x) maxlinkdim=$χ") + end + end + + # ── Normalise ───────────────────────────────────────────────────────────── + result = zeros(Float64, Nω) + for iω in 1:Nω + valid[iω] || continue + if dos_weighting == :sample + result[iω] = (accum_full[iω] + + ((N_bound > 0 && is_exc) ? accum_bound[iω] : 0.0)) / denom[iω] + elseif N_bound > 0 && is_exc + result[iω] = ((D - N_phys) * accum_full[iω] + + N_phys * accum_bound[iω]) / denom[iω] + elseif continuum_only && is_exc + result[iω] = (D - N_phys) * accum_full[iω] / denom[iω] + else + result[iω] = D * accum_full[iω] / denom[iω] + end + end + if normalize && dos_weighting == :trace + norm_dim = (continuum_only && is_exc && N_bound == 0) ? (D - N_phys) : D + result ./= norm_dim + end + return result +end + + +""" + get_exciton_ldos_spatial_gpu(H, Ncheb, ω_phys_vals; X_list, X_groups, + num_x, num_avg, x_start, x_end, kernel, + lambda, eta, m_order, maxdim, cutoff, verbose, printinfo) + -> Matrix{Float64} (Nω × n_X) + +GPU-accelerated spatial exciton LDOS: for each bound exciton position `X` (electron += hole = X, 1-indexed in `1:H.N`) the local spectral weight +`A(X, ω) = ⟨X,X|δ(ω−H)|X,X⟩` is reconstructed from the Chebyshev moments +`μ_n = ⟨X,X|T_n(H̃)|X,X⟩`. One GPU MPS Chebyshev recursion is run per X starting +from `|X,X⟩ = mpsexciton(X, H.sites)`; moments are scalars pulled to CPU. + +This is the spatial / batched GPU analog of the CPU `get_exciton_ldos` (same +reconstruction via the KPM weight matrix), columns ordered as `X_list` or as +the coarse centers of the generated spatial groups. + +`X_list` selects the positions directly. `X_groups` selects explicit position +groups and averages all probes inside each group into one output column. If +neither is provided, `num_x` coarse groups are built over `x_start:x_end`; each +group contains `num_avg` subpositions with the same stride convention as +`get_ldos_spatial`. All positions are 1-indexed in `1:H.N`. + +`kernel=:hodc` selects the Higher-Order Delta Chebyshev reconstruction (`eta`, +`m_order`); its weights carry the full normalisation (no `√(1−ω²)` denominator). +`eta=0` falls back to `1/(Ncheb+1)`. Otherwise `kernel` is a convolution kernel +(`:jackson` default, `:lorentz` with `lambda`, `:fejer`, `:dirichlet`). +""" +function get_exciton_ldos_spatial_gpu(H::TBHamiltonian, Ncheb::Int, ω_phys_vals; + X_list = nothing, + X_groups = nothing, + x_groups = nothing, + num_x::Int = H.N, + num_avg::Int = 1, + x_start::Int = 1, + x_end::Int = H.N, + kernel::Symbol = :jackson, + lambda::Real = 4.0, + eta::Real = 0.0, + m_order::Int = 4, + maxdim::Int = 100, + cutoff::Real = 1e-8, + verbose::Bool = false, + printinfo::Bool = false) + + _check_gpu("get_exciton_ldos_spatial_gpu") + cutoff < 1e-6 && @warn "get_exciton_ldos_spatial_gpu: cutoff=$cutoff is below 1e-6; ComplexF32 eigendecomposition may produce NaN on large systems — consider cutoff ≥ 1e-4." + _ensure_scale!(H) + length(H.sites) == 2 * H.L || + error("get_exciton_ldos_spatial_gpu: H is not an exciton Hamiltonian (expected length(H.sites) == 2*H.L).") + + X_groups !== nothing && x_groups !== nothing && + error("get_exciton_ldos_spatial_gpu: pass only one of X_groups or x_groups.") + X_list !== nothing && (X_groups !== nothing || x_groups !== nothing) && + error("get_exciton_ldos_spatial_gpu: pass either X_list or grouped positions, not both.") + + group_arg = X_groups !== nothing ? X_groups : x_groups + groups = if group_arg !== nothing + group_arg isa AbstractVector{<:AbstractVector} ? + [collect(Int, grp) for grp in group_arg] : + [[Int(x)] for x in group_arg] + elseif X_list !== nothing + [[Int(x)] for x in X_list] + else + num_x > 0 || error("get_exciton_ldos_spatial_gpu: num_x must be positive.") + num_avg > 0 || error("get_exciton_ldos_spatial_gpu: num_avg must be positive.") + 1 <= x_start <= x_end <= H.N || + error("get_exciton_ldos_spatial_gpu: expected 1 <= x_start <= x_end <= H.N.") + window = x_end - x_start + 1 + num_x <= window || + error("get_exciton_ldos_spatial_gpu: num_x=$num_x exceeds sampling window length $window.") + dx = div(window, num_x) + dx_sub = max(1, div(dx, num_avg)) + [[x_start + (i - 1) * dx + k * dx_sub + for k in 0:num_avg-1 + if x_start + (i - 1) * dx + k * dx_sub <= x_end] + for i in 1:num_x] + end + isempty(groups) && error("get_exciton_ldos_spatial_gpu: no spatial groups were selected.") + for grp in groups + isempty(grp) && error("get_exciton_ldos_spatial_gpu: empty spatial group.") + all(x -> 1 <= x <= H.N, grp) || + error("get_exciton_ldos_spatial_gpu: all positions must lie in 1:H.N.") + end + Xs = first.(groups) + + I_mpo_cpu = MPO(H.sites, "Id") + Ham_n_cpu = (1 / H.scale) * +(H.mpo, (-H.center) * I_mpo_cpu; cutoff=cutoff) + Ham_n_gpu = _to_gpu_mpo(Ham_n_cpu) + + ω_vals = (collect(ω_phys_vals) .- H.center) ./ H.scale + Nω = length(ω_vals) + W, denom = _dos_weight_matrix(Ncheb, ω_vals; + kernel=kernel, lambda=lambda, eta=eta, m_order=m_order) + valid = [abs(ω) < 1.0 for ω in ω_vals] + + nX = length(groups) + result = zeros(Float64, Nω, nX) + apply_kwargs = (cutoff=Float64(cutoff), maxdim=maxdim) + + for (j, group) in enumerate(groups) + last_linkdim = 0 + + for X in group + psi0_gpu = _to_gpu_mps(mpsexciton(X, H.sites)) + accum = zeros(Float64, Nω) + + function kpm_step!(phi, n) + mu = Float64(real(inner(psi0_gpu, phi))) + for iω in 1:Nω + valid[iω] || continue + accum[iω] += W[n, iω] * mu + end + end + + phi_km2 = psi0_gpu + phi_km1 = apply(Ham_n_gpu, phi_km2; apply_kwargs...) + kpm_step!(phi_km2, 1) + kpm_step!(phi_km1, 2) + for k in 3:Ncheb + phi_k = +(2 * apply(Ham_n_gpu, phi_km1; apply_kwargs...), + -phi_km2; apply_kwargs...) + kpm_step!(phi_k, k) + phi_km2 = phi_km1 + phi_km1 = phi_k + end + + last_linkdim = maxlinkdim(phi_km1) + for iω in 1:Nω + valid[iω] || continue + result[iω, j] += accum[iω] / denom[iω] + end + + _gpu_gc!() + end + + for iω in 1:Nω + result[iω, j] /= length(group) + end + (verbose || printinfo) && (j % 5 == 0 || j == nX) && + println(" [gpu] exciton ldos $j/$nX (X=$(Xs[j]), n_avg=$(length(group))) maxlinkdim=$last_linkdim") + end + + return result +end + + +# ============================================================ +# GPU Chern marker +# ============================================================ + +""" + get_C_gpu(H::TBHamiltonian, xfunc=nothing, yfunc=nothing; kwargs...) -> Function + +GPU-accelerated real-space Chern marker. Mirrors `get_C` exactly but runs all +MPO×MPO products (projector assembly and C1–C4 construction) on GPU in F32. + +Returns the same closure `C_at(uc::Int) -> ComplexF64` as `get_C`. + +# Key differences from `get_C` +- All `apply`/`truncate!` operations run on GPU tensors (F32). +- `cutoff` is passed directly; a warning is emitted if `cutoff < 1e-6` since + ComplexF32 eigendecompositions can produce NaN on large systems at tight cutoffs. +- The projector is built on CPU first (via `_get_projector`), then moved to GPU. + For method=:mcweeny this means the purification loop runs on GPU. +- `sequential` mode is not supported (non-sequential quenched is always used). + +All keyword arguments are identical to `get_C`. +""" +function get_C_gpu(H::TBHamiltonian, xfunc=nothing, yfunc=nothing; + method::Symbol = :mcweeny, + fermi::Real = 0.0, + l = nothing, + Λ::Real = 10, + Lambda = nothing, + Nchebychev::Int = 300, + maxdim::Int = 500, + cutoff::Real = 1e-8, + Nel = nothing, + quenched::Bool = true, + printinfo::Bool = false) + + _check_gpu("get_C_gpu") + cutoff < 1e-6 && @warn "get_C_gpu: cutoff=$cutoff is below 1e-6; ComplexF32 eigendecomposition may produce NaN on large systems — consider cutoff ≥ 1e-4." + Λ_val = Lambda !== nothing ? Float64(Lambda) : Float64(Λ) + ak = (cutoff=Float64(cutoff), maxdim=maxdim) + + # ── geometry ────────────────────────────────────────────────────────────── + if xfunc === nothing || yfunc === nothing + geom = H.geometry_uc !== nothing ? H.geometry_uc : + H.geometry !== nothing ? H.geometry : + error("get_C_gpu: H has no geometry; provide xfunc and yfunc explicitly.") + xfunc === nothing && (xfunc = (i, _) -> geom(i + 1)[1]) + yfunc === nothing && (yfunc = (i, _) -> geom(i + 1)[2]) + end + + # ── sublattice bookkeeping (mirrors get_C_op_MPO_from_P) ────────────────── + L = H.L + l_bits = l === nothing ? div(L, 2) : l + L_chain = 2^l_bits + sites = H.sites + n_sub = length(sites) > L ? dim(sites[L+1]) : 1 + has_sub = n_sub > 1 + pos_sites = has_sub ? collect(sites[1:L]) : collect(sites) + sub_s = has_sub ? sites[L+1] : nothing + I_mat = has_sub ? Matrix{Float64}(LinearAlgebra.I, n_sub, n_sub) : nothing + + xfunc_pos = has_sub ? ((i, Lc) -> xfunc(i * n_sub, Lc)) : xfunc + yfunc_pos = has_sub ? ((i, Lc) -> yfunc(i * n_sub, Lc)) : yfunc + + a1x = xfunc_pos(1, L_chain) - xfunc_pos(0, L_chain) + a1y = yfunc_pos(1, L_chain) - yfunc_pos(0, L_chain) + a2x = xfunc_pos(L_chain, L_chain) - xfunc_pos(0, L_chain) + a2y = yfunc_pos(L_chain, L_chain) - yfunc_pos(0, L_chain) + A_cell = abs(a1x * a2y - a1y * a2x) + + # ── projector: build initial guess on CPU, purify on GPU ────────────────── + printinfo && println("[gpu] Building initial projector guess (CPU)...") + _ensure_scale!(H) + P0_cpu = purification_initial_guess(H; ϵF=fermi, maxdim=maxdim, cutoff=cutoff) + P = _to_gpu_mpo(P0_cpu) + + if method == :mcweeny + printinfo && println("[gpu] McWeeny purification on GPU...") + maxiters_mc = 30 + tol_mc = 1e-5 + for iter in 1:maxiters_mc + P2 = apply(P, P; ak...) + ITensorMPS.truncate!(P2; cutoff=Float64(cutoff)) + err = let diff = +(P2, -1.0 * P; cutoff=1e-12) + n = norm(diff); d = norm(P); d > 0 ? n / d : n + end + printinfo && iter % 5 == 0 && + println(" McWeeny iter $iter: err=$err maxlinkdim=$(maxlinkdim(P))") + err < tol_mc && break + P_inte = +(3.0 * P, -2.0 * P2; cutoff=Float64(cutoff)) + P = apply(P, P_inte; ak...) + ITensorMPS.truncate!(P; cutoff=Float64(cutoff)) + _gpu_gc!() + end + H._density_cache = nothing # don't cache GPU MPO in CPU field + elseif method == :sp2 + Nel_val = Nel === nothing ? H.N ÷ 2 : Int(Nel) + printinfo && println("[gpu] SP2 purification on GPU (Nel=$Nel_val)...") + maxiters_sp = 40 + tol_sp = 1e-5 + for iter in 1:maxiters_sp + P2 = apply(P, P; ak...) + ITensorMPS.truncate!(P2; cutoff=Float64(cutoff)) + err = let diff = +(P2, -1.0 * P; cutoff=1e-12) + n = norm(diff); d = norm(P); d > 0 ? n / d : n + end + printinfo && println(" SP2 iter $iter: err=$err maxlinkdim=$(maxlinkdim(P))") + err < tol_sp && break + tr_P2 = real(tr(P2)) + if tr_P2 >= Nel_val + P = P2 + else + P = +(2.0 * P, -1.0 * P2; ak...) + ITensorMPS.truncate!(P; cutoff=Float64(cutoff)) + end + _gpu_gc!() + end + elseif method == :KPM + # KPM: use CPU projector, just move to GPU + P_cpu = _get_projector(H; method=:KPM, fermi=fermi, Nchebychev=Nchebychev, + maxdim=maxdim, cutoff=cutoff) + P = _to_gpu_mpo(P_cpu) + else + error("get_C_gpu: unknown method :$method. Choose :mcweeny, :sp2, or :KPM") + end + printinfo && println("[gpu] Projector ready, maxlinkdim=$(maxlinkdim(P))") + + # ── Q = I − P on GPU ────────────────────────────────────────────────────── + I_gpu = _to_gpu_mpo(MPO(collect(sites), "Id")) + Q = +(I_gpu, -1.0 * P; ak...) + ITensorMPS.truncate!(Q; cutoff=Float64(cutoff)) + _gpu_gc!() + + # ── basis MPS closure (returns GPU MPS) ─────────────────────────────────── + make_alpha_gpu = if has_sub + all_sites = collect(sites) + alpha -> begin + n_cell = (alpha - 1) ÷ n_sub + sub = (alpha - 1) % n_sub + 1 + pos_bits = [((n_cell >> (L - i)) & 1) + 1 for i in 1:L] + _to_gpu_mps(_product_state_mps(all_sites, [pos_bits; sub])) + end + else + alpha -> _to_gpu_mps(binary_to_MPS(alpha - 1, L, collect(sites))) + end + + if quenched + # ── position operators on GPU ────────────────────────────────────────── + sinX_gpu = _to_gpu_mpo(has_sub ? + postpend_op(get_sinx_op(L, pos_sites, L_chain, Λ_val, xfunc_pos), sub_s, I_mat) : + get_sinx_op(L, pos_sites, L_chain, Λ_val, xfunc_pos)) + cosX_gpu = _to_gpu_mpo(has_sub ? + postpend_op(get_cosx_op(L, pos_sites, L_chain, Λ_val, xfunc_pos), sub_s, I_mat) : + get_cosx_op(L, pos_sites, L_chain, Λ_val, xfunc_pos)) + sinY_gpu = _to_gpu_mpo(has_sub ? + postpend_op(get_siny_op(L, pos_sites, L_chain, Λ_val, yfunc_pos), sub_s, I_mat) : + get_siny_op(L, pos_sites, L_chain, Λ_val, yfunc_pos)) + cosY_gpu = _to_gpu_mpo(has_sub ? + postpend_op(get_cosy_op(L, pos_sites, L_chain, Λ_val, yfunc_pos), sub_s, I_mat) : + get_cosy_op(L, pos_sites, L_chain, Λ_val, yfunc_pos)) + printinfo && println("[gpu] Position operators on GPU.") + + # ── 8 intermediate MPO products ──────────────────────────────────────── + sinY_P = apply(sinY_gpu, P; ak...); cosY_P = apply(cosY_gpu, P; ak...) + P_sinX = apply(P, sinX_gpu; ak...); P_cosX = apply(P, cosX_gpu; ak...) + sinY_Q = apply(sinY_gpu, Q; ak...); cosY_Q = apply(cosY_gpu, Q; ak...) + Q_sinX = apply(Q, sinX_gpu; ak...); Q_cosX = apply(Q, cosX_gpu; ak...) + printinfo && println("[gpu] 8 intermediate MPO products done.") + _gpu_gc!() + + # C1 = Q sinX P sinY Q − P sinX Q sinY P + C1 = +(apply(apply(Q_sinX, P; ak...), sinY_Q; ak...), + -apply(apply(P_sinX, Q; ak...), sinY_P; ak...); ak...) + ITensorMPS.truncate!(C1; cutoff=Float64(cutoff)) + printinfo && println("[gpu] C1 done, maxlinkdim=$(maxlinkdim(C1))") + _gpu_gc!() + + # C2 = Q cosX P cosY Q − P cosX Q cosY P + C2 = +(apply(apply(Q_cosX, P; ak...), cosY_Q; ak...), + -apply(apply(P_cosX, Q; ak...), cosY_P; ak...); ak...) + ITensorMPS.truncate!(C2; cutoff=Float64(cutoff)) + printinfo && println("[gpu] C2 done, maxlinkdim=$(maxlinkdim(C2))") + _gpu_gc!() + + # C3 = Q sinX P cosY Q − P sinX Q cosY P + C3 = +(apply(apply(Q_sinX, P; ak...), cosY_Q; ak...), + -apply(apply(P_sinX, Q; ak...), cosY_P; ak...); ak...) + ITensorMPS.truncate!(C3; cutoff=Float64(cutoff)) + printinfo && println("[gpu] C3 done, maxlinkdim=$(maxlinkdim(C3))") + _gpu_gc!() + + # C4 = Q cosX P sinY Q − P cosX Q sinY P + C4 = +(apply(apply(Q_cosX, P; ak...), sinY_Q; ak...), + -apply(apply(P_cosX, Q; ak...), sinY_P; ak...); ak...) + ITensorMPS.truncate!(C4; cutoff=Float64(cutoff)) + printinfo && println("[gpu] C4 done. Closure ready.") + _gpu_gc!() + + calculate_chern_number = uc -> begin + sum(sub -> begin + alpha = (uc - 1) * n_sub + sub + α = make_alpha_gpu(alpha) + x = xfunc(alpha - 1, L_chain) + y = yfunc(alpha - 1, L_chain) + cos_x, sin_x = cos(x / Λ_val), sin(x / Λ_val) + cos_y, sin_y = cos(y / Λ_val), sin(y / Λ_val) + ch = cos_x * cos_y * inner(α', C1, α) + ch += sin_x * sin_y * inner(α', C2, α) + ch -= cos_x * sin_y * inner(α', C3, α) + ch -= sin_x * cos_y * inner(α', C4, α) + ch * 2im * π * Λ_val^2 + end, 1:n_sub) / A_cell + end + + else + # flat (non-quenched) mode + x_op = has_sub ? + postpend_op(get_diagonal_mpo(L, pos_sites, i -> xfunc_pos(i-1, L_chain)), sub_s, I_mat) : + get_diagonal_mpo(L, pos_sites, i -> xfunc_pos(i-1, L_chain)) + y_op = has_sub ? + postpend_op(get_diagonal_mpo(L, pos_sites, i -> yfunc_pos(i-1, L_chain)), sub_s, I_mat) : + get_diagonal_mpo(L, pos_sites, i -> yfunc_pos(i-1, L_chain)) + x_gpu = _to_gpu_mpo(x_op) + y_gpu = _to_gpu_mpo(y_op) + + T1 = apply(Q, apply(x_gpu, apply(P, apply(y_gpu, Q; ak...); ak...); ak...); ak...) + T2 = apply(P, apply(x_gpu, apply(Q, apply(y_gpu, P; ak...); ak...); ak...); ak...) + C_op = 2im * π * +(T1, -1.0 * T2; ak...) + ITensorMPS.truncate!(C_op; cutoff=Float64(cutoff)) + _gpu_gc!() + + calculate_chern_number = uc -> begin + sum(sub -> begin + alpha = (uc - 1) * n_sub + sub + α = make_alpha_gpu(alpha) + inner(α', C_op, α) + end, 1:n_sub) / A_cell + end + end + + return calculate_chern_number +end + + +# ============================================================ +# GPU magnetic Hubbard SCF +# ============================================================ + +# GPU McWeeny purification of a (rescaled) single-channel Hamiltonian. +# Builds the initial guess on CPU, moves it to GPU, iterates the McWeeny map +# on GPU (F32), and returns the purified density matrix back on CPU +# (ComplexF64). Mirrors the purification loop in `get_C_gpu`. +function _mcweeny_purify_gpu(H::TBHamiltonian; ϵF::Real, + maxdim::Int, cutoff::Real, + maxiters::Int, tol::Real, + return_gpu::Bool = false) + ak = (cutoff = Float64(cutoff), maxdim = maxdim) + P0_cpu = purification_initial_guess(H; ϵF=ϵF, maxdim=maxdim, cutoff=Float64(cutoff)) + P = _to_gpu_mpo(P0_cpu) + for iter in 1:maxiters + P2 = apply(P, P; ak...) + ITensorMPS.truncate!(P2; cutoff=Float64(cutoff)) + err = let diff = +(P2, -1.0 * P; cutoff=1e-12) + n = norm(diff); d = norm(P); d > 0 ? n / d : n + end + err < tol && break + P_inte = +(3.0 * P, -2.0 * P2; cutoff=Float64(cutoff)) + P = apply(P, P_inte; ak...) + ITensorMPS.truncate!(P; cutoff=Float64(cutoff)) + _gpu_gc!() + end + return return_gpu ? P : _to_cpu_mpo(P) +end + +function _purification_initial_guess_gpu(H_mpo_gpu::MPO, sites; + ϵF::Real, + scale::Real, + center::Real = 0.0, + maxdim::Int, + cutoff::Real, + Id_gpu::Union{Nothing,MPO} = nothing) + scale == 0 && error("_purification_initial_guess_gpu: scale must be non-zero.") + Id = Id_gpu === nothing ? _to_gpu_mpo(MPO(collect(sites), "Id")) : Id_gpu + coeff_I = 0.5 + (ϵF + center) / (2 * scale) + coeff_H = -0.5 / scale + ρ0 = +(coeff_I * Id, coeff_H * H_mpo_gpu; cutoff=Float64(cutoff)) + ITensorMPS.truncate!(ρ0; maxdim=maxdim, cutoff=Float64(cutoff)) + return ρ0 +end + +function _mcweeny_purify_mpo_gpu(H_mpo_gpu::MPO, sites; + ϵF::Real, + scale::Real, + center::Real = 0.0, + Id_gpu::Union{Nothing,MPO} = nothing, + maxdim::Int, + cutoff::Real, + maxiters::Int, + tol::Real) + ak = (cutoff = Float64(cutoff), maxdim = maxdim) + P = _purification_initial_guess_gpu(H_mpo_gpu, sites; + ϵF=ϵF, scale=scale, center=center, maxdim=maxdim, + cutoff=cutoff, Id_gpu=Id_gpu) + for iter in 1:maxiters + P2 = apply(P, P; ak...) + ITensorMPS.truncate!(P2; cutoff=Float64(cutoff)) + err = let diff = +(P2, -1.0 * P; cutoff=1e-12) + n = norm(diff); d = norm(P); d > 0 ? n / d : n + end + err < tol && break + P_inte = +(3.0 * P, -2.0 * P2; cutoff=Float64(cutoff)) + P = apply(P, P_inte; ak...) + ITensorMPS.truncate!(P; cutoff=Float64(cutoff)) + _gpu_gc!() + end + return P +end + +""" + scf_magnetic_hubbard_gpu(H0, U; kwargs...) -> NamedTuple + +GPU-accelerated two-channel collinear magnetic mean-field loop for the on-site +Hubbard model. The SCF iteration keeps the density profiles, Hartree MPOs, +Hamiltonian MPOs, density matrices, RMS checks, and mixing on GPU; CPU objects +are built only for initialization and for the compatibility fields returned at +the end. + +```text +H_up = H0_up + U·diag(n_dn − background) +H_dn = H0_dn + U·diag(n_up − background) +``` + +Only `density_method=:mcweeny` is supported here (grand-canonical at `fermi`); +for particle-number-fixed SP2 use the CPU `scf_magnetic_hubbard`. A concrete +purification `scale` is required so the GPU initial guess can be formed without +estimating spectral bounds on CPU during the loop. + +ComplexF32 eigen-decompositions can NaN at very tight cutoffs; a warning is +emitted if `cutoff < 1e-5`, but the requested `cutoff` is used as-is. + +Post-convergence observables are intentionally separate. Use +[`get_scf_magnetization_gpu`](@ref) or [`get_scf_bands_gpu`](@ref) on the +returned result when you want those GPU-accelerated diagnostics. +""" +function scf_magnetic_hubbard_gpu(H0::TBHamiltonian, U::Union{Number, MPO}; + initial_up::Union{Nothing,MPS}=nothing, + initial_dn::Union{Nothing,MPS}=nothing, + background::Real = 0.5, + Nel_up::Int = H0.N ÷ 2, + Nel_dn::Int = H0.N ÷ 2, + fermi::Real = 0.0, + scale::Union{Nothing,Real} = H0.scale == 0.0 ? nothing : H0.scale, + purification_scale_padding::Real = 1.05, + max_scf_iter::Int = 30, + scf_tol::Real = 1e-6, + mix::Real = 0.4, + maxdim::Int = 100, + cutoff::Real = 1e-8, + purif_maxiter::Int = 40, + purif_tol::Real = 1e-6, + verbose::Bool = true) + _check_gpu("scf_magnetic_hubbard_gpu") + cutoff < 1e-5 && @warn "scf_magnetic_hubbard_gpu: cutoff=$cutoff is below 1e-5; ComplexF32 eigen-decomposition may produce NaN — consider cutoff ≥ 1e-5." + + H0_up, H0_dn = _split_spin_channels(H0) + sites = H0_up.sites + scale === nothing && + error("scf_magnetic_hubbard_gpu: pass a concrete nonzero scale to keep the SCF loop GPU-resident.") + scale_eff = Float64(scale) * Float64(purification_scale_padding) + scale_eff == 0.0 && + error("scf_magnetic_hubbard_gpu: scale must be nonzero.") + if initial_up === nothing || initial_dn === nothing + rho_up, rho_dn = staggered_magnetic_initial(H0; background=background) + initial_up === nothing || (rho_up = initial_up) + initial_dn === nothing || (rho_dn = initial_dn) + else + rho_up, rho_dn = initial_up, initial_dn + end + + rho_up_gpu = _to_gpu_mps(rho_up) + rho_dn_gpu = _to_gpu_mps(rho_dn) + bg_gpu = _to_gpu_mps(constant_mps(collect(sites), background)) + H0_up_gpu = _to_gpu_mpo(H0_up.mpo) + H0_dn_gpu = _to_gpu_mpo(H0_dn.mpo) + Id_gpu = _to_gpu_mpo(MPO(collect(sites), "Id")) + U_gpu = U isa MPO ? _to_gpu_mpo(U) : nothing + + history = NamedTuple[] + density_up_mpo_gpu = nothing + density_dn_mpo_gpu = nothing + Hup_mpo_gpu = H0_up_gpu + Hdn_mpo_gpu = H0_dn_gpu + err = Inf + + function _result(converged::Bool, iters::Int) + density_up_mpo = density_up_mpo_gpu === nothing ? nothing : _to_cpu_mpo(density_up_mpo_gpu) + density_dn_mpo = density_dn_mpo_gpu === nothing ? nothing : _to_cpu_mpo(density_dn_mpo_gpu) + Hup = _copy_with_mpo(H0_up, _to_cpu_mpo(Hup_mpo_gpu); scale=scale_eff, center=0.0) + Hdn = _copy_with_mpo(H0_dn, _to_cpu_mpo(Hdn_mpo_gpu); scale=scale_eff, center=0.0) + return ( + converged=converged, + iterations=iters, + rms_error=err, + rho_up=_to_cpu_mps(rho_up_gpu), + rho_dn=_to_cpu_mps(rho_dn_gpu), + density_up_mpo=density_up_mpo, + density_dn_mpo=density_dn_mpo, + H_up=Hup, + H_dn=Hdn, + rho_up_gpu=rho_up_gpu, + rho_dn_gpu=rho_dn_gpu, + density_up_mpo_gpu=density_up_mpo_gpu, + density_dn_mpo_gpu=density_dn_mpo_gpu, + H_up_mpo_gpu=Hup_mpo_gpu, + H_dn_mpo_gpu=Hdn_mpo_gpu, + history=history, + ) + end + + for iter in 1:max_scf_iter + V_up_gpu = U isa MPO ? + _hartree_mpo_from_density_gpu(rho_dn_gpu, U_gpu, sites, bg_gpu; + maxdim=maxdim, cutoff=cutoff) : + _local_hartree_from_density_gpu(rho_dn_gpu, sites, U, bg_gpu; + maxdim=maxdim, cutoff=cutoff) + V_dn_gpu = U isa MPO ? + _hartree_mpo_from_density_gpu(rho_up_gpu, U_gpu, sites, bg_gpu; + maxdim=maxdim, cutoff=cutoff) : + _local_hartree_from_density_gpu(rho_up_gpu, sites, U, bg_gpu; + maxdim=maxdim, cutoff=cutoff) + + Hup_mpo_gpu = +(H0_up_gpu, V_up_gpu; maxdim=maxdim, cutoff=Float64(cutoff)) + Hdn_mpo_gpu = +(H0_dn_gpu, V_dn_gpu; maxdim=maxdim, cutoff=Float64(cutoff)) + + density_up_mpo_gpu = _mcweeny_purify_mpo_gpu(Hup_mpo_gpu, sites; + ϵF=fermi, scale=scale_eff, center=0.0, Id_gpu=Id_gpu, + maxdim=maxdim, cutoff=cutoff, maxiters=purif_maxiter, + tol=Float64(purif_tol)) + density_dn_mpo_gpu = _mcweeny_purify_mpo_gpu(Hdn_mpo_gpu, sites; + ϵF=fermi, scale=scale_eff, center=0.0, Id_gpu=Id_gpu, + maxdim=maxdim, cutoff=cutoff, maxiters=purif_maxiter, + tol=Float64(purif_tol)) + + rho_up_new_gpu = density_profile_from_dm_gpu(density_up_mpo_gpu, sites; + maxdim=maxdim, cutoff=cutoff) + rho_dn_new_gpu = density_profile_from_dm_gpu(density_dn_mpo_gpu, sites; + maxdim=maxdim, cutoff=cutoff) + + err_up = _rms_error_gpu(rho_up_new_gpu, rho_up_gpu) + err_dn = _rms_error_gpu(rho_dn_new_gpu, rho_dn_gpu) + err = sqrt((err_up^2 + err_dn^2) / 2) + particle_err = abs(real(tr(density_up_mpo_gpu)) - float(Nel_up)) + + abs(real(tr(density_dn_mpo_gpu)) - float(Nel_dn)) + + push!(history, (iter=iter, rms_error=err, rms_up=err_up, rms_dn=err_dn, + particle_error=particle_err)) + verbose && println("magnetic SCF (gpu) iter=$iter rms=$err particle_err=$particle_err") + + rho_up_mixed = +(mix * rho_up_new_gpu, (1.0 - mix) * rho_up_gpu; + maxdim=maxdim, cutoff=Float64(cutoff)) + rho_dn_mixed = +(mix * rho_dn_new_gpu, (1.0 - mix) * rho_dn_gpu; + maxdim=maxdim, cutoff=Float64(cutoff)) + + rho_up_gpu, rho_dn_gpu = rho_up_mixed, rho_dn_mixed + _gpu_gc!() + err < scf_tol && return _result(true, iter) + end + + return _result(false, max_scf_iter) +end + +# Thin 2D-grid wrapper around the shared geometry-aware planner (Utils.jl). +# Used by get_scf_magnetization_gpu; returns (centers, groups) of unit-cell +# indices laid out on a num_x × num_y grid (or x_groups override). +function _tb_spatial_groups_gpu(sites; + num_x::Int = 0, + num_y::Union{Nothing,Int} = nothing, + num_avg::Int = 1, + x_start::Int = 1, + x_end::Int = prod(dim(s) for s in sites), + x_groups = nothing, + box_half::Int = 0, + Lx::Union{Nothing,Int} = nothing) + L = length(sites) + plan = spatial_sampling_plan(L; + Lx = something(Lx, div(L, 2)), + grid = x_groups === nothing, + num_x = num_x, num_y = num_y, num_avg = num_avg, + x_start = x_start, x_end = x_end, + x_groups = x_groups, box_half = box_half) + return plan.centers, plan.groups +end + +""" + get_scf_magnetization_gpu(res; kwargs...) -> (values, centers, groups, n_up, n_dn) + +Sample the converged magnetic SCF density matrices on GPU and extract only the +final scalar values. If `res` carries GPU density MPOs from +`scf_magnetic_hubbard_gpu`, they are reused directly; otherwise the CPU density +MPOs are uploaded once. Each sampled point is evaluated in the same big-endian +real-space convention as `binary_to_MPS`. +""" +function get_scf_magnetization_gpu(res; + num_x::Int = 0, + num_y::Union{Nothing,Int} = nothing, + num_avg::Int = 1, + x_start::Int = 1, + x_end::Int = prod(dim(s) for s in res.H_up.sites), + x_groups = nothing, + box_half::Int = 0, + Lx::Union{Nothing,Int} = nothing) + up_mpo = hasproperty(res, :density_up_mpo_gpu) && res.density_up_mpo_gpu !== nothing ? + res.density_up_mpo_gpu : res.density_up_mpo + dn_mpo = hasproperty(res, :density_dn_mpo_gpu) && res.density_dn_mpo_gpu !== nothing ? + res.density_dn_mpo_gpu : res.density_dn_mpo + + up_mpo === nothing && + error("get_scf_magnetization_gpu: res.density_up_mpo is missing.") + dn_mpo === nothing && + error("get_scf_magnetization_gpu: res.density_dn_mpo is missing.") + + sites = res.H_up.sites + centers, groups = _tb_spatial_groups_gpu(sites; + num_x=num_x, num_y=num_y, num_avg=num_avg, x_start=x_start, x_end=x_end, + x_groups=x_groups, box_half=box_half, Lx=Lx) + + up_diag_gpu = density_profile_from_dm_gpu(up_mpo, sites) + dn_diag_gpu = density_profile_from_dm_gpu(dn_mpo, sites) + + n_up = Float64[ + sum(_eval_mps_bigendian_gpu(up_diag_gpu, x - 1) for x in grp) / length(grp) + for grp in groups + ] + n_dn = Float64[ + sum(_eval_mps_bigendian_gpu(dn_diag_gpu, x - 1) for x in grp) / length(grp) + for grp in groups + ] + values = (n_up .- n_dn) ./ 2 + _gpu_gc!() + return (values=values, centers=centers, groups=groups, n_up=n_up, n_dn=n_dn) +end + +""" + get_scf_bands_gpu(res, Ncheb, omega; kwargs...) -> (Ak, omega, ticks, labels) + +Compute spin-summed mean-field bands from a converged magnetic SCF result. This +is deliberately separate from `scf_magnetic_hubbard_gpu`: it initializes from +the CPU `res.H_up`/`res.H_dn`, then each `get_bands_gpu` call uploads once and +keeps the Chebyshev/QFT accumulation on GPU, extracting only scalars. +""" +function get_scf_bands_gpu(res, Ncheb::Int, omega; kwargs...) + rb_up = get_bands_gpu(res.H_up, Ncheb, omega; kwargs...) + rb_dn = get_bands_gpu(res.H_dn, Ncheb, omega; kwargs...) + Ak_up = rb_up isa NamedTuple ? rb_up.Ak : rb_up + Ak_dn = rb_dn isa NamedTuple ? rb_dn.Ak : rb_dn + return (Ak = Ak_up .+ Ak_dn, + omega = collect(omega), + ticks = rb_up isa NamedTuple ? rb_up.ticks : nothing, + labels = rb_up isa NamedTuple ? rb_up.labels : nothing) +end From f2f2964deadc6c0596cfeebeb8ec537ceb72eab9 Mon Sep 17 00:00:00 2001 From: Anouar Moustaj Date: Fri, 12 Jun 2026 14:50:41 +0300 Subject: [PATCH 5/7] include GPU_tk.jl --- src/TensorBinding.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/TensorBinding.jl b/src/TensorBinding.jl index aa87a11..26be19a 100644 --- a/src/TensorBinding.jl +++ b/src/TensorBinding.jl @@ -53,5 +53,6 @@ include("physics/NH_tk.jl") include("physics/QFT_tk.jl") include("physics/Supercond_tk.jl") #include("RSI_tk.jl") +include("GPU_tk.jl") end From 9464ddbc01f49ed7ccae4869fe0b3baf43c1dfea Mon Sep 17 00:00:00 2001 From: Anouar Moustaj Date: Fri, 12 Jun 2026 14:52:18 +0300 Subject: [PATCH 6/7] Add spatial_sampling_plan: shared geometry-aware sampling for LDOS/SCF maps Adds a single planner used by every spatial sampler (eval_mps_spatial here, plus get_ldos_spatial, get_ldos_spatial_gpu, get_scf_magnetization_gpu) so they share one geometry-aware notion of "where to sample" and "how to reduce the cells under each pixel." - spatial_sampling_plan(L; ...) - builds sample centers/groups for a 2^Lx x 2^Ly system. Supports two reduction modes: - :point (default) - sample/box-average at grid positions; cheap but aliases thin features on a coarse grid. - :block - gap-free coarse-graining: integrate each block by tracing out the within-block position bits (cost independent of block size), for large-scale maps of thin edge/domain-wall features. Also decides whether multi-atom unit cells are sublattice-:resolved (per-atom columns) or :averaged (one value per unit cell), or :auto based on sampling resolution. Extensive docstring documents all three procedures with usage examples. - _eval_block_mps(A, ixp, iyp, a, b, Lx, Ly) - CPU block-integration MPS evaluator backing reduce=:block. - eval_mps_spatial(A; ...) - new spatial sampler for profile MPS (e.g. SCF density/magnetization), built on spatial_sampling_plan; same num_x/num_avg/x_groups/box_half API as get_ldos_spatial but evaluates via eval_mps directly (no KPM recursion). --- src/core/Utils.jl | 290 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 290 insertions(+) diff --git a/src/core/Utils.jl b/src/core/Utils.jl index b42444a..854ec73 100644 --- a/src/core/Utils.jl +++ b/src/core/Utils.jl @@ -321,6 +321,296 @@ function eval_mps(A::MPS, n::Int) return real(inner(psi, A)) end +# Block-integrated MPS element (reduce=:block): the sum of `A` over one coarse +# block, obtained by tracing out the within-block position bits (contracted with +# [1,1]) and pinning the kept top a/b block bits to the coarse pixel (ixp, iyp). +# Big-endian site order [iy_MSB..iy_LSB, ix_MSB..ix_LSB]: sites 1..Ly carry iy, +# Ly+1..L carry ix. See [`spatial_sampling_plan`](@ref) `reduce=:block`. +function _eval_block_mps(A::MPS, ixp::Int, iyp::Int, + a::Int, b::Int, Lx::Int, Ly::Int) + s = siteinds(A) + ElT = eltype(A[1]) + L = Lx + Ly + acc = ITensor(one(ElT)) + for i in 1:L + v_arr = zeros(ElT, dim(s[i])) + if i <= b # keep: iy block bit (b - i) + v_arr[((iyp >> (b - i)) & 1) + 1] = one(real(ElT)) + elseif i <= Ly # sum: iy within-block bit + v_arr .= one(real(ElT)) + elseif i <= Ly + a # keep: ix block bit (a - (i - Ly)) + v_arr[((ixp >> (a - (i - Ly))) & 1) + 1] = one(real(ElT)) + else # sum: ix within-block bit + v_arr .= one(real(ElT)) + end + acc *= A[i] * ITensor(v_arr, s[i]) + end + return real(scalar(acc)) +end + +""" + spatial_sampling_plan(L; Lx, grid, reduce, n_sub, num_x, num_y, num_avg, + x_start, x_end, xwin, ywin, x_groups, box_half, sublattice) + -> (; centers, groups, resolve_sublattice, n_sub, stride_x, stride_y, + grid, reduce, a, b) + +Geometry-aware real-space sampling plan shared by every spatial sampler +([`eval_mps_spatial`](@ref), `get_ldos_spatial`, `get_ldos_spatial_gpu`, +`get_scf_magnetization_gpu`). It decides **where** to sample, **how** each output +pixel reduces the cells under it (`reduce`), and — for multi-atom unit cells — +whether to **resolve** or **average** the sublattice. + +# The three sampling procedures (`reduce`) + +A spatial map of a `2^Lx × 2^Ly`-unit-cell system at a coarse output resolution +can reduce the cells beneath each pixel in three qualitatively different ways. +The right choice depends on whether the quantity is *smooth on the large scale* +(e.g. a Chern marker, an SCF density envelope) or a *thin feature on a flat +background* (e.g. in-gap edge/domain-wall LDOS, width ξ ≪ system size). + +1. **`:point` (default) — point / box sampling.** + Lay out `num_x[×num_y]` sample positions and read the profile *at* each one. + With `box_half > 0` each pixel is the **mean** over a `(2·box_half+1)²` + neighbourhood (smoothing). Cost ∝ (number of pixels) × (box cells). + + *Aliasing caveat.* The pixels probe only the cells they land on (± `box_half`). + On a grid coarser than a feature's width this **misses** thin features that + fall between pixels: a domain-wall LDOS channel of width ξ sampled at stride + `s ≫ ξ` is caught only on the rare pixel within `box_half` of it. Making the + box *tile* the plane (`box_half ≈ s/2`) closes the gaps but then evaluates + essentially every cell — i.e. full-resolution cost. Use `:point` for smooth + quantities or for a fully-resolved zoom (`grid=true` + a small window). + +2. **`:block` — block integration (gap-free coarse-graining).** + Partition the system into `num_x × num_y` equal blocks (`num_x = 2^a`, + `num_y = 2^b`, powers of two) and report, per pixel, the **sum** over its + whole block. This is computed by *tracing out the low-order position bits* + (contracting the within-block bits of the profile MPS with `[1,1]` and keeping + the `a + b` high-order block bits) — a partial contraction, **not** a per-cell + sweep, so the cost is independent of block size and scales to `Lx, Ly ≈ 14+`. + + Because every cell belongs to exactly one block, a thin feature **cannot fall + between pixels** — whichever blocks it threads light up, on an otherwise dark + (gapped) background. This is the tool for imaging edge / domain-wall networks + on a heavily downsampled map. Block centres are reported in `centers`; the + per-axis block widths are `stride_x = 2^(Lx-a)`, `stride_y = 2^(Ly-b)`. + +The fields `reduce`, `a`, `b` echo the chosen mode back to the caller; for +`:point` they are `(:point, 0, 0)`. + +# Sublattice resolve vs average + +For a multi-atom unit cell (`n_sub > 1`) the plan also decides whether to +**resolve** the sublattice (one output column per atom) or **average** it (one +value per unit cell, atoms traced out), via `sublattice`: + +- `:auto` (default) — **resolve** only at the atomic scale: consecutive samples + are adjacent unit cells (`:point` with `stride == 1` and `box_half == 0`). + Otherwise (coarse grid, `box_half > 0`, or any `:block` map) **average**, since + the intra-cell sublattice is below the sampling resolution. +- `:resolve` / `:average` force the choice. `n_sub == 1` is always `false`. + +# Layout (`:point` mode) + +`groups`/`centers` are 1-indexed unit-cell indices with `n = ix + iy·2^Lx`. + +- `grid=false` (default) — centers on a **1D linear** sweep of the row-major index + (`x_start`/`x_end`, `num_x` points, `num_avg` sub-probes per block). Stride + `dx = window ÷ num_x`. `Lx` is used only for the optional `box_half` neighbourhood. +- `grid=true` (needs `Lx`) — centers on a **2D xy grid** of `num_x × num_y` + points over the unit-cell window `xwin=(ix0,ix1)`, `ywin=(iy0,iy1)` (0-indexed; + default full system). Per-axis strides `Nx_win÷num_x`, `Ny_win÷num_y`. + +`x_groups` overrides the `:point` layout entirely; the stride is then unknown, so +`:auto` resolves (treats it as atomic) unless `box_half > 0`. +""" +function spatial_sampling_plan(L::Int; + Lx::Union{Nothing,Int} = nothing, + grid::Bool = false, + reduce::Symbol = :point, + n_sub::Int = 1, + num_x::Int = 0, + num_y::Union{Nothing,Int} = nothing, + num_avg::Int = 1, + x_start::Int = 1, + x_end::Int = 2^L, + xwin = nothing, + ywin = nothing, + x_groups = nothing, + box_half::Int = 0, + sublattice::Symbol = :auto) + sublattice in (:auto, :resolve, :average) || + error("spatial_sampling_plan: sublattice must be :auto, :resolve, or :average.") + reduce in (:point, :block) || + error("spatial_sampling_plan: reduce must be :point or :block.") + grid && Lx === nothing && + error("spatial_sampling_plan: grid=true requires Lx (the x-qubit count).") + + # ── :block — coarse-grain by tracing out the within-block position bits ──── + if reduce === :block + Lx === nothing && + error("spatial_sampling_plan: reduce=:block requires Lx.") + Ly = L - Lx + num_x > 0 || + error("spatial_sampling_plan: reduce=:block requires num_x > 0 (a power of two).") + nyv = num_y === nothing ? num_x : num_y + nyv > 0 || + error("spatial_sampling_plan: reduce=:block requires num_y > 0 (a power of two).") + a = round(Int, log2(num_x)) + b = round(Int, log2(nyv)) + 2^a == num_x || + error("spatial_sampling_plan: reduce=:block needs num_x a power of two (got $num_x).") + 2^b == nyv || + error("spatial_sampling_plan: reduce=:block needs num_y a power of two (got $nyv).") + (0 <= a <= Lx) || + error("spatial_sampling_plan: reduce=:block needs 1 <= num_x <= 2^Lx=$(2^Lx).") + (0 <= b <= Ly) || + error("spatial_sampling_plan: reduce=:block needs 1 <= num_y <= 2^Ly=$(2^Ly).") + Nx = 2^Lx + Wx = 2^(Lx - a) # block width in x (unit cells) + Wy = 2^(Ly - b) # block width in y + # Block centre cell, row-major over coarse pixels (ixp fastest): + # col = ixp + iyp*num_x + 1 + centers = Int[(ixp * Wx + Wx ÷ 2) + (iyp * Wy + Wy ÷ 2) * Nx + 1 + for iyp in 0:(2^b - 1) for ixp in 0:(2^a - 1)] + groups = [[c] for c in centers] # nominal; block eval does not use these + resolve = n_sub > 1 && sublattice === :resolve # block is large-scale → average + return (; centers, groups, resolve_sublattice=resolve, n_sub=max(n_sub, 1), + stride_x=Wx, stride_y=Wy, grid=true, reduce=:block, a, b) + end + + stride_x = 1 + stride_y = 1 + stride_known = true + + local centers::Vector{Int} + local groups::Vector{Vector{Int}} + + if x_groups !== nothing + groups = x_groups isa AbstractVector{<:AbstractVector} ? + [collect(Int, g) for g in x_groups] : [[Int(x)] for x in x_groups] + centers = Int[first(g) for g in groups] + stride_known = false # caller-supplied positions: stride is not defined + elseif grid + Nx = 2^Lx + Ny = 2^(L - Lx) + ix0, ix1 = xwin === nothing ? (0, Nx - 1) : (Int(xwin[1]), Int(xwin[2])) + iy0, iy1 = ywin === nothing ? (0, Ny - 1) : (Int(ywin[1]), Int(ywin[2])) + Nx_win = ix1 - ix0 + 1 + Ny_win = iy1 - iy0 + 1 + nx = num_x <= 0 ? Nx_win : min(num_x, Nx_win) + ny = num_y === nothing ? (num_x <= 0 ? Ny_win : min(nx, Ny_win)) : + (num_y <= 0 ? Ny_win : min(num_y, Ny_win)) + stride_x = Nx_win ÷ nx + stride_y = Ny_win ÷ ny + xcenters = nx <= 1 ? [ix0] : round.(Int, range(ix0, ix1; length=nx)) + ycenters = ny <= 1 ? [iy0] : round.(Int, range(iy0, iy1; length=ny)) + centers = Int[ix + iy * Nx + 1 for iy in ycenters for ix in xcenters] + groups = [[c] for c in centers] + else + window = x_end - x_start + 1 + nx = num_x <= 0 ? window : num_x + dx = max(window ÷ nx, 1) + stride_x = dx + dx_sub = max(1, dx ÷ num_avg) + centers = Int[x_start + (i - 1) * dx for i in 1:nx] + groups = [[ x_start + (i - 1) * dx + k * dx_sub + for k in 0:num_avg-1 + if x_start + (i - 1) * dx + k * dx_sub <= x_end ] + for i in 1:nx] + end + + # ── 2D box averaging (periodic wrap) ─────────────────────────────────────── + if box_half > 0 && Lx !== nothing + Nx = 2^Lx + Ny = 2^(L - Lx) + groups = [ + let uc0 = first(grp) - 1 + ix0 = uc0 % Nx + iy0 = uc0 ÷ Nx + unique([mod(ix0 + Δx, Nx) + mod(iy0 + Δy, Ny) * Nx + 1 + for Δy in -box_half:box_half for Δx in -box_half:box_half]) + end + for grp in groups + ] + end + + # ── Sublattice resolve / average decision ────────────────────────────────── + resolve = if n_sub <= 1 + false + elseif sublattice === :resolve + true + elseif sublattice === :average + false + else # :auto + box_half == 0 && + (stride_known ? (stride_x <= 1 && (grid ? stride_y <= 1 : true)) : true) + end + + return (; centers, groups, resolve_sublattice=resolve, n_sub=max(n_sub, 1), + stride_x, stride_y, grid, reduce=:point, a=0, b=0) +end + +""" + eval_mps_spatial(A::MPS; num_x, num_avg, x_start, x_end, x_groups, + box_half, Lx) -> (values, centers, groups) + +Higher-level spatial sampler for a profile MPS such as an SCF occupation/density +profile (`res.rho_up`). It mirrors `get_ldos_spatial`'s `num_x` / `num_avg` / +`x_groups` / `box_half` sampling-and-averaging API, but evaluates the MPS +directly with [`eval_mps`](@ref) instead of running a KPM recursion — so it is +cheap enough to sweep a very large system by sampling a grid of positions and +averaging, rather than evaluating all `2^L` sites. + +For each sampled group of (1-indexed) site coordinates the returned value is the +mean of `eval_mps(A, x-1)` over that group. With `box_half > 0` each sampled +position is expanded into a `(2·box_half+1)²` neighborhood on the 2D grid +(periodic wrap), exactly like `get_ldos_spatial`; this needs the 2D layout, taken +from `Lx` (defaults to `L÷2`, with `Ly = L - Lx`). + +# Keyword arguments +- `num_x` : number of sampled grid positions (default: all `2^L` sites). +- `num_avg` : sub-positions averaged per grid point along the 1D index (stride). +- `x_start`, `x_end` : 1-indexed sampling window (default `1 … 2^L`). +- `x_groups` : explicit groups — a vector of site indices (one per group) or a + vector of vectors (each averaged). Overrides `num_x`/`num_avg`/`x_start`/`x_end`. +- `box_half` : 2D neighborhood half-width for averaging (0 = no box averaging). +- `Lx` : number of x qubits for the 2D layout (default `L÷2`). + +# Returns +- `values` : `Vector{Float64}`, the averaged MPS value per group. +- `centers` : `Vector{Int}`, the 1-indexed center site of each sampled group. +- `groups` : `Vector{Vector{Int}}`, the site indices averaged over per group. + +For a 2D map, the center `(ix, iy)` of group `g` is +`ix = (centers[g]-1) % 2^Lx`, `iy = (centers[g]-1) ÷ 2^Lx`. +""" +function eval_mps_spatial(A::MPS; + num_x::Int = prod(dim(s) for s in siteinds(A)), + num_avg::Int = 1, + x_start::Int = 1, + x_end::Int = prod(dim(s) for s in siteinds(A)), + x_groups = nothing, + box_half::Int = 0, + Lx::Union{Nothing,Int} = nothing) + sites = siteinds(A) + L = length(sites) + + # ── Build groups + grid centers via the shared geometry-aware planner ────── + plan = spatial_sampling_plan(L; + Lx = (box_half > 0 && Lx === nothing) ? L ÷ 2 : Lx, + num_x = num_x, num_avg = num_avg, + x_start = x_start, x_end = x_end, + x_groups = x_groups, box_half = box_half) + centers = plan.centers + groups = plan.groups + + # ── Evaluate + average ───────────────────────────────────────────────────── + values = Float64[ sum(eval_mps(A, x - 1) for x in grp) / length(grp) + for grp in groups ] + return (values=values, centers=centers, groups=groups) +end + """ rms_error(a, b) -> Float64 From 52f1e5cea7314ad277ce6e3a17586bf3e5e828bf Mon Sep 17 00:00:00 2001 From: Anouar Moustaj Date: Fri, 12 Jun 2026 14:57:01 +0300 Subject: [PATCH 7/7] get_ldos_spatial: add reduce=:block (gap-free block-integration via spatial_sampling_plan, requires num_x/num_y powers of two, :mpo mode only) alongside the existing :point sampling, plus sublattice=:resolve /:average/:auto for geometry-aware sublattice handling. New num_y, grid, xwin, ywin, box_half kwargs enable 2D grid/window sampling. accumulate_Tn! is simplified via a new spatial_vals_cpu closure that unifies point vs. block reduction across resolved/averaged sublattice layouts. Docstring rewritten to document the sampling procedures, sublattice decision logic, and resulting shapes. get_dos_stochastic: add dos_weighting=:trace (default, unchanged behavior) / :sample (raw sampled signal before sector-size weighting, for inspecting exciton bound peaks) and HODC reconstruction support via new eta/m_order kwargs. New _dos_weight_matrix helper centralizes the per-kernel weight matrix and normalization denom, replacing the old _kpm_weight_matrix + hardcoded pi^2*Ncheb norm, and is shared with the new exciton LDOS path. Exciton LDOS: replace the cache-based get_exciton_ldos (now internal _get_exciton_ldos_cached, kept for legacy use) with get_exciton_ldos_spatial, an online MPS Chebyshev recursion over one or many bound-pair positions |X,X> with no cache stored on H. New public get_exciton_ldos (scalar and vector-omega forms) wrap get_exciton_ldos_spatial for a single position. Update the Pathway 2/4 overview at the top of the file to match. Minor: fix "HODC" expansion (High-Order Damping Correction -> Higher-Order Delta Chebyshev) in get_ldos_hodc_from_mun's docstring. --- src/solvers/KPM_tk.jl | 455 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 369 insertions(+), 86 deletions(-) diff --git a/src/solvers/KPM_tk.jl b/src/solvers/KPM_tk.jl index 213b150..9b016b0 100644 --- a/src/solvers/KPM_tk.jl +++ b/src/solvers/KPM_tk.jl @@ -154,15 +154,14 @@ Pathway 1 — MPO × MPO cache [legacy / rarely used] O(Ncheb × χ_T²). Prefer Pathways 3 or 4 unless the cache is reused for multiple downstream calls. Kept mainly for legacy compatibility. -Pathway 2 — MPS cache [exciton LDOS, fixed reference state] +Pathway 2 — MPS cache [legacy / fixed reference state] KPM_Tn(H, Ncheb; mode=:mps, psi0=ψ₀) # cache {T_n(H̃)|ψ₀⟩} MPS on H - → get_exciton_ldos(H, X, ω; …) # μₙ = ⟨X|T_n(H̃)|X⟩ reusing cache → get_ldos(H, ω; mode=:mps, psi0=ψ₀) # μₙ = ⟨ψ₀|T_n(H̃)|ψ₀⟩ → scalar Propagates a single reference MPS and stores the full trajectory {|φ_n⟩ = T_n(H̃)|ψ₀⟩} for repeated re-use across many energy queries on the - same state. Natural for exciton LDOS where many sites |X⟩ are probed - sequentially after building the cache once. + same state. Kept for advanced workflows; the public LDOS helpers below avoid + storing this cache. Pathway 3 — Online MPO × MPO [k-space and spatial spectral functions] get_bands(H, Ncheb, D, ωlist; …) # k-resolved A(k,ω), QFT-conjugated @@ -175,6 +174,8 @@ Pathway 3 — Online MPO × MPO [k-space and spatial spectral functions] Pathway 4 — Online MPO × MPS [single-particle default, most memory-efficient] get_ldos_online(H, Ncheb, X, ωlist; …) # LDOS at one site, all ω get_ldos_spatial(H, Ncheb, ωlist; mode=:mps; …) # per-position MPS recursion + get_exciton_ldos_spatial(H, Ncheb, ωlist; …) # bound-pair exciton LDOS + get_exciton_ldos(H, X, ωlist; …) # one-position wrapper get_dos_stochastic(H, Ncheb, ωlist; …) # stochastic trace DOS Propagates MPS states rather than full MPOs: only 3 MPS alive per sample/site. @@ -654,35 +655,62 @@ end Spatially-resolved LDOS, real-space analogue of `get_bands`. -**Return shape** +**Sampling procedures (`reduce`)** — full detail in [`spatial_sampling_plan`](@ref). -- **No sublattice DOF** (`H.sublattice_s === nothing`): `(Nω × ng)` where `ng = num_x`. -- **With sublattice DOF** (`H.sublattice_s` set): `(Nω × ng×n_sub)` where `n_sub = - dim(H.sublattice_s)`. Columns are interleaved in atom order matching the - corresponding `*_positions` function: `[A₀, B₀, A₁, B₁, …]` for 2-sublattice - lattices, `[A₀, B₀, C₀, …]` for 3-sublattice ones. Pair directly with - `plot_ldos_2d` which consumes this column layout without further transformation. +- `:point` (default) — read the LDOS *at* `num_x[×num_y]` sample cells; with + `box_half > 0` each pixel is the mean over a `(2·box_half+1)²` box. Cheap, but + a grid coarser than a feature's width **aliases** it (thin in-gap edge / + domain-wall channels can fall between pixels and be missed). +- `:block` — partition the system into `num_x × num_y` blocks (powers of two) and + report the **integral** over each block, computed by tracing out the + within-block position bits (a partial contraction, cost independent of block + size). Gap-free: every cell belongs to one block, so a thin feature on a gapped + background **cannot** be missed. Use it for large-scale maps of edge networks. + Output columns are row-major over coarse pixels (`col = ixp + iyp·num_x + 1`); + block centres come from the plan. `:mpo` mode only. -**Sublattice auto-detection** +**Return shape (sublattice geometry-awareness)** -`H.sublattice_s` is detected automatically. When set: -- `proj_sl=nothing` (default): a single Chebyshev pass fills *all* sublattice columns. -- `proj_sl=k`: only sublattice `k` columns are filled; all others are zero. - `sublat_proj=true` is accepted for API compatibility but is no longer required. +For a multi-atom unit cell (`H.sublattice_s` set) the layout depends on the +*sampling scale*, decided by [`spatial_sampling_plan`](@ref) from the local +stride (see `sublattice` below): + +- **resolved** (atomic scale — every unit cell probed, or `proj_sl=k`): + `(Nω × ng×n_sub)`, columns interleaved in atom order matching the + `*_positions` functions — `[A₀, B₀, A₁, B₁, …]` (2-sublattice), + `[A₀, B₀, C₀, …]` (3-sublattice). `proj_sl=k` fills only sublattice `k`. +- **averaged** (large scale — the grid skips unit cells): `(Nω × ng)`, the + sublattice is traced out into one value per unit cell (mean over the `n_sub` + atoms). + +With no sublattice DOF the shape is always `(Nω × ng)`, `ng = num_x`. + +**`sublattice` (resolve vs average)** + +- `:auto` (default) — resolve when sampling at full unit-cell resolution + (`stride == 1`, e.g. zooming a small window and probing every cell); average + whenever the grid is coarser or `box_half > 0`. +- `:resolve` — always emit per-atom columns. `:average` — always trace the + sublattice to one value per cell. `proj_sl=k` always resolves that one atom. **Sampling parameters** -- `num_x` : coarse position count (default `H.N` for full resolution). -- `num_avg` : sub-samples per coarse block for local averaging (default 1). -- `x_start`, `x_end` : 1-indexed position range (defaults: `1` and `H.N`). -- `x_groups` : explicit `Vector{Vector{Int}}` override. +- `num_x`/`num_y` : sample counts (per axis for `grid=true`; `num_x` is the total + for the default 1D linear sweep). Default `H.N` = full resolution. +- `num_avg` : sub-samples per coarse block for local averaging (1D, default 1). +- `x_start`, `x_end` : 1-indexed linear position range (1D layout). +- `grid` : `true` lays centers on a 2D `num_x × num_y` unit-cell grid. +- `xwin`, `ywin` : 0-indexed unit-cell `(lo, hi)` windows for `grid=true` (e.g. + zoom into a patch of a large system). +- `x_groups` : explicit `Vector{Vector{Int}}` override (treated as atomic). +- `box_half` : 2D neighbourhood half-width (averages, forces sublattice averaging). +- `reduce` : `:point` (sample/box) or `:block` (block-integrate; see above). **Modes** - `:mpo` (default) — single Chebyshev pass; evaluates all positions simultaneously. Cost `∝ Ncheb × (MPO×MPO)`, independent of `num_x` or `n_sub`. - `:mps` — independent MPS recursion per (position, sector) combination. - Use for systems where MPO×MPO is too expensive. **Other auxiliary DOF projections** (same interface as `get_bands`): `nambu_proj`/`proj_nambu`, `spin_proj`/`proj_s`, `layer_proj`/`proj_layer`. @@ -693,23 +721,35 @@ Examples # Standard 1D chain — shape (Nω × 8) ldos = get_ldos_spatial(H, 200, ωlist; num_x=8) -# Kagome: full resolution, all sublattices — shape (Nω × 3*H.N) -ldos_all = get_ldos_spatial(H_kg, 200, ωlist; num_x=H_kg.N) +# Honeycomb, large-scale map — sublattice averaged, shape (Nω × 64) +ldos_uc = get_ldos_spatial(H_hc, 200, ωlist; num_x=64) + +# Honeycomb, atomic zoom into a 50×50 patch — sublattice resolved (Nω × 50*50*2) +ldos_zoom = get_ldos_spatial(H_hc, 200, ωlist; grid=true, + xwin=(1000, 1049), ywin=(1000, 1049)) + +# Large 2^14×2^14 system: 128×128 block-integrated map — catches thin in-gap +# edge channels that point sampling would alias away. Shape (Nω × 128*128). +ldos_blk = get_ldos_spatial(H_big, 200, ωlist; reduce=:block, num_x=128, num_y=128) # Kagome: sublattice A only — only A columns filled, B/C columns zero ldos_A = get_ldos_spatial(H_kg, 200, ωlist; proj_sl=1, num_x=H_kg.N) - -# BdG chain: particle sector only -ldos_p = get_ldos_spatial(H_bdg, 200, ωlist; nambu_proj=true, proj_nambu=1) ``` """ function get_ldos_spatial(H::TBHamiltonian, Ncheb::Int, ω_phys_vals; num_x::Int = H.N, + num_y = nothing, num_avg::Int = 1, mode::Symbol = :mpo, x_start::Int = 1, x_end::Int = H.N, x_groups = nothing, + grid::Bool = false, + xwin = nothing, + ywin = nothing, + box_half::Int = 0, + reduce::Symbol = :point, + sublattice::Symbol = :auto, kernel::Symbol = :jackson, lambda::Real = 4.0, maxdim::Int = 100, @@ -724,19 +764,33 @@ function get_ldos_spatial(H::TBHamiltonian, Ncheb::Int, ω_phys_vals; sublat_proj::Bool = false, # kept for backward compat; auto-on when H.sublattice_s is set proj_sl = nothing) - # ── Build x_groups ──────────────────────────────────────────────────────── - groups = if x_groups !== nothing - x_groups isa AbstractVector{<:AbstractVector} ? - collect.(x_groups) : [[x] for x in x_groups] - else - window = x_end - x_start + 1 - dx = window ÷ num_x - dx_sub = max(1, dx ÷ num_avg) - [[ x_start + (i-1)*dx + k*dx_sub - for k in 0:num_avg-1 - if x_start + (i-1)*dx + k*dx_sub <= x_end ] - for i in 1:num_x] + # ── Geometry-aware sampling plan (unit-cell groups + sublattice decision) ── + if box_half > 0 || grid || xwin !== nothing || ywin !== nothing || reduce === :block + isnothing(H.geometry) && + error("get_ldos_spatial: box_half/grid/window/block sampling requires H.geometry to be set.") + length(H.geometry(1)) == 2 || + error("get_ldos_spatial: box_half/grid/window/block sampling is only supported for 2D systems.") end + reduce === :block && mode === :mps && + error("get_ldos_spatial: reduce=:block is only supported in mode=:mpo.") + Lx_uc = something(H.Lx, H.L ÷ 2) + Ly_uc = H.L - Lx_uc + n_sub_H = isnothing(H.sublattice_s) ? 1 : dim(H.sublattice_s) + plan = spatial_sampling_plan(H.L; + Lx = Lx_uc, + grid = grid, + reduce = reduce, + n_sub = n_sub_H, + num_x = num_x, num_y = num_y, num_avg = num_avg, + x_start = x_start, x_end = x_end, + xwin = xwin, ywin = ywin, + x_groups = x_groups, box_half = box_half, + sublattice = sublattice) + groups = plan.groups + is_block = plan.reduce === :block + block_a = plan.a + block_b = plan.b + nbx = 2^block_a _ensure_scale!(H) nambu_proj, spin_proj, layer_proj, sublat_proj = @@ -764,14 +818,19 @@ function get_ldos_spatial(H::TBHamiltonian, Ncheb::Int, ω_phys_vals; end # ── Sublattice layout ───────────────────────────────────────────────────── - # When H.sublattice_s is set, the result always covers every atom: - # shape = (Nω, ng × n_sub), col = (ig-1)*n_sub + s - # This matches the atom ordering of honeycomb/kagome/lieb positions functions. - # proj_sl=k → fill only sublattice k (others stay 0) - # proj_sl=nothing → fill all sublattices (auto when sublat_proj=false too) - has_sublat = !isnothing(sublat_s_det) - n_sub = has_sublat ? dim(sublat_s_det::Index) : 1 - sl_fill = has_sublat ? + # When H.sublattice_s is set the geometry-aware plan decides the layout: + # • resolve (atomic scale, or proj_sl=k): one column per atom, + # shape (Nω, ng × n_sub), col = (ig-1)*n_sub + s (matches the + # honeycomb/kagome/lieb positions-function atom ordering; proj_sl=k + # fills only sublattice k, others stay 0). + # • average (large scale): the sublattice is traced out — one value per + # unit cell, shape (Nω, ng), col = ig (mean over the n_sub atoms). + has_sublat = !isnothing(sublat_s_det) + n_sub = has_sublat ? dim(sublat_s_det::Index) : 1 + # proj_sl=k pins a single sublattice → always resolved (that one column). + resolve_sl = has_sublat && (plan.resolve_sublattice || !isnothing(proj_sl)) + average_sl = has_sublat && !resolve_sl + sl_fill = has_sublat ? (isnothing(proj_sl) ? (1:n_sub) : (proj_sl:proj_sl)) : (1:1) @@ -784,7 +843,8 @@ function get_ldos_spatial(H::TBHamiltonian, Ncheb::Int, ω_phys_vals; valid = [abs(ω) < 1.0 for ω in ω_vals] ng = length(groups) - n_cols = ng * n_sub + # Averaging collapses the n_sub atoms into one column per group. + n_cols = average_sl ? ng : ng * n_sub result = zeros(Float64, Nω, n_cols) L_tot = length(H.sites) @@ -814,8 +874,13 @@ function get_ldos_spatial(H::TBHamiltonian, Ncheb::Int, ω_phys_vals; end # sector loop end # x - for s in sl_fill - result[:, (ig-1)*n_sub + s] = grp_accum[:, s] ./ length(grp) + if average_sl + # Trace out the sublattice: one column per unit cell (mean atom). + result[:, ig] = vec(sum(grp_accum; dims=2)) ./ (n_sub * length(grp)) + else + for s in sl_fill + result[:, (ig-1)*n_sub + s] = grp_accum[:, s] ./ length(grp) + end end n_done += length(grp) @@ -849,6 +914,21 @@ function get_ldos_spatial(H::TBHamiltonian, Ncheb::Int, ω_phys_vals; local _layer_side = layer_side_det local _sublat_side = sublat_side_det + # Reduce a diagonal profile MPS to per-pixel scalars, returning (u, value) + # pairs where u is the 1-indexed output pixel (column unit): + # reduce=:point → mean of inner products over each group's cells, + # reduce=:block → integral over each coarse block (_eval_block_mps). + function spatial_vals_cpu(diag_n) + if is_block + return [(ixp + iyp * nbx + 1, + _eval_block_mps(diag_n, ixp, iyp, block_a, block_b, Lx_uc, Ly_uc)) + for iyp in 0:(2^block_b - 1) for ixp in 0:(nbx - 1)] + else + return [(ig, sum(real(inner(psi_dict[x], diag_n)) for x in grp) / length(grp)) + for (ig, grp) in enumerate(groups)] + end + end + function accumulate_Tn!(Tk, n) # Non-sublattice projections (nambu → spin → layer) after_nambu = nambu_proj ? @@ -871,28 +951,29 @@ function get_ldos_spatial(H::TBHamiltonian, Ncheb::Int, ω_phys_vals; end if has_sublat - # Project per sublattice sector; each gets its own columns + # Project per sublattice sector. Resolved → each sector gets its + # own column; averaged (large scale) → all sectors fold into the + # single per-pixel column u (mean over the n_sub atoms). for Tl in after_layer, s in sl_fill Tp = project_aux(Tl, sublat_s_det::Index, s; side=_sublat_side) diag_n = ITensorMPS.truncate!(extract_diagonal_to_mps(Tp); cutoff=cutoff) - for (ig, grp) in enumerate(groups) - val = sum(real(inner(psi_dict[x], diag_n)) for x in grp) / length(grp) - col = (ig - 1) * n_sub + s + scale = average_sl ? 1.0 / n_sub : 1.0 + for (u, val) in spatial_vals_cpu(diag_n) + col = average_sl ? u : (u - 1) * n_sub + s for iω in 1:Nω valid[iω] || continue - accum[iω, col] += W[n, iω] * val + accum[iω, col] += W[n, iω] * val * scale end end end else - # No sublattice: one column per group (original behavior) + # No sublattice: one column per pixel (original behavior) for Tp in after_layer diag_n = ITensorMPS.truncate!(extract_diagonal_to_mps(Tp); cutoff=cutoff) - for (ig, grp) in enumerate(groups) - val = sum(real(inner(psi_dict[x], diag_n)) for x in grp) / length(grp) + for (u, val) in spatial_vals_cpu(diag_n) for iω in 1:Nω valid[iω] || continue - accum[iω, ig] += W[n, iω] * val + accum[iω, u] += W[n, iω] * val end end end @@ -931,8 +1012,8 @@ end """ get_dos_stochastic(H::TBHamiltonian, Ncheb::Int, ω_phys_vals; - N_sample, N_bound, seed, normalize, - kernel, lambda, maxdim, cutoff, verbose, + N_sample, N_bound, seed, normalize, dos_weighting, + kernel, lambda, eta, m_order, maxdim, cutoff, verbose, nambu_proj, proj_nambu, spin_proj, proj_s, layer_proj, proj_layer, sublat_proj, proj_sl) -> Vector{Float64} @@ -941,10 +1022,13 @@ Stochastic full DOS via random trace estimation (MPS Chebyshev, 3 MPS per sample **Normalization** -- `normalize=false` (default): returns the **total** spectral weight `Tr[δ(ω−H)]`, - which grows as `D` (Hilbert space dimension). -- `normalize=true`: divides by `D`, giving a **per-state** DOS that is intensive - (independent of `L`) and directly comparable to `get_ldos_spatial` values. +- `dos_weighting=:trace` (default): returns the trace DOS. With + `normalize=false` this is the total spectral weight `Tr[δ(ω-H)]`; with + `normalize=true` it is divided by the traced Hilbert-space dimension. +- `dos_weighting=:sample`: returns the unweighted sample signal. For exciton + stratified runs this is `avg_full + avg_bound` (when `N_bound > 0`), with no + phase-space factor multiplying the continuum. This is intended for + visualising the bound peak; `normalize` is ignored in this mode. **Auxiliary DOF projections** @@ -967,6 +1051,15 @@ dedicates `N_bound` samples to the bound sector `|x,x⟩` and `N_sample` to the full Hilbert space, combining with proper weights: `DOS = N_phys × avg_bound + (D − N_phys) × avg_scatter`. `N_bound = 0` (default) = uniform sampling over all D states. +Set `dos_weighting=:sample` to inspect the sampled spectral signal before these +sector-size weights are applied. + +**Reconstruction kernel** + +`kernel=:hodc` uses the Higher-Order Delta Chebyshev contour reconstruction +(`eta`, `m_order` control it; `eta=0` → `1/(Ncheb+1)`), whose weights already +carry the full KPM normalisation. Other values are convolution kernels +(`:jackson` default, `:lorentz` with `lambda`, `:fejer`, `:dirichlet`). Examples -------- @@ -988,8 +1081,11 @@ function get_dos_stochastic(H::TBHamiltonian, Ncheb::Int, ω_phys_vals; N_bound::Int = 0, seed::Union{Int,Nothing} = 42, normalize::Bool = false, + dos_weighting::Symbol = :trace, kernel::Symbol = :jackson, lambda::Real = 4.0, + eta::Real = 0.0, + m_order::Int = 4, maxdim::Int = 100, cutoff::Real = 1e-8, verbose::Bool = false, @@ -1003,6 +1099,8 @@ function get_dos_stochastic(H::TBHamiltonian, Ncheb::Int, ω_phys_vals; sublat_proj::Bool = false, proj_sl = nothing) _ensure_scale!(H) + dos_weighting in (:trace, :sample) || + error("get_dos_stochastic: dos_weighting must be :trace or :sample.") I_mpo = MPO(H.sites, "Id") Ham_n = (1 / H.scale) * +(H.mpo, (-H.center) * I_mpo; cutoff=cutoff) @@ -1017,13 +1115,13 @@ function get_dos_stochastic(H::TBHamiltonian, Ncheb::Int, ω_phys_vals; ω_vals = (collect(ω_phys_vals) .- H.center) ./ H.scale Nω = length(ω_vals) - W = _kpm_weight_matrix(Ncheb, ω_vals; kernel=kernel, lambda=lambda) + W, denom = _dos_weight_matrix(Ncheb, ω_vals; + kernel=kernel, lambda=lambda, eta=eta, m_order=m_order) valid = [abs(ω) < 1.0 for ω in ω_vals] rng = seed === nothing ? Random.default_rng() : Random.MersenneTwister(seed) accum_full = zeros(Float64, Nω) accum_bound = zeros(Float64, Nω) - norm = π^2 * Ncheb if any_aux_proj # ── Projected DOS: sample position states with fixed aux sectors ───── @@ -1049,9 +1147,13 @@ function get_dos_stochastic(H::TBHamiltonian, Ncheb::Int, ω_phys_vals; result = zeros(Float64, Nω) for iω in 1:Nω valid[iω] || continue - result[iω] = D_eff * accum_full[iω] / (norm * sqrt(1 - ω_vals[iω]^2)) + if dos_weighting == :sample + result[iω] = accum_full[iω] / denom[iω] + else + result[iω] = D_eff * accum_full[iω] / denom[iω] + end end - normalize && (result ./= N_phys) + normalize && dos_weighting == :trace && (result ./= D_eff) return result end @@ -1080,15 +1182,17 @@ function get_dos_stochastic(H::TBHamiltonian, Ncheb::Int, ω_phys_vals; result = zeros(Float64, Nω) for iω in 1:Nω valid[iω] || continue - denom = norm * sqrt(1 - ω_vals[iω]^2) - if N_bound > 0 && is_exc + if dos_weighting == :sample + result[iω] = (accum_full[iω] + + ((N_bound > 0 && is_exc) ? accum_bound[iω] : 0.0)) / denom[iω] + elseif N_bound > 0 && is_exc result[iω] = ((D - N_phys) * accum_full[iω] + - N_phys * accum_bound[iω]) / denom + N_phys * accum_bound[iω]) / denom[iω] else - result[iω] = D * accum_full[iω] / denom + result[iω] = D * accum_full[iω] / denom[iω] end end - normalize && (result ./= N_phys) + normalize && dos_weighting == :trace && (result ./= D) return result end @@ -1136,7 +1240,7 @@ end """ get_ldos_hodc_from_mun(mun_list, N, E; eta=0.02, m_order=6) -> Real -HODC (High-Order Damping Correction) variant of `get_ldos_from_mun`. Uses a +HODC (Higher-Order Delta Chebyshev) variant of `get_ldos_from_mun`. Uses a contour-based kernel that gives sharper spectral features than the Jackson kernel, at the cost of `m_order` extra parameters. @@ -1204,6 +1308,45 @@ function get_hodc_weights(y_target, N, eta, zl, wl) return nu end +""" + _dos_weight_matrix(Ncheb, ω_vals; kernel, lambda, eta, m_order) + -> (W::Matrix, denom::Vector) + +Stochastic-DOS reconstruction weights `W[n, iω]` and per-ω normalisation +`denom[iω]` for a given KPM `kernel`. The DOS is recovered from the (sample- +averaged) Chebyshev moments `μ_n` as `Σ_n W[n,iω] μ_n / denom[iω]`. + +- Convolution kernels (`:jackson`, `:lorentz`, `:fejer`, `:dirichlet`): + `W` follows `_kpm_weight_matrix` and `denom = π²·Ncheb·√(1−ω²)`, matching + `get_ldos_from_mun`. +- `:hodc`: the contour weights `νₙ(ω)` from `get_hodc_weights` already carry the + full normalisation (`denom = 1`), matching `get_ldos_hodc_from_mun`. `eta=0` + falls back to `1/(Ncheb+1)`. + +Entries with `|ω| ≥ 1` are zeroed in `W` (outside the rescaled spectral support). +""" +function _dos_weight_matrix(Ncheb::Int, ω_vals; + kernel::Symbol = :jackson, + lambda::Real = 4.0, + eta::Real = 0.0, + m_order::Int = 4) + Nω = length(ω_vals) + if kernel == :hodc + eta_ = eta == 0.0 ? 1 / (Ncheb + 1) : eta + zl, wl = compute_hodc_params(m_order) + W = zeros(Float64, Ncheb, Nω) + for iω in 1:Nω + abs(ω_vals[iω]) >= 1.0 && continue + W[:, iω] .= get_hodc_weights(ω_vals[iω], Ncheb, eta_, zl, wl) + end + return W, ones(Float64, Nω) + else + W = _kpm_weight_matrix(Ncheb, ω_vals; kernel=kernel, lambda=lambda) + denom = [π^2 * Ncheb * sqrt(max(1 - ω^2, 0.0)) for ω in ω_vals] + return W, denom + end +end + # Returns complex weights π*(ν_HT - i*ν_δ) for the retarded Green's function. # ν_δ comes from -Im[...]/π (same as get_hodc_weights), # ν_HT comes from Re[...]/π (real part of the same rational sum — no extra cost). @@ -1436,20 +1579,13 @@ end """ - get_exciton_ldos(H::TBHamiltonian, X::Int, ω_phys; Ncheb, kernel, eta, m_order, - lambda, maxdim, cutoff, verbose) -> Real + _get_exciton_ldos_cached(H::TBHamiltonian, X::Int, omega; kwargs...) -> Real -Exciton local spectral weight at physical energy `ω_phys` for the exciton state -|X,X⟩, where `X ∈ {1, …, 2^L}` (1-indexed, consistent with `add_onsite!`). - -If `H._tn_mps_cache` already holds `Ncheb` Chebyshev states (from a prior -`KPM_Tn(H, Ncheb, X)` call) they are reused — call `KPM_Tn(H, Ncheb, X)` first -when sweeping over many energies for the same site. Otherwise the Chebyshev -states are built on the fly and cached for subsequent calls. - -Returns `0` when `|E| ≥ 1` (energy outside the rescaled spectral support). +Internal legacy cache-backed scalar exciton LDOS helper. Public exciton LDOS now +routes through `get_exciton_ldos_spatial`, which performs online MPS recursion +without storing a Chebyshev cache on `H`. """ -function get_exciton_ldos(H::TBHamiltonian, X::Int, ω_phys::Real; +function _get_exciton_ldos_cached(H::TBHamiltonian, X::Int, ω_phys::Real; Ncheb::Int = 200, kernel::Symbol = :jackson, lambda::Real = 4.0, @@ -1478,6 +1614,153 @@ function get_exciton_ldos(H::TBHamiltonian, X::Int, ω_phys::Real; end +""" + get_exciton_ldos_spatial(H, Ncheb, omega_phys_vals; X_list, X_groups, + num_x, num_avg, x_start, x_end, kernel, + lambda, eta, m_order, maxdim, cutoff, + verbose, printinfo) -> Matrix{Float64} + +CPU spatial exciton LDOS. For each bound exciton position `X` (electron = hole = +`X`, 1-indexed in `1:H.N`) this runs an online MPS Chebyshev recursion from +`|X,X>` and accumulates all requested energies in one pass. No Chebyshev cache is +stored on `H`. + +Rows are energies, columns are positions/groups. `X_list` selects positions +directly. `X_groups` (or alias `x_groups`) averages several bound-pair probes into +one output column. If no explicit positions are provided, `num_x` coarse groups +are generated over `x_start:x_end`, with `num_avg` subpositions per group. + +`kernel=:hodc` uses the HODC reconstruction (`eta`, `m_order`); otherwise the +standard KPM kernels are available (`:jackson`, `:lorentz`, `:fejer`, +`:dirichlet`). +""" +function get_exciton_ldos_spatial(H::TBHamiltonian, Ncheb::Int, omega_phys_vals; + X_list = nothing, + X_groups = nothing, + x_groups = nothing, + num_x::Int = H.N, + num_avg::Int = 1, + x_start::Int = 1, + x_end::Int = H.N, + kernel::Symbol = :jackson, + lambda::Real = 4.0, + eta::Real = 0.0, + m_order::Int = 4, + maxdim::Int = 100, + cutoff::Real = 1e-8, + verbose::Bool = false, + printinfo::Bool = false) + _ensure_scale!(H) + length(H.sites) == 2 * H.L || + error("get_exciton_ldos_spatial: H is not an exciton Hamiltonian (expected length(H.sites) == 2*H.L).") + + X_groups !== nothing && x_groups !== nothing && + error("get_exciton_ldos_spatial: pass only one of X_groups or x_groups.") + X_list !== nothing && (X_groups !== nothing || x_groups !== nothing) && + error("get_exciton_ldos_spatial: pass either X_list or grouped positions, not both.") + + group_arg = X_groups !== nothing ? X_groups : x_groups + groups = if group_arg !== nothing + group_arg isa AbstractVector{<:AbstractVector} ? + [collect(Int, grp) for grp in group_arg] : + [[Int(x)] for x in group_arg] + elseif X_list !== nothing + [[Int(x)] for x in X_list] + else + num_x > 0 || error("get_exciton_ldos_spatial: num_x must be positive.") + num_avg > 0 || error("get_exciton_ldos_spatial: num_avg must be positive.") + 1 <= x_start <= x_end <= H.N || + error("get_exciton_ldos_spatial: expected 1 <= x_start <= x_end <= H.N.") + window = x_end - x_start + 1 + num_x <= window || + error("get_exciton_ldos_spatial: num_x=$num_x exceeds sampling window length $window.") + dx = div(window, num_x) + dx_sub = max(1, div(dx, num_avg)) + [[x_start + (i - 1) * dx + k * dx_sub + for k in 0:num_avg-1 + if x_start + (i - 1) * dx + k * dx_sub <= x_end] + for i in 1:num_x] + end + + isempty(groups) && error("get_exciton_ldos_spatial: no spatial groups were selected.") + for grp in groups + isempty(grp) && error("get_exciton_ldos_spatial: empty spatial group.") + all(x -> 1 <= x <= H.N, grp) || + error("get_exciton_ldos_spatial: all positions must lie in 1:H.N.") + end + + I_mpo = MPO(H.sites, "Id") + Ham_n = (1 / H.scale) * +(H.mpo, (-H.center) * I_mpo; cutoff=cutoff) + + omega_vals = (collect(omega_phys_vals) .- H.center) ./ H.scale + Nomega = length(omega_vals) + W, denom = _dos_weight_matrix(Ncheb, omega_vals; + kernel=kernel, lambda=lambda, + eta=eta, m_order=m_order) + valid = [abs(omega) < 1.0 for omega in omega_vals] + + nX = length(groups) + Xs = first.(groups) + result = zeros(Float64, Nomega, nX) + + for (j, group) in enumerate(groups) + last_linkdim = 0 + accum_group = zeros(Float64, Nomega) + + for X in group + psi0 = mpsexciton(X, H.sites) + last_linkdim = _run_kpm_mps!(Ham_n, psi0, Ncheb, W, valid, accum_group; + weight=1.0 / length(group), + cutoff=cutoff, maxdim=maxdim) + end + + for iomega in 1:Nomega + valid[iomega] || continue + result[iomega, j] = accum_group[iomega] / denom[iomega] + end + + (verbose || printinfo) && (j % 5 == 0 || j == nX) && + println(" exciton ldos $j/$nX (X=$(Xs[j]), n_avg=$(length(group))) maxlinkdim=$last_linkdim") + end + + return result +end + +function get_exciton_ldos(H::TBHamiltonian, X::Int, omega_phys::Real; + Ncheb::Int = 200, + kernel::Symbol = :jackson, + lambda::Real = 4.0, + eta::Real = 0.0, + m_order::Int = 4, + maxdim::Int = 40, + cutoff::Real = 1e-8, + verbose::Bool = false) + ldos = get_exciton_ldos_spatial(H, Ncheb, [omega_phys]; + X_list=[X], kernel=kernel, + lambda=lambda, eta=eta, + m_order=m_order, maxdim=maxdim, + cutoff=cutoff, verbose=verbose) + return ldos[1, 1] +end + +function get_exciton_ldos(H::TBHamiltonian, X::Int, omega_phys_vals; + Ncheb::Int = 200, + kernel::Symbol = :jackson, + lambda::Real = 4.0, + eta::Real = 0.0, + m_order::Int = 4, + maxdim::Int = 40, + cutoff::Real = 1e-8, + verbose::Bool = false) + ldos = get_exciton_ldos_spatial(H, Ncheb, omega_phys_vals; + X_list=[X], kernel=kernel, + lambda=lambda, eta=eta, + m_order=m_order, maxdim=maxdim, + cutoff=cutoff, verbose=verbose) + return vec(ldos[:, 1]) +end + + """ ldos_exc_KPM_Tn(H, N, X; cutoff, maxdim) -> Vector