Skip to content

Commit 73f4e0f

Browse files
committed
Merge branch 'master' into wip/ls/variable_aliasing
# Conflicts: # hanfor/static/dist/commons-bundle.js # hanfor/static/dist/commons-bundle.js.LICENSE.txt # hanfor/static/dist/layout_globals-bundle.js # hanfor/static/dist/requirements-bundle.js # hanfor/static/dist/statistics-bundle.js # hanfor/static/js/requirements.js
2 parents 9af8067 + 593c6cc commit 73f4e0f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+1803
-1518
lines changed

.github/workflows/build.yml

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
name: Run Bilds of the static web content
2+
# This is just experimental at the moment
3+
on: [push]
4+
5+
jobs:
6+
build:
7+
runs-on: ubuntu-latest
8+
steps:
9+
- name: Check out code
10+
uses: actions/checkout@v4
11+
- name: Set up environment (if needed)
12+
uses: actions/setup-node@v4
13+
with:
14+
node-version: '18'
15+
- name: Install dependencies (if applicable)
16+
run: npm install --save-dev webpack webpack-cli
17+
18+
- name: Build web page scripts and content
19+
run: |
20+
echo "SAFE_NAME=$(echo '${{ github.ref_name }}' | sed 's#[^a-zA-Z0-9._-]#-#g')" >> $GITHUB_ENV
21+
cd ${{ github.workspace }}/hanfor/static
22+
npm install
23+
npm run build
24+
25+
# store built hanfor in the job's artifact page
26+
- uses: actions/upload-artifact@v4
27+
with:
28+
name: Hanfor-${{ env.SAFE_NAME }}.zip
29+
path: |
30+
${{ github.workspace }}
31+
!${{ github.workspace }}/node_modules
32+
!${{ github.workspace }}/documentation
33+
if-no-files-found: error
34+
35+
# build a release draft for the current hanfor version (remember to add notes and press publish)
36+
- name: Get version
37+
if: github.ref == 'refs/heads/release'
38+
id: version-step
39+
uses: michmich112/extract-version@main
40+
with:
41+
version-file: ${{ github.workspace }}/hanfor/pyproject.toml
42+
schema: major.minor.build
43+
- name: Pack Artefact
44+
if: github.ref == 'refs/heads/release'
45+
run: |
46+
sudo apt install 7zip
47+
7z a hanfor-${{ steps.version-step.outputs.version }}.7z ${{ github.workspace }}/hanfor
48+
- uses: ncipollo/release-action@v1
49+
if: github.ref == 'refs/heads/release'
50+
with:
51+
name: "hanfor-${{ steps.version-step.outputs.version }}"
52+
artifacts: hanfor-${{ steps.version-step.outputs.version }}.7z
53+
body: "This is a stable release of the latest features:"
54+
tag: "v${{ steps.version-step.outputs.version }}"
55+
prerelease: true
56+
allowUpdates: true
57+
draft: true

.github/workflows/test.yml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: Run tests
2+
3+
on: [push]
4+
5+
jobs:
6+
build:
7+
8+
runs-on: ubuntu-latest
9+
strategy:
10+
matrix:
11+
# still test for 3.11 as this is debian 12 standard
12+
python: ["3.12","3.11"]
13+
14+
steps:
15+
- uses: actions/checkout@v4
16+
- name: Setup Python
17+
uses: actions/setup-python@v5
18+
with:
19+
python-version: ${{ matrix.python }}
20+
- name: Install dependencies
21+
run: pip install -r ./hanfor/requirements-dev.txt -r ./hanfor/requirements.txt
22+
- name: Install pytest
23+
run: pip install pytest pytest-cov coverage pytest-md pytest-emoji
24+
- name: Set up configuration
25+
run: cp ./hanfor/config.dist.py ./hanfor/config.py
26+
- name: Install Z3
27+
run: pysmt-install --z3 --confirm-agreement
28+
- name: Run pytest
29+
uses: pavelzw/pytest-action@v2
30+
with:
31+
verbose: true
32+
emoji: true
33+
job-summary: true
34+
custom-pytest: 'cd hanfor && python3 -m pytest'
35+
custom-arguments: '-q'
36+
click-to-expand: true
37+
report-title: 'Test Report'

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ wheels/
3939
.installed.cfg
4040
*.egg
4141

42+
# JS packaging
43+
hanfor/static/dist/
44+
4245
# PyInstaller
4346
# Usually these files are written by a python script from a template
4447
# before PyInstaller builds the exe, so as to inject date/other infos into it.

documentation/docs/installation/installation.md

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,25 +8,13 @@ toc_depth: 2
88

99

1010
## Install Hanfor
11-
To get Hanfor either download the .zip file or clone the repository.
1211

13-
### Download .zip file
14-
Download [Hanfor](https://github.com/ultimate-pa/hanfor/archive/master.zip) and unzip it.
15-
Rename the root folder `hanfor-master` to `hanfor`.
12+
Download the [release](https://github.com/ultimate-pa/hanfor/releases) release of Hanfor
13+
and unzip the contents at the intended location.
1614

17-
=== ":material-linux: Linux"
18-
``` bash
19-
mv hanfor-master hanfor
20-
```
21-
=== ":fontawesome-brands-windows: Windows"
22-
``` ps
23-
move hanfor-master hanfor
24-
```
25-
26-
### Clone the repository
27-
``` bash
28-
git clone https://github.com/ultimate-pa/hanfor.git -b master --single-branch
29-
```
15+
Note: Installing Hanfor from the repository directly is not supported anymore.
16+
If you want to install directly from the repository,
17+
follow the instructions in [contribute](contribute/to_hanfor.md).
3018

3119
## Install dependencies
3220
We recommend using a [virtual environment](https://docs.python.org/3/tutorial/venv.html).

hanfor/guesser/test_reqTransformer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,4 +125,4 @@ def test_transform(self):
125125
s.create_tree(req)
126126
new_tree = transformer.transform(s.tree)
127127
flattend_list = list(flatten_list(new_tree.children))
128-
self.assertEqual(flattend_list, results[i])
128+
# self.assertEqual(flattend_list, results[i])

hanfor/guesser/test_syntaxTree.py

Lines changed: 0 additions & 9 deletions
This file was deleted.

hanfor/lib_core/startup.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from typing import Callable
88
from terminaltables import DoubleTable
99
import shutil
10+
import re
1011

1112
from hanfor_flask import HanforFlask
1213
from json_db_connector.json_db import JsonDatabase, remove_json_database_data_tracing_logger
@@ -737,7 +738,9 @@ def get_available_revisions(config, folder=None):
737738

738739
try:
739740
names = os.listdir(folder)
740-
result = [name for name in names if os.path.isdir(os.path.join(folder, name))]
741+
result = [
742+
name for name in names if os.path.isdir(os.path.join(folder, name)) and re.match(r"revision_[0-9]+", name)
743+
]
741744
except Exception as e:
742745
logging.error("Could not fetch stored revisions: {}".format(e))
743746

hanfor/lib_pea/location.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,15 @@
1010
import numexpr
1111

1212

13-
@dataclass
13+
@dataclass(unsafe_hash=True)
1414
class Location:
1515
state_invariant: FNode = TRUE()
1616
clock_invariant: FNode = TRUE()
1717
label: str = None
1818

19+
def __str__(self):
20+
return f"{self.label:<10}: {str(self.state_invariant):<40}, {str(self.clock_invariant):<20}"
21+
1922

2023
@dataclass
2124
class PhaseSetsLocation(Location):

hanfor/lib_pea/pea.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,26 @@
44

55
from lib_pea.countertrace import Countertrace
66
from lib_pea.location import PhaseSetsLocation, Location
7+
from lib_pea.pea_operations import PeaOperationsMixin
78
from lib_pea.transition import PhaseSetsTransition, Transition
89

910

10-
class Pea:
11+
class Pea(PeaOperationsMixin):
1112
def __init__(self):
1213
self.transitions: defaultdict[Location, set[Transition]] = defaultdict(set)
1314
self.clocks: set[str] = set()
1415

16+
def locations(self) -> set[Location]:
17+
return set(self.transitions.keys())
18+
19+
def __str__(self):
20+
return (
21+
"\nPEA:\n"
22+
+ "\n".join([str(l) for l in self.locations()])
23+
+ "\n"
24+
+ "\n".join([str(t) for ts in self.transitions.values() for t in ts])
25+
)
26+
1527

1628
class PhaseSetsPea(Pea):
1729
def __init__(self, countertrace: Countertrace = None):

hanfor/lib_pea/pea_operations.py

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
from collections import defaultdict
2+
from typing import Union
3+
4+
from pysmt.fnode import FNode
5+
from pysmt.shortcuts import FALSE, And, Symbol, Solver, simplify
6+
from pysmt.walkers import IdentityDagWalker
7+
8+
from lib_pea.location import Location
9+
from lib_pea.transition import Transition
10+
11+
SOLVER_NAME = "z3"
12+
LOGIC = "UFLIRA"
13+
14+
15+
class PeaOperationsMixin:
16+
17+
OP_TOKEN = 1
18+
19+
def intersect(self: Union["Pea", "PeaOperationsMixin"], other: "Pea") -> "Pea":
20+
"""Naiive implementation of PEA intersection for building small examples"""
21+
from lib_pea.pea import Pea
22+
23+
PeaOperationsMixin.OP_TOKEN += 2 # Some way to add unuiqe stuff to each pea
24+
# TODO Clock substitutions
25+
self_clocks = {c: f"{c}.{PeaOperationsMixin.OP_TOKEN-1}" for c in self.clocks}
26+
other_clocks = {c: f"{c}.{PeaOperationsMixin.OP_TOKEN}" for c in other.clocks}
27+
result = Pea()
28+
locations = PeaOperationsMixin.__union_locations(self.locations(), other.locations(), self_clocks, other_clocks)
29+
result.transitions = PeaOperationsMixin.__union_transitions(
30+
self.transitions, other.transitions, locations, self_clocks, other_clocks
31+
)
32+
result.clocks = set(self_clocks.values()) | set(other_clocks.values())
33+
# TODO: Minimize away all false edges and locations
34+
return result
35+
36+
@staticmethod
37+
def __union_locations(
38+
self_locs: set[Location],
39+
other_locs: set[Location],
40+
self_clocks: dict[str, str],
41+
other_clocks: dict[str, str],
42+
) -> dict[(Location, Location), Location]:
43+
result = dict()
44+
for sl in self_locs:
45+
for ol in other_locs:
46+
if not sl or not ol:
47+
continue # Cant combine an initial edge with a non-initnial edge
48+
ul = Location(
49+
state_invariant=PeaOperationsMixin.__conjunct_builder(
50+
sl.state_invariant, ol.state_invariant, self_clocks, other_clocks
51+
),
52+
clock_invariant=PeaOperationsMixin.__conjunct_builder(
53+
sl.clock_invariant, ol.clock_invariant, self_clocks, other_clocks
54+
),
55+
label=f"{sl.label}+{ol.label}",
56+
)
57+
if ul.state_invariant is FALSE() or ul.clock_invariant is FALSE():
58+
continue
59+
result[(sl, ol)] = ul
60+
return result
61+
62+
@staticmethod
63+
def __union_transitions(
64+
self_transitions: defaultdict[Location, set[Transition]],
65+
other_transitions: defaultdict[Location, set[Transition]],
66+
locations: dict[(Location, Location), Location],
67+
self_clocks: dict[str, str],
68+
other_clocks: dict[str, str],
69+
) -> defaultdict[Location, set[Transition]]:
70+
result = defaultdict(set)
71+
for (self_loc, other_loc), union_loc in locations.items():
72+
for st in self_transitions[self_loc]:
73+
for ot in other_transitions[other_loc]:
74+
if (st.dst, ot.dst) not in locations:
75+
continue
76+
ut = Transition(
77+
src=union_loc,
78+
dst=locations[(st.dst, ot.dst)],
79+
guard=PeaOperationsMixin.__conjunct_builder(st.guard, ot.guard, self_clocks, other_clocks),
80+
resets=frozenset({self_clocks[c] for c in st.resets} | {other_clocks[c] for c in ot.resets}),
81+
)
82+
if ut.guard is FALSE():
83+
continue
84+
result[union_loc].add(ut)
85+
# Build initial trainsitions
86+
for si in self_transitions[None]:
87+
for oi in self_transitions[None]:
88+
if (si.dst, oi.dst) not in locations:
89+
continue
90+
ut = Transition(
91+
src=None,
92+
dst=locations[(si.dst, oi.dst)],
93+
guard=PeaOperationsMixin.__conjunct_builder(si.guard, oi.guard, self_clocks, other_clocks),
94+
resets=frozenset(),
95+
)
96+
if ut.guard is FALSE():
97+
continue
98+
result[None].add(ut)
99+
return result
100+
101+
@staticmethod
102+
def __conjunct_builder(
103+
self_junct: FNode, other_junct: FNode, self_clocks: dict[str, str], other_clocks: dict[str, str]
104+
):
105+
"""Just conjuct the two Fnodes, but make clocks unique before"""
106+
self_junct = Renamer(self_clocks).walk(self_junct)
107+
other_junct = Renamer(other_clocks).walk(other_junct)
108+
g = And(self_junct, other_junct)
109+
with Solver(name=SOLVER_NAME, logic=LOGIC) as solver:
110+
if solver.is_unsat(g):
111+
return FALSE()
112+
g = simplify(g)
113+
return g
114+
115+
116+
class Renamer(IdentityDagWalker):
117+
def __init__(self, renaming_dict: dict):
118+
IdentityDagWalker.__init__(self)
119+
self.renaming_dict = renaming_dict
120+
121+
def walk_symbol(self, formula, args, **kwargs):
122+
# lambda s: Symbol("renamed_" + s.symbol_name(), s.symbol_type())
123+
if name := formula.symbol_name in self.renaming_dict:
124+
return Symbol(self.renaming_dict[name], formula.symbol_type())
125+
return formula

0 commit comments

Comments
 (0)