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

thouska / spotpy / 13286948569

12 Feb 2025 01:52PM UTC coverage: 13.126% (-64.2%) from 77.332%
13286948569

Pull #330

github

web-flow
Merge 97957329c into 7e5e3f4f2
Pull Request #330: Pr 316

29 of 202 new or added lines in 7 files covered. (14.36%)

261 existing lines in 2 files now uncovered.

726 of 5531 relevant lines covered (13.13%)

0.53 hits per line

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

50.38
/src/spotpy/parameter.py
1
# -*- coding: utf-8 -*-
2
"""
3
Copyright (c) 2015 by Tobias Houska
4
This file is part of Statistical Parameter Estimation Tool (SPOTPY).
5
:author: Philipp Kraft and Tobias Houska
6
Contains classes to generate random parameter sets
7
"""
8
import copy
4✔
9
import sys
4✔
10
from itertools import cycle
4✔
11

12
import numpy as np
4✔
13
import numpy.random as rnd
4✔
14

15

16
class _ArgumentHelper(object):
4✔
17
    """
18
    A helper to assess the arguments to the __init__ method of a parameter.
19

20
    Using constructor arguments in Python is normally trivial. However, with the spotpy parameters,
21
    we are between a rock and a hard place:
22
    On the one hand, the standard way to create a parameter should not change, and that means
23
    the first argument to the __init__ method of a parameter is the name. On the other hand
24
    the new way to define parameters as class properties in a setup class is ugly if a parameter name
25
    needs to be given. This class helps by checking keyword and arguments for their types.
26
    """
27

28
    def __init__(self, parent, *args, **kwargs):
4✔
29
        self.parent = parent
4✔
30
        self.classname = type(parent).__name__
4✔
31
        self.args = list(args)
4✔
32
        self.kwargs = kwargs.copy()
4✔
33
        self.processed_args = 0
4✔
34

35
    def name(self):
4✔
36
        """
37
        A helper method for Base.__init__.
38

39
        Looks for a name of the parameter.
40
        First it looks at args[0], if this is a string, this function assumes it is the name and the
41
        distribution arguments follow. If args[0] is not a string but a number, the function looks
42
        for a keyword argument "name" and uses that or, if it fails the name of the parameter is the
43
        empty string
44

45
        For the usage of this function look at the parameter realisations in this file, eg. Uniform
46

47
        :return: name
48
        """
49
        # Check if args[0] is string like (and exists)
50
        #if self.args and str(self.args[0]) == self.args[0]:
51
        if self.args and (str(self.args[0]) == self.args[0]):
4✔
52
        #if (self.args & isinstance(self.args[0], str)).all():
53
            name = self.args.pop(0)
×
54
            self.processed_args += 1
×
55
        # else get the name from the keywords
56
        elif "name" in self.kwargs:
4✔
UNCOV
57
            name = self.kwargs.pop("name")
×
58
            self.processed_args += 1
×
59
        # or just do not use a name
60
        else:
61
            name = ""
4✔
62

63
        return name
4✔
64

65
    def alias(self, name, target):
4✔
66
        """
67
        Moves a keyword from one name to another
68
        """
69
        if name in self.kwargs and target not in self.kwargs:
4✔
UNCOV
70
            self.kwargs[target] = self.kwargs.pop(name)
×
71

72
    def attributes(self, names, raise_for_missing=None, as_dict=False):
4✔
73
        """
74

75
        :param names:
76
        :param raise_for_missing:
77
        :return:
78
        """
79
        # A list of distribution parameters
80
        attributes = []
4✔
81
        # a list of distribution parameter names that are missing. Should be empty.
82
        missing = []
4✔
83

84
        # Loop through the parameter names to get their values from
85
        # a) the args
86
        # b) if the args are fully processed, the kwargs
87
        # c) if the args are processed and the name is not present in the kwargs, add to missing
88
        for i, pname in enumerate(names):
4✔
89
            if self.args:
4✔
90
                # First use up positional arguments for the rndargs
91
                attributes.append(self.args.pop(0))
4✔
92
                self.processed_args += 1
4✔
93
            elif pname in self.kwargs:
4✔
94
                # If no positional arguments are left, look for a keyword argument
95
                attributes.append(self.kwargs.pop(pname))
4✔
96
                self.processed_args += 1
4✔
97
            else:
98
                # Argument not found, add to missing and raise an Exception afterwards
99
                missing.append(pname)
4✔
100
                attributes.append(None)
4✔
101

102
        # If the algorithm did not find values for distribution parameters in args are kwargs, fail
103
        if missing and raise_for_missing:
4✔
UNCOV
104
            raise TypeError(
×
105
                "{T} expected values for the parameters {m}".format(
106
                    T=self.classname, m=", ".join(missing)
107
                )
108
            )
109
        # Return the name, the distribution parameter values, and a tuple of unprocessed args and kwargs
110
        if as_dict:
4✔
111
            # Creates the name / value dict with entries where the value is not None
112
            return dict((n, a) for n, a in zip(names, attributes) if a is not None)
4✔
113
        else:
114
            return attributes
4✔
115

116
    def __len__(self):
4✔
117
        return len(self.args) + len(self.kwargs)
4✔
118

119
    def get(self, argname, default=None):
4✔
120
        """
121
        Checks if argname is in kwargs, if present it is returned and removed else none.
122
        :param argname:
123
        :return:
124
        """
125
        return self.kwargs.pop(argname, default)
4✔
126

127
    def check_complete(self):
4✔
128
        """
129
        Checks if all args and kwargs have been processed.
130
        Raises TypeError if unprocessed arguments are left
131
        """
132
        total_args = len(self) + self.processed_args
4✔
133
        if len(self):
4✔
UNCOV
134
            error = "{}: {} arguments where given but only {} could be used".format(
×
135
                self.classname, total_args, self.processed_args
136
            )
UNCOV
137
            raise TypeError(error)
×
138

139

140
def _round_sig(x, sig=3):
4✔
141
    """
142
    Rounds x to sig significant digits
143
    :param x: The value to round
144
    :param sig: Number of significant digits
145
    :return: rounded value
146
    """
147
    from math import floor, log10
4✔
148

149
    # Check for zero to avoid math value error with log10(0.0)
150
    if abs(x) < 1e-12:
4✔
151
        return 0
4✔
152
    else:
153
        return round(x, sig - int(floor(log10(abs(x)))))
4✔
154

155

156
class Base(object):
4✔
157
    """
158
    This is a universal random parameter class
159
    It creates a random number (or array) drawn from specified distribution.
160

161
    How to create a concrete Parameter class:
162

163
    Let us assume, we have a random distribution function foo with the parameters a and b:
164
    ``foo(a, b, size=1000)``. Then the parameter class is coded as:
165

166
    .. code ::
167
        class Foo(Base):
168
            __rndargs__ = 'a', 'b' # A tuple of the distribution argument names
169
            def __init__(*args, **kwargs):
170
                Base.__init__(foo, *args, **kwargs)
171

172
    The Uniform parameter class is the reference implementation.
173
    """
174

175
    __rndargs__ = ()
4✔
176

177
    def __init__(self, rndfunc, rndfuncname, *args, **kwargs):
4✔
178
        """
179
        :name:     Name of the parameter
180
        :rndfunc:  Function to draw a random number,
181
                   eg. the random functions from numpy.random using the rndargs
182
        :rndargs:  tuple of the argument names for the random function
183
                   eg. for uniform: ('low', 'high'). The values for the rndargs are retrieved
184
                   from positional and keyword arguments, args and kwargs.
185
        :step:     (optional) number for step size required for some algorithms
186
                    eg. mcmc need a parameter of the variance for the next step
187
                    default is quantile(0.5) - quantile(0.4) of
188
        :optguess: (optional) number for start point of parameter
189
                default is median of rndfunc(*rndargs, size=1000)
190
                rndfunc(*rndargs, size=1000)
191
        """
192
        self.rndfunc = rndfunc
4✔
193
        self.rndfunctype = rndfuncname
4✔
194
        arghelper = _ArgumentHelper(self, *args, **kwargs)
4✔
195
        self.name = arghelper.name()
4✔
196
        arghelper.alias("default", "optguess")
4✔
197
        self.rndargs = arghelper.attributes(type(self).__rndargs__, type(self).__name__)
4✔
198

199
        if self.rndfunc:
4✔
200
            # Get the standard arguments for the parameter or create them
201
            param_args = arghelper.attributes(
4✔
202
                ["step", "optguess", "minbound", "maxbound"], as_dict=True
203
            )
204
            # Draw one sample of size 1000
205
            sample = self(size=100000)
4✔
206
            self.step = param_args.get(
4✔
207
                "step",
208
                _round_sig(np.percentile(sample, 50) - np.percentile(sample, 40)),
209
            )
210
            self.optguess = param_args.get("optguess", _round_sig(np.median(sample)))
4✔
211
            self.minbound = param_args.get("minbound", _round_sig(np.min(sample)))
4✔
212
            self.maxbound = param_args.get("maxbound", _round_sig(np.max(sample)))
4✔
213

214
        else:
215

UNCOV
216
            self.step = 0.0
×
217
            self.optguess = 0.0
×
218
            self.minbound = 0.0
×
219
            self.maxbound = 0.0
×
220

221
        self.description = arghelper.get("doc")
4✔
222

223
        self.as_int = not not arghelper.get("as_int")
4✔
224
        arghelper.check_complete()
4✔
225

226
    def __call__(self, **kwargs):
4✔
227
        """
228
        Returns a parameter realization
229
        """
230
        return self.rndfunc(*self.rndargs, **kwargs)
4✔
231

232
    def astuple(self):
4✔
233
        """
234
        Returns a tuple of a realization and the other parameter properties
235
        """
UNCOV
236
        return (
×
237
            self(),
238
            self.name,
239
            self.step,
240
            self.optguess,
241
            self.minbound,
242
            self.maxbound,
243
            self.as_int,
244
        )
245

246
    def __repr__(self):
247
        """
248
        Returns a textual representation of the parameter
249
        """
250
        return "{tname}('{p.name}', {p.rndargs})".format(
251
            tname=type(self).__name__, p=self
252
        )
253

254
    def __str__(self):
255
        """
256
        Returns the description of the parameter, if available, else repr(self)
257
        :return:
258
        """
259
        doc = vars(self).get("description")
260
        if doc:
261
            res = "{} ({})".format(doc, repr(self))
262
            return res
263
        else:
264
            return repr(self)
265

266
    def __unicode__(self):
4✔
UNCOV
267
        doc = vars(self).get("description")
×
268
        if doc:
×
269
            return "{}({})".format(str(doc), repr(self))
×
270
        else:
UNCOV
271
            return repr(self)
×
272

273

274
class Uniform(Base):
4✔
275
    """
276
    A specialization of the Base parameter for uniform distributions
277
    """
278

279
    __rndargs__ = "low", "high"
4✔
280

281
    def __init__(self, *args, **kwargs):
4✔
282
        """
283
        :name: Name of the parameter
284
        :low: lower bound of the uniform distribution
285
        :high: higher bound of the uniform distribution
286
        :step:     (optional) number for step size required for some algorithms,
287
                eg. mcmc need a parameter of the variance for the next step
288
                default is median of rndfunc(*rndargs, size=1000)
289
        :optguess: (optional) number for start point of parameter
290
                default is quantile(0.5) - quantile(0.4) of
291
                rndfunc(*rndargs, size=1000)
292
        """
293
        super(Uniform, self).__init__(rnd.uniform, "Uniform", *args, **kwargs)
4✔
294

295

296
class List(Base):
4✔
297
    """
298
    A specialization to sample from a list (or other iterable) of parameter sets.
299

300
    Usage:
301
    list_param = List([1,2,3,4], repeat=True)
302
    list_param()
303
    1
304
    """
305

306
    __rndargs__ = ("values",)
4✔
307

308
    def __init__(self, *args, **kwargs):
4✔
309
        self.repeat = kwargs.pop("repeat", False)
4✔
310
        super(List, self).__init__(None, "List", *args, **kwargs)
4✔
UNCOV
311
        (self.values,) = self.rndargs
×
312

313
        # Hack to avoid skipping the first value. See __call__ function below.
UNCOV
314
        self.throwaway_first = True
×
315

UNCOV
316
        if self.repeat:
×
317
            # If the parameter list should repeated, create an inifinite loop of the data iterator
UNCOV
318
            self.iterator = cycle(self.values)
×
319
        else:
320
            # If the list should end when the list is exhausted, just get a normal iterator
UNCOV
321
            self.iterator = iter(self.values)
×
322

323
    def __call__(self, size=None):
4✔
324
        """
325
        Returns the next value from the data list
326
        :param size: Number of sample to draw from data
327
        :return:
328
        """
329
        # Hack to avoid skipping the first value of the parameter list.
330
        # This function is called once when the _algorithm __init__
331
        # has to initialize the parameter names. Because of this, we end up
332
        # losing the first value in the list, which is undesirable
333
        # This check makes sure that the first call results in a dummy value
UNCOV
334
        if self.throwaway_first:
×
335
            self.throwaway_first = False
×
336
            return None
×
337

UNCOV
338
        if size:
×
339
            return np.fromiter(self.iterator, dtype=float, count=size)
×
340
        else:
UNCOV
341
            try:
×
342
                return next(self.iterator)
×
343
            except StopIteration:
×
344
                text = "Number of repetitions is higher than the number of available parameter sets"
×
345
                raise IndexError(text)
×
346

347
    def astuple(self):
4✔
UNCOV
348
        return self(), self.name, 0, 0, 0, 0, self.as_int
×
349

350

351
class Constant(Base):
4✔
352
    """
353
    A specialization that produces always the same constant value
354
    """
355

356
    __rndargs__ = ("scalar",)
4✔
357

358
    def __init__(self, *args, **kwargs):
4✔
359
        super(Constant, self).__init__(self, "Constant", *args, **kwargs)
4✔
360

361
    value = property(lambda self: self.rndargs[0])
4✔
362

363
    def __call__(self, size=None):
4✔
364
        """
365
        Returns the next value from the data list
366
        :param size: Number of items to draw from parameter
367
        :return:
368
        """
369
        if size:
4✔
370
            return np.ones(size, dtype=float) * self.value
4✔
371
        else:
UNCOV
372
            return self.value
×
373

374
    def astuple(self):
4✔
UNCOV
375
        return self(), self.name, 0, self.value, self.value, self.value, self.as_int
×
376

377

378
class Normal(Base):
4✔
379
    """
380
    A specialization of the Base parameter for normal distributions
381
    """
382

383
    __rndargs__ = "mean", "stddev"
4✔
384

385
    def __init__(self, *args, **kwargs):
4✔
386
        """
387
        :name: Name of the parameter
388
        :mean: center of the normal distribution
389
        :stddev: variance of the normal distribution
390
        :step:     (optional) number for step size required for some algorithms,
391
                eg. mcmc need a parameter of the variance for the next step
392
                default is median of rndfunc(*rndargs, size=1000)
393
        :optguess: (optional) number for start point of parameter
394
                default is quantile(0.5) - quantile(0.4) of
395
                rndfunc(*rndargs, size=1000)
396
        """
397

UNCOV
398
        super(Normal, self).__init__(rnd.normal, "Normal", *args, **kwargs)
×
399

400

401
class logNormal(Base):
4✔
402
    """
403
    A specialization of the Base parameter for normal distributions
404
    """
405

406
    __rndargs__ = "mean", "sigma"
4✔
407

408
    def __init__(self, *args, **kwargs):
4✔
409
        """
410
        :name: Name of the parameter
411
        :mean: Mean value of the underlying normal distribution
412
        :sigma: Standard deviation of the underlying normal distribution >0
413
        :step:     (optional) number for step size required for some algorithms,
414
                eg. mcmc need a parameter of the variance for the next step
415
                default is median of rndfunc(*rndargs, size=1000)
416
        :optguess: (optional) number for start point of parameter
417
                default is quantile(0.5) - quantile(0.4) of
418
                rndfunc(*rndargs, size=1000)
419
        """
UNCOV
420
        super(logNormal, self).__init__(rnd.lognormal, "logNormal", *args, **kwargs)
×
421

422

423
class Chisquare(Base):
4✔
424
    """
425
    A specialization of the Base parameter for chisquare distributions
426
    """
427

428
    __rndargs__ = ("dt",)
4✔
429

430
    def __init__(self, *args, **kwargs):
4✔
431
        """
432
        :name: Name of the parameter
433
        :dt: Number of degrees of freedom.
434
        :step:     (optional) number for step size required for some algorithms,
435
                eg. mcmc need a parameter of the variance for the next step
436
                default is median of rndfunc(*rndargs, size=1000)
437
        :optguess: (optional) number for start point of parameter
438
                default is quantile(0.5) - quantile(0.4) of
439
                rndfunc(*rndargs, size=1000)
440
        """
UNCOV
441
        super(Chisquare, self).__init__(rnd.chisquare, "Chisquare", *args, **kwargs)
×
442

443

444
class Exponential(Base):
4✔
445
    """
446
    A specialization of the Base parameter for exponential distributions
447
    """
448

449
    __rndargs__ = ("scale",)
4✔
450

451
    def __init__(self, *args, **kwargs):
4✔
452
        """
453
        :name: Name of the parameter
454
        :scale: The scale parameter, \beta = 1 divided by lambda.
455
        :step:     (optional) number for step size required for some algorithms,
456
                eg. mcmc need a parameter of the variance for the next step
457
                default is median of rndfunc(*rndargs, size=1000)
458
        :optguess: (optional) number for start point of parameter
459
                default is quantile(0.5) - quantile(0.4) of
460
                rndfunc(*rndargs, size=1000)
461
        """
UNCOV
462
        super(Exponential, self).__init__(
×
463
            rnd.exponential, "Exponential", *args, **kwargs
464
        )
465

466

467
class Gamma(Base):
4✔
468
    """
469
    A specialization of the Base parameter for gamma distributions
470
    """
471

472
    __rndargs__ = "shape", "scale"
4✔
473

474
    def __init__(self, *args, **kwargs):
4✔
475
        """
476
        :name: Name of the parameter
477
        :shape: The shape of the gamma distribution.
478
        :scale: The scale of the gamme distribution
479
        :step:     (optional) number for step size required for some algorithms,
480
                eg. mcmc need a parameter of the variance for the next step
481
                default is median of rndfunc(*rndargs, size=1000)
482
        :optguess: (optional) number for start point of parameter
483
                default is quantile(0.5) - quantile(0.4) of
484
                rndfunc(*rndargs, size=1000)
485
        """
486

UNCOV
487
        super(Gamma, self).__init__(rnd.gamma, "Gamma", *args, **kwargs)
×
488

489

490
class Wald(Base):
4✔
491
    """
492
    A specialization of the Base parameter for Wald distributions
493
    """
494

495
    __rndargs__ = "mean", "scale"
4✔
496

497
    def __init__(self, *args, **kwargs):
4✔
498
        """
499
        :name: Name of the parameter
500
        :mean: Shape of the distribution.
501
        :scale: Shape of the distribution.
502
        :step:     (optional) number for step size required for some algorithms,
503
                eg. mcmc need a parameter of the variance for the next step
504
                default is median of rndfunc(*rndargs, size=1000)
505
        :optguess: (optional) number for start point of parameter
506
                default is quantile(0.5) - quantile(0.4) of
507
                rndfunc(*rndargs, size=1000)
508
        """
UNCOV
509
        super(Wald, self).__init__(rnd.wald, "Wald", *args, **kwargs)
×
510

511

512
class Weibull(Base):
4✔
513
    """
514
    A specialization of the Base parameter for Weibull distributions
515
    """
516

517
    __rndargs__ = ("a",)
4✔
518

519
    def __init__(self, *args, **kwargs):
4✔
520
        """
521
        :name: Name of the parameter
522
        :a: Shape of the distribution.
523
        :step:     (optional) number for step size required for some algorithms,
524
                eg. mcmc need a parameter of the variance for the next step
525
                default is median of rndfunc(*rndargs, size=1000)
526
        :optguess: (optional) number for start point of parameter
527
                default is quantile(0.5) - quantile(0.4) of
528
                rndfunc(*rndargs, size=1000)
529
        """
UNCOV
530
        super(Weibull, self).__init__(rnd.weibull, "Weibull", *args, **kwargs)
×
531

532

533
class Triangular(Base):
4✔
534
    """
535
    A parameter sampling from a triangular distribution
536
    """
537

538
    __rndargs__ = "left", "mode", "right"
4✔
539

540
    def __init__(self, *args, **kwargs):
4✔
541
        """
542
        :name: Name of the parameter
543
        :left: Lower limit of the parameter
544
        :mode: The value where the peak of the distribution occurs.
545
        :right: Upper limit, should be larger than `left`.
546
        :step:     (optional) number for step size required for some algorithms,
547
                eg. mcmc need a parameter of the variance for the next step
548
                default is median of rndfunc(*rndargs, size=1000)
549
        :optguess: (optional) number for start point of parameter
550
                default is quantile(0.5) - quantile(0.4) of
551
                rndfunc(*rndargs, size=1000)
552
        """
UNCOV
553
        super(Triangular, self).__init__(rnd.triangular, "Triangular", *args, **kwargs)
×
554

555

556
class ParameterSet(object):
4✔
557
    """
558
    A Pickable parameter set to use named parameters in a setup
559
    Is not created by a user directly, but in algorithm.
560
    Older versions used a namedtuple, which is not pickable.
561

562
    An instance of ParameterSet is sent to the users setup.simulate method.
563

564
    Usage:
565
     ps = ParameterSet(...)
566

567
    Update values by arguments or keyword arguments
568

569
     ps(0, 1, 2)
570
     ps(a=1, c=2)
571

572
    Assess parameter values of this parameter set
573

574
     ps[0] == ps['a'] == ps.a
575

576
    A parameter set is a sequence:
577

578
     list(ps)
579

580
    Assess the parameter set properties as arrays
581
     [ps.maxbound, ps.minbound, ps.optguess, ps.step, ps.random]
582

583

584
    """
585

586
    def __init__(self, param_info):
4✔
587
        """
588
        Creates a set of parameters from a parameter info array.
589
        To create the parameter set from a setup use either:
590
         setup = ...
591
         ps = ParameterSet(get_parameters_array(setup))
592

593
        or you can just use a function for this:
594

595
         ps = create_set(setup)
596

597
        :param param_info: A record array containing the properties of the parameters
598
               of this set.
599
        """
UNCOV
600
        self.__lookup = dict(
×
601
            ("p" + x if x.isdigit() else x, i) for i, x in enumerate(param_info["name"])
602
        )
UNCOV
603
        self.__info = param_info
×
604

605
    def __call__(self, *values, **kwargs):
4✔
606
        """
607
        Populates the values ('random') of the parameter set with new data
608
        :param values: Contains the new values or omitted.
609
                       If given, the number of values needs to match the number
610
                       of parameters
611
        :param kwargs: Can be used to set only single parameter values
612
        :return:
613
        """
UNCOV
614
        if values:
×
615
            if len(self.__info) != len(values):
×
616
                raise ValueError(
×
617
                    "Given values do are not the same length as the parameter set"
618
                )
UNCOV
619
            self.__info["random"][:] = values
×
620
        for k in kwargs:
×
621
            try:
×
622
                self.__info["random"][self.__lookup[k]] = kwargs[k]
×
623
            except KeyError:
×
624
                raise TypeError("{} is not a parameter of this set".format(k))
×
625
        return self
×
626

627
    def __len__(self):
4✔
UNCOV
628
        return len(self.__info["random"])
×
629

630
    def __iter__(self):
4✔
UNCOV
631
        return iter(self.__info["random"])
×
632

633
    def __getitem__(self, item):
4✔
634
        """
635
        Provides item access
636
         ps[0] == ps['a']
637

638
        :raises: KeyError, IndexError and TypeError
639
        """
UNCOV
640
        if type(item) is str:
×
641
            item = self.__lookup[item]
×
642
        return self.__info["random"][item]
×
643

644
    def __setitem__(self, key, value):
4✔
645
        """
646
        Provides setting of item
647
         ps[0] = 1
648
         ps['a'] = 2
649
        """
UNCOV
650
        if key in self.__lookup:
×
651
            key = self.__lookup[key]
×
652
        self.__info["random"][key] = value
×
653

654
    def __getattr__(self, item):
4✔
655
        """
656
        Provides the attribute access like
657
         print(ps.a)
658
        """
UNCOV
659
        if item.startswith("_"):
×
660
            raise AttributeError(
×
661
                "{} is not a member of this parameter set".format(item)
662
            )
UNCOV
663
        elif item in self.__lookup:
×
664
            return self.__info["random"][self.__lookup[item]]
×
665
        elif item in self.__info.dtype.names:
×
666
            return self.__info[item]
×
667
        else:
UNCOV
668
            raise AttributeError(
×
669
                "{} is not a member of this parameter set".format(item)
670
            )
671

672
    def __setattr__(self, key, value):
4✔
673
        """
674
        Provides setting of attributes
675
         ps.a = 2
676
        """
677
        # Allow normal usage
UNCOV
678
        if key.startswith("_") or key not in self.__lookup:
×
679
            return object.__setattr__(self, key, value)
×
680
        else:
UNCOV
681
            self.__info["random"][self.__lookup[key]] = value
×
682

683
    def __str__(self):
684
        return "parameters({})".format(
685
            ", ".join(
686
                "{}={:g}".format(k, self.__info["random"][i])
687
                for i, k in enumerate(self.__info["name"])
688
            )
689
        )
690

691
    def __repr__(self):
692
        return "spotpy.parameter.ParameterSet()"
693

694
    def __dir__(self):
4✔
695
        """
696
        Helps to show the field names in an interactive environment like IPython.
697
        See: http://ipython.readthedocs.io/en/stable/config/integrating.html
698

699
        :return: List of method names and fields
700
        """
UNCOV
701
        attrs = [attr for attr in vars(type(self)) if not attr.startswith("_")]
×
702
        return attrs + list(self.__info["name"]) + list(self.__info.dtype.names)
×
703

704
    def set_by_array(self, array):
4✔
UNCOV
705
        for i, a in enumerate(array):
×
706
            self.__setitem__(i, a)
×
707

708
    def copy(self):
4✔
UNCOV
709
        return ParameterSet(copy.deepcopy(self.__info))
×
710

711

712
def get_classes():
4✔
UNCOV
713
    keys = []
×
714
    current_module = sys.modules[__name__]
×
715
    for key in dir(current_module):
×
716
        if isinstance(getattr(current_module, key), type):
×
717
            keys.append(key)
×
718
    return keys
×
719

720

721
def generate(parameters):
4✔
722
    """
723
    This function generates a parameter set from a list of parameter objects. The parameter set
724
    is given as a structured array in the format the parameters function of a setup expects
725
    :parameters: A sequence of parameter objects
726
    """
UNCOV
727
    dtype = [
×
728
        ("random", "<f8"),
729
        ("name", "|U100"),
730
        ("step", "<f8"),
731
        ("optguess", "<f8"),
732
        ("minbound", "<f8"),
733
        ("maxbound", "<f8"),
734
        ("as_int", "bool"),
735
    ]
736

UNCOV
737
    return np.fromiter(
×
738
        (param.astuple() for param in parameters), dtype=dtype, count=len(parameters)
739
    )
740

741

742
def check_parameter_types(parameters, unaccepted_parameter_types):
4✔
UNCOV
743
    if unaccepted_parameter_types:
×
744
        problems = []
×
745
        for param in parameters:
×
746
            for param_type in unaccepted_parameter_types:
×
747
                if type(param) == param_type:
×
748
                    problems.append([param, param_type])
×
749

UNCOV
750
        if problems:
×
751
            problem_string = ", ".join(
×
752
                "{} is {}".format(param, param_type) for param, param_type in problems
753
            )
UNCOV
754
            raise TypeError(
×
755
                "List parameters are only accepted for Monte Carlo (MC) sampler: "
756
                + problem_string
757
            )
758

UNCOV
759
    return parameters
×
760

761

762
def get_parameters_array(setup, unaccepted_parameter_types=()):
4✔
763
    """
764
    Returns the parameter array from the setup
765
    """
766
    # Put the parameter arrays as needed here, they will be merged at the end of this
767
    # function
UNCOV
768
    param_arrays = []
×
769
    # Get parameters defined with the setup class
UNCOV
770
    setup_parameters = get_parameters_from_setup(setup)
×
771
    check_parameter_types(setup_parameters, unaccepted_parameter_types)
×
772
    param_arrays.append(
×
773
        # generate creates the array as defined in the setup API
774
        generate(setup_parameters)
775
    )
776

UNCOV
777
    if hasattr(setup, "parameters") and callable(setup.parameters):
×
778
        # parameters is a function, as defined in the setup API up to at least spotpy version 1.3.13
UNCOV
779
        param_arrays.append(setup.parameters())
×
780

781
    # Return the class and the object parameters together
UNCOV
782
    res = np.concatenate(param_arrays)
×
783
    return res
×
784

785

786
def find_constant_parameters(parameter_array):
4✔
787
    """
788
    Checks which parameters are constant
789
    :param parameter_array: Return array from parameter.get_parameter_array(setup)
790
    :return: A True / False array with the len(result) == len(parameter_array)
791
    """
UNCOV
792
    return parameter_array["maxbound"] - parameter_array["minbound"] == 0.0
×
793

794

795
def create_set(setup, valuetype="random", **kwargs):
4✔
796
    """
797
    Returns a named tuple holding parameter values, to be used with the simulation method of a setup
798

799
    This function is typically used to test models, before they are used in a sampling
800

801
    Usage:
802
     import spotpy
803
     from spotpy.examples.spot_setup_rosenbrock import spot_setup
804
     model = spot_setup()
805
     param_set = spotpy.parameter.create_set(model, x=2)
806
     result = model.simulation(param_set)
807

808
    :param setup: A spotpy compatible Model object
809
    :param valuetype: Select between 'optguess' (defaul), 'random', 'minbound' and 'maxbound'.
810
    :param kwargs: Any keywords can be used to set certain parameters to fixed values
811
    :return: namedtuple of parameter values
812
    """
813

814
    # Get the array of parameter realizations
UNCOV
815
    params = get_parameters_array(setup)
×
816

817
    # Create the namedtuple from the parameter names
UNCOV
818
    partype = ParameterSet(params)
×
819

820
    # Return the namedtuple with fitting names
UNCOV
821
    return partype(*params[valuetype], **kwargs)
×
822

823

824
def get_constant_indices(setup, unaccepted_parameter_types=(Constant,)):
4✔
825
    """
826
    Returns a list of the class defined parameters, and
827
    overwrites the names of the parameters.
828
    By defining parameters as class members, as shown below,
829
    one can omit the parameters function of the setup.
830

831
    Usage:
832
     from spotpy import parameter
833
     class SpotpySetup:
834
         # Add parameters p1 & p2 to the setup.
835
         p1 = parameter.Uniform(20, 100)
836
         p2 = parameter.Gamma(2.3)
837

838
    setup = SpotpySetup()
839
    parameters = parameter.get_parameters_from_setup(setup)
840
    """
841

842
    # Get all class variables
UNCOV
843
    cls = type(setup)
×
844
    class_variables = vars(cls).items()
×
845

UNCOV
846
    par_index = []
×
847
    i = 0
×
848
    contained_class_parameter = False
×
849
    # for i in range(len(class_variables)):
UNCOV
850
    for attrname, attrobj in class_variables:
×
851
        # Check if it is an spotpy parameter
UNCOV
852
        if isinstance(attrobj, Base):
×
853
            contained_class_parameter = True
×
854
            if isinstance(attrobj, unaccepted_parameter_types):
×
855
                par_index.append(i)
×
856
            i += 1
×
857

UNCOV
858
    if contained_class_parameter:
×
859
        return par_index
×
860

861

862
def get_parameters_from_setup(setup):
4✔
863
    """
864
    Returns a list of the class defined parameters, and
865
    overwrites the names of the parameters.
866
    By defining parameters as class members, as shown below,
867
    one can omit the parameters function of the setup.
868

869
    Usage:
870

871
     from spotpy import parameter
872
     class SpotpySetup:
873
         # Add parameters p1 & p2 to the setup.
874
         p1 = parameter.Uniform(20, 100)
875
         p2 = parameter.Gamma(2.3)
876

877
     setup = SpotpySetup()
878
     parameters = parameter.get_parameters_from_setup(setup)
879
    """
880

881
    # Get all class variables
UNCOV
882
    cls = type(setup)
×
883
    class_variables = vars(cls).items()
×
884

UNCOV
885
    parameters = []
×
886
    for attrname, attrobj in class_variables:
×
887
        # Check if it is an spotpy parameter
UNCOV
888
        if isinstance(attrobj, Base):
×
889
            #            if isinstance(attrobj, unaccepted_parameter_types):
890
            #                pass
891
            #            # Set the attribute name
892
            #            else:
UNCOV
893
            if not attrobj.name:
×
894
                attrobj.name = attrname
×
895
            # Add parameter to dict
UNCOV
896
            parameters.append(attrobj)
×
897

898
    # Get the parameters list of setup if the parameter attribute is not a method:
UNCOV
899
    if hasattr(setup, "parameters") and not callable(setup.parameters):
×
900
        # parameters is not callable, assume a list of parameter objects.
901
        # Generate the parameters array from it and append it to our list
UNCOV
902
        parameters.extend(setup.parameters)
×
903

UNCOV
904
    return parameters
×
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

© 2026 Coveralls, Inc