@@ -1455,33 +1455,73 @@ def cdf(self, val):
14551455 return np .clip (result , 0 , 1 )
14561456
14571457
1458- class DiscreteValues (Prior ):
1459- def __init__ (self , values , name = None , latex_label = None ,
1460- unit = None , boundary = "periodic" ):
1461- """ An equal-weighted discrete-valued prior
1458+ class WeightedDiscreteValues (Prior ):
1459+ def __init__ (
1460+ self ,
1461+ values ,
1462+ weights = None ,
1463+ name = None ,
1464+ latex_label = None ,
1465+ unit = None ,
1466+ boundary = "periodic" ,
1467+ ):
1468+ """A weighted discrete-valued prior
14621469
14631470 Parameters
14641471 ==========
1465- values: array
1472+ values: 1d array_like of numeric type
14661473 The discrete values of the prior.
1474+ weights: 1d array_like of numeric type or None
1475+ The weights of each category. If None, then all values are
1476+ equally weighted. Default: None.
14671477 name: str
1468- See superclass
1478+ The name of the parameter
14691479 latex_label: str
1470- See superclass
1480+ The latex label of the parameter. Used for plotting.
14711481 unit: str
1472- See superclass
1482+ The unit of the parameter. Used for plotting.
14731483 """
1484+
14741485 nvalues = len (values )
1486+ values = np .array (values )
1487+ if values .shape != (nvalues ,):
1488+ raise ValueError (
1489+ f"Shape of argument 'values' must be 1d array-like but has shape { values .shape } "
1490+ )
14751491 minimum = np .min (values )
14761492 # Small delta added to help with MCMC walking
14771493 maximum = np .max (values ) * (1 + 1e-15 )
1478- super (DiscreteValues , self ).__init__ (
1494+ super (WeightedDiscreteValues , self ).__init__ (
14791495 name = name , latex_label = latex_label , minimum = minimum ,
14801496 maximum = maximum , unit = unit , boundary = boundary )
14811497 self .nvalues = nvalues
1482- self .values = np .sort (np .array (values ))
1483- self .p = 1 / self .nvalues
1484- self .lnp = - np .log (self .nvalues )
1498+ sorter = np .argsort (values )
1499+ self ._values_array = values [sorter ]
1500+
1501+ # inititialization of priors from repr only supports
1502+ # python buildins
1503+ self .values = self ._values_array .tolist ()
1504+
1505+ weights = (
1506+ np .array (weights ) / np .sum (weights )
1507+ if weights is not None
1508+ else np .ones (self .nvalues ) / self .nvalues
1509+ )
1510+ # check for consistent shape of input
1511+ if weights .shape != (self .nvalues ,):
1512+ raise ValueError (
1513+ "Inconsistent shape of weights and number of values:"
1514+ + f"weights has shape { weights .shape } "
1515+ + f"while number of values is { self .values } "
1516+ )
1517+ self ._weights_array = weights [sorter ]
1518+ self .weights = self ._weights_array .tolist ()
1519+ self ._lnweights_array = np .log (self ._weights_array )
1520+
1521+ # save cdf for rescaling
1522+ _cumulative_weights_array = np .cumsum (self ._weights_array )
1523+ # insert 0 for values smaller than minimum
1524+ self ._cumulative_weights_array = np .insert (_cumulative_weights_array , 0 , 0 )
14851525
14861526 def rescale (self , val ):
14871527 """
@@ -1498,8 +1538,22 @@ def rescale(self, val):
14981538 =======
14991539 Union[float, array_like]: Rescaled probability
15001540 """
1501- idx = np .asarray (np .floor (val * self .nvalues ), dtype = int )
1502- return self .values [idx ]
1541+ index = np .searchsorted (self ._cumulative_weights_array [1 :], val )
1542+ return self ._values_array [index ]
1543+
1544+ def cdf (self , val ):
1545+ """Return the cumulative prior probability of val.
1546+
1547+ Parameters
1548+ ==========
1549+ val: Union[float, int, array_like]
1550+
1551+ Returns
1552+ =======
1553+ float: cumulative prior probability of val
1554+ """
1555+ index = np .searchsorted (self ._values_array , val , side = "right" )
1556+ return self ._cumulative_weights_array [index ]
15031557
15041558 def prob (self , val ):
15051559 """Return the prior probability of val.
@@ -1512,17 +1566,11 @@ def prob(self, val):
15121566 =======
15131567 float: Prior probability of val
15141568 """
1515- if isinstance (val , (float , int )):
1516- if val in self .values :
1517- return self .p
1518- else :
1519- return 0
1520- else :
1521- val = np .atleast_1d (val )
1522- probs = np .zeros_like (val , dtype = np .float64 )
1523- idxs = np .isin (val , self .values )
1524- probs [idxs ] = self .p
1525- return probs
1569+ index = np .searchsorted (self ._values_array , val )
1570+ index = np .clip (index , 0 , self .nvalues - 1 )
1571+ p = np .where (self ._values_array [index ] == val , self ._weights_array [index ], 0 )
1572+ # turn 0d numpy array to scalar
1573+ return p [()]
15261574
15271575 def ln_prob (self , val ):
15281576 """Return the logarithmic prior probability of val
@@ -1536,24 +1584,86 @@ def ln_prob(self, val):
15361584 float:
15371585
15381586 """
1539- if isinstance (val , (float , int )):
1540- if val in self .values :
1541- return self .lnp
1542- else :
1543- return - np .inf
1544- else :
1545- val = np .atleast_1d (val )
1546- probs = - np .inf * np .ones_like (val , dtype = np .float64 )
1547- idxs = np .isin (val , self .values )
1548- probs [idxs ] = self .lnp
1549- return probs
1587+ index = np .searchsorted (self ._values_array , val )
1588+ index = np .clip (index , 0 , self .nvalues - 1 )
1589+ lnp = np .where (
1590+ self ._values_array [index ] == val , self ._lnweights_array [index ], - np .inf
1591+ )
1592+ # turn 0d numpy array to scalar
1593+ return lnp [()]
15501594
15511595
1552- class Categorical ( DiscreteValues ):
1553- def __init__ (self , ncategories , name = None , latex_label = None ,
1596+ class DiscreteValues ( WeightedDiscreteValues ):
1597+ def __init__ (self , values , name = None , latex_label = None ,
15541598 unit = None , boundary = "periodic" ):
1555- """ An equal-weighted Categorical prior
1599+ """An equal-weighted discrete-valued prior
15561600
1601+ Parameters
1602+ ==========
1603+ values: array
1604+ The discrete values of the prior.
1605+ name: str
1606+ See superclass
1607+ latex_label: str
1608+ See superclass
1609+ unit: str
1610+ See superclass
1611+ """
1612+ weights = np .ones_like (values )
1613+ super (DiscreteValues , self ).__init__ (
1614+ values = values ,
1615+ weights = weights ,
1616+ name = name ,
1617+ latex_label = latex_label ,
1618+ unit = unit ,
1619+ boundary = boundary ,
1620+ )
1621+
1622+
1623+ class WeightedCategorical (WeightedDiscreteValues ):
1624+ def __init__ (
1625+ self ,
1626+ ncategories ,
1627+ weights = None ,
1628+ name = None ,
1629+ latex_label = None ,
1630+ unit = None ,
1631+ boundary = "periodic" ,
1632+ ):
1633+ """A weighted Categorical prior
1634+
1635+ Parameters
1636+ ==========
1637+ ncategories: int
1638+ The number of available categories. The prior mass support is then
1639+ integers [0, ncategories - 1].
1640+ weights: 1d array_like or None
1641+ The weights of each category. If None, then all categories are
1642+ equally weighted. Default None.
1643+ name: str
1644+ The name of the parameter
1645+ latex_label: str
1646+ The latex label of the parameter. Used for plotting.
1647+ unit: str
1648+ The unit of the parameter. Used for plotting.
1649+ """
1650+ self .ncategories = ncategories
1651+ values = np .arange (0 , ncategories )
1652+ super (WeightedCategorical , self ).__init__ (
1653+ values = values ,
1654+ weights = weights ,
1655+ name = name ,
1656+ latex_label = latex_label ,
1657+ unit = unit ,
1658+ boundary = boundary ,
1659+ )
1660+
1661+
1662+ class Categorical (DiscreteValues ):
1663+ def __init__ (
1664+ self , ncategories , name = None , latex_label = None , unit = None , boundary = "periodic"
1665+ ):
1666+ """An equal-weighted Categorical prior
15571667 Parameters
15581668 ==========
15591669 ncategories: int
@@ -1566,9 +1676,15 @@ def __init__(self, ncategories, name=None, latex_label=None,
15661676 unit: str
15671677 See superclass
15681678 """
1679+ self .ncategories = ncategories
15691680 values = np .arange (0 , ncategories )
1570- DiscreteValues .__init__ (self , values = values , name = name , latex_label = latex_label ,
1571- unit = unit , boundary = boundary )
1681+ super (Categorical , self ).__init__ (
1682+ values = values ,
1683+ name = name ,
1684+ latex_label = latex_label ,
1685+ unit = unit ,
1686+ boundary = boundary ,
1687+ )
15721688
15731689
15741690class Triangular (Prior ):
0 commit comments