Random functions

Random functions can be implemented in Quibbler using random quibs. Random quibs are function quibs that call random functions, specified with is_random property. Defining a quib running a random function automatically caches its output, so that multiple requests for its value give the same random realization (quenched randomness). Then, to refresh randomization, we invalidate the cached values of these random quibs. Such re-randomization can be done either centrally for all random quibs, or individually for each given random quib. Such invalidation of random quibs will then invalidate any downstream calculations that depend on these random values, causing re-evaluated of the random function upon downstream output request.

Import

import pyquibbler as qb
from pyquibbler import iquib
qb.initialize_quibbler()
import numpy as np

Quibs calling NumPy random functions are automatically defined as random quibs

By default, all standard NumPy functions that generate random output are automatically implemented as random function quibs. We can therefore define random quibs simply by calling NumPy random functions with quib arguments.

For example,

n = iquib(3)
rand_numbers = np.random.rand(n)
rand_numbers.get_value()
array([0.24936599, 0.49959145, 0.70940961])
rand_numbers.is_random
True

Random quibs always cache their results

Random quibs always cache their results, so repeated calls for their value yields the same randomization:

rand_numbers.get_value()
array([0.24936599, 0.49959145, 0.70940961])

Because the randomization is fixed, mathematical trivialities hold true:

rand_numbers_plus_1 = rand_numbers + 1
should_equal_zero = np.sum(rand_numbers_plus_1 - rand_numbers) - n
should_equal_zero.get_value()
0.0

Refreshing randomization

The cache of the random quibs can be invalidated either centrally for all random quibs, or individually for a given specific random quib. Upon invalidation, all downstream dependent quibs are also invalidated. Requesting the value of such downstream calculations will then lead to recalculation of the random function (re-randomization).

Central re-randomization of all random quibs. To simply refresh randomization of all the random quibs in an entire analysis pipeline, we use the reset_random_quibs function. All downstream results are also invalidated and upon request for their value, new randomization will be calculated:

rand_numbers_plus_1.get_value()
array([1.24936599, 1.49959145, 1.70940961])
qb.reset_random_quibs()
rand_numbers_plus_1.get_value()
array([1.76673854, 1.29385858, 1.72750647])

Quib-specific re-randomization. To specifically refresh the randomization of a given chosen random quib, we can invalidate its cache using the invalidate method. Any function quibs downstream of this specific quib will thereby also invalidate. Request the value of such downstream results will lead to new randomization:

rand_numbers.invalidate()
rand_numbers_plus_1.get_value()
array([1.44229483, 1.94557109, 1.37758801])

User-defined randmon functions

To implement quibs that call user defined random functions, we can set the is_random property of the function to True, when converting it to a quiby function using the quiby() decorator:

@qb.quiby(is_random=True)
def sum_of_dice(n: int):
    return np.sum(np.random.randint(1, 7, n))

num_dice = iquib(4)
sum_dice = sum_of_dice(n)
sum_dice.get_value()
13
sum_dice.get_value()
13
qb.reset_random_quibs()
sum_dice.get_value()
11

Examples

For an example of an Quibbler app with random quibs, see: