Skip to content

Portfolio

The engines are complementary: BMC finds counterexamples fastest, and k-induction, backward reachability and PDR each prove properties the others cannot. mk_portfolio() runs them all at the same time, and stops them as soon as one gives a conclusive answer.

portfolio = ctx.mk_portfolio()          # or engines=('kind', 'pdr'), max_depth=50
portfolio.add_target(bad)
portfolio.add_watch(c)
result = portfolio.reach_targets(timeout=60)
print(result, portfolio.get_last_engine(), portfolio.get_last_time())
if result == EngineResult.REACHABLE:
    print(portfolio.get_last_trace().get_as_dataframe(ctx.net2name))

The engines are bmc, kind (k-induction), br (backward reachability) and pdr, all of them by default; max_depth bounds the depths that bmc and kind try, which are unbounded by default. reach_targets() returns the first REACHABLE or UNREACHABLE answer, or UNKNOWN if every engine gives up or timeout seconds pass first; get_last_engine() says which engine answered, and get_last_errors() reports the engines that failed.

How it runs

Each engine runs in a process of its own, so the portfolio uses as many cores as engines, and stopping the others is immediate. Each process builds the circuit again from the recipe of the context: every context records the calls that build its circuit, so a circuit must be built through the methods of Context (as the Lustre and Structured Text translators and the parser do), not through intrepyd.api directly. The counterexample of a REACHABLE answer is rebuilt in the caller's context, by BMC at the depth the engine found, the first time get_last_trace() is called.

Guard the entry point

The processes are started with multiprocessing, which imports the main module of the program in each of them: a script that uses a portfolio must keep its top-level code under if __name__ == '__main__':.