一尘不染

创建受约束的随机数?

algorithm

清理文本:

如何创建m = 5个将upp加起来的随机数,例如n = 100。但是,第一个随机数是10 <x1 <30,第二个随机nr是5 <x2
<20,第三个随机nr是10 <x3 <25,依此类推。所以这五个随机数加起来为100。我可以创建这些受约束的五个数字吗?

[[

相关问题A1):创建五个加起来等于100的随机数的标准方法是对[0,100]之间的四个数字进行采样,并添加边界0和100,然后对这六个数字[0,x1,x2,
x3,x4,100]。我寻找的五个随机数是增量。那是,

100 - x[4] = delta 5
x[4]- x[3] = delta 4
x[3]- x[2] = delta 3
x[2]- x[1] = delta 2
x[1] - 0   = delta 1

这五个增量现在总计为100。例如,它们可能为0、1、2、7、90。这是一些解决此问题的代码:

total_sum = 100
n = 5
v = numpy.random.multinomial(total_sum, numpy.ones(n)/n)

]]

对于我的问题,我不允许出现较大的间隔,上方的最大价差是90-7 =
83,这太宽了。因此,我必须指定更紧密的利差,例如[10,30]。这意味着最大的随机数是30,不允许较大的点差,例如83。

[[

相关问题A2):创建五个具有 相同 边界(10 <x_i
<30)的数字的部分解决方案,总计为100,这是这样的:就像在A1)中一样,但将下边界10加到增量中。所以我得到了我想要的五个随机数:

100 - x[4] = delta 5 + 10
x[4]- x[3] = delta 4 + 10
x[3]- x[2] = delta 3 + 10
x[2]- x[1] = delta 2 + 10
x[1] - 0   = delta 1 + 10

基本上,我确实像A1)中一样,但不是从0开始,而是从10开始。因此,每个数字的下边界为10,但它们没有上限,它可能很大也可能太大。如何将上限限制为30?这里的问题是如何限制上限

]]

概括地说,我尝试解决的问题的类型如下所示:我需要五个随机数加起来等于100,并且需要分别为每个数字指定边界,例如,第一个随机数为[10,30],并且然后[5,10]代表第二个随机数,[15,35]代表第三个随机数,依此类推。它们的总和必须等于100。

但是我使用的实际数据有〜100个数字x_i(m =
50),所有这些数字加起来约为40万。对于数字x_i,范围通常为[3000,5000]。这些数字并不是很准确,我只是想传达一些有关问题规模的信息。目的是进行MCMC仿真,因此需要快速生成这些数字。人们提出了非常实用的非常优雅的解决方案,但是它们花费的时间太长,因此我无法使用它们。该问题仍未解决。理想情况下,我需要O(m)解决方案和O(1)内存解决方案。

这个问题不应该是NP难题,它不像它。应该有多项式时间解,对吗?


阅读 426

收藏
2020-07-28

共1个答案

一尘不染

假设您需要[10,30]中的n_1,[20,40]中的n_2,[30,50]中的n_3以及n1 + n2 + n3 = 90

如果您需要每个可能的三元组(n_1,n_2,n_3)都一样,那将很困难。形式(20,n_2,n_3)的三元组的数量大于(10,n_2,n_3)的三元组的数量,因此您不能只是均匀地选择n_1。

极其缓慢但准确的方法是在正确的范围内生成所有5个随机数,如果总和不正确,则拒绝整个组。

。。。啊!

我找到了一种有效地参数化选择的方法。但是,为简单起见,首先请注意,下限之和是最小可能的和。如果从目标数字中减去下限的总和,然后从每个生成的数字中减去下限,则会出现一个问题,其中每个数字的间隔为[0,max_k-
min_k]。这简化了数学和数组(列表)处理。令n_k为0 <= n_k <= max_k-min_k的从0开始的选择。

总和的顺序是按字典顺序,所有总和首先以n_1 = 0(如果有)开头,然后是n_1 ==
1,以此类推。总和在每个组中按n_2排序,然后按n_3排序,依此类推。如果您知道有多少个和添加到目标(称为T),以及有多少个以n_1 =
0,1,2,…开头,那么您可以在该列表中找到总和S的起始数字n1。然后,您可以减少问题,添加n_2 + n_3 + …以获得T-
n_1,找到总和数S-(原始总和数以小于n_1的数字开头)。

令pulse(n)为n + 1个列表:(n + 1)* [1]用Python术语表示。令max_k,min_k为第k个选择的限制,m_k = max_k-
min_k为基于0的选择的上限。那么从第一个数字的选择中就有1 +
m_1个不同的“和”,而pulse(m_k)给出了分布:1是使每个和从0到m_1。对于前两个选择,有m_1 + m_ +
1个不同的和。事实证明,脉冲(m_1)与脉冲(m_2)的卷积给出了分布。

是时候停止一些代码了:

    def pulse(width, value=1):
        ''' Returns a vector of (width+1) integer ones. '''
        return (width+1)*[value]

    def stepconv(vector, width):
        ''' Computes the discrete convolution of vector with a "unit"
            pulse of given width.

            Formula: result[i] = Sum[j=0 to width] 1*vector[i-j]
            Where 0 <= i <= len(vector)+width-1, and the "1*" is the value
            of the implied unit pulse function: pulse[j] = 1 for 0<=j<=width.
        '''
        result = width*[0] + vector;
        for i in range(len(vector)):
            result[i] = sum(result[i:i+width+1])
        for i in range(len(vector), len(result)):
            result[i] = sum(result[i:])
        return result

这是专门为仅使用“脉冲”数组进行卷积而编码的,因此卷积中的每个线性组合都是和。

这些仅在最终类解决方案的构造函数中使用:

class ConstrainedRandom(object):
    def __init__(self, ranges=None, target=None, seed=None):
        self._rand = random.Random(seed)
        if ranges != None: self.setrange(ranges)
        if target != None: self.settarget(target)

    def setrange(self, ranges):
        self._ranges = ranges
        self._nranges = len(self._ranges)
        self._nmin, self._nmax = zip(*self._ranges)
        self._minsum = sum(self._nmin)
        self._maxsum = sum(self._nmax)
        self._zmax = [y-x for x,y in self._ranges]
        self._rconv = self._nranges * [None]
        self._rconv[-1] = pulse(self._zmax[-1])
        for k in range(self._nranges-1, 0, -1):
            self._rconv[k-1] = stepconv(self._rconv[k], self._zmax[k-1])

    def settarget(self, target):
        self._target = target

    def next(self, target=None):
        k = target if target != None else self._target
        k = k - self._minsum;
        N = self._rconv[0][k]
        seq = self._rand.randint(0,N-1)
        result = self._nranges*[0]
        for i in range(len(result)-1):
            cv = self._rconv[i+1]
            r_i = 0
            while k >= len(cv):
                r_i += 1
                k -= 1
            while cv[k] <= seq:
                seq -= cv[k]
                r_i += 1
                k -= 1
            result[i] = r_i
        result[-1] = k # t
        return [x+y for x,y in zip(result, self._nmin)]

    # end clss ConstrainedRandom

结合使用:

ranges = [(low, high), (low, high), ...]
cr = ConstrainedRandom(ranges, target)
seq = cr.next();
print(seq)
assert sum(seq)==target

seq = cr.next(); # get then get the next one.

…等等。该类可以减少一些,但主要的空间开销在_rconv列表中,该列表具有存储的卷积。对于O(NT)存储,大约为N * T / 2。

卷积仅使用范围,并且在相同约束条件下生成大量随机数,表构建时间“摊销”为零。就_rconv列表中的索引数而言,.next()的时间复杂度平均约为T /
2,O(T)。


要了解该算法的工作原理,请假设序列包含3个从零开始的选择,最大值为(5,7,3),从0开始的目标T =
10。在空闲会话中定义或导入Pulse和stepconv函数,然后:

>>> pulse(5)
[1, 1, 1, 1, 1, 1]
>>> K1 = pulse (5)
>>> K2 = stepconv(K1, 7)
>>> K3 = stepconv(K2, 3)
>>> K1
[1, 1, 1, 1, 1, 1]
>>> K2
[1, 2, 3, 4, 5, 6, 6, 6, 5, 4, 3, 2, 1]
>>> K3
[1, 3, 6, 10, 14, 18, 21, 23, 23, 21, 18, 14, 10, 6, 3, 1]
>>> K3[10]
18
>>> sum(K3)
192
>>> (5+1)*(7+1)*(3+1)
192

K3 [i]显示不同选择n_1,n_2,n_3的数量,以使0 <= n_k <= m_k和Σn_k =
i。当将应用于其中两个列表时,表示为卷积。然后,pulse(m_2)* pulse(m_3)给出n_2和n_3之和的分布:

>>> R23 = stepconv(pulse(7),3)
>>> R23
[1, 2, 3, 4, 4, 4, 4, 4, 3, 2, 1]
>>> len(R23)
11

从0到T = 10的每个值都是(几乎)是可能的,因此第一个数字是任何选择都是可能的,并且以N1开头的T = 10中有R23
[T-n_1]个可能的三元组。因此,一旦发现18个可能的总和加到10,就生成一个随机数S = randint(18)并通过R23
[T:T-m_1-1:-1]数组递减计数:

>>> R23[10:10-5-1:-1]
[1, 2, 3, 4, 4, 4]
>>> sum(R23[10:10-5-1:-1])
18

请注意,该列表的总和是上面K3 [10]中计算的总和。健全性检查。无论如何,如果S ==
9是随机选择,则找出不加S的情况下可以求和该数组的多少个前导项。这就是n_1的值。在这种情况下1 + 2 + 3 <= S但1 + 2 + 3 + 4>
S,因此n_1为3。

如上所述,您可以减少问题来查找n_2。最终编号(在此示例中为n_3)将唯一确定。

2020-07-28