Skip to content

Commit 8224006

Browse files
authored
Merge pull request #518 from sosy-lab/princess-2025-06-25
Princess/Ostrich update
2 parents fa89068 + ae36c38 commit 8224006

12 files changed

+123
-141
lines changed

lib/ivy.xml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -170,16 +170,16 @@ SPDX-License-Identifier: Apache-2.0
170170
<dependency org="de.uni-freiburg.informatik.ultimate" name="smtinterpol" rev="2.5-1242-g5c50fb6d" conf="runtime-smtinterpol->master; contrib->sources"/>
171171

172172
<!-- Princess and Ostrich for our Maven release -->
173-
<dependency org="io.github.uuverifiers" name="princess_2.13" rev="2024-11-08" conf="runtime-princess-with-javacup->default; contrib->sources"/>
174-
<dependency org="io.github.uuverifiers" name="ostrich_2.13" rev="1.4.1" conf="runtime-princess-with-javacup->default; contrib->sources"/>
173+
<dependency org="io.github.uuverifiers" name="princess_2.13" rev="2025-06-25" conf="runtime-princess-with-javacup->default; contrib->sources"/>
174+
<dependency org="io.github.uuverifiers" name="ostrich_2.13" rev="2.0" conf="runtime-princess-with-javacup->default; contrib->sources"/>
175175
<!-- Princess and Ostrich for our Ivy release -->
176-
<dependency org="io.github.uuverifiers" name="princess_2.13" rev="2024-11-08" conf="runtime-princess->default; contrib->sources">
176+
<dependency org="io.github.uuverifiers" name="princess_2.13" rev="2025-06-25" conf="runtime-princess->default; contrib->sources">
177177
<!-- Exclude dependency on java-cup and replace it with java-cup-runtime, which is enough.
178178
We use the JAR that is published by us instead of the one from net.sf.squirrel-sql.thirdparty-non-maven
179179
because the latter does not provide a separate JAR for java-cup-runtime. -->
180180
<exclude org="net.sf.squirrel-sql.thirdparty-non-maven" name="java-cup"/>
181181
</dependency>
182-
<dependency org="io.github.uuverifiers" name="ostrich_2.13" rev="1.4.1" conf="runtime-princess->default; contrib->sources">
182+
<dependency org="io.github.uuverifiers" name="ostrich_2.13" rev="2.0" conf="runtime-princess->default; contrib->sources">
183183
<!-- Exclude dependency on java-cup and replace it with java-cup-runtime, which is enough.
184184
We use the JAR that is published by us instead of the one from net.sf.squirrel-sql.thirdparty-non-maven
185185
because the latter does not provide a separate JAR for java-cup-runtime. -->

src/org/sosy_lab/java_smt/basicimpl/AbstractProver.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
public abstract class AbstractProver<T> implements BasicProverEnvironment<T> {
3131

3232
protected final boolean generateModels;
33-
private final boolean generateAllSat;
33+
protected final boolean generateAllSat;
3434
protected final boolean generateUnsatCores;
3535
private final boolean generateUnsatCoresOverAssumptions;
3636
protected final boolean enableSL;

src/org/sosy_lab/java_smt/solvers/princess/PrincessAbstractProver.java

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import java.util.List;
3030
import java.util.Optional;
3131
import java.util.Set;
32+
import java.util.concurrent.Callable;
3233
import org.sosy_lab.common.ShutdownNotifier;
3334
import org.sosy_lab.common.UniqueIdGenerator;
3435
import org.sosy_lab.common.collect.PathCopyingPersistentTreeMap;
@@ -53,7 +54,7 @@ abstract class PrincessAbstractProver<E> extends AbstractProverWithAllSat<E> {
5354
* Values returned by {@link Model#evaluate(Formula)}.
5455
*
5556
* <p>We need to record these to make sure that the values returned by the evaluator are
56-
* consistant. Calling {@link #isUnsat()} will reset this list as the underlying model has been
57+
* consistent. Calling {@link #isUnsat()} will reset this list as the underlying model has been
5758
* updated.
5859
*/
5960
protected final Set<IFormula> evaluatedTerms = new LinkedHashSet<>();
@@ -92,7 +93,10 @@ public boolean isUnsat() throws SolverException {
9293
final Value result = api.checkSat(true);
9394
if (result.equals(SimpleAPI.ProverStatus$.MODULE$.Sat())) {
9495
wasLastSatCheckSat = true;
95-
evaluatedTerms.add(api.partialModelAsFormula());
96+
if (this.generateModels || this.generateAllSat) {
97+
// we only build the model if we have set the correct options
98+
evaluatedTerms.add(callOrThrow(api::partialModelAsFormula));
99+
}
96100
return false;
97101
} else if (result.equals(SimpleAPI.ProverStatus$.MODULE$.Unsat())) {
98102
return true;
@@ -148,6 +152,9 @@ protected void popImpl() {
148152
* extend the original model.
149153
*/
150154
Collection<IFormula> getEvaluatedTerms() {
155+
Preconditions.checkState(
156+
this.generateModels || this.generateAllSat,
157+
"Model generation was not enabled, no evaluated terms available.");
151158
return Collections.unmodifiableSet(evaluatedTerms);
152159
}
153160

@@ -168,22 +175,22 @@ public Model getModel() throws SolverException {
168175
@SuppressWarnings("resource")
169176
@Override
170177
protected PrincessModel getEvaluatorWithoutChecks() throws SolverException {
171-
final PartialModel partialModel;
172-
try {
173-
partialModel = partialModel();
174-
} catch (SimpleAPIException ex) {
175-
throw new SolverException(ex.getMessage(), ex);
176-
}
178+
final PartialModel partialModel = callOrThrow(api::partialModel);
177179
return registerEvaluator(new PrincessModel(this, partialModel, creator, api));
178180
}
179181

180182
/**
181-
* This method only exists to allow catching the exception from Scala in Java.
183+
* This method only exists to allow catching some exceptions from Scala in Java.
182184
*
183-
* @throws SimpleAPIException if model can not be constructed.
185+
* @throws SolverException if the callable throws an Exception (including {@link
186+
* SimpleAPIException})
184187
*/
185-
private PartialModel partialModel() throws SimpleAPIException {
186-
return api.partialModel();
188+
private <T> T callOrThrow(Callable<T> callable) throws SolverException {
189+
try {
190+
return callable.call();
191+
} catch (Exception ex) { // mainly for catching SimpleAPIException
192+
throw new SolverException(ex.getMessage(), ex);
193+
}
187194
}
188195

189196
@Override

src/org/sosy_lab/java_smt/solvers/princess/PrincessFormulaCreator.java

Lines changed: 46 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
import static scala.collection.JavaConverters.asJava;
1616
import static scala.collection.JavaConverters.asJavaCollection;
1717

18-
import ap.basetypes.IdealInt;
1918
import ap.parser.IAtom;
2019
import ap.parser.IBinFormula;
2120
import ap.parser.IBinJunctor;
@@ -42,16 +41,14 @@
4241
import ap.theories.arrays.ExtArray;
4342
import ap.theories.bitvectors.ModuloArithmetic;
4443
import ap.theories.nia.GroebnerMultiplication$;
45-
import ap.theories.rationals.Fractions;
46-
import ap.theories.rationals.Rationals$;
44+
import ap.theories.rationals.Rationals;
4745
import ap.types.Sort;
4846
import ap.types.Sort$;
4947
import com.google.common.base.Preconditions;
5048
import com.google.common.collect.HashBasedTable;
5149
import com.google.common.collect.ImmutableList;
5250
import com.google.common.collect.ImmutableSet;
5351
import com.google.common.collect.Table;
54-
import java.lang.reflect.InvocationTargetException;
5552
import java.util.HashMap;
5653
import java.util.LinkedHashMap;
5754
import java.util.List;
@@ -108,6 +105,13 @@ class PrincessFormulaCreator
108105
// modmod.bv_xnor()?
109106
// modmod.bv_comp()?
110107

108+
theoryFunctionKind.put(Rationals.addition(), FunctionDeclarationKind.ADD);
109+
theoryFunctionKind.put(Rationals.multiplication(), FunctionDeclarationKind.MUL);
110+
theoryFunctionKind.put(Rationals.multWithFraction(), FunctionDeclarationKind.MUL);
111+
theoryFunctionKind.put(Rationals.multWithRing(), FunctionDeclarationKind.MUL);
112+
theoryFunctionKind.put(Rationals.division(), FunctionDeclarationKind.DIV);
113+
theoryFunctionKind.put(Rationals.RatDivZero(), FunctionDeclarationKind.OTHER);
114+
111115
// casts to integer, sign/zero-extension?
112116

113117
theoryPredKind.put(ModuloArithmetic.bv_ult(), FunctionDeclarationKind.BV_ULT);
@@ -207,30 +211,28 @@ public Object convertValue(IExpression value) {
207211
return ((IIntLit) value).value().bigIntValue();
208212
}
209213
if (value instanceof IFunApp) {
210-
IFunApp fun = (IFunApp) value;
211-
switch (fun.fun().name()) {
214+
IFunApp app = (IFunApp) value;
215+
switch (app.fun().name()) {
212216
case "true":
213-
Preconditions.checkArgument(fun.fun().arity() == 0);
217+
Preconditions.checkArgument(app.fun().arity() == 0);
214218
return true;
215219
case "false":
216-
Preconditions.checkArgument(fun.fun().arity() == 0);
220+
Preconditions.checkArgument(app.fun().arity() == 0);
217221
return false;
218222
case "mod_cast":
219223
// we found a bitvector BV(lower, upper, ctxt), lets extract the last parameter
220-
return ((IIntLit) fun.apply(2)).value().bigIntValue();
221-
case "_int":
222-
case "Rat_int":
223-
Preconditions.checkArgument(fun.fun().arity() == 1);
224-
ITerm term = fun.apply(0);
224+
return ((IIntLit) app.apply(2)).value().bigIntValue();
225+
case "Rat_fromRing":
226+
Preconditions.checkArgument(app.fun().arity() == 1);
227+
ITerm term = app.apply(0);
225228
if (term instanceof IIntLit) {
226229
return ((IIntLit) term).value().bigIntValue();
227230
}
228231
break;
229-
case "_frac":
230232
case "Rat_frac":
231-
Preconditions.checkArgument(fun.fun().arity() == 2);
232-
ITerm term1 = fun.apply(0);
233-
ITerm term2 = fun.apply(1);
233+
Preconditions.checkArgument(app.fun().arity() == 2);
234+
ITerm term1 = app.apply(0);
235+
ITerm term2 = app.apply(1);
234236
if (term1 instanceof IIntLit && term2 instanceof IIntLit) {
235237
Rational ratValue =
236238
Rational.of(
@@ -241,7 +243,7 @@ public Object convertValue(IExpression value) {
241243
break;
242244
case "str_empty":
243245
case "str_cons":
244-
return strToString(fun);
246+
return strToString(app);
245247
default:
246248
}
247249
}
@@ -360,46 +362,39 @@ private String getName(IExpression input) {
360362
}
361363
}
362364

363-
/** Returns true if the expression is a constant number. */
364-
private static boolean isConstant(IFunApp pExpr) {
365-
for (IExpression sub : asJava(pExpr.args())) {
366-
if (!(sub instanceof IIntLit)) {
367-
return false;
365+
/** Returns true if the expression is a constant value. */
366+
private static boolean isValue(IExpression input) {
367+
if (input instanceof IBoolLit || input instanceof IIntLit) {
368+
// Boolean or integer literal
369+
return true;
370+
} else if (input instanceof IFunApp) {
371+
IFunApp app = (IFunApp) input;
372+
IFunction fun = app.fun();
373+
if (fun.equals(Rationals.fromRing()) || fun.equals(Rationals.frac())) {
374+
// Rational number literal
375+
for (IExpression sub : asJava(app.args())) {
376+
if (!(sub instanceof IIntLit)) {
377+
return false;
378+
}
379+
}
380+
return true;
381+
}
382+
if (fun.equals(ModuloArithmetic.mod_cast()) && input.apply(2) instanceof IIntLit) {
383+
// Bitvector literal
384+
return true;
385+
}
386+
if (CONSTANT_UFS.contains(fun.name())) {
387+
// Nil, Cons from String theory
388+
return true;
368389
}
369390
}
370-
return true;
371-
}
372-
373-
/** Returns true if the expression is an integer literal. */
374-
private static boolean isRatInt(IFunApp pExpr) {
375-
// We need to use reflection to get Rationals.int() as `int` can't be a method name in Java
376-
final IFunction ratInt;
377-
try {
378-
ratInt = (IFunction) Fractions.class.getMethod("int").invoke(Rationals$.MODULE$);
379-
} catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException ex) {
380-
throw new RuntimeException(ex);
381-
}
382-
return isConstant(pExpr) && pExpr.fun().equals(ratInt);
383-
}
384-
385-
/** Returns true if the expression is a faction literal. */
386-
private static boolean isRatFrac(IFunApp pExpr) {
387-
return isConstant(pExpr) && pExpr.fun().equals(Rationals$.MODULE$.frac());
391+
return false;
388392
}
389393

390394
@SuppressWarnings("deprecation")
391395
@Override
392396
public <R> R visit(FormulaVisitor<R> visitor, final Formula f, final IExpression input) {
393-
if (input instanceof IIntLit) {
394-
IdealInt value = ((IIntLit) input).value();
395-
return visitor.visitConstant(f, value.bigIntValue());
396-
397-
} else if (input instanceof IBoolLit) {
398-
IBoolLit literal = (IBoolLit) input;
399-
return visitor.visitConstant(f, literal.value());
400-
401-
} else if (input instanceof IFunApp
402-
&& (isRatInt((IFunApp) input) || isRatFrac((IFunApp) input))) {
397+
if (isValue(input)) {
403398
return visitor.visitConstant(f, convertValue(input));
404399

405400
} else if (input instanceof IQuantified) {
@@ -474,20 +469,6 @@ public <R> R visit(FormulaVisitor<R> visitor, final Formula f, final IExpression
474469
return visit(visitor, f, ((IIntFormula) input).t());
475470
}
476471

477-
if (kind == FunctionDeclarationKind.OTHER && input instanceof IFunApp) {
478-
if (ModuloArithmetic.mod_cast().equals(((IFunApp) input).fun())
479-
&& ((IFunApp) input).apply(2) instanceof IIntLit) {
480-
// mod_cast(0, 256, 7) -> BV=7 with bitsize=8
481-
return visitor.visitConstant(f, convertValue(input));
482-
}
483-
}
484-
485-
if (kind == FunctionDeclarationKind.UF && input instanceof IFunApp) {
486-
if (CONSTANT_UFS.contains(((IFunApp) input).fun().name())) {
487-
return visitor.visitConstant(f, convertValue(input));
488-
}
489-
}
490-
491472
ImmutableList.Builder<Formula> args = ImmutableList.builder();
492473
ImmutableList.Builder<FormulaType<?>> argTypes = ImmutableList.builder();
493474
int arity = input.length();

src/org/sosy_lab/java_smt/solvers/princess/PrincessRationalFormulaManager.java

Lines changed: 29 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,14 @@ public class PrincessRationalFormulaManager
4040
@Override
4141
protected boolean isNumeral(IExpression value) {
4242
if (value instanceof IFunApp) {
43-
IFunApp fun = (IFunApp) value;
44-
switch (fun.fun().name()) {
45-
case "int":
46-
case "Rat_int":
47-
assert fun.fun().arity() == 1;
48-
return ifmgr.isNumeral(fun.apply(0));
49-
case "frac":
50-
assert fun.fun().arity() == 2;
51-
return ifmgr.isNumeral(fun.apply(0)) && ifmgr.isNumeral(fun.apply(1));
43+
IFunApp app = (IFunApp) value;
44+
switch (app.fun().name()) {
45+
case "Rat_fromRing":
46+
assert app.fun().arity() == 1;
47+
return ifmgr.isNumeral(app.apply(0));
48+
case "Rat_frac":
49+
assert app.fun().arity() == 2;
50+
return ifmgr.isNumeral(app.apply(0)) && ifmgr.isNumeral(app.apply(1));
5251
}
5352
}
5453
return false;
@@ -68,40 +67,34 @@ protected IExpression makeNumberImpl(BigInteger i) {
6867
return fromInteger(ifmgr.makeNumberImpl(i));
6968
}
7069

71-
@Override
72-
protected IExpression makeNumberImpl(Rational pI) {
73-
return divide(makeNumberImpl(pI.getNum()), makeNumberImpl(pI.getDen()));
74-
}
75-
76-
@Override
77-
protected IExpression makeNumberImpl(String i) {
78-
return makeNumberImpl(new BigDecimal(i));
79-
}
80-
8170
@Override
8271
protected IExpression makeNumberImpl(double pNumber) {
8372
return makeNumberImpl(BigDecimal.valueOf(pNumber));
8473
}
8574

8675
@Override
8776
protected IExpression makeNumberImpl(BigDecimal pNumber) {
88-
if (pNumber.stripTrailingZeros().scale() <= 0) {
89-
// We have an integer number
90-
// Return the term for a/1
91-
return PrincessEnvironment.rationalTheory.int2ring(
92-
ifmgr.makeNumberImpl(pNumber.toBigInteger()));
77+
return makeNumberImpl(Rational.ofBigDecimal(pNumber));
78+
}
79+
80+
@Override
81+
protected IExpression makeNumberImpl(Rational pI) {
82+
if (pI.isIntegral()) {
83+
return makeNumberImpl(pI.getNum());
9384
} else {
94-
// We have a fraction a/b
95-
// Convert the numerator and the divisor and then return the fraction
96-
List<ITerm> args =
97-
ImmutableList.of(
98-
ifmgr.makeNumberImpl(pNumber.unscaledValue()),
99-
ifmgr.makeNumberImpl(BigInteger.valueOf(10).pow(pNumber.scale())));
10085
return new IFunApp(
101-
PrincessEnvironment.rationalTheory.frac(), PrincessEnvironment.toSeq(args));
86+
PrincessEnvironment.rationalTheory.frac(),
87+
PrincessEnvironment.toSeq(
88+
ImmutableList.of(
89+
ifmgr.makeNumberImpl(pI.getNum()), ifmgr.makeNumberImpl(pI.getDen()))));
10290
}
10391
}
10492

93+
@Override
94+
protected IExpression makeNumberImpl(String i) {
95+
return makeNumberImpl(new BigDecimal(i));
96+
}
97+
10598
@Override
10699
protected IExpression makeVariableImpl(String varName) {
107100
return getFormulaCreator().makeVariable(PrincessEnvironment.FRACTION_SORT, varName);
@@ -117,7 +110,7 @@ protected ITerm toType(IExpression param) {
117110

118111
@Override
119112
protected IExpression floor(IExpression number) {
120-
throw new UnsupportedOperationException("floor is not supported in Princess");
113+
return Rationals.ring2int((ITerm) number);
121114
}
122115

123116
@Override
@@ -142,7 +135,10 @@ protected IExpression multiply(IExpression number1, IExpression number2) {
142135

143136
@Override
144137
protected IExpression divide(IExpression number1, IExpression number2) {
145-
return Rationals.div(toType(number1), toType(number2));
138+
// SMT-LIB allows division by zero, so we use divWithSpecialZero here.
139+
// If the divisor is zero, divWithSpecialZero will evaluate to a unary UF `ratDivZero`,
140+
// otherwise it is the normal division
141+
return Rationals.divWithSpecialZero(toType(number1), toType(number2));
146142
}
147143

148144
@Override

0 commit comments

Comments
 (0)