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.
        alpha_dist['broadcast'] = [2, 3]
        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.
        alpha_dist['broadcast'] = [2, 0]
        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.
        alpha_dist['broadcast'] = [0, 3]
        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
                )
            )