Closed zenna closed 3 years ago
A quick primer on Cassette. Mostly for @jburroni
Cassette allows you to implement some (actually many) kinds of non-standard interpretations.
The general approach is to first create a context, and then add new definitions to that context.
using Cassette
Cassette.@context MyCtx # Creates a new context type called MyCtx
In this new context I will replace sin
with cos
# adding methods to Cassette.overdub is how to define new semantics
function Cassette.overdub(::MyCtx, ::typeof(sin), x)
println("I will now do cos instead of sin")
cos(x)
end
Try it out
myprogram() = sin(3)
# execute myprogram in MyCtx
Cassette.overdub(MyCtx(), myprogram)
One thing it quite easily allows you to do something equivalent to adding extra state to the environment, in the operational semantics sense. This is done by adding data ('metadata') to your context. In this example I'll add metadata which just counts the number of function calls
mutable struct Counter
count::Int
end
# This will intercept __every__ function call (except one's in Base that Cassette has defaults to not recurse into)
function Cassette.overdub(ctx::MyCtx, f, args...)
ctx.metadata.count += 1
# Now Continue running
Cassette.recurse(ctx, f, args...)
end
# Let's try it out
ctx = MyCtx(metadata = Counter(1))
Cassette.overdub(ctx, myprogram)
println("Number of function calls is $(ctx.metadata)")
Better in discussions
This issue proposes a big change to the structure of Omega and the semantics of ciid. #120
Overall setup
unif1, unif2, ...
that are i.i.d.y(ω) = f(unif(ω))
i ~ x <| (x, y, z, ....)
. This constructs a random variable that is thei
th element in a sequence of random variables that have the same functional form asx
, sharex
,y
andz
as parents. Consequently:x
and conditionally independent givenx
,y
,z
, ....1 ~ x, 2 ~ x, ...
are all i.i.d.Independence
Operationally, it works as follows. First, consider the simplest case, the i.i.d. case, and the following example:
unif1, unif2, ...
unif_gen
be a mapping from tuples of integers to random variables in this sequence.context
, which is a tuple of integersunif(ω)
. In a contexts
,unif(ω)
is interpreted asunif_gen(s)(ω)
a
has the forma(ω) = context_apply(1, unif, ω)
.context_apply(i, x, ω)
means to evaluatesx(ω)
in a modified environment wherei
is appended onto the contexta(ω)
will ultimately evaluateunif(ω)
within the context(1,)
.context_apply
is applied within another context, the contexts are merged.Conditional independence
For example, in the following snippet.
m1
andm2
are conditionally independent givenx
, andm3
is completely independent from them both,Operationally, conditional independence is possible with a single modification. To illustrate, in order for
m1
to beciid
givenx
, the the evaluation ofm1(ω)
we should evaluatex(ω)
in the context thatx
uses ordinarily, and not whatever contextm1
has introduced. Hence, we simply to evaluatex(ω)
with out any context, a new context will be reintroduced byx
.Prototype
Port to Omega?
There may be some small bugs (please check) in this approach but I'm fairly confident it is both mostly correct, more general, and simpler than what we are currently doing. So, I should probably port the change to real Omega.
A few concerns?
RandVar
sRandVar
s have explicit ids. These ids are used to check random variable equivalence for (i) memoization, and causal interventions. The above code uses Julia's function equivalence checking. It's not clear exactly what that is doing, but I imagine it's doing some kind of memory check. It's not clear that integer ID's are much better because: we can define the same random variable (functionally) and give them different IDs.RandVar
s have type information that can be useful, e.g. for analytic computation of expectations.Overall, I'm inclined to say we support any object that "purely" implements (::Type)(ω::Ω). We can still use typed and/or id'd objects for the above reasons, but not if we don't need that.
This code would break, or more precisely, do something different to what it currently does. The reason is that currently this code would share the value of
x
, butciid
(aka~
) without any shared parameters would create an i.i.d. variable. Fortunately though, because we no longer have a distinction between random variables and functions, this does not mean we always need to explicitly stat what we want the parents to be, we in fact do not need theciid
at all in this example