MPI-SCATCI 2.0
An MPI version of SCATCI
Loading...
Searching...
No Matches
Postprocessing_module.F90
Go to the documentation of this file.
1! Copyright 2019
2!
3! For a comprehensive list of the developers that contributed to these codes
4! see the UK-AMOR website.
5!
6! This file is part of UKRmol-in (UKRmol+ suite).
7!
8! UKRmol-in is free software: you can redistribute it and/or modify
9! it under the terms of the GNU General Public License as published by
10! the Free Software Foundation, either version 3 of the License, or
11! (at your option) any later version.
12!
13! UKRmol-in is distributed in the hope that it will be useful,
14! but WITHOUT ANY WARRANTY; without even the implied warranty of
15! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16! GNU General Public License for more details.
17!
18! You should have received a copy of the GNU General Public License
19! along with UKRmol-in (in source/COPYING). Alternatively, you can also visit
20! <https://www.gnu.org/licenses/>.
21
22!> \brief Further processing of the diagonalization results
23!> \authors J Benda
24!> \date 2019 - 2023
25!>
26!> Contains subroutines that can be used to extract further interesting data from the eigenvectors.
27!> In particular, this means extraction of boundary amplitudes for outer region codes (see \ref outer_interface)
28!> and calculation of multipole moments, mainly for use in the time-dependent R-matrix code (see \ref cdenprop_properties).
29!>
30!> This module currently supports only the UKRmol+ integral format.
31!>
32module postprocessing_module
33
34 use blas_lapack_gbl, only: blasint
35 use cdenprop_defs, only: civect
36 use precisn, only: shortint, wp
37
38 implicit none
39
40 private
41
42 public postprocess
43
44 ! data types used by RMT and expected in the 'molecular_data' file
45 integer, parameter :: rmt_int = shortint
46 integer, parameter :: rmt_real = wp
47
48 !> \brief SWINTERF-inspired post-processing object
49 !> \authors J Benda
50 !> \date 2019 - 2024
51 !>
52 !> This type extracts and stores outer region channels, as well as boundary amplitudes
53 !> evaluated from the solutions.
54 !>
55 type :: outerinterface
56 integer :: maxchan = 10000 !< Maximal number of channels per irreducible representation. TODO replace by actual number
57 integer :: maxnmo !< Maximal number of continuum molecular orbitals.
58 integer :: ntarg !< Number of outer region states.
59 integer :: nchan !< Number of outer region channels.
60 integer :: ismax !< Largest angular momentum transfer considered (a.k.a. lamax).
61 logical :: auto_nvo !< Automatically determine contracted virtual orbitals.
62
63 integer :: nfdm !< Number of radii at which to evaluate the boundary amplitudes.
64 real(wp) :: rmatr !< R-matrix radius.
65
66 real(wp), allocatable :: r_points(:) !< Boundary amplitude evaluation radii.
67 type(civect), allocatable :: wamp(:) !< Boundary amplitudes (per channel, per eigenstate) for each evaluation radius.
68
69 integer, allocatable :: idtarg(:) !< Denprop-to-congen state order permutation.
70 integer, allocatable :: irrchl(:) !< Irreducible representation of the channel state.
71 integer, allocatable :: ichl(:) !< Index of target per channel.
72 integer, allocatable :: lchl(:) !< Projectile angular momentum per channel.
73 integer, allocatable :: mchl(:) !< Projection of angular momentum per channel.
74 integer, allocatable :: qchl(:) !< Projectile charge per channel.
75 real(wp), allocatable :: echl(:) !< Projectile energy per channel.
76 real(wp), allocatable :: a(:,:) !< Long-range coupling coefficients.
77 integer, allocatable :: core_charges(:) !< Number of electrons, on each atom, represented by ECPs.
78 contains
79 procedure, public :: init => init_outer_interface
80 procedure, public :: deinit => deinit_outer_interface
81 procedure, public :: setup_amplitudes
82
83 procedure, public :: extract_data
84 procedure, private :: get_channel_info
85 procedure, private :: get_channel_couplings
86 procedure, private :: get_boundary_data
87
88 procedure, public :: write_data
89 procedure, private :: write_channel_info
90 procedure, private :: write_boundary_data
91 end type outerinterface
92
93contains
94
95 !> \brief Full post-processing pass
96 !> \authors J Benda
97 !> \date 2019
98 !>
99 !> Call the post-processing sequence, starting with evaluation of inner-region dipole moments, followed by
100 !> extraction of information useful for the outer region.
101 !>
102 !> \param[in] SCATCI_input Input SCATCI namelist data for all symmetries.
103 !> \param[in] solutions Eigenvectors and related stuff calculated by the diagonalizer.
104 !>
105 subroutine postprocess (SCATCI_input, solutions)
106
107 use class_molecular_properties_data, only: molecular_properties_data
108 use options_module, only: optionsset
109 use solutionhandler_module, only: solutionhandler
110
111 type(optionsset), intent(in) :: scatci_input
112 type(solutionhandler), allocatable, intent(inout) :: solutions(:)
113
114 integer :: iitf
115
116 if (allocated(scatci_input % interface_opts)) then
117 call redistribute_solutions(scatci_input, solutions)
118 do iitf = 1, size(scatci_input % interface_opts)
119 block
120 type(molecular_properties_data) :: properties_all
121 call cdenprop_properties(scatci_input, iitf, solutions, properties_all)
122 call outer_interface(scatci_input, iitf, solutions, properties_all)
123 end block
124 end do
125 end if
126
127 end subroutine postprocess
128
129
130 !> \brief Redistribute solutions from groups to everyone
131 !> \authors J Benda
132 !> \date 2019
133 !>
134 !> This subroutine needs to be called before using solutions in other subroutines of this module. The diagonalizations
135 !> may have run in MPI groups and so the eigenvectors are not evenly distributed among all processes, which would cause
136 !> comlications in communication. This subroutine will redistribute the eigenvectors from groups to the MPI world group.
137 !>
138 !> \param[in] SCATCI_input Input SCATCI namelist data for all symmetries.
139 !> \param[in] solutions Eigenvectors and related stuff calculated by the diagonalizer.
140 !>
141 subroutine redistribute_solutions (SCATCI_input, solutions)
142
144 use mpi_gbl, only: mpiint, mpi_mod_bcast
145 use options_module, only: optionsset
146 use solutionhandler_module, only: solutionhandler
147 use parallelization_module, only: grid => process_grid
148
149 type(optionsset), intent(in) :: SCATCI_input
150 type(solutionhandler), allocatable, intent(inout) :: solutions(:)
151
152 type(solutionhandler) :: oldsolution
153 integer(mpiint) :: srcrank
154 integer :: i, nnuc, nstat
155
156 ! nothing to do when the solutions are already uniformly distributed everywhere
157 if (grid % sequential) return
158
159 ! set up the uniformly distributed solutions
160 do i = 1, size(scatci_input % opts)
161 if (iand(scatci_input % opts(i) % vector_storage_method, pass_to_cdenprop) /= 0 .and. &
162 scatci_input % opts(i) % diagonalization_flag /= no_diagonalization) then
163
164 ! 1. Share metadata for the solutions from the master of group that found that solution to everyone.
165
166 srcrank = grid % group_master_world_rank(grid % which_group_is_work(i))
167
168 call mpi_mod_bcast(solutions(i) % core_energy, srcrank)
169 call mpi_mod_bcast(solutions(i) % vec_dimen, srcrank)
170
171 call mpi_mod_bcast(solutions(i) % ci_vec % nstat, srcrank)
172 call mpi_mod_bcast(solutions(i) % ci_vec % mgvn, srcrank)
173 call mpi_mod_bcast(solutions(i) % ci_vec % s, srcrank)
174 call mpi_mod_bcast(solutions(i) % ci_vec % sz, srcrank)
175 call mpi_mod_bcast(solutions(i) % ci_vec % nnuc, srcrank)
176 call mpi_mod_bcast(solutions(i) % ci_vec % e0, srcrank)
177 call mpi_mod_bcast(solutions(i) % ci_vec % CV_is_scalapack, srcrank)
178 call mpi_mod_bcast(solutions(i) % ci_vec % mat_dimen_r, srcrank)
179 call mpi_mod_bcast(solutions(i) % ci_vec % mat_dimen_c, srcrank)
180
181 nnuc = solutions(i) % ci_vec % nnuc
182 nstat = solutions(i) % ci_vec % nstat
183
184 if (.not. allocated(solutions(i) % ci_vec % ei)) then
185 allocate (solutions(i) % ci_vec % ei(nstat))
186 end if
187
188 call mpi_mod_bcast(solutions(i) % ci_vec % cname(1:nnuc), srcrank)
189 call mpi_mod_bcast(solutions(i) % ci_vec % charge(1:nnuc), srcrank)
190 call mpi_mod_bcast(solutions(i) % ci_vec % xnuc(1:nnuc), srcrank)
191 call mpi_mod_bcast(solutions(i) % ci_vec % ynuc(1:nnuc), srcrank)
192 call mpi_mod_bcast(solutions(i) % ci_vec % znuc(1:nnuc), srcrank)
193 call mpi_mod_bcast(solutions(i) % ci_vec % ei(1:nstat), srcrank)
194
195 ! 2. Backup the original solutions, which are distributed over the MPI group.
196
197 call oldsolution % destroy()
198 oldsolution = solutions(i)
199
200 if (.not. grid % is_my_group_work(i)) then
201 oldsolution % ci_vec % blacs_context = -1
202 oldsolution % ci_vec % descr_CV_mat = -1
203
204 ! Initialize dummy submatrices for ranks that are not members of the original group.
205 ! This is needed for formal correctness; gfortran's -fcheck=all otherwise chokes
206 ! when passing these submatrices to pdgemr2d in the redistribution below.
207 allocate (oldsolution % ci_vec % cv(0, 0))
208 end if
209
210 ! 3. Redistribute the eigenvectors over whole MPI world.
211
212 solutions(i) % ci_vec % blacs_context = grid % wcntxt
213 solutions(i) % ci_vec % nprow = grid % wprows
214 solutions(i) % ci_vec % npcol = grid % wpcols
215
216 call solutions(i) % ci_vec % init_CV(int(oldsolution % ci_vec % mat_dimen_r), &
217 int(oldsolution % ci_vec % mat_dimen_c))
218 call solutions(i) % ci_vec % redistribute(oldsolution % ci_vec)
219
220 end if
221 end do
222
223 end subroutine redistribute_solutions
224
225
226 !> \brief Evaluate multipoles in CDENPROP
227 !> \authors J Benda
228 !> \date 2019 - 2024
229 !>
230 !> Calculate properties (multipole moments) of the solutions using CDENPROP library. Only those symmetries
231 !> that have `igh = 1, vecstore = 3` set will be used. This subroutine also assumes that setups for all
232 !> symmetries are compatible, in particular:
233 !> - use the same molecular integral format
234 !> - use the same CI targets (in case of scattering diagonalization)
235 !>
236 !> \param[in] SCATCI_input Input SCATCI namelist data for all symmetries.
237 !> \param[in] iitf Which outer_interface namelist to consider.
238 !> \param[in] solutions Eigenvectors and related stuff calculated by the diagonalizer.
239 !> \param[out] properties_all Table of properties calculated by CDENPROP.
240 !>
241 subroutine cdenprop_properties (SCATCI_input, iitf, solutions, properties_all)
242
243 use class_molecular_properties_data, only: molecular_properties_data
244 use class_namelists, only: cdenprop_namelists
246 use cdenprop_procs, only: cdenprop_drv
247 use cdenprop_defs, only: ir_max
248 use mpi_gbl, only: master
249 use options_module, only: optionsset
250 use solutionhandler_module, only: solutionhandler
251 use timing_module, only: master_timer
252
253 type(optionsset), intent(in) :: SCATCI_input
254 integer, intent(in) :: iitf
255 type(solutionhandler), allocatable, intent(inout) :: solutions(:)
256 type(molecular_properties_data), intent(inout) :: properties_all
257
258 type(cdenprop_namelists) :: namelist_ij
259 type(civect) :: oldblock
260
261 integer :: i, j, ntgsym, ninputs, maxpole, nblock, mxstat
262
263 ninputs = size(scatci_input % opts)
264 maxpole = 2
265
266 ! for now, skip evaluation of properties if RMT data not required
267 if (.not. scatci_input % interface_opts(iitf) % write_dip .and. &
268 .not. scatci_input % interface_opts(iitf) % write_rmt) return
269
270 ! prepare free slots for the distributed dense property matrices
271 call properties_all % clean
272 call properties_all % preallocate_property_blocks(ninputs**2 * (maxpole + 1)**2)
273 properties_all % dense_blocks(:) % mat_dimen = 0
274
275 ! calculate properties for all pairs of symmetries
276 sym_i_loop: do i = 1, ninputs
277 if (iand(scatci_input % opts(i) % vector_storage_method, pass_to_cdenprop) /= 0 .and. &
278 scatci_input % opts(i) % diagonalization_flag /= no_diagonalization) then
279
280 sym_j_loop: do j = i, ninputs
281 if (iand(scatci_input % opts(j) % vector_storage_method, pass_to_cdenprop) /= 0 .and. &
282 scatci_input % opts(j) % diagonalization_flag /= no_diagonalization) then
283
284 ! only calculate self-overlaps when required
285 if (i == j .and. .not. scatci_input % interface_opts(iitf) % all_props) cycle
286
287 ! virtual CDENPROP namelist
288 call namelist_ij % init(2)
289
290 ! assign values obtained from SCATCI namelists
291 namelist_ij % lucsf(1:2) = (/ scatci_input % opts(i) % megul, &
292 scatci_input % opts(j) % megul /)
293 namelist_ij % nciset(1:2) = (/ scatci_input % opts(i) % output_ci_set_number, &
294 scatci_input % opts(j) % output_ci_set_number /)
295 namelist_ij % nstat = 0 ! use all states
296 namelist_ij % lupropw = 0 ! no output needed now, will be done later
297 namelist_ij % lucivec = 0 ! no CI units needed, will be given as arguments
298 namelist_ij % numtgt = 0 ! clear target states; subset of the array populated below
299 namelist_ij % lutdip = scatci_input % interface_opts(iitf) % lutdip
300 namelist_ij % ukrmolp_ints = scatci_input % opts(i) % use_UKRMOL_integrals
301 namelist_ij % luprop = merge(scatci_input % opts(i) % integral_unit, &
302 scatci_input % interface_opts(iitf) % luprop, &
303 scatci_input % interface_opts(iitf) % luprop < 0)
304 namelist_ij % isw = scatci_input % interface_opts(iitf) % isw
305
306 ntgsym = scatci_input % opts(i) % num_target_sym
307
308 ! following depend on whether this is target or scattering calculation
309 if (scatci_input % opts(i) % ci_target_flag == no_ci_target) then
310 namelist_ij % max_multipole = 2 ! dipole + quadrupole
311 else
312 namelist_ij % max_multipole = 1 ! dipole only
313 namelist_ij % numtgt(1:ntgsym) = scatci_input % opts(i) % num_target_state_sym(1:ntgsym)
314 namelist_ij % itarget_spin(1:ntgsym) = scatci_input % interface_opts(iitf) % itspin(1:ntgsym)
315 end if
316
317 ! use BLACS context of the first vector for communication (they are all of the same shape anyway)
318 solutions(j) % ci_vec % blacs_context = solutions(i) % ci_vec % blacs_context
319 solutions(j) % ci_vec % descr_CV_mat(2) = solutions(i) % ci_vec % descr_CV_mat(2)
320
321 ! get the properties in CDENPROP
322 call master_timer % start_timer("Cdenprop")
323 call cdenprop_drv(solutions(i) % ci_vec, solutions(j) % ci_vec, namelist_ij, properties_all)
324 call master_timer % stop_timer("Cdenprop")
325 call master_timer % report_timers
326
327 ! cleanup
328 call namelist_ij % dealloc
329
330 end if
331 end do sym_j_loop
332
333 end if
334 end do sym_i_loop
335
336 ! prefer SWINTERF-compatible format of the property file (so that it can be, e.g., read back to CDENPROP_ALL)
337 properties_all % swintf_format = .true.
338
339 ! write calculated properties
340 if (scatci_input % interface_opts(iitf) % write_dip .and. properties_all % no_symmetries > 0) then
341 if (scatci_input % interface_opts(iitf) % all_props) call properties_all % sort_by_energy
342 call properties_all % write_properties(scatci_input % interface_opts(iitf) % lupropw, &
343 scatci_input % opts(1) % use_UKRMOL_integrals)
344 end if
345
346 ! inflate all property matrices to uniform size for easy output for RMT, which wants it that way
347 if (scatci_input % interface_opts(iitf) % write_rmt) then
348 nblock = properties_all % no_blocks
349 mxstat = max(maxval(properties_all % dense_blocks(1:nblock) % mat_dimen_r), &
350 maxval(properties_all % dense_blocks(1:nblock) % mat_dimen_c))
351 do i = 1, nblock
352 if (properties_all % dense_blocks(i) % mat_dimen_r /= mxstat .or. &
353 properties_all % dense_blocks(i) % mat_dimen_c /= mxstat) then
354 oldblock = properties_all % dense_blocks(i)
355 call properties_all % dense_blocks(i) % init_CV(mxstat, mxstat)
356 properties_all % dense_blocks(i) % CV = 0
357 call properties_all % dense_blocks(i) % redistribute(oldblock)
358 call oldblock % final_CV
359 end if
360 properties_all % dense_blocks(i) % mat_dimen = max(properties_all % dense_blocks(i) % mat_dimen_r, &
361 properties_all % dense_blocks(i) % mat_dimen_c)
362 end do
363 end if
364
365 end subroutine cdenprop_properties
366
367
368 !> \brief Extract data needed by outer-region codes
369 !> \authors J Benda
370 !> \date 2019 - 2024
371 !>
372 !> This subroutine does the following:
373 !> - write channel information to unit LUCHAN
374 !> - write boundary amplitudes to unit LURMT
375 !> - write RMT data to file `molecular_data`
376 !>
377 !> Essentially, it should cover the work of SWINTERF and RMT_INTERFACE. At the moment, only the symmetries
378 !> marked with `vecstore` with the second least-significant bit set (i.e., 2, 5 or 7) are processed in the outer interface.
379 !>
380 !> The outer interface has an automatic support for the contracted virtual orbitals. When the number of continuum orbitals
381 !> specified in GBTOlib (scatci_integrals) is different than the number of CI target continuum orbitals specified in the input
382 !> for MPI-SCATCI, the difference (i.e. target orbitals included as fake continuum) are automatically recognized as contracted
383 !> virtual orbitals. This is equivalent to the NVO infrastructure in SWINTERF. If more fine-grained control over calculation
384 !> of NVO is desired, use the legacy SCATCI with SWINTERF instead.
385 !>
386 !> \param[in] SCATCI_input Input SCATCI namelist data for all symmetries.
387 !> \param[in] iitf Which outer_interface namelist to use.
388 !> \param[in] solutions Eigenvectors and related stuff calculated by the diagonalizer.
389 !> \param[out] inner_properties Table of (N+1) properties calculated by CDENPROP.
390 !>
391 subroutine outer_interface (SCATCI_input, iitf, solutions, inner_properties)
392
393 use class_molecular_properties_data, only: molecular_properties_data
395 use options_module, only: optionsset
396 use solutionhandler_module, only: solutionhandler
397 use timing_module, only: master_timer
398
399 type(optionsset), intent(in) :: SCATCI_input
400 integer, intent(in) :: iitf
401 type(solutionhandler), allocatable, intent(in) :: solutions(:)
402 type(molecular_properties_data), intent(in) :: inner_properties
403
404 type(molecular_properties_data) :: target_properties
405 type(outerinterface), allocatable :: intf(:)
406
407 integer :: i, nsym, nset
408
409 nset = 0 ! number of sets in the output files fort.10 and fort.21
410 nsym = size(scatci_input % opts) ! number of inputs (symmetries) that we dealt with
411
412 if (.not. scatci_input % interface_opts(iitf) % write_amp .and. &
413 .not. scatci_input % interface_opts(iitf) % write_rmt) return
414
415 allocate (intf(nsym))
416
417 call target_properties % read_properties(scatci_input % interface_opts(iitf) % lutarg, 1)
418
419 call master_timer % start_timer("Outer interface")
420
421 ! open the outer interface files for writing
422 if (scatci_input % interface_opts(iitf) % cform == 'F') then
423 open (unit = scatci_input % interface_opts(iitf) % luchan, form = 'formatted', action = 'write')
424 else
425 open (unit = scatci_input % interface_opts(iitf) % luchan, form = 'unformatted', action = 'write')
426 end if
427 if (scatci_input % interface_opts(iitf) % rform == 'F') then
428 open (unit = scatci_input % interface_opts(iitf) % lurmt, form = 'formatted', action = 'write')
429 else
430 open (unit = scatci_input % interface_opts(iitf) % lurmt, form = 'unformatted', action = 'write')
431 end if
432
433 ! run SWINTERF for all symmetries
434 do i = 1, nsym
435 call intf(i) % init(scatci_input % interface_opts(iitf))
436 if (iand(scatci_input % opts(i) % vector_storage_method, pass_to_cdenprop) /= 0 .and. &
437 scatci_input % opts(i) % diagonalization_flag /= no_diagonalization) then
438 nset = nset + 1
439 call intf(i) % setup_amplitudes(scatci_input % opts(i))
440 call intf(i) % extract_data(scatci_input % opts(i), target_properties, solutions(i))
441 if (scatci_input % interface_opts(iitf) % write_amp) then
442 call intf(i) % write_data(i, scatci_input, iitf, target_properties, solutions(i))
443 end if
444 end if
445 end do
446
447 close (unit = scatci_input % interface_opts(iitf) % luchan)
448 close (unit = scatci_input % interface_opts(iitf) % lurmt)
449
450 if (scatci_input % interface_opts(iitf) % write_rmt) then
451 call write_rmt_data(scatci_input, iitf, inner_properties, solutions, intf)
452 end if
453
454 call master_timer % stop_timer("Outer interface")
455
456 end subroutine outer_interface
457
458
459 !> \brief Allocate memory for channel information
460 !> \authors J Benda
461 !> \date 2019 - 2024
462 !>
463 !> \param[in] this Interface object to initialize.
464 !> \param[in] input Input SCATCI namelist data for all symmetries.
465 !>
466 subroutine init_outer_interface (this, input)
467
468 use mpi_gbl, only: mpi_xermsg
469 use options_module, only: interfaceoptions
470
471 class(outerinterface), intent(inout) :: this
472 type(interfaceoptions), intent(in) :: input
473
474 integer :: i, ierr
475
476 this % ntarg = 0
477 this % nchan = 0
478 this % ismax = 0
479 this % maxnmo = 0
480
481 this % nfdm = merge(input % nfdm, 0, input % write_rmt)
482 this % rmatr = input % rmatr
483 this % auto_nvo = input % auto_nvo
484
485 allocate (this % r_points(this % nfdm + 1))
486
487 do i = 1, this % nfdm + 1
488 this % r_points(i) = this % rmatr - (this % nfdm + 1 - i) * input % delta_r
489 end do
490
491 allocate (this % irrchl(this % maxchan), &
492 this % ichl(this % maxchan), &
493 this % lchl(this % maxchan), &
494 this % mchl(this % maxchan), &
495 this % qchl(this % maxchan), &
496 this % echl(this % maxchan), stat = ierr)
497
498 if (ierr /= 0) then
499 call mpi_xermsg('OuterInterface', 'init_outer_interface', 'Failed to allocate memory for channel data.', 1, 1)
500 end if
501
502 if (allocated(input % idtarg)) then
503 this % ntarg = size(input % idtarg)
504 this % idtarg = input % idtarg
505 end if
506
507 end subroutine init_outer_interface
508
509
510 !> \brief Release memory held by this object
511 !> \authors J Benda
512 !> \date 2019
513 !>
514 !> \param[in] this Interface object to finalize.
515 !>
516 subroutine deinit_outer_interface (this)
517#if defined(usempi) && defined(scalapack)
518 use cdenprop_defs, only: blacs_gridexit
519#endif
520 class(outerinterface), intent(inout) :: this
521
522 integer :: i
523
524 ! release the no-longer-needed BLACS context
525 if (allocated(this % wamp)) then
526 do i = 1, size(this % wamp)
527 call this % wamp(i) % final_CV
528#if defined(usempi) && defined(scalapack)
529 if (i == 1 .and. this % wamp(this % nfdm + 1) % blacs_context >= 0) then
530 call blacs_gridexit(this % wamp(this % nfdm + 1) % blacs_context)
531 end if
532#endif
533 end do
534 deallocate (this % wamp)
535 end if
536
537 if (allocated(this % irrchl)) deallocate (this % irrchl)
538 if (allocated(this % ichl)) deallocate (this % ichl)
539 if (allocated(this % lchl)) deallocate (this % lchl)
540 if (allocated(this % mchl)) deallocate (this % mchl)
541 if (allocated(this % qchl)) deallocate (this % qchl)
542 if (allocated(this % echl)) deallocate (this % echl)
543 if (allocated(this % a)) deallocate (this % a)
544 if (allocated(this % r_points)) deallocate (this % r_points)
545
546 this % nchan = 0
547 this % ismax = 0
548 this % maxnmo = 0
549 this % nfdm = 0
550
551 end subroutine deinit_outer_interface
552
553
554 !> \brief Initialize raw boundary amplitudes (in integral library)
555 !> \authors J Benda
556 !> \date 2019
557 !>
558 !> \param[in] this Interface object to read.
559 !> \param[in] opts SCATCI input namelist data.
560 !>
561 subroutine setup_amplitudes (this, opts)
562
563 use options_module, only: options
564 use ukrmol_interface_gbl, only: read_ukrmolp_basis, eval_amplitudes, get_ecp_core_charges
565
566 class(outerinterface), intent(inout) :: this
567 type(options), intent(in) :: opts
568
569 if (opts % use_UKRMOL_integrals) then
570 call read_ukrmolp_basis(opts % integral_unit)
571 call eval_amplitudes(this % rmatr, .true.)
572 call get_ecp_core_charges(this % core_charges)
573 end if
574
575 end subroutine setup_amplitudes
576
577
578 !> \brief Get interface data
579 !> \authors J Benda
580 !> \date 2019 - 2023
581 !>
582 !> This mirros the functionality of SWITERF (UKRmol-out), particularly:
583 !> - reads the target property file (*fort.24*)
584 !> - assembles the list of outer region channels
585 !> - evaluates amplitudes of the eigenstates at R-matrix boundary and, if requested, in its vicinity for use in RMT
586 !>
587 !> All data are then stored internally for writing.
588 !>
589 !> \param[in] this Interface object to read.
590 !> \param[in] opts SCATCI input namelist data.
591 !> \param[in] prop Target properties.
592 !> \param[in] solution Eigenvectors and related stuff calculated by the diagonalizer.
593 !>
594 subroutine extract_data (this, opts, prop, solution)
595
596 use class_molecular_properties_data, only: molecular_properties_data
597 use mpi_gbl, only: master, myrank
598 use options_module, only: options
599 use solutionhandler_module, only: solutionhandler
600
601 class(outerinterface), intent(inout) :: this
602 type(molecular_properties_data), intent(in) :: prop
603 type(options), intent(in) :: opts
604 type(solutionhandler), intent(in) :: solution
605
606 integer, parameter :: ismax = 2
607 real(wp), parameter :: alpha0 = 0
608 real(wp), parameter :: alpha2 = 0
609
610 logical :: use_pol
611 integer :: lamax
612
613 lamax = ismax
614 use_pol = .false. ! if set to .true., then channel_couplings will take into account the polarizability
615
616 if (alpha0 /= 0 .or. alpha2 /= 0) then
617 use_pol = .true.
618 lamax = max(ismax, 3) ! polarizability (1/r^4) corresponds to lambda = 3
619 end if
620
621 this % ismax = lamax
622
623 call this % get_channel_info(opts, prop)
624 call this % get_boundary_data(opts, prop, solution)
625
626 ! only the master process writes the outer region potential coefficients; other do not need to have them
627 if (myrank == master) then
628 call this % get_channel_couplings(lamax, prop % no_states, prop, alpha0, alpha2, use_pol)
629 end if
630
631 ! redefine magnetic quantum number by multiplying in the projectile charge
632 this % mchl(1:this % nchan) = this % mchl(1:this % nchan) * this % qchl(1:this % nchan)
633
634 end subroutine extract_data
635
636
637 !> \brief Write interface data
638 !> \authors J Benda
639 !> \date 2019 - 2024
640 !>
641 !> Updates the channel and R-matrix dump files (fort.10 and fort.21, respectively) by adding/overwriting a data set.
642 !>
643 !> \param[in] this Interface object to read.
644 !> \param[in] isym Output set number for this irreducible representation.
645 !> \param[in] opts SCATCI input namelist data.
646 !> \param[in] iitf Which outer_interface namelist to use.
647 !> \param[in] prop Target properties.
648 !> \param[in] solution Eigenvectors and related stuff calculated by the diagonalizer.
649 !>
650 subroutine write_data (this, isym, opts, iitf, prop, solution)
651
652 use class_molecular_properties_data, only: molecular_properties_data
653 use options_module, only: optionsset
654 use solutionhandler_module, only: solutionhandler
655
656 class(outerinterface), intent(in) :: this
657 type(molecular_properties_data), intent(in) :: prop
658 type(optionsset), intent(in) :: opts
659 type(solutionhandler), intent(in) :: solution
660 integer, intent(in) :: isym, iitf
661
662 call this % write_channel_info(isym, &
663 opts % opts(isym), &
664 opts % interface_opts(iitf) % luchan, &
665 opts % interface_opts(iitf) % cform, &
666 prop)
667
668 call this % write_boundary_data(isym, &
669 opts % opts(isym), &
670 opts % interface_opts(iitf) % lurmt, &
671 opts % interface_opts(iitf) % rform, &
672 prop, &
673 solution)
674
675 end subroutine write_data
676
677
678 !> \brief Assemble the list of outer channels
679 !> \authors J Benda
680 !> \date 2019
681 !>
682 !> Adapted from SWCHANL (UKRmol-out). Reads the target information from `fort.24` and, with the knowledge
683 !> of the (N+1)-electron setup, determines the individual channels.
684 !>
685 !> \param[inout] this Interface object to update.
686 !> \param[in] opts SCATCI namelist information for this irreducible representation.
687 !> \param[in] prop Target properties.
688 !>
689 subroutine get_channel_info (this, opts, prop)
690
691 use algorithms, only: findloc
692 use class_molecular_properties_data, only: molecular_properties_data
693 use const_gbl, only: stdout
694 use global_utils, only: mprod
695 use mpi_gbl, only: mpi_xermsg
696 use options_module, only: options
697 use precisn, only: wp
698 use ukrmol_interface_gbl, only: ukp_preamp
699
700 class(outerinterface), intent(inout) :: this
701 type(options), intent(in) :: opts
702 type(molecular_properties_data), intent(in) :: prop
703
704 integer :: i, j, k, mgvn, spin, tgt_symm, tgt_mgvn, tgt_spin, nch, prj_mgvn, congen_tgt_id, denprop_tgt_id, isym
705 real(wp) :: tgt_enrg, ebase
706
707 ebase = prop % energies(1)
708 mgvn = opts % lambda
709 spin = nint(2 * opts % spin + 1)
710
711 this % maxnmo = sum(opts % num_orbitals_sym)
712 this % nchan = 0
713
714 ! construct default IDTARG, i.e. denprop (energy-order) index for each congen (symmetry-order) target state
715 if (.not. allocated(this % idtarg)) then
716 this % ntarg = opts % total_num_target_states
717 allocate (this % idtarg(this % ntarg))
718 this % idtarg = -1
719 congen_tgt_id = 0
720 congen_symm_loop: do j = 1, size(opts % num_target_state_sym)
721 congen_targ_loop: do k = 1, opts % num_target_state_sym(j)
722 congen_tgt_id = congen_tgt_id + 1
723 denprop_tgt_id = 0
724 tgt_mgvn = opts % target_spatial(congen_tgt_id)
725 tgt_spin = opts % target_multiplicity(congen_tgt_id)
726 denprop_targ_loop: do i = 1, prop % no_states
727 isym = prop % energies_index(2, i)
728 if (prop % symmetries_index(1, isym) == tgt_mgvn .and. &
729 prop % symmetries_index(2, isym) == tgt_spin) then
730 denprop_tgt_id = denprop_tgt_id + 1
731 end if
732 if (denprop_tgt_id == k) then
733 this % idtarg(congen_tgt_id) = i
734 exit denprop_targ_loop
735 end if
736 end do denprop_targ_loop
737 end do congen_targ_loop
738 end do congen_symm_loop
739 if (any(this % idtarg == -1)) then
740 call mpi_xermsg('Postprocessing_module', 'get_channel_info', 'Property file not compatible with target symmetries&
741 &given in the SCATCI input file.', 1, 1)
742 end if
743 end if
744
745 write (stdout, '(/,1x,"Target state indices sorted by energy (IDTARG): ")')
746 write (stdout, '( 1x,20I4)') this % idtarg
747
748 do i = 1, prop % no_states
749
750 ! skip states not referenced in IDTARG
751 if (findloc(this % idtarg, i, 1) == 0) cycle
752
753 ! retrieve target data from the properties structure
754 tgt_symm = prop % energies_index(2, i)
755 tgt_mgvn = prop % symmetries_index(1, tgt_symm)
756 tgt_spin = prop % symmetries_index(2, tgt_symm)
757 tgt_enrg = prop % energies(i)
758
759 ! angular momentum composition rule -- must allow for projectile spin
760 if (abs(spin - tgt_spin) /= 1) cycle
761
762 ! get projectile total symmetry IRR from the multiplication table
763 prj_mgvn = mprod(tgt_mgvn + 1, mgvn + 1, 0, stdout)
764
765 ! retrieve channel descriptions from the integral library
766 if (opts % use_UKRMOL_integrals) then
767 call ukp_preamp(prj_mgvn, this % nchan + 1, this % lchl, this % mchl, this % qchl, nch, this % maxnmo)
768 else
769 call mpi_xermsg('Postprocessing_module', 'get_channel_info', &
770 'Interface for SWINTERF integrals not implemented.', 1, 1)
771 end if
772
773 ! fill in the remaining channel identifiers
774 do j = 1, nch
775 this % ichl(this % nchan + j) = i
776 this % echl(this % nchan + j) = 2 * (tgt_enrg - ebase)
777 this % irrchl(this % nchan + j) = prj_mgvn
778 end do
779
780 ! update total number of channels
781 this % nchan = this % nchan + nch
782
783 end do
784
785 write (stdout, '(/," Channel Target l m q Irr Energy Energy(eV)")')
786
787 do i = 1, this % nchan
788 write (stdout, '(I5,I8,I5,2I3,2x,A4,I3,2F10.6)') i, this % ichl(i), this % lchl(i), this % mchl(i), this % qchl(i), &
789 'N/A', this % irrchl(i), this % echl(i), this % echl(i) / 0.073500d0
790 end do
791
792 end subroutine get_channel_info
793
794
795 !> \brief Evaluate boundary amplitudes for propagation and RMT
796 !> \authors J Benda
797 !> \date 2019 - 2024
798 !>
799 !> Calculates the contribution to each channel amplitude per eigenstate. The master process retrieves the raw boundary
800 !> amplitudes from the integral library, then distributes them in the BLACS context of the solution vector, where the
801 !> multiplication takes place.
802 !>
803 !> The boundary amplitudes evaluated at the R_matrix sphere (but not those evaluated inside) are then collected to master
804 !> process, who will need to write them to the record-based swinterf output file.
805 !>
806 !> \todo This subroutine contains a potential hidden memory scaling obstacle, which is the evaluation of the boundary
807 !> amplitudes in GBTOlib. Even though GBTOlib internally stores just a compact array of orbital boundary amplitudes
808 !> (dimension Npw × Norb), its interface only allows retrieval of the channel boundary amplitudes (Nchan × Norb).
809 !> For calculations with large numbers of channels (= target states), this is a significantly larger matrix, which
810 !> should be distributed. However, this is not possible with the current GBTOlib interface.
811 !>
812 !> \param[in] this Interface object to update.
813 !> \param[in] opts SCATCI namelist information for this irreducible representation.
814 !> \param[in] prop Results from denprop.
815 !> \param[in] solution Eigenvectors and related stuff calculated by the diagonalizer.
816 !>
817 subroutine get_boundary_data (this, opts, prop, solution)
818
819 use algorithms, only: findloc
820 use class_molecular_properties_data, only: molecular_properties_data
821#if defined(usempi) && defined(scalapack)
822 use cdenprop_defs, only: blacs_get, blacs_gridinit
823#endif
824 use const_gbl, only: stdout
825 use global_utils, only: mprod
826 use mpi_gbl, only: master, myrank, mpi_xermsg
827 use options_module, only: options
828 use solutionhandler_module, only: solutionhandler
829 use ukrmol_interface_gbl, only: eval_amplitudes, ukp_readamp
830
831 class(outerinterface), intent(inout) :: this
832 type(options), intent(in) :: opts
833 type(molecular_properties_data), intent(in) :: prop
834 type(solutionhandler), intent(in) :: solution
835
836 type(civect) :: orb_amps, dist_orb_amps
837
838 integer, allocatable :: idchl(:), nvo(:), tgcontirr(:)
839 real(wp), allocatable :: ampls(:)
840
841 integer :: i, j, k, ir, ncontmo(8), iprnt = 0, nchan, nstat, norbs, morbs, ncnt, offs, totirr, tgtirr
842
843 nchan = this % nchan
844 nstat = solution % ci_vec % nstat
845 morbs = maxval(opts % num_orbitals_sym) ! safe upper bound on maximal number of continuum orbitals per channel...
846 norbs = dot_product(opts % num_target_state_sym, opts % num_continuum_orbitals_target) ! ... and total
847
848 allocate(ampls(morbs), idchl(nchan), tgcontirr(this % ntarg), nvo(opts % num_syms))
849
850 ! set up energy order channel map
851 k = 0
852 do j = 1, this % ntarg
853 do i = 1, nchan
854 if (this % ichl(i) == this % idtarg(j)) then
855 k = k + 1
856 idchl(k) = i
857 end if
858 end do
859 totirr = opts % lambda + 1
860 tgtirr = prop % symmetries_index(1, prop % energies_index(2, j)) + 1
861 tgcontirr(j) = mprod(totirr, tgtirr, 0, stdout)
862 end do
863
864 write (stdout, '(/,1x,"Channel target state energy map (IDCHL): ")')
865 write (stdout, '( 1x,20I5)') idchl
866
867 ! set up storage for the raw boundary amplitudes (master-only ScaLAPACK matrix)
868 orb_amps % nprow = 1
869 orb_amps % npcol = 1
870 orb_amps % blacs_context = -1
871 orb_amps % CV_is_scalapack = solution % ci_vec % CV_is_scalapack
872#if defined(usempi) && defined(scalapack)
873 if (orb_amps % CV_is_scalapack) then
874 call blacs_get(-1_blasint, 0_blasint, orb_amps % blacs_context)
875 call blacs_gridinit(orb_amps % blacs_context, 'R', 1_blasint, 1_blasint)
876 end if
877#endif
878 call orb_amps % init_CV(nchan, norbs) ! WARNING: this can be a fairly big matrix, but cannot be distributed
879
880 ! set up storage for the evaluated boundary amplitudes
881 if (.not. allocated(this % wamp)) then
882 allocate (this % wamp(this % nfdm + 1))
883 end if
884
885 ! set up cyclic distribution descriptors
886 this % wamp(:) % nprow = solution % ci_vec % nprow
887 this % wamp(:) % npcol = solution % ci_vec % npcol
888 this % wamp(:) % blacs_context = solution % ci_vec % blacs_context
889 this % wamp(:) % CV_is_scalapack = solution % ci_vec % CV_is_scalapack
890
891 ! allocate the matrices
892 do ir = 1, this % nfdm + 1
893 call this % wamp(ir) % init_CV(nchan, nstat)
894 end do
895
896 ! for all evaluation radii
897 do ir = 1, this % nfdm + 1
898
899 ! get raw boundary amplitudes of the continuum orbitals
900 if (myrank == master) then
901 ! retrieve orbital amplitudes from the integral library
902 call eval_amplitudes(this % r_points(ir), .false.)
903 call ukp_readamp(orb_amps % CV, this % nchan, this % irrchl, this % lchl, this % mchl, this % qchl, ncontmo, &
904 opts % lambda_continuum_orbitals_target, iprnt)
905
906 ! calculate number of contracted virtual orbitals (i.e. orbitals that are not continuum orbitals according
907 ! to GBTOlib, but they are specified as continuum in SCATCI anyway)
908 if (this % auto_nvo) then
909 nvo(1 : opts % num_syms) = opts % num_orbitals_sym(1 : opts % num_syms) &
910 - opts % num_target_orbitals_sym(1 : opts % num_syms) &
911 - ncontmo(1 : opts % num_syms)
912 else
913 nvo = 0
914 end if
915
916 if (ir == 1) then
917 write (stdout, '(/,1x,"Virtual orbitals per CI target continuum spin-symmetry: ")')
918 write (stdout, '(*(I4))') pack(nvo, [ (findloc(tgcontirr, i, 1) /= 0, i = 1, opts % num_syms) ])
919 write (stdout, '(/,1x,"Virtual orbitals per CI target state continuum in energy order (NVO): ")')
920 write (stdout, '(20I4)') nvo(tgcontirr)
921 end if
922
923 if (any(nvo(tgcontirr) < 0)) then
924 call mpi_xermsg('Postprocessing_module', 'get_boundary_data', &
925 'Some NVOs are negative. This looks like incorrect setup of continuum orbitals.', 1, 1)
926 end if
927
928 ! re-align the orbital amplitude data to their absolute positions in the list of all virt+cont orbitals:
929 !
930 ! 1st target used in channels...........2nd target used in channels...........etc....
931 ! orb_amps: [virtual orbitals][continuum orbitals][virtual orbitals][continuum orbitals]etc...
932 do i = 1, nchan
933 j = idchl(i) ! channel index in CONGEN ordering of target states
934 k = this % irrchl(j) ! partial wave irreducible representation (1, 2, ...)
935 if (i == 1) then
936 ! initial offset for the first channel: place evaluated continuum orbital boundary amplitudes
937 ! at positions after the last virtual orbital
938 offs = nvo(k)
939 else if (this % ichl(j) /= this % ichl(idchl(i - 1))) then
940 ! if the target changed between the preceding and the current channel, increment the offset by the number
941 ! of continuum orbitals of the previous state and the number of virtual orbitals of the current state
942 offs = offs + ncnt + nvo(k)
943 end if
944 ncnt = ncontmo(k)
945 ampls(1:ncnt) = orb_amps % CV(j, 1:ncnt)
946 orb_amps % CV(j, 1:norbs) = 0
947 orb_amps % CV(j, offs + 1:offs + ncnt) = ampls(1:ncnt)
948 end do
949 end if
950
951 ! multiply the raw boundary amplitudes with the eigenvectors
952 if (.not. solution % ci_vec % CV_is_scalapack) then
953 call this % wamp(ir) % A_B_matmul(orb_amps, solution % ci_vec, 'N', 'N')
954 else
955 ! distribute raw boundary amplitudes over solution context
956 dist_orb_amps % nprow = solution % ci_vec % nprow
957 dist_orb_amps % npcol = solution % ci_vec % npcol
958 dist_orb_amps % blacs_context = solution % ci_vec % blacs_context
959 dist_orb_amps % CV_is_scalapack = solution % ci_vec % CV_is_scalapack
960 call dist_orb_amps % init_CV(nchan, norbs)
961 call dist_orb_amps % redistribute(orb_amps)
962
963 ! multiply distributed matrices
964 call this % wamp(ir) % A_B_matmul(dist_orb_amps, solution % ci_vec, 'N', 'N')
965 end if
966
967 ! change normalization convention to the RMT one (but not for the sufrace boundaries)
968 if (ir < this % nfdm + 1) then
969 this % wamp(ir) % CV = sqrt(2.0_wp) * this % wamp(ir) % CV
970 end if
971
972 end do
973
974 end subroutine get_boundary_data
975
976
977 !> \brief Write the channel list to disk
978 !> \authors J Benda
979 !> \date 2019 - 2023
980 !>
981 !> Adapted from WRITCH (UKRmol-out). Writes the information into the selected set
982 !> of the channel output file *fort.10*.
983 !>
984 !> \param[in] this Interface object to update.
985 !> \param[in] nchset Output data set in the file unit.
986 !> \param[in] opts SCATCI namelist information for this irreducible representation.
987 !> \param[in] luchan Unit number for channel info output.
988 !> \param[in] cform 'F'/'U' for formatted or unformatted output.
989 !> \param[in] prop Results from denprop.
990 !>
991 subroutine write_channel_info (this, nchset, opts, luchan, cform, prop)
992
993 use algorithms, only: findloc
994 use class_molecular_properties_data, only: molecular_properties_data
995 use consts, only: amu
996 use consts_mpi_ci, only: symtype_d2h
997 use mpi_gbl, only: myrank, master
998 use options_module, only: options
999
1000 class(outerinterface), intent(in) :: this
1001 integer, intent(in) :: nchset, luchan
1002 type(options), intent(in) :: opts
1003 character(len=1), intent(in) :: cform
1004
1005 type(molecular_properties_data), intent(in) :: prop
1006
1007 integer, parameter :: keych = 10
1008
1009 integer :: i, k, nvd, nrec, ninfo, ndata, nvib, ndis, stot, gutot, ntarg, ivtarg(1), iv(1), ion, mtarg, starg, gutarg, nnuc
1010 integer :: isymm
1011 real(wp) :: r, rmass, etarg
1012
1013 if (myrank /= master) return ! the file is written by rank 0 only
1014
1015 nnuc = prop % no_nuclei
1016 nvib = 0
1017 ndis = 0
1018 gutot = symtype_d2h ! to indicate polyatomic code
1019
1020 if (opts % use_UKRMOL_integrals) then
1021 ion = nint(sum(prop % charge(1:nnuc) - this % core_charges(1:nnuc))) - (opts % num_electrons - 1)
1022 else
1023 ion = nint(sum(prop % charge(1:nnuc))) - (opts % num_electrons - 1)
1024 endif
1025
1026 r = 0
1027 ntarg = this % ntarg
1028 stot = nint(2 * opts % spin + 1)
1029 rmass = sum(1 / prop % mass(1:nnuc), prop % mass(1:nnuc) > 0)
1030
1031 if (rmass > 0) then
1032 rmass = amu / rmass
1033 end if
1034
1035 nvd = nvib + ndis
1036 nrec = 3 + ntarg + this % nchan + nvd
1037 ninfo = 1
1038 ndata = nrec - ninfo
1039
1040 if (cform == 'F') then
1041 write (luchan, '(16I5)') keych, nchset, nrec, ninfo, ndata
1042 write (luchan, '(A80)') opts % diag_name
1043 write (luchan, '(16I5)') ntarg, nvib, ndis, this % nchan
1044 write (luchan, '(4I5,2D20.12)') opts % lambda, stot, gutot, ion, r, rmass
1045
1046 k = 0
1047 do i = 1, prop % no_states
1048 if (findloc(this % idtarg, i, 1) /= 0) then
1049 k = k + 1
1050 isymm = prop % energies_index(2, i)
1051 mtarg = prop % symmetries_index(1, isymm)
1052 starg = prop % symmetries_index(2, isymm)
1053 gutarg = 0
1054 etarg = prop % energies(i)
1055 write (luchan, '(4I5,2D20.12)') k, mtarg, starg, gutarg, etarg
1056 end if
1057 end do
1058 do i = 1, this % nchan
1059 write (luchan, '(4I5,2D20.12)') i, this % ichl(i), this % lchl(i), this % mchl(i), this % echl(i)
1060 end do
1061 do i = 1, nvd
1062 write (luchan, '(16I5)') i, ivtarg(i), iv(i)
1063 end do
1064 else
1065 write (luchan) keych, nchset, nrec, ninfo, ndata
1066 write (luchan) opts % diag_name(1:80)
1067 write (luchan) ntarg, nvib, ndis, this % nchan
1068 write (luchan) opts % lambda, stot, gutot, ion, r, rmass
1069
1070 k = 0
1071 do i = 1, prop % no_states
1072 if (findloc(this % idtarg, i, 1) > 0) then
1073 k = k + 1
1074 isymm = prop % energies_index(2, i)
1075 mtarg = prop % symmetries_index(1, isymm)
1076 starg = prop % symmetries_index(2, isymm)
1077 gutarg = 0
1078 etarg = prop % energies(i)
1079 write (luchan) k, mtarg, starg, gutarg, etarg
1080 end if
1081 end do
1082 do i = 1, this % nchan
1083 write (luchan) i, this % ichl(i), this % lchl(i), this % mchl(i), this % echl(i)
1084 end do
1085 do i = 1, nvd
1086 write (luchan) i, ivtarg(i), iv(i)
1087 end do
1088 end if
1089
1090 end subroutine write_channel_info
1091
1092
1093 !> \brief Write the R-matrix amplitudes to disk
1094 !> \authors J Benda
1095 !> \date 2019 - 2025
1096 !>
1097 !> Writes the information into the selected set of the R-matrix output file (*fort.21*).
1098 !> Adapted from WRITRM (UKRmol-out). The boundary amplitudes are written using derived-type I/O, so that if they are
1099 !> distributed, the data are gathered during the writing one column by another. This is to avoid the need to fetch
1100 !> the complete boundary amplitude matrix, which would negatively affect memory scaling.
1101 !>
1102 !> There is a bug (CMPLRLIBS-35389) in the Intel oneAPI compiler*, which prevents use of the derived-type I/O with large
1103 !> datasets. To overcome this bug, a temporary workaround is introduced. In case of Intel compiler the amplitudes are actually
1104 !> all collected by the master and written at once using a standard defined I/O. This construction can be removed
1105 !> once the compiler is fixed.
1106 !>
1107 !> *) https://community.intel.com/t5/Intel-Fortran-Compiler/Unformatted-DTIO-failure-with-gt-2GiB-records/m-p/1705503
1108 !>
1109 !> \param[in] this Interface object to update.
1110 !> \param[in] nrmset Output data set in the file unit.
1111 !> \param[in] opts SCATCI namelist information for this irreducible representation.
1112 !> \param[in] lurmt Unit number for boundary amplitudes output.
1113 !> \param[in] rform 'F'/'U' for formatted or unformatted output.
1114 !> \param[in] prop Results from denprop.
1115 !> \param[in] solution Eigenvectors and related stuff calculated by the diagonalizer.
1116 !>
1117 subroutine write_boundary_data (this, nrmset, opts, lurmt, rform, prop, solution)
1118
1119 use class_molecular_properties_data, only: molecular_properties_data
1120 use consts, only: amu
1121 use consts_mpi_ci, only: symtype_d2h
1122 use mpi_gbl, only: myrank, master
1123 use precisn_gbl, only: wp_bytes
1124 use options_module, only: options
1125 use solutionhandler_module, only: solutionhandler
1126
1127 class(outerinterface), intent(in) :: this
1128 integer, intent(in) :: nrmset, lurmt
1129 type(options), intent(in) :: opts
1130 character(len=1), intent(in) :: rform
1131 type(molecular_properties_data), intent(in) :: prop
1132 type(solutionhandler), intent(in) :: solution
1133
1134 integer, parameter :: keyrm = 11
1135 character(len=10) :: io_msg
1136 type(civect) :: wamp
1137
1138 integer :: i, j, k, nchsq, nrec, ninfo, ndata, ntarg, nvib, ndis, nchan, stot, gutot, ion, ibut, iex, nocsf, npole, nnuc
1139 integer :: ismax, nstat, io_stat, v_list(0)
1140 logical :: intel_bug_workaround
1141 real(wp) :: r, rmass, rmatr
1142
1143 nnuc = prop % no_nuclei
1144 iex = 0
1145 ibut = 0
1146 nocsf = 0
1147 npole = 0
1148 ismax = 2
1149 rmatr = this % rmatr
1150 ntarg = this % ntarg
1151 nstat = solution % ci_vec % nstat
1152 nvib = 0
1153 ndis = 0
1154 nchan = this % nchan
1155 stot = nint(2 * opts % spin + 1)
1156 gutot = symtype_d2h ! to indicate polyatomic code
1157
1158 if (opts % use_UKRMOL_integrals) then
1159 ion = nint(sum(prop % charge(1:nnuc) - this % core_charges(1:nnuc))) - (opts % num_electrons - 1)
1160 else
1161 ion = nint(sum(prop % charge(1:nnuc))) - (opts % num_electrons - 1)
1162 endif
1163
1164 r = 0
1165 nchsq = nchan * (nchan + 1) / 2
1166 nrec = 4
1167 rmass = sum(1 / prop % mass(1:nnuc), prop % mass(1:nnuc) > 0)
1168
1169 if (rmass > 0) then
1170 rmass = amu / rmass
1171 end if
1172
1173 if (rform == 'F') then
1174 if (ismax > 0) nrec = nrec + (nchsq * ismax + 3) / 4
1175 nrec = nrec + (nstat + 3) / 4
1176 nrec = nrec + (nchan * nstat + 3) / 4
1177 if (npole > 0) nrec = nrec + (nocsf * npole + 3) / 4
1178 if (ibut > 0) nrec = nrec + (3 * nchan + 3) / 4
1179 if (abs(ibut) > 1) nrec = nrec + 1 + (nchan + 3) / 4 + (iex + 3) / 4 + (nchan * iex + 3) / 4
1180 else
1181 nrec = nrec + 2
1182 if (ismax > 0) nrec = nrec + 1
1183 if (npole > 0) nrec = nrec + 1
1184 if (ibut > 0) nrec = nrec + 1
1185 if (abs(ibut) > 1) nrec = nrec + 4
1186 end if
1187
1188 ninfo = 1
1189 ndata = nrec - ninfo
1190 intel_bug_workaround = .false.
1191
1192#ifdef __INTEL_COMPILER
1193 ! auxiliary master-only copy of the boundary amplitudes to work around Intel oneAPI bug CMPLRLIBS-35389;
1194 ! used for matrix size > 1 GiB = 2^30 B = 2^27 FP64 values
1195 if (nchan*nstat > 2**30/wp_bytes) then
1196 intel_bug_workaround = .true.
1197 wamp % nprow = 1
1198 wamp % npcol = 1
1199 wamp % blacs_context = -1
1200 wamp % CV_is_scalapack = this % wamp(this % nfdm + 1) % CV_is_scalapack
1201#if defined(usempi) && defined(scalapack)
1202 if (wamp % CV_is_scalapack) then
1203 call blacs_get(-1_blasint, 0_blasint, wamp % blacs_context)
1204 call blacs_gridinit(wamp % blacs_context, 'R', 1_blasint, 1_blasint)
1205 end if
1206#endif
1207 call wamp % init_CV(nchan, nstat)
1208 end if
1209#endif
1210
1211 if (rform == 'F') then
1212 if (myrank == master) then
1213 write (lurmt, '(10I7)') keyrm, nrmset, nrec, ninfo, ndata
1214 write (lurmt, '(A80)') opts % diag_name
1215 write (lurmt, '(16I5)') ntarg, nvib, ndis, nchan
1216 write (lurmt, '(4I5,2D20.13)') opts % lambda, stot, gutot, ion, r, rmass
1217 write (lurmt, '(4I5,2D20.13)') ismax, nstat, npole, ibut, rmatr
1218 !if (abs(ibut) > 1) write(lurmt, '(i10,d20.13,i5)') nocsf, ezero, iex
1219 if (ismax > 0) write(lurmt, '(4D20.13)') ((this % a(i,k),i=1,nchsq),k=1,ismax)
1220 write (lurmt, '(4D20.13)') (solution % ci_vec % ei(i) + solution % core_energy,i=1,nstat)
1221 if (intel_bug_workaround) then
1222 call wamp % redistribute(this%wamp(this%nfdm + 1), this%wamp(this%nfdm + 1)%blacs_context)
1223 write (lurmt, '(4D20.13)') wamp % CV(1:nchan, 1:nstat)
1224 else
1225 write (lurmt, '(dt(4,20,13))') this % wamp(this % nfdm + 1) ! derived-type I/O (formatted)
1226 end if
1227 !if (npole > 0) write (lurmt, '(4D20.13)') ((solution % ci_vec % cv(i,j),i=1,nocsf),j=1,npole)
1228 !if (ibut > 0) write (lurmt, '(4D20.13)') ((bcoef(i,j),i=1,3),j=1,nchan)
1229 !if (abs(ibut) > 1) write (lurmt, '(4D20.13)') (sfac(j),j=1,nchan)
1230 !if (abs(ibut) > 1) write (lurmt, '(4D20.13)') (ecex(j),j=1,iex)
1231 !if (abs(ibut) > 1) write (lurmt, '(4D20.13)') ((rcex(i,j),i=1,nchan),j=1,iex)
1232 else
1233 if (intel_bug_workaround) then
1234 ! a dummy call from non-master tasks to collaborate with redistribution to master above
1235 call wamp % redistribute(this%wamp(this%nfdm + 1), this%wamp(this%nfdm + 1)%blacs_context)
1236 else
1237 ! a dummy call from non-master tasks to collaborate with master's derived-type I/O above
1238 call this % wamp(this % nfdm + 1) % formatted_write(-1, '', v_list, io_stat, io_msg)
1239 end if
1240 end if
1241 else
1242 if (myrank == master) then
1243 write(lurmt) keyrm, nrmset, nrec, ninfo, ndata
1244 write(lurmt) opts % diag_name(1:80)
1245 write(lurmt) ntarg, nvib, ndis, nchan
1246 write(lurmt) opts % lambda, stot, gutot, ion, r, rmass
1247 write(lurmt) ismax, nstat, npole, ibut, rmatr
1248 !if (abs(ibut) > 1) write(lurmt) nocsf, ezero, iex
1249 if (ismax > 0) write(lurmt) ((this % a(i,k),i=1,nchsq),k=1,ismax)
1250 write (lurmt) (solution % ci_vec % ei(i) + solution % core_energy,i=1,nstat)
1251 if (intel_bug_workaround) then
1252 call wamp % redistribute(this%wamp(this%nfdm + 1), this%wamp(this%nfdm + 1)%blacs_context)
1253 write (lurmt) wamp % CV(1:nchan, 1:nstat)
1254 else
1255 write (lurmt) this % wamp(this % nfdm + 1) ! derived-type I/O (unformatted)
1256 end if
1257 !if (npole > 0) write (lurmt) ((solution % ci_vec % cv(i,j),i=1,nocsf),j=1,npole)
1258 !if (ibut > 0) write (lurmt) ((bcoef(i,j),i=1,3),j=1,nchan)
1259 !if (abs(ibut) > 1) write (lurmt) (sfac(j),j=1,nchan)
1260 !if (abs(ibut) > 1) write (lurmt) (ecex(j),j=1,iex)
1261 !if (abs(ibut) > 1) write (lurmt) ((rcex(i,j),i=1,nchan),j=1,iex)
1262 else
1263 if (intel_bug_workaround) then
1264 ! a dummy call from non-master tasks to collaborate with redistribution to master above
1265 call wamp % redistribute(this%wamp(this%nfdm + 1), this%wamp(this%nfdm + 1)%blacs_context)
1266 else
1267 ! a dummy call from non-master tasks to collaborate with master's derived-type I/O above
1268 call this % wamp(this % nfdm + 1) % unformatted_write(-1, io_stat, io_msg)
1269 end if
1270 end if
1271 end if
1272
1273 end subroutine write_boundary_data
1274
1275
1276 !> \brief Evaluate long-range channel coupling coefficients
1277 !> \authors Z Masin
1278 !> \date 2014
1279 !>
1280 !> This is a completely rewritten version of the original SWINTERF routine SWASYMC. The coupling potentials for the outer region
1281 !> calculation are defined as:
1282 !>
1283 !> \f[
1284 !> V_{ij}(r)=\sum_{\lambda=0}^{\infty}\frac{1}{r^{\lambda+1}}\times \nonumber \\
1285 !> \times\underbrace{\sum_{m=-\lambda}^{\lambda}\langle{\cal Y}_{l_{i},m_{i}}\vert {\cal {Y}}_{\lambda ,m}\vert{\cal Y}_{l_{j},m_{j}}\rangle
1286 !> \sqrt{\frac{4\pi}{2\lambda+1}}\underbrace{\sqrt{\frac{4\pi}{2\lambda+1}}\left( T_{ij}^{\lambda m} -
1287 !> \langle \Phi_{i}\vert\Phi_{j}\rangle
1288 !> \sum_{k=1}^{Nuclei}Z_{k}{\cal Y}_{\lambda,m}(\hat{\mathbf{R}}_{k})R_{k}^{\lambda}\right)}_{Q_{ij}^{\lambda m}}}_{a_{ij\lambda}} .
1289 !> \f]
1290 !>
1291 !> This routine calculates the coefficients \f$ a_{ij\lambda} \f$ using the information for all channels (i,j) and the target
1292 !> permanent and transition multipole moments \f$ Q_{ij}^{\lambda m} \f$. The main difference from SWASYMC is that here
1293 !> the coupling coefficients for the real spherical harmonics are calculated independently of the molecular orientation,
1294 !> symmetry and the lambda value. Tests were performed for pyrazine (\f$ D_{2h} \f$), uracil (\f$ C_s \f$) and water (\f$ C_{2v} \f$).
1295 !> The inclusion of the additional polarization potential is also possible, but note that only the spherical part is taken into
1296 !> account at the moment (see the comment for the variable `alpha2`).
1297 !>
1298 !> \note 06/02/2019 - Jakub Benda: Copied over from UKRmol-out.
1299 !>
1300 !> \param[inout] this Interface object to update.
1301 !> \param[in] ismax Maximum lambda value for the multipole potential contribution.
1302 !> \param[in] ntarg Total number of target states.
1303 !> \param[in] target_properties Denprop data.
1304 !> \param[in] alpha0 Spherical part of the ground state target polarizability.
1305 !> \param[in] alpha2 Non-spherical part of the ground state target polarizability. Note that this has not been implemented
1306 !> in this subroutine yet since it is not clear to me what is the convention defining this value in the old code.
1307 !> \param[in] use_pol If .true. then the coefficients for the polarization potential will be constructed.
1308 !>
1309 subroutine get_channel_couplings (this, ismax, ntarg, target_properties, alpha0, alpha2, use_pol)
1310
1311 use coupling_obj_gbl, only: couplings_type
1312 use class_molecular_properties_data, only: molecular_properties_data
1313 use mpi_gbl, only: mpi_xermsg
1314 use precisn, only: wp
1315
1316 class(outerinterface), intent(inout) :: this
1317 type(molecular_properties_data), intent(in) :: target_properties
1318
1319 integer, intent(in) :: ismax, ntarg
1320 real(wp), intent(in) :: alpha0, alpha2
1321 logical, intent(in) :: use_pol
1322
1323 real(wp), parameter :: fourpi = 12.5663706143591729538505735331180115_wp
1324 real(wp), parameter :: roneh = 0.70710678118654752440084436210484903_wp !sqrt(0.5)
1325 real(wp), parameter :: rothree = 0.57735026918962576450914878050195745_wp !1/sqrt(3)
1326
1327 ! The Gaunt coefficients for the real spherical harmonics defined below are needed to express the xx, yy, zz angular
1328 ! behaviour in terms of the real spherical harmonics.
1329
1330 ! x^2 = x*x ~ X_{11}*X_{11} = x2_X00*X_{00} + x2_X20*X_{20} + x2_X22*X_{22}
1331 real(wp), parameter :: x2_X00 = 0.282094791773878e+00_wp
1332 real(wp), parameter :: x2_X20 = -0.126156626101008e+00_wp
1333 real(wp), parameter :: x2_X22 = 0.218509686118416e+00_wp
1334 ! y^2 = y*y ~ X_{1-1}*X_{1-1} = y2_X00*X_{00} + y2_X20*X_{20} + y2_X22*X_{22}
1335 real(wp), parameter :: y2_X00 = 0.282094791773878e+00_wp
1336 real(wp), parameter :: y2_X20 = -0.126156626101008e+00_wp
1337 real(wp), parameter :: y2_X22 = -0.218509686118416e+00_wp
1338 ! z^2 = z*z ~ X_{10}*X_{10} = z2_X00*X_{00} + z2_X20*X_{20}
1339 real(wp), parameter :: z2_X00 = 0.282094791773878e+00_wp
1340 real(wp), parameter :: z2_X20 = 0.252313252202016e+00_wp
1341
1342 type(couplings_type) :: couplings
1343
1344 real(wp), allocatable :: prop(:,:,:)
1345
1346 real(wp) :: cpl, sph_cpl, fac
1347 integer :: l1, m1, q1, l2, m2, q2, no_cpl, lqt, isq, it1, it2
1348 integer :: ch_1, ch_2, lambda, mlambda, lmin, lmax
1349 logical :: use_alpha2
1350
1351 if (use_pol .and. ntarg > 1) then
1352 write (*, '("WARNING: adding polarization potential while more than one target state is present in the outer region.")')
1353 end if
1354
1355 ! get a convenient dense matrix set with the properties
1356 call target_properties % decompress(1, ismax, prop)
1357
1358 ! find size parameters based on polarizability setup
1359 use_alpha2 = (use_pol .and. alpha2 /= 0.0_wp)
1360 lmax = merge(max(ismax, 3), ismax, use_pol) ! maximal angular momentum transfer (polarizability corresponds to lambda=3)
1361 no_cpl = this % nchan * (this % nchan + 1) / 2 ! total number of unique combinations of the scattering channels
1362
1363 ! allocate memory for the coupling coefficients
1364 if (allocated(this % a)) deallocate (this % a)
1365 allocate (this % a(no_cpl, lmax)) ! indices (:, 0) are never needed, though
1366 this % a(:,:) = 0.0_wp
1367
1368 do ch_1 = 1, this % nchan
1369
1370 l1 = this % lchl(ch_1) ! the l, |m| values correspond to the l,|m| values of the real spherical harmonics
1371 m1 = this % mchl(ch_1)
1372 q1 = this % qchl(ch_1) ! q is sign(m) or 0 if m=0.
1373 m1 = m1 * q1 ! determine m
1374 it1 = this % ichl(ch_1) ! sequence number of the target state corresponding to this channel
1375
1376 do ch_2 = 1, ch_1
1377
1378 l2 = this % lchl(ch_2) ! the l, |m| values correspond to the l,|m| values of the real spherical harmonics
1379 m2 = this % mchl(ch_2)
1380 q2 = this % qchl(ch_2) ! q is sign(m) or 0 if m=0.
1381 m2 = m2 * q2 ! determine m
1382 it2 = this % ichl(ch_2) ! sequence number of the target state corresponding to this channel
1383
1384 ! use the selection rules for the real spherical harmonics to determine the range of lambda values
1385 ! which may give non-zero real Gaunt coefficients
1386 call couplings % bounds_rg(l1, l2, m1, m2, lmin, lmax)
1387
1388 !don't include potentials with lambda > ismax
1389 if (lmax > ismax) lmax = ismax
1390
1391 ! lambda = 0 would correspond to the monopole contribution, i.e. taking into account the total (perhaps nonzero)
1392 ! molecular charge. This is taken into account separately in RSOLVE.
1393 if (lmin == 0) lmin = 2
1394
1395 ! linear index corresponding to the current combination of the channels (ch_1,ch_2).
1396 lqt = ch_1 * (ch_1 - 1) / 2 + ch_2
1397
1398 ! Loop over the multipole moments which may be non-zero: we loop in steps of two since the selection rules
1399 ! for the r.s.h. imply that only if the sum of all L values is even the coupling may be non-zero.
1400 do lambda = lmin, lmax, 2
1401
1402 ! see the formula for V_{ij}: effectively this converts the inner region property from solid harmonic
1403 ! normalization to spherical harmonic normalization as needed by the Leg. expansion.
1404 fac = sqrt(fourpi / (2 * lambda + 1.0_wp))
1405
1406 do mlambda = -lambda, lambda
1407
1408 ! rgaunt: the coupling coefficient for the real spherical harmonics (l1,m1,l2,m2,lambda,mlambda);
1409 ! The factor 2.0_wp converts the units of the inner region
1410 ! properties from Hartree to Rydberg since that's the energy
1411 ! unit used in the outer region.
1412 cpl = 2.0_wp * fac * couplings % rgaunt(l1, lambda, l2, m1, mlambda, m2)
1413
1414 ! linear index corresponding to the current (lambda,mlambda) values. lambda*lambda is the number
1415 ! of all (lambda1,mlambda1) combinations for lambda1 = lambda-1.
1416 isq = lambda * lambda + lambda + mlambda
1417
1418 ! increment the value of the coefficient for the multipole coupling potential of order lambda between
1419 ! the target states it1 and it2
1420 this % a(lqt, lambda) = this % a(lqt, lambda) + cpl * prop(it1, it2, isq)
1421
1422 end do !mlambda
1423
1424 end do !lambda
1425
1426 ! add polarizabilities for the ground state channels, but only if use_pol == .true.
1427 if (use_pol .and. it1 == 1 .and. it2 == 1) then
1428
1429 ! the radial dependence of the polarization potential is r^{-4} which corresponds to lambda = 3
1430 lambda = 3
1431
1432 ! obviously, the spherical polarizability (l=0) couples only the channels with identical angular behaviour
1433 if (l1 == l2 .and. m1 == m2) then
1434 sph_cpl = 1.0_wp
1435 write(*, '("Spherical part of the polarization potential will be added to the &
1436 &channel (target state,l,m), coefficient value: (",3i5,") ",e25.15)') &
1437 it1, l1, m1, -sph_cpl * alpha0
1438 this % a(lqt, lambda) = this % a(lqt, lambda) - sph_cpl * alpha0 ! add the spherical part of the polarizability
1439 else
1440 sph_cpl = 0.0_wp
1441 end if
1442
1443 ! Add the non-spherical part of the polarizability; the polarization potential here corresponds to:
1444 ! alpha_{2}*C_{i}*r_{-4}, where the angular behaviour of C_{i} = xy, xz, yz, xx, yy, zz
1445 ! Note that we assume below that the polarizability tensor has the same values (alpha2) for all components.
1446 ! Also note that as long as l1,m1 and l2,m2 belong to the same IR, only the totally symmetric components
1447 ! of the pol. tensor (xx,yy,zz) may contribute.
1448 if (use_alpha2) then
1449
1450 write(*, '("Non-spherical part of the polarization potential &
1451 &(target state,l1,m2,l2,m2): ",5i5)') it1, l1, m1, l2, m2
1452
1453 ! x^2 component
1454 cpl = x2_x00 * couplings % rgaunt(l1, 0, l2, m1, 0, m2) + &
1455 x2_x20 * couplings % rgaunt(l1, 2, l2, m1, 0, m2) + &
1456 x2_x22 * couplings % rgaunt(l1, 2, l2, m1, 2, m2)
1457 write(*, '("x^2 coefficient: ",e25.15)') -cpl * alpha2
1458 this % a(lqt, lambda) = this % a(lqt, lambda) - cpl * alpha2
1459
1460 ! y^2 component
1461 cpl = y2_x00 * couplings % rgaunt(l1, 0, l2, m1, 0, m2) + &
1462 y2_x20 * couplings % rgaunt(l1, 2, l2, m1, 0, m2) + &
1463 y2_x22 * couplings % rgaunt(l1, 2, l2, m1, 2, m2)
1464 write(*, '("y^2 coefficient: ",e25.15)') -cpl * alpha2
1465 this % a(lqt, lambda) = this % a(lqt, lambda) - cpl * alpha2
1466
1467 ! z^2 component
1468 cpl = z2_x00 * couplings % rgaunt(l1, 0, l2, m1, 0, m2) + &
1469 z2_x20 * couplings % rgaunt(l1, 2, l2, m1, 0, m2)
1470 write(*, '("z^2 coefficient: ",e25.15)') -cpl * alpha2
1471 this % a(lqt, lambda) = this % a(lqt, lambda) - cpl * alpha2
1472
1473 ! xz component
1474 cpl = couplings % rgaunt(l1, 2, l2, m1, 1, m2) * rothree
1475 write(*, '("xz coefficient: ",e25.15)') -cpl * alpha2
1476 this % a(lqt, lambda) = this % a(lqt, lambda) - cpl * alpha2
1477
1478 ! yz component
1479 cpl = couplings % rgaunt(l1, 2, l2, m1, -1, m2) * rothree
1480 write(*, '("yz coefficient: ",e25.15)') -cpl * alpha2
1481 this % a(lqt, lambda) = this % a(lqt, lambda) - cpl * alpha2
1482
1483 ! xy component
1484 cpl = couplings % rgaunt(l1, 2, l2, m1, -2, m2) * rothree
1485 write(*, '("xy coefficient: ",e25.15)') -cpl * alpha2
1486 this % a(lqt, lambda) = this % a(lqt, lambda) - cpl * alpha2
1487
1488 end if ! use_alpha2
1489
1490 end if ! use_pol
1491
1492 end do ! ch_2
1493
1494 end do ! ch_1
1495
1496 end subroutine get_channel_couplings
1497
1498
1499 !> \brief Compose the RMT molecular input data file
1500 !> \authors J Benda
1501 !> \date 2019 - 2024
1502 !>
1503 !> Based on `generate_data_for_rmt` from the original program `rmt_interface` (UKRmol-out).
1504 !>
1505 !> This subroutine writes the input data file used by molecular version of RMT. The file is always called 'molecular_data',
1506 !> it is a binary stream file and it contains the following data:
1507 !> - (For m = -1, 0, 1) s, s1, s2, iidip(1:s), ifdip(1:s), dipsto(1:s1,1:s2, 1:s): Number of distinct symmetry pairs, maximal
1508 !> number of states in bra symmetry maximal number of states in ket symmetry, list of bra symmetries, list of ket symmetries,
1509 !> (N + 1)-electron state transition dipole blocks (padded by zeros to s1 x s2).
1510 !> - ntarg: Number of targets (N-electron ions).
1511 !> - (For m = -1, 0, 1) crlv: Dipole transition matrix for N-electron states
1512 !> - n_rg, rg, lm_rg: Number of real Gaunt coeffcients, array of them, their angular momentum quantum numbers.
1513 !> - nelc, nz, lrang2, lamax, ntarg, inast, nchmx, nstmx, lmaxp1: Number of electrons, nuclear charge, maximal angular momentum,
1514 !> maximal angular momentum transfer, number of target states, number of irreducible representations, maximal number of channels
1515 !> in all irreducible representations, maximal number of eigenstates in all irreducible representations, maximal angular
1516 !> momentum.
1517 !> - rmatr, bbloch: R-matrix radius, Bloch b-coeffcient.
1518 !> - etarg, ltarg, starg: Ionic state eigen-energies, their irreducible representations (one-based), and spins.
1519 !> - nfdm, delta_r: Number of finite difference points inside the R-matrix sphere, their spacing.
1520 !> - r_points: List of the finite difference points inside the R-matrix sphere.
1521 !> - (For all target symmetries) lrgl, nspn, npty, nchan, mnp1, nconat, l2p, m2p, eig, wamp, cf, ichl, s1, s2, wamp2:
1522 !> Irreducible representation (one-based), spin multiplicity, parity (not used, always zero), number of continuum channels,
1523 !> number of (N + 1)-electron states, number of continuum channels per target (ionic) state, angular momentum per channel,
1524 !> angular momentum projection per channel, eigenenergies of the (N + 1)-electron states (+ core energy), contributions of the
1525 !> (N + 1)-electron eigenstates to every channel (boundary amplitudes), long-range multipole coupling coefficients,
1526 !> target state index per continuum channel, row size of wamp2, col size of wamp2, wamp evaluated at inner-region
1527 !> finite difference points.
1528 !>
1529 !> \todo At the moment, the subroutine assumes that all inner states have the same spin.
1530 !>
1531 !> \warning The subroutine makes use of MPI I/O for writing the distributed arrays. In the present, straightforward implementation
1532 !> this limits the local portion of the distributed matrices to 2**31 elements (i.e. 16 GiB for 8-byte real arrays).
1533 !> If this became a problem, one would need to rewrite the subroutine to write everything in one go as a custom MPI
1534 !> datatype, whose building routines accept long integers. However, 16 GiB per core per distributed matrix seems quite
1535 !> acceptable for now.
1536 !>
1537 !> \param[in] input Input SCATCI namelist data for all symmetries.
1538 !> \param[in] iitf Which outer_interface namelist to use.
1539 !> \param[in] inner_properties Results from cdenprop.
1540 !> \param[in] solutions Results from diagonalization.
1541 !> \param[in] intf Extracted channel and boundary data.
1542 !>
1543 subroutine write_rmt_data (input, iitf, inner_properties, solutions, intf)
1544
1545 use algorithms, only: findloc, insertion_sort
1546 use class_molecular_properties_data, only: molecular_properties_data
1547 use const_gbl, only: stdout
1549 use mpi_gbl, only: myrank, master, mpiint, mpi_xermsg, mpi_mod_file_open_write, &
1550 mpi_mod_file_close, mpi_mod_file_set_size, mpi_mod_file_write, mpi_mod_barrier
1551 use options_module, only: optionsset
1552 use timing_module, only: master_timer
1553 use solutionhandler_module, only: solutionhandler
1554
1555 type(optionsset), intent(in) :: input
1556 integer, intent(in) :: iitf
1557 type(molecular_properties_data), intent(in) :: inner_properties
1558 type(solutionhandler), allocatable, intent(in) :: solutions(:)
1559 type(outerinterface), allocatable, intent(in) :: intf(:)
1560
1561 real(rmt_real), allocatable :: cf(:,:), crlv(:,:), etarg(:), rg(:), eig(:), r_points(:)
1562 integer(rmt_int), allocatable :: iidip(:), ifdip(:), lm_rg(:,:), ltarg(:), starg(:), nconat(:), l2p(:), m2p(:), ichl(:), &
1563 propblocks(:)
1564 integer, allocatable :: ions(:)
1565
1566 type(molecular_properties_data) :: target_properties
1567
1568 type(civect) :: wmat
1569 real(rmt_real) :: bbloch = 0, prop, rmatr, delta_r
1570 integer :: i, j, k, l, m, irr_map(8,8), u, v, nsymm, ind, ip, irr1, irr2, it1, it2, nnuc, &
1571 ierr, nblock, sym1, sym2, ref
1572 integer(mpiint) :: fh
1573 integer(rmt_int) :: s1, s2, ntarg, n_rg, nelc, nz, lamax, inast, nchmx, nstmx, lmaxp1, nchan, lrgl, nspn, npty, &
1574 mnp1, mxstat, lrang2, lmax, nfdm
1575
1576 call target_properties % read_properties(input % interface_opts(iitf) % lutdip, 1)
1577
1578 ! number of N + 1 irrs for which we have inner solution and dipoles
1579 inast = count(iand(input % opts(:) % vector_storage_method, pass_to_cdenprop) /= 0 .and. &
1580 input % opts(:) % diagonalization_flag /= no_diagonalization)
1581
1582 ref = maxloc(intf(:) % ntarg, 1) ! irr with the first non-zero number of outer region states
1583 nnuc = target_properties % no_nuclei ! number of nuclei in the molecule
1584 nfdm = input % interface_opts(iitf) % nfdm ! number of boundary amplitudes evaluation radii *inside* R-matrix sphere
1585 delta_r = input % interface_opts(iitf) % delta_r ! spacing between the evaluation distances
1586 rmatr = input % interface_opts(iitf) % rmatr ! radius of the inner region
1587 nsymm = size(input % opts(:)) ! number of N + 1 irrs for which we have inputs (= size(intf(:)))
1588 ntarg = intf(ref) % ntarg ! number of target ("ionic") states
1589 nelc = input % opts(1) % num_electrons - 1 ! number of electrons of the target (ion)
1590 mxstat = maxval(inner_properties % dense_blocks(1:inner_properties % no_blocks) % mat_dimen) ! largest dipole block size
1591
1592 ! avoid the routine altogether when there are no data for any reason
1593 if (nsymm == 0 .or. inast == 0 .or. ntarg == 0 .or. mxstat <= 0) then
1594 write (stdout, *) 'Nothing to do in RMT interface'
1595 return
1596 end if
1597
1598 nz = sum(target_properties % charge(1:nnuc) - intf(ref) % core_charges(1:nnuc)) ! number of protons in the molecule
1599
1600 lrang2 = 0 ! angular momentum limit
1601 lamax = 0 ! angular momentum limit
1602 nchmx = 0 ! maximal number of channels across irreducible representations
1603 nstmx = 0 ! maximal number of inner region eigenstates across representations
1604
1605 call master_timer % start_timer("RMT interface")
1606
1607 ! get energy-sorted list of ionic states for use in outer region (only a subset of all if IDTARG is incomplete)
1608 ions = intf(ref) % idtarg
1609 call insertion_sort(ions)
1610
1611 ! open the output file and reset its contents
1612 call mpi_mod_file_open_write('molecular_data', fh, ierr)
1613 call mpi_mod_file_set_size(fh, 0)
1614
1615 allocate (propblocks(inner_properties % no_blocks))
1616
1617 write (stdout, '(/,"Writing RMT data file")')
1618 write (stdout, '( "=====================")')
1619 write (stdout, '(/," Inner region dipole block max size: ",I0)') mxstat
1620 write (stdout, '(/," Ionic states used in outer region: ")')
1621 write (stdout, '(/," ",20I5)') ions
1622
1623 ! store all (N + 1)-electron dipole blocks
1624 do m = -1, 1
1625
1626 ! find out which IRR pairs yield non-zero matrix elements of Y[1,m]
1627 irr_map = 0
1628 nblock = 0
1629 do ip = 1, inner_properties % no_blocks
1630 if (inner_properties % dense_index(3, ip) == 1 .and. &
1631 inner_properties % dense_index(4, ip) == m) then
1632 sym1 = inner_properties % dense_index(1, ip)
1633 sym2 = inner_properties % dense_index(2, ip)
1634 irr1 = inner_properties % symmetries_index(1, sym1) + 1
1635 irr2 = inner_properties % symmetries_index(1, sym2) + 1
1636 nblock = nblock + 1
1637 propblocks(nblock) = ip
1638 irr_map(irr1, irr2) = nblock
1639 end if
1640 end do
1641
1642 ! get some free memory
1643 if (allocated(iidip)) deallocate (iidip)
1644 if (allocated(ifdip)) deallocate (ifdip)
1645 allocate (iidip(nblock))
1646 allocate (ifdip(nblock))
1647 iidip(:) = -1
1648 ifdip(:) = -1
1649
1650 ! compose sequence of bra and ket symmetry indices
1651 do ip = 1, nblock
1652 sym1 = inner_properties % dense_index(1, propblocks(ip))
1653 sym2 = inner_properties % dense_index(2, propblocks(ip))
1654 irr1 = inner_properties % symmetries_index(1, sym1) + 1
1655 irr2 = inner_properties % symmetries_index(1, sym2) + 1
1656 iidip(ip) = irr1
1657 ifdip(ip) = irr2
1658 end do
1659
1660 ! write dipole metadata
1661 call mpi_mod_file_write(fh, int(nblock, rmt_int))
1662 call mpi_mod_file_write(fh, mxstat)
1663 call mpi_mod_file_write(fh, mxstat)
1664 call mpi_mod_file_write(fh, iidip, nblock)
1665 call mpi_mod_file_write(fh, ifdip, nblock)
1666
1667 ! collective write of the ScaLAPACK matrices to the stream file
1668 do ip = 1, nblock
1669 call inner_properties % dense_blocks(propblocks(ip)) % stream_write(fh)
1670 end do
1671 end do
1672
1673 ! set up angular momentum limits
1674 do i = 1, nsymm
1675 if (iand(input % opts(i) % vector_storage_method, pass_to_cdenprop) /= 0 .and. &
1676 input % opts(i) % diagonalization_flag /= no_diagonalization) then
1677 if (intf(i) % nchan > 0) then
1678 lrang2 = max(int(lrang2), maxval(intf(i) % lchl(1:intf(i) % nchan)))
1679 lamax = max(int(lamax), intf(i) % ismax)
1680 nchmx = max(int(nchmx), intf(i) % nchan)
1681 end if
1682 nstmx = max(int(nstmx), solutions(i) % vec_dimen)
1683 end if
1684 end do
1685 lrang2 = lrang2 + 1
1686 lmaxp1 = lrang2
1687
1688 ! number of target (ion) states
1689 call mpi_mod_file_write(fh, ntarg)
1690
1691 ! allocate memory for auxiliary arrays (dummy for non-master processes)
1692 if (myrank == master) then
1693 allocate (etarg(ntarg), ltarg(ntarg), starg(ntarg), nconat(ntarg), l2p(nchmx), m2p(nchmx), eig(nstmx), &
1694 cf(nchmx, nchmx), ichl(nchmx), crlv(ntarg, ntarg), r_points(nfdm + 1), stat = ierr)
1695 else
1696 allocate (etarg(0), ltarg(0), starg(0), nconat(0), l2p(0), m2p(0), eig(0), rg(0), lm_rg(0, 0), &
1697 cf(0, 0), ichl(0), crlv(0, 0), r_points(0), stat = ierr)
1698 end if
1699 if (ierr /= 0) then
1700 call mpi_xermsg('Postprocessing_module', 'write_rmt_data', 'Memory allocation failure.', 1, 1)
1701 end if
1702
1703 ! set up a distributed matrix for reshaping the boundary amplitudes
1704 wmat % nprow = 1
1705 wmat % npcol = 1
1706 wmat % blacs_context = intf(1) % wamp(nfdm + 1) % blacs_context
1707 wmat % CV_is_scalapack = intf(1) % wamp(nfdm + 1) % CV_is_scalapack
1708 call wmat % init_CV(int(nchmx), int(nstmx))
1709
1710 ! target data
1711 if (myrank == master) then
1712 do i = 1, ntarg
1713 etarg(i) = target_properties % energies(ions(i))
1714 ltarg(i) = target_properties % symmetries_index(1, target_properties % energies_index(2, ions(i))) + 1
1715 starg(i) = target_properties % symmetries_index(2, target_properties % energies_index(2, ions(i)))
1716 end do
1717 end if
1718
1719 ! finite difference points inside (and at) the R-matrix sphere
1720 if (myrank == master) then
1721 do i = 1, nfdm + 1
1722 r_points(i) = rmatr - (nfdm + 1 - i) * delta_r
1723 end do
1724 end if
1725
1726 ! store all N-electron dipole blocks
1727 do m = -1, 1
1728 if (myrank == master) then
1729 crlv(:,:) = 0
1730 do ip = 1, target_properties % non_zero_properties
1731 if (target_properties % properties_index(3, ip) == 1 .and. &
1732 target_properties % properties_index(4, ip) == m) then
1733 ! get state information for this property
1734 it1 = target_properties % properties_index(1, ip)
1735 it2 = target_properties % properties_index(2, ip)
1736 prop = target_properties % properties(ip)
1737 ! check that these two states are requested in the outer region
1738 it1 = findloc(ions, it1, 1)
1739 it2 = findloc(ions, it2, 1)
1740 if (it1 > 0 .and. it2 > 0) then
1741 crlv(it1, it2) = prop
1742 crlv(it2, it1) = prop
1743 end if
1744 end if
1745 end do
1746 end if
1747
1748 call mpi_mod_file_write(fh, crlv, int(ntarg), int(ntarg))
1749 end do
1750
1751 ! generate the coupling coefficients needed in RMT to construct the laser-target and laser-electron asymptotic potentials
1752 lmax = 0
1753 do i = 1, nsymm
1754 if (iand(input % opts(i) % vector_storage_method, pass_to_cdenprop) /= 0 .and. &
1755 input % opts(i) % diagonalization_flag /= no_diagonalization) then
1756 if (intf(i) % nchan > 0) then
1757 lmax = max(int(lmax), maxval(intf(i) % lchl(1:intf(i) % nchan)))
1758 end if
1759 end if
1760 end do
1761 if (myrank == master) then
1762 call generate_couplings(lmax, n_rg, rg, lm_rg)
1763 end if
1764
1765 call mpi_mod_file_write(fh, n_rg)
1766 call mpi_mod_file_write(fh, rg, int(n_rg))
1767 call mpi_mod_file_write(fh, lm_rg, 6, int(n_rg))
1768 call mpi_mod_file_write(fh, nelc)
1769 call mpi_mod_file_write(fh, nz)
1770 call mpi_mod_file_write(fh, lrang2)
1771 call mpi_mod_file_write(fh, lamax)
1772 call mpi_mod_file_write(fh, ntarg)
1773 call mpi_mod_file_write(fh, inast)
1774 call mpi_mod_file_write(fh, nchmx)
1775 call mpi_mod_file_write(fh, nstmx)
1776 call mpi_mod_file_write(fh, lmaxp1)
1777 call mpi_mod_file_write(fh, rmatr)
1778 call mpi_mod_file_write(fh, bbloch)
1779 call mpi_mod_file_write(fh, etarg, int(ntarg))
1780 call mpi_mod_file_write(fh, ltarg, int(ntarg))
1781 call mpi_mod_file_write(fh, starg, int(ntarg))
1782 call mpi_mod_file_write(fh, nfdm)
1783 call mpi_mod_file_write(fh, delta_r)
1784 call mpi_mod_file_write(fh, r_points, int(nfdm + 1))
1785
1786 ! write channel information (per symmetry)
1787 do i = 1, nsymm
1788 if (iand(input % opts(i) % vector_storage_method, pass_to_cdenprop) /= 0 .and. &
1789 input % opts(i) % diagonalization_flag /= no_diagonalization) then
1790
1791 if (myrank == master) then
1792 lrgl = 1 + input % opts(i) % lambda
1793 nspn = int(2 * input % opts(i) % spin + 1, rmt_int)
1794 npty = 0
1795 nchan = intf(i) % nchan
1796 mnp1 = solutions(i) % vec_dimen
1797
1798 do j = 1, ntarg
1799 nconat(j) = count(intf(i) % ichl(1:nchan) == j)
1800 end do
1801
1802 l2p(:) = 0
1803 m2p(:) = 0
1804 ichl(:) = 0
1805
1806 do j = 1, nchan
1807 l2p(j) = intf(i) % lchl(j)
1808 m2p(j) = intf(i) % mchl(j)
1809 ichl(j) = intf(i) % ichl(j)
1810 end do
1811 end if
1812
1813 call mpi_mod_file_write(fh, lrgl)
1814 call mpi_mod_file_write(fh, nspn)
1815 call mpi_mod_file_write(fh, npty)
1816 call mpi_mod_file_write(fh, nchan)
1817 call mpi_mod_file_write(fh, mnp1)
1818 call mpi_mod_file_write(fh, nconat, int(ntarg))
1819 call mpi_mod_file_write(fh, l2p, int(nchmx))
1820 call mpi_mod_file_write(fh, m2p, int(nchmx))
1821
1822 if (myrank == master) then
1823 eig(:) = 0
1824 eig(1:mnp1) = solutions(i) % ci_vec % ei(1:mnp1) + solutions(i) % core_energy
1825 end if
1826
1827 call mpi_mod_file_write(fh, eig, int(nstmx))
1828
1829 ! resize the boundary amplitudes matrix to nchmx by nstmx
1830 wmat % CV = 0
1831 call wmat % redistribute(intf(i) % wamp(nfdm + 1), intf(i) % wamp(nfdm + 1) % blacs_context)
1832 wmat % CV = sqrt(2.0_wp) * wmat % CV ! switch to RMT convention
1833 call wmat % stream_write(fh)
1834
1835 do l = 1, lamax
1836 if (myrank == master) then
1837 cf(:,:) = 0
1838 if (l <= intf(i) % ismax) then
1839 do k = 1, nchan
1840 do j = 1, nchan
1841 u = max(j, k)
1842 v = min(j, k)
1843 ind = u * (u - 1) / 2 + v
1844 cf(j, k) = intf(i) % a(ind, l)
1845 end do
1846 end do
1847 end if
1848 end if
1849 call mpi_mod_file_write(fh, cf, int(nchmx), int(nchmx))
1850 end do
1851
1852 call mpi_mod_file_write(fh, ichl, int(nchmx))
1853
1854 s1 = intf(i) % wamp(1) % mat_dimen_r
1855 s2 = intf(i) % wamp(1) % mat_dimen_c
1856
1857 call mpi_mod_file_write(fh, s1)
1858 call mpi_mod_file_write(fh, s2)
1859
1860 ! collective write of the ScaLAPACK matrix to the stream file
1861 do j = 1, nfdm
1862 call intf(i) % wamp(j) % stream_write(fh)
1863 end do
1864
1865 end if
1866 end do
1867
1868 call mpi_mod_file_close(fh, ierr)
1869
1870 call master_timer % stop_timer("RMT interface")
1871
1872 end subroutine write_rmt_data
1873
1874
1875 !> \brief Evaluate angular couplings for outer region of RMT
1876 !> \authors Z Masin
1877 !> \date 2017
1878 !>
1879 !> Precompute the necessary Gaunt coefficients.
1880 !>
1881 !> \param[in] maxl Limit on angular quantum number.
1882 !> \param[out] n_rg Number of calculated Gaunt coefficients.
1883 !> \param[out] rg Values of the non-zero Gaunt coefficients.
1884 !> \param[out] lm_rg Integer sextets l1,m1,l2,m2,l3,m3 for each of the non-zero Gaunt coefficients.
1885 !>
1886 subroutine generate_couplings (maxl, n_rg, rg, lm_rg)
1887
1888 use coupling_obj_gbl, only: couplings_type
1889
1890 integer(rmt_int), intent(in) :: maxl
1891 integer(rmt_int), intent(out) :: n_rg
1892 real(rmt_real), allocatable, intent(inout) :: rg(:)
1893 integer(rmt_int), allocatable, intent(inout) :: lm_rg(:,:)
1894
1895 type(couplings_type) :: cpl
1896
1897 integer :: l1, l2, l3, m1, m2, m3, err
1898 real(wp) :: tmp
1899
1900 call cpl % prec_cgaunt(int(maxl))
1901
1902 l3 = 1
1903
1904 ! How many non-zero dipole couplings there are
1905 n_rg = 0
1906 do l1 = 0, maxl
1907 do m1 = -l1, l1
1908 do l2 = 0, maxl
1909 do m2 = -l2, l2
1910 do m3 = -l3, l3
1911 tmp = cpl % rgaunt(l1, l2, l3, m1, m2, m3)
1912 if (tmp /= 0) n_rg = n_rg + 1
1913 end do !m3
1914 end do !m2
1915 end do !l2
1916 end do !m1
1917 end do !l1
1918
1919 if (allocated(rg)) deallocate (rg)
1920 if (allocated(lm_rg)) deallocate (lm_rg)
1921 n_rg = max(n_rg, 1_rmt_int) ! make sure that even if all couplings are zero this routine allocates rg, lm_rg
1922 allocate (rg(n_rg), lm_rg(6, n_rg), stat = err)
1923 rg = 0
1924 lm_rg = -2
1925
1926 ! Save them
1927 n_rg = 0
1928 do l1 = 0, maxl
1929 do m1 = -l1, l1
1930 do l2 = 0, maxl
1931 do m2 = -l2, l2
1932 do m3 = -l3, l3
1933 tmp = cpl % rgaunt(l1, l2, l3, m1, m2, m3)
1934 if (tmp /= 0) then
1935 n_rg = n_rg + 1
1936 rg(n_rg) = tmp
1937 lm_rg(1:6, n_rg) = (/ l1, m1, l2, m2, l3, m3 /)
1938 end if
1939 end do !m3
1940 end do !m2
1941 end do !l2
1942 end do !m1
1943 end do !l1
1944
1945 end subroutine generate_couplings
1946
1947end module postprocessing_module
MPI SCATCI Constants module.
integer, parameter pass_to_cdenprop
Eigenpairs will be saved in memory and passed to CDENPROP and outer interface.
integer, parameter no_diagonalization
No diagonalization.
integer, parameter symtype_d2h
This describes D_2_h symmetries.
integer, parameter no_ci_target
No Ci target contraction (target only run)