-
Notifications
You must be signed in to change notification settings - Fork 304
/
mfsfr2.py
2924 lines (2585 loc) · 127 KB
/
mfsfr2.py
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
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
__author__ = 'aleaf'
import sys
import textwrap
import os
import numpy as np
import warnings
import copy
from numpy.lib import recfunctions
from ..pakbase import Package
from ..utils import MfList
from ..utils.flopy_io import line_parse
from ..utils.recarray_utils import create_empty_recarray
from ..utils.optionblock import OptionBlock
from collections import OrderedDict
try:
import pandas as pd
except:
pd = False
class ModflowSfr2(Package):
"""
Streamflow-Routing (SFR2) Package Class
Parameters
----------
model : model object
The model object (of type :class:'flopy.modflow.mf.Modflow') to which
this package will be added.
nstrm : integer
An integer value that can be specified to be positive or negative. The absolute value of NSTRM is equal to
the number of stream reaches (finite-difference cells) that are active during the simulation and the number of
lines of data to be included in Item 2, described below. When NSTRM is specified to be a negative integer,
it is also used as a flag for changing the format of the data input, for simulating unsaturated flow beneath
streams, and (or) for simulating transient streamflow routing (for MODFLOW-2005 simulations only), depending
on the values specified for variables ISFROPT and IRTFLG, as described below. When NSTRM is negative, NSFRPAR
must be set to zero, which means that parameters cannot be specified.
By default, nstrm is set to negative.
nss : integer
An integer value equal to the number of stream segments (consisting of one or more reaches) that are used
to define the complete stream network. The value of NSS represents the number of segments that must be
defined through a combination of parameters and variables in Item 4 or variables in Item 6.
nparseg : integer
An integer value equal to (or exceeding) the number of stream-segment definitions associated with all
parameters. This number can be more than the total number of segments (NSS) in the stream network because
the same segment can be defined in multiple parameters, and because parameters can be time-varying. NPARSEG
must equal or exceed the sum of NLST x N for all parameters, where N is the greater of 1 and NUMINST;
that is, NPARSEG must equal or exceed the total number of repetitions of item 4b. This variable must be zero
when NSTRM is negative.
const : float
A real value (or conversion factor) used in calculating stream depth for stream reach. If stream depth is
not calculated using Manning's equation for any stream segment (that is, ICALC does not equal 1 or 2), then
a value of zero can be entered. If Manning's equation is used, a constant of 1.486 is used for flow units of
cubic feet per second, and a constant of 1.0 is used for units of cubic meters per second. The constant must
be multiplied by 86,400 when using time units of days in the simulation. An explanation of time units used
in MODFLOW is given by Harbaugh and others (2000, p. 10).
dleak : float
A real value equal to the tolerance level of stream depth used in computing leakage between each stream
reach and active model cell. Value is in units of length. Usually a value of 0.0001 is sufficient when units
of feet or meters are used in model.
ipakcb : integer
An integer value used as a flag for writing stream-aquifer leakage values. If ipakcb > 0, unformatted leakage
between each stream reach and corresponding model cell will be saved to the main cell-by-cell budget file whenever
when a cell-by-cell budget has been specified in Output Control (see Harbaugh and others, 2000, pages 52-55).
If ipakcb = 0, leakage values will not be printed or saved. Printing to the listing file (ipakcb < 0) is not supported.
istcsb2 : integer
An integer value used as a flag for writing to a separate formatted file all information on inflows and
outflows from each reach; on stream depth, width, and streambed conductance; and on head difference and
gradient across the streambed. If ISTCB2 > 0, then ISTCB2 also represents the unit number to which all
information for each stream reach will be saved to a separate file when a cell-by-cell budget has been
specified in Output Control. If ISTCB2 < 0, it is the unit number to which unformatted streamflow out of
each reach will be saved to a file whenever the cell-by-cell budget has been specified in Output Control.
Unformatted output will be saved to <model name>.sfq.
isfropt : integer
An integer value that defines the format of the input data and whether or not unsaturated flow is simulated
beneath streams. Values of ISFROPT are defined as follows
0 No vertical unsaturated flow beneath streams. Streambed elevations, stream slope, streambed thickness,
and streambed hydraulic conductivity are read for each stress period using variables defined in Items 6b
and 6c; the optional variables in Item 2 are not used.
1 No vertical unsaturated flow beneath streams. Streambed elevation, stream slope, streambed thickness,
and streambed hydraulic conductivity are read for each reach only once at the beginning of the simulation
using optional variables defined in Item 2; Items 6b and 6c are used to define stream width and depth for
ICALC = 0 and stream width for ICALC = 1.
2 Streambed and unsaturated-zone properties are read for each reach only once at the beginning of the
simulation using optional variables defined in Item 2; Items 6b and 6c are used to define stream width and
depth for ICALC = 0 and stream width for ICALC = 1. When using the LPF Package, saturated vertical
hydraulic conductivity for the unsaturated zone is the same as the vertical hydraulic conductivity of the
corresponding layer in LPF and input variable UHC is not read.
3 Same as 2 except saturated vertical hydraulic conductivity for the unsaturated zone (input variable UHC)
is read for each reach.
4 Streambed and unsaturated-zone properties are read for the beginning and end of each stream segment using
variables defined in Items 6b and 6c; the optional variables in Item 2 are not used. Streambed properties
can vary each stress period. When using the LPF Package, saturated vertical hydraulic conductivity for the
unsaturated zone is the same as the vertical hydraulic conductivity of the corresponding layer in LPF
and input variable UHC1 is not read.
5 Same as 4 except saturated vertical hydraulic conductivity for the unsaturated zone (input variable UHC1)
is read for each segment at the beginning of the first stress period only.
nstrail : integer
An integer value that is the number of trailing wave increments used to represent a trailing wave. Trailing
waves are used to represent a decrease in the surface infiltration rate. The value can be increased to improve
mass balance in the unsaturated zone. Values between 10 and 20 work well and result in unsaturated-zone mass
balance errors beneath streams ranging between 0.001 and 0.01 percent. Please see Smith (1983) for further
details. (default is 10; for MODFLOW-2005 simulations only when isfropt > 1)
isuzn : integer
An integer value that is the maximum number of vertical cells used to define the unsaturated zone beneath a
stream reach. If ICALC is 1 for all segments then ISUZN should be set to 1.
(default is 1; for MODFLOW-2005 simulations only when isfropt > 1)
nsfrsets : integer
An integer value that is the maximum number of different sets of trailing waves used to allocate arrays.
Arrays are allocated by multiplying NSTRAIL by NSFRSETS. A value of 30 is sufficient for problems where the
stream depth varies often. NSFRSETS does not affect model run time.
(default is 30; for MODFLOW-2005 simulations only when isfropt > 1)
irtflg : integer
An integer value that indicates whether transient streamflow routing is active. IRTFLG must be specified
if NSTRM < 0. If IRTFLG > 0, streamflow will be routed using the kinematic-wave equation (see USGS Techniques
and Methods 6-D1, p. 68-69); otherwise, IRTFLG should be specified as 0. Transient streamflow routing is only
available for MODFLOW-2005; IRTFLG can be left blank for MODFLOW-2000 simulations.
(default is 1)
numtim : integer
An integer value equal to the number of sub time steps used to route streamflow. The time step that will be
used to route streamflow will be equal to the MODFLOW time step divided by NUMTIM.
(default is 2; for MODFLOW-2005 simulations only when irtflg > 0)
weight : float
A real number equal to the time weighting factor used to calculate the change in channel storage. WEIGHT has
a value between 0.5 and 1. Please refer to equation 83 in USGS Techniques and Methods 6-D1 for further
details. (default is 0.75; for MODFLOW-2005 simulations only when irtflg > 0)
flwtol : float
A real number equal to the streamflow tolerance for convergence of the kinematic wave equation used for
transient streamflow routing. A value of 0.00003 cubic meters per second has been used successfully in test
simulations (and would need to be converted to whatever units are being used in the particular simulation).
(default is 0.0001; for MODFLOW-2005 simulations only when irtflg > 0)
reach_data : recarray
Numpy record array of length equal to nstrm, with columns for each variable entered in item 2
(see SFR package input instructions). In following flopy convention, layer, row, column and node number
(for unstructured grids) are zero-based; segment and reach are one-based.
segment_data : recarray
Numpy record array of length equal to nss, with columns for each variable entered in items 6a, 6b and 6c
(see SFR package input instructions). Segment numbers are one-based.
dataset_5 : dict of lists
Optional; will be built automatically from segment_data unless specified.
Dict of lists, with key for each stress period. Each list contains the variables [itmp, irdflag, iptflag].
(see SFR documentation for more details):
itmp : list of integers (len = NPER)
For each stress period, an integer value for reusing or reading stream segment data that can change each
stress period. If ITMP = 0 then all stream segment data are defined by Item 4 (NSFRPAR > 0; number of stream
parameters is greater than 0). If ITMP > 0, then stream segment data are not defined in Item 4 and must be
defined in Item 6 below for a number of segments equal to the value of ITMP. If ITMP < 0, then stream segment
data not defined in Item 4 will be reused from the last stress period (Item 6 is not read for the current
stress period). ITMP must be defined >= 0 for the first stress period of a simulation.
irdflag : int or list of integers (len = NPER)
For each stress period, an integer value for printing input data specified for this stress period.
If IRDFLG = 0, input data for this stress period will be printed. If IRDFLG > 0, then input data for this
stress period will not be printed.
iptflag : int or list of integers (len = NPER)
For each stress period, an integer value for printing streamflow-routing results during this stress period.
If IPTFLG = 0, or whenever the variable ICBCFL or "Save Budget" is specified in Output Control, the results
for specified time steps during this stress period will be printed. If IPTFLG > 0, then the results during
this stress period will not be printed.
extension : string
Filename extension (default is 'sfr')
unit_number : int
File unit number (default is None).
filenames : str or list of str
Filenames to use for the package and the output files. If
filenames=None the package name will be created using the model name
and package extension and the cbc output and sfr output name will be
created using the model name and .cbc the .sfr.bin/.sfr.out extensions
(for example, modflowtest.cbc, and modflowtest.sfr.bin), if ipakcbc and
istcb2 are numbers greater than zero. If a single string is passed the
package name will be set to the string and other uzf output files will
be set to the model name with the appropriate output file extensions.
To define the names for all package files (input and output) the
length of the list of strings should be 3. Default is None.
Attributes
----------
outlets : nested dictionary
Contains the outlet for each SFR segment; format is {per: {segment: outlet}}
This attribute is created by the get_outlets() method.
outsegs : dictionary of arrays
Each array is of shape nss rows x maximum of nss columns. The first column contains the SFR segments,
the second column contains the outsegs of those segments; the third column the outsegs of the outsegs,
and so on, until all outlets have been encountered, or nss is reached. The latter case indicates
circular routing. This attribute is created by the get_outlets() method.
Methods
-------
See Also
--------
Notes
-----
Parameters are not supported in FloPy.
MODFLOW-OWHM is not supported.
The Ground-Water Transport (GWT) process is not supported.
Limitations on which features are supported...
Examples
--------
>>> import flopy
>>> ml = flopy.modflow.Modflow()
>>> sfr2 = flopy.modflow.ModflowSfr2(ml, ...)
"""
_options = OrderedDict([("reachinput",
OptionBlock.simple_flag),
("transroute",
OptionBlock.simple_flag),
("tabfiles",
OptionBlock.simple_tabfile),
("lossfactor", {OptionBlock.dtype: np.bool_,
OptionBlock.nested: True,
OptionBlock.n_nested: 1,
OptionBlock.vars:
{"factor":
OptionBlock.simple_float}}),
("strhc1kh", {OptionBlock.dtype: np.bool_,
OptionBlock.nested: True,
OptionBlock.n_nested: 1,
OptionBlock.vars:
{"factorkh":
OptionBlock.simple_float}}),
("strhc1kv", {OptionBlock.dtype: np.bool_,
OptionBlock.nested: True,
OptionBlock.n_nested: 1,
OptionBlock.vars:
{"factorkv":
OptionBlock.simple_float}})])
nsfrpar = 0
heading = '# Streamflow-Routing (SFR2) file for MODFLOW, generated by Flopy'
default_value = 0.
# LENUNI = {"u": 0, "f": 1, "m": 2, "c": 3}
len_const = {1: 1.486, 2: 1.0, 3: 100.}
# {"u": 0, "s": 1, "m": 2, "h": 3, "d": 4, "y": 5}
time_const = {1: 1., 2: 60., 3: 3600., 4: 86400., 5: 31557600.}
def __init__(self, model, nstrm=-2, nss=1, nsfrpar=0, nparseg=0,
const=None, dleak=0.0001, ipakcb=None, istcb2=None,
isfropt=0,
nstrail=10, isuzn=1, nsfrsets=30, irtflg=0, numtim=2,
weight=0.75, flwtol=0.0001,
reach_data=None,
segment_data=None,
channel_geometry_data=None,
channel_flow_data=None,
dataset_5=None, irdflag=0, iptflag=0,
reachinput=False, transroute=False,
tabfiles=False, tabfiles_dict=None,
extension='sfr', unit_number=None,
filenames=None, options=None):
"""
Package constructor
"""
# set default unit number of one is not specified
if unit_number is None:
unit_number = ModflowSfr2.defaultunit()
# set filenames
if filenames is None:
filenames = [None, None, None]
elif isinstance(filenames, str):
filenames = [filenames, None, None]
elif isinstance(filenames, list):
if len(filenames) < 3:
for idx in range(len(filenames), 3):
filenames.append(None)
# update external file information with cbc output, if necessary
if ipakcb is not None:
fname = filenames[1]
model.add_output_file(ipakcb, fname=fname,
package=ModflowSfr2.ftype())
else:
ipakcb = 0
# add sfr flow output file
if istcb2 is not None:
if abs(istcb2) > 0:
binflag = False
ext = 'out'
if istcb2 < 0:
binflag = True
ext = 'bin'
fname = filenames[2]
if fname is None:
fname = model.name + '.sfr.{}'.format(ext)
model.add_output_file(abs(istcb2), fname=fname,
binflag=binflag,
package=ModflowSfr2.ftype())
else:
istcb2 = 0
# Fill namefile items
name = [ModflowSfr2.ftype()]
units = [unit_number]
extra = ['']
# set package name
fname = [filenames[0]]
# Call ancestor's init to set self.parent, extension, name and unit number
Package.__init__(self, model, extension=extension, name=name,
unit_number=units, extra=extra, filenames=fname)
self.url = 'sfr2.htm'
# Dataset 0 -----------------------------------------------------------------------
self.heading = '# {} package for '.format(self.name[0]) + \
' {}, '.format(model.version_types[model.version]) + \
'generated by Flopy.'
# Dataset 1a and 1b. -----------------------------------------------------------------------
self.reachinput = reachinput
self.transroute = transroute
self.tabfiles = tabfiles
self.tabfiles_dict = tabfiles_dict
self.numtab = 0 if not tabfiles else len(tabfiles_dict)
self.maxval = np.max([tb['numval'] for tb in
tabfiles_dict.values()]) if self.numtab > 0 else 0
if options is None:
if (reachinput, transroute, tabfiles) != (False, False, False):
options = OptionBlock("", ModflowSfr2, block=False)
self.options = options
# Dataset 1c. ----------------------------------------------------------------------
self._nstrm = np.sign(nstrm) * len(
reach_data) if reach_data is not None else nstrm # number of reaches, negative value is flag for unsat. flow beneath streams and/or transient routing
if segment_data is not None:
# segment_data is a zero-d array
if not isinstance(segment_data, dict):
if len(segment_data.shape) == 0:
segment_data = np.atleast_1d(segment_data)
nss = len(segment_data)
segment_data = {0: segment_data}
nss = len(segment_data[0])
else:
pass
# use atleast_1d for length since segment_data might be a 0D array
# this seems to be OK, because self.segment_data is produced by the constructor (never 0D)
self.nsfrpar = nsfrpar
self.nparseg = nparseg
# conversion factor used in calculating stream depth for stream reach (icalc = 1 or 2)
self._const = const if const is not None else None
self.dleak = dleak # tolerance level of stream depth used in computing leakage
self.ipakcb = ipakcb
self.istcb2 = istcb2 # flag; unit number for writing table of SFR output to text file
# if nstrm < 0
self.isfropt = isfropt # defines the format of the input data and whether or not unsaturated flow is simulated
# if isfropt > 1
self.nstrail = nstrail # number of trailing wave increments
self.isuzn = isuzn # max number of vertical cells used to define unsat. zone
self.nsfrsets = nsfrsets # max number trailing waves sets
# if nstrm < 0 (MF-2005 only)
self.irtflg = irtflg # switch for transient streamflow routing (> 0 = kinematic wave)
# if irtflg > 0
self.numtim = numtim # number of subtimesteps used for routing
self.weight = weight # time weighting factor used to calculate the change in channel storage
self.flwtol = flwtol # streamflow tolerance for convergence of the kinematic wave equation
# Dataset 2. -----------------------------------------------------------------------
self.reach_data = self.get_empty_reach_data(np.abs(self._nstrm))
if reach_data is not None:
for n in reach_data.dtype.names:
self.reach_data[n] = reach_data[n]
# assign node numbers if there are none (structured grid)
if np.diff(self.reach_data.node).max() == 0 and self.parent.has_package('DIS'):
# first make kij list
lrc = np.array(self.reach_data)[['k', 'i', 'j']].tolist()
self.reach_data['node'] = self.parent.dis.get_node(lrc)
# assign unique ID and outreach columns to each reach
self.reach_data.sort(order=['iseg', 'ireach'])
new_cols = {'reachID': np.arange(1, len(self.reach_data) + 1),
'outreach': np.zeros(len(self.reach_data))}
for k, v in new_cols.items():
if k not in self.reach_data.dtype.names:
recfunctions.append_fields(self.reach_data, names=k, data=v,
asrecarray=True)
# create a stress_period_data attribute to enable parent functions (e.g. plot)
self.stress_period_data = MfList(self, self.reach_data,
dtype=self.reach_data.dtype)
# Datasets 4 and 6. -----------------------------------------------------------------------
# list of values that indicate segments outside of the model
# (depending on how SFR package was constructed)
self.not_a_segment_values = [999999]
self.segment_data = {0: self.get_empty_segment_data(nss)}
if segment_data is not None:
for i in segment_data.keys():
nseg = len(segment_data[i])
self.segment_data[i] = self.get_empty_segment_data(nseg)
for n in segment_data[i].dtype.names:
#inds = (segment_data[i]['nseg'] -1).astype(int)
self.segment_data[i][n] = segment_data[i][n]
# compute outreaches if nseg and outseg columns have non-default values
if np.diff(self.reach_data.iseg).max() != 0 and \
np.diff(self.segment_data[0].nseg).max() != 0 \
and np.diff(self.segment_data[0].outseg).max() != 0:
if len(self.segment_data[0]) == 1:
self.segment_data[0]['nseg'] = 1
self.reach_data['iseg'] = 1
consistent_seg_numbers = len(set(self.reach_data.iseg).difference(
set(self.segment_data[0].nseg))) == 0
if not consistent_seg_numbers:
warnings.warn("Inconsistent segment numbers of reach_data and segment_data")
# first convert any not_a_segment_values to 0
for v in self.not_a_segment_values:
self.segment_data[0].outseg[
self.segment_data[0].outseg == v] = 0
self.set_outreaches()
self.channel_geometry_data = channel_geometry_data
self.channel_flow_data = channel_flow_data
# Dataset 5 -----------------------------------------------------------------------
# set by property from segment_data unless specified manually
self._dataset_5 = dataset_5
self.irdflag = irdflag
self.iptflag = iptflag
# Attributes not included in SFR package input
self.outsegs = {} # dictionary of arrays; see Attributes section of documentation
self.outlets = {} # nested dictionary of format {per: {segment: outlet}}
# -input format checks:
assert isfropt in [0, 1, 2, 3, 4, 5]
# derived attributes
self._paths = None
self.parent.add_package(self)
def __setattr__(self, key, value):
if key == "nstrm":
super(ModflowSfr2, self). \
__setattr__("_nstrm", value)
elif key == "dataset_5":
super(ModflowSfr2, self). \
__setattr__("_dataset_5", value)
elif key == "segment_data":
super(ModflowSfr2, self). \
__setattr__("segment_data", value)
self._dataset_5 = None
elif key == "const":
super(ModflowSfr2, self). \
__setattr__("_const", value)
else: # return to default behavior of pakbase
super(ModflowSfr2, self).__setattr__(key, value)
@property
def const(self):
if self._const is None:
const = self.len_const[self.parent.dis.lenuni] * \
self.time_const[self.parent.dis.itmuni]
else:
const = self._const
return const
@property
def nss(self):
# number of stream segments
return len(np.atleast_1d(self.segment_data[0]))
@property
def nstrm(self):
return np.sign(self._nstrm) * len(self.reach_data)
@property
def nper(self):
nper = self.parent.nrow_ncol_nlay_nper[-1]
nper = 1 if nper == 0 else nper # otherwise iterations from 0, nper won't run
return nper
@property
def dataset_5(self):
"""auto-update itmp so it is consistent with segment_data."""
ds5 = self._dataset_5
nss = self.nss
if ds5 is None:
irdflag = self._get_flag('irdflag')
iptflag = self._get_flag('iptflag')
ds5 = {0: [nss, irdflag[0], iptflag[0]]}
for per in range(1, self.nper):
sd = self.segment_data.get(per, None)
if sd is None:
ds5[per] = [-nss, irdflag[per], iptflag[per]]
else:
ds5[per] = [len(sd), irdflag[per], iptflag[per]]
return ds5
@property
def graph(self):
graph = dict(
zip(self.segment_data[0].nseg, self.segment_data[0].outseg))
outlets = set(graph.values()).difference(
set(graph.keys())) # including lakes
graph.update({o: 0 for o in outlets})
return graph
@property
def paths(self):
if self._paths is None:
self._set_paths()
return self._paths
# check to see if routing in segment data was changed
nseg = np.array(sorted(self._paths.keys()), dtype=int)
nseg = nseg[nseg > 0].copy()
outseg = np.array([self._paths[k][1] for k in nseg])
sd = self.segment_data[0]
if not np.array_equal(nseg, sd.nseg) or not np.array_equal(outseg,
sd.outseg):
self._set_paths()
return self._paths
@property
def df(self):
if pd:
return pd.DataFrame(self.reach_data)
else:
msg = 'ModflowSfr2.df: pandas not available'
raise ImportError(msg)
def _set_paths(self):
graph = self.graph
self._paths = {seg: find_path(graph, seg) for seg in graph.keys()}
def _get_flag(self, flagname):
"""populate values for each stress period"""
flg = self.__dict__[flagname]
flg = [flg] if np.isscalar(flg) else flg
if len(flg) < self.nper:
return flg + [flg[-1]] * (self.nper - len(flg))
return flg
@staticmethod
def get_empty_reach_data(nreaches=0, aux_names=None, structured=True,
default_value=0.):
# get an empty recarray that corresponds to dtype
dtype = ModflowSfr2.get_default_reach_dtype(structured=structured)
if aux_names is not None:
dtype = Package.add_to_dtype(dtype, aux_names, np.float32)
d = create_empty_recarray(nreaches, dtype, default_value=default_value)
d['reachID'] = np.arange(1, nreaches + 1)
return d
@staticmethod
def get_empty_segment_data(nsegments=0, aux_names=None, default_value=0.):
# get an empty recarray that corresponds to dtype
dtype = ModflowSfr2.get_default_segment_dtype()
if aux_names is not None:
dtype = Package.add_to_dtype(dtype, aux_names, np.float32)
d = create_empty_recarray(nsegments, dtype, default_value=default_value)
return d
@staticmethod
def get_default_reach_dtype(structured=True):
if structured:
# include node column for structured grids (useful for indexing)
return np.dtype([('node', np.int),
('k', np.int),
('i', np.int),
('j', np.int),
('iseg', np.int),
('ireach', np.int),
('rchlen', np.float32),
('strtop', np.float32),
('slope', np.float32),
('strthick', np.float32),
('strhc1', np.float32),
('thts', np.float32),
('thti', np.float32),
('eps', np.float32),
('uhc', np.float32),
('reachID', np.int),
('outreach', np.int)])
else:
return np.dtype([('node', np.int),
('iseg', np.int),
('ireach', np.int),
('rchlen', np.float32),
('strtop', np.float32),
('slope', np.float32),
('strthick', np.float32),
('strhc1', np.float32),
('thts', np.float32),
('thti', np.float32),
('eps', np.float32),
('uhc', np.float32),
('reachID', np.int),
('outreach', np.int)])
@staticmethod
def get_default_segment_dtype():
return np.dtype([('nseg', np.int),
('icalc', np.int),
('outseg', np.int),
('iupseg', np.int),
('iprior', np.int),
('nstrpts', np.int),
('flow', np.float32),
('runoff', np.float32),
('etsw', np.float32),
('pptsw', np.float32),
('roughch', np.float32),
('roughbk', np.float32),
('cdpth', np.float32),
('fdpth', np.float32),
('awdth', np.float32),
('bwdth', np.float32),
('hcond1', np.float32),
('thickm1', np.float32),
('elevup', np.float32),
('width1', np.float32),
('depth1', np.float32),
('thts1', np.float32),
('thti1', np.float32),
('eps1', np.float32),
('uhc1', np.float32),
('hcond2', np.float32),
('thickm2', np.float32),
('elevdn', np.float32),
('width2', np.float32),
('depth2', np.float32),
('thts2', np.float32),
('thti2', np.float32),
('eps2', np.float32),
('uhc2', np.float32)])
@staticmethod
def load(f, model, nper=None, gwt=False, nsol=1, ext_unit_dict=None):
if model.verbose:
sys.stdout.write('loading sfr2 package file...\n')
tabfiles = False
tabfiles_dict = {}
transroute = False
reachinput = False
structured = model.structured
if nper is None:
nrow, ncol, nlay, nper = model.get_nrow_ncol_nlay_nper()
nper = 1 if nper == 0 else nper # otherwise iterations from 0, nper won't run
if not hasattr(f, 'read'):
filename = f
f = open(filename, 'r')
# Item 0 -- header
while True:
line = f.readline()
if line[0] != '#':
break
options = None
if model.version == "mfnwt" and "options" in line.lower():
options = OptionBlock.load_options(f, ModflowSfr2)
else:
query = ("reachinput", "transroute", "tabfiles",
"lossfactor", "strhc1kh", "strhc1kv")
for i in query:
if i in line.lower():
options = OptionBlock(line.lower().strip(),
ModflowSfr2, block=False)
break
if options is not None:
line = f.readline()
# check for 1b in modflow-2005
if "tabfile" in line.lower():
t = line.strip().split()
options.tabfiles = True
options.numtab = int(t[1])
options.maxval = int(t[2])
line = f.readline()
# set varibles to be passed to class args
transroute = options.transroute
reachinput = options.reachinput
if isinstance(options.tabfiles, np.ndarray):
tabfiles = True
else:
tabfiles = False
numtab = options.numtab if tabfiles else 0
maxval = options.maxval if tabfiles else 0
# item 1c
nstrm, nss, nsfrpar, nparseg, const, dleak, ipakcb, istcb2, \
isfropt, nstrail, isuzn, nsfrsets, \
irtflg, numtim, weight, flwtol, option = _parse_1c(line,
reachinput=reachinput,
transroute=transroute)
# item 2
# set column names, dtypes
names = _get_item2_names(nstrm, reachinput, isfropt, structured)
dtypes = [d for d in ModflowSfr2.get_default_reach_dtype().descr
if d[0] in names]
lines = []
for i in range(abs(nstrm)):
line = f.readline()
line = line_parse(line)
ireach = tuple(map(float, line[:len(dtypes)]))
lines.append(ireach)
tmp = np.array(lines, dtype=dtypes)
# initialize full reach_data array with all possible columns
reach_data = ModflowSfr2.get_empty_reach_data(len(lines))
for n in names:
reach_data[n] = tmp[
n] # not sure if there's a way to assign multiple columns
# zero-based convention
inds = ['k', 'i', 'j'] if structured else ['node']
_markitzero(reach_data, inds)
# items 3 and 4 are skipped (parameters not supported)
# item 5
segment_data = {}
channel_geometry_data = {}
channel_flow_data = {}
dataset_5 = {}
aux_variables = {} # not sure where the auxiliary variables are supposed to go
for i in range(0, nper):
# Dataset 5
dataset_5[i] = _get_dataset(f.readline(), [1, 0, 0, 0])
itmp = dataset_5[i][0]
if itmp > 0:
# Item 6
current = ModflowSfr2.get_empty_segment_data(nsegments=itmp,
aux_names=option)
current_aux = {} # container to hold any auxiliary variables
current_6d = {} # these could also be implemented as structured arrays with a column for segment number
current_6e = {}
#print(i,icalc,nstrm,isfropt,reachinput)
for j in range(itmp):
dataset_6a = _parse_6a(f.readline(), option)
current_aux[j] = dataset_6a[-1]
dataset_6a = dataset_6a[:-1] # drop xyz
icalc = dataset_6a[1]
dataset_6b = _parse_6bc(f.readline(), icalc, nstrm, isfropt,
reachinput, per=i)
dataset_6c = _parse_6bc(f.readline(), icalc, nstrm, isfropt,
reachinput, per=i)
current[j] = dataset_6a + dataset_6b + dataset_6c
if icalc == 2:
# ATL: not sure exactly how isfropt logic functions for this
# dataset 6d description suggests that this line isn't read for isfropt > 1
# but description of icalc suggest that icalc=2 (8-point channel) can be used with any isfropt
if i == 0 or nstrm > 0 and not reachinput: # or isfropt <= 1:
dataset_6d = []
for k in range(2):
dataset_6d.append(
_get_dataset(f.readline(), [0.0] * 8))
# dataset_6d.append(list(map(float, f.readline().strip().split())))
current_6d[j + 1] = dataset_6d
if icalc == 4:
nstrpts = dataset_6a[5]
dataset_6e = []
for k in range(3):
dataset_6e.append(
_get_dataset(f.readline(), [0.0] * nstrpts))
current_6e[j + 1] = dataset_6e
segment_data[i] = current
aux_variables[j + 1] = current_aux
if len(current_6d) > 0:
channel_geometry_data[i] = current_6d
if len(current_6e) > 0:
channel_flow_data[i] = current_6e
if tabfiles and i == 0:
for j in range(numtab):
segnum, numval, iunit = map(int, f.readline().strip().split())
tabfiles_dict[segnum] = {'numval': numval, 'inuit': iunit}
else:
continue
# determine specified unit number
unitnumber = None
filenames = [None, None, None]
if ext_unit_dict is not None:
for key, value in ext_unit_dict.items():
if value.filetype == ModflowSfr2.ftype():
unitnumber = key
filenames[0] = os.path.basename(value.filename)
if ipakcb > 0:
if key == ipakcb:
filenames[1] = os.path.basename(value.filename)
model.add_pop_key_list(key)
if abs(istcb2) > 0:
if key == abs(istcb2):
filenames[2] = os.path.basename(value.filename)
model.add_pop_key_list(key)
return ModflowSfr2(model, nstrm=nstrm, nss=nss, nsfrpar=nsfrpar,
nparseg=nparseg, const=const, dleak=dleak,
ipakcb=ipakcb, istcb2=istcb2,
isfropt=isfropt, nstrail=nstrail, isuzn=isuzn,
nsfrsets=nsfrsets, irtflg=irtflg,
numtim=numtim, weight=weight, flwtol=flwtol,
reach_data=reach_data,
segment_data=segment_data,
dataset_5=dataset_5,
channel_geometry_data=channel_geometry_data,
channel_flow_data=channel_flow_data,
reachinput=reachinput, transroute=transroute,
tabfiles=tabfiles, tabfiles_dict=tabfiles_dict,
unit_number=unitnumber, filenames=filenames,
options=options)
def check(self, f=None, verbose=True, level=1):
"""
Check sfr2 package data for common errors.
Parameters
----------
f : str or file handle
String defining file name or file handle for summary file
of check method output. If a string is passed a file handle
is created. If f is None, check method does not write
results to a summary file. (default is None)
verbose : bool
Boolean flag used to determine if check method results are
written to the screen
level : int
Check method analysis level. If level=0, summary checks are
performed. If level=1, full checks are performed.
Returns
-------
None
Examples
--------
>>> import flopy
>>> m = flopy.modflow.Modflow.load('model.nam')
>>> m.sfr2.check()
"""
chk = check(self, verbose=verbose, level=level)
chk.for_nans()
chk.numbering()
chk.routing()
chk.overlapping_conductance()
chk.elevations()
chk.slope()
if f is not None:
if isinstance(f, str):
pth = os.path.join(self.parent.model_ws, f)
f = open(pth, 'w')
f.write('{}\n'.format(chk.txt))
# f.close()
return chk
def assign_layers(self, adjust_botms=False, pad=1.):
"""Assigns the appropriate layer for each SFR reach,
based on cell bottoms at location of reach.
Parameters
----------
adjust_botms : bool
Streambed bottom elevations below the model bottom
will cause an error in MODFLOW. If True, adjust
bottom elevations in lowest layer of the model
so they are at least pad distance below any co-located
streambed elevations.
pad : scalar
Minimum distance below streambed bottom to set
any conflicting model bottom elevations.
Notes
-----
Streambed bottom = strtop - strthick
This routine updates the elevations in the botm array
of the flopy.model.ModflowDis instance. To produce a
new DIS package file, model.write() or flopy.model.ModflowDis.write()
must be run.
"""
streambotms = self.reach_data.strtop - self.reach_data.strthick
i, j = self.reach_data.i, self.reach_data.j
layers = self.parent.dis.get_layer(i, j, streambotms)
# check against model bottom
logfile = 'sfr_botm_conflicts.chk'
logtxt = ''
mbotms = self.parent.dis.botm.array[-1, i, j]
below = streambotms <= mbotms
below_i = self.reach_data.i[below]
below_j = self.reach_data.j[below]
l = []
header = ''
if np.any(below):
print('Warning: SFR streambed elevations below model bottom. '
'See sfr_botm_conflicts.chk')
if not adjust_botms:
l += [below_i,
below_j,
mbotms[below],
streambotms[below]]
header += 'i,j,model_botm,streambed_botm'
else:
print('Fixing elevation conflicts...')
botm = self.parent.dis.botm.array.copy()
for ib, jb in zip(below_i, below_j):
inds = (self.reach_data.i == ib) & (
self.reach_data.j == jb)
botm[-1, ib, jb] = streambotms[inds].min() - pad
# l.append(botm[-1, ib, jb])
# botm[-1, below_i, below_j] = streambotms[below] - pad
l.append(botm[-1, below_i, below_j])
header += ',new_model_botm'
self.parent.dis.botm = botm
mbotms = self.parent.dis.botm.array[-1, i, j]
assert not np.any(streambotms <= mbotms)
print('New bottom array assigned to Flopy DIS package '
'instance.\nRun flopy.model.write() or '
'flopy.model.ModflowDis.write() to write new DIS file.')
header += '\n'
with open(logfile, 'w') as log:
log.write(header)
a = np.array(l).transpose()
for line in a:
log.write(','.join(map(str, line)) + '\n')
self.reach_data['k'] = layers
def deactivate_ibound_above(self):
"""Sets ibound to 0 for all cells above active SFR cells.
Parameters
----------
none
Notes
-----
This routine updates the ibound array of the flopy.model.ModflowBas6 instance. To produce a
new BAS6 package file, model.write() or flopy.model.ModflowBas6.write()
must be run.
"""
ib = self.parent.bas6.ibound.array
deact_lays = [list(range(i)) for i in self.reach_data.k]
for ks, i, j in zip(deact_lays, self.reach_data.i, self.reach_data.j):
for k in ks:
ib[k, i, j] = 0
self.parent.bas6.ibound = ib
def get_outlets(self, level=0, verbose=True):
"""Traces all routing connections from each headwater to the outlet.
"""
txt = ''
for per in range(self.nper):
if per > 0 > self.dataset_5[per][
0]: # skip stress periods where seg data not defined
continue
segments = self.segment_data[per].nseg
outsegs = self.segment_data[per].outseg
'''
all_outsegs = np.vstack([segments, outsegs])
max_outseg = all_outsegs[-1].max()
knt = 1
while max_outseg > 0:
nextlevel = np.array([outsegs[s - 1] if s > 0 and s < 999999 else 0
for s in all_outsegs[-1]])
all_outsegs = np.vstack([all_outsegs, nextlevel])
max_outseg = nextlevel.max()
if max_outseg == 0:
break
knt += 1
if knt > self.nss:
# subset outsegs map to only include rows with outseg number > 0 in last column
circular_segs = all_outsegs.T[all_outsegs[-1] > 0]