Skip to content
This repository was archived by the owner on Apr 25, 2024. It is now read-only.

Commit e822ed3

Browse files
Merge branch 'master' into fix-deduplicate-productions
2 parents 7412737 + 606bd70 commit e822ed3

File tree

3 files changed

+282
-39
lines changed

3 files changed

+282
-39
lines changed

src/pyk/proof/proof.py

Lines changed: 66 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import logging
55
from abc import ABC, abstractmethod
66
from enum import Enum
7+
from itertools import chain
78
from typing import TYPE_CHECKING
89

910
from ..utils import hash_file, hash_str
@@ -29,20 +30,33 @@ class Proof(ABC):
2930

3031
id: str
3132
proof_dir: Path | None
33+
_subproofs: dict[str, Proof]
3234

33-
def __init__(self, id: str, proof_dir: Path | None = None) -> None:
35+
def __init__(self, id: str, proof_dir: Path | None = None, subproof_ids: Iterable[str] = ()) -> None:
3436
self.id = id
3537
self.proof_dir = proof_dir
38+
self._subproofs = {}
39+
if self.proof_dir is None and len(list(subproof_ids)) > 0:
40+
raise ValueError(f'Cannot read subproofs {subproof_ids} of proof {self.id} with no proof_dir')
41+
if len(list(subproof_ids)) > 0:
42+
for proof_id in subproof_ids:
43+
self.fetch_subproof(proof_id, force_reread=True)
3644

37-
def write_proof(self) -> None:
45+
@property
46+
def subproof_ids(self) -> list[str]:
47+
return [sp.id for sp in self._subproofs.values()]
48+
49+
def write_proof(self, subproofs: bool = False) -> None:
3850
if not self.proof_dir:
3951
return
4052
proof_path = self.proof_dir / f'{hash_str(self.id)}.json'
41-
if not self.up_to_date():
53+
if not self.up_to_date:
4254
proof_json = json.dumps(self.dict)
4355
proof_path.write_text(proof_json)
44-
self._last_modified = proof_path.stat().st_mtime_ns
4556
_LOGGER.info(f'Updated proof file {self.id}: {proof_path}')
57+
if subproofs:
58+
for sp in self.subproofs:
59+
sp.write_proof(subproofs=subproofs)
4660

4761
@staticmethod
4862
def proof_exists(id: str, proof_dir: Path) -> bool:
@@ -53,32 +67,69 @@ def proof_exists(id: str, proof_dir: Path) -> bool:
5367
def digest(self) -> str:
5468
return hash_str(json.dumps(self.dict))
5569

56-
def up_to_date(self, check_method: str = 'checksum') -> bool:
70+
@property
71+
def up_to_date(self) -> bool:
5772
"""
5873
Check that the proof's representation on disk is up-to-date.
59-
By default, compares the file timestamp to self._last_modified. Use check_method = 'checksum' to compare hashes instead.
6074
"""
6175
if self.proof_dir is None:
6276
raise ValueError(f'Cannot check if proof {self.id} with no proof_dir is up-to-date')
6377
proof_path = self.proof_dir / f'{hash_str(id)}.json'
6478
if proof_path.exists() and proof_path.is_file():
65-
match check_method:
66-
case 'checksum':
67-
return self.digest == hash_file(proof_path)
68-
case _: # timestamp
69-
return self._last_modified == proof_path.stat().st_mtime_ns
79+
return self.digest == hash_file(proof_path)
7080
else:
7181
return False
7282

83+
def add_subproof(self, proof_id: str) -> None:
84+
if self.proof_dir is None:
85+
raise ValueError(f'Cannot add subproof to the proof {self.id} with no proof_dir')
86+
assert self.proof_dir
87+
if not Proof.proof_exists(proof_id, self.proof_dir):
88+
raise ValueError(f"Cannot find subproof {proof_id} in parent proof's {self.id} proof_dir {self.proof_dir}")
89+
self._subproofs[proof_id] = self.fetch_subproof(proof_id, force_reread=True)
90+
91+
def remove_subproof(self, proof_id: str) -> None:
92+
del self._subproofs[proof_id]
93+
94+
def fetch_subproof(
95+
self, proof_id: str, force_reread: bool = False, uptodate_check_method: str = 'timestamp'
96+
) -> Proof:
97+
"""Get a subproof, re-reading from disk if it's not up-to-date"""
98+
99+
if self.proof_dir is not None and (force_reread or not self._subproofs[proof_id].up_to_date):
100+
updated_subproof = Proof.read_proof(proof_id, self.proof_dir)
101+
self._subproofs[proof_id] = updated_subproof
102+
return updated_subproof
103+
else:
104+
return self._subproofs[proof_id]
105+
106+
@property
107+
def subproofs(self) -> Iterable[Proof]:
108+
"""Return the subproofs, re-reading from disk the ones that changed"""
109+
return self._subproofs.values()
110+
111+
@property
112+
def subproofs_status(self) -> ProofStatus:
113+
any_subproof_failed = any([p.status == ProofStatus.FAILED for p in self.subproofs])
114+
any_subproof_pending = any([p.status == ProofStatus.PENDING for p in self.subproofs])
115+
if any_subproof_failed:
116+
return ProofStatus.FAILED
117+
elif any_subproof_pending:
118+
return ProofStatus.PENDING
119+
else:
120+
return ProofStatus.PASSED
121+
73122
@property
74123
@abstractmethod
75124
def status(self) -> ProofStatus:
76125
...
77126

78127
@property
79-
@abstractmethod
80128
def dict(self) -> dict[str, Any]:
81-
...
129+
return {
130+
'id': self.id,
131+
'subproof_ids': self.subproof_ids,
132+
}
82133

83134
@classmethod
84135
@abstractmethod
@@ -107,7 +158,5 @@ def json(self) -> str:
107158

108159
@property
109160
def summary(self) -> Iterable[str]:
110-
return [
111-
f'Proof: {self.id}',
112-
f' status: {self.status}',
113-
]
161+
subproofs_summaries = [subproof.summary for subproof in self.subproofs]
162+
return chain([f'Proof: {self.id}', f' status: {self.status}'], *subproofs_summaries)

src/pyk/proof/reachability.py

Lines changed: 63 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import json
44
import logging
5+
from itertools import chain
56
from typing import TYPE_CHECKING
67

78
from pyk.kore.rpc import LogEntry
@@ -51,8 +52,9 @@ def __init__(
5152
logs: dict[int, tuple[LogEntry, ...]],
5253
terminal_nodes: Iterable[NodeIdLike] | None = None,
5354
proof_dir: Path | None = None,
55+
subproof_ids: Iterable[str] = (),
5456
):
55-
super().__init__(id, proof_dir=proof_dir)
57+
super().__init__(id, proof_dir=proof_dir, subproof_ids=subproof_ids)
5658
self.kcfg = kcfg
5759
self.init = init
5860
self.target = target
@@ -90,24 +92,36 @@ def read_proof(id: str, proof_dir: Path) -> APRProof:
9092
def status(self) -> ProofStatus:
9193
if len(self.terminal) > 0:
9294
return ProofStatus.FAILED
93-
elif len(self.pending) > 0:
95+
elif len(self.pending) > 0 or self.subproofs_status == ProofStatus.PENDING:
9496
return ProofStatus.PENDING
9597
else:
96-
return ProofStatus.PASSED
98+
if self.subproofs_status == ProofStatus.PASSED:
99+
return ProofStatus.PASSED
100+
else:
101+
return ProofStatus.FAILED
97102

98103
@classmethod
99104
def from_dict(cls: type[APRProof], dct: Mapping[str, Any], proof_dir: Path | None = None) -> APRProof:
100105
cfg = KCFG.from_dict(dct['cfg'])
101-
terminal_nodes = dct['terminal_nodes']
102106
init_node = dct['init']
107+
terminal_nodes = dct['terminal_nodes']
103108
target_node = dct['target']
104109
id = dct['id']
110+
subproof_ids = dct['subproof_ids'] if 'subproof_ids' in dct else []
105111
if 'logs' in dct:
106112
logs = {k: tuple(LogEntry.from_dict(l) for l in ls) for k, ls in dct['logs'].items()}
107113
else:
108114
logs = {}
109-
110-
return APRProof(id, cfg, init_node, target_node, logs, terminal_nodes=terminal_nodes, proof_dir=proof_dir)
115+
return APRProof(
116+
id,
117+
cfg,
118+
init_node,
119+
target_node,
120+
logs,
121+
terminal_nodes=terminal_nodes,
122+
proof_dir=proof_dir,
123+
subproof_ids=subproof_ids,
124+
)
111125

112126
@staticmethod
113127
def from_claim(defn: KDefinition, claim: KClaim, *args: Any, **kwargs: Any) -> APRProof:
@@ -130,29 +144,34 @@ def path_constraints(self, final_node_id: NodeIdLike) -> KInner:
130144

131145
@property
132146
def dict(self) -> dict[str, Any]:
147+
dct = super().dict
148+
dct['type'] = 'APRProof'
149+
dct['cfg'] = self.kcfg.to_dict()
150+
dct['init'] = self.init
151+
dct['target'] = self.target
152+
dct['terminal_nodes'] = self._terminal_nodes
133153
logs = {k: [l.to_dict() for l in ls] for k, ls in self.logs.items()}
134-
return {
135-
'type': 'APRProof',
136-
'id': self.id,
137-
'cfg': self.kcfg.to_dict(),
138-
'init': self.init,
139-
'target': self.target,
140-
'terminal_nodes': self._terminal_nodes,
141-
'logs': logs,
142-
}
154+
dct['logs'] = logs
155+
return dct
143156

144157
def add_terminal(self, nid: NodeIdLike) -> None:
145158
self._terminal_nodes.append(self.kcfg._resolve(nid))
146159

147160
@property
148161
def summary(self) -> Iterable[str]:
149-
return [
162+
subproofs_summaries = chain(subproof.summary for subproof in self.subproofs)
163+
yield from [
150164
f'APRProof: {self.id}',
151165
f' status: {self.status}',
152166
f' nodes: {len(self.kcfg.nodes)}',
167+
f' frontier: {len(self.kcfg.frontier)}',
168+
f' stuck: {len(self.kcfg.stuck)}',
169+
'Subproofs:' if len(self.subproof_ids) else '',
153170
f' pending: {len(self.pending)}',
154171
f' terminal: {len(self.terminal)}',
155172
]
173+
for summary in subproofs_summaries:
174+
yield from summary
156175

157176

158177
class APRBMCProof(APRProof):
@@ -171,8 +190,9 @@ def __init__(
171190
bmc_depth: int,
172191
bounded_nodes: Iterable[int] | None = None,
173192
proof_dir: Path | None = None,
193+
subproof_ids: Iterable[str] = (),
174194
):
175-
super().__init__(id, kcfg, init, target, logs, proof_dir=proof_dir)
195+
super().__init__(id, kcfg, init, target, logs, proof_dir=proof_dir, subproof_ids=subproof_ids)
176196
self.bmc_depth = bmc_depth
177197
self._bounded_nodes = list(bounded_nodes) if bounded_nodes is not None else []
178198

@@ -202,26 +222,40 @@ def is_bounded(self, node_id: NodeIdLike) -> bool:
202222

203223
@property
204224
def status(self) -> ProofStatus:
205-
if len(self.terminal) > 0:
225+
if (
226+
any(nd.id not in self._bounded_nodes for nd in self.kcfg.stuck)
227+
or self.subproofs_status == ProofStatus.FAILED
228+
):
206229
return ProofStatus.FAILED
207-
elif len(self.pending) > 0:
230+
elif len(self.pending) > 0 or self.subproofs_status == ProofStatus.PENDING:
208231
return ProofStatus.PENDING
209232
else:
210233
return ProofStatus.PASSED
211234

212235
@classmethod
213236
def from_dict(cls: type[APRBMCProof], dct: Mapping[str, Any], proof_dir: Path | None = None) -> APRBMCProof:
214237
cfg = KCFG.from_dict(dct['cfg'])
215-
id = dct['id']
216238
init = dct['init']
217239
target = dct['target']
218240
bounded_nodes = dct['bounded_nodes']
219241
bmc_depth = dct['bmc_depth']
242+
id = dct['id']
243+
subproof_ids = dct['subproof_ids'] if 'subproof_ids' in dct else []
220244
if 'logs' in dct:
221245
logs = {k: tuple(LogEntry.from_dict(l) for l in ls) for k, ls in dct['logs'].items()}
222246
else:
223247
logs = {}
224-
return APRBMCProof(id, cfg, init, target, logs, bmc_depth, bounded_nodes=bounded_nodes, proof_dir=proof_dir)
248+
return APRBMCProof(
249+
id,
250+
cfg,
251+
init,
252+
target,
253+
logs,
254+
bmc_depth,
255+
bounded_nodes=bounded_nodes,
256+
proof_dir=proof_dir,
257+
subproof_ids=subproof_ids,
258+
)
225259

226260
@staticmethod
227261
def from_claim_with_bmc_depth(defn: KDefinition, claim: KClaim, bmc_depth: int) -> APRBMCProof:
@@ -243,14 +277,21 @@ def add_bounded(self, nid: NodeIdLike) -> None:
243277

244278
@property
245279
def summary(self) -> Iterable[str]:
246-
return [
280+
subproofs_summaries = chain(subproof.summary for subproof in self.subproofs)
281+
yield from [
247282
f'APRBMCProof(depth={self.bmc_depth}): {self.id}',
248283
f' status: {self.status}',
249284
f' nodes: {len(self.kcfg.nodes)}',
285+
f' frontier: {len(self.kcfg.frontier)}',
286+
f' stuck: {len([nd for nd in self.kcfg.stuck if nd.id not in self._bounded_nodes])}',
287+
f' bmc-depth-bounded: {len(self._bounded_nodes)}',
288+
'Subproofs' if len(self.subproof_ids) else '',
250289
f' pending: {len(self.pending)}',
251290
f' terminal: {len(self.terminal)}',
252291
f' bounded: {len(self.bounded)}',
253292
]
293+
for summary in subproofs_summaries:
294+
yield from summary
254295

255296

256297
class APRProver:

0 commit comments

Comments
 (0)