GCC Code Coverage Report


Directory: src/athena/
File: athena_kipf_msgpass_layer.f90
Date: 2025-12-10 07:37:07
Exec Total Coverage
Lines: 0 0 100.0%
Functions: 0 0 -%
Branches: 0 0 -%

Line Branch Exec Source
1 module athena__kipf_msgpass_layer
2 !! Module implementing Kipf & Welling Graph Convolutional Network (GCN)
3 !!
4 !! This module implements the graph convolutional layer from Kipf & Welling
5 !! (2017) with symmetric degree normalisation for semi-supervised learning.
6 !!
7 !! Mathematical operation:
8 !! \[ H^{(l+1)} = \sigma\left( \tilde{D}^{-1/2} \tilde{A} \tilde{D}^{-1/2} H^{(l)} W^{(l)} \right) \]
9 !!
10 !! where:
11 !! * \( \tilde{A} = A + I \) (adjacency matrix with added self-loops)
12 !! * \( \tilde{D} \) is the degree matrix of \( \tilde{A} \)
13 !! * \( H^{(l)} \) is the node feature matrix at layer l
14 !! * \( W^{(l)} \) is a learnable weight matrix
15 !! * \( \sigma \) is the activation function
16 !!
17 !! The normalisation \( \tilde{D}^{-1/2} \tilde{A} \tilde{D}^{-1/2} \) ensures
18 !! proper scaling by degree.
19 !! Preserves graph structure, producing node-level (not graph-level) outputs.
20 !!
21 !! Reference: Kipf & Welling (2017), ICLR
22 use coreutils, only: real32, stop_program
23 use graphstruc, only: graph_type
24 use athena__misc_types, only: base_actv_type, base_init_type
25 use diffstruc, only: array_type
26 use athena__base_layer, only: base_layer_type
27 use athena__msgpass_layer, only: msgpass_layer_type
28 use athena__diffstruc_extd, only: kipf_propagate, kipf_update
29 use diffstruc, only: matmul
30 implicit none
31
32
33 private
34
35 public :: kipf_msgpass_layer_type
36 public :: read_kipf_msgpass_layer
37
38
39 !-------------------------------------------------------------------------------
40 ! Message passing layer
41 !-------------------------------------------------------------------------------
42 type, extends(msgpass_layer_type) :: kipf_msgpass_layer_type
43
44 ! this is for chen 2021 et al
45 ! type(array2d_type), dimension(:), allocatable :: edge_weight
46 ! !! Weights for the edges
47 ! type(array2d_type), dimension(:), allocatable :: vertex_weight
48 ! !! Weights for the vertices
49
50 contains
51 procedure, pass(this) :: get_num_params => get_num_params_kipf
52 !! Get the number of parameters for the message passing layer
53 procedure, pass(this) :: set_hyperparams => set_hyperparams_kipf
54 !! Set the hyperparameters for the message passing layer
55 procedure, pass(this) :: init => init_kipf
56 !! Initialise the message passing layer
57 procedure, pass(this) :: print_to_unit => print_to_unit_kipf
58 !! Print the message passing layer
59 procedure, pass(this) :: read => read_kipf
60 !! Read the message passing layer
61
62 procedure, pass(this) :: update_message => update_message_kipf
63 !! Update the message
64
65 procedure, pass(this) :: update_readout => update_readout_kipf
66 !! Update the readout
67 end type kipf_msgpass_layer_type
68
69 ! Interface for setting up the MPNN layer
70 !-----------------------------------------------------------------------------
71 interface kipf_msgpass_layer_type
72 !! Interface for setting up the MPNN layer
73 module function layer_setup( &
74 num_vertex_features, num_time_steps, &
75 activation, &
76 kernel_initialiser, &
77 verbose &
78 ) result(layer)
79 !! Set up the message passing layer
80 integer, dimension(:), intent(in) :: num_vertex_features
81 !! Number of features
82 integer, intent(in) :: num_time_steps
83 !! Number of time steps
84 class(*), optional, intent(in) :: activation, kernel_initialiser
85 !! Activation function and kernel initialiser
86 integer, optional, intent(in) :: verbose
87 !! Verbosity level
88 type(kipf_msgpass_layer_type) :: layer
89 !! Instance of the message passing layer
90 end function layer_setup
91 end interface kipf_msgpass_layer_type
92
93 contains
94
95
96 !##############################################################################!
97 ! * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * !
98 !##############################################################################!
99
100
101 !###############################################################################
102 pure function get_num_params_kipf(this) result(num_params)
103 !! Get the number of parameters for the message passing layer
104 !!
105 !! This function calculates the number of parameters for the message passing
106 !! layer.
107 !! This procedure is based on code from the neural-fortran library
108 implicit none
109
110 ! Arguments
111 class(kipf_msgpass_layer_type), intent(in) :: this
112 !! Instance of the message passing layer
113 integer :: num_params
114 !! Number of parameters
115
116 ! Local variables
117 integer :: t
118 !! Loop index
119
120 num_params = 0
121 do t = 1, this%num_time_steps
122 num_params = num_params + &
123 this%num_vertex_features(t-1) * this%num_vertex_features(t)
124 end do
125
126 end function get_num_params_kipf
127 !###############################################################################
128
129
130 !##############################################################################!
131 ! * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * !
132 !##############################################################################!
133
134
135 !###############################################################################
136 module function layer_setup( &
137 num_vertex_features, num_time_steps, &
138 activation, &
139 kernel_initialiser, &
140 verbose &
141 ) result(layer)
142 !! Set up the message passing layer
143 use athena__activation, only: activation_setup
144 use athena__initialiser, only: initialiser_setup
145 implicit none
146
147 ! Arguments
148 integer, dimension(:), intent(in) :: num_vertex_features
149 !! Number of features
150 integer, intent(in) :: num_time_steps
151 !! Number of time steps
152 class(*), optional, intent(in) :: activation, kernel_initialiser
153 !! Activation function and kernel initialiser
154 integer, optional, intent(in) :: verbose
155 !! Verbosity level
156 type(kipf_msgpass_layer_type) :: layer
157 !! Instance of the message passing layer
158
159 ! Local variables
160 integer :: verbose_ = 0
161 !! Verbosity level
162 class(base_actv_type), allocatable :: activation_
163 !! Activation function object
164 class(base_init_type), allocatable :: kernel_initialiser_
165 !! Kernel initialisers
166
167 if(present(verbose)) verbose_ = verbose
168
169
170 !---------------------------------------------------------------------------
171 ! Set activation functions based on input name
172 !---------------------------------------------------------------------------
173 if(present(activation))then
174 activation_ = activation_setup(activation)
175 else
176 activation_ = activation_setup("none")
177 end if
178
179
180 !---------------------------------------------------------------------------
181 ! Define weights (kernels) and biases initialisers
182 !---------------------------------------------------------------------------
183 if(present(kernel_initialiser))then
184 kernel_initialiser_ = initialiser_setup(kernel_initialiser)
185 end if
186
187
188 !---------------------------------------------------------------------------
189 ! Set hyperparameters
190 !---------------------------------------------------------------------------
191 call layer%set_hyperparams( &
192 num_vertex_features = num_vertex_features, &
193 num_time_steps = num_time_steps, &
194 activation = activation_, &
195 kernel_initialiser = kernel_initialiser_, &
196 verbose = verbose_ &
197 )
198
199
200 !---------------------------------------------------------------------------
201 ! Initialise layer shape
202 !---------------------------------------------------------------------------
203 call layer%init(input_shape=[layer%num_vertex_features(0), 0])
204
205 end function layer_setup
206 !###############################################################################
207
208
209 !###############################################################################
210 subroutine set_hyperparams_kipf( &
211 this, &
212 num_vertex_features, &
213 num_time_steps, &
214 activation, &
215 kernel_initialiser, &
216 verbose &
217 )
218 !! Set the hyperparameters for the message passing layer
219 use athena__activation, only: activation_setup
220 use athena__initialiser, only: get_default_initialiser, initialiser_setup
221 implicit none
222
223 ! Arguments
224 class(kipf_msgpass_layer_type), intent(inout) :: this
225 !! Instance of the message passing layer
226 integer, dimension(:), intent(in) :: num_vertex_features
227 !! Number of vertex features
228 integer, intent(in) :: num_time_steps
229 !! Number of time steps
230 class(base_actv_type), allocatable, intent(in) :: activation
231 !! Activation function
232 class(base_init_type), allocatable, intent(in) :: kernel_initialiser
233 !! Kernel initialiser
234 integer, optional, intent(in) :: verbose
235 !! Verbosity level
236
237 ! Local variables
238 integer :: t
239 !! Loop index
240 character(len=256) :: buffer
241
242
243 this%name = 'kipf'
244 this%type = 'msgp'
245 this%input_rank = 2
246 this%output_rank = 2
247 this%use_graph_output = .true.
248 this%num_time_steps = num_time_steps
249 if(allocated(this%num_vertex_features)) &
250 deallocate(this%num_vertex_features)
251 if(allocated(this%num_edge_features)) &
252 deallocate(this%num_edge_features)
253 if(size(num_vertex_features, 1) .eq. 1) then
254 allocate( &
255 this%num_vertex_features(0:num_time_steps), &
256 source = num_vertex_features(1) &
257 )
258 elseif(size(num_vertex_features, 1) .eq. num_time_steps + 1) then
259 allocate( &
260 this%num_vertex_features(0:this%num_time_steps), &
261 source = num_vertex_features &
262 )
263 else
264 call stop_program( &
265 "Error: num_vertex_features must be a scalar or a vector of length &
266 &num_time_steps + 1" &
267 )
268 end if
269 allocate( this%num_edge_features(0:this%num_time_steps), source = 0 )
270 this%use_graph_input = .true.
271 if(allocated(this%activation)) deallocate(this%activation)
272 if(.not.allocated(activation))then
273 this%activation = activation_setup("none")
274 else
275 allocate(this%activation, source=activation)
276 end if
277 if(allocated(this%kernel_init)) deallocate(this%kernel_init)
278 if(.not.allocated(kernel_initialiser))then
279 buffer = get_default_initialiser(this%activation%name)
280 this%kernel_init = initialiser_setup(buffer)
281 else
282 allocate(this%kernel_init, source=kernel_initialiser)
283 end if
284 if(present(verbose))then
285 if(abs(verbose).gt.0)then
286 write(*,'("KIPF activation function: ",A)') &
287 trim(this%activation%name)
288 write(*,'("KIPF kernel initialiser: ",A)') &
289 trim(this%kernel_init%name)
290 end if
291 end if
292 if(allocated(this%num_params_msg)) deallocate(this%num_params_msg)
293 allocate(this%num_params_msg(1:this%num_time_steps))
294 do t = 1, this%num_time_steps
295 this%num_params_msg(t) = &
296 this%num_vertex_features(t-1) * this%num_vertex_features(t)
297 end do
298 if(allocated(this%input_shape)) deallocate(this%input_shape)
299 if(allocated(this%output_shape)) deallocate(this%output_shape)
300
301 end subroutine set_hyperparams_kipf
302 !###############################################################################
303
304
305 !###############################################################################
306 subroutine init_kipf(this, input_shape, verbose)
307 !! Initialise the message passing layer
308 use athena__initialiser, only: initialiser_setup
309 implicit none
310
311 ! Arguments
312 class(kipf_msgpass_layer_type), intent(inout) :: this
313 !! Instance of the fully connected layer
314 integer, dimension(:), intent(in) :: input_shape
315 !! Input shape
316 integer, optional, intent(in) :: verbose
317 !! Verbosity level
318
319 ! Local variables
320 integer :: t
321 !! Loop index
322 integer :: verbose_ = 0
323 !! Verbosity level
324
325
326 !---------------------------------------------------------------------------
327 ! Initialise optional arguments
328 !---------------------------------------------------------------------------
329 if(present(verbose)) verbose_ = verbose
330
331
332 !---------------------------------------------------------------------------
333 ! Initialise number of inputs
334 !---------------------------------------------------------------------------
335 if(.not.allocated(this%input_shape)) call this%set_shape(input_shape)
336 this%output_shape = [this%num_vertex_features(this%num_time_steps), 0]
337 this%num_params = this%get_num_params()
338 if(allocated(this%weight_shape)) deallocate(this%weight_shape)
339 if(allocated(this%bias_shape)) deallocate(this%bias_shape)
340 allocate(this%weight_shape(2,this%num_time_steps))
341 do t = 1, this%num_time_steps
342 this%weight_shape(:,t) = &
343 [ this%num_vertex_features(t), this%num_vertex_features(t-1) ]
344 end do
345
346
347 !---------------------------------------------------------------------------
348 ! Allocate weight, weight steps (velocities), output, and activation
349 !---------------------------------------------------------------------------
350 if(allocated(this%params)) deallocate(this%params)
351 allocate(this%params(this%num_time_steps))
352 do t = 1, this%num_time_steps
353 call this%params(t)%allocate( &
354 array_shape = [ this%weight_shape(:,t), 1 ] &
355 )
356 call this%params(t)%set_requires_grad(.true.)
357 this%params(t)%is_sample_dependent = .false.
358 this%params(t)%is_temporary = .false.
359 this%params(t)%fix_pointer = .true.
360 end do
361
362
363 !---------------------------------------------------------------------------
364 ! Initialise weights (kernels)
365 !---------------------------------------------------------------------------
366 do t = 1, this%num_time_steps
367 call this%kernel_init%initialise( &
368 this%params(t)%val(:,1), &
369 fan_in = this%num_vertex_features(t-1), &
370 fan_out = this%num_vertex_features(t), &
371 spacing = [ this%num_vertex_features(t) ] &
372 )
373 end do
374
375
376 !---------------------------------------------------------------------------
377 ! Allocate arrays
378 !---------------------------------------------------------------------------
379 if(allocated(this%output)) deallocate(this%output)
380
381 end subroutine init_kipf
382 !###############################################################################
383
384
385 !##############################################################################!
386 ! * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * !
387 !##############################################################################!
388
389
390 !###############################################################################
391 subroutine print_to_unit_kipf(this, unit)
392 !! Print kipf message passing layer to unit
393 use coreutils, only: to_upper
394 implicit none
395
396 ! Arguments
397 class(kipf_msgpass_layer_type), intent(in) :: this
398 !! Instance of the message passing layer
399 integer, intent(in) :: unit
400 !! File unit
401
402 ! Local variables
403 integer :: t
404 !! Loop index
405 character(100) :: fmt
406 !! Format string
407
408
409 ! Write initial parameters
410 !---------------------------------------------------------------------------
411 write(unit,'(3X,"NUM_TIME_STEPS = ",I0)') this%num_time_steps
412 write(fmt,'("(3X,""NUM_VERTEX_FEATURES ="",",I0,"(1X,I0))")') &
413 this%num_time_steps + 1
414 write(unit,fmt) this%num_vertex_features
415
416 if(this%activation%name .ne. 'none')then
417 call this%activation%print_to_unit(unit)
418 end if
419
420
421 ! Write learned parameters
422 !---------------------------------------------------------------------------
423 write(unit,'("WEIGHTS")')
424 do t = 1, this%num_time_steps, 1
425 write(unit,'(5(E16.8E2))') this%params(t)%val
426 end do
427 write(unit,'("END WEIGHTS")')
428
429 end subroutine print_to_unit_kipf
430 !###############################################################################
431
432
433 !###############################################################################
434 subroutine read_kipf(this, unit, verbose)
435 !! Read the message passing layer
436 use athena__tools_infile, only: assign_val, assign_vec, get_val, move
437 use coreutils, only: to_lower, to_upper, icount
438 use athena__activation, only: read_activation
439 use athena__initialiser, only: initialiser_setup
440 implicit none
441
442 ! Arguments
443 class(kipf_msgpass_layer_type), intent(inout) :: this
444 !! Instance of the message passing layer
445 integer, intent(in) :: unit
446 !! Unit to read from
447 integer, optional, intent(in) :: verbose
448 !! Verbosity level
449
450 ! Local variables
451 integer :: stat
452 !! Status of read
453 integer :: verbose_ = 0
454 !! Verbosity level
455 integer :: t, j, k, c, itmp1, iline
456 !! Loop variables and temporary integer
457 integer :: num_time_steps = 0
458 !! Number of time steps
459 character(14) :: kernel_initialiser_name=''
460 !! Initialisers
461 character(20) :: activation_name=''
462 !! Activation function name
463 class(base_actv_type), allocatable :: activation
464 !! Activation function
465 class(base_init_type), allocatable :: kernel_initialiser
466 !! Initialisers
467 integer, dimension(:), allocatable :: num_vertex_features
468 !! Number of vertex and edge features
469 character(256) :: buffer, tag, err_msg
470 !! Buffer, tag, and error message
471 real(real32), allocatable, dimension(:) :: data_list
472 !! Data list
473 integer :: param_line, final_line
474 !! Parameter line number
475
476
477 ! Initialise optional arguments
478 !---------------------------------------------------------------------------
479 if(present(verbose)) verbose_ = verbose
480
481
482 ! Loop over tags in layer card
483 !---------------------------------------------------------------------------
484 iline = 0
485 param_line = 0
486 final_line = 0
487 tag_loop: do
488
489 ! Check for end of file
490 !------------------------------------------------------------------------
491 read(unit,'(A)',iostat=stat) buffer
492 if(stat.ne.0)then
493 write(err_msg,'("file encountered error (EoF?) before END ",A)') &
494 to_upper(this%name)
495 call stop_program(err_msg)
496 return
497 end if
498 if(trim(adjustl(buffer)).eq."") cycle tag_loop
499
500 ! Check for end of layer card
501 !------------------------------------------------------------------------
502 if(trim(adjustl(buffer)).eq."END "//to_upper(trim(this%name)))then
503 final_line = iline
504 backspace(unit)
505 exit tag_loop
506 end if
507 iline = iline + 1
508
509 tag=trim(adjustl(buffer))
510 if(scan(buffer,"=").ne.0) tag=trim(tag(:scan(tag,"=")-1))
511
512 ! Read parameters from file
513 !------------------------------------------------------------------------
514 select case(trim(tag))
515 case("NUM_TIME_STEPS")
516 call assign_val(buffer, num_time_steps, itmp1)
517 case("NUM_VERTEX_FEATURES")
518 itmp1 = icount(get_val(buffer))
519 allocate(num_vertex_features(itmp1), source=0)
520 call assign_vec(buffer, num_vertex_features, itmp1)
521 case("ACTIVATION")
522 iline = iline - 1
523 backspace(unit)
524 activation = read_activation(unit, iline)
525 case("KERNEL_INITIALISER", "KERNEL_INIT", "KERNEL_INITIALisER")
526 call assign_val(buffer, kernel_initialiser_name, itmp1)
527 case("WEIGHTS")
528 kernel_initialiser_name = 'zeros'
529 param_line = iline
530 case default
531 ! Don't look for "e" due to scientific notation of numbers
532 ! ... i.e. exponent (E+00)
533 if(scan(to_lower(trim(adjustl(buffer))),&
534 'abcdfghijklmnopqrstuvwxyz').eq.0)then
535 cycle tag_loop
536 elseif(tag(:3).eq.'END')then
537 cycle tag_loop
538 end if
539 write(err_msg,'("Unrecognised line in input file: ",A)') &
540 trim(adjustl(buffer))
541 call stop_program(err_msg)
542 return
543 end select
544 end do tag_loop
545 kernel_initialiser = initialiser_setup(kernel_initialiser_name)
546
547
548 ! Set hyperparameters and initialise layer
549 !---------------------------------------------------------------------------
550 if(num_time_steps.gt.0 .and. num_time_steps.ne.size(num_vertex_features,1)-1)then
551 write(err_msg,'("NUM_TIME_STEPS = ",I0," does not match length of "// &
552 &"NUM_VERTEX_FEATURES = ",I0)') num_time_steps, &
553 size(num_vertex_features,1)-1
554 call stop_program(err_msg)
555 return
556 end if
557 call this%set_hyperparams( &
558 num_time_steps = num_time_steps, &
559 num_vertex_features = num_vertex_features, &
560 activation = activation, &
561 kernel_initialiser = kernel_initialiser, &
562 verbose = verbose_ &
563 )
564 call this%init(input_shape=[this%num_vertex_features(0), 0])
565
566
567 ! Check if WEIGHTS card was found
568 !---------------------------------------------------------------------------
569 if(param_line.eq.0)then
570 write(0,*) "WARNING: WEIGHTS card in "//to_upper(trim(this%name))//" not found"
571 else
572 call move(unit, param_line - iline, iostat=stat)
573 do t = 1, this%num_time_steps
574 allocate(data_list(this%num_params_msg(t)), source=0._real32)
575 c = 1
576 k = 1
577 data_concat_loop: do while(c.le.this%num_params_msg(t))
578 read(unit,'(A)',iostat=stat) buffer
579 if(stat.ne.0) exit data_concat_loop
580 k = icount(buffer)
581 read(buffer,*,iostat=stat) (data_list(j),j=c,c+k-1)
582 c = c + k
583 end do data_concat_loop
584 this%params(t)%val(:,1) = data_list(1:this%num_params_msg(t))
585 deallocate(data_list)
586 end do
587
588 ! Check for end of weights card
589 !------------------------------------------------------------------------
590 read(unit,'(A)') buffer
591 if(trim(adjustl(buffer)).ne."END WEIGHTS")then
592 write(0,*) trim(adjustl(buffer))
593 call stop_program("END WEIGHTS not where expected")
594 return
595 end if
596 end if
597
598
599 !---------------------------------------------------------------------------
600 ! Check for end of layer card
601 !---------------------------------------------------------------------------
602 read(unit,'(A)') buffer
603 if(trim(adjustl(buffer)).ne."END "//to_upper(trim(this%name)))then
604 write(0,*) trim(adjustl(buffer))
605 write(err_msg,'("END ",A," not where expected")') to_upper(this%name)
606 call stop_program(err_msg)
607 return
608 end if
609
610 end subroutine read_kipf
611 !###############################################################################
612
613
614 !###############################################################################
615 function read_kipf_msgpass_layer(unit, verbose) result(layer)
616 !! Read kipf message passing layer from file and return layer
617 implicit none
618
619 ! Arguments
620 integer, intent(in) :: unit
621 !! Unit number
622 integer, optional, intent(in) :: verbose
623 !! Verbosity level
624 class(base_layer_type), allocatable :: layer
625 !! Instance of the message passing layer
626
627 ! Local variables
628 integer :: verbose_ = 0
629 !! Verbosity level
630
631 if(present(verbose)) verbose_ = verbose
632 allocate(layer, source = kipf_msgpass_layer_type( &
633 num_time_steps = 1, &
634 num_vertex_features = [ 0, 0 ] &
635 ))
636 call layer%read(unit, verbose=verbose_)
637
638 end function read_kipf_msgpass_layer
639 !###############################################################################
640
641
642 !##############################################################################!
643 ! * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * !
644 !##############################################################################!
645
646
647 !##############################################################################!
648 subroutine update_message_kipf(this, input)
649 !! Update the message
650 implicit none
651
652 ! Arguments
653 class(kipf_msgpass_layer_type), intent(inout), target :: this
654 !! Instance of the message passing layer
655 class(array_type), dimension(:,:), intent(in), target :: input
656 !! Input to the message passing layer
657
658 ! Local variables
659 integer :: s, t
660 !! Batch index, time step
661 type(array_type), pointer :: ptr1, ptr2, ptr3
662 !! Pointers to arrays
663
664 if(allocated(this%output))then
665 if(size(this%output,2).ne.size(input,2))then
666 deallocate(this%output)
667 allocate(this%output(1,size(input,2)))
668 end if
669 else
670 allocate(this%output(1,size(input,2)))
671 end if
672
673 do s = 1, size(input,2)
674 ptr1 => input(1,s)
675 do t = 1, this%num_time_steps
676 ptr2 => kipf_propagate( &
677 ptr1, &
678 this%graph(s)%adj_ia, this%graph(s)%adj_ja &
679 )
680
681 ! this%z(t,s) = kipf_update( &
682 ! this%message(t,s), this%params(t), this%graph(s)%adj_ia &
683 ! )
684 ptr3 => matmul( this%params(t), ptr2 )
685 ptr1 => this%activation%apply( ptr3 )
686 end do
687 call this%output(1,s)%zero_grad()
688 call this%output(1,s)%assign_and_deallocate_source(ptr1)
689 this%output(1,s)%is_temporary = .false.
690 end do
691
692 end subroutine update_message_kipf
693 !###############################################################################
694
695
696 !###############################################################################
697 subroutine update_readout_kipf(this)
698 !! Update the readout
699 implicit none
700
701 ! Arguments
702 class(kipf_msgpass_layer_type), intent(inout), target :: this
703 !! Instance of the message passing layer
704
705 ! Local variables
706 integer :: s, v
707 !! Loop indices
708
709
710 ! do s = 1, size(this%output,2)
711 ! this%output(1,s)%val = this%vertex_features(this%num_time_steps,s)%val
712 ! this%output(2,s)%val = this%edge_features(this%num_time_steps,s)%val
713 ! end do
714
715 end subroutine update_readout_kipf
716 !###############################################################################
717
718 end module athena__kipf_msgpass_layer
719