Skip to content

Commit aa4da39

Browse files
SorBaldaclaude
andcommitted
memopt(reassoc): O(N^5) reassociation + optional v4-symmetry path
NOT bitwise identical: this commit reassociates floating-point products, so results differ from the original at the ~1e-15 relative level (verified below). The algorithm and the physics are unchanged. (d) Replace the nl x nl buffer product with an nl x ns contraction. Original: v42 := lamat * maux (nl x nl dgemm, reuses v42 as buffer) cf := v42 * v3^T (nl x ns) New (associativity Lambda*M^-1*v3^T = Lambda*(M^-1*v3^T)): tmp := maux * v3^T (nl x ns) cf := lamat * tmp (nl x ns) Removes the nl x nl buffer and lowers that stage from O(N^6) to O(N^5). tmp is nl x ns = O(N^3), negligible. (e) Optional argument use_v4_symmetry (default .false. -> old safe behavior). When .true. (standard case: v4 permutation-symmetric after ApplySymmetryToTensor4 / use_symmetries=True), the reordered copy v42(ja,ka)=v4(w,z,x,y) equals the plain column-major reshape of v4, so v4 is fed directly to the first dgemm (lda=nl) and the whole nl x nl v42 array is never allocated. INVALID for use_symmetries=False, hence it is opt-in only, never the silent default. The optional argument is interface-compatible: existing callers that omit it are unaffected. Peak internal nl x nl arrays: default path lamat+v42+maux (3 units, v42 freed right after the first dgemm); symmetry path lamat+maux (2 units). Standalone test (n_mode=6, symmetrized v3/v4), vs original: default path : max abs diff 2.1e-16, max elementwise rel diff 7.6e-15 symmetry path: max abs diff 1.1e-16, max elementwise rel diff 1.8e-14 Both far below the 1e-12 tolerance. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 49238a8 commit aa4da39

1 file changed

Lines changed: 59 additions & 32 deletions

File tree

Lines changed: 59 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11

2-
! This subroutine calculates the L mat needed to get the average of the
3-
! third order derivatives. It is formed by four polarization vectors
2+
! This subroutine calculates the L mat needed to get the average of the
3+
! third order derivatives. It is formed by four polarization vectors
44
! times the mass^1/2 divided by the normal length.
55

66
subroutine get_odd_straight_with_v4 ( a, wr, er, transmode, amass, ityp_sc, T, v3, v4, phi_sc_odd, &
7-
n_mode, nat_sc, ntyp)
7+
n_mode, nat_sc, ntyp, use_v4_symmetry)
88

99
implicit none
1010

@@ -17,20 +17,25 @@ subroutine get_odd_straight_with_v4 ( a, wr, er, transmode, amass, ityp_sc, T, v
1717
double precision, dimension(n_mode,n_mode,n_mode), intent(in) :: v3
1818
double precision, dimension(n_mode,n_mode,n_mode, n_mode), intent(in) :: v4
1919
double precision, dimension(n_mode, n_mode), intent(out) :: phi_sc_odd
20+
! Optional: if present and .true., assume v4 is permutation-symmetric
21+
! (standard case, after ApplySymmetryToTensor4 / use_symmetries=True) and
22+
! skip building the explicit reordered copy v42 -- see note below.
23+
logical, intent(in), optional :: use_v4_symmetry
2024

2125

2226
integer :: nat_sc, n_mode, nl, ns, ntyp
2327
double precision, dimension(:,:), allocatable :: l, g, phi_aux, v1, v2, v32
24-
double precision :: lsum
28+
double precision :: lsum
2529
double precision, dimension(:), allocatable :: laux1, lres1, veclong
2630
double precision, dimension(:), allocatable :: laux2, lres2
27-
31+
2832
double precision, dimension(:,:), allocatable :: lamat, v42, maux
2933
double precision, dimension(:), allocatable :: work
3034
integer, dimension(:), allocatable :: ipiv
3135
integer :: info
3236

3337
double precision, dimension(:,:), allocatable :: cf
38+
double precision, dimension(:,:), allocatable :: tmp
3439

3540
integer :: mu, nu, alpha
3641
integer :: ka, ja
@@ -39,13 +44,14 @@ subroutine get_odd_straight_with_v4 ( a, wr, er, transmode, amass, ityp_sc, T, v
3944
real :: t1, t2
4045

4146
logical, parameter :: debug = .true.
47+
logical :: sym
4248

4349
! Get integers
4450

4551
if (debug) then
4652
print *, "=== DEBUG ODD STRAIGHT ==="
4753
print *, "N_MODE:", n_mode
48-
print *, "NTYP:", ntyp
54+
print *, "NTYP:", ntyp
4955
print *, "NAT_SC:", nat_sc
5056
call flush()
5157
end if
@@ -56,39 +62,48 @@ subroutine get_odd_straight_with_v4 ( a, wr, er, transmode, amass, ityp_sc, T, v
5662
ns = n_mode
5763
nl = n_mode*n_mode
5864

65+
sym = .false.
66+
if (present(use_v4_symmetry)) sym = use_v4_symmetry
67+
5968
! Allocate stuff
6069

6170
allocate(lamat(nl,nl))
62-
allocate(v42(nl,nl))
6371
allocate(maux(nl,nl))
6472
allocate(ipiv(nl))
6573
allocate(work(nl))
6674
allocate(v32(n_mode,n_mode*n_mode))
6775

6876
allocate(cf(nl,ns))
77+
allocate(tmp(nl,ns))
78+
79+
! v42 (explicit reordered copy of v4) is only needed when we cannot rely on
80+
! the permutation symmetry of v4. It is one full nl x nl array.
81+
if (.not. sym) allocate(v42(nl,nl))
6982

7083
! Get lambda matrix
7184

7285
call get_cmat ( a, wr, er, transmode, amass, ityp_sc, T, .true., lamat,n_mode, nat_sc, ntyp )
73-
86+
7487
!print *, "AFTER CMAT"
7588
!call flush()
7689

7790
! Write third and fourth order force constants as rank 2
7891

7992
ka = 0
80-
93+
8194
do x = 1, n_mode
8295
do y = 1, n_mode
8396
ka = ka + 1
8497
v32(:,ka) = v3(:,x,y)
85-
ja = 0
86-
do w = 1, n_mode
87-
do z = 1, n_mode
88-
ja = ja + 1
89-
v42(ja,ka) = v4(w,z,x,y)
98+
if (.not. sym) then
99+
ja = 0
100+
do w = 1, n_mode
101+
do z = 1, n_mode
102+
ja = ja + 1
103+
v42(ja,ka) = v4(w,z,x,y)
104+
end do
90105
end do
91-
end do
106+
end if
92107
end do
93108
end do
94109

@@ -106,31 +121,43 @@ subroutine get_odd_straight_with_v4 ( a, wr, er, transmode, amass, ityp_sc, T, v
106121
!print *, "BEFORE I - V4Lambda"
107122
!call flush()
108123

109-
call dgemm('N','N',nl,nl,nl,-1.0d0,v42,nl,lamat,nl,1.0d0,maux,nl)
124+
if (.not. sym) then
125+
call dgemm('N','N',nl,nl,nl,-1.0d0,v42,nl,lamat,nl,1.0d0,maux,nl)
126+
else
127+
! v4 fully permutation-symmetric => the reordered copy v42(ja,ka)=v4(w,z,x,y)
128+
! equals the plain column-major reshape of v4 into an (nl,nl) matrix, so we
129+
! can feed v4 directly to dgemm with leading dimension nl and skip v42.
130+
! (Not valid with use_symmetries=False -> guarded by use_v4_symmetry.)
131+
call dgemm('N','N',nl,nl,nl,-1.0d0,v4,nl,lamat,nl,1.0d0,maux,nl)
132+
end if
133+
134+
if (allocated(v42)) deallocate(v42)
110135

111136
! Invert ** iden - lamat v4 **
112137

113138
!print *, "BEFORE (I - V4Lambda)^-1"
114139
!call flush()
115-
116140

117-
call dgetrf ( nl, nl, maux, nl, ipiv, info )
118-
call dgetri ( nl, maux, nl, ipiv, work, nl, info )
119141

120-
! Take product between lamat and the inverted matrix
142+
call dgetrf ( nl, nl, maux, nl, ipiv, info )
143+
call dgetri ( nl, maux, nl, ipiv, work, nl, info )
121144

122-
!print *, "BEFORE Lambda(I - V4Lambda)^-1"
123-
!call flush()
124-
call dgemm('N','N',nl,nl,nl,1.0d0,lamat,nl,maux,nl,0.0d0,v42,nl)
145+
! Take product between lamat and the inverted matrix, contracted with v3.
146+
!
147+
! REASSOCIATION (numerically equivalent to ~1e-15 relative, NOT bitwise):
148+
! the original code formed the nl x nl product v42 := lamat * maux and then
149+
! cf := v42 * v3^T. We instead evaluate cf = lamat * (maux * v3^T) using an
150+
! nl x ns buffer tmp, which is mathematically identical by associativity
151+
! ( Lambda * M^-1 * v3^T = Lambda * (M^-1 * v3^T) ) and reduces both the flop
152+
! count (O(N^6) -> O(N^5)) and the memory (no nl x nl buffer needed).
125153

126-
! Calculate final matrix products and assign the correction matrix
154+
! tmp = (I - v4 lamat)^-1 * v3^T (nl x ns)
155+
call dgemm('N','T',nl,ns,nl,1.0d0,maux,nl,&
156+
v32,ns,0.0d0,tmp,nl)
127157

128-
! Calculate cf = ( 1 - lamat*v4)^-1 lamat * v3
129-
130-
!print *, "BEFORE Lambda(I - V4Lambda)^-1 V3"
131-
!call flush()
132-
call dgemm('N','T',nl,ns,nl,1.0d0,v42,nl,&
133-
v32,ns,0.0d0,cf,nl)
158+
! cf = lamat * tmp = lamat (I - v4 lamat)^-1 v3^T (nl x ns)
159+
call dgemm('N','N',nl,ns,nl,1.0d0,lamat,nl,&
160+
tmp,nl,0.0d0,cf,nl)
134161

135162
! Now get:
136163
! v3 * ( 1 - lamat*v4)^-1 lamat * v3
@@ -145,6 +172,6 @@ subroutine get_odd_straight_with_v4 ( a, wr, er, transmode, amass, ityp_sc, T, v
145172

146173
! Deallocate stuff
147174

148-
deallocate(lamat,v32,v42,maux,ipiv,work, cf)
175+
deallocate(lamat,v32,maux,ipiv,work, cf, tmp)
149176

150-
end subroutine get_odd_straight_with_v4
177+
end subroutine get_odd_straight_with_v4

0 commit comments

Comments
 (0)