Repository navigation
Expand file tree
/
Copy pathgenerate.py
More file actions
137 lines (106 loc) · 4.47 KB
/
Copy pathgenerate.py
File metadata and controls
137 lines (106 loc) · 4.47 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
"""Generate narrowing benchmark problems from the Alloy4Fun dataset."""
import argparse
import json
from pathlib import Path
from alloy import AlloyParseError, parse
# Validation only needs a positive bound to exercise the same Alloy 6 -> Alloy 5
# translation used by narrow.py. The generated problem does not embed this bound.
VALIDATION_TRACE_LENGTH = 1
def argument_parser():
parser = argparse.ArgumentParser(
description="Generate benchmark Alloy models from dataset.json."
)
parser.add_argument("min_size", type=int)
parser.add_argument("max_size", type=int)
parser.add_argument("step_size", type=int)
parser.add_argument("models", nargs="+")
parser.add_argument(
"--mode",
choices=("static", "temporal"),
default="static",
help=(
"validation mode: static rejects Alloy 6 temporal features; temporal "
"accepts the bounded temporal subset supported by narrow.py"
),
)
return parser
def _trace_length(mode):
return VALIDATION_TRACE_LENGTH if mode == "temporal" else None
def _alloy5_error(content):
"""Return an AlloyMax parse/type error for translated content, if any."""
import jpype
import jpype.imports
if not jpype.isJVMStarted():
jar = Path(__file__).with_name("AlloyMax-1.0.2.jar")
jpype.startJVM(classpath=[str(jar)])
from edu.mit.csail.sdg.alloy4 import Err
from edu.mit.csail.sdg.parser import CompUtil
try:
CompUtil.parseEverything_fromString(None, content)
return None
except Err as error:
return str(error.msg)
def validation_error(content, mode):
"""Return why the narrowing input path rejects content, or None."""
try:
collector = parse(content, trace_length=_trace_length(mode))
except AlloyParseError as error:
return str(error)
return _alloy5_error(collector.content)
def specification_error(base, specification, mode):
"""Validate one candidate in the same predicate shape written to a problem."""
candidate = f"{base}\npred __generate_candidate {{ {specification} }}"
return validation_error(candidate, mode)
def generate_problems(dataset, sizes, model_ids, mode, output_dir=Path("problems")):
"""Generate nested candidate sets only for requirements supporting every size."""
sizes = tuple(sizes)
if not sizes:
return []
maximum_size = max(sizes)
generated = []
selected = set(model_ids)
for example in (item for item in dataset if item["id"] in selected):
base_model = example["model"]
error = validation_error(base_model, mode)
if error is not None:
print(f"Skipped model {example['id']}: {error}")
continue
for requirement in example["requirements"]:
specifications = []
for specification in [requirement["oracle"], *requirement["erroneous"]]:
error = specification_error(base_model, specification, mode)
if error is None:
specifications.append(specification)
else:
print(
"Discarded specification for "
f"{example['id']} {requirement['pred']}: {error}"
)
if len(specifications) < maximum_size:
print(
f"Skipped requirement {example['id']} "
f"{requirement['pred']}: only {len(specifications)} "
"supported specifications are available for maximum "
f"requested size {maximum_size}."
)
continue
for size in sizes:
model = base_model
for index, specification in enumerate(specifications[:size]):
model += f"\npred S{index} {{ {specification} }}\n"
output_dir.mkdir(parents=True, exist_ok=True)
requirement_name = requirement["pred"][5:]
output = output_dir / (
f"{example['id']}_{requirement_name}_{size}.als"
)
output.write_text(model)
generated.append(output)
return generated
def main(argv=None):
args = argument_parser().parse_args(argv)
sizes = range(args.min_size, args.max_size + 1, args.step_size)
with open("dataset.json", "r") as file:
dataset = json.load(file)
generate_problems(dataset, sizes, args.models, args.mode)
if __name__ == "__main__":
main()