-
Notifications
You must be signed in to change notification settings - Fork 145
/
Copy pathobs_model_mod.f90
625 lines (487 loc) · 26 KB
/
obs_model_mod.f90
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
! DART software - Copyright UCAR. This open source software is provided
! by UCAR, "as is", without charge, subject to all terms of use at
! http://www.image.ucar.edu/DAReS/DART/DART_download
module obs_model_mod
use utilities_mod, only : error_handler, E_ERR, E_MSG, E_WARN, &
get_unit, file_exist, set_output
use assim_model_mod, only : get_closest_state_time_to, &
get_model_time_step, adv_1step
use state_vector_io_mod, only : read_state, write_state
use obs_sequence_mod, only : obs_sequence_type, obs_type, &
get_obs_def, init_obs, destroy_obs, get_num_copies, &
get_num_qc, get_first_obs, get_next_obs_from_key, &
get_obs_time_range
use obs_def_mod, only : obs_def_type, get_obs_def_time
use time_manager_mod, only : time_type, set_time, get_time, &
operator(/=), operator(>), operator(-), &
operator(/), operator(+), operator(<), operator(==), &
operator(<=), operator(>=)
use ensemble_manager_mod, only : get_ensemble_time, ensemble_type, map_task_to_pe, &
prepare_to_update_vars
use mpi_utilities_mod, only : my_task_id, task_sync, block_task, &
sum_across_tasks, shell_execute, my_task_id
use io_filenames_mod, only : file_info_type
implicit none
private
public :: move_ahead, advance_state, set_obs_model_trace, have_members
character(len=*), parameter :: source = 'obs_model_mod.f90'
logical :: module_initialized = .false.
integer :: print_timestamps = 0
integer :: print_trace_details = 0
logical :: debug = .false. ! set to true to get more status msgs
! Module storage for writing error messages
character(len=512) :: errstring, errstring1, errstring2
contains
!======================================================================
subroutine initialize_module
module_initialized = .true.
end subroutine initialize_module
!-------------------------------------------------------------------------
subroutine move_ahead(ens_handle, ens_size, seq, last_key_used, window_time, &
key_bounds, num_obs_in_set, curr_ens_time, next_ens_time)
! Based on the current ens time and the time of the next available
! observation, compute whether and how far the ensemble copies of the
! state need to be advanced. This returns the number of obs in the
! next assimilation window, and both the current and expected next data
! times. It NO LONGER advances the model - the calling code must call
! advance_state() itself in order to do that if next time is /= curr time.
implicit none
type(ensemble_type), intent(inout) :: ens_handle
integer, intent(in) :: ens_size
type(obs_sequence_type), intent(in) :: seq
integer, intent(in) :: last_key_used
type(time_type), intent(in) :: window_time
integer, intent(out) :: key_bounds(2), num_obs_in_set
type(time_type), intent(inout) :: curr_ens_time
type(time_type), intent(out) :: next_ens_time
type(time_type) :: next_time, time2, start_time, end_time, delta_time, ens_time
type(obs_type) :: observation
type(obs_def_type) :: obs_def
logical :: is_this_last, is_there_one, out_of_range, leaving_early
! Initialize if needed
if(.not. module_initialized) then
call initialize_module
module_initialized = .true.
endif
! Prepare values for 'early' returns
num_obs_in_set = 0
key_bounds(1:2) = -99
leaving_early = .false.
!curr_ens_time = set_time(0,0)
next_ens_time = set_time(0,0)
! If none of my copies are regular ensemble members no need to advance.
! This is true either if I have no copies or if the copy #s I have are
! all larger than the ensemble count. This does assume that ensemble
! copies are in the first ens_size global copies of ensemble and that
! global indices are monotonically increasing in each pes local copies
! (note: Violating the private boundaries on ens_handle by direct access)
! This was the original code:
! if((ens_handle%my_num_copies < 1) .or. (ens_handle%my_copies(1) > ens_size)) then
! but if the compiler tried to evaluate both halves at the same time,
! (which it's allowed to do), it got an access violation if num copies was 0.
! if this task has no ensembles to advance, return directly from here.
! the calling code will sort out syncing up in the parallel case.
if (.not. have_members(ens_handle, ens_size)) return
! if you get here, this task has at least one ensemble to try to advance;
! it is possible we are at the end of the observations and there in fact
! is no need to advance. if so, can return.
! ens_handle%my_pe 0 does the output.
! Don't want two pes outputing if task 0 also has a copy
! FIMXE Commment
if ( map_task_to_pe(ens_handle, 0) >= ens_handle%num_copies .and. &
ens_handle%my_pe == 0 .and. my_task_id() /= 0) then
call set_output(.true.)
endif
! Initialize a temporary observation type to use
! after here, must delete observation before returning.
call init_obs(observation, get_num_copies(seq), get_num_qc(seq))
! Get the next observation in the sequence that hasn't already been assimilated
if(last_key_used > 0) then
call get_next_obs_from_key(seq, last_key_used, observation, is_this_last)
! If the end of the observation sequence has been passed, return
if(is_this_last) leaving_early = .true.
else
is_there_one = get_first_obs(seq, observation)
if(.not. is_there_one) leaving_early = .true.
endif
if (leaving_early) then
! need to destroy obs here before returning
call destroy_obs(observation)
if (ens_handle%my_pe == 0 .and. my_task_id() /= 0) then
call set_output(.false.)
endif
return
endif
! Get the time of this observation
call get_obs_def(observation, obs_def)
next_time = get_obs_def_time(obs_def)
! Get the time of the ensemble, assume consistent across all ensemble copies
call get_ensemble_time(ens_handle, 1, ens_time)
! Compute the model time step and center a window around the closest time
delta_time = get_model_time_step()
! print out current window, if requested
if (print_trace_details > 0) then
if(delta_time / 2 > ens_time) then
start_time = set_time(0, 0)
else
start_time = ens_time - delta_time / 2 + set_time(1, 0)
endif
end_time = ens_time + delta_time / 2
call timechat(ens_time, 'move_ahead', .false., 'Current model data time is: ')
call timechat(start_time, 'move_ahead', .false., 'Current assimilation window starts at: ')
call timechat(next_time, 'move_ahead', .false., 'Next available observation time is: ')
call timechat(end_time, 'move_ahead', .false., 'Current assimilation window ends at: ')
!call timechat(delta_time, 'move_ahead', .false., 'Width of assimilation window is: ')
endif
! now recompute for the next window, so the code below can remain unchanged.
! Figure out what time to advance the model to.
! More control over time window use of observations would come in here
time2 = get_closest_state_time_to(ens_time, next_time)
! WATCH OUT FOR USING BOUNDARY TIME OBS TWICE; add one second to bottom time
! ALSO, avoid having a negative time for the start (for low-order models in general)
if(delta_time / 2 > time2) then
start_time = set_time(0, 0)
else
start_time = time2 - delta_time / 2 + set_time(1, 0)
endif
end_time = time2 + delta_time / 2
! Output very brief current start and end time at the message level
if (print_trace_details == 0) then
call timechat(start_time, 'move_ahead', .false., 'Next assimilation window starts at: ')
call timechat(end_time, 'move_ahead', .false., 'Next assimilation window ends at: ')
endif
! This block of code gets called either if the next obs is not in the current window,
! or if you're asking for the details of the assimilation window and obs times.
if(next_time < start_time .or. next_time > end_time .or. print_trace_details > 0) then
if (time2 /= ens_time) then
!call timechat(next_time, 'move_ahead', .false., 'Next available observation time is: ')
call timechat(time2, 'move_ahead', .false., 'Next data time should be at: ', &
'Not within current window, model will be called to advance state.')
call timechat(start_time, 'move_ahead', .false., 'Next assimilation window starts at: ')
call timechat(end_time, 'move_ahead', .false., 'Next assimilation window ends at: ')
else
if (next_time >= start_time .and. next_time <= end_time) then
call timechat(next_time, 'move_ahead', .false., 'Next available observation time is: ', &
'Within current assimilation window, model does not need advance.')
else
call timechat(next_time, 'move_ahead', .false., 'Next available observation time is: ', &
'Next obs outside current assimilation window.')
endif
endif
! if different mpi tasks have different times, the default is only process 0
! will print messages. in this case we're headed towards a fatal error and
! just trying to give the most info possible before exiting. so make all
! mpi tasks which get into this block output. in the worst case you'll get
! N sets of these messages which is messy, but probably better than having
! the case where process 0 works but some other tasks fail and you get no
! helpful info from them.
if (next_time < start_time .or. next_time > end_time) then
call set_output(.true.)
call error_handler(E_MSG, ' ', ' ')
call error_handler(E_MSG, 'move_ahead', 'Inconsistent model state/observation times. ')
if (next_time < start_time) then
call error_handler(E_MSG, 'move_ahead', &
'Next observation cannot be earlier than start of new time window')
call error_handler(E_MSG, ' ', ' ')
call timechat(next_time, 'move_ahead', .false., 'Next available observation at: ')
call timechat(start_time, 'move_ahead', .false., 'Next assimilation window starts at: ')
call timechat(ens_time, 'move_ahead', .false., 'Current model data time is: ')
errstring1 = 'If this is the start of the obs_seq file, '
errstring2 = 'can use filter namelist to set first obs or initial data time.'
else
call error_handler(E_MSG, 'move_ahead', &
'Next observation is later than end of new time window')
call error_handler(E_MSG, ' ', ' ')
call timechat(next_time, 'move_ahead', .false., 'Next available observation at: ')
call timechat(end_time, 'move_ahead', .false., 'Next assimilation window ends at: ')
call timechat(ens_time, 'move_ahead', .false., 'Current model data time is: ')
errstring1 = 'should not happen; code has miscomputed how far to advance'
errstring2 = ''
endif
call error_handler(E_MSG, ' ', ' ')
call error_handler(E_ERR, 'move_ahead', &
'Inconsistent model state/observation times, cannot continue', &
source, text2=errstring1, text3=errstring2)
endif
endif
! WANT BETTER WINDOW CONTROL, TIME INTERPOLATION, TOO.
! Get all the observations that are in the observation window - do this with the
! window times, but set the returns to the data times.
call get_obs_time_range(seq, start_time, end_time, key_bounds, num_obs_in_set, &
out_of_range, observation)
! ok, not really a time detail, but if turned off, the output is pretty much
! the same as the original.
if (print_trace_details > 0) then
write (errstring, '(A,I8,A)') 'Next assimilation window contains up to ', &
num_obs_in_set, ' observations'
call error_handler(E_MSG, 'move_ahead', errstring)
endif
curr_ens_time = ens_time
next_ens_time = time2
! Release the storage associated with the observation temp variable
call destroy_obs(observation)
if (ens_handle%my_pe == 0 .and. my_task_id() /= 0) then
call set_output(.false.)
endif
end subroutine move_ahead
!------------------------------------------------------------------------------
subroutine advance_state(ens_handle, ens_size, target_time, async, adv_ens_command, &
tasks_per_model_advance, file_info_output, file_info_input)
! Advances all ensemble copies of the state to the target_time. Note that
! there may be more than ens_size copies in the ens_handle storage. Copies other
! than the ensemble copies need not be advanced in time.
implicit none
! ens_size is the number of ensembles that are model state and need to be advanced
type(ensemble_type), intent(inout) :: ens_handle
type(time_type), intent(in) :: target_time
integer, intent(in) :: ens_size, async
character(len=*), intent(in) :: adv_ens_command
integer, intent(in) :: tasks_per_model_advance
type(file_info_type), intent(inout) :: file_info_output
type(file_info_type), intent(inout) :: file_info_input
character(len = 129), dimension(ens_handle%num_copies) :: ic_file_name, ud_file_name
character(len = 129) :: control_file_name
! WARNING: ARE THESE LENGTHS OKAY?
character(len = 129) :: system_string
type(time_type) :: time_step, ens_time
integer :: is1, is2, id1, id2, my_num_state_copies, global_ens_index
integer :: i, control_unit, rc
integer :: need_advance, any_need_advance
logical :: read_time_from_file = .true.
! Initialize if needed
if(.not. module_initialized) then
call initialize_module
module_initialized = .true.
endif
! Tasks without ensemble members do not know the ensemble time, so assume
! no advance until we can read the time on at least some tasks and confirm
! the advance is needed (which it should be).
need_advance = 0
! Determine model time_step
time_step = get_model_time_step()
! Loop through each model state and advance; Count number of copies that are state ensembles
my_num_state_copies = 0
ENSEMBLE_MEMBERS: do i = 1, ens_handle%my_num_copies
global_ens_index = ens_handle%my_copies(i)
! If global_ens_index is not one of the model state ensembles, don't need to advance
! Assumes that state ensemble copies have smallest global index and are stored
! contiguously on local processor
if(global_ens_index > ens_size) exit ENSEMBLE_MEMBERS
! No need to advance if already at target time
if(ens_handle%time(i) == target_time) exit ENSEMBLE_MEMBERS
! Check for time error
if(ens_handle%time(i) > target_time) then
call get_time(ens_handle%time(i), is1, id1)
call get_time(target_time, is2, id2)
write(errstring,*)'target time ',is2,id2,' is before model_time ',is1,id1
call error_handler(E_ERR,'advance_state', errstring, source)
endif
! Ok, this task does need to advance something.
need_advance = 1
call prepare_to_update_vars(ens_handle)
! Increment number of ensemble member copies I have.
my_num_state_copies = my_num_state_copies + 1
!------------- Block for subroutine callable adv_1step interface -----------
if(async == 0) then
do while(ens_handle%time(i) < target_time)
call adv_1step(ens_handle%vars(:, i), ens_handle%time(i))
ens_handle%time(i) = ens_handle%time(i) + time_step
end do
!-------------- End single subroutine callable adv_1step interface ---------
else
!-------------- Block for calling shell to advance model -------------------
! Can only handle up to 10000 ensemble members
if(global_ens_index >= 10000) then
write(errstring,*)'Trying to use ', ens_size,' model states -- too many.'
call error_handler(E_WARN,'advance_state',errstring,source)
call error_handler(E_ERR,'advance_state','Use less than 10000 member ensemble.', &
source)
endif
!>@todo FIXME write the netCDF restart names into the control file
! Create file names for input and output state files for this copy
! write(ic_file_name(i), '("assim_model_state_ic.", i4.4)') global_ens_index
! write(ud_file_name(i), '("assim_model_state_ud.", i4.4)') global_ens_index
! Open a restart file and write state following a target time
call write_state(ens_handle, file_info_output)
endif
!-------------- End of block for calling shell to advance model -------------
end do ENSEMBLE_MEMBERS
! Following is for async options that use shell to advance model
SHELL_ADVANCE_METHODS: if(async /= 0) then
! If no one needs advance, get out now. This is a global communication routine.
call sum_across_tasks(need_advance, any_need_advance)
if (any_need_advance == 0) then
call error_handler(E_MSG, 'advance_state', 'Model time already at requested time')
return
endif
! Get a unique name for the control file; use process id
if(my_task_id() >= 10000) call error_handler(E_ERR, 'advance_state', &
'Can only have 10000 processes', source)
write(control_file_name, '("filter_control", i5.5)') my_task_id()
if (async == 2) then
if (have_members(ens_handle, ens_size)) then
! Everyone writes the ic and ud file names to the control file
control_unit = get_unit()
open(unit = control_unit, file = trim(control_file_name))
! Write out the file names to a control file
! Also write out the global ensemble member number for the script
! if it needs to create unique filenames for its own use.
do i = 1, my_num_state_copies
write(control_unit, '(i5)') ens_handle%my_copies(i)
write(control_unit, '(a)' ) trim(ic_file_name(i)) !!@todo netCDF
write(control_unit, '(a)' ) trim(ud_file_name(i)) !!@todo netCDF
end do
close(control_unit)
! Arguments to advance model script are unique id and number of copies
write(system_string, '(i10, 1x, i10)') my_task_id(), my_num_state_copies
if (debug) write(*,*) 'iam ', my_task_id(), ' ready to execute shell command'
! Issue a system command with arguments my_task, my_num_copies, and control_file
rc = shell_execute(trim(adv_ens_command)//' '//trim(system_string)//' '//trim(control_file_name))
if (debug) write(*,*) 'iam ', my_task_id(), ' shell execute returns ', rc
if (debug) write(*,*) 'iam ', my_task_id(), ' checking existance of file ', trim(control_file_name)
! if control file is still here, the advance failed
if(file_exist(control_file_name)) then
write(errstring, "(A)")'If advance script finishes ok it removes '//trim(control_file_name)
write(errstring1,"(A)")'It still exists, so 1+ members listed in that file failed to run'
write(errstring2,"(A)")'Check the output of the model or script to find the error.'
call error_handler(E_ERR,'advance_state', errstring, source, &
text2=errstring1, text3=errstring2)
endif
endif
else if (async == 4) then
! Only task 0 writes all ens names to file.
if (my_task_id() == 0) then
! Write the ic and ud file names to the control file
control_unit = get_unit()
open(unit = control_unit, file = trim(control_file_name))
! Write out the file names to a control file
! Also write out the global ensemble member number for the script
! if it needs to create unique filenames for its own use.
do i = 1, ens_size
write(control_unit, '(i5)') i
write(control_unit, '("assim_model_state_ic.", i4.4)') i
write(control_unit, '("assim_model_state_ud.", i4.4)') i
end do
close(control_unit)
endif
! make sure all tasks have finished writing their initial condition files
! (in the previous block above) before letting proc 0 start the model advances.
call task_sync()
! PE0 tells the run script to all the model advance script here, then sleeps.
! the other tasks just sleep. TODO: this code is too intertwined with the
! run script - it assumes that the script has already created 2 fifo/pipe
! files with specific names: mkfifo filter_to_model.lock model_to_filter.lock
! eventually it should probably be yet another separate executable which
! keeps the code all together in the mpi module.
! all tasks now get here, even those which do not have ensemble members,
! so this bookkeeping is not needed, i think.
!! tell any waiting tasks that we are indeed doing a model advance
!! here and they need to block.
!call sum_across_tasks(1, i)
! block, not spin, until the model advance is done.
! the code does not resume running until the separate program 'wakeup_filter'
! calls the companion 'resume_task()'.
call block_task()
! if control file is still here, the advance failed
if(file_exist(control_file_name)) then
write(errstring, "(A)")'If advance script finishes ok it removes '//trim(control_file_name)
write(errstring1,"(A)")'It still exists, so 1+ members listed in that file failed to run'
write(errstring2,"(A)")'Check the output of the model or script to find the error.'
call error_handler(E_ERR,'advance_state', errstring, source, &
text2=errstring1, text3=errstring2)
endif
else
! Unsupported option for async error
write(errstring,*)'input.nml - async is ',async,' must be 0, 2, or 4'
call error_handler(E_ERR,'advance_state', errstring, source)
endif
! All should be done, read in the states and proceed
! NOTE: if async 2 and we add support for tasks_per_model_advance, then we need
! another sync here before trying to read in from a task which might not have been
! the one which advanced it.
call read_state(ens_handle, file_info_input, read_time_from_file, ens_time)
end if SHELL_ADVANCE_METHODS
! any process that owns members, check that they advanced ok.
if (have_members(ens_handle, ens_size)) then
do i = 1, my_num_state_copies
! Get times of all ensemble members, one by one, to catch errors
! when some advanced ok but not all even if they appeared to.
call get_ensemble_time(ens_handle, i, ens_time)
! error out if model state did not advance to when requested.
if (ens_time /= target_time) then
! make sure times print out on whatever task this is (it uses msgs)
call set_output(.true.)
call timechat(ens_time, 'advance_state', .true., 'Model advance time is now: ')
call timechat(target_time, 'advance_state', .false., 'Model advance time NOT what requested: ')
write(errstring2, *) 'in timestamp on ensemble member ', ens_handle%my_copies(i)
call error_handler(E_ERR, 'advance_state', 'Model advance complete but model time not correct', &
source, text2=errstring2)
endif
enddo
if (print_trace_details > 0) then
call error_handler(E_MSG, 'advance_state', 'Model advance complete, model time updated to requested time')
endif
endif
end subroutine advance_state
!--------------------------------------------------------------------
! subroutine wait_if_needed(async) ! REMOVED Wed Feb 17 09:17:44 MST 2016 r9776ish
!--------------------------------------------------------------------
!--------------------------------------------------------------------
subroutine timechat(a_time, label, blank, string1, string2, string3)
type(time_type), intent(in) :: a_time
character(len=*), intent(in) :: label, string1
logical, intent(in) :: blank
character(len=*), intent(in), optional :: string2, string3
! prettyprint a time, with an optional preceeding blank line, a label
! before the time, and up to 2 additional text lines afterwards.
integer :: a_day, a_sec
call get_time(a_time, a_sec, a_day)
write (errstring, '(A,A,I8,A,I6)') string1, ' day=', a_day, ' sec=', a_sec
if (blank) call error_handler(E_MSG, ' ', ' ')
call error_handler(E_MSG, label, trim(errstring))
if (present(string2)) then
call error_handler(E_MSG, label, string2)
endif
if (present(string3)) then
call error_handler(E_MSG, label, string3)
endif
end subroutine timechat
!--------------------------------------------------------------------
subroutine set_obs_model_trace(execution_level, timestamp_level)
integer, intent(in) :: execution_level
integer, intent(in) :: timestamp_level
! set module local vars from the calling code to indicate how much
! output we should generate from this code. execution level is
! intended to make it easier to figure out where in the code a crash
! is happening; timestamp level is intended to help with gross levels
! of overall performance profiling. eventually, a level of 1 will
! print out only basic info; level 2 will be more detailed.
! (right now, only > 0 prints anything and it doesn't matter how
! large the value is.)
print_trace_details = execution_level
print_timestamps = timestamp_level
end subroutine set_obs_model_trace
!--------------------------------------------------------------------
function have_members(ens_handle, ens_size)
! return true if this task contains ensemble copies.
! mean, sd, etc do not count.
!
! note this depends on how filter (or the calling code) has laid out
! the actual ensemble members vs the other state-length arrays
! carried around in the ensemble handle.
type(ensemble_type), intent(in) :: ens_handle
integer, intent(in) :: ens_size
logical :: have_members
integer :: my_first_copy
! assumes copies are stored in the lowest numbered slots.
if(ens_handle%my_num_copies >= 1) then
my_first_copy = ens_handle%my_copies(1)
else
my_first_copy = 0
endif
! if i have at least one copy, and it is a real member,
! not aux data (mean, inflation, etc), return true.
have_members = ((ens_handle%my_num_copies >= 1) .and. (my_first_copy <= ens_size))
end function have_members
!--------------------------------------------------------------------
end module obs_model_mod