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 all 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
100 changes: 69 additions & 31 deletions adcc/AdcMatrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ class AdcMatrixlike:
class AdcMatrix(AdcMatrixlike):
# Default perturbation-theory orders for the matrix blocks (== standard ADC-PP).
default_block_orders = {
# ph_ph=0, ph_pphh=None, pphh_ph=None, pphh_pphh=None),
"adc0": dict(ph_ph=0, ph_pphh=None, pphh_ph=None, pphh_pphh=None), # noqa: E501
"adc1": dict(ph_ph=1, ph_pphh=None, pphh_ph=None, pphh_pphh=None), # noqa: E501
"adc2": dict(ph_ph=2, ph_pphh=1, pphh_ph=1, pphh_pphh=0), # noqa: E501
Expand All @@ -87,7 +86,6 @@ def __init__(self, method, hf_or_mp, block_orders=None, intermediates=None,
diagonal_precomputed=None):
"""
Initialise an ADC matrix.

Parameters
----------
method : str or AdcMethod
Expand Down Expand Up @@ -130,21 +128,31 @@ def __init__(self, method, hf_or_mp, block_orders=None, intermediates=None,
self.ndim = 2
self.extra_terms = []

if self.reference_state.is_qed:
if method.base_method.name in ["adc2x", "adc3"] or self.is_core_valence_separated: # noqa: E501
raise NotImplementedError("Neither adc2x and adc3 nor cvs methods "
"are implemented for QED-ADC")
elif method.base_method.name == "adc2" and not self.reference_state.qed_hf: # noqa: E501
raise NotImplementedError("QED-ADC(2) is only available for a "
"QED-HF reference")

self.intermediates = intermediates
if self.intermediates is None:
self.intermediates = Intermediates(self.ground_state)

# Determine orders of PT in the blocks
if block_orders is None:
block_orders = self.default_block_orders[method.base_method.name]
if self.reference_state.is_qed and not self.reference_state.approx:
block_orders["ph_gs"] = block_orders["ph_ph"]
else:
tmp_orders = self.default_block_orders[method.base_method.name].copy()
tmp_orders.update(block_orders)
block_orders = tmp_orders

# Sanity checks on block_orders
for block in block_orders.keys():
if block not in ("ph_ph", "ph_pphh", "pphh_ph", "pphh_pphh"):
if block not in ("ph_gs", "ph_ph", "ph_pphh", "pphh_ph", "pphh_pphh"):
raise ValueError(f"Invalid block order key: {block}")
if block_orders["ph_pphh"] != block_orders["pphh_ph"]:
raise ValueError("ph_pphh and pphh_ph should always have "
Expand All @@ -154,19 +162,53 @@ def __init__(self, method, hf_or_mp, block_orders=None, intermediates=None,
raise ValueError("pphh_pphh cannot be None if ph_pphh isn't.")
self.block_orders = block_orders

self.qed_dispatch_dict = {
"elec": "",
"elec_couple": "_couple", "phot_couple": "_phot_couple",
"phot": "_phot", "phot2": "_phot2",
"elec_couple_inner": "_couple_inner",
"elec_couple_edge": "_couple_edge",
"phot_couple_inner": "_phot_couple_inner",
"phot_couple_edge": "_phot_couple_edge"
}

def get_pp_blocks(disp_str):
"""
Extraction of blocks from the adc_pp matrix
----------
disp_str : string
key specifying block to return
"""
return { # TODO Rename to self.block in 0.16.0
block: ppmatrix.block(self.ground_state, block.split("_"),
order=str(order) +\
self.qed_dispatch_dict[disp_str],
intermediates=self.intermediates,
variant=variant)
for block, order in block_orders.items() if order is not None
}

# Build the blocks and diagonals

with self.timer.record("build"):
variant = None
if self.is_core_valence_separated:
if method.is_core_valence_separated:
variant = "cvs"
blocks = {
block: ppmatrix.block(self.ground_state, block.split("_"),
order=order, intermediates=self.intermediates,
variant=variant)
for block, order in self.block_orders.items() if order is not None
}
# TODO Rename to self.block in 0.16.0
self.blocks_ph = {bl: blocks[bl].apply for bl in blocks}
# Build full QED-matrix
if self.reference_state.is_qed and not self.reference_state.approx:
blocks = {
bl + "_" + key: get_pp_blocks(key)[bl]
for bl, order in block_orders.items()
if order is not None
for key in self.qed_dispatch_dict
}
else: # Build "standard" ADC-matrix
blocks = {
bl: get_pp_blocks("elec")[bl]
for bl, order in block_orders.items()
if order is not None
}

if diagonal_precomputed:
self.__diagonal = diagonal_precomputed
else:
Expand All @@ -175,9 +217,11 @@ def __init__(self, method, hf_or_mp, block_orders=None, intermediates=None,
self.__diagonal.evaluate()
self.__init_space_data(self.__diagonal)

# TODO Rename to self.block in 0.16.0
self.blocks_ph = {bl: blocks[bl].apply for bl in blocks}

def __iadd__(self, other):
"""In-place addition of an :py:class:`AdcExtraTerm`

Parameters
----------
other : AdcExtraTerm
Expand Down Expand Up @@ -205,12 +249,10 @@ def patched_apply(ampl, original=orig_app, other=other_app):
def __add__(self, other):
"""Addition of an :py:class:`AdcExtraTerm`, creating
a copy of self and adding the term to the new matrix

Parameters
----------
other : AdcExtraTerm
the extra term to be added

Returns
-------
AdcMatrix
Expand All @@ -233,10 +275,17 @@ def __init_space_data(self, diagonal):
self.axis_spaces = {}
self.axis_lengths = {}
for block in diagonal.blocks_ph:
self.axis_spaces[block] = getattr(diagonal, block).subspaces
self.axis_lengths[block] = np.prod([
self.mospaces.n_orbs(sp) for sp in self.axis_spaces[block]
])
if "gs" in block:
# Either include g1 in whole libadcc backend, or use this
# approach for now, which is only required for functionalities,
# which should not be used with the full qed matrix yet anyway
self.axis_spaces[block] = ['g1']
self.axis_lengths[block] = 1
else:
self.axis_spaces[block] = getattr(diagonal, block).subspaces
self.axis_lengths[block] = np.prod([
self.mospaces.n_orbs(sp) for sp in self.axis_spaces[block]
])
self.shape = (sum(self.axis_lengths.values()),
sum(self.axis_lengths.values()))

Expand Down Expand Up @@ -339,7 +388,7 @@ def __matmul__(self, other):
if isinstance(other, AmplitudeVector):
return self.matvec(other)
if isinstance(other, list):
if all(isinstance(elem, AmplitudeVector) for elem in other):
if all(isinstance(elem, AmplitudeVector) for elem in other): # noqa: E501
return [self.matvec(ov) for ov in other]
return NotImplemented

Expand Down Expand Up @@ -370,11 +419,9 @@ def construct_symmetrisation_for_blocks(self):
applied to relevant blocks of an AmplitudeVector in order
to symmetrise it to the right symmetry in order to be used
with the various matrix-vector-products of this function.

Most importantly the returned functions antisymmetrise
the occupied and virtual parts of the doubles parts
if this is sensible for the method behind this adcmatrix.

Returns a dictionary block identifier -> function
"""
ret = {}
Expand All @@ -394,7 +441,6 @@ def dense_basis(self, axis_blocks=None, ordering="adcc"):
"""
Return the list of indices and their values
of the dense basis representation

ordering: adcc, spin, spatial
"""
ret = []
Expand Down Expand Up @@ -484,19 +530,15 @@ def to_ndarray(self, out=None):
Return the ADC matrix object as a dense numpy array. Converts the sparse
internal representation of the ADC matrix to a dense matrix and return
as a numpy array.

Notes
-----

This method is only intended to be used for debugging and
visualisation purposes as it involves computing a large amount of
matrix-vector products and the returned array consumes a considerable
amount of memory.

The resulting matrix has no spin symmetry imposed, which means that
its eigenspectrum may contain non-physical excitations (e.g. with linear
combinations of α->β and α->α components in the excitation vector).

This function has not been sufficiently tested to be considered stable.
"""
# TODO Update to ph / pphh
Expand Down Expand Up @@ -589,7 +631,6 @@ def __init__(self, matrix, shift=0.0):
"""
Initialise a shifted ADC matrix. Applying this class to a vector ``v``
represents an efficient version of ``matrix @ v + shift * v``.

Parameters
----------
matrix : AdcMatrix
Expand Down Expand Up @@ -638,18 +679,15 @@ def __init__(self, matrix, excitation_blocks, core_orbitals=None,
Initialise a projected ADC matrix, i.e. represents the expression
``P @ M @ P`` where ``P`` is a projector onto a subset of
``excitation_blocks``.

The ``excitation_blocks`` are defined by partitioning the ``o1`` occupied
and ``v1`` virtual space of the ``matrix.mospaces`` into a core-occupied
``c``, valence-occupied ``o``, inner-virtual ``v`` and outer-virtual ``w``.
This matrix will only keep selected blocks in the amplitudes non-zero, which
are selected in the ``excitation_blocks`` list
(e.g. ``["cv", "ccvv", "ocvv"]``).

For details on the option how to select the spaces, see the documentation
in :py:`adcc.ReferenceState.__init__` (``outer_virtuals`` follows the same
rules as ``frozen_virtuals``).

Parameters
----------
matrix : AdcMatrix
Expand Down
120 changes: 120 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,122 @@ 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])

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(comp, s2s(i, j, name))
for comp in dipole_integrals]
for j in np.arange(n_states)]
for i in np.arange(n_states)])

block_dict = {"qed_adc1_off_diag": final_block("adc1")}

if self.method.name == "adc2":
keys = ("qed_adc2_diag", "qed_adc2_edge_couple",
"qed_adc2_edge_phot_couple", "qed_adc2_ph_pphh",
"qed_adc2_pphh_ph")
for key in keys:
block_dict[key] = final_block(key)
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
Loading