Let's consider a generalization of this problem. There are $m=4$ cans of paint of $m=4$ distinct colors and $n^{(0)}=100$ balls. Can $i$ can hold up to $a^{(0)}_i = (100, 100, 50, 75)$ balls. You wish to generate configurations of balls in the cans having at least $b_i = (0, 50, 0, 25)$ balls in can $i$ for each $i$, each configuration with equal probability.
Such configurations are in one-to-one correspondence with the configurations obtained after removing $b_i$ balls from can $i$, limiting the $n = n^{(0)} - \sum_i b_i = 100 - (0+50+0+25)=25$ remaining balls to at most $a_i = a^{(0)}_i - b_i = (100, 50, 50, 50)$ per can. I will therefore just generate these and let you adjust them afterwards (by putting $b_i$ balls back into can $i$ for every $i$).
To count these configurations up, fix all but two of the indices, say $i$ and $j$. Suppose there are $s_k$ balls already in can $k$ for each $k$ differing from $i$ and $j$. That leaves $s_i+s_j$ balls. Conditional on where the other $n - (s_i+s_j)$ balls are, these are distributed uniformly within cans $i$ and $j$. The possible configurations are $1 + \min(a_i + a_j - s_i - s_j, s_i+s_j)$ in number (see the comments), ranging from placing as many balls in can $i$ as possible all the way through placing as many balls in can $j$ as possible.
If you wish, you could count the total number of configurations by applying this argument recursively to the remaining $m-2$ cans. However, to obtain samples we don't even need to know this count. All we need to do is repeatedly visit all possible unordered pairs $\{i,j\}$ of cans and randomly (and uniformly) change the distribution of balls within those two cans. This is a Markov chain with a limiting probability distribution that is uniform over all possible states (as is readily shown using standard methods). Therefore it suffices to start in any state, run the chain long enough to reach the limiting distribution, and then keep track of the states visited by this procedure. As usual, to avoid serial correlation, this sequence of states should be "thinned" by skipping through them (or revisited randomly). Thinning by a factor of about half the number of cans tends to work well, because after that many steps on average each can has been affected, producing a genuinely new configuration.
This algorithm costs $O(m)$ effort to generate each random configuration on average. Although other $O(m)$ algorithms exist, this one has the advantage of not needing to do the combinatorial calculations beforehand.
As an example, let's work out a smaller situation manually. Let $a=(4,3,2,1)$ and $n=3$, for instance. There are 15 valid configurations, which may be written as strings of occupancy numbers. For example, 0201
places two balls into the second can and one ball in the fourth can. Emulating the argument, let's consider the total occupancy of the first two cans. When that is $s_1+s_2=3$ balls, no balls are left for the last two cans. That gives the states
30**, 21**, 12**, 03**
where **
represents all the possible occupancy numbers for the last two cans: namely, 00
. When $s_1+s_2=2$, the states are
20**, 11**, 02**
where now **
can be either 10
or 01
. That gives $3\times 2=6$ more states. When $s_1+s_2=1$, the states are
10**, 01**
where now **
can be 20
, 11
, but not 02
(due to the limit of one ball in the last can). That gives $2\times 2=4$ more states. Finally, when $s_1+s_2=0$, all balls are in the last two cans, which must be full to their limits of $2$ and $1$. The $4+6+4+1=15$ equally probable states therefore are
3000, 2100, 1200, 0300; 2010, 2001, 1110, 1101, 0210, 0201; 1020, 1011, 0120, 0111; 0021.
Using the code below, a sequence of $10,009$ such configurations was generated and thinned to every third one, creating $3337$ configurations of the $15$ states. Their frequencies were the following:
State: 3000 2100 1200 0300 2010 1110 0210 1020 0120 2001 1101 0201 1011 0111 0021
Count: 202 227 232 218 216 208 238 227 237 209 239 222 243 211 208
A $\chi^2$ test of uniformity gives a $\chi^2$ value of $11.2$, $p=0.67$ ($14$ degrees of freedom): that is beautiful agreement with the hypothesis that this procedure produces equally probable states.
This R
code is set up to handle the situation in the question. Change a
and n
to work with other situations. Set N
to be large enough to generate the number of realizations you need after thinning.
This code cheats a little bit by cycling systematically through all $(i,j)$ pairs. If you want to be strict about running the Markov chain, generate i
, j
, and ij
randomly, as given in the commented code.
#
# Gibbs-like sampler.
#
# `a` is an array of maximum numbers of balls of each type. Its values should
# all be integers greater than zero.
# `n` is the total number of balls.
#------------------------------------------------------------------------------#
g <- function(j, state, a) {
#
# `state` contains the occupancy numbers.
# `a` is the array of maximum occupancy numbers.
# `j` is a pair of indexes into `a` to "rotate".
#
k <- sum(state[j]) # Total occupancy.
x <- floor(runif(1, max(0, k - a[j[2]]), min(k, a[j[1]]) + 1))
state[j] <- c(x, k-x)
return(state)
}
#
# Set up the problem.
#
a <- c(100, 50, 50, 50)
n <- 25
# a <- 4:1
# n <- 3
#
# Initialize the state.
#
state <- round(n * a / sum(a))
i <- 1
while (sum(state) < n) {
if (state[i] < a[i]) state[i] <- state[i] + 1
i <- i+1
}
while (sum(state) > n) {
i <- i-1
if (state[i] > 0) state[i] <- state[i] - 1
}
#
# Conduct a sequence of random changes.
#
set.seed(17)
N <- 1e5
sim <- matrix(state, ncol=1)
u <- ceiling(N / choose(length(state), 2) / 2)
i <- rep(rep(1:length(state), each=length(state)-1), u)
j <- rep(rep(length(state):1, length(state)-1), u)
ij <- rbind(i, j)
#
# Alternatively, generate `ij` randomly:
# i <- sample.int(length(state), N, replace=TRUE)
# j <- sample.int(length(state)-1, N, replace=TRUE)
# ij <- rbind(i, ((i+j-1) %% length(state))+1)
#
sim <- cbind(sim, apply(ij, 2, function(j) {state <<- g(j, state, a); state}))
rownames(sim) <- paste("Can", 1:nrow(sim))
#
# Thin them for use. Each column is a state.
#
thin <- function(x, stride=1, start=1) {
i <- round(seq(start, ncol(x), by=stride))
x[, i]
}
#
# Make a scatterplot of the results, to illustrate.
#
par(mfrow=c(1,1))
s <- thin(sim, stride=max(1, N/1e4))
pairs(t(s) + runif(length(s), -1/2, 1/2), cex=1/2, col="#00000005", pch=16)