mirror of
https://github.com/LCPQ/quantum_package
synced 2024-12-25 13:53:49 +01:00
Anthony Scemama
873035e016
commit 4b9c435dce0f3b3078d573e66fd32b40fca26497 Merge:74e559c8
093e3fd0
Author: Anthony Scemama <scemama@irsamc.ups-tlse.fr> Date: Tue Sep 4 16:58:51 2018 +0200 Merge branch 'thesis' of git://github.com/garniron/quantum_package into garniron-thesis commit093e3fd021
Author: Yann Garniron <yann.garniron@yahoo.fr> Date: Tue Sep 4 16:13:00 2018 +0200 removed ungodly hack commit8529a0f3f6
Author: Yann Garniron <yann.garniron@yahoo.fr> Date: Tue Sep 4 14:57:19 2018 +0200 reduced prints in pt2_stoch commit03b8f353bd
Author: Yann Garniron <yann.garniron@yahoo.fr> Date: Tue Sep 4 14:41:46 2018 +0200 teeth building check for pt2_stoch commit0d91b9310a
Author: Yann Garniron <yann.garniron@yahoo.fr> Date: Tue Sep 4 14:35:04 2018 +0200 timestamp of first pull commit34d9fa0165
Author: Yann Garniron <yann.garniron@yahoo.fr> Date: Tue Sep 4 14:27:10 2018 +0200 potential numerical precision bug commit9a0f900d8c
Author: Yann Garniron <yann.garniron@yahoo.fr> Date: Tue Sep 4 14:09:51 2018 +0200 tests if teeth can be built commitdda0dc34df
Author: Yann Garniron <yann.garniron@yahoo.fr> Date: Mon Sep 3 17:48:04 2018 +0200 corrected pt2_find_sample commita521f0cb82
Author: Yann Garniron <yann.garniron@yahoo.fr> Date: Mon Sep 3 16:08:02 2018 +0200 tasks get by batches of Nproc commit997a5a1265
Author: Yann Garniron <yann.garniron@yahoo.fr> Date: Mon Sep 3 14:18:04 2018 +0200 buffered task_id send commit99ea7948e0
Author: Yann Garniron <yann.garniron@yahoo.fr> Date: Mon Sep 3 12:29:12 2018 +0200 unbalanced fragmentation commitabb3b7e08b
Author: Yann Garniron <yann.garniron@yahoo.fr> Date: Sun Sep 2 17:18:44 2018 +0200 overflow of pt2_J commit8df49f394b
Author: Yann Garniron <yann.garniron@yahoo.fr> Date: Sun Sep 2 15:58:48 2018 +0200 removed useless computation of intermediate checkpoints commit4ba5b79eb3
Author: Yann Garniron <yann.garniron@yahoo.fr> Date: Sun Sep 2 15:50:14 2018 +0200 dressing only sent for chosen checkpoint commita4a6a69459
Author: Yann Garniron <yann.garniron@yahoo.fr> Date: Sat Sep 1 17:01:56 2018 +0200 cumulative dot_F commit6a7f04cb79
Author: Yann Garniron <yann.garniron@yahoo.fr> Date: Sat Sep 1 16:58:07 2018 +0200 simpler purge commit168ca2f2e2
Author: Yann Garniron <yann.garniron@yahoo.fr> Date: Fri Aug 31 21:07:01 2018 +0200 task list optimized commitde4a0d0caf
Author: Yann Garniron <yann.garniron@yahoo.fr> Date: Fri Aug 31 18:57:03 2018 +0200 removed print commitfee31d4e3e
Author: Yann Garniron <yann.garniron@yahoo.fr> Date: Fri Aug 31 18:56:23 2018 +0200 dress fragmentation commit02893a419d
Author: Yann Garniron <yann.garniron@yahoo.fr> Date: Fri Aug 31 15:52:16 2018 +0200 bug in blocked search - replaced with thesis version commitbb6e073cf1
Author: Yann Garniron <yann.garniron@yahoo.fr> Date: Thu Aug 30 21:24:45 2018 +0200 ungodly hack to prevent double providing commit0609e8c627
Author: Yann Garniron <yann.garniron@yahoo.fr> Date: Thu Aug 30 20:52:05 2018 +0200 debugging commita254fdd7cf
Author: Yann Garniron <yann.garniron@yahoo.fr> Date: Thu Aug 30 15:24:07 2018 +0200 parallel bug commit2a6c1941d4
Author: Yann Garniron <yann.garniron@yahoo.fr> Date: Thu Aug 30 11:43:11 2018 +0200 corrected when relative_error=0d0 commitbac039bdf1
Author: Yann Garniron <yann.garniron@yahoo.fr> Date: Thu Aug 30 10:58:17 2018 +0200 relative error 1d-5 commitaae9d203ec
Author: Yann Garniron <yann.garniron@yahoo.fr> Date: Thu Aug 30 10:07:02 2018 +0200 potential fragmentation bug commitad69f39f99
Author: Yann Garniron <yann.garniron@yahoo.fr> Date: Wed Aug 29 20:54:58 2018 +0200 dress_zmq re-implemented commitd78f64732a
Author: Yann Garniron <yann.garniron@yahoo.fr> Date: Wed Aug 29 11:30:19 2018 +0200 pt2_stoch re-implemented commit4b9b54e19a
Author: Yann Garniron <yann.garniron@yahoo.fr> Date: Tue Aug 28 10:24:38 2018 +0200 removed test for phase_mask_bit commit3abccca5e3
Author: Yann Garniron <yann.garniron@yahoo.fr> Date: Fri Aug 3 23:44:05 2018 +0200 phasemask_bit
448 lines
13 KiB
Fortran
448 lines
13 KiB
Fortran
BEGIN_PROVIDER [ integer, pt2_stoch_istate ]
|
|
implicit none
|
|
BEGIN_DOC
|
|
! State for stochatsic PT2
|
|
END_DOC
|
|
pt2_stoch_istate = 1
|
|
END_PROVIDER
|
|
|
|
|
|
BEGIN_PROVIDER [ integer, pt2_N_teeth ]
|
|
&BEGIN_PROVIDER [ integer, pt2_minDetInFirstTeeth ]
|
|
&BEGIN_PROVIDER [ integer, pt2_n_tasks_max ]
|
|
&BEGIN_PROVIDER [ integer, pt2_F, (N_det_generators) ]
|
|
implicit none
|
|
logical, external :: testTeethBuilding
|
|
pt2_F(:) = 1
|
|
!pt2_F(:N_det_generators/1000*0+50) = 1
|
|
pt2_n_tasks_max = 16 ! N_det_generators/100 + 1
|
|
|
|
if(N_det_generators < 1024) then
|
|
pt2_minDetInFirstTeeth = 1
|
|
pt2_N_teeth = 1
|
|
else
|
|
do pt2_N_teeth=32,1,-1
|
|
pt2_minDetInFirstTeeth = min(5, N_det_generators)
|
|
if(testTeethBuilding(pt2_minDetInFirstTeeth, pt2_N_teeth)) exit
|
|
end do
|
|
end if
|
|
print *, pt2_N_teeth
|
|
END_PROVIDER
|
|
|
|
|
|
logical function testTeethBuilding(minF, N)
|
|
implicit none
|
|
integer, intent(in) :: minF, N
|
|
integer :: n0, i
|
|
double precision :: u0, Wt, r
|
|
|
|
double precision, allocatable :: tilde_w(:), tilde_cW(:)
|
|
integer, external :: dress_find_sample
|
|
|
|
allocate(tilde_w(N_det_generators), tilde_cW(0:N_det_generators))
|
|
|
|
tilde_cW(0) = 0d0
|
|
do i=1,N_det_generators
|
|
tilde_w(i) = psi_coef_generators(i,pt2_stoch_istate)**2
|
|
tilde_cW(i) = tilde_cW(i-1) + tilde_w(i)
|
|
enddo
|
|
tilde_cW(N_det_generators) = 1d0
|
|
|
|
n0 = 0
|
|
do
|
|
u0 = tilde_cW(n0)
|
|
r = tilde_cW(n0 + minF)
|
|
Wt = (1d0 - u0) / dble(N)
|
|
if(Wt >= r - u0) then
|
|
testTeethBuilding = .true.
|
|
return
|
|
end if
|
|
n0 += 1
|
|
if(N_det_generators - n0 < minF * N) then
|
|
testTeethBuilding = .false.
|
|
return
|
|
end if
|
|
end do
|
|
stop "exited testTeethBuilding"
|
|
end function
|
|
|
|
|
|
|
|
subroutine ZMQ_pt2(E, pt2,relative_error, absolute_error, error)
|
|
use f77_zmq
|
|
use selection_types
|
|
|
|
implicit none
|
|
|
|
character(len=64000) :: task
|
|
integer(ZMQ_PTR) :: zmq_to_qp_run_socket, zmq_socket_pull
|
|
integer, external :: omp_get_thread_num
|
|
double precision, intent(in) :: relative_error, absolute_error, E(N_states)
|
|
double precision, intent(out) :: pt2(N_states),error(N_states)
|
|
|
|
|
|
integer :: i, j, k
|
|
|
|
double precision, external :: omp_get_wtime
|
|
double precision :: state_average_weight_save(N_states), w(N_states)
|
|
integer(ZMQ_PTR), external :: new_zmq_to_qp_run_socket
|
|
|
|
if (N_det < max(10,N_states)) then
|
|
pt2=0.d0
|
|
call ZMQ_selection(0, pt2)
|
|
error(:) = 0.d0
|
|
else
|
|
|
|
state_average_weight_save(:) = state_average_weight(:)
|
|
do pt2_stoch_istate=1,N_states
|
|
state_average_weight(:) = 0.d0
|
|
state_average_weight(pt2_stoch_istate) = 1.d0
|
|
TOUCH state_average_weight pt2_stoch_istate
|
|
|
|
provide nproc pt2_F mo_bielec_integrals_in_map mo_mono_elec_integral pt2_w psi_selectors
|
|
|
|
print *, '========== ================= ================= ================='
|
|
print *, ' Samples Energy Stat. Error Seconds '
|
|
print *, '========== ================= ================= ================='
|
|
|
|
call new_parallel_job(zmq_to_qp_run_socket, zmq_socket_pull, 'pt2')
|
|
|
|
integer, external :: zmq_put_psi
|
|
integer, external :: zmq_put_N_det_generators
|
|
integer, external :: zmq_put_N_det_selectors
|
|
integer, external :: zmq_put_dvector
|
|
integer, external :: zmq_put_ivector
|
|
if (zmq_put_psi(zmq_to_qp_run_socket,1) == -1) then
|
|
stop 'Unable to put psi on ZMQ server'
|
|
endif
|
|
if (zmq_put_N_det_generators(zmq_to_qp_run_socket, 1) == -1) then
|
|
stop 'Unable to put N_det_generators on ZMQ server'
|
|
endif
|
|
if (zmq_put_N_det_selectors(zmq_to_qp_run_socket, 1) == -1) then
|
|
stop 'Unable to put N_det_selectors on ZMQ server'
|
|
endif
|
|
if (zmq_put_dvector(zmq_to_qp_run_socket,1,'energy',pt2_e0_denominator,size(pt2_e0_denominator)) == -1) then
|
|
stop 'Unable to put energy on ZMQ server'
|
|
endif
|
|
if (zmq_put_dvector(zmq_to_qp_run_socket,1,'state_average_weight',state_average_weight,N_states) == -1) then
|
|
stop 'Unable to put state_average_weight on ZMQ server'
|
|
endif
|
|
if (zmq_put_ivector(zmq_to_qp_run_socket,1,'pt2_stoch_istate',pt2_stoch_istate,1) == -1) then
|
|
stop 'Unable to put pt2_stoch_istate on ZMQ server'
|
|
endif
|
|
if (zmq_put_dvector(zmq_to_qp_run_socket,1,'threshold_selectors',threshold_selectors,1) == -1) then
|
|
stop 'Unable to put threshold_selectors on ZMQ server'
|
|
endif
|
|
if (zmq_put_dvector(zmq_to_qp_run_socket,1,'threshold_generators',threshold_generators,1) == -1) then
|
|
stop 'Unable to put threshold_generators on ZMQ server'
|
|
endif
|
|
|
|
|
|
integer, external :: add_task_to_taskserver
|
|
|
|
|
|
do i=1,N_det_generators
|
|
do j=1,pt2_F(i) !!!!!!!!!!!!
|
|
write(task(1:20),'(I9,1X,I9''|'')') j, pt2_J(i)
|
|
if (add_task_to_taskserver(zmq_to_qp_run_socket,trim(task(1:20))) == -1) then
|
|
stop 'Unable to add task to task server'
|
|
endif
|
|
end do
|
|
end do
|
|
|
|
integer, external :: zmq_set_running
|
|
if (zmq_set_running(zmq_to_qp_run_socket) == -1) then
|
|
print *, irp_here, ': Failed in zmq_set_running'
|
|
endif
|
|
|
|
|
|
integer :: nproc_target
|
|
nproc_target = nproc
|
|
double precision :: mem
|
|
mem = 8.d0 * N_det * (N_int * 2.d0 * 3.d0 + 3.d0 + 5.d0) / (1024.d0**3)
|
|
call write_double(6,mem,'Estimated memory/thread (Gb)')
|
|
if (qp_max_mem > 0) then
|
|
nproc_target = max(1,int(dble(qp_max_mem)/mem))
|
|
nproc_target = min(nproc_target,nproc)
|
|
endif
|
|
|
|
!$OMP PARALLEL DEFAULT(shared) NUM_THREADS(nproc_target+1) &
|
|
!$OMP PRIVATE(i)
|
|
i = omp_get_thread_num()
|
|
if (i==0) then
|
|
call pt2_collector(zmq_socket_pull, E(pt2_stoch_istate),relative_error, absolute_error, w, error)
|
|
pt2(pt2_stoch_istate) = w(pt2_stoch_istate)
|
|
else
|
|
call pt2_slave_inproc(i)
|
|
endif
|
|
!$OMP END PARALLEL
|
|
call end_parallel_job(zmq_to_qp_run_socket, zmq_socket_pull, 'pt2')
|
|
|
|
print *, '========== ================= ================= ================='
|
|
|
|
enddo
|
|
FREE pt2_stoch_istate
|
|
state_average_weight(:) = state_average_weight_save(:)
|
|
TOUCH state_average_weight
|
|
endif
|
|
do k=N_det+1,N_states
|
|
pt2(k) = 0.d0
|
|
enddo
|
|
|
|
end subroutine
|
|
|
|
|
|
subroutine pt2_slave_inproc(i)
|
|
implicit none
|
|
integer, intent(in) :: i
|
|
|
|
call run_pt2_slave(1,i,pt2_e0_denominator)
|
|
end
|
|
|
|
|
|
subroutine pt2_collector(zmq_socket_pull, E, relative_error, absolute_error, pt2, error)
|
|
use f77_zmq
|
|
use selection_types
|
|
use bitmasks
|
|
implicit none
|
|
|
|
|
|
integer(ZMQ_PTR), intent(in) :: zmq_socket_pull
|
|
double precision, intent(in) :: relative_error, absolute_error, E
|
|
double precision, intent(out) :: pt2(N_states), error(N_states)
|
|
|
|
|
|
double precision, allocatable :: eI(:,:), eI_task(:,:), S(:), S2(:)
|
|
integer(ZMQ_PTR),external :: new_zmq_to_qp_run_socket
|
|
integer(ZMQ_PTR) :: zmq_to_qp_run_socket
|
|
integer, external :: zmq_delete_tasks
|
|
integer, external :: pt2_find_sample
|
|
|
|
integer :: more, n, i, p, c, t, n_tasks, U
|
|
integer, allocatable :: task_id(:)
|
|
integer, allocatable :: index(:)
|
|
|
|
double precision, external :: omp_get_wtime
|
|
double precision :: v, x, avg, eqt, E0
|
|
double precision :: time, time0
|
|
|
|
integer, allocatable :: f(:)
|
|
logical, allocatable :: d(:)
|
|
|
|
zmq_to_qp_run_socket = new_zmq_to_qp_run_socket()
|
|
allocate(task_id(pt2_n_tasks_max), index(pt2_n_tasks_max), f(N_det_generators))
|
|
allocate(d(N_det_generators+1))
|
|
allocate(eI(N_states, N_det_generators), eI_task(N_states, pt2_n_tasks_max))
|
|
allocate(S(pt2_N_teeth+1), S2(pt2_N_teeth+1))
|
|
|
|
S(:) = 0d0
|
|
S2(:) = 0d0
|
|
n = 1
|
|
t = 0
|
|
U = 0
|
|
eI(:,:) = 0d0
|
|
f(:) = pt2_F(:)
|
|
d(:) = .false.
|
|
n_tasks = 0
|
|
E0 = E
|
|
more = 1
|
|
time0 = omp_get_wtime()
|
|
|
|
do while (n <= N_det_generators)
|
|
if(f(pt2_J(n)) == 0) then
|
|
d(pt2_J(n)) = .true.
|
|
do while(d(U+1))
|
|
U += 1
|
|
end do
|
|
do while(t <= pt2_N_teeth)
|
|
if(U >= pt2_n_0(t+1)) then
|
|
t=t+1
|
|
E0 = E
|
|
do i=pt2_n_0(t),1,-1
|
|
E0 += eI(pt2_stoch_istate, i)
|
|
end do
|
|
else
|
|
exit
|
|
end if
|
|
end do
|
|
|
|
c = pt2_R(n)
|
|
if(c /= 0) then
|
|
x = 0d0
|
|
do p=pt2_N_teeth, 1, -1
|
|
v = pt2_u_0 + pt2_W_T * (pt2_u(c) + dble(p-1))
|
|
i = pt2_find_sample(v, pt2_cW)
|
|
x += eI(pt2_stoch_istate, i) * pt2_W_T / pt2_w(i)
|
|
S(p) += x
|
|
S2(p) += x**2
|
|
end do
|
|
avg = S(t) / dble(c)
|
|
eqt = (S2(t) / c) - (S(t)/c)**2
|
|
eqt = sqrt(eqt / dble(c-1))
|
|
pt2(pt2_stoch_istate) = E0-E+avg
|
|
error(pt2_stoch_istate) = eqt
|
|
time = omp_get_wtime()
|
|
if(mod(c,10)==1 .or. n==N_det_generators) print '(G10.3, 2X, F16.10, 2X, G16.3, 2X, F16.4, A20)', c, avg+E0, eqt, time-time0, ''
|
|
end if
|
|
n += 1
|
|
else if(more == 0) then
|
|
exit
|
|
else
|
|
call pull_pt2_results(zmq_socket_pull, index, eI_task, task_id, n_tasks)
|
|
if (zmq_delete_tasks(zmq_to_qp_run_socket,zmq_socket_pull,task_id,n_tasks,more) == -1) then
|
|
stop 'Unable to delete tasks'
|
|
endif
|
|
do i=1,n_tasks
|
|
eI(:, index(i)) += eI_task(:, i)
|
|
f(index(i)) -= 1
|
|
end do
|
|
end if
|
|
end do
|
|
call end_zmq_to_qp_run_socket(zmq_to_qp_run_socket)
|
|
end subroutine
|
|
|
|
|
|
integer function pt2_find_sample(v, w)
|
|
implicit none
|
|
double precision, intent(in) :: v, w(0:N_det_generators)
|
|
integer :: i,l,r
|
|
|
|
l = 0
|
|
r = N_det_generators
|
|
|
|
do while(r-l > 1)
|
|
i = (r+l) / 2
|
|
if(w(i) < v) then
|
|
l = i
|
|
else
|
|
r = i
|
|
end if
|
|
end do
|
|
|
|
pt2_find_sample = r
|
|
end function
|
|
|
|
|
|
BEGIN_PROVIDER[ integer, pt2_J, (N_det_generators)]
|
|
&BEGIN_PROVIDER[ double precision, pt2_u, (N_det_generators)]
|
|
&BEGIN_PROVIDER[ integer, pt2_R, (N_det_generators)]
|
|
implicit none
|
|
integer :: N_c, N_j, U, t, i
|
|
double precision :: v
|
|
logical, allocatable :: d(:)
|
|
integer, external :: pt2_find_sample
|
|
|
|
allocate(d(N_det_generators))
|
|
|
|
pt2_R(:) = 0
|
|
N_c = 0
|
|
N_j = pt2_n_0(1)
|
|
d(:) = .false.
|
|
|
|
do i=1,N_j
|
|
d(i) = .true.
|
|
pt2_J(i) = i
|
|
end do
|
|
call random_seed(put=(/3211,64,6566,321,65,321,654,65,321,6321,654,65,321,621,654,65,321,65,654,65,321,65/))
|
|
call RANDOM_NUMBER(pt2_u)
|
|
call RANDOM_NUMBER(pt2_u)
|
|
|
|
|
|
|
|
U = 0
|
|
|
|
do while(N_j < N_det_generators)
|
|
!ADD_COMB
|
|
N_c += 1
|
|
do t=0, pt2_N_teeth-1
|
|
v = pt2_u_0 + pt2_W_T * (dble(t) + pt2_u(N_c))
|
|
i = pt2_find_sample(v, pt2_cW)
|
|
if(.not. d(i)) then
|
|
N_j += 1
|
|
pt2_J(N_j) = i
|
|
d(i) = .true.
|
|
end if
|
|
end do
|
|
|
|
pt2_R(N_j) = N_c
|
|
|
|
!FILL_TOOTH
|
|
do while(U < N_det_generators)
|
|
U += 1
|
|
if(.not. d(U)) then
|
|
N_j += 1
|
|
pt2_J(N_j) = U
|
|
d(U) = .true.
|
|
exit;
|
|
end if
|
|
end do
|
|
enddo
|
|
if(N_det_generators > 1) then
|
|
pt2_R(N_det_generators-1) = 0
|
|
pt2_R(N_det_generators) = N_c
|
|
end if
|
|
END_PROVIDER
|
|
|
|
|
|
BEGIN_PROVIDER [ double precision, pt2_w, (N_det_generators) ]
|
|
&BEGIN_PROVIDER [ double precision, pt2_cW, (0:N_det_generators) ]
|
|
&BEGIN_PROVIDER [ double precision, pt2_W_T ]
|
|
&BEGIN_PROVIDER [ double precision, pt2_u_0 ]
|
|
&BEGIN_PROVIDER [ integer, pt2_n_0, (pt2_N_teeth+1) ]
|
|
implicit none
|
|
integer :: i, t
|
|
double precision, allocatable :: tilde_w(:), tilde_cW(:)
|
|
double precision :: r, tooth_width
|
|
integer, external :: pt2_find_sample
|
|
|
|
allocate(tilde_w(N_det_generators), tilde_cW(0:N_det_generators))
|
|
|
|
tilde_cW(0) = 0d0
|
|
|
|
do i=1,N_det_generators
|
|
tilde_w(i) = psi_coef_generators(i,pt2_stoch_istate)**2
|
|
tilde_cW(i) = tilde_cW(i-1) + tilde_w(i)
|
|
enddo
|
|
|
|
pt2_n_0(1) = 0
|
|
do
|
|
pt2_u_0 = tilde_cW(pt2_n_0(1))
|
|
r = tilde_cW(pt2_n_0(1) + pt2_minDetInFirstTeeth)
|
|
pt2_W_T = (1d0 - pt2_u_0) / dble(pt2_N_teeth)
|
|
if(pt2_W_T >= r - pt2_u_0) then
|
|
exit
|
|
end if
|
|
pt2_n_0(1) += 1
|
|
if(N_det_generators - pt2_n_0(1) < pt2_minDetInFirstTeeth * pt2_N_teeth) then
|
|
stop "teeth building failed"
|
|
end if
|
|
end do
|
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
|
|
do t=2, pt2_N_teeth
|
|
r = pt2_u_0 + pt2_W_T * dble(t-1)
|
|
pt2_n_0(t) = pt2_find_sample(r, tilde_cW)
|
|
end do
|
|
pt2_n_0(pt2_N_teeth+1) = N_det_generators
|
|
|
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
pt2_w(:pt2_n_0(1)) = tilde_w(:pt2_n_0(1))
|
|
do t=1, pt2_N_teeth
|
|
tooth_width = tilde_cW(pt2_n_0(t+1)) - tilde_cW(pt2_n_0(t))
|
|
do i=pt2_n_0(t)+1, pt2_n_0(t+1)
|
|
pt2_w(i) = tilde_w(i) * pt2_W_T / tooth_width
|
|
end do
|
|
end do
|
|
|
|
pt2_cW(0) = 0d0
|
|
do i=1,N_det_generators
|
|
pt2_cW(i) = pt2_cW(i-1) + pt2_w(i)
|
|
end do
|
|
pt2_n_0(pt2_N_teeth+1) = N_det_generators
|
|
END_PROVIDER
|
|
|
|
|
|
|
|
|
|
|