Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Full and approximate polaritonic ADC energies up to second order #153

Open
wants to merge 69 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 61 commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
f886af7
qed adc1
BauerMarco Jul 1, 2021
b94d13f
qed-adc2 equations without adjustments to vector- and matrix class
BauerMarco Jul 7, 2021
28235eb
added the lower order terms to all second order blocks
BauerMarco Jul 9, 2021
c64a6d6
full qed-adc2 converges for few states of all tested molecules and ba…
BauerMarco Jul 13, 2021
c906dc9
adc1 expanded to also include doubly excited photonic contributions
BauerMarco Jul 17, 2021
adc8695
still trying to fix the symmetry problem, which is probably not only …
BauerMarco Aug 12, 2021
3495f9b
still trying to fix the symmetry problem
BauerMarco Aug 19, 2021
bdee15d
symmetry seems to work fine with restricted triples or singles
BauerMarco Aug 24, 2021
744c2d6
Lanczos works for qed, applied massive improvement to guesses. Also D…
BauerMarco Sep 16, 2021
7fdb127
check
BauerMarco Oct 8, 2021
ad0b144
check
BauerMarco Oct 13, 2021
9bdca70
check
BauerMarco Oct 19, 2021
c2c9a1a
enabled qed-adc1 from qed-hf reference (for single + double photon di…
BauerMarco Oct 26, 2021
bf46dde
started correcting for wrong factors (cancellations should be done by…
BauerMarco Nov 29, 2021
b3580ac
some factors are still wrong
BauerMarco Nov 30, 2021
380a582
QED-ADC(2) for QED-HF reference should work now
BauerMarco Dec 16, 2021
0c56424
included check for unrestricted or restricted reference
BauerMarco Dec 16, 2021
77a96ea
cashed some stuff in matrix.py
BauerMarco Dec 17, 2021
8179a2b
corrected QED-ADC(2) from QED-HF reference (should be correct now and…
BauerMarco Jan 17, 2022
2d028d5
removed .gs from the implementation (since it is always zero) and imp…
BauerMarco Feb 3, 2022
001e2d3
added the possibility to only take first order photonic coupling into…
BauerMarco Feb 15, 2022
7b8ac29
first_order_coupling option now also treats additive part to ERIs in …
BauerMarco Feb 18, 2022
1866ce1
first_order_coupling option now also treats additive part to ERIs in …
BauerMarco Feb 18, 2022
479ab98
uncommented missing H_1 terms in photonic coupling blocks
BauerMarco Feb 21, 2022
f1c9203
corrected mistake within the qed pphh_ph (and vice versa) blocks
BauerMarco Feb 25, 2022
ad3f8c7
corrected sign error in ph_pphh photonic coupling blocks
BauerMarco Mar 2, 2022
c60a4b0
full_diagonalize option included for qed-adc(1)
BauerMarco Mar 3, 2022
0940196
added omega with corresponding factor to diagonal doubles space
BauerMarco Mar 8, 2022
b209615
added 1. order coupling blocks to first order coupling
BauerMarco Mar 9, 2022
06c18fe
corrected the scaling factor for omega in the doubles block
BauerMarco Mar 11, 2022
3a1d8b4
documentary stuff
BauerMarco Mar 16, 2022
d5e8867
changed factor and sign of H_1 term of photonic coupling blocks
BauerMarco Mar 18, 2022
7c2fe87
again factor changes in H_1 part of photonic coupling blocks
BauerMarco Mar 18, 2022
72a4fa8
see above
BauerMarco Mar 18, 2022
8fc907f
QED-ADC(2) test works now
BauerMarco Mar 21, 2022
911fb88
less printout in davidson
BauerMarco May 6, 2022
6167f34
import for orbital energies and restricted checks in psi4 now works v…
BauerMarco May 16, 2022
696269b
orbital energies in psi4 backend need to be adjusted, depending on in…
BauerMarco May 17, 2022
41c8bab
adjusted psi4 backend for hilbert qed-(u/r)hf and psi4numpy cqed-rhf …
BauerMarco May 20, 2022
5730053
merge check from qed_adc into master
May 22, 2022
40e752b
merged changes from master into submatrix class
May 24, 2022
99c78a9
AdcMatrix.py now has only one matrix class, which is capable of handl…
Jun 2, 2022
292d3f9
included approximation scheme, which seems to provide a tiny error, o…
Jun 8, 2022
9ff2131
minor changes
BauerMarco Jul 4, 2022
ee4be7e
searching for numerical inconsistency in approx function
BauerMarco Jul 4, 2022
28ca773
numerical inconsistency in approx function found and removed
BauerMarco Jul 4, 2022
89b12cc
reintroduced approx method
BauerMarco Jul 5, 2022
8dfbb93
no printout from LazyMp anymore
BauerMarco Jul 13, 2022
aa44ebb
started with qed test
BauerMarco Jul 28, 2022
cb369d1
qed parameters can now also be set via the run_adc function. Tests fo…
BauerMarco Aug 1, 2022
0d7d166
less printout and made the max subspace for qed a fully manual option…
BauerMarco Aug 2, 2022
c25cc1d
final cleanup for initial commit to master
BauerMarco Aug 9, 2022
f214171
Merge pull request #2 from BauerMarco/qed_merge_check
BauerMarco Aug 9, 2022
671183b
removed mp2_diffdm for qed, since it has not been tested thoroughly yet
BauerMarco Aug 9, 2022
ec2f2dd
lgtm corrections
BauerMarco Aug 9, 2022
da9555a
making the linter happy
BauerMarco Aug 10, 2022
9d5f74a
removed numerical inconsistency of approx method, by reintroducing th…
BauerMarco Aug 22, 2022
1fabe94
adapted the test cases, so they also don't fail, if all tests are run…
BauerMarco Sep 9, 2022
9cec484
apparently mp1 contribution got added twice in qed-mp2 energy from no…
BauerMarco Oct 4, 2022
e8a032d
qed approx method is now able to account for photon losses in the cav…
BauerMarco Oct 13, 2022
45c7b8e
Merge pull request #3 from BauerMarco/qed_adc_complex_omega
BauerMarco Oct 13, 2022
af4b380
qed adc runs with AmplitudeVector object, instead of QED_AmplitudeVector
BauerMarco Oct 27, 2022
529c6a6
further code cleanup, due to full qed handling via AmplitudeVector, a…
BauerMarco Oct 27, 2022
f9771b1
further cleanup
BauerMarco Oct 28, 2022
21a1585
Merge branch 'adc-connect:master' into review_cycle1
BauerMarco Oct 28, 2022
6edf332
Merge pull request #4 from BauerMarco/review_cycle1
BauerMarco Oct 28, 2022
9b7b618
corrected qed_t1 denominator
BauerMarco Feb 21, 2023
5bbb3eb
arbitrary spatial orientations for polaritonic frequency and coupling…
BauerMarco Dec 14, 2023
889e213
make quasi-diabatic matrices accessible from resulting ExcitedStates …
Feb 28, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
219 changes: 183 additions & 36 deletions adcc/AdcMatrix.py

Large diffs are not rendered by default.

159 changes: 159 additions & 0 deletions adcc/AmplitudeVector.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
## ---------------------------------------------------------------------
import warnings

import numpy as np


class AmplitudeVector(dict):
def __init__(self, *args, **kwargs):
Expand Down Expand Up @@ -217,3 +219,160 @@ def __radd__(self, other):
else:
ret = {k: other + tensor for k, tensor in self.items()}
return AmplitudeVector(**ret)


class QED_AmplitudeVector:
Copy link
Author

Choose a reason for hiding this comment

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

merge this class with AmplitudeVector


# TODO: Initialize this class with **kwargs, and think of a functionality,
# which then eases up e.g. the matvec function in the AdcMatrix.py for
# arbitrarily large QED vectors. However, this is not necessarily required,
# since e.g. QED-ADC(3) is very complicated to derive and QED-ADC(1) (with
# just the single dispersion mode) is purely for academic purposes and
# hence not required to provide optimum performance.

def __init__(self, ph=None, pphh=None, gs1=None, ph1=None, pphh1=None,
gs2=None, ph2=None, pphh2=None):

if pphh is not None:
self.elec = AmplitudeVector(ph=ph, pphh=pphh)
self.phot = AmplitudeVector(ph=ph1, pphh=pphh1)
self.phot2 = AmplitudeVector(ph=ph2, pphh=pphh2)
else:
self.elec = AmplitudeVector(ph=ph)
self.phot = AmplitudeVector(ph=ph1)
self.phot2 = AmplitudeVector(ph=ph2)

self.gs1 = gs1
self.gs2 = gs2
self.ph = ph
self.ph1 = ph1
self.ph2 = ph2
self.pphh = pphh
self.pphh1 = pphh1
self.pphh2 = pphh2

def dot(self, invec):
def dot_(self, invec):
if "pphh" in self.elec.blocks_ph:
return (self.elec.ph.dot(invec.elec.ph)
+ self.elec.pphh.dot(invec.elec.pphh)
+ self.gs1 * invec.gs1 + self.phot.ph.dot(invec.phot.ph)
+ self.phot.pphh.dot(invec.phot.pphh)
+ self.gs2 * invec.gs2 + self.phot2.ph.dot(invec.phot2.ph)
+ self.phot2.pphh.dot(invec.phot2.pphh))
else:
return (self.elec.ph.dot(invec.elec.ph)
+ self.gs1 * invec.gs1 + self.phot.ph.dot(invec.phot.ph)
+ self.gs2 * invec.gs2 + self.phot2.ph.dot(invec.phot2.ph))
if isinstance(invec, list):
return np.array([dot_(self, elem) for elem in invec])
else:
return dot_(self, invec)

def __matmul__(self, other):
if isinstance(other, QED_AmplitudeVector):
return self.dot(other)
if isinstance(other, list):
if all(isinstance(elem, QED_AmplitudeVector) for elem in other):
return self.dot(other)
return NotImplemented

def __sub__(self, invec):
if isinstance(invec, (float, int)):
if "pphh" in self.elec.blocks_ph:
return QED_AmplitudeVector(
ph=self.elec.ph.__sub__(invec),
pphh=self.elec.pphh.__sub__(invec),
gs1=self.gs1 - invec, ph1=self.phot.ph.__sub__(invec),
pphh1=self.phot.pphh.__sub__(invec),
gs2=self.gs2 - invec, ph2=self.phot2.ph.__sub__(invec),
pphh2=self.phot2.pphh.__sub__(invec))
else:
return QED_AmplitudeVector(
ph=self.elec.ph.__sub__(invec),
gs1=self.gs1 - invec, ph1=self.phot.ph.__sub__(invec),
gs2=self.gs2 - invec, ph2=self.phot2.ph.__sub__(invec))

def __truediv__(self, other):
if isinstance(other, QED_AmplitudeVector):
if "pphh" in self.elec.blocks_ph:
return QED_AmplitudeVector(
ph=self.elec.ph.__truediv__(other.elec.ph),
pphh=self.elec.pphh.__truediv__(other.elec.pphh),
gs1=self.gs1 / other.gs1,
ph1=self.phot.ph.__truediv__(other.phot.ph),
pphh1=self.phot.pphh.__truediv__(other.phot.pphh),
gs2=self.gs2 / other.gs2,
ph2=self.phot2.ph.__truediv__(other.phot2.ph),
pphh2=self.phot2.pphh.__truediv__(other.phot2.pphh))
else:
return QED_AmplitudeVector(
ph=self.elec.ph.__truediv__(other.elec.ph),
gs1=self.gs1 / other.gs1,
ph1=self.phot.ph.__truediv__(other.phot.ph),
gs2=self.gs2 / other.gs2,
ph2=self.phot2.ph.__truediv__(other.phot2.ph))
elif isinstance(other, (float, int)):
if "pphh" in self.elec.blocks_ph:
return QED_AmplitudeVector(
ph=self.elec.ph.__truediv__(other),
pphh=self.elec.pphh.__truediv__(other),
gs1=self.gs1 / other, ph1=self.phot.ph.__truediv__(other),
pphh1=self.phot.pphh.__truediv__(other),
gs2=self.gs2 / other, ph2=self.phot2.ph.__truediv__(other),
pphh2=self.phot2.pphh.__truediv__(other))
else:
return QED_AmplitudeVector(
ph=self.elec.ph.__truediv__(other),
gs1=self.gs1 / other, ph1=self.phot.ph.__truediv__(other),
gs2=self.gs2 / other, ph2=self.phot2.ph.__truediv__(other))

def zeros_like(self):
if "pphh" in self.elec.blocks_ph:
return QED_AmplitudeVector(
ph=self.elec.zeros_like().ph,
pphh=self.elec.zeros_like().pphh,
gs1=0, ph1=self.phot.zeros_like().ph,
pphh1=self.phot.zeros_like().pphh,
gs2=0, ph2=self.phot2.zeros_like().ph,
pphh2=self.phot2.zeros_like().pphh)
else:
return QED_AmplitudeVector(
ph=self.elec.zeros_like().ph, pphh=None,
gs1=0, ph1=self.phot.zeros_like().ph, pphh1=None,
gs2=0, ph2=self.phot2.zeros_like().ph, pphh2=None)

def empty_like(self):
if "pphh" in self.elec.blocks_ph:
return QED_AmplitudeVector(
ph=self.elec.empty_like().ph,
pphh=self.elec.empty_like().pphh,
gs1=0, ph1=self.phot.empty_like().ph,
pphh1=self.phot.empty_like().pphh,
gs2=0, ph2=self.phot2.empty_like().ph,
pphh2=self.phot2.empty_like().pphh)
else:
QED_AmplitudeVector(
ph=self.elec.empty_like().ph, pphh=None,
gs1=0, ph1=self.phot.empty_like().ph, pphh1=None,
gs2=0, ph2=self.phot2.empty_like().ph, pphh2=None)

def copy(self):
if "pphh" in self.elec.blocks_ph:
return QED_AmplitudeVector(
ph=self.elec.copy().ph, pphh=self.elec.copy().pphh,
gs1=self.gs1, ph1=self.phot.copy().ph,
pphh1=self.phot.copy().pphh,
gs2=self.gs2, ph2=self.phot2.copy().ph,
pphh2=self.phot2.copy().pphh)
else:
QED_AmplitudeVector(
ph=self.elec.copy().ph, pphh=None,
gs1=self.gs1, ph1=self.phot.copy().ph, pphh1=None,
gs2=self.gs2, ph2=self.phot2.copy().ph, pphh2=None)

def evaluate(self):
self.elec.evaluate()
self.phot.evaluate()
self.phot2.evaluate()
return self
129 changes: 129 additions & 0 deletions adcc/ElectronicTransition.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@
from .visualisation import ExcitationSpectrum
from .OneParticleOperator import product_trace
from .AdcMethod import AdcMethod
from adcc.functions import einsum
from adcc import block as b
from adcc.adc_pp.state2state_transition_dm import state2state_transition_dm
from adcc.adc_pp.transition_dm import transition_dm

from scipy import constants
from .Excitation import mark_excitation_property
Expand Down Expand Up @@ -165,6 +169,131 @@ def transition_dipole_moment(self):
for tdm in self.transition_dm
])

@cached_property
@mark_excitation_property()
@timed_member_call(timer="_property_timer")
def transition_dipole_moments_qed(self):
"""
List of transition dipole moments of all computed states
to build the QED-matrix in the basis of the diagonal
purely electric subblock
"""
if self.reference_state.approx:

dipole_integrals = self.operators.electric_dipole

def tdm(i, prop_level):
self.ground_state.tdm_contribution = prop_level
return transition_dm(self.method, self.ground_state,
self.excitation_vector[i])

if hasattr(self.reference_state, "first_order_coupling"):

return np.array([
[product_trace(comp, tdm(i, "adc0"))
for comp in dipole_integrals]
for i in np.arange(len(self.excitation_energy))
])
else:
prop_level = "adc" + str(self.property_method.level - 1)
return np.array([
[product_trace(comp, tdm(i, prop_level))
for comp in dipole_integrals]
for i in np.arange(len(self.excitation_energy))
])
else:
return ("transition_dipole_moments_qed are only calculated,"
"if reference_state contains 'approx' attribute")

@cached_property
@mark_excitation_property()
@timed_member_call(timer="_property_timer")
def s2s_dipole_moments_qed(self):
"""
List of s2s transition dipole moments of all computed states
to build the QED-matrix in the basis of the diagonal
purely electric subblock
"""
if self.reference_state.approx:
dipole_integrals = self.operators.electric_dipole
print("note, that only the z coordinate of the "
"dipole integrals is calculated")
n_states = len(self.excitation_energy)

def s2s(i, f, s2s_contribution):
self.ground_state.s2s_contribution = s2s_contribution
vec = self.excitation_vector
return state2state_transition_dm(self.method, self.ground_state,
vec[i], vec[f])

def final_block(name):
return np.array([[product_trace(dipole_integrals[2], s2s(i, j, name)) # noqa: E501
for j in np.arange(n_states)]
for i in np.arange(n_states)])

block_dict = {}

block_dict["qed_adc1_off_diag"] = final_block("adc1")

if self.method.name == "adc2" and not hasattr(self.reference_state, "first_order_coupling"): # noqa: E501
block_dict["qed_adc2_diag"] = final_block("qed_adc2_diag")
block_dict["qed_adc2_edge_couple"] = final_block("qed_adc2_edge_couple") # noqa: E501
block_dict["qed_adc2_edge_phot_couple"] = final_block("qed_adc2_edge_phot_couple") # noqa: E501
block_dict["qed_adc2_ph_pphh"] = final_block("qed_adc2_ph_pphh")
block_dict["qed_adc2_pphh_ph"] = final_block("qed_adc2_pphh_ph")
return block_dict
else:
return ("s2s_dipole_moments_qed are only calculated,"
"if reference_state contains 'approx' attribute")

@cached_property
@mark_excitation_property()
@timed_member_call(timer="_property_timer")
def qed_second_order_ph_ph_couplings(self):
"""
List of blocks containing the expectation value of the perturbation
of the Hamiltonian for all computed states required
to build the QED-matrix in the basis of the diagonal
purely electric subblock
"""
if self.reference_state.approx:
qed_t1 = self.ground_state.qed_t1(b.ov)

def couple(qed_t1, ul, ur):
return {
b.ooov: einsum("kc,ia,ja->kjic", qed_t1, ul, ur)
+ einsum("ka,ia,jb->jkib", qed_t1, ul, ur),
b.ovvv: einsum("kc,ia,ib->kacb", qed_t1, ul, ur)
+ einsum("ic,ia,jb->jabc", qed_t1, ul, ur)
}

def phot_couple(qed_t1, ul, ur):
return {
b.ooov: einsum("kc,ia,ja->kijc", qed_t1, ul, ur)
+ einsum("kb,ia,jb->ikja", qed_t1, ul, ur),
b.ovvv: einsum("kc,ia,ib->kbca", qed_t1, ul, ur)
+ einsum("jc,ia,jb->ibac", qed_t1, ul, ur)
}

def prod_sum(hf, two_p_op):
return (einsum("ijka,ijka->", hf.ooov, two_p_op[b.ooov])
+ einsum("iabc,iabc->", hf.ovvv, two_p_op[b.ovvv]))

def final_block(func):
return np.array([
[prod_sum(self.reference_state, func(qed_t1, i.ph, j.ph))
for i in self.excitation_vector]
for j in self.excitation_vector])

block_dict = {}
block_dict["couple"] = final_block(couple)
block_dict["phot_couple"] = final_block(phot_couple)

return block_dict
else:
return ("qed_second_order_ph_ph_couplings are only calculated,"
"if reference_state contains 'approx' attribute")

@cached_property
@mark_excitation_property()
@timed_member_call(timer="_property_timer")
Expand Down
14 changes: 14 additions & 0 deletions adcc/ExcitedStates.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
from .OneParticleOperator import product_trace
from .ElectronicTransition import ElectronicTransition
from .FormatDominantElements import FormatDominantElements
from .AmplitudeVector import QED_AmplitudeVector


class EnergyCorrection:
Expand Down Expand Up @@ -416,6 +417,12 @@ def describe_amplitudes(self, tolerance=0.01, index_format=None):

# Optimise the formatting by pre-inspecting all tensors
for tensor in self.excitation_vector:
if isinstance(tensor, QED_AmplitudeVector):
# TODO: Implement tdm and s2s_tdm for QED, so properties can also
# be evaluated for QED_AmplitudeVector objects. For now only
# use AmplitudeVector describing the electric part, so the
# formatting does not have to be adapted.
tensor = tensor.elec
vector_format.optimise_formatting(tensor)

# Determine width of a line
Expand All @@ -424,6 +431,13 @@ def describe_amplitudes(self, tolerance=0.01, index_format=None):

ret = separator
for i, vec in enumerate(self.excitation_vector):
if isinstance(vec, QED_AmplitudeVector):
# TODO: Implement tdm and s2s_tdm for QED, so properties can also
# be evaluated for QED_AmplitudeVector objects. For now only
# use AmplitudeVector describing the electric part, since
# most low-energy states are almost purely electric, so the
# formatting does not have to be adapted.
vec = vec.elec
ene = self.excitation_energy[i]
eev = ene * eV
head = f"State {i:3d} , {ene:13.7g} au"
Expand Down
Loading