home > software > eco-imp > index

Expected Cost Analysis of Probabilistic Imperative Programs

Our tool ecoimp performs an expected cost analysis for simple imperative programs featuring probabilistic sampling instructions, as outlined in our recent paper.

Download and Installation

The tool is open source and can be downloaded from here. The source code is available via our Gitlab repository. It is implemented in Haskell and relies on a recent version of the Glasgow Haskell Compiler. It requires various auxiliary libraries and tools, such as our GUBS solver. Thus, the easiest way to install ecoimp is via stack which takes care of installing all dependencies. To install ecoimp via stack, navigate to the source distribution folder in a terminal and type:

stack install

This will install an executable ecoimp together with all its requirements. Note however that GUBS solver needs an SMT-solver installed, specifically, only yices (version >= 2.6.1) is supported at the moment.

Usage

You can invoke our tool on the command line by

ecoimp <file>

where <file> is the program that should be analysed. The program should adhere to the following format. Notice that similar to Python or Haskell, the syntax of commands Cmd is block sensitive: blocks <Cmd> are delimited by whitespace and indentation, rather than braces. For examples, look here.

  Cmd := skip
       | abort
       | tick Exp
       | Var = DExp | Var = Exp
       | if BExp: <Cmd> [else: <Cmd>]?
       | while BExp: <Cmd>
       | nondet: <Cmd> [else: <Cmd>]?
       | prob (Const, Const): <Cmd> [else: <Cmd>]?
       | Cmd Cmd

  Exp := Const
       | Exp + Exp
       | Exp * Exp
       | Exp - Exp
       | -Exp
       | (Exp)

 DExp := rand(Exp)
       | unif(Const,Const)
       | ber(Const,Const)
       | hyper(Const,Const,Const)
       | bin(Const,Const,Const)

 BExp := BLit | not BExp | BExp and BExp | BExp or BExp | (BExp)

  Var := [a-zA-Z][a-zA-Z0-9_]*

Const := [0-9]+

 BLit := Exp <= Exp | Exp >= Exp | Exp < Exp | Exp > Exp | Exp = Exp

A program is given by a definition

def procedure-name():
   Cmds

where procedure-name is the name of the defined procedure and Cmds is a sequence of commands Cmd. The list of program commands may be optionally preceded by a sequence of variable declarations var Varlist. All variables range over integers and are globally scoped. Used variables are implicitly declared.

Most of the commands are self-explanatory. The command tick e consumes e >= 0 resources and defines the analysed cost-measure of the program. A command

nondet:
   C
else:
   D

implements the non-deterministic choice between C and D. In contrast, the command

prob(n,m):
   C
else:
   D

executes C with probability p = n/n+m and D with probability 1-p = m/n+m. The assignment operator = is used both for the usual assignment (as e.g. in x = x + 2) and for sampling from a distribution (as e.g. in x = rand(y + 1)). Currently, the following distributions are implemented:

  • rand(e): samples an integer value uniformly in the interval [0,e] where e can be any expression Exp.

  • unif(n,m): samples an integer uniformly in the interval [n,m] for constants n and m.

  • ber(n,m): samples an integer from the Bernoulli distribution with probability p = n / n + m. Here n+m should be greater than zero.

  • hyper(N,K,n): samples an integer from the Hypergeometric distribution for a population size of N, number K of successes in the population and n the number of draws.

  • bin(n,m,N): samples an integer from the Binomial Distribution with N trials and a success probability of n / n + m (where n+m greater than zero).

An Example

The next example gives an implementation of the Coupon Collector Problem following Kaminski et al. [2018].

> cat examples/coupons-N.imp
# Coupon Collector's Problem
# Upper Bound: O(n*ln n)

def collector():
  var coupons, draw, N

  coupons = 0
  while 0 <= coupons and coupons < N:
    tick 1
    draw = rand(N)
    draw = draw + 1
    if draw > coupons:
      coupons = coupons + 1

> ecoimp examples/coupons-N.imp
ExecutionLog
|
`- [ect] 0
   |
   +- [Problem]
   |    coupons :~ {1 : 0}
   |    0:While(N ≥ 1 + coupons ∧ coupons ≥ 0)
   |        Tick(1)
   |        draw :~ rand(N)
   |        draw :~ {1 : 1 + draw}
   |        1:If(draw ≥ 1 + coupons) Then
   |            coupons :~ {1 : 1 + coupons}
   |          Else
   |            Skip
   |
   +- [While.Step]
   |  |
 [...]
   |
   `- 1/2 + [N | N ≥ 0] + 1/2·[N^2 | N ≥ 0]

[Success] 1/2 + [N | N ≥ 0] + 1/2·[N^2 | N ≥ 0]

The output of the tool consists on the one hand of an execution log detailing the analysis steps (such as derived bounds for sub-programs, chosen base-functions and their expected values) as well the overall result of the analysis, drawn in the final line. In the example above, showing a cost preserving abstraction of an a quadratic bound on the expected number of trials is derived. More precisely, an expression [p | ϕ] corresponds to p if ϕ is true, and 0 otherwise. The estimated complexity is thus given by 1/2 + N + 1/2 · N^2 for all non-negative N, and 1/2 otherwise.

Experiments

The results of our evaluation are compiled in the following Table, showing the derived upper bounds on the program’s cost as well as the execution time of our implementation. Execution times were measured on a Intel i7-7600U CPU with 2.8GHz and 16GB RAM under Linux. An expression [p | ϕ] corresponds to p if ϕ is true, and 0 otherwise.

Program Inferred Bound secs
linear benchmark from [Ngo et al. 2018]
2drwalk 2·[1 + -d + n | 1 + -d + n ≥ 0] 0.026
bayesian_network 5·[n | n ≥ 0] 0.002
ber 2·[n + -x | n + -x ≥ 0] 0.001
bin 1024/1023·[n | n ≥ 0] 0.006
C4B_t09 41·[x | x ≥ 0] 0.006
C4B_t15 [y ≥ 0] · 2·[1 + x | 1 + x ≥ 0] 0.006
C4B_t19 2·[-100 + i | -100 + i ≥ 0] + [51 + i + k | 51 + i + k ≥ 0] 0.006
C4B_t30 [y ≥ -1] · ([2 + y | 2 + y ≥ 0] + [x | x ≥ 0]) 0.003
C4B_t61 160/19·[-7 + l | -7 + l ≥ 0] + 7 0.005
condand 2·[m | m ≥ 0] 0.002
cooling 21/10·[1 + t | 1 + t ≥ 0] + [mt + -st | mt + -st ≥ 0] 0.015
coupon 25 0.003
cowboy_duel 6/5 0.001
cowboy_duel_3way 83/24 0.004
fcall 2·[n + -x | n + -x ≥ 0] 0.001
filling_vol 3/5·[1 + volToFill | 1 + volToFill ≥ 0] + 3/5·[volToFill | volToFill ≥ 0] 0.006
geo [1 | ⊤] 0.001
hyper 145/28·[-1 + n + -x | -1 + n + -x ≥ 0] 0.002
linear01 [-1 + x | -1 + x ≥ 0] 0.001
no_loop 5 0.001
prdwalk 8/3·[n + -x | n + -x ≥ 0] 0.002
prnes [y ≥ 0] · (50/9·[99·(n) + -n·y | -99 + y ≥ 0 ∧ -n ≥ 0] + 250000/81·[n^2 | -n ≥ 0]) 0.126
prseq [y ≥ 10] · (3·[-2 + x + -y | -2 + x + -y ≥ 0] + 3/2·[-9 + y | -9 + y ≥ 0] + 3·[-2 + x + -y | -2 + x + -y ≥ 0]) 0.010
prseq_bin [y ≥ 10] · (16/5·[-2 + x + -y | -2 + x + -y ≥ 0] + 3/2·[-9 + y | -9 + y ≥ 0] + 16/5·[-2 + x + -y | -2 + x + -y ≥ 0]) 0.016
prspeed 4/3·[-2 + n + -x | -2 + n + -x ≥ 0] + 2·[m + -y | m + -y ≥ 0] 0.013
race 11/6·[3 + -h + t | 3 + -h + t ≥ 0] 0.010
rdseql [y ≥ 0] · (9/4·[x | x ≥ 0] + [y | y ≥ 0]) 0.007
rdspeed 4/3·[-2 + n + -x | -2 + n + -x ≥ 0] + 2·[m + -y | m + -y ≥ 0] 0.012
rdwalk 2·[1 + n + -x | 1 + n + -x ≥ 0] 0.003
rejection_sampling 2·[n | n ≥ 0] 0.002
rfind_lv 2 0.001
rfind_mc [k | k ≥ 0] 0.002
robot 5/4·[n | n ≥ 0] 0.006
roulette 74/15·[10011 + -n | 10011 + -n ≥ 0] 0.049
simple_game 2·[1 + x | 1 + x ≥ 0] 0.006
sprdwalk 2·[n + -x | n + -x ≥ 0] 0.001
trapped_miner 15/2·[n | n ≥ 0] 0.002
polynomial benchmark from [Ngo et al. 2018]
complex [M ≥ 0 ∧ y ≥ 0] · ([w | w ≥ 0] + 18·[M·N + N | 3 + 3·(M) ≥ 0 ∧ N ≥ 0] + 3·[y | y ≥ 0]) 0.029
multirace [m ≥ 0] · 4·[m·n + n | 1 + m ≥ 0 ∧ n ≥ 0] 0.013
pol04 15/2·[x | x ≥ 0] + 9/2·[x^2 | x ≥ 0] 0.07
pol05 3/4·[x | x ≥ 0] + 3/2·[x^2 | x ≥ 0] 0.007
pol06 [1 + minPrice + minPrice·sPrice + sPrice | 1 + minPrice ≥ 0 ∧ 1 + sPrice ≥ 0] + [-minPrice + -minPrice·sPrice + sPrice + sPrice^2 | 1 + sPrice ≥ 0 ∧ -minPrice + sPrice ≥ 0] 0.040
pol07 3/2·[1 + -2·(n) + n^2 | -1 + n ≥ 0] 0.015
/rdbub 3·[n^2 | n ≥ 0] 0.007
trader 20·[-minPrice + minPrice·sPrice + -minPrice^2 + sPrice | 1 + minPrice ≥ 0 ∧ -minPrice + sPrice ≥ 0] + 10·[-2·(minPrice·sPrice) + minPrice^2 + sPrice^2 | -minPrice + sPrice ≥ 0] 0.030
additional examples from [Wang et al. 2019]
2drobot 1/2·[a + -2·(a·b) + a^2 + -b + b^2 | 1 + a + -b ≥ 0 ∧ a + -b ≥ 0] + 2·[1 + 2·(a) + -2·(a·b) + a^2 + -2·(b) + b^2 | 1 + a + -b ≥ 0] + 1267/18·[6 + a + -b | 6 + a + -b ≥ 0] 1.760
queueing-network 1132981/7411500·[1 + n | 1 + n ≥ 0] + 125/243 + 125/244 2.215
ecoimp examples
bridge [-a·b + a·x + b·x + -x^2 | -a + x ≥ 0 ∧ b + -x ≥ 0] 0.005
nest-1 4·[n | n ≥ 0] 0.011
nest-2 16·[n^2 | n ≥ 0] + 4·[n | n ≥ 0] 0.019
nest-3 2·[2 + n | 2 + n ≥ 0] + 16·[n^2 | n ≥ 0] + 64·[n^3 | n ≥ 0] 0.068
nest-4 144·[2·(n^2) + n^3 | 2 + n ≥ 0 ∧ n ≥ 0] + 10·[2 + n | 2 + n ≥ 0] + 256·[n^4 | n ≥ 0] 0.554
trader-10 10·[-min + min·price + -min^2 + price | 1 + min ≥ 0 ∧ -min + price ≥ 0] + 5·[-2·(min·price) + min^2 + price^2 | -min + price ≥ 0] 0.025
trader-100 100·[-min + min·price + -min^2 + price | 1 + min ≥ 0 ∧ -min + price ≥ 0] + 50·[-2·(min·price) + min^2 + price^2 | -min + price ≥ 0] 0.027
trader-1000 1000·[-min + min·price + -min^2 + price | 1 + min ≥ 0 ∧ -min + price ≥ 0] + 500·[-2·(min·price) + min^2 + price^2 | -min + price ≥ 0] 0.039
trader-10000 10000·[-min + min·price + -min^2 + price | 1 + min ≥ 0 ∧ -min + price ≥ 0] + 5000·[-2·(min·price) + min^2 + price^2 | -min + price ≥ 0] 0.153
trader-100000 100000·[-min + min·price + -min^2 + price | 1 + min ≥ 0 ∧ -min + price ≥ 0] + 50000·[-2·(min·price) + min^2 + price^2 | -min + price ≥ 0] 2.113
coupons-10 110 0.015
coupons-50 2550 0.306
coupons-100 10100 1.141
coupons-N 1/2 + [N | N ≥ 0] + 1/2·[N^2 | N ≥ 0] 0.195
deterministic benchmark from [Carbonneaux et al. 2015]
gcd [x | x ≥ 0] + [y | y ≥ 0] 0.005
speed_pldi09_fig1 2·[n | n ≥ 0] 0.053
speed_pldi09_fig4_2 [m ≥ 1] · ([1 + -m | 1 + -m ≥ 0] + 2·[n | n ≥ 0]) 0.013
speed_pldi09_fig4_4 [m ≥ 1] · [n | n ≥ 0] 0.002
speed_pldi09_fig4_5 ? 0.108
speed_pldi10_ex1 1/2·[n^2 | n ≥ 0] 0.172
speed_pldi10_ex3 1/2·[n | n ≥ 0] + 1/2·[n^2 | n ≥ 0] 1.052
speed_pldi10_ex4 [1 | ⊤] + 2·[n | n ≥ 0] 0.007
speed_pldi10_fig2_1 [m + -y | m + -y ≥ 0] + [n + -x | n + -x ≥ 0] 0.008
speed_pldi10_fig2_2 [1 + x + -z | 1 + x + -z ≥ 0] + 2·[n + -x | n + -x ≥ 0] 0.007
speed_popl10_nstd_multiple [m·n + -m·x + -n·y + x·y | m + -y ≥ 0 ∧ n + -x ≥ 0] + [n + -x | n + -x ≥ 0] 0.188
speed_popl10_nstd_single 1/2·[n | n ≥ 0] + 1/2·[n^2 | n ≥ 0] 1.199
speed_popl10_smpl_multiple [m | m ≥ 0] + [n | n ≥ 0] 0.008
speed_popl10_smpl_single [n | n ≥ 0] 0.001
speed_popl10_smpl_single2 [m | m ≥ 0] + [n | n ≥ 0] 0.025
speed_popl10_sqntl_single 2·[n | n ≥ 0] 0.027
t07 [x | x ≥ 0] + 2·[x | x ≥ 0] + [y | y ≥ 0] + [1 | ⊤] 0.007
t08 [-y + z | -y + z ≥ 0] + [-2 + y | -2 + y ≥ 0] + [-y + z | -y + z ≥ 0] 0.006
t10 [x + -y | x + -y ≥ 0] 0.001
t11 [m + -y | m + -y ≥ 0] + [n + -x | n + -x ≥ 0] 0.008
t13 1/2·[x | x ≥ 0] + [x·y | x ≥ 0 ∧ y ≥ 0] + 1/2·[x^2 | x ≥ 0] 0.045
t15 [y ≥ 0] · [1 + x | 1 + x ≥ 0] 0.005
t16 [y ≥ 0] · 101·[1 + x | 1 + x ≥ 0] 0.006
t19 [-100 + i | -100 + i ≥ 0] + [51 + i + k | 51 + i + k ≥ 0] 0.007
t20 [-x + y | -x + y ≥ 0] + [x + -y | x + -y ≥ 0] 0.004
t27 [901 + y | 901 + y ≥ 0] + -1001·[n | -n ≥ 0] 0.005
t28 [x + -y | x + -y ≥ 0] + 1000·[x + -y | x + -y ≥ 0] + [y | y ≥ 0] + 1/2 + 1/2·[x^2 | -x ≥ 0] 0.037
t30 [y ≥ -1] · (1/2·[4 + 4·(y) + y^2 | 2 + y ≥ 0] + 2·[x | x ≥ 0] + 1/2·[x^2 | x ≥ 0]) 0.009
t37 [x | x ≥ 0] + [x | x ≥ 0] + [y | y ≥ 0] 0.006
t39 [1 | ⊤] + [y + -z | y + -z ≥ 0] 0.156
t46 [y | y ≥ 0] + [x | x ≥ 0] + [y | y ≥ 0] 0.011
t47 [1 | ⊤] + [n | n ≥ 0] 0.005
t61 [l·n + -7·(n) | -7 + l ≥ 0 ∧ n ≥ 0] + 7 0.019
t62 timeout 60.00