Line data Source code
1 : !--------------------------------------------------------------------------------------------------!
2 : ! CP2K: A general program to perform molecular dynamics simulations !
3 : ! Copyright 2000-2026 CP2K developers group <https://cp2k.org> !
4 : ! !
5 : ! SPDX-License-Identifier: GPL-2.0-or-later !
6 : !--------------------------------------------------------------------------------------------------!
7 :
8 : #ifdef __GAUXC
9 : #include "gauxc/gauxc_config.f"
10 : #endif
11 :
12 : MODULE xc_gauxc_cache
13 :
14 : USE cp_log_handling, ONLY: cp_logger_get_default_io_unit
15 : USE iso_c_binding, ONLY: c_double
16 : USE kinds, ONLY: default_path_length,&
17 : default_string_length
18 : USE message_passing, ONLY: mp_comm_self,&
19 : mp_para_env_type
20 : USE particle_types, ONLY: particle_type
21 : USE qs_kind_types, ONLY: qs_kind_type
22 : USE xc_gauxc_gradient_policy, ONLY: gauxc_resolve_gradient_policy
23 : USE xc_gauxc_interface, ONLY: &
24 : cp_gauxc_basisset_type, cp_gauxc_grid_type, cp_gauxc_integrator_type, &
25 : cp_gauxc_molecule_type, cp_gauxc_status_type, gauxc_check_status, gauxc_create_basisset, &
26 : gauxc_create_grid, gauxc_create_integrator, gauxc_create_molecule, gauxc_destroy_basisset, &
27 : gauxc_destroy_grid, gauxc_destroy_integrator, gauxc_destroy_molecule
28 : #include "../base/base_uses.f90"
29 :
30 : IMPLICIT NONE
31 : PRIVATE
32 :
33 : TYPE cp_gauxc_cache_params
34 : INTEGER :: natom = -1
35 : INTEGER :: nspins = -1
36 : INTEGER :: batch_size = -1
37 : REAL(c_double) :: device_runtime_fill_fraction = 0.0_c_double
38 : CHARACTER(LEN=default_string_length) :: xc_fun_name = ""
39 : CHARACTER(LEN=default_string_length) :: grid_type = ""
40 : CHARACTER(LEN=default_string_length) :: radial_quadrature = ""
41 : CHARACTER(LEN=default_string_length) :: pruning_scheme = ""
42 : CHARACTER(LEN=default_string_length) :: lb_exec_space = ""
43 : CHARACTER(LEN=default_string_length) :: int_exec_space = ""
44 : CHARACTER(LEN=default_path_length) :: model_eval_name = ""
45 : CHARACTER(len=default_string_length) :: lwd_kernel = ""
46 : CHARACTER(len=default_string_length) :: gradient_runtime = "AUTO"
47 : LOGICAL :: need_xc_gradient = .FALSE.
48 : LOGICAL :: gapw_method = .FALSE.
49 : LOGICAL :: force_fd_gradient = .FALSE.
50 : LOGICAL :: use_mpi_runtime = .FALSE.
51 : LOGICAL :: use_gradient_mpi_runtime = .FALSE.
52 : LOGICAL :: use_self_runtime = .FALSE.
53 : LOGICAL :: use_gradient_self_runtime = .FALSE.
54 : LOGICAL :: use_fd_gradient = .FALSE.
55 : LOGICAL :: use_gauxc_model = .FALSE.
56 : END TYPE cp_gauxc_cache_params
57 :
58 : TYPE, extends(cp_gauxc_cache_params) :: cp_gauxc_cache_type
59 : TYPE(cp_gauxc_molecule_type) :: molecule = cp_gauxc_molecule_type()
60 : TYPE(cp_gauxc_basisset_type) :: basisset = cp_gauxc_basisset_type()
61 : TYPE(cp_gauxc_grid_type) :: grid = cp_gauxc_grid_type()
62 : TYPE(cp_gauxc_grid_type) :: gradient_grid = cp_gauxc_grid_type()
63 : TYPE(cp_gauxc_integrator_type) :: integrator = cp_gauxc_integrator_type()
64 : TYPE(cp_gauxc_integrator_type) :: gradient_integrator = cp_gauxc_integrator_type()
65 : INTEGER :: mpi_comm = -1
66 : LOGICAL :: is_init = .FALSE.
67 : REAL(c_double), DIMENSION(:, :), ALLOCATABLE :: last_positions
68 : CONTAINS
69 :
70 : PROCEDURE, PUBLIC :: needs_gradient_grid => gauxc_cache_type_needs_gradient_grid
71 : PROCEDURE, PUBLIC :: max_l => gauxc_cache_type_max_l
72 : END TYPE cp_gauxc_cache_type
73 :
74 : PUBLIC :: &
75 : cp_gauxc_cache_type, &
76 : gauxc_cache_init, &
77 : gauxc_cache_is_valid, &
78 : gauxc_cache_release, cp_gauxc_cache_params
79 :
80 : CONTAINS
81 :
82 : ! **************************************************************************************************
83 : !> \brief ...
84 : !> \param this ...
85 : !> \return ...
86 : ! **************************************************************************************************
87 82 : FUNCTION gauxc_cache_type_needs_gradient_grid(this) RESULT(res)
88 : class(cp_gauxc_cache_type) :: this
89 : LOGICAL :: res
90 :
91 82 : res = this%use_gradient_self_runtime
92 82 : END FUNCTION gauxc_cache_type_needs_gradient_grid
93 :
94 : ! **************************************************************************************************
95 : !> \brief ...
96 : !> \param this ...
97 : !> \return ...
98 : ! **************************************************************************************************
99 82 : FUNCTION gauxc_cache_type_max_l(this) RESULT(res)
100 : class(cp_gauxc_cache_type) :: this
101 : INTEGER :: res
102 :
103 82 : res = this%basisset%max_l
104 82 : END FUNCTION gauxc_cache_type_max_l
105 :
106 : ! **************************************************************************************************
107 : !> \brief ...
108 : !> \param cache ...
109 : !> \param params ...
110 : !> \param para_env ...
111 : !> \param particle_set ...
112 : !> \param qs_kind_set ...
113 : !> \param status ...
114 : ! **************************************************************************************************
115 668 : SUBROUTINE gauxc_cache_init( &
116 : cache, &
117 : params, &
118 : para_env, &
119 : particle_set, &
120 : qs_kind_set, &
121 : status)
122 :
123 : TYPE(cp_gauxc_cache_type), INTENT(inout) :: cache
124 : TYPE(cp_gauxc_cache_params), INTENT(inout) :: params
125 : TYPE(mp_para_env_type), INTENT(in), POINTER :: para_env
126 : TYPE(particle_type), DIMENSION(:), INTENT(in), &
127 : POINTER :: particle_set
128 : TYPE(qs_kind_type), DIMENSION(:), INTENT(in), &
129 : POINTER :: qs_kind_set
130 : TYPE(cp_gauxc_status_type), INTENT(inout) :: status
131 :
132 : #ifdef __GAUXC
133 : INTEGER :: gradient_error, unit_nr
134 :
135 668 : IF (gauxc_cache_is_valid( &
136 : cache, &
137 : params, &
138 : para_env, &
139 : particle_set)) THEN
140 586 : params%use_fd_gradient = cache%use_fd_gradient
141 586 : params%use_gradient_mpi_runtime = cache%use_gradient_mpi_runtime
142 586 : params%use_gradient_self_runtime = cache%use_gradient_self_runtime
143 586 : status%status%code = 0
144 586 : RETURN
145 : END IF
146 :
147 82 : CALL gauxc_cache_release(cache)
148 :
149 : cache%molecule = gauxc_create_molecule( &
150 : particle_set, &
151 82 : status)
152 82 : IF (status%status%code /= 0) THEN
153 0 : CALL cleanup_after_molecule()
154 0 : RETURN
155 : END IF
156 :
157 : cache%basisset = gauxc_create_basisset( &
158 : qs_kind_set, &
159 : particle_set, &
160 82 : status)
161 82 : IF (status%status%code /= 0) THEN
162 0 : CALL cleanup_after_basisset()
163 0 : RETURN
164 : END IF
165 :
166 : CALL gauxc_resolve_gradient_policy( &
167 : params%need_xc_gradient, params%gapw_method, cache%max_l(), params%use_gauxc_model, para_env%num_pe, &
168 : params%use_self_runtime, TRIM(params%gradient_runtime), params%force_fd_gradient, &
169 82 : params%use_fd_gradient, params%use_gradient_mpi_runtime, params%use_gradient_self_runtime, gradient_error)
170 0 : SELECT CASE (gradient_error)
171 : CASE (1)
172 : CALL cp_abort(__LOCATION__, &
173 0 : "MODEL_GRADIENT_RUNTIME MPI requires verified GauXC OneDFT gradient support (PR #222).")
174 : CASE (2)
175 : CALL cp_abort(__LOCATION__, &
176 : "MODEL_GRADIENT_RUNTIME MPI requires an MPI energy/VXC runtime. "// &
177 0 : "Use AUTO/SELF to retain the open-shell replicated-runtime safeguard.")
178 : CASE (3)
179 : CALL cp_abort(__LOCATION__, &
180 0 : "MODEL_GRADIENT_RUNTIME MPI is incompatible with FORCE_NUMERICAL_GRADIENT.")
181 : CASE (4)
182 82 : CALL cp_abort(__LOCATION__, "Unknown GAUXC%MODEL_GRADIENT_RUNTIME value.")
183 : END SELECT
184 :
185 82 : IF (params%use_fd_gradient .AND. .NOT. params%force_fd_gradient .AND. para_env%mepos == 0) THEN
186 : CALL cp_warn( &
187 : __LOCATION__, &
188 : "Using finite-difference GauXC XC gradients for METHOD GAPW/GAPW_XC with "// &
189 : "basis functions beyond f shells. Analytical high-l gradients have not been "// &
190 0 : "enabled for this GauXC installation and functional path.")
191 : END IF
192 82 : IF (params%need_xc_gradient .AND. para_env%mepos == 0) THEN
193 7 : unit_nr = cp_logger_get_default_io_unit()
194 7 : IF (unit_nr > 0) THEN
195 7 : WRITE (unit_nr, '(A,A)') " GAUXC| Molecular XC gradient method: ", &
196 20 : TRIM(MERGE("FD ", "ANALYTICAL", params%use_fd_gradient))
197 7 : WRITE (unit_nr, '(A,A)') " GAUXC| Molecular XC gradient runtime: ", &
198 21 : MERGE("MPI ", "SELF", params%use_gradient_mpi_runtime)
199 : END IF
200 : END IF
201 82 : IF (params%use_self_runtime .OR. .NOT. params%use_gauxc_model) THEN
202 : ! SKALA currently needs a replicated molecular runtime for reproducible
203 : ! open-shell densities across CP2K MPI ranks.
204 : ! Conventional GauXC also uses a replicated runtime here: CP2K has
205 : ! already allreduced the dense AO density matrix on every rank.
206 : cache%grid = gauxc_create_grid( &
207 : cache%molecule, &
208 : cache%basisset, &
209 : params%grid_type, &
210 : params%radial_quadrature, &
211 : params%pruning_scheme, &
212 : params%lb_exec_space, &
213 : params%batch_size, &
214 : params%device_runtime_fill_fraction, &
215 : status, &
216 : mpi_comm=mp_comm_self%get_handle(), &
217 52 : force_new_runtime=.TRUE.)
218 : ELSE
219 : ! Use the QS force-evaluation communicator rather than GauXC's global
220 : ! runtime. In mixed CDFT the diabatic states can be built on disjoint
221 : ! MPI subgroups; using the global communicator would make GauXC wait
222 : ! for ranks that are working on another state.
223 : cache%grid = gauxc_create_grid( &
224 : cache%molecule, &
225 : cache%basisset, &
226 : params%grid_type, &
227 : params%radial_quadrature, &
228 : params%pruning_scheme, &
229 : params%lb_exec_space, &
230 : params%batch_size, &
231 : params%device_runtime_fill_fraction, &
232 : status, &
233 30 : mpi_comm=para_env%get_handle())
234 : END IF
235 82 : IF (status%status%code /= 0) THEN
236 0 : CALL cleanup_after_grid()
237 0 : RETURN
238 : END IF
239 :
240 : cache%integrator = gauxc_create_integrator( &
241 : TRIM(params%xc_fun_name), &
242 : cache%grid, &
243 : params%int_exec_space, &
244 : params%lwd_kernel, &
245 : params%nspins, &
246 82 : status)
247 82 : IF (status%status%code /= 0) THEN
248 0 : CALL cleanup_after_integrator()
249 0 : RETURN
250 : END IF
251 :
252 82 : IF (params%use_gradient_self_runtime) THEN
253 : ! Retain the energy/VXC runtime when SELF is requested or the external
254 : ! GauXC installation has not advertised corrected distributed gradients.
255 : cache%gradient_grid = gauxc_create_grid( &
256 : cache%molecule, &
257 : cache%basisset, &
258 : params%grid_type, &
259 : params%radial_quadrature, &
260 : params%pruning_scheme, &
261 : params%lb_exec_space, &
262 : params%batch_size, &
263 : params%device_runtime_fill_fraction, &
264 : status, &
265 : mpi_comm=mp_comm_self%get_handle(), &
266 6 : force_new_runtime=.TRUE.)
267 6 : IF (status%status%code /= 0) THEN
268 0 : CALL cleanup_after_gradient_grid()
269 0 : RETURN
270 : END IF
271 : cache%gradient_integrator = gauxc_create_integrator( &
272 : TRIM(params%xc_fun_name), &
273 : cache%gradient_grid, &
274 : params%int_exec_space, &
275 : params%lwd_kernel, &
276 : params%nspins, &
277 6 : status)
278 6 : IF (status%status%code /= 0) THEN
279 0 : CALL cleanup_after_gradient_integrator()
280 0 : RETURN
281 : END IF
282 : END IF
283 :
284 82 : cache%natom = params%natom
285 82 : cache%nspins = params%nspins
286 82 : cache%batch_size = params%batch_size
287 82 : cache%device_runtime_fill_fraction = REAL(params%device_runtime_fill_fraction, c_double)
288 82 : cache%xc_fun_name = TRIM(params%xc_fun_name)
289 82 : cache%grid_type = TRIM(params%grid_type)
290 82 : cache%radial_quadrature = TRIM(params%radial_quadrature)
291 82 : cache%pruning_scheme = TRIM(params%pruning_scheme)
292 82 : cache%lb_exec_space = TRIM(params%lb_exec_space)
293 82 : cache%int_exec_space = TRIM(params%int_exec_space)
294 82 : cache%model_eval_name = TRIM(params%model_eval_name)
295 82 : cache%lwd_kernel = TRIM(params%lwd_kernel)
296 82 : cache%gradient_runtime = params%gradient_runtime
297 82 : cache%need_xc_gradient = params%need_xc_gradient
298 82 : cache%gapw_method = params%gapw_method
299 82 : cache%force_fd_gradient = params%force_fd_gradient
300 82 : cache%use_gauxc_model = params%use_gauxc_model
301 82 : cache%use_gradient_mpi_runtime = params%use_gradient_mpi_runtime
302 82 : cache%use_mpi_runtime = params%use_mpi_runtime
303 82 : cache%use_gradient_self_runtime = params%use_gradient_self_runtime
304 82 : cache%use_self_runtime = params%use_self_runtime
305 82 : cache%use_fd_gradient = params%use_fd_gradient
306 82 : IF (ALLOCATED(cache%last_positions)) THEN
307 0 : DEALLOCATE (cache%last_positions)
308 : END IF
309 246 : ALLOCATE (cache%last_positions(3, params%natom))
310 :
311 : BLOCK
312 : INTEGER :: iatom
313 282 : DO iatom = 1, params%natom
314 882 : cache%last_positions(:, iatom) = REAL(particle_set(iatom)%r(:), c_double)
315 : END DO
316 : END BLOCK
317 :
318 82 : cache%mpi_comm = para_env%get_handle()
319 82 : cache%is_init = .TRUE.
320 :
321 : #else
322 : MARK_USED(params)
323 : MARK_USED(para_env)
324 : MARK_USED(particle_set)
325 : MARK_USED(qs_kind_set)
326 : MARK_USED(status)
327 : cache%is_init = .FALSE.
328 : #endif
329 :
330 : CONTAINS
331 : ! **************************************************************************************************
332 : !> \brief ...
333 : ! **************************************************************************************************
334 0 : SUBROUTINE cleanup_after_gradient_integrator()
335 0 : CALL gauxc_destroy_integrator(cache%gradient_integrator, status)
336 0 : CALL gauxc_check_status(status)
337 0 : CALL cleanup_after_gradient_grid()
338 0 : END SUBROUTINE cleanup_after_gradient_integrator
339 :
340 : ! **************************************************************************************************
341 : !> \brief ...
342 : ! **************************************************************************************************
343 0 : SUBROUTINE cleanup_after_gradient_grid()
344 0 : CALL gauxc_destroy_grid(cache%gradient_grid, status)
345 0 : CALL gauxc_check_status(status)
346 0 : CALL cleanup_after_integrator()
347 0 : END SUBROUTINE cleanup_after_gradient_grid
348 :
349 : ! **************************************************************************************************
350 : !> \brief ...
351 : ! **************************************************************************************************
352 0 : SUBROUTINE cleanup_after_integrator()
353 0 : CALL gauxc_destroy_integrator(cache%integrator, status)
354 0 : CALL gauxc_check_status(status)
355 0 : CALL cleanup_after_grid()
356 0 : END SUBROUTINE cleanup_after_integrator
357 :
358 : ! **************************************************************************************************
359 : !> \brief ...
360 : ! **************************************************************************************************
361 0 : SUBROUTINE cleanup_after_grid()
362 0 : CALL gauxc_destroy_grid(cache%grid, status)
363 0 : CALL gauxc_check_status(status)
364 0 : CALL cleanup_after_basisset()
365 0 : END SUBROUTINE cleanup_after_grid
366 :
367 : ! **************************************************************************************************
368 : !> \brief ...
369 : ! **************************************************************************************************
370 0 : SUBROUTINE cleanup_after_basisset()
371 0 : CALL gauxc_destroy_basisset(cache%basisset, status)
372 0 : CALL gauxc_check_status(status)
373 0 : CALL cleanup_after_molecule()
374 0 : END SUBROUTINE cleanup_after_basisset
375 :
376 : ! **************************************************************************************************
377 : !> \brief ...
378 : ! **************************************************************************************************
379 0 : SUBROUTINE cleanup_after_molecule()
380 0 : CALL gauxc_destroy_molecule(cache%molecule, status)
381 0 : CALL gauxc_check_status(status)
382 0 : CALL cleanup_final()
383 0 : END SUBROUTINE cleanup_after_molecule
384 :
385 : ! **************************************************************************************************
386 : !> \brief ...
387 : ! **************************************************************************************************
388 0 : SUBROUTINE cleanup_final()
389 0 : IF (ALLOCATED(cache%last_positions)) DEALLOCATE (cache%last_positions)
390 0 : cache%is_init = .FALSE.
391 0 : END SUBROUTINE cleanup_final
392 :
393 : END SUBROUTINE gauxc_cache_init
394 :
395 : ! **************************************************************************************************
396 : !> \brief Release all GauXC objects in a cache and deallocate arrays
397 : !> \param cache ...
398 : ! **************************************************************************************************
399 136 : SUBROUTINE gauxc_cache_release(cache)
400 : TYPE(cp_gauxc_cache_type), INTENT(INOUT) :: cache
401 :
402 : #ifdef __GAUXC
403 : TYPE(cp_gauxc_status_type) :: status
404 :
405 136 : IF (cache%is_init) THEN
406 82 : IF (cache%needs_gradient_grid()) THEN
407 6 : CALL gauxc_destroy_integrator(cache%gradient_integrator, status)
408 6 : CALL gauxc_check_status(status)
409 6 : CALL gauxc_destroy_grid(cache%gradient_grid, status)
410 6 : CALL gauxc_check_status(status)
411 : END IF
412 82 : CALL gauxc_destroy_integrator(cache%integrator, status)
413 82 : CALL gauxc_check_status(status)
414 82 : CALL gauxc_destroy_grid(cache%grid, status)
415 82 : CALL gauxc_check_status(status)
416 82 : CALL gauxc_destroy_basisset(cache%basisset, status)
417 82 : CALL gauxc_check_status(status)
418 82 : CALL gauxc_destroy_molecule(cache%molecule, status)
419 82 : CALL gauxc_check_status(status)
420 : END IF
421 136 : IF (ALLOCATED(cache%last_positions)) DEALLOCATE (cache%last_positions)
422 136 : cache%is_init = .FALSE.
423 : #else
424 : MARK_USED(cache)
425 : IF (ALLOCATED(cache%last_positions)) DEALLOCATE (cache%last_positions)
426 : cache%is_init = .FALSE.
427 : #endif
428 136 : END SUBROUTINE gauxc_cache_release
429 :
430 : ! **************************************************************************************************
431 : !> \brief Check if the GauXC cache is valid for the given parameters
432 : !> \param cache ...
433 : !> \param params ...
434 : !> \param para_env ...
435 : !> \param particle_set ...
436 : !> \return ...
437 : !> \retval cache_valid ...
438 : ! **************************************************************************************************
439 668 : FUNCTION gauxc_cache_is_valid( &
440 : cache, &
441 : params, &
442 : para_env, &
443 : particle_set) &
444 : RESULT(cache_valid)
445 :
446 : TYPE(cp_gauxc_cache_type), INTENT(IN) :: cache
447 : TYPE(cp_gauxc_cache_params), INTENT(in) :: params
448 : TYPE(mp_para_env_type), INTENT(in), POINTER :: para_env
449 : TYPE(particle_type), DIMENSION(:), POINTER :: particle_set
450 : LOGICAL :: cache_valid
451 :
452 : #ifdef __GAUXC
453 : INTEGER :: iatom
454 : REAL(c_double), PARAMETER :: pos_tol = 1.0E-8_c_double
455 :
456 668 : cache_valid = .FALSE.
457 668 : IF (.NOT. cache%is_init) RETURN
458 614 : IF (cache%natom /= params%natom) RETURN
459 614 : IF (cache%nspins /= params%nspins) RETURN
460 614 : IF (cache%batch_size /= params%batch_size) RETURN
461 614 : IF (ABS(cache%device_runtime_fill_fraction - REAL(params%device_runtime_fill_fraction, c_double)) > &
462 : 1.0E-10_c_double) RETURN
463 614 : IF (TRIM(cache%xc_fun_name) /= TRIM(params%xc_fun_name)) RETURN
464 614 : IF (TRIM(cache%grid_type) /= TRIM(params%grid_type)) RETURN
465 614 : IF (TRIM(cache%radial_quadrature) /= TRIM(params%radial_quadrature)) RETURN
466 614 : IF (TRIM(cache%pruning_scheme) /= TRIM(params%pruning_scheme)) RETURN
467 614 : IF (TRIM(cache%lb_exec_space) /= TRIM(params%lb_exec_space)) RETURN
468 614 : IF (TRIM(cache%int_exec_space) /= TRIM(params%int_exec_space)) RETURN
469 614 : IF (TRIM(cache%model_eval_name) /= TRIM(params%model_eval_name)) RETURN
470 614 : IF (TRIM(cache%lwd_kernel) /= TRIM(params%lwd_kernel)) RETURN
471 614 : IF (cache%use_self_runtime .NEQV. params%use_self_runtime) RETURN
472 614 : IF (cache%use_mpi_runtime .NEQV. params%use_mpi_runtime) RETURN
473 :
474 : ! Compare requests, not flags resolved from the basis during initialization.
475 614 : IF (cache%gradient_runtime /= params%gradient_runtime) RETURN
476 614 : IF (cache%need_xc_gradient .NEQV. params%need_xc_gradient) RETURN
477 598 : IF (cache%gapw_method .NEQV. params%gapw_method) RETURN
478 598 : IF (cache%force_fd_gradient .NEQV. params%force_fd_gradient) RETURN
479 598 : IF (cache%use_gauxc_model .NEQV. params%use_gauxc_model) RETURN
480 598 : IF (cache%mpi_comm /= para_env%get_handle()) RETURN
481 :
482 598 : IF (.NOT. ALLOCATED(cache%last_positions)) RETURN
483 598 : IF (SIZE(cache%last_positions, 2) /= params%natom) RETURN
484 1962 : DO iatom = 1, params%natom
485 5492 : IF (ANY(ABS(cache%last_positions(:, iatom) - &
486 586 : REAL(particle_set(iatom)%r(:), c_double)) > pos_tol)) RETURN
487 : END DO
488 668 : cache_valid = .TRUE.
489 : #else
490 : MARK_USED(cache)
491 : MARK_USED(particle_set)
492 : MARK_USED(params)
493 : MARK_USED(para_env)
494 : cache_valid = .FALSE.
495 : #endif
496 : END FUNCTION gauxc_cache_is_valid
497 :
498 0 : END MODULE xc_gauxc_cache
|