Skip to content
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
6a37798
Added user propagators
ThomasHaas Jan 12, 2024
0b7a8be
Cleanup of Z3UserPropagator
ThomasHaas Jan 15, 2024
17bd762
Removed unused code in example
ThomasHaas Jan 15, 2024
c8a1d6b
Added user propagators
ThomasHaas Jan 12, 2024
2673fc5
Cleanup of Z3UserPropagator
ThomasHaas Jan 15, 2024
4e42194
Removed unused code in example
ThomasHaas Jan 15, 2024
7e20470
Merge remote-tracking branch 'origin/userPropagator' into userPropagator
ThomasHaas Jan 15, 2024
134e2fd
Temporary commit
ThomasHaas Jan 18, 2024
ac52ef4
Removed all functionality related to equality tracking (it was partia…
ThomasHaas Jan 18, 2024
22fdefc
Improved user propagator documentation.
ThomasHaas Jan 19, 2024
22376a9
Updated licensing information
ThomasHaas Jan 20, 2024
0c7294f
Merge remote-tracking branch 'upstream/master' into userPropagator
ThomasHaas Feb 4, 2024
225367d
- Added user propagator feature: onDecision callback.
ThomasHaas Feb 4, 2024
e3f2a0c
Updated SimpleUserPropagator example to use the new onDecision callback
ThomasHaas Feb 4, 2024
78d7b8e
More efficient Z3UserPropagator implementation trying to reuse Boolea…
ThomasHaas Feb 4, 2024
efe8115
Changed signature of UserPropagator.onKnownValue.
ThomasHaas Feb 5, 2024
2bf65c9
Fixed performance bottleneck in Nqueens+Propagator example.
ThomasHaas Feb 5, 2024
511a562
More efficient Z3UserPropagator.encapsulate implementation.
ThomasHaas Feb 5, 2024
bf209de
Removed unused variable
ThomasHaas Feb 5, 2024
1938a23
Removed more unused variables.
ThomasHaas Feb 5, 2024
7fc347f
- Combined UserPropagator.initialize with UserPropagator.injectBackend.
ThomasHaas Feb 11, 2024
84c109d
Replaced bitshift by bitwise rotation in Z3UserPropagator.encapsulate
ThomasHaas Feb 11, 2024
f147009
Evaluator: cleanup
kfriedberger Feb 4, 2024
e396610
#352: add model-evaluation for floating-point numbers.
kfriedberger Feb 4, 2024
d02a3c0
#357: replace check for boolean constants with direct pointer compari…
kfriedberger Feb 5, 2024
887798e
reduce expiration time for artifact of build-dependencies.
kfriedberger Feb 5, 2024
a208911
include more information about segmentation faults in build artifacts.
kfriedberger Feb 5, 2024
edd6df1
format code and add some documentation.
kfriedberger Feb 12, 2024
7f757b6
extend NQueens with ALL-SAT routine (which is as fast/slow as the pla…
kfriedberger Feb 12, 2024
4221a04
fix simple styling issues.
kfriedberger Feb 12, 2024
91f4293
small refactoring and adding a simple Junit test.
kfriedberger Feb 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/org/sosy_lab/java_smt/api/BasicProverEnvironment.java
Original file line number Diff line number Diff line change
Expand Up @@ -189,4 +189,16 @@ interface AllSatCallback<R> {
/** Returning the result generated after all the {@link #apply} calls went through. */
R getResult() throws InterruptedException;
}

/**
* Registers a {@link UserPropagator} for this prover environment.
* Only a single user propagator can be registered at a time.
*
* @param propagator The user propagator to register.
* @return true, if the user propagator was successfully registered. Most SMT solvers
* do not support user propagators and hence return "false".
*/
default boolean registerUserPropagator(UserPropagator propagator) {
return false;
}
}
100 changes: 100 additions & 0 deletions src/org/sosy_lab/java_smt/api/PropagatorBackend.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// This file is part of JavaSMT,
// an API wrapper for a collection of SMT solvers:
// https://github.com/sosy-lab/java-smt
//
// SPDX-FileCopyrightText: 2024 Dirk Beyer <https://www.sosy-lab.org>
//
// SPDX-License-Identifier: Apache-2.0

package org.sosy_lab.java_smt.api;

import java.util.Optional;

/**
* The PropagatorBackend class is used by {@link UserPropagator} to implement custom user
* propagators.
* It contains functions to interact with the SAT/SMT core during solving, for example, it provides
* the ability to propagate conflicts and to influence the decision-making.
*/
public interface PropagatorBackend {
Comment thread
kfriedberger marked this conversation as resolved.

/**
* Registers an expression to be observed by a {@link UserPropagator}.
* See {@link UserPropagator#onKnownValue} and {@link UserPropagator#onDecision}
* for more information.
*
* @param theoryExpr The expression to observe.
*/
void registerExpression(BooleanFormula theoryExpr);

/**
* Raises a conflict caused by a set of conflicting assignments. Shall only be called
* from within a callback by {@link UserPropagator} such as
* {@link UserPropagator#onKnownValue} and
* {@link UserPropagator#onFinalCheck()}.
*
* <p> Effectively causes the solver to learn the implication "{@code conflictExpressions} =>
* false"
*
* @param conflictExpressions A set of inconsistent assignments.
*/
void propagateConflict(BooleanFormula[] conflictExpressions);

/**
* Propagates a consequence implied by a set of assigned expressions. Shall only be called
* from within a callback by {@link UserPropagator} such as
* {@link UserPropagator#onKnownValue} and
* {@link UserPropagator#onFinalCheck()}.
*
* <p> Effectively causes the solver to learn the implication "/\ {@code assignedExpressions} =>
* {@code consequence}"
* It is possible to have an empty set of {@code assignedExpressions} to generate a theory lemma.
*
* @param assignedExpressions A set of assigned expressions.
* @param consequence The consequence implied by the assigned expressions.
*/
void propagateConsequence(BooleanFormula[] assignedExpressions, BooleanFormula consequence);

/**
* Propagates a decision to be made by the solver.
* If called during {@link UserPropagator#onKnownValue}, will set the next decision to be made.
* If called during {@link UserPropagator#onDecision}, will overwrite the current decision to be
* made.
*
* @param expr The expression to assign to next.
* @param value The value to be assigned. If not given, the solver will decide.
* @return False, if the value of {@code expr} is already assigned. True, otherwise.
* Note that the value of {@code expr} may already be decided before being reported via
* {@link UserPropagator#onKnownValue}.
*/
boolean propagateNextDecision(BooleanFormula expr, Optional<Boolean> value);

/**
* Enables tracking of expression values for the associated {@link UserPropagator} via
* {@link UserPropagator#onKnownValue(BooleanFormula, BooleanFormula)}.
*
* <p>This function is typically called from {@link UserPropagator#initialize()} if the
* theory solver needs to listen to the value of expressions registered by
* {@link #registerExpression}.
*/
void notifyOnKnownValue();

/**
* Enables tracking of decisions made for the associated {@link UserPropagator} via
* {@link UserPropagator#onDecision(BooleanFormula, boolean)}.
*
* <p>This function is typically called from {@link UserPropagator#initialize()} if the
* theory solver needs to listen to and/or modify the decisions made by the solver on
* expressions registered by {@link #registerExpression}.
*/
void notifyOnDecision();

/**
* Enables the final callback {@link UserPropagator#onFinalCheck()} that is invoked
* when the solver finds a full satisfying assignment.
*
* <p>This function is typically called from {@link UserPropagator#initialize()} if the
* theory solver needs to perform final consistency checks.
*/
void notifyOnFinalCheck();
}
114 changes: 114 additions & 0 deletions src/org/sosy_lab/java_smt/api/UserPropagator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// This file is part of JavaSMT,
// an API wrapper for a collection of SMT solvers:
// https://github.com/sosy-lab/java-smt
//
// SPDX-FileCopyrightText: 2024 Dirk Beyer <https://www.sosy-lab.org>
//
// SPDX-License-Identifier: Apache-2.0

package org.sosy_lab.java_smt.api;

/**
* Allows user-defined propagators (~ theory solvers) to be implemented.
* It is advised to inherit from {@link org.sosy_lab.java_smt.basicimpl.AbstractUserPropagator}
* rather than implementing this interface directly.
*
* <p> A user propagator provides various callbacks that are invoked by the solver during the
* solving process. Within these callbacks, the user can react to and influence the solver
* by calling into the {@link PropagatorBackend}. For example, he can raise conflicts
* whenever the solver makes assignments inconsistent to the user-defined theory.
*/
public interface UserPropagator {

/**
* This callback is invoked whenever the solver creates a new decision level.
* A user propagator should maintain backtracking points on each push to enable later
* backtracking.
*/
void onPush();

/**
* This callback is invoked when the solver backtracks. The solver can backtrack multiple
* levels simultaneously.
* @param numPoppedLevels The number of levels to backtrack (~ number of pushes to backtrack).
*/
void onPop(int numPoppedLevels);

/**
* This callback is invoked when the solver finds a complete satisfying assignment.
* The user can check the found model for consistency and potentially raise conflicts via
* {@link PropagatorBackend#propagateConflict(BooleanFormula[])}.
* <p>
* Note: This callback is only invoked if the user propagator enabled it via
* {@link PropagatorBackend#notifyOnFinalCheck()}.
*/
void onFinalCheck();

/**
* This callback is invoked if the solver gets to know the value of a registered expression
* ({@link #registerExpression}).
* Within the callback, the user can raise conflicts via
* {@link PropagatorBackend#propagateConflict}, propagate consequences via
* {@link PropagatorBackend#propagateConsequence}, or influence the solvers decision heuristics
* via {@link PropagatorBackend#propagateNextDecision}.
* <p>
* The reported value is only known on the current and later push levels,
* but will get invalidated when backtracking.
* <p>
* Note: This callback is only invoked if the user propagator enabled it via
* {@link PropagatorBackend#notifyOnKnownValue()}.
*
* @param expr The expressions whose value is known.
* @param value The value of the expression.
*/
void onKnownValue(BooleanFormula expr, boolean value);

/**
* This callback is invoked if the solver decides to branch on a registered expression.
* ({@link #registerExpression}).
* Within the callback, the user can change the decision by calling
* {@link PropagatorBackend#propagateNextDecision}.
* <p>
* Note: This callback is only invoked if the user propagator enabled it via
* {@link PropagatorBackend#notifyOnDecision()}.
* @param expr The expressions whose value gets decided (usually a literal).
* @param value The decision value.
*/
void onDecision(BooleanFormula expr, boolean value);

/**
* Connects this user propagator with a {@link PropagatorBackend}. The backend is used

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this method be called mutliple times?
If yes, is any existing propagator replaced by the new one?
If no, do we want to throw an exception?
PLease document this behaviour.

@kfriedberger kfriedberger Feb 11, 2024

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a user-propagator at each point in time, e.g., after pushing several formulas? Or should we create a user-propagator before creating any symbols? For Z3, it seems to be sufficient to just instantiate the user-propagator right before calling the SMT/SAT-check.

@ThomasHaas ThomasHaas Feb 11, 2024

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have not tried out what the limitations are, but it is definitely fine to add the user propagator anytime before the first SAT-check. You can create the whole solver formula and then add the propagator (you need to register the expressions after adding the propagator though)
Whether one can add them after the first SAT-check is something I didn't test.

EDIT: Regarding multiple calls to injectBackend (and initialize): I guess in theory this could get called multiple times if the user reuses the same propagator for multiple solvers (at any given time it should only be associated to a single solver though). Generally I would advise against this practice: the user should simply not do this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After thinking more about this, I think it is always bad to register the same user propagator multiple times.
I made this point clear in the documentation and the implementation provided by AbstractUserPropagator will throw an error if the method is called multiple times.

* to register expressions, raise conflicts, propagate consequences, etc.
* @param backend The propagator backend.
*/
void injectBackend(PropagatorBackend backend);

/**
* This method is similar to a constructor but is guaranteed to get invoked only after
* {@link #injectBackend} was successfully called.
* The user can enable notifications by accessing the injected {@link PropagatorBackend}.
*/
void initialize();
Comment thread
kfriedberger marked this conversation as resolved.
Outdated

/**
* Registers an expression to be observed by the {@link UserPropagator}.
* Solver actions related to the expression are reported:
* <ul>
* <li>
* The callback {@link UserPropagator#onKnownValue} gets invoked if the value of a
* registered expression becomes known (if notification was enabled via
* {@link PropagatorBackend#notifyOnKnownValue}).
* </li>
* <li>
* The callback {@link UserPropagator#onDecision} gets invoked right before the
* solver decides on the value of a registered expression (if notification was enabled via
* {@link PropagatorBackend#notifyOnDecision()}).
* </li>
* </ul>
*
* @param theoryExpr The expression to observe.
*/
void registerExpression(BooleanFormula theoryExpr);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this expression need to be a symbol (variable) or can it be a complex boolean formula?
Can we also register non-boolean symbols?

Examples:

  • Can we register to "a&&b"? Would the propagator then be called for each operation involving "a&&b" in that exact combination/operation?
  • Can we register "counter==42"? Would the propagator then be called for each operation involving "counter"?

@ThomasHaas ThomasHaas Feb 11, 2024

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can register any boolean expression, even counter==42. However, the propagator will notify on the value of the boolean expression and not on any variables inside. Basically, the propagator works on the boolean skeleton of the formula.
So you cannot track the precise value of counter and only indirectly know about its value by tracking atoms/predicates over counter.

Also, the propagator is more on the semantic level rather the syntactic one. If your whole asserted formula is a || b but you registered a && b, the propagator will still notify you once its partially found model fixes the value of a && b.
In this case, the solver will likely do this:

Decide a = false
Unit propagate b = true
Report (a&&b) == false to UserPropagator

You can run the SimpleUserPropagator example I added which showcases various interactions and prints the solver steps involved.

EDIT: While all of the above is true for Z3, I don't really want to give any hard guarantees in case other solvers in the future behave differently. That being said, tracking of simple variables would be sufficient to track arbitrary expressions by enriching the formula with equivalences of the form var <=> expr (Z3 just does this trick internally).

EDIT 2: Z3 has a richer set of user propagator features that also allow the user to register non-boolean expressions.
However, on other types of expressions(*) it only reports equalities/disequalities rather than their concrete values (I didn't add the equality callback though).

(*) An exception are BV expressions which can be bit-blasted to booleans. Because of this, Z3 is able to report values/decisions on booleans and BVs.



}
59 changes: 59 additions & 0 deletions src/org/sosy_lab/java_smt/basicimpl/AbstractUserPropagator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* JavaSMT is an API wrapper for a collection of SMT solvers.
* This file is part of JavaSMT.
*
* Copyright (C) 2007-2016 Dirk Beyer
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.sosy_lab.java_smt.basicimpl;

import com.google.common.base.Preconditions;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.sosy_lab.java_smt.api.BooleanFormula;
import org.sosy_lab.java_smt.api.UserPropagator;
import org.sosy_lab.java_smt.api.PropagatorBackend;

public abstract class AbstractUserPropagator implements UserPropagator {

protected @Nullable PropagatorBackend backend;

@Override
public void onFinalCheck() {

Comment thread
kfriedberger marked this conversation as resolved.
}

@Override
public void onKnownValue(BooleanFormula expr, boolean value) {

}

@Override
public void onDecision(BooleanFormula expr, boolean value) {

}

@Override
public final void injectBackend(PropagatorBackend backend) {
this.backend = Preconditions.checkNotNull(backend);
}

@Override
public void registerExpression(BooleanFormula theoryExpr) {
Preconditions.checkState(backend != null);
Comment thread
kfriedberger marked this conversation as resolved.
Outdated
backend.registerExpression(theoryExpr);
}

}
Loading