├── .gitattributes ├── .gitignore ├── README.md ├── conformance-tests ├── generateTestTemplate.py ├── pfa-tests.json ├── runTest.py ├── runTestHadrian.py └── runTestTitus.py ├── libfcns.xml ├── make-specification ├── fastFill_libfcns.py ├── libfcns.tex ├── pfa-specification-source.tex └── pfa-specification.tex └── pfa-specification.pdf /.gitattributes: -------------------------------------------------------------------------------- 1 | 2 | conformance-tests/pfa-tests.json filter=lfs diff=lfs merge=lfs -text 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.aux 3 | *.log 4 | *.out 5 | *.toc 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Specification of the Portable Format for Analytics (PFA). See the documentation website for more information: http://dmg.org/pfa/index.html. 2 | -------------------------------------------------------------------------------- /conformance-tests/pfa-tests.json: -------------------------------------------------------------------------------- 1 | Please download this file from: 2 | 3 | http://github.com/datamininggroup/pfa/releases/download/0.8.1/pfa-tests.json 4 | -------------------------------------------------------------------------------- /conformance-tests/runTest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import json 4 | import base64 5 | import math 6 | 7 | # NOTE: Due to limitations in JSON, the following substitutions must be made. 8 | # (JSON can only store finite numbers and legal Unicode strings.) 9 | # 10 | # When expecting a "float" or "double": 11 | # "inf" --> floating-point positive infinity 12 | # "-inf" --> floating-point negative infinity 13 | # "nan" --> floating-point not a number 14 | # 15 | # When expecting a "bytes" or "fixed": 16 | # content must be base64-decoded 17 | 18 | def convertIn(x, t): 19 | if t == "float" or t == "double": 20 | if x == "inf": 21 | return float("inf") 22 | elif x == "-inf": 23 | return float("-inf") 24 | elif x == "nan": 25 | return float("nan") 26 | else: 27 | return x 28 | 29 | elif t == "bytes" or (isinstance(t, dict) and t["type"] == "fixed"): 30 | return base64.b64decode(x) 31 | 32 | elif isinstance(t, dict) and t["type"] == "array": 33 | if not isinstance(x, list): raise Exception 34 | return [convertIn(v, t["items"]) for v in x] 35 | 36 | elif isinstance(t, dict) and t["type"] == "map": 37 | if not isinstance(x, dict): raise Exception 38 | return dict((k, convertIn(v, t["values"])) for k, v in x.items()) 39 | 40 | elif isinstance(t, dict) and t["type"] == "record": 41 | if not isinstance(x, dict): raise Exception 42 | return dict((f["name"], convertIn(x[f["name"]], f["type"])) for f in t["fields"]) 43 | 44 | elif isinstance(t, list): 45 | if x is None: 46 | return x 47 | else: 48 | tag, value = x.items()[0] 49 | for ti in t: 50 | if isinstance(ti, dict) and ti["type"] in ("record", "enum", "fixed"): 51 | name = ti["name"] 52 | elif isinstance(ti, dict): 53 | name = ti["type"] 54 | elif isinstance(ti, basestring): 55 | name = ti 56 | if tag == name: 57 | return {tag: convertIn(value, ti)} 58 | 59 | else: 60 | return x 61 | 62 | def convertOut(x, t, dobase64=True): 63 | if x is None and t == "null": 64 | return x 65 | 66 | elif x is True or x is False and t == "boolean": 67 | return x 68 | 69 | elif isinstance(x, (int, long)) and t in ("int", "long"): 70 | return x 71 | 72 | elif isinstance(x, (int, long, float)) and t in ("float", "double"): 73 | if math.isinf(x): 74 | if x > 0.0: 75 | return "inf" 76 | else: 77 | return "-inf" 78 | elif math.isnan(x): 79 | return "nan" 80 | else: 81 | return x 82 | 83 | elif isinstance(x, basestring) and t == "string": 84 | return x 85 | 86 | elif isinstance(x, str) and (t == "bytes" or (isinstance(t, dict) and t["type"] == "fixed")): 87 | if dobase64: 88 | return base64.b64encode(x) 89 | else: 90 | return x 91 | 92 | elif isinstance(x, list) and isinstance(t, dict) and t["type"] == "array": 93 | return [convertOut(v, t["items"], dobase64) for v in x] 94 | 95 | elif isinstance(x, dict) and isinstance(t, dict) and t["type"] == "map": 96 | return dict((k, convertOut(v, t["values"], dobase64)) for k, v in x.items()) 97 | 98 | elif isinstance(x, dict) and isinstance(t, dict) and t["type"] == "record" and set(x.keys()) == set(f["name"] for f in t["fields"]): 99 | return dict((f["name"], convertOut(x[f["name"]], f["type"], dobase64)) for f in t["fields"]) 100 | 101 | elif isinstance(t, list): 102 | if x is None: 103 | if "null" in t: 104 | return x 105 | else: 106 | raise Exception 107 | elif isinstance(x, dict) and len(x) == 1: 108 | tag, value = x.items()[0] 109 | for ti in t: 110 | if isinstance(ti, dict) and ti["type"] in ("record", "enum", "fixed"): 111 | name = ti["name"] 112 | elif isinstance(ti, dict): 113 | name = ti["type"] 114 | elif isinstance(ti, basestring): 115 | name = ti 116 | if tag == name: 117 | return {tag: convertOut(value, ti, dobase64)} 118 | else: 119 | for ti in t: 120 | try: 121 | out = convertOut(x, ti, dobase64) 122 | except: 123 | pass 124 | else: 125 | if isinstance(ti, dict) and ti["type"] in ("record", "enum", "fixed"): 126 | if "namespace" in ti: 127 | name = ti["namespace"] + "." + ti["name"] 128 | else: 129 | name = ti["name"] 130 | elif isinstance(ti, dict): 131 | name = ti["type"] 132 | elif ti in ("boolean", "int", "long", "float", "double", "string", "bytes"): 133 | name = ti 134 | return {name: out} 135 | raise Exception 136 | 137 | else: 138 | raise Exception 139 | 140 | def checkInputType(x, t, typeNames): 141 | if t == "null": 142 | if x is not None: 143 | raise TypeError("Input incorrectly prepared: " + repr(x) + " " + json.dumps(t)) 144 | elif t == "boolean": 145 | if x is not True and x is not False: 146 | raise TypeError("Input incorrectly prepared: " + repr(x) + " " + json.dumps(t)) 147 | elif t == "int" or t == "long": 148 | if not isinstance(x, (int, long)): 149 | raise TypeError("Input incorrectly prepared: " + repr(x) + " " + json.dumps(t)) 150 | elif t == "float" or t == "double": 151 | if not isinstance(x, (int, long, float)): 152 | raise TypeError("Input incorrectly prepared: " + repr(x) + " " + json.dumps(t)) 153 | elif t == "string": 154 | if not isinstance(x, unicode): 155 | raise TypeError("Input incorrectly prepared: " + repr(x) + " " + json.dumps(t)) 156 | elif t == "bytes": 157 | if not isinstance(x, str): 158 | raise TypeError("Input incorrectly prepared: " + repr(x) + " " + json.dumps(t)) 159 | elif isinstance(t, basestring): 160 | t = typeNames[t] 161 | if isinstance(t, dict) and t["type"] == "array": 162 | if not isinstance(x, list): 163 | raise TypeError("Input incorrectly prepared: " + repr(x) + " " + json.dumps(t)) 164 | for v in x: 165 | checkInputType(v, t["items"], typeNames) 166 | elif isinstance(t, dict) and t["type"] == "map": 167 | if not isinstance(x, dict): 168 | raise TypeError("Input incorrectly prepared: " + repr(x) + " " + json.dumps(t)) 169 | for k, v in x.items(): 170 | checkInputType(v, t["values"], typeNames) 171 | elif isinstance(t, dict) and t["type"] == "record": 172 | if not isinstance(x, dict) or set(x.keys()) != set(f["name"] for f in t["fields"]): 173 | raise TypeError("Input incorrectly prepared: " + repr(x) + " " + json.dumps(t)) 174 | for f in t["fields"]: 175 | checkInputType(x[f["name"]], f["type"], typeNames) 176 | elif isinstance(t, dict) and t["type"] == "fixed": 177 | if not isinstance(x, str): 178 | raise TypeError("Input incorrectly prepared: " + repr(x) + " " + json.dumps(t)) 179 | elif isinstance(t, dict) and t["type"] == "enum": 180 | if not isinstance(x, unicode): 181 | raise TypeError("Input incorrectly prepared: " + repr(x) + " " + json.dumps(t)) 182 | elif isinstance(t, list): 183 | if x is None: 184 | if "null" not in t: 185 | raise TypeError("Input incorrectly prepared: " + repr(x) + " " + json.dumps(t)) 186 | elif isinstance(x, dict) and len(x) == 1: 187 | tag, value = x.items()[0] 188 | found = False 189 | for ti in t: 190 | if isinstance(ti, dict) and ti["type"] in ("record", "enum", "fixed"): 191 | name = ti["name"] 192 | elif isinstance(ti, dict): 193 | name = ti["type"] 194 | elif isinstance(ti, basestring): 195 | name = ti 196 | if tag == name: 197 | found = True 198 | checkInputType(value, ti, typeNames) 199 | if not found: 200 | raise TypeError("Input incorrectly prepared: " + repr(x) + " " + json.dumps(t)) 201 | else: 202 | raise TypeError("Input incorrectly prepared: " + repr(x) + " " + json.dumps(t)) 203 | elif not isinstance(t, basestring): 204 | raise TypeError("Input incorrectly prepared: " + repr(x) + " " + json.dumps(t)) 205 | return x 206 | 207 | def getNamesFromType(t): 208 | if isinstance(t, dict) and t["type"] == "array": 209 | return getNamesFromType(t["items"]) 210 | elif isinstance(t, dict) and t["type"] == "map": 211 | return getNamesFromType(t["values"]) 212 | elif isinstance(t, dict) and t["type"] == "record": 213 | out = {t["name"]: t} 214 | for f in t["fields"]: 215 | out.update(getNamesFromType(f["type"])) 216 | return out 217 | elif isinstance(t, dict) and t["type"] == "fixed": 218 | return {t["name"]: t} 219 | elif isinstance(t, dict) and t["type"] == "enum": 220 | return {t["name"]: t} 221 | elif isinstance(t, list): 222 | out = {} 223 | for ti in t: 224 | out.update(getNamesFromType(ti)) 225 | return out 226 | else: 227 | return {} 228 | 229 | def getNamesFromFunctions(fcns): 230 | out = {} 231 | for fcn in fcns: 232 | for param in fcn["params"]: 233 | out.update(getNamesFromType(param.values()[0])) 234 | out.update(getNamesFromType(fcn["ret"])) 235 | return out 236 | 237 | def convertInput(example): 238 | inputType = example["engine"]["input"] 239 | typeNames = getNamesFromFunctions([x for x in example["engine"]["action"][example["function"]] if isinstance(x, dict) and "params" in x]) 240 | typeNames.update(getNamesFromType(inputType)) 241 | try: 242 | trials = [dict(x, sample=checkInputType(convertIn(x["sample"], inputType), inputType, typeNames)) for x in example["trials"]] 243 | except TypeError: 244 | print example["function"] + "\t" + json.dumps(example["engine"]) 245 | raise 246 | return dict(example, trials=trials) 247 | 248 | def getExamples(openFile): 249 | # take advantage of the formatting of the file to find breaks between examples 250 | inFunction = False 251 | collectedString = [] 252 | for line in openFile: 253 | if line.startswith(""" {"function":"""): 254 | inFunction = True 255 | elif line == """ },\n""" or line == """ }\n""": 256 | inFunction = False 257 | collectedString.append("}") 258 | yield convertInput(json.loads("".join(collectedString))) 259 | collectedString = [] 260 | if inFunction: 261 | collectedString.append(line) 262 | 263 | def compare(one, two, zeroTolerance, fractionalTolerance, infinityTolerance, breadcrumbs=None): 264 | if breadcrumbs is None: 265 | breadcrumbs = ["top"] 266 | if isinstance(one, dict) and isinstance(two, dict): 267 | if set(one.keys()) != set(two.keys()): 268 | yield "different dict keys: {%s} vs {%s} at %s" % (", ".join(sorted(one.keys())), ", ".join(sorted(two.keys())), " -> ".join(breadcrumbs)) 269 | else: 270 | for k in sorted(one.keys()): 271 | for x in compare(one[k], two[k], zeroTolerance, fractionalTolerance, infinityTolerance, breadcrumbs + [k]): 272 | yield x 273 | elif isinstance(one, list) and isinstance(two, list): 274 | if len(one) != len(two): 275 | yield "different list lengths: %d vs %d at %s" % (len(one), len(two), " -> ".join(breadcrumbs)) 276 | else: 277 | for i in xrange(len(one)): 278 | for x in compare(one[i], two[i], zeroTolerance, fractionalTolerance, infinityTolerance, breadcrumbs + [str(i)]): 279 | yield x 280 | elif isinstance(one, basestring) and isinstance(two, basestring): 281 | if one != two: 282 | yield "different values: %s vs %s at %s" % (json.dumps(one), json.dumps(two), " -> ".join(breadcrumbs)) 283 | elif isinstance(one, bool) and isinstance(two, bool): 284 | if one != two: 285 | yield "different values: %r vs %r at %s" % (one, two, " -> ".join(breadcrumbs)) 286 | elif isinstance(one, (int, long)) and isinstance(two, (int, long)): 287 | if one != two: 288 | yield "different values: %d vs %d at %s" % (one, two, " -> ".join(breadcrumbs)) 289 | elif one == "inf" and isinstance(two, (int, long, float)) and two > infinityTolerance: 290 | pass 291 | elif one == "-inf" and isinstance(two, (int, long, float)) and two < -infinityTolerance: 292 | pass 293 | elif two == "inf" and isinstance(one, (int, long, float)) and one > infinityTolerance: 294 | pass 295 | elif two == "-inf" and isinstance(one, (int, long, float)) and one < -infinityTolerance: 296 | pass 297 | elif (one == "inf" or one == "-inf" or one == "nan") and isinstance(two, (int, long, float)): 298 | yield "different values: %s vs %g at %s" % (one, two, " -> ".join(breadcrumbs)) 299 | elif (two == "inf" or two == "-inf" or two == "nan") and isinstance(one, (int, long, float)): 300 | yield "different values: %g vs %s at %s" % (one, two, " -> ".join(breadcrumbs)) 301 | elif isinstance(one, (int, long, float)) and isinstance(two, (int, long, float)): 302 | if abs(one) < zeroTolerance and abs(two) < zeroTolerance: 303 | pass # they're both about zero 304 | elif abs(one) < zeroTolerance: 305 | yield "different values beyond tolerance: %g ~ 0 vs %g at %s" % (one, two, " -> ".join(breadcrumbs)) 306 | elif abs(two) < zeroTolerance: 307 | yield "different values beyond tolerance: %g vs %g ~ 0 at %s" % (one, two, " -> ".join(breadcrumbs)) 308 | elif abs(one - two)/abs(one) > fractionalTolerance: 309 | yield "different values beyond tolerance: abs(%g - %g)/%g = %g at %s" % (one, two, abs(one), abs(one - two)/abs(one), " -> ".join(breadcrumbs)) 310 | elif isinstance(one, bool) and isinstance(two, bool): 311 | if one != two: 312 | yield "different values: %r vs %r at %s" % (one, two, " -> ".join(breadcrumbs)) 313 | elif one is None and two is None: 314 | pass 315 | else: 316 | yield "different types: %s vs %s at %s" % (type(one).__name__, type(two).__name__, " -> ".join(breadcrumbs)) 317 | 318 | if __name__ == "__main__": 319 | for example in getExamples(open("pfa-tests.json")): 320 | print json.dumps(example["engine"]) 321 | -------------------------------------------------------------------------------- /conformance-tests/runTestHadrian.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import json 4 | import signal 5 | import sys 6 | import re 7 | 8 | from runTest import * 9 | 10 | import java.lang.Exception 11 | 12 | from com.opendatagroup.hadrian.errors import PFARuntimeException 13 | from com.opendatagroup.antinous.pfainterface import PFAEngineFactory 14 | 15 | if __name__ == "__main__": 16 | pef = PFAEngineFactory() 17 | pef.setDebug(False) 18 | 19 | if len(sys.argv[1:]) == 1: 20 | inputFile, = sys.argv[1:] 21 | outputFile = None 22 | else: 23 | inputFile, outputFile = sys.argv[1:] 24 | 25 | if outputFile is not None: 26 | template = dict(enumerate(open(inputFile).readlines())) 27 | lookup = {} 28 | numFunctions = 0 29 | for lineNumber, lineContent in template.items(): 30 | m = re.search('"(UNKNOWN_[0-9]+)"', lineContent) 31 | if m is not None: 32 | lookup[m.group(1)] = lineNumber 33 | if lineContent.startswith(' {"function":'): 34 | numFunctions += 1 35 | else: 36 | template = None 37 | lookup = None 38 | numFunctions = None 39 | 40 | for counter, example in enumerate(getExamples(open(inputFile))): 41 | engine = pef.engineFromJson(json.dumps(example["engine"])) 42 | 43 | if numFunctions is not None: 44 | print "%4d/%4d %-20s" % (counter + 1, numFunctions, example["function"]) # %s -> %s , json.dumps(example["engine"]["input"]), json.dumps(example["engine"]["output"]) 45 | 46 | functionWritten = False 47 | def maybeWriteFunction(functionWritten): 48 | if not functionWritten: 49 | print "%4d %-20s%s" % (counter + 1, example["function"], json.dumps(example["engine"])) 50 | return True 51 | 52 | for trial in example["trials"]: 53 | trialWritten = False 54 | try: 55 | result = {"success": convertOut(pef.action(engine, trial["sample"]), json.loads(engine.outputType().toString()), dobase64=False)} 56 | except PFARuntimeException as err: 57 | result = {"fail": err.code()} 58 | 59 | if "success" in result: 60 | actual = json.dumps(result["success"]) 61 | else: 62 | actual = "ERROR CODE " + str(result["fail"]) 63 | 64 | def maybeWriteTrial(trialWritten): 65 | if not trialWritten: 66 | print " input: " + json.dumps(trial["sample"]) 67 | print " expected: " + json.dumps(trial["result"]) 68 | print " actual: " + actual 69 | return True 70 | 71 | if "error" in trial: 72 | if trial["error"] != result.get("fail", None): 73 | functionWritten = maybeWriteFunction(functionWritten) 74 | if not trialWritten: 75 | print " input: " + json.dumps(trial["sample"]) 76 | print " expected: ERROR CODE " + str(trial["error"]) 77 | print " actual: " + actual 78 | trialWritten = True 79 | 80 | elif trial.get("nondeterministic", None) is not None: 81 | if outputFile is not None and trial["result"].startswith("UNKNOWN_"): 82 | lineNumber = lookup[trial["result"]] 83 | if "success" in result: 84 | if trial["nondeterministic"] == "pseudorandom": 85 | template[lineNumber] = template[lineNumber].replace(', "result": "' + trial["result"] + '"', "") 86 | else: 87 | template[lineNumber] = template[lineNumber].replace('"result": "' + trial["result"] + '"', '"result": ' + json.dumps(result["success"])) 88 | else: 89 | template[lineNumber] = template[lineNumber].replace('"result": "' + trial["result"] + '", "nondeterministic": "' + trial["nondeterministic"] + '"', '"error": ' + json.dumps(result["fail"])) 90 | else: 91 | if trial["nondeterministic"] == "unordered": 92 | if "success" in result: 93 | for errorMessage in compare(sorted(trial["result"]), sorted(result["success"]), 1e-8, 0.01, 1e80): 94 | functionWritten = maybeWriteFunction(functionWritten) 95 | trialWritten = maybeWriteTrial(trialWritten) 96 | print " " + errorMessage 97 | else: 98 | functionWritten = maybeWriteFunction(functionWritten) 99 | trialWritten = maybeWriteTrial(trialWritten) 100 | print " " + errorMessage 101 | 102 | else: 103 | if outputFile is not None and trial["result"].startswith("UNKNOWN_"): 104 | lineNumber = lookup[trial["result"]] 105 | if "success" in result: 106 | template[lineNumber] = template[lineNumber].replace('"result": "' + trial["result"] + '"', '"result": ' + json.dumps(result["success"])) 107 | else: 108 | template[lineNumber] = template[lineNumber].replace('"result": "' + trial["result"] + '"', '"error": ' + json.dumps(result["fail"])) 109 | else: 110 | if "success" in result: 111 | for errorMessage in compare(trial["result"], result["success"], 1e-8, 0.01, 1e80): 112 | functionWritten = maybeWriteFunction(functionWritten) 113 | trialWritten = maybeWriteTrial(trialWritten) 114 | print " " + errorMessage 115 | else: 116 | functionWritten = maybeWriteFunction(functionWritten) 117 | trialWritten = maybeWriteTrial(trialWritten) 118 | print " " + errorMessage 119 | 120 | if outputFile is None and not functionWritten: 121 | print "%4d %s" % (counter + 1, example["function"]) 122 | 123 | if outputFile is not None: 124 | out = open(outputFile, "w") 125 | for lineNumber in xrange(len(template)): 126 | out.write(template[lineNumber]) 127 | out.close() 128 | -------------------------------------------------------------------------------- /conformance-tests/runTestTitus.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import json 4 | import sys 5 | 6 | from titus.genpy import PFAEngine 7 | from titus.errors import PFARuntimeException 8 | 9 | from runTest import * 10 | 11 | inputFile, = sys.argv[1:] 12 | 13 | # Failures that I'm giving up on: 14 | # 15 | # prob.dist.binomialQF({"p": 0.99999, "prob": 1e-05, "size": 1}) should be 1, is 0 (rounding in count) 16 | # {"p": 0.9, "prob": 0.1, "size": 1} should be 1, is 0 (same reason) 17 | # prob.dist.hypergeometricPDF \ 18 | # prob.dist.hypergeometricCDF } many errors! and the QF has a long or infinite loop 19 | # prob.dist.hypergeometricQF / 20 | # prob.dist.negativeBinomialPDF({"x": 17, "prob": 0.9, "size": 100}) should be 0.00245, is 0.02715 21 | # {"x": 100, "prob": 0.1, "size": 17} should be 0.00245, is 0.00462 22 | # {"x": 100, "prob": 0.5, "size": 100} should be 5.7e42, is 0.02817 23 | # prob.dist.negativeBinomialQF has many errors (though not as many as the hypergeometric) 24 | 25 | for counter, example in enumerate(getExamples(open(inputFile))): 26 | engine, = PFAEngine.fromJson(example["engine"]) 27 | 28 | if example["function"] in ("prob.dist.binomialQF", "prob.dist.hypergeometricPDF", "prob.dist.hypergeometricCDF", "prob.dist.hypergeometricQF", "prob.dist.negativeBinomialPDF", "prob.dist.negativeBinomialQF"): 29 | continue 30 | 31 | functionWritten = False 32 | def maybeWriteFunction(functionWritten): 33 | if not functionWritten: 34 | print "%4d %-20s%s" % (counter + 1, example["function"], json.dumps(example["engine"])) 35 | return True 36 | 37 | for trial in example["trials"]: 38 | trialWritten = False 39 | try: 40 | result = {"success": convertOut(engine.action(trial["sample"]), engine.outputType.jsonNode(set()), dobase64=True)} 41 | except PFARuntimeException as err: 42 | result = {"fail": err.code} 43 | except Exception: 44 | # PFAEngine.fromJson(example["engine"], debug=True) 45 | print "function: " + example["function"] 46 | print "engine: " + json.dumps(example["engine"]) 47 | print "input: " + repr(trial["sample"]) 48 | if "error" in trial: 49 | print "expected: ERROR CODE " + repr(trial["error"]) 50 | elif "result" in trial: 51 | print "expected: " + repr(trial["result"]) 52 | print 53 | raise 54 | 55 | if "success" in result: 56 | actual = json.dumps(result["success"]) 57 | else: 58 | actual = "ERROR CODE " + str(result["fail"]) 59 | 60 | if "error" in trial: 61 | if trial["error"] != result.get("fail", None): 62 | functionWritten = maybeWriteFunction(functionWritten) 63 | if not trialWritten: 64 | print " input: " + json.dumps(trial["sample"]) 65 | print " expected: ERROR CODE " + str(trial["error"]) 66 | print " actual: " + actual 67 | trialWritten = True 68 | 69 | elif trial.get("nondeterministic", None) in ("pseudorandom", "unstable"): 70 | pass 71 | 72 | else: 73 | def maybeWriteTrial(trialWritten): 74 | if not trialWritten: 75 | print " input: " + json.dumps(trial["sample"]) 76 | print " expected: " + json.dumps(trial["result"]) 77 | print " actual: " + actual 78 | return True 79 | 80 | if "success" in result: 81 | left = trial["result"] 82 | right = result["success"] 83 | 84 | if trial.get("nondeterministic", None) == "unordered": 85 | if not isinstance(left, list) or not isinstance(right, list): 86 | raise Exception 87 | left.sort() 88 | right.sort() 89 | 90 | for errorMessage in compare(left, right, 1e-4, 0.05, 1e80): 91 | functionWritten = maybeWriteFunction(functionWritten) 92 | trialWritten = maybeWriteTrial(trialWritten) 93 | print " " + errorMessage 94 | else: 95 | functionWritten = maybeWriteFunction(functionWritten) 96 | trialWritten = maybeWriteTrial(trialWritten) 97 | 98 | if not functionWritten: 99 | print "%4d %s" % (counter + 1, example["function"]) 100 | -------------------------------------------------------------------------------- /make-specification/fastFill_libfcns.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import re 4 | 5 | pfaSpecification = open("pfa-specification-source.tex").read() 6 | 7 | for line in open("libfcns.tex"): 8 | m = re.match(r"^ {(.+)}{(\\hypertarget.+)}%$", line) 9 | if m is not None: 10 | name, replacement = m.groups() 11 | libfcnName = r"\libfcn{" + name + "}" 12 | if libfcnName in pfaSpecification: 13 | pfaSpecification = pfaSpecification.replace(libfcnName, replacement) 14 | else: 15 | print name, "in libfcns.tex but not in pfa-specification-source.tex" 16 | 17 | for name in re.findall(r"\\libfcn{([^}]+)}", pfaSpecification): 18 | print name, "in pfa-specification-source.tex but not libfcns.tex" 19 | 20 | if "n" not in raw_input("overwrite pfa-specification.tex [Y/n]? ").lower(): 21 | open("pfa-specification.tex", "w").write(pfaSpecification) 22 | -------------------------------------------------------------------------------- /pfa-specification.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datamininggroup/pfa/adad7aff6e050707028c6b9501e0a5d57a4229e1/pfa-specification.pdf --------------------------------------------------------------------------------