Input Programs¶
OPPAS tools analyze programs written in a simplified procedural programming language. POMC’s input language is called MiniProc, and POPACheck’s is called MiniProb. The two languages are mostly the same, and they differ only in that MiniProc supports non-determinism, try-catch statements and exceptions, while MiniProb supports sampling from discrete stochastic distributions, nested queries, and conditioning.
These languages are not Turing-complete, because all variables are fixed-size. So, any use of the word “program” when referring to code written in them is a bit of an abuse of terminology. This limitation, however, allows POMC (resp. POPACheck) to translate every MiniProc (resp. MiniProb) program into an OPA (resp. pOPA), that is then checked against the user-supplied requirements.
Common syntax¶
prog ::= (decl";")+func+ decl ::=typeidentifier(","identifier)* type ::= "bool" | "u"int| "s"int| "u"int"["int"]" | "s"int"["int"]" func ::=f"("type["&"]x₁(","type["&"]x₂)+ ")" "{" (decl";")+block"}" stmt ::=lval"="ef"(" (e₁"|"lval₁) ["," (e₂"|"lval₂)]* ")" "if" "("guard")" "{"block"}" "else" "{"block"}" "while" "("guard")" "{"block"}" block ::= [stmt";"]+ lval ::=identifier|identifier"["e"]" guard ::=exprlvalue ::=identifieridentifier"["expr"]"
An identifier is any sequence of letters, numbers, or characters
‘.’, ‘:’ and ‘_’, starting with a letter or an underscore.
Syntax of Expressions¶
expr ::=expr"||"conj|conjconj ::=conj"&&"bterm|btermbterm ::=iexprcompiexpr|iexprcomp ::= "==" | "!=" | "<" | "<=" | ">" | ">=" iexpr ::=iexpr"+"pexpr|iexpr"-"pexpr|pexprpexpr ::=pexpr"*"iterm|pexpr"/"iterm|itermiterm ::= "!"iterm| "("expr")" |identifier|identifier"["expr"]" |literalliteral ::=intlit| "+"intlit| "-"intlit| "true" | "false" intlit ::=int|int"u"int|int"s"int
The program starts with a variable declaration, which must include all
global variables used in the program. Variables can be Boolean, or of
signed or unsigned fixed-width integer types, or fixed-size arrays
thereof. Then, a sequence of functions are defined, the first one being
the entry-point to the program. Functions can have formal parameters
that are passed by value or by value-result [1], the latter being
marked with the & symbol. Function bodies consist of
semicolon-separated statements, which start after zero or more lists of
local variables. Assignments, while loops and ifs have the usual
semantics. Functions can be called by prepending their name to actual
parameters enclosed in parentheses. Actual parameters passed by
value-result can only be variable names. Expressions can be made of the
usual arithmetic operations when they involve integer variables, and
arrays can be indexed by integer expressions enclosed in square
brackets, both for assigning and reading them. Integer literals can be
specified by a decimal number, optionally followed by the type of the literal
(e.g., u8 for an 8-bit unsigned integer, s16 for a 16-bit signed
integer, etc.), and possibly preceded by its sign. Boolean expressions can
contain comparisons between integers, and can be composed through the
logical and (&&), or (||) and negation (!) operators.
Boolean operators also handle integers (0 means false, everything else true).
It is possible to declare modules by including a double colon (::)
in function names. E.g., function A::B::C() is contained in module
A::B, which is contained in A. In the OPA resulting from the
program, the module names hold whenever a contained function is called
or returns. This is useful for referring to multiple functions at once
in requirements, hence drastically reducing their length.
When providing input models as programs, it is possible to use MiniProc expressions as atomic propositions in formulas by using the syntax:
[ identifier | expr ]
where identifier, which is optional, is a function name and expr
is any MiniProc expression. The expression will be evaluated in
the scope of the specified function, or in the global scope if none is
given; it will evaluate to false during the execution of all other
functions. The expression may only refer to variables either global or
local to the specified function, and an error is raised otherwise.
OPPAS tools automatically encode programs into the appropriate kind of OPA defined on the following OPM:
MiniProc Syntax¶
We add the following syntax rules:
guard ::= "*" stmt ::=lval"=" "*" "try" { (stmt;)+ } "catch" { (stmt;)+ } "throw"
MiniProc (POMC’s input language) additionally supports non-determinism and exception handling.
When a variable is assigned the special value *,
it will be considered as if it could take any of the values allowed by its type.
Similarly, when * appears in the guard of an if or a while statement,
it non-deterministically evaluated to both true and false.
The try-catch statement executes the catch block whenever an
exception is thrown by any statement in the try block (or any function
it calls). Exceptions are thrown by the throw statement, and they
are not typed (i.e., there is no way to distinguish different kinds of
exceptions).
An example MiniProc program is given below:
bool foo;
pa() {
foo = false;
try {
pb();
} catch {
pc();
}
}
pb() {
if (foo) {
throw;
} else {}
}
pc() { }
MiniProb Syntax¶
We add the following syntax rules:
stmt ::=lval"="Distribution"(" "..." ")"lval"="e₁("{"e₂":"e₃"}"e₄)+ "query"f"(" (e₁"|"lval₁) ["," (e₂"|"lval₂)]* ")" "observe" "("e")"
POPACheck analyzes programs written in MiniProb, a simple probabilistic programming language.
Programs may sample from \(\texttt{Bernoulli(} e_1, e_2 \texttt{)}\), which returns 1 with probability \(p = e_1 / e_2\), and 0 with probability \(1-p\), or from \(\texttt{Uniform(} e_1, e_2 \texttt{)}\), which samples uniformly among integers from \(e_1\) to \(e_2\).
Random assignments of the form \(x = e_1 \{ e_2 / e_3 \} e_4\) mean that \(x\) is assigned the value of \(e_1\) with probability \(e_2 / e_3\), and \(e_4\) with probability \(1 - e_2 / e_3\).
Finally, functions can query the distribution
on value-result parameters of another function, and condition on a
Boolean expression with observe.
Comparison with WebPPL¶
For comparison, we show informally how constructs of a general purpose probabilistic programming language, WebPPL [2], map to MiniProb operators.
Sampling¶
Sampling from primitive distributions is implicit in MiniProb, hence WebPPL
var a = sample(dist);
translates to MiniProb directly to
s16 a;
a = dist;
where we have assumed that variable a is a 16-bit signed integer. In
MiniProb, all variables must be declared before usage. Sampling from the
distribution obtained from marginal inference directly is not possible
in MiniProb, but we show a workaround later.
Primitive Distributions¶
All WebPPL primitive distributions that are supported by MiniProb are listed in the following table. Those not listed here are not supported. Note that MiniProb supports only discrete probability distributions, and rational probabilities.
Distribution |
WebPPL |
MiniProb |
|---|---|---|
Bernoulli |
|
|
Categorical |
|
|
Coin flip |
|
|
Delta |
|
|
Discrete |
|
|
Integer Uniform |
|
|
Marginal Inference¶
Marginal inference (or just inference) is the process of reifying the distribution on return values implicitly represented by a stochastic computation. [2]
In WebPPL, it is expressed as, for example,
var a = sample (Infer(function() {
return flip() + flip();
}));
In MiniProb, it corresponds to
main() {
u2 a;
query function(a);
}
function (u2 &res) {
bool b,c;
b = 1{1/2}0;
c = 1{1/2}0;
res = b + c;
}
In a nutshell, query .. corresponds to Infer(..). In MiniProb
procedures do not have a return statement; however it is possible to
bind a variable a to a sample from the the queried distribution by
passing a to a parameter by value-result (i.e., with &) of the
queried function, and then assigning a sample from the distribution to
the parameter as last statement of the queried function.
Conditioning¶
The only conditioning construct in MiniProb is observe(c),
corresponding to condition(bool) in WebPPL.
Footnotes