Source code for pyProximation.measure

from .base import Foundation


[docs] class Measure(Foundation): """ An instance of this class is a measure on a given set `supp`. The support is either + a python variable of type `set`, or + a list of tuples which represents a box in euclidean space. Initializes a measure object according to the inputs: + *dom* must be either - a list of 2-tuples - a non-empty dictionary + *w* must be a - a function if `dom` defines a region - left blank (None) if `dom` is a dictionary """ def __init__(self, dom, w=None): """ Initializes a measure object according to the inputs: +`dom` must be either - a list of 2-tuples - a non-empty dictionary + `w` must be a - a function if `dom` defines a region - left blank (None) if `dom` is a dictionary """ self.ErrorMsg = "" self.dim = 0 self.card = 0 if not self.check(dom, w): raise ValueError(self.ErrorMsg) def _call_on_point(self, func, point): try: return func(point) except TypeError as original_error: if isinstance(point, tuple): try: return func(*point) except TypeError as expanded_error: raise TypeError( "Failed to evaluate the integrand on support point %r. " "For tuple-valued support, provide a callable that accepts " "either a single tuple or one positional argument per coordinate." % (point,) ) from expanded_error raise TypeError( "Failed to evaluate the integrand on support point %r." % (point,) ) from original_error
[docs] def boxCheck(self, B): """ Checks the structure of the box *B*. Returns `True` id `B` is a list of 2-tuples, otherwise it returns `False`. """ flag = True for interval in B: flag = flag and isinstance(interval, tuple) and (len(interval) == 2) if flag: flag = flag and (interval[0] <= interval[1]) return flag
[docs] def check(self, dom, w): """ Checks the input types and their consistency, according to the *__init__* arguments. """ from numbers import Number if isinstance(dom, list): if not self.boxCheck(dom): self.ErrorMsg = "Each support interval must be a 2-tuple `(lower, upper)` with `lower <= upper`." return False self.DomType = "box" self.dim = len(dom) self.supp = dom if w is None: self.ErrorMsg = "A continuous measure requires a weight function or a numeric constant." return False if callable(w): self.weight = w elif isinstance(w, Number): self.weight = lambda *x: w else: self.ErrorMsg = "Weight must be either a callable or a numeric constant." return False elif isinstance(dom, dict): if len(dom) == 0: self.ErrorMsg = "A discrete measure cannot have an empty support." return False self.DomType = "set" self.card = len(dom) self.supp = tuple(dom.keys()) self.weight = dom else: self.ErrorMsg = "The support must be either a list of intervals or a dictionary of point masses." return False return True
[docs] def measure(self, S): """ Returns the measure of the set `S`. `S` must be a list of 2-tuples. """ m = 0 if self.DomType == "set": if not isinstance(S, (set, list, tuple)): raise TypeError("A discrete subset must be a `set`, `list`, or `tuple`.") for p in S: if p in self.supp: m += self.weight[p] else: if (not isinstance(S, list)) or (not self.boxCheck(S)): raise TypeError("A continuous subset must be a list of 2-tuples.") from scipy import integrate m = integrate.nquad(self.weight, S)[0] return m
[docs] def integral(self, f): """ Returns the integral of `f` with respect to the currwnt measure over the support. """ m = 0 if self.DomType == "set": if not isinstance(f, dict) and not callable(f): raise TypeError("For a discrete measure, the integrand must be a `dict` or a callable.") if isinstance(f, dict): for p in self.supp: if p in f: m += self.weight[p] * f[p] else: for p in self.supp: m += self.weight[p] * self._call_on_point(f, p) else: if not callable(f): raise TypeError("For a continuous measure, the integrand must be callable.") from scipy import integrate fw = lambda *x: f(*x) * self.weight(*x) m = integrate.nquad(fw, self.supp)[0] return m
[docs] def norm(self, p, f): """ Computes the norm-`p` of the `f` with respect to the current measure. """ from math import pow if p <= 0: raise ValueError("The norm order `p` must be positive.") if not callable(f): raise TypeError("The norm expects a callable integrand.") absfp = lambda *x: pow(abs(f(*x)), p) return pow(self.integral(absfp), 1. / p)
[docs] def sample(self, num): """ Samples from the support according to the measure. """ if not isinstance(num, int) or num < 1: raise ValueError("Sample size must be a positive integer.") if self.DomType == 'box': from math import ceil, pow from itertools import product import random from random import uniform SubRegions = {} NumSample = {} points = [] n = int(ceil(pow(num, (1. / self.dim)))) delta = [(r[1] - r[0]) / float(n) for r in self.supp] for o in product(range(n), repeat=self.dim): SubRegions[o] = [(self.supp[i][0] + o[i] * delta[i], self.supp[i] [0] + (o[i] + 1) * delta[i]) for i in range(self.dim)] numpnts = max(num, len(SubRegions)) muSupp = self.measure(self.supp) if muSupp <= 0: raise ValueError("Cannot sample from a continuous measure with non-positive total mass.") for o in SubRegions: NumSample[o] = int(ceil(numpnts * self.measure(SubRegions[o]) / float(muSupp))) for o in NumSample: pnts = [] while len(pnts) < NumSample[o]: v = [] for rng in SubRegions[o]: v.append(uniform(rng[0], rng[1])) v = tuple(v) if v not in pnts: pnts.append(v) points += pnts return random.sample(points, num) else: weights = [self.weight[p] for p in self.supp] if any(weight < 0 for weight in weights): raise ValueError("Cannot sample from a discrete measure with negative weights.") TotM = sum(weights) if TotM <= 0: raise ValueError("Cannot sample from a discrete measure with non-positive total mass.") from random import choices return choices(self.supp, weights=weights, k=num)