Skip to content

Post missing symmetry breaking constraint #73

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

Merged
merged 3 commits into from
Dec 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 6 additions & 8 deletions src/main/java/cryptator/cmd/CryptaBiConsumer.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import cryptator.specs.ICryptaEvaluation;
import cryptator.specs.ICryptaNode;
import cryptator.specs.ICryptaSolution;
import cryptator.specs.ICryptaSolutionStore;
import cryptator.tree.CryptaEvaluation;
import cryptator.tree.CryptaEvaluationException;
import cryptator.tree.GraphvizExport;
Expand All @@ -27,11 +28,11 @@
import guru.nidi.graphviz.engine.Graphviz;
import guru.nidi.graphviz.model.Graph;

public class CryptaBiConsumer implements BiConsumer<ICryptaNode, ICryptaSolution> {
public class CryptaBiConsumer implements BiConsumer<ICryptaNode, ICryptaSolution>, ICryptaSolutionStore {

private final Logger logger;

private long solutionCount;
private int solutionCount;

private Optional<ICryptaSolution> lastSolution;

Expand All @@ -46,18 +47,15 @@ public CryptaBiConsumer(final Logger logger) {
internal = new SolutionCounter();
}

public final long getSolutionCount() {
@Override
public final int getSolutionCount() {
return solutionCount;
}

public final Optional<ICryptaSolution> getLastSolution() {
return lastSolution;
}

public final Optional<ICryptaSolution> getUniqueSolution() {
return solutionCount <= 1 ? lastSolution : Optional.empty();
}

public final int getErrorCount() {
return errorCount;
}
Expand Down Expand Up @@ -94,8 +92,8 @@ private class SolutionCounter implements BiConsumer<ICryptaNode, ICryptaSolution

@Override
public void accept(final ICryptaNode t, final ICryptaSolution u) {
lastSolution = Optional.of(u);
solutionCount++;
lastSolution = Optional.of(u);
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/main/java/cryptator/gen/CryptaGenModel.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ private void postLeftOrRightConstraints() {
}

private void postSymBreakLengthLenConstraint() {
left.getMaxLength().le(right.getMaxLength());
left.getMaxLength().le(right.getMaxLength()).post();

}

Expand Down
15 changes: 10 additions & 5 deletions src/main/java/cryptator/gen/CryptaListGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ private static void parallelSolve(final CryptaGenModel gen, final Consumer<ICryp
}

@Override
public void generate(final BiConsumer<ICryptaNode, ICryptaSolution> consumer) throws CryptaModelException {
public long generate(final BiConsumer<ICryptaNode, ICryptaSolution> consumer) throws CryptaModelException {
final CryptaGenModel gen = buildModel();
clog.logOnModel(gen);

Expand All @@ -119,6 +119,7 @@ public void generate(final BiConsumer<ICryptaNode, ICryptaSolution> consumer) th
parallelSolve(gen, cons, nthreads);
}
clog.logOnSolver(gen);
return gen.getModel().getSolver().getSolutionCount();
}

// FIXME are consumers thread-safe ? they are used in parallel !
Expand Down Expand Up @@ -167,13 +168,17 @@ public void accept(final ICryptaNode t) {
try {
final CryptaBiConsumer collect = buildBiConsumer();
solver.solve(t, config, collect);
Optional<ICryptaSolution> solution = collect.getUniqueSolution();
if (solution.isPresent()) {
internal.accept(t, solution.get());
if (collect.getErrorCount() == 0) {
final Optional<ICryptaSolution> solution = collect.getUniqueSolution();
if (solution.isPresent()) {
internal.accept(t, solution.get());
}
} else {
logger.log(Level.WARNING, "Solve the candidate cryptarithm [ERROR]");
}
} catch (CryptaModelException | CryptaSolverException e) {
errorCount.incrementAndGet();
logger.log(Level.WARNING, "Fail to solve the cryptarithm", e);
logger.log(Level.WARNING, "Solve the candidate cryptarithm [FAIL]", e);
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/cryptator/specs/ICryptaGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ public interface ICryptaGenerator {
*
* @param consumer the consumer that handles the generated cryptarithm along
* with its solution.
* @return the number of candidate cryptarithm
* @throws CryptaModelException if there was an error during the generation.
*/
void generate(BiConsumer<ICryptaNode, ICryptaSolution> consumer) throws CryptaModelException;
long generate(BiConsumer<ICryptaNode, ICryptaSolution> consumer) throws CryptaModelException;

}
23 changes: 23 additions & 0 deletions src/main/java/cryptator/specs/ICryptaSolutionStore.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* This file is part of cryptator, https://github.com/arnaud-m/cryptator
*
* Copyright (c) 2022, Université Côte d'Azur. All rights reserved.
*
* Licensed under the BSD 3-clause license.
* See LICENSE file in the project root for full license information.
*/
package cryptator.specs;

import java.util.Optional;

public interface ICryptaSolutionStore {

int getSolutionCount();

Optional<ICryptaSolution> getLastSolution();

default Optional<ICryptaSolution> getUniqueSolution() {
return getSolutionCount() <= 1 ? getLastSolution() : Optional.empty();
}

}
44 changes: 30 additions & 14 deletions src/test/java/cryptator/GenerateTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import static org.junit.Assert.assertEquals;

import java.util.Arrays;
import java.util.OptionalInt;

import org.junit.BeforeClass;
import org.junit.Test;
Expand All @@ -28,44 +29,59 @@ public static void configureTestLoggers() {
JULogUtil.configureTestLoggers();
}

public long testGenerate(final WordArray wordArray, final boolean lightModel, final boolean lightPropagation)
private void testGenerate(final int expectedSolCount, final OptionalInt expectedCandCount,
final WordArray wordArray, final boolean lightModel, final boolean lightPropagation)
throws CryptaModelException {
final CryptagenConfig config = new CryptagenConfig();
config.setLightModel(lightModel);
config.setLightPropagation(lightPropagation);
final CryptaListGenerator gen = new CryptaListGenerator(wordArray, config, Cryptagen.LOGGER);
CryptaBiConsumer cons = new CryptaBiConsumer(Cryptagen.LOGGER);
cons.withSolutionLog();
cons.withSolutionCheck(config.getArithmeticBase());
gen.generate(cons);
assertEquals(0, cons.getErrorCount());
return cons.getSolutionCount();
long actualCandCount = gen.generate(cons);
if (expectedCandCount.isPresent()) {
assertEquals(expectedCandCount.getAsInt(), actualCandCount);
}
assertEquals(expectedSolCount, cons.getSolutionCount());
}

public void testGenerate(final int expectedSolCount, final WordArray wordArray) throws CryptaModelException {
assertEquals(expectedSolCount, testGenerate(wordArray, false, false));
assertEquals(expectedSolCount, testGenerate(wordArray, false, true));
assertEquals(expectedSolCount, testGenerate(wordArray, true, false));
assertEquals(expectedSolCount, testGenerate(wordArray, true, true));
private void testGenerate(final int expectedSolCount, final WordArray wordArray) throws CryptaModelException {
testGenerate(expectedSolCount, OptionalInt.empty(), wordArray);
}

public void testGenerate(final int expectedSolCount, final String rightMember, final String... words)
throws CryptaModelException {
testGenerate(expectedSolCount, new WordArray(Arrays.asList(words), rightMember));
private void testGenerate(final int expectedSolCount, final OptionalInt expectedCandCount,
final WordArray wordArray) throws CryptaModelException {
testGenerate(expectedSolCount, expectedCandCount, wordArray, false, false);
testGenerate(expectedSolCount, expectedCandCount, wordArray, false, true);
testGenerate(expectedSolCount, expectedCandCount, wordArray, true, false);
testGenerate(expectedSolCount, expectedCandCount, wordArray, true, true);
}

@Test
public void testSendMoreMoney() throws CryptaModelException {
testGenerate(1, null, "send", "more", "money");
final WordArray words = new WordArray(Arrays.asList("send", "more", "money"), null);
testGenerate(1, OptionalInt.of(1), words);
}

@Test
public void testSendMuchMoreMoney() throws CryptaModelException {
WordArray words = new WordArray(Arrays.asList("send", "much", "more", "money"), null);
testGenerate(1, OptionalInt.of(6), words, false, true);

}

@Test
public void testPlanets1() throws CryptaModelException {
testGenerate(2, null, "venus", "earth", "uranus", "saturn");
WordArray words = new WordArray(Arrays.asList("venus", "earth", "uranus", "saturn"), null);
testGenerate(2, words);
}

@Test
public void testPlanets2() throws CryptaModelException {
testGenerate(1, "planets", "venus", "earth", "uranus", "saturn");
WordArray words = new WordArray(Arrays.asList("venus", "earth", "uranus", "saturn"), "planets");
testGenerate(1, words);
}

@Test
Expand Down