# Non-scalar parameters¶

Note

You should be familiar with indexing and broadcasting numpy arrays.

## Non-scalar samples¶

To draw samples for non-scalar parameters, you need to specify which dimensions should be uncorrelated.

For example, consider a parameter alpha with dimensions $$2 \times 3$$ (i.e., one sample of this parameter contains 6 values). There are four different ways in which you can draw samples for this parameter:

1. All 6 values are correlated, and should be drawn at the same quantiles:

def test_alpha_1():
alpha_dist = {
'name': 'beta',
'args': {'a': 1, 'b': 1},
'shape': 1,
}
rng = np.random.default_rng(12345)
num_samples = 10
samples = lhs.draw(rng, num_samples, {'alpha': alpha_dist})
alphas = samples['alpha']
assert alphas.shape == (10,)
# Transform this into 10 samples of a 2x3 array.
shape_out = (num_samples, 2, 3)
alpha_out = np.broadcast_to(alphas[:, None, None], shape_out)

# Alternatively, instruct lhs.draw() to broadcast the samples.
rng = np.random.default_rng(12345)
samples2 = lhs.draw(rng, num_samples, {'alpha': alpha_dist})
alphas2 = samples2['alpha']
assert alphas2.shape == alpha_out.shape
assert np.allclose(alphas2, alpha_out)

2. Values in the first dimension (length 2) are correlated:

def test_alpha_2():
alpha_dist = {
'name': 'beta',
'args': {'a': 1, 'b': 1},
'shape': 3,
}
rng = np.random.default_rng(12345)
num_samples = 10
samples = lhs.draw(rng, num_samples, {'alpha': alpha_dist})
alphas = samples['alpha']
assert alphas.shape == (10, 3)
# Transform this into 10 samples of a 2x3 array.
shape_out = (num_samples, 2, 3)
alpha_out = np.broadcast_to(alphas[:, None, :], shape_out)

# Alternatively, instruct lhs.draw() to broadcast the samples.
rng = np.random.default_rng(12345)
samples2 = lhs.draw(rng, num_samples, {'alpha': alpha_dist})
alphas2 = samples2['alpha']
assert alphas2.shape == alpha_out.shape
assert np.allclose(alphas2, alpha_out)

3. Values in the second dimension (length 3) are correlated:

def test_alpha_3():
alpha_dist = {
'name': 'beta',
'args': {'a': 1, 'b': 1},
'shape': 2,
}
rng = np.random.default_rng(12345)
num_samples = 10
samples = lhs.draw(rng, num_samples, {'alpha': alpha_dist})
alphas = samples['alpha']
assert alphas.shape == (10, 2)
# Transform this into 10 samples of a 2x3 array.
shape_out = (num_samples, 2, 3)
alpha_out = np.broadcast_to(alphas[:, :, None], shape_out)

# Alternatively, instruct lhs.draw() to broadcast the samples.
rng = np.random.default_rng(12345)
samples2 = lhs.draw(rng, num_samples, {'alpha': alpha_dist})
alphas2 = samples2['alpha']
assert alphas2.shape == alpha_out.shape
assert np.allclose(alphas2, alpha_out)

4. None of the values are correlated:

def test_alpha_4():
alpha_dist = {
'name': 'beta',
'args': {'a': 1, 'b': 1},
'shape': (2, 3),
}
rng = np.random.default_rng(12345)
num_samples = 10
samples = lhs.draw(rng, num_samples, {'alpha': alpha_dist})
alphas = samples['alpha']
assert alphas.shape == (10, 2, 3)


## Non-scalar shape parameters¶

You can use non-scalar shape parameters, but they must be consistent with the desired samples shape. In the example below, we draw samples for several parameters with dimensions $$2 \times 3$$. Note that the two shape parameters have dimensions $$2 \times 1$$ and $$1 \times 3$$, and so they can both be broadcast to $$2 \times 3$$.

def test_alpha_5():
# Broadcast shape parameter a to (2, 1)
a = np.array([1, 5])[:, None]
# Broadcast shape parameter b to (1, 3)
b = np.array([1, 10, 50])[None, :]

# Construct the Beta distribution for this parameter.
alpha_dist = scipy.stats.beta(a=a, b=b)
alpha_shape = (2, 3)

# Define three parameters that are drawn from the same distribution.
params = {
'alpha1': {
'name': 'beta',
'args': {'a': a, 'b': b},
'shape': alpha_shape,
},
'alpha2': {
'distribution': alpha_dist,
'shape': alpha_shape,
},
'alpha3': {
'ppf': alpha_dist.ppf,
'shape': alpha_shape,
},
}

# Draw samples for these parameters.
rng = np.random.default_rng(12345)
num_samples = 10
values = lhs.draw(rng, num_samples, params)

# NOTE: The samples for (a=1, b=1) should be drawn from the uniform
# distribution over the unit interval, and so we should have one value in
# each of the 10 (num_samples) evenly-spaced bins of the unit interval.
bin_edges = np.arange(0, 1, step=1 / num_samples)
for param in values.keys():
uniform_values = values[param][:, 0, 0]
assert len(uniform_values) == num_samples
for (lower, upper) in zip(bin_edges[:-1], bin_edges[1:]):
assert np.any(
np.logical_and(
uniform_values >= lower, uniform_values <= upper
)
)