• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

GeoStat-Framework / welltestpy / 10691143420

03 Sep 2024 09:48PM UTC coverage: 76.813% (+0.8%) from 76.01%
10691143420

Pull #35

github

web-flow
Merge 1ebfe99fb into 81a2299af
Pull Request #35: Bump actions/download-artifact from 2 to 4.1.7 in /.github/workflows

1928 of 2510 relevant lines covered (76.81%)

10.59 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

70.71
/src/welltestpy/data/varlib.py
1
"""welltestpy subpackage providing flow datastructures for variables."""
2
import numbers
14✔
3
from copy import deepcopy as dcopy
14✔
4

5
import numpy as np
14✔
6

7
from . import data_io
14✔
8

9
__all__ = [
14✔
10
    "Variable",
11
    "TimeVar",
12
    "HeadVar",
13
    "TemporalVar",
14
    "CoordinatesVar",
15
    "Observation",
16
    "StdyObs",
17
    "DrawdownObs",
18
    "StdyHeadObs",
19
    "TimeSeries",
20
    "Well",
21
]
22

23

24
class Variable:
14✔
25
    """Class for a variable.
26

27
    This is a class for a physical variable which is either a scalar or an
28
    array.
29

30
    It has a name, a value, a symbol, a unit and a descrition string.
31

32
    Parameters
33
    ----------
34
    name : :class:`str`
35
        Name of the Variable.
36
    value : :class:`int` or :class:`float` or :class:`numpy.ndarray`
37
        Value of the Variable.
38
    symbole : :class:`str`, optional
39
        Name of the Variable. Default: ``"x"``
40
    units : :class:`str`, optional
41
        Units of the Variable. Default: ``"-"``
42
    description : :class:`str`, optional
43
        Description of the Variable. Default: ``"no description"``
44
    """
45

46
    def __init__(
14✔
47
        self, name, value, symbol="x", units="-", description="no description"
48
    ):
49
        self.name = data_io._formstr(name)
14✔
50
        self.__value = None
14✔
51
        self.value = value
14✔
52
        self.symbol = str(symbol)
14✔
53
        self.units = str(units)
14✔
54
        self.description = str(description)
14✔
55

56
    def __call__(self, value=None):
14✔
57
        """Call a variable.
58

59
        Here you can set a new value or you can get the value of the variable.
60

61
        Parameters
62
        ----------
63
        value : :class:`int` or :class:`float` or :class:`numpy.ndarray`,
64
        optional
65
            Value of the Variable. Default: ``None``
66

67
        Returns
68
        -------
69
        value : :class:`int` or :class:`float` or :class:`numpy.ndarray`
70
            Value of the Variable.
71
        """
72
        if value is not None:
14✔
73
            self.value = value
14✔
74
        return self.value
14✔
75

76
    @property
14✔
77
    def info(self):
12✔
78
        """:class:`str`: Info about the Variable."""
79
        info = ""
×
80
        info += " Variable-name: " + str(self.name) + "\n"
×
81
        info += " -Value:        " + str(self.value) + "\n"
×
82
        info += " -Symbol:       " + str(self.symbol) + "\n"
×
83
        info += " -Units:        " + str(self.units) + "\n"
×
84
        info += " -Description:  " + str(self.description) + "\n"
×
85
        return info
×
86

87
    @property
14✔
88
    def scalar(self):
12✔
89
        """:class:`bool`: State if the variable is of scalar type."""
90
        return np.isscalar(self.__value)
14✔
91

92
    @property
14✔
93
    def label(self):
12✔
94
        """:class:`str`: String containing: ``symbol in units``."""
95
        return f"{self.symbol} in {self.units}"
14✔
96

97
    @property
14✔
98
    def value(self):
12✔
99
        """:class:`int` or :class:`float` or :class:`numpy.ndarray`: Value."""
100
        return self.__value
14✔
101

102
    @value.setter
14✔
103
    def value(self, value):
12✔
104
        if issubclass(np.asanyarray(value).dtype.type, numbers.Real):
14✔
105
            if np.ndim(np.squeeze(value)) == 0:
14✔
106
                self.__value = float(np.squeeze(value))
14✔
107
            else:
108
                self.__value = np.squeeze(np.array(value, dtype=float))
14✔
109
        elif issubclass(np.asanyarray(value).dtype.type, numbers.Integral):
×
110
            if np.ndim(np.squeeze(value)) == 0:
×
111
                self.__value = int(np.squeeze(value))
×
112
            else:
113
                self.__value = np.squeeze(np.array(value, dtype=int))
×
114
        else:
115
            raise ValueError("Variable: 'value' is neither integer nor float")
×
116

117
    def __repr__(self):
118
        """Representation."""
119
        return f"{self.name} {self.symbol}: {self.value} {self.units}"
120

121
    def __str__(self):
122
        """Representation."""
123
        return f"{self.name} {self.label}"
124

125
    def save(self, path="", name=None):
14✔
126
        """Save a variable to file.
127

128
        This writes the variable to a csv file.
129

130
        Parameters
131
        ----------
132
        path : :class:`str`, optional
133
            Path where the variable should be saved. Default: ``""``
134
        name : :class:`str`, optional
135
            Name of the file. If ``None``, the name will be generated by
136
            ``"Var_"+name``. Default: ``None``
137

138
        Notes
139
        -----
140
        The file will get the suffix ``".var"``.
141
        """
142
        return data_io.save_var(self, path, name)
14✔
143

144

145
class TimeVar(Variable):
14✔
146
    """Variable class special for time series.
147

148
    Parameters
149
    ----------
150
    value : :class:`int` or :class:`float` or :class:`numpy.ndarray`
151
        Value of the Variable.
152
    symbole : :class:`str`, optional
153
        Name of the Variable. Default: ``"t"``
154
    units : :class:`str`, optional
155
        Units of the Variable. Default: ``"s"``
156
    description : :class:`str`, optional
157
        Description of the Variable. Default: ``"time given in seconds"``
158

159
    Notes
160
    -----
161
    Here the variable should be at most 1 dimensional and the name is fix set
162
    to ``"time"``.
163
    """
164

165
    def __init__(
14✔
166
        self, value, symbol="t", units="s", description="time given in seconds"
167
    ):
168
        super().__init__("time", value, symbol, units, description)
14✔
169
        if np.ndim(self.value) > 1:
14✔
170
            raise ValueError(
×
171
                "TimeVar: 'time' should have at most one dimension"
172
            )
173

174

175
class HeadVar(Variable):
14✔
176
    """
177
    Variable class special for groundwater head.
178

179
    Parameters
180
    ----------
181
    value : :class:`int` or :class:`float` or :class:`numpy.ndarray`
182
        Value of the Variable.
183
    symbole : :class:`str`, optional
184
        Name of the Variable. Default: ``"h"``
185
    units : :class:`str`, optional
186
        Units of the Variable. Default: ``"m"``
187
    description : :class:`str`, optional
188
        Description of the Variable. Default: ``"head given in meters"``
189

190
    Notes
191
    -----
192
    Here the variable name is fix set to ``"head"``.
193
    """
194

195
    def __init__(
14✔
196
        self, value, symbol="h", units="m", description="head given in meters"
197
    ):
198
        super().__init__("head", value, symbol, units, description)
14✔
199

200

201
class TemporalVar(Variable):
14✔
202
    """
203
    Variable class for a temporal variable.
204

205
    Parameters
206
    ----------
207
    value : :class:`int` or :class:`float` or :class:`numpy.ndarray`,
208
    optional
209
        Value of the Variable. Default: ``0.0``
210
    """
211

212
    def __init__(self, value=0.0):
14✔
213
        super().__init__("temporal", value, description="temporal variable")
×
214

215

216
class CoordinatesVar(Variable):
14✔
217
    """Variable class special for coordinates.
218

219
    Parameters
220
    ----------
221
    lat : :class:`int` or :class:`float` or :class:`numpy.ndarray`
222
        Lateral values of the coordinates.
223
    lon : :class:`int` or :class:`float` or :class:`numpy.ndarray`
224
        Longitutional values of the coordinates.
225
    symbole : :class:`str`, optional
226
        Name of the Variable. Default: ``"[Lat,Lon]"``
227
    units : :class:`str`, optional
228
        Units of the Variable. Default: ``"[deg,deg]"``
229
    description : :class:`str`, optional
230
        Description of the Variable. Default: ``"Coordinates given in
231
        degree-North and degree-East"``
232

233
    Notes
234
    -----
235
    Here the variable name is fix set to ``"coordinates"``.
236

237
    ``lat`` and ``lon`` should have the same shape.
238
    """
239

240
    def __init__(
14✔
241
        self,
242
        lat,
243
        lon,
244
        symbol="[Lat,Lon]",
245
        units="[deg,deg]",
246
        description="Coordinates given in degree-North and degree-East",
247
    ):
248
        ilat = np.array(np.squeeze(lat), ndmin=1)
14✔
249
        ilon = np.array(np.squeeze(lon), ndmin=1)
14✔
250

251
        if (
14✔
252
            len(ilat.shape) != 1
253
            or len(ilon.shape) != 1
254
            or ilat.shape != ilon.shape
255
        ):
256
            raise ValueError(
×
257
                "CoordinatesVar: 'lat' and 'lon' should have "
258
                "same quantity and should be given as lists"
259
            )
260

261
        value = np.array([ilat, ilon]).T
14✔
262

263
        super().__init__("coordinates", value, symbol, units, description)
14✔
264

265

266
class Observation:
14✔
267
    """
268
    Class for a observation.
269

270
    This is a class for time-dependent observations.
271
    It has a name and a description.
272

273
    Parameters
274
    ----------
275
    name : :class:`str`
276
        Name of the Variable.
277
    observation : :class:`Variable`
278
        Name of the Variable. Default: ``"x"``
279
    time : :class:`Variable`
280
        Value of the Variable.
281
    description : :class:`str`, optional
282
        Description of the Variable. Default: ``"Observation"``
283
    """
284

285
    def __init__(
14✔
286
        self, name, observation, time=None, description="Observation"
287
    ):
288
        self.__it = None
14✔
289
        self.__itfinished = None
14✔
290
        self._time = None
14✔
291
        self._observation = None
14✔
292
        self.name = data_io._formstr(name)
14✔
293
        self.description = str(description)
14✔
294

295
        self._setobservation(observation)
14✔
296
        self._settime(time)
14✔
297
        self._checkshape()
14✔
298

299
    def __call__(self, observation=None, time=None):
14✔
300
        """Call a variable.
301

302
        Here you can set a new value or you can get the value of the variable.
303

304
        Parameters
305
        ----------
306
        observation : scalar, :class:`numpy.ndarray`, :class:`Variable`, optional
307
            New Value for observation.
308
            Default: ``"None"``
309
        time : scalar, :class:`numpy.ndarray`, :class:`Variable`, optional
310
            New Value for time.
311
            Default: ``"None"``
312

313
        Returns
314
        -------
315
        [:class:`tuple` of] :class:`int` or :class:`float`
316
        or :class:`numpy.ndarray`
317
            ``(time, observation)`` or ``observation``.
318
        """
319
        if observation is not None:
14✔
320
            self._setobservation(observation)
14✔
321
        if time is not None:
14✔
322
            self._settime(time)
14✔
323
        if observation is not None or time is not None:
14✔
324
            self._checkshape()
14✔
325
        return self.value
14✔
326

327
    def __repr__(self):
328
        """Representation."""
329
        return f"Observation '{self.name}' {self.label}"
330

331
    def __str__(self):
332
        """Representation."""
333
        return self.__repr__()
334

335
    @property
14✔
336
    def labels(self):
12✔
337
        """[:class:`tuple` of] :class:`str`: ``symbol in units``."""
338
        if self.state == "transient":
14✔
339
            return self._time.label, self._observation.label
14✔
340
        return self._observation.label
×
341

342
    @property
14✔
343
    def label(self):
12✔
344
        """[:class:`tuple` of] :class:`str`: ``symbol in units``."""
345
        return self.labels
×
346

347
    @property
14✔
348
    def info(self):
12✔
349
        """Get information about the observation.
350

351
        Here you can display information about the observation.
352
        """
353
        info = ""
×
354
        info += "Observation-name: " + str(self.name) + "\n"
×
355
        info += " -Description:    " + str(self.description) + "\n"
×
356
        info += " -Kind:           " + str(self.kind) + "\n"
×
357
        info += " -State:          " + str(self.state) + "\n"
×
358
        if self.state == "transient":
×
359
            info += " --- \n"
×
360
            info += self._time.info + "\n"
×
361
        info += " --- \n"
×
362
        info += self._observation.info + "\n"
×
363
        return info
×
364

365
    @property
14✔
366
    def value(self):
12✔
367
        """
368
        Value of the Observation.
369

370
        [:class:`tuple` of] :class:`int` or :class:`float`
371
        or :class:`numpy.ndarray`
372
        """
373
        if self.state == "transient":
14✔
374
            return self.observation, self.time
14✔
375
        return self.observation
14✔
376

377
    @property
14✔
378
    def state(self):
12✔
379
        """
380
        :class:`str`: String containing state of the observation.
381

382
        Either ``"steady"`` or ``"transient"``.
383
        """
384
        return "steady" if self._time is None else "transient"
14✔
385

386
    @property
14✔
387
    def kind(self):
12✔
388
        """:class:`str`: name of the observation variable."""
389
        return self._observation.name
×
390

391
    @property
14✔
392
    def time(self):
12✔
393
        """
394
        Time values of the observation.
395

396
        :class:`int` or :class:`float` or :class:`numpy.ndarray`
397
        """
398
        return self._time.value if self.state == "transient" else None
14✔
399

400
    @time.setter
14✔
401
    def time(self, time):
12✔
402
        self._settime(time)
×
403
        self._checkshape()
×
404

405
    @time.deleter
14✔
406
    def time(self):
12✔
407
        self._time = None
14✔
408

409
    @property
14✔
410
    def observation(self):
12✔
411
        """
412
        Observed values of the observation.
413

414
        :class:`int` or :class:`float` or :class:`numpy.ndarray`
415
        """
416
        return self._observation.value
14✔
417

418
    @observation.setter
14✔
419
    def observation(self, observation):
12✔
420
        self._setobservation(observation)
14✔
421
        self._checkshape()
14✔
422

423
    @property
14✔
424
    def units(self):
12✔
425
        """[:class:`tuple` of] :class:`str`: units of the observation."""
426
        if self.state == "steady":
×
427
            return self._observation.units
×
428
        return f"{self._time.units}, {self._observation.units}"
×
429

430
    def reshape(self):
14✔
431
        """Reshape observations to flat array."""
432
        if self.state == "transient":
×
433
            tmp = len(np.shape(self.time))
×
434
            self._settime(np.reshape(self.time, -1))
×
435
            shp = np.shape(self.time) + np.shape(self.observation)[tmp:]
×
436
            self._setobservation(np.reshape(self.observation, shp))
×
437

438
    def _settime(self, time):
14✔
439
        if isinstance(time, Variable):
14✔
440
            self._time = dcopy(time)
14✔
441
        elif time is None:
14✔
442
            self._time = None
×
443
        elif self._time is None:
14✔
444
            self._time = TimeVar(time)
×
445
        else:
446
            self._time(time)
14✔
447

448
    def _setobservation(self, observation):
14✔
449
        if isinstance(observation, Variable):
14✔
450
            self._observation = dcopy(observation)
14✔
451
        elif observation is None:
14✔
452
            self._observation = None
×
453
        else:
454
            self._observation(observation)
14✔
455

456
    def _checkshape(self):
14✔
457
        if self.state == "transient" and (
14✔
458
            np.shape(self.time)
459
            != np.shape(self.observation)[: len(np.shape(self.time))]
460
        ):
461
            raise ValueError(
×
462
                "Observation: 'observation' has a shape-mismatch with 'time'"
463
            )
464

465
    def __iter__(self):
14✔
466
        """Iterate over Observations."""
467
        if self.state == "transient":
×
468
            self.__it = np.nditer(self.time, flags=["multi_index"])
×
469
        else:
470
            self.__itfinished = False
×
471
        return self
×
472

473
    def __next__(self):
14✔
474
        """Iterate through observations."""
475
        if self.state == "transient":
×
476
            if self.__it.finished:
×
477
                raise StopIteration
×
478
            ret = (
×
479
                self.__it[0].item(),
480
                self.observation[self.__it.multi_index],
481
            )
482
            self.__it.iternext()
×
483
        else:
484
            if self.__itfinished:
×
485
                raise StopIteration
×
486
            ret = self.observation
×
487
            self.__itfinished = True
×
488
        return ret
×
489

490
    def save(self, path="", name=None):
14✔
491
        """Save an observation to file.
492

493
        This writes the observation to a csv file.
494

495
        Parameters
496
        ----------
497
        path : :class:`str`, optional
498
            Path where the variable should be saved. Default: ``""``
499
        name : :class:`str`, optional
500
            Name of the file. If ``None``, the name will be generated by
501
            ``"Obs_"+name``. Default: ``None``
502

503
        Notes
504
        -----
505
        The file will get the suffix ``".obs"``.
506
        """
507
        return data_io.save_obs(self, path, name)
14✔
508

509

510
class StdyObs(Observation):
14✔
511
    """
512
    Observation class special for steady observations.
513

514
    Parameters
515
    ----------
516
    name : :class:`str`
517
        Name of the Variable.
518
    observation : :class:`Variable`
519
        Name of the Variable. Default: ``"x"``
520
    description : :class:`str`, optional
521
        Description of the Variable. Default: ``"Steady observation"``
522
    """
523

524
    def __init__(self, name, observation, description="Steady observation"):
14✔
525
        super().__init__(name, observation, None, description)
×
526

527
    def _settime(self, time):
14✔
528
        """For steady observations, this raises a ``ValueError``."""
529
        if time is not None:
×
530
            raise ValueError("Observation: 'time' not allowed in steady-state")
×
531

532

533
class TimeSeries(Observation):
14✔
534
    """
535
    Time series observation.
536

537
    Parameters
538
    ----------
539
    name : :class:`str`
540
        Name of the Variable.
541
    values : :class:`Variable`
542
        Values of the time-series.
543
    time : :class:`Variable`
544
        Time points of the time-series.
545
    description : :class:`str`, optional
546
        Description of the Variable. Default: ``"Timeseries."``
547
    """
548

549
    def __init__(self, name, values, time, description="Timeseries."):
14✔
550
        if not isinstance(time, Variable):
×
551
            time = TimeVar(time)
×
552
        if not isinstance(values, Variable):
×
553
            values = Variable(name, values, description=description)
×
554
        super().__init__(name, values, time, description)
×
555

556

557
class DrawdownObs(Observation):
14✔
558
    """
559
    Observation class special for drawdown observations.
560

561
    Parameters
562
    ----------
563
    name : :class:`str`
564
        Name of the Variable.
565
    observation : :class:`Variable`
566
        Observation.
567
    time : :class:`Variable`
568
        Time points of observation.
569
    description : :class:`str`, optional
570
        Description of the Variable. Default: ``"Drawdown observation"``
571
    """
572

573
    def __init__(
14✔
574
        self, name, observation, time, description="Drawdown observation"
575
    ):
576
        if not isinstance(time, Variable):
14✔
577
            time = TimeVar(time)
14✔
578
        if not isinstance(observation, Variable):
14✔
579
            observation = HeadVar(observation)
14✔
580
        super().__init__(name, observation, time, description)
14✔
581

582

583
class StdyHeadObs(Observation):
14✔
584
    """
585
    Observation class special for steady drawdown observations.
586

587
    Parameters
588
    ----------
589
    name : :class:`str`
590
        Name of the Variable.
591
    observation : :class:`Variable`
592
        Observation.
593
    description : :class:`str`, optional
594
        Description of the Variable. Default: ``"Steady observation"``
595
    """
596

597
    def __init__(
14✔
598
        self,
599
        name,
600
        observation,
601
        description="Steady State Drawdown observation",
602
    ):
603
        if not isinstance(observation, Variable):
14✔
604
            observation = HeadVar(observation)
14✔
605
        super().__init__(name, observation, None, description)
14✔
606

607
    def _settime(self, time):
14✔
608
        """For steady observations, this raises a ``ValueError``."""
609
        if time is not None:
14✔
610
            raise ValueError("Observation: 'time' not allowed in steady-state")
×
611

612

613
class Well:
14✔
614
    """Class for a pumping-/observation-well.
615

616
    This is a class for a well within a aquifer-testing campaign.
617

618
    It has a name, a radius, coordinates and a depth.
619

620
    Parameters
621
    ----------
622
    name : :class:`str`
623
        Name of the Variable.
624
    radius : :class:`Variable` or :class:`float`
625
        Value of the Variable.
626
    coordinates : :class:`Variable` or :class:`numpy.ndarray`
627
        Value of the Variable.
628
    welldepth : :class:`Variable` or :class:`float`, optional
629
        Depth of the well (in saturated zone). Default: 1.0
630
    aquiferdepth : :class:`Variable` or :class:`float`, optional
631
        Aquifer depth at the well (saturated zone). Defaults to welldepth.
632
        Default: ``"None"``
633
    screensize : :class:`Variable` or :class:`float`, optional
634
        Size of the screen at the well. Defaults to 0.0.
635
        Default: ``"None"``
636

637
    Notes
638
    -----
639
    You can calculate the distance between two wells ``w1`` and ``w2`` by
640
    simply calculating the difference ``w1 - w2``.
641
    """
642

643
    def __init__(
14✔
644
        self,
645
        name,
646
        radius,
647
        coordinates,
648
        welldepth=1.0,
649
        aquiferdepth=None,
650
        screensize=None,
651
    ):
652
        self._radius = None
14✔
653
        self._coordinates = None
14✔
654
        self._welldepth = None
14✔
655
        self._aquiferdepth = None
14✔
656
        self._screensize = None
14✔
657

658
        self.name = data_io._formstr(name)
14✔
659
        self.wellradius = radius
14✔
660
        self.coordinates = coordinates
14✔
661
        self.welldepth = welldepth
14✔
662
        self.aquiferdepth = aquiferdepth
14✔
663
        self.screensize = screensize
14✔
664

665
    @property
14✔
666
    def info(self):
12✔
667
        """Get information about the variable.
668

669
        Here you can display information about the variable.
670
        """
671
        info = ""
×
672
        info += "----\n"
×
673
        info += "Well-name: " + str(self.name) + "\n"
×
674
        info += "--\n"
×
675
        info += self._radius.info + "\n"
×
676
        info += self.coordinates.info + "\n"
×
677
        info += self._welldepth.info + "\n"
×
678
        info += self._aquiferdepth.info + "\n"
×
679
        info += self._screensize.info + "\n"
×
680
        info += "----\n"
×
681
        return info
×
682

683
    @property
14✔
684
    def radius(self):
12✔
685
        """:class:`float`: Radius of the well."""
686
        return self._radius.value
14✔
687

688
    @property
14✔
689
    def wellradius(self):
12✔
690
        """:class:`Variable`: Radius variable of the well."""
691
        return self._radius
14✔
692

693
    @wellradius.setter
14✔
694
    def wellradius(self, radius):
12✔
695
        if isinstance(radius, Variable):
14✔
696
            self._radius = dcopy(radius)
14✔
697
        elif self._radius is None:
14✔
698
            self._radius = Variable(
14✔
699
                "radius",
700
                float(radius),
701
                "r",
702
                "m",
703
                f"Inner radius of well '{self.name}'",
704
            )
705
        else:
706
            self._radius(radius)
×
707
        if not self._radius.scalar:
14✔
708
            raise ValueError("Well: 'radius' needs to be scalar")
×
709
        if not self.radius > 0.0:
14✔
710
            raise ValueError("Well: 'radius' needs to be positive")
×
711

712
    @property
14✔
713
    def pos(self):
12✔
714
        """:class:`numpy.ndarray`: Position of the well."""
715
        return self._coordinates.value
14✔
716

717
    @property
14✔
718
    def coordinates(self):
12✔
719
        """:class:`Variable`: Coordinates variable of the well."""
720
        return self._coordinates
14✔
721

722
    @coordinates.setter
14✔
723
    def coordinates(self, coordinates):
12✔
724
        if isinstance(coordinates, Variable):
14✔
725
            self._coordinates = dcopy(coordinates)
14✔
726
        elif self._coordinates is None:
14✔
727
            self._coordinates = Variable(
14✔
728
                "coordinates",
729
                coordinates,
730
                "XY",
731
                "m",
732
                f"coordinates of well '{self.name}'",
733
            )
734
        else:
735
            self._coordinates(coordinates)
×
736
        if np.shape(self.pos) != (2,) and not np.isscalar(self.pos):
14✔
737
            raise ValueError(
×
738
                "Well: 'coordinates' should be given as "
739
                "[x,y] values or one single distance value"
740
            )
741

742
    @property
14✔
743
    def depth(self):
12✔
744
        """:class:`float`: Depth of the well."""
745
        return self._welldepth.value
14✔
746

747
    @property
14✔
748
    def welldepth(self):
12✔
749
        """:class:`Variable`: Depth variable of the well."""
750
        return self._welldepth
14✔
751

752
    @welldepth.setter
14✔
753
    def welldepth(self, welldepth):
12✔
754
        if isinstance(welldepth, Variable):
14✔
755
            self._welldepth = dcopy(welldepth)
14✔
756
        elif self._welldepth is None:
14✔
757
            self._welldepth = Variable(
14✔
758
                "welldepth",
759
                float(welldepth),
760
                "L_w",
761
                "m",
762
                f"depth of well '{self.name}'",
763
            )
764
        else:
765
            self._welldepth(welldepth)
×
766
        if not self._welldepth.scalar:
14✔
767
            raise ValueError("Well: 'welldepth' needs to be scalar")
×
768
        if not self.depth > 0.0:
14✔
769
            raise ValueError("Well: 'welldepth' needs to be positive")
×
770

771
    @property
14✔
772
    def aquifer(self):
12✔
773
        """:class:`float`: Aquifer depth at the well."""
774
        return self._aquiferdepth.value
14✔
775

776
    @property
14✔
777
    def aquiferdepth(self):
12✔
778
        """:class:`Variable`: Aquifer depth at the well."""
779
        return self._aquiferdepth
14✔
780

781
    @aquiferdepth.setter
14✔
782
    def aquiferdepth(self, aquiferdepth):
12✔
783
        if isinstance(aquiferdepth, Variable):
14✔
784
            self._aquiferdepth = dcopy(aquiferdepth)
14✔
785
        elif self._aquiferdepth is None:
14✔
786
            self._aquiferdepth = Variable(
14✔
787
                "aquiferdepth",
788
                self.depth if aquiferdepth is None else float(aquiferdepth),
789
                "L_a",
790
                self.welldepth.units,
791
                f"aquifer depth at well '{self.name}'",
792
            )
793
        else:
794
            self._aquiferdepth(aquiferdepth)
×
795
        if not self._aquiferdepth.scalar:
14✔
796
            raise ValueError("Well: 'aquiferdepth' needs to be scalar")
×
797
        if not self.aquifer > 0.0:
14✔
798
            raise ValueError("Well: 'aquiferdepth' needs to be positive")
×
799

800
    @property
14✔
801
    def is_piezometer(self):
12✔
802
        """:class:`bool`: Whether the well is only a standpipe piezometer."""
803
        return np.isclose(self.screen, 0)
×
804

805
    @property
14✔
806
    def screen(self):
12✔
807
        """:class:`float`: Screen size at the well."""
808
        return self._screensize.value
14✔
809

810
    @property
14✔
811
    def screensize(self):
12✔
812
        """:class:`Variable`: Screen size at the well."""
813
        return self._screensize
14✔
814

815
    @screensize.setter
14✔
816
    def screensize(self, screensize):
12✔
817
        if isinstance(screensize, Variable):
14✔
818
            self._screensize = dcopy(screensize)
14✔
819
        elif self._screensize is None:
14✔
820
            self._screensize = Variable(
14✔
821
                "screensize",
822
                0.0 if screensize is None else float(screensize),
823
                "L_s",
824
                self.welldepth.units,
825
                f"screen size at well '{self.name}'",
826
            )
827
        else:
828
            self._screensize(screensize)
×
829
        if not self._screensize.scalar:
14✔
830
            raise ValueError("Well: 'screensize' needs to be scalar")
×
831
        if self.screen < 0.0:
14✔
832
            raise ValueError("Well: 'screensize' needs to be non-negative")
×
833

834
    def distance(self, well):
14✔
835
        """Calculate distance to the well.
836

837
        Parameters
838
        ----------
839
        well : :class:`Well` or :class:`tuple` of :class:`float`
840
            Coordinates to calculate the distance to or another well.
841
        """
842
        if isinstance(well, Well):
14✔
843
            return np.linalg.norm(self.pos - well.pos)
14✔
844
        try:
×
845
            return np.linalg.norm(self.pos - well)
×
846
        except ValueError:
×
847
            raise ValueError(
×
848
                "Well: the distant-well needs to be an "
849
                "instance of Well-class "
850
                "or a tuple of x-y coordinates "
851
                "or a single distance value "
852
                "and of same coordinates-type."
853
            )
854

855
    def __repr__(self):
856
        """Representation."""
857
        return f"{self.name} r={self.radius} at {self._coordinates}"
858

859
    def __sub__(self, well):
14✔
860
        """Distance between wells."""
861
        return self.distance(well)
14✔
862

863
    def __add__(self, well):
14✔
864
        """Distance between wells."""
865
        return self.distance(well)
×
866

867
    def __and__(self, well):
14✔
868
        """Distance between wells."""
869
        return self.distance(well)
×
870

871
    def __abs__(self):
14✔
872
        """Distance to origin."""
873
        return np.linalg.norm(self.pos)
×
874

875
    def save(self, path="", name=None):
14✔
876
        """Save a well to file.
877

878
        This writes the variable to a csv file.
879

880
        Parameters
881
        ----------
882
        path : :class:`str`, optional
883
            Path where the variable should be saved. Default: ``""``
884
        name : :class:`str`, optional
885
            Name of the file. If ``None``, the name will be generated by
886
            ``"Well_"+name``. Default: ``None``
887

888
        Notes
889
        -----
890
        The file will get the suffix ``".wel"``.
891
        """
892
        return data_io.save_well(self, path, name)
14✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc