omega icon indicating copy to clipboard operation
omega copied to clipboard

How does logic.past work?

Open thisiscam opened this issue 5 years ago • 3 comments

Hi, I was studying about past LTL operators, and ran into this repo. Could someone explain how the translate routine omega.logic.past works? Specifically, I wonder what is the returned tuple value and what does "initial condition" mean?

thisiscam avatar May 16 '20 16:05 thisiscam

About past LTL, please refer to the literature cited in the docstring of the module omega.logic.past:

https://github.com/tulip-control/omega/blob/v0.3.1/omega/logic/past.py#L4-L27

A usage example of this module is the function openpromela.logic.map_to_future:

https://github.com/johnyf/openpromela/blob/8a74b37b2b481cb9b1914fd9b2b4b38bb8162a73/openpromela/logic.py#L2272 (requires a previous version of omega https://github.com/tulip-control/omega/blob/v0.0.7/omega/logic/past.py)

of the package openpromela, which is described in a paper and a technical report.

The function omega.logic.past.translate

https://github.com/tulip-control/omega/blob/v0.3.1/omega/logic/past.py#L245

takes a formula that may contain past LTL operators and returns a translated formula with operators of future LTL, together with additional variables that work as memory for representing the past LTL operators, and formulas about these additional variables. These additional variables and formulas are called temporal testers.

The past operators are listed in the documentation

https://github.com/tulip-control/omega/blob/v0.3.1/doc/doc.md#temporal-logic-syntax

  • '-X' expr # weak previous
  • '--X' expr # strong previous
  • '-[]' expr # historically (dual of [])
  • '-<>' expr # once (dual of <>)
  • expr 'S' expr # since

For example, assuming that u is a variable, translating the operator "weak previous":

>>> from omega.logic import past

>>> past.translate('-X u')

({'u_prev1': {'type': 'bool', 'dom': None, 'owner': 'sys'}},
 'u_prev1',
 'u_prev1',
 '((X u_prev1) <=> u)',
 [])

The function omega.logic.past.translate returns a tuple with 5 items:

dvars, r, init, trans, win
  • dvars: dictionary of variable declarations. These are the additional memory variables added to represent the past LTL operators. In the example, u_prev1 is the additional variable, and is Boolean-valued.

  • r: translated formula in future LTL. In the example, -X u has been translated to u_prev1.

  • init: the initial condition applicable to the additional variable. Weak previous is TRUE at the initial state, so the initial condition is u_prev1. This initial condition together with the transition formula and the liveness formula describe the temporal tester.

  • trans: transition formula applicable to the additional variable. In the example, ((X u_prev1) <=> u). The operator X means "next" and can also be written using a prime u_prev1' <=> u. This formula requires that the next value of the memory variable u_prev1 be equal to the current value of the variable u. In other words, the variable u_prev1 remembers the previous value of variable u, and thus implements the operator "weak previous".

    This formula is understood to be placed within the operator []. So what trans means is []((X u_prev1) <=> u). The operator "always" has been omitted because the returned formulas are intended to be stored in an automaton instance, in the attributes aut.init, aut.action, and aut.win. The automaton represents a formula with initial condition, []action formulas, and []<>win formulas for each item in win.

    The automaton class is omega.symbolic.temporal.Automaton:

    https://github.com/tulip-control/omega/blob/v0.3.1/omega/symbolic/temporal.py#L36

  • win: recurrence formula, to be placed within []<>. The example of translating the operator "until" (below) includes a liveness formula (see also d7e8d1e7f833e0f0bfdf84d2109288995cadaac1).

Translating the operator "strong previous":

>>> past.translate('--X u')

({'u_prev1': {'type': 'bool', 'dom': None, 'owner': 'sys'}},
 'u_prev1',
 '(~ u_prev1)',
 '((X u_prev1) <=> u)',
 [])

Compared to the operator "weak previous", the operator "strong previous" is represented by a variable with initial value FALSE, as described by (~ u_prev1). The reason is that:

  • The operator "strong previous" (--X) is TRUE if there exists a previous state and the argument is TRUE in the previous state.

  • The operator "weak previous" (-X) is TRUE if there does not exist a previous state or there exists a previous state and the argument is TRUE in the previous state.

Translating the operator "historically":

>>> past.translate('-[] u')

({'_aux0': {'type': 'bool', 'dom': None, 'owner': 'sys'}},
 '(~ _aux0)',
 '(_aux0 <=> ( ~ u ))',
 '((X _aux0) <=> (    (X ( ~ u )) \\/ ((X True) /\\ _aux0)))',
 [])

The translation of the formula -[] u is ~ _aux0. The auxiliary variable _aux0 starts equal to ~ u (negated u) and becomes TRUE if ~ u becomes TRUE. After becoming TRUE, variable _aux0 remains TRUE. So variable _aux0 remembers whether u has become FALSE, and ~ _aux0 is TRUE if u has been uninterruptedly TRUE in the past.

Translating the operator "once":

>>> past.translate('-<> u')

({'_aux0': {'type': 'bool', 'dom': None, 'owner': 'sys'}},
 '_aux0',
 '(_aux0 <=> u)',
 '((X _aux0) <=> (    (X u) \\/ ((X True) /\\ _aux0)))',
 [])

The translation of the formula -<> u is _aux0. The auxiliary variable _aux0 starts equal to u and becomes TRUE if u becomes TRUE. After becoming TRUE, variable _aux0 remains TRUE. So variable _aux0 remembers whether u has become TRUE, and _aux0 is TRUE if u has been TRUE at least once in the past.

Translating the operator "since":

>>> past.translate('a S b')

({'_aux0': {'type': 'bool', 'dom': None, 'owner': 'sys'}},
 '_aux0',
 '(_aux0 <=> b)',
 '((X _aux0) <=> (    (X b) \\/ ((X a) /\\ _aux0)))',
 [])

The translation of the formula a S b is _aux0. The variable _aux0 becomes TRUE when b becomes TRUE. Afterwards, _aux0 remains TRUE as long as a is TRUE, until b become TRUE again. So variable _aux0 remembers if a has been uninterruptedly TRUE since the last state when b was TRUE.

The operator "until" can also be translated (for a closed system):

>>> past.translate('a U b', until=True)

({'_aux0': {'type': 'bool', 'dom': None, 'owner': 'sys'}},
 '_aux0',
 'True',
 '(_aux0 <=> (    (b) \\/ ((a) /\\ (X _aux0))))',
 ['((b) \\/ ~ _aux0)'])

An example with more than one temporal operator of past LTL:

>>> past.translate('-[] (a S b)')

({'_aux0': {'type': 'bool', 'dom': None, 'owner': 'sys'},
  '_aux1': {'type': 'bool', 'dom': None, 'owner': 'sys'}},
 '(~ _aux1)',
 '((_aux0 <=> b)) /\\ ((_aux1 <=> ( ~ _aux0 )))',
 '(((X _aux0) <=> (    (X b) \\/ ((X a) /\\ _aux0)))) /\\ (((X _aux1) <=> (    (X ( ~ _aux0 )) \\/ ((X True) /\\ _aux1))))',
 [])

johnyf avatar May 17 '20 13:05 johnyf

@johnyf Thanks for the detailed reply!

I think I understand now. Just to confirm, is the following correct:

>>> past.translate('a S b')

({'_aux0': {'type': 'bool', 'dom': None, 'owner': 'sys'}},
 '_aux0',
 '(_aux0 <=> b)',
 '((X _aux0) <=> (    (X b) \\/ ((X a) /\\ _aux0)))',
 [])

This is really saying that a S b is equivalent to _aux0, given that (_aux0 <=> b) /\\ []((X _aux0) <=> ( (X b) \\/ ((X a) /\\ _aux0))) (initial condition conjuncted with "always transition relation".

In other words, I think another way to say this is that a S b is equivalent to

exists _aux0, (_aux0 <=> b) /\\ []((X _aux0) <=> (    (X b) \\/ ((X a) /\\ _aux0)))

(where I existentially quantified _aux0 to project it out)

thisiscam avatar May 17 '20 23:05 thisiscam

This is correct provided that the translated formula a S b occur within the scope of the temporal existential quantifier.

Stutter-invariant temporal existential quantification in the temporal logic of actions, TLA+ is denoted by \EE. Please refer to this book about TLA+ and this paper about TLA.

If we denote stutter-sensitive temporal existential quantification in raw TLA+ by \ee, then the translation of the formula a S b could be written as follows (in TLA+ prime ' denotes the operator "next"):

\ee _aux0:  /\ _aux0
            /\ (_aux0 <=> b)
            /\ []((_aux0') <=> ((b') \/ ((a') /\ _aux0)))

The translation of past temporal operators is based on Section 5 of this paper. Quantified propositional temporal logic (QPTL) is described in this paper.

Another example would be translating the formula [](a S b).

>>> past.translate('[] (a S b)')

({'_aux0': {'type': 'bool', 'dom': None, 'owner': 'sys'}},
 '( [] _aux0 )',
 '(_aux0 <=> b)',
 '((X _aux0) <=> (    (X b) \\/ ((X a) /\\ _aux0)))',
 [])

The translated formula in raw TLA+ would be (== in TLA+ denotes a definition)

TranslatedFormula ==
    \ee _aux0:
        /\ [] _aux0
        /\ _aux0 <=> b
        /\ [](_aux0' <=> (b' \/ (a' /\ _aux0)))

The formula [](a S b) is equivalent to the translated formula, i.e.,

OriginalFormula == [](a S b)

THEOREM
    OriginalFormula <=> TranslatedFormula

The TLA+ proof language is described in this document.

The variable _aux0 could be considered a history-determined variable in raw TLA+. History-determined variables in TLA are discussed in Section 2.4 of this paper.

Realizability of properties with quantified history-determined variables is preserved when "unhiding" the history-determined variables by removing the temporal quantifier, as discussed in Section 9.7 on pages 154--157 of this dissertation (Table 3.2 on page 18 lists temporal and rigid quantifiers).

So GR(1) synthesis can be used when past temporal operators occur, after applying the translation of the past temporal operators and the addition of the auxiliary, history-determined variables.

The preservation of realizability when unhiding history-determined variables is described in detail in the module HistoryIsRealizable in the supplemental material, in particular the theorem RealizingHistory.

A definition of stutter-sensitive temporal quantification is included on page 4 of the module TemporalLogic in the supplemental material.

johnyf avatar May 18 '20 17:05 johnyf