Skip to content

Commit 818d2c6

Browse files
fabio-rovaiclaude
andauthored
The reasoner benchmark measures what it says it measures (#310)
* The Java reference emits the full entailed hierarchy, not direct superclasses JavaReasoner.java emitted getSuperClasses(cls, true) for each class, the DIRECT superclasses only. The engine reports every superclass in classification[].superclasses, so a comparison between the two counted every indirect pair the engine got right as an unsound extra. Direct superclasses also never include equivalent classes. In ORE file ore_ont_8937 the axioms SubClassOf(owl:Thing C23) and their siblings make C23, C25 and others equivalent to owl:Thing, so the true pair C25 -> C23 was absent from the reference and a correct engine answer was misread as unsound. For every named class HermiT finds satisfiable, the output now lists every named superclass with direct=false plus every named equivalent class. owl:Thing and owl:Nothing never appear in a pair, and unsatisfiable classes are listed under "unsatisfiable" instead of contributing a pair to every class in the ontology. Run on ore_ont_8937 the reference now holds 402 pairs and includes C25 -> C23. The output also carries told_subsumptions, the transitive closure of the asserted named hierarchy computed by the OWL API with no reasoner: SubClassOf between named classes, pairwise EquivalentClasses among named members, and the tautological edge from every named class to owl:Thing. The benchmark driver subtracts it from the reference to isolate the pairs that need reasoning. Times are now reported separately instead of as one figure: JVM start to main, parse, reasoner construction (HermiT clausifies there), and precomputeInferences alone. time_ms remains, defined as construction plus classification, for older callers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * The regressions note no longer claims Pizza is free of nominals regressions/README.md said the Pizza ontology contains no owl:hasValue and classifies in about 0.04s, and used that to argue the owl-dl blowup is specific to nominals rather than to scale. The figure came from a stripped 566-triple fixture. The reference benchmark/reference/pizza-reference.owl carries six owl:hasValue restrictions and one owl:oneOf enumeration of five individuals, counted as XML elements. A line count with grep reports two oneOf, because it also matches the closing tag; the main README repeats that miscount and is corrected in a later commit. The note now says what the reference file does under the current development build: over 2,332 loaded triples, reason --profile owl-dl returns complete: false with the budget exhausted in satisfiability and abox, 143 of 144 named classes undetermined and zero inferred subsumptions. It quotes no run time, because the phase budgets set it. The closing paragraph on blocking had been garbled by an earlier edit that replaced one line of a sentence with another, leaving two half sentences. It now says what the history shows: subset blocking was the original implementation, pairwise blocking has since landed as is_pairwise_blocked, and the NI-rule is still outstanding. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * The Java reference reports an inconsistent ontology instead of crashing on it HermiT throws InconsistentOntologyException from precomputeInferences when the ontology has no model. JavaReasoner let it propagate, so the run died with a stack trace and the benchmark could only record that HermiT failed. ORE file ore_ont_6720 in the EL classification track is such a file: HermiT finds it inconsistent both in its original Functional Syntax and after conversion to RDF/XML, so this is a property of the file, not of the conversion. The exception is now caught and the output says consistent: false with no pairs, which lets the driver ask the question that matters on such a file: whether the engine also noticed, or reported a consistent ontology and a hierarchy for it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * run_ore.py scores the engine against HermiT on the ORE 2015 corpus, pair by pair There was no driver for the ORE 2015 classification corpus. run_ore.py takes a track (el, dl or pure_dl) and a set of files from that track's fileorder.txt, runs both reasoners, and writes a JSON record and a markdown table to benchmark/reasoner/results/. It uses the Python standard library only. Both reasoners read the same bytes. The engine does not read OWL Functional Syntax yet, so each ORE input is converted once to RDF/XML by the OWL API (Convert.java, now tracked) and the converted file is given to both. --native gives the original file to both instead; until the engine reads Functional Syntax every such run fails the load gate, which is the intended outcome. The engine runs as one `batch -` process per run, load then reason --profile owl-dl, under a wall-clock cap the driver enforces by killing the process group. A run reports no timing when its load line lacks triples_loaded, has it at 0 or carries an error, when its reason line is missing or carries an error, when the process exits non-zero, or when the cap killed it. If any one of the --repeat runs (default 3) fails a gate, the file reports no timing at all, because a median over the runs that happened to finish would hide the one that did not. The reference is HermiT's full entailed named hierarchy from JavaReasoner. The driver also takes the told closure from the OWL API and subtracts it, so each file reports gold, told and inferred-only counts, and recall on all pairs and on inferred-only pairs. A file with no inferred-only pairs is flagged told_only and kept out of the headline, because a walk of the asserted axioms scores 100% on it. On the six smallest files of the EL track every file is told_only, so a headline built from them would have measured nothing about reasoning. Wrong answers are listed, not counted away: pairs the engine claims and HermiT does not entail, from any repeat and not only the first; classes the engine calls unsatisfiable that HermiT finds satisfiable; and an engine that calls consistent an ontology HermiT finds inconsistent. The table also carries the engine's undetermined count, budget_exhausted_in, and whether its answer was identical across repeats. --selftest drives fake engines through the same code and asserts that each gate fires: an empty classification, a fabricated wrong pair, a fabricated wrong pair in the second of three runs only, a false unsatisfiability claim, a false consistency claim, an empty load, a load error, a missing triples_loaded, a reason error, a crash, a hang, a hang in one repeat of three, an answer that changes between runs, and a told-only file, plus a correct engine and a structural walk as controls. All 17 checks pass. Each gate was also disabled in turn in a scratch copy, and every mutation made the self-test fail except one: with the explicit load-error check removed, a failed load is still caught, because it also has no triples_loaded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * The Pizza benchmark gates on keys the engine emits and compares like with like run_pizza_correctness.sh aborted every real run with "reasoned over an empty graph" although the load line reported 2,332 triples. Its guard read result["classes"] or result["class_count"], and the owl-dl reason result has neither key, so the count was always zero. The guard now requires the load line to carry triples_loaded > 0 and no error, and the reason line to carry no error and a non-zero named_classes, a key the result has (falling back to agents.satisfiability_agent.classes_checked). The guard was checked in both directions. On an empty input file it stops with "load reported triples_loaded=0" and exit 1. On an OWL Functional Syntax file, which the engine cannot read, it stops with the engine's own load error. That case also exposed a second fault: the engine exits 1 when a batch command fails, and under set -e the script ended there with no message at all. It now captures the engine's status and lets the gate say what failed. On the real reference file the old guard, run unchanged, still rejects the run, and the new one accepts it: 2,332 triples loaded, 144 named classes. compare_results.py read a "subsumptions" key from the engine result. The engine never emits one, so the engine side was always the empty set, compared against HermiT's direct superclasses. It now reads classification plus equivalences and scores them with the same code as run_ore.py against the full hierarchy from JavaReasoner, reporting gold, told and inferred-only counts, recall on each, wrong pairs, and unsatisfiable classes on both sides. The script ran Pellet unconditionally, so with a lib/ holding no Pellet jars it stopped at that step under set -e. Pellet now runs only when a pellet-*.jar or openllet-*.jar is present. JavaReasoner is compiled into target/java by the script itself, and PIZZA_OWL, LIB_DIR, JAVA and RESULTS_DIR can be overridden. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * The reasoner benchmark README documents the ORE driver and the told-closure subtraction The README's setup section asked for OWL API 5.x and HermiT.jar from hermit-reasoner.net. JavaReasoner.java is compiled against OWL API 4, which is what setup_jars.sh fetches, so the instructions now name that and list the jar set the harness was actually run with. The scripts compile the Java sources themselves, so no Maven step is described. A new section says what the HermiT reference is (the full entailed named hierarchy, not direct superclasses), why the direct form misread correct engine answers as unsound, using ore_ont_8937 as the worked case, and which times JavaReasoner reports separately. The Pizza section said the reference file has two owl:oneOf enumerations. It has one, of five individuals; the two came from a line count that also matched the closing tag. The section now describes the gate the script applies and the like-with-like comparison, and states that run_benchmark.py and the committed results/pizza_comparison.json predate both fixes: the former still runs load and reason as separate processes, and the latter reports named_classes: 1, the signature of an empty store. The ORE section documents run_ore.py: same bytes to both reasoners, the --native flag, the single-process engine run under a driver-enforced cap, the gates that withhold a timing, the told-closure subtraction and why a file with no inferred-only pairs is excluded from the headline, the per-file fields, and the self-test. It makes no performance claim; the engine is being changed in parallel and the driver exists to measure it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * The ORE table names unscored files for what they are The headline listed every file outside ok and told_only under "failed a gate". That includes ore_ont_6720, which HermiT finds inconsistent and the engine also reports inconsistent, so nothing failed; there is just no hierarchy to score. The line now reads "not scored" and names the three reasons a file can land there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 40bb0ab commit 818d2c6

8 files changed

Lines changed: 1303 additions & 156 deletions

File tree

‎benchmark/reasoner/.gitignore‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,9 @@ lib/
22
*.class
33
*.owl
44
target/
5+
corpora/
6+
results/ore_*
7+
results/oo_batch.json
8+
results/oo_result.json
9+
results/hermit_result.json
10+
results/pellet_result.json

‎benchmark/reasoner/Convert.java‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import org.semanticweb.owlapi.apibinding.OWLManager;
2+
import org.semanticweb.owlapi.formats.RDFXMLDocumentFormat;
3+
import org.semanticweb.owlapi.model.*;
4+
import java.io.*;
5+
6+
/**
7+
* Re-serialise an ontology to RDF/XML with the OWL API.
8+
*
9+
* The ORE 2015 corpus ships in OWL Functional Syntax. run_ore.py converts each
10+
* input ONCE with this program and then feeds the converted file to both the
11+
* engine and HermiT, so the two reasoners always read the same bytes. Run
12+
* run_ore.py with --native to skip the conversion and feed the original file.
13+
*
14+
* Usage: java -cp <classes>:<lib>/* Convert <in> <out.rdf>
15+
*/
16+
public class Convert {
17+
public static void main(String[] args) throws Exception {
18+
if (args.length != 2) {
19+
System.err.println("Usage: Convert <in> <out.rdf>");
20+
System.exit(1);
21+
}
22+
OWLOntologyManager m = OWLManager.createOWLOntologyManager();
23+
OWLOntology o = m.loadOntologyFromOntologyDocument(new File(args[0]));
24+
try (OutputStream out = new BufferedOutputStream(new FileOutputStream(args[1]))) {
25+
m.saveOntology(o, new RDFXMLDocumentFormat(), out);
26+
}
27+
}
28+
}
Lines changed: 199 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,233 @@
11
import org.semanticweb.owlapi.apibinding.OWLManager;
22
import org.semanticweb.owlapi.model.*;
3+
import org.semanticweb.owlapi.model.parameters.Imports;
34
import org.semanticweb.owlapi.reasoner.*;
45
import java.io.*;
6+
import java.lang.management.ManagementFactory;
57
import java.util.*;
68

9+
/**
10+
* Reference classification for the reasoner benchmarks.
11+
*
12+
* Output is the FULL entailed named-class hierarchy, not the direct one. For
13+
* every named class C that the reasoner finds satisfiable, it emits "C -> D"
14+
* for every named D with C entailed to be a subclass of D: all superclasses
15+
* (direct=false) plus all equivalent classes. owl:Thing and owl:Nothing never
16+
* appear in a pair. Unsatisfiable classes are listed under "unsatisfiable" and
17+
* contribute no pairs, because an unsatisfiable class is trivially a subclass
18+
* of everything and would swamp the comparison.
19+
*
20+
* Why not direct superclasses: the engine reports all superclasses, so a
21+
* direct-only reference makes every indirect pair look like an unsound extra.
22+
* Direct superclasses also never include equivalent classes. In ORE file
23+
* ore_ont_8937, axioms SubClassOf(owl:Thing C23) make C23 equivalent to
24+
* owl:Thing, so the true pair C25 -> C23 is absent from a direct-only
25+
* reference and a correct engine answer was misread as unsound.
26+
*
27+
* It also emits "told_subsumptions": the reflexive-free transitive closure of
28+
* the ASSERTED named hierarchy, i.e. SubClassOf(A, B) with A and B named,
29+
* pairwise EquivalentClasses among named members, and the tautological edge
30+
* A -> owl:Thing for every named A. No reasoner is involved in computing it.
31+
* A structural walk of the asserted axioms finds exactly these pairs, so the
32+
* benchmark subtracts them to measure what reasoning adds.
33+
*
34+
* Times are reported separately and never summed silently:
35+
* jvm_start_ms JVM start to entry of main (process boot cost)
36+
* load_ms parse the document into an OWLOntology
37+
* reasoner_create_ms construct the reasoner (HermiT clausifies here)
38+
* classify_ms precomputeInferences(CLASS_HIERARCHY) only
39+
* extract_ms read the hierarchy back out (not reasoning cost)
40+
* time_ms reasoner_create_ms + classify_ms, kept for older callers
41+
*/
742
public class JavaReasoner {
843
public static void main(String[] args) throws Exception {
44+
long mainEntry = System.currentTimeMillis();
45+
long jvmStart = ManagementFactory.getRuntimeMXBean().getStartTime();
946
if (args.length < 3) {
10-
System.err.println("Usage: JavaReasoner <reasoner> <ontology.owl> <output.json>");
47+
System.err.println("Usage: JavaReasoner <hermit|pellet> <ontology> <output.json>");
1148
System.exit(1);
1249
}
13-
String reasonerName = args[0]; // "hermit" or "pellet"
50+
String reasonerName = args[0];
1451
String ontologyPath = args[1];
1552
String outputPath = args[2];
1653

54+
long t0 = System.nanoTime();
1755
OWLOntologyManager manager = OWLManager.createOWLOntologyManager();
1856
OWLOntology ontology = manager.loadOntologyFromOntologyDocument(new File(ontologyPath));
57+
long loadNs = System.nanoTime() - t0;
1958

20-
OWLReasonerFactory factory;
21-
if (reasonerName.equals("hermit")) {
22-
factory = new org.semanticweb.HermiT.ReasonerFactory();
23-
} else if (reasonerName.equals("pellet")) {
24-
// Load Pellet via reflection (may not be on classpath)
25-
try {
26-
Class<?> cls = Class.forName("com.clarkparsia.pellet.owlapi.PelletReasonerFactory");
27-
factory = (OWLReasonerFactory) cls.getMethod("getInstance").invoke(null);
28-
} catch (ClassNotFoundException e) {
29-
// Try Openllet fork
30-
try {
31-
Class<?> cls = Class.forName("openllet.owlapi.OpenlletReasonerFactory");
32-
factory = (OWLReasonerFactory) cls.getMethod("getInstance").invoke(null);
33-
} catch (ClassNotFoundException e2) {
34-
System.err.println("ERROR: Neither Pellet nor Openllet found on classpath");
35-
System.exit(1);
36-
return;
37-
}
38-
}
39-
} else {
40-
throw new IllegalArgumentException("Unknown reasoner: " + reasonerName);
41-
}
59+
OWLReasonerFactory factory = factoryFor(reasonerName);
4260

43-
long startTime = System.currentTimeMillis();
61+
long t1 = System.nanoTime();
4462
OWLReasoner reasoner = factory.createReasoner(ontology);
45-
reasoner.precomputeInferences(InferenceType.CLASS_HIERARCHY);
46-
long elapsed = System.currentTimeMillis() - startTime;
63+
long createNs = System.nanoTime() - t1;
4764

48-
// Extract all subsumption pairs
49-
Set<OWLClass> classes = ontology.getClassesInSignature();
65+
// An inconsistent ontology makes HermiT throw from precomputeInferences.
66+
// Report it as consistent=false with no pairs rather than crashing, so
67+
// the driver can check whether the engine noticed.
68+
long t2 = System.nanoTime();
69+
boolean consistent;
70+
try {
71+
reasoner.precomputeInferences(InferenceType.CLASS_HIERARCHY);
72+
consistent = reasoner.isConsistent();
73+
} catch (InconsistentOntologyException e) {
74+
consistent = false;
75+
}
76+
long classifyNs = System.nanoTime() - t2;
77+
78+
long t3 = System.nanoTime();
79+
Set<OWLClass> classes = ontology.getClassesInSignature(Imports.INCLUDED);
5080
List<String> subsumptions = new ArrayList<>();
81+
List<String> unsatisfiable = new ArrayList<>();
82+
int namedClasses = 0;
5183
for (OWLClass cls : classes) {
52-
for (OWLClass sup : reasoner.getSuperClasses(cls, true).getFlattened()) {
84+
if (cls.isOWLThing() || cls.isOWLNothing()) continue;
85+
namedClasses++;
86+
if (!consistent) continue;
87+
if (!reasoner.isSatisfiable(cls)) {
88+
unsatisfiable.add(cls.getIRI().toString());
89+
continue;
90+
}
91+
Set<OWLClass> supers = new HashSet<>(reasoner.getSuperClasses(cls, false).getFlattened());
92+
supers.addAll(reasoner.getEquivalentClasses(cls).getEntities());
93+
for (OWLClass sup : supers) {
94+
if (sup.equals(cls) || sup.isOWLThing() || sup.isOWLNothing()) continue;
5395
subsumptions.add(cls.getIRI().toString() + " -> " + sup.getIRI().toString());
5496
}
5597
}
5698
Collections.sort(subsumptions);
99+
Collections.sort(unsatisfiable);
100+
long extractNs = System.nanoTime() - t3;
57101

58-
// Write JSON output
102+
long t4 = System.nanoTime();
103+
List<String> told = toldClosure(ontology, classes);
104+
long toldNs = System.nanoTime() - t4;
105+
106+
long createMs = createNs / 1_000_000L;
107+
long classifyMs = classifyNs / 1_000_000L;
59108
StringBuilder sb = new StringBuilder();
60-
sb.append("{\"reasoner\":\"").append(reasonerName).append("\",");
61-
sb.append("\"time_ms\":").append(elapsed).append(",");
62-
sb.append("\"classes\":").append(classes.size()).append(",");
63-
sb.append("\"subsumptions\":[");
64-
for (int i = 0; i < subsumptions.size(); i++) {
109+
sb.append("{\"reasoner\":").append(q(reasonerName)).append(",");
110+
sb.append("\"ontology\":").append(q(ontologyPath)).append(",");
111+
sb.append("\"pairs\":\"all_superclasses_plus_equivalents\",");
112+
sb.append("\"jvm_start_ms\":").append(mainEntry - jvmStart).append(",");
113+
sb.append("\"load_ms\":").append(loadNs / 1_000_000L).append(",");
114+
sb.append("\"reasoner_create_ms\":").append(createMs).append(",");
115+
sb.append("\"classify_ms\":").append(classifyMs).append(",");
116+
sb.append("\"extract_ms\":").append(extractNs / 1_000_000L).append(",");
117+
sb.append("\"told_ms\":").append(toldNs / 1_000_000L).append(",");
118+
sb.append("\"time_ms\":").append(createMs + classifyMs).append(",");
119+
sb.append("\"consistent\":").append(consistent).append(",");
120+
sb.append("\"axioms\":").append(ontology.getLogicalAxiomCount()).append(",");
121+
sb.append("\"classes\":").append(namedClasses).append(",");
122+
appendArray(sb, "unsatisfiable", unsatisfiable);
123+
sb.append(",");
124+
appendArray(sb, "subsumptions", subsumptions);
125+
sb.append(",");
126+
appendArray(sb, "told_subsumptions", told);
127+
sb.append("}");
128+
129+
try (Writer w = new OutputStreamWriter(new FileOutputStream(outputPath), "UTF-8")) {
130+
w.write(sb.toString());
131+
}
132+
reasoner.dispose();
133+
System.out.println("Done: " + reasonerName + " create " + createMs + "ms, classify "
134+
+ classifyMs + "ms, " + subsumptions.size() + " entailed pairs, "
135+
+ told.size() + " told pairs, " + unsatisfiable.size() + " unsatisfiable");
136+
}
137+
138+
/** Transitive closure of the asserted named-class hierarchy, no reasoner. */
139+
static List<String> toldClosure(OWLOntology ontology, Set<OWLClass> classes) {
140+
Map<OWLClass, Set<OWLClass>> up = new HashMap<>();
141+
OWLClass thing = null;
142+
for (OWLClass c : classes) {
143+
if (c.isOWLThing()) thing = c;
144+
}
145+
for (OWLSubClassOfAxiom ax : ontology.getAxioms(AxiomType.SUBCLASS_OF, Imports.INCLUDED)) {
146+
if (ax.getSubClass().isAnonymous() || ax.getSuperClass().isAnonymous()) continue;
147+
edge(up, ax.getSubClass().asOWLClass(), ax.getSuperClass().asOWLClass());
148+
}
149+
for (OWLEquivalentClassesAxiom ax : ontology.getAxioms(AxiomType.EQUIVALENT_CLASSES, Imports.INCLUDED)) {
150+
List<OWLClass> named = new ArrayList<>(ax.getNamedClasses());
151+
for (OWLClass a : named) {
152+
for (OWLClass b : named) {
153+
if (!a.equals(b)) edge(up, a, b);
154+
}
155+
}
156+
}
157+
// Every named class is a subclass of owl:Thing. This only matters when
158+
// the ontology gives owl:Thing a told superclass, as ore_ont_8937 does.
159+
if (thing != null) {
160+
for (OWLClass c : classes) {
161+
if (!c.isOWLThing() && !c.isOWLNothing()) edge(up, c, thing);
162+
}
163+
}
164+
List<String> out = new ArrayList<>();
165+
for (OWLClass c : classes) {
166+
if (c.isOWLThing() || c.isOWLNothing()) continue;
167+
Set<OWLClass> seen = new HashSet<>();
168+
Deque<OWLClass> stack = new ArrayDeque<>(up.getOrDefault(c, Collections.emptySet()));
169+
while (!stack.isEmpty()) {
170+
OWLClass d = stack.pop();
171+
if (!seen.add(d)) continue;
172+
stack.addAll(up.getOrDefault(d, Collections.emptySet()));
173+
}
174+
for (OWLClass d : seen) {
175+
if (d.equals(c) || d.isOWLThing() || d.isOWLNothing()) continue;
176+
out.add(c.getIRI().toString() + " -> " + d.getIRI().toString());
177+
}
178+
}
179+
Collections.sort(out);
180+
return out;
181+
}
182+
183+
static void edge(Map<OWLClass, Set<OWLClass>> up, OWLClass a, OWLClass b) {
184+
up.computeIfAbsent(a, k -> new HashSet<>()).add(b);
185+
}
186+
187+
static OWLReasonerFactory factoryFor(String name) throws Exception {
188+
if (name.equals("hermit")) {
189+
return new org.semanticweb.HermiT.ReasonerFactory();
190+
}
191+
if (name.equals("pellet")) {
192+
for (String fqcn : new String[] {
193+
"com.clarkparsia.pellet.owlapi.PelletReasonerFactory",
194+
"openllet.owlapi.OpenlletReasonerFactory"}) {
195+
try {
196+
Class<?> cls = Class.forName(fqcn);
197+
return (OWLReasonerFactory) cls.getMethod("getInstance").invoke(null);
198+
} catch (ClassNotFoundException e) {
199+
// try the next one
200+
}
201+
}
202+
System.err.println("ERROR: Neither Pellet nor Openllet found on classpath");
203+
System.exit(2);
204+
}
205+
throw new IllegalArgumentException("Unknown reasoner: " + name);
206+
}
207+
208+
static void appendArray(StringBuilder sb, String key, List<String> items) {
209+
sb.append(q(key)).append(":[");
210+
for (int i = 0; i < items.size(); i++) {
65211
if (i > 0) sb.append(",");
66-
sb.append("\"").append(subsumptions.get(i).replace("\"", "\\\"")).append("\"");
212+
sb.append(q(items.get(i)));
67213
}
68-
sb.append("]}");
214+
sb.append("]");
215+
}
69216

70-
try (FileWriter fw = new FileWriter(outputPath)) {
71-
fw.write(sb.toString());
217+
static String q(String s) {
218+
StringBuilder sb = new StringBuilder("\"");
219+
for (char ch : s.toCharArray()) {
220+
switch (ch) {
221+
case '"': sb.append("\\\""); break;
222+
case '\\': sb.append("\\\\"); break;
223+
case '\n': sb.append("\\n"); break;
224+
case '\r': sb.append("\\r"); break;
225+
case '\t': sb.append("\\t"); break;
226+
default:
227+
if (ch < 0x20) sb.append(String.format("\\u%04x", (int) ch));
228+
else sb.append(ch);
229+
}
72230
}
73-
System.out.println("Done: " + reasonerName + " in " + elapsed + "ms, " + subsumptions.size() + " subsumptions");
231+
return sb.append("\"").toString();
74232
}
75233
}

0 commit comments

Comments
 (0)