Correlated Distributions

Occasionally, you may want to randomize multiple variables subject to some specific constraints.

  • When randomizing physics parameters, you may want V_L and V_R to be randomized subject to the constraint that V_L == -V_R.

  • When adding noise, you may want to randomize the strengths of two different noise types, such that their sum is always a certain value.

This can be accomplished through any of the CorrelatedDistribution classes provided with QDFlow, or by creating a custom CorrelatedDistribution.

from qdflow.util import distribution
import numpy as np
from qdflow import generate

A CorrelatedDistribution is essentially a multivariate distribution, where the variables are correlated in some way. A single draw from a CorrelatedDistribution will return an array of length num_variables.

The simplest CorrelatedDistribution included in QDFlow is FullyCorrelated, which simply returns a number of copies of the same value.

dist_single = distribution.Normal(20, 5) # A single-variable distribution

# A correlated distribution that returns 5 copies of the same value
num_variables = 5
corr_dist = distribution.FullyCorrelated(dist_single, num_variables)

rng = np.random.default_rng(seed=6)

# Draw a single sample of each of the 5 variables
single_sample = corr_dist.draw(rng)
print("Single sample: ", single_sample)

# Draw multiple samples of each variable
num_samples = 3
samples = corr_dist.draw(rng, size=num_samples)
print("Multiple samples:\n", samples)
Single sample:  [25.26557877 25.26557877 25.26557877 25.26557877 25.26557877]
Multiple samples:
 [[28.88245652 28.88245652 28.88245652 28.88245652 28.88245652]
 [ 7.23354081  7.23354081  7.23354081  7.23354081  7.23354081]
 [19.31017469 19.31017469 19.31017469 19.31017469 19.31017469]]

When performing randomization, QDFlow expects each field in the randomization class to be given a seperate distribution.

Distributions for each of the individual variables can be obtained from a CorrelatedDistribution via the dependent_distributions() method.

# Create a correlated distribution with 2 variables
dist_single = distribution.Normal(0, 1)
num_variables = 2
corr_dist = distribution.FullyCorrelated(dist_single, num_variables)

# Obtain a list [dist_1, dist_2] of the individual distributions for each variable
individual_dists = corr_dist.dependent_distributions()
dist_1, dist_2 = individual_dists

rng = np.random.default_rng(seed=7)

# Draw a single sample from one of the individual distributions
sample_1 = dist_1.draw(rng)
print("Sample 1: ", sample_1)

# Now draw a sample from the other individual distribution
sample_2 = dist_2.draw(rng)
print("Sample 2: ", sample_2)
Sample 1:  0.0012301533574825742
Sample 2:  0.0012301533574825742

These individual distributions can then be used to specify how physics parameters should be randomized.

# Create a randomization object with default distributions for each parameter
phys_rand = generate.PhysicsRandomization.default()

# Set V_L and V_R distributions such that they will always be negative of each other.
phys_rand.V_L = 2 * dist_1
phys_rand.V_R = -2 * dist_2

# Generate a randomized set of physics parameters
generate.set_rng_seed(8)
n_devices = 6
phys_params = generate.random_physics(phys_rand, n_devices)
print("V_L: ", phys_params[0].V_L)
print("V_R: ", phys_params[0].V_R)
V_L:  -0.37779439216921556
V_R:  0.37779439216921556