-
Notifications
You must be signed in to change notification settings - Fork 1
Public Interface #63
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Public Interface #63
Conversation
sourceSets { | ||
commonMain { | ||
dependencies { | ||
implementation(project(":core")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The module naming (and splitting, in the first place) is weird. API should NOT depend on the implementation, but the "implementation" should implement the public interface contract. Note that the "implementation" has a separate choice of making API dependency truly api
(in Gradle terms, api
vs implementation
configuration), however it is not "mandatory": the implementation might choose to comply with the interface without actually letting us know about it.
One example of a "good" api/impl split is SLF4J and its implementations (e.g. slf4j-simple). The same goes for log4j, if you prefer it.
That way, ideally, if the user only wants the interface, it declares the dependency on api
module and receives only that - bare interface. However, when the user wants some kinda implementation, it declares the dependency on "core"/"impl" module, and receives the working classes. If he wants to interact with this implementation via some fancy interface, it should either additionally declare a dependency on API-providing module, or force "core"/"impl" to provide it via api
Gradle-configuration.
Currently, your "kosat-api" module provides a mere "fancy wrapper" for CDCL, not an actual API.
See https://github.com/Lipen/sat-nexus/blob/master/lib/wrappers/src/wrap_minisat.rs
All in all, I would suggest NOT splitting impl into "core" and "wrapper", since it requires the duplicate multiplatform setup, and does not provide any value. Simply provide both "internal solver" (CDCL
) and "external solver" (KoSAT
) in the same module (and even in the same package org.kosat.*
).
* indexing). All variables must be defined with [newVariable] before adding | ||
* clauses. | ||
*/ | ||
fun newClause(vararg literals: Int) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about the users that already have an IntArray of literals? Are we going to force them to use *
-spread operator solver.newClause(*literals)
?
* clauses. | ||
*/ | ||
fun newClause(vararg literals: Int) { | ||
newClause(literals.asIterable()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You probably don't want to allow users override this method.
|
||
/** | ||
* Solve the SAT problem with the given assumptions, if any. Assumptions are | ||
* literals in DIMACS format. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only "vararg" version of solve
has the comment about DIMACS literals. Instead, it should be noted in each method, or simply in the class docs.
} | ||
|
||
/** | ||
* Solve the SAT problem with the given assumptions |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Document the return type.
true
= SAT (ok to query the model)
false
= UNSAT or UNKNOWN (model querying is prohibited)
/** | ||
* Solve the SAT problem with the given assumptions | ||
*/ | ||
fun solve(assumptions: Iterable<Int>): Boolean { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about UNKNOWN results? Budget exhaustion and async interrupts might lead to the solver state with is neither SAT (has a satisfying model) nor UNSAT (truly proved unsatisfiability).
*/ | ||
fun value(literal: Int): Boolean { | ||
if (literal == 0) { | ||
throw IllegalArgumentException("Literal must not be 0") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be written via require
(it throws IllegalArgumentException
, contrary to check
which throws IllegalStateException
).
fun tieShirtVarargs() { | ||
val solver = Kosat() | ||
|
||
assertFailsWith(IllegalArgumentException::class) { solver.newClause(1, 2) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about assertFailsWith<IllegalStateException> { ... }
? No need for ::class
when we have reified
:)
assertTrue(solver.solve(setOf(-1))) | ||
assertFalse(solver.solve(listOf(1))) | ||
assertTrue(solver.solve(listOf(-1, 2))) | ||
assertFalse(solver.solve(setOf(-1, -2))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about testing the vararg
(and IntArray, if you happen to choose to split them as such) solve
method?
myInclude("core") | ||
myInclude("cli") | ||
myInclude("web") | ||
myInclude("api") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"wrapper", see the first comment in the review
Depends on #62. Implements a public wrapper for solver in
Kosat
class.I have chosen to sacrifice a little bit of performance with
Iterable<Int>
instead ofList<Int>
and a little bit of code duplication. I don't think performance should be a concern at this point, as memory is reallocated into arrays anyway, at least twice in the solver itself.Furthermore, I have also decided to include rigorous checks into methods (like enforcing allocating variables before adding clauses) because it will be easier to undo this decision in the future (as opposed to adding the checks later and introducing a breaking change~ well, not really, it's not like it's specified anywhere, might be ok to bump patch).
Unfortunately, I still was not able to include Java tests in the same project, so I have only tested the interface from java world manually. It looks ok, but if writing tests in Java is necessary, I will figure something out.