Skip to content

Generate multiplications based on a words list #110

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 2 commits into from
Jan 25, 2023
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
4 changes: 4 additions & 0 deletions src/main/java/cryptator/config/CryptaLogConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,8 @@ public final Verbosity getVerbosity() {
return verbosity;
}

public final void setVerbosity(Verbosity verbosity) {
this.verbosity = verbosity;
}

}
11 changes: 11 additions & 0 deletions src/main/java/cryptator/config/CryptagenConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ public class CryptagenConfig extends CryptaCmdConfig {
@Option(name = "-grid", usage = "grid size for crossword cryptarithm)")
private int gridSize = 0;

@Option(name = "-mult", handler = ExplicitBooleanOptionHandler.class, usage = "generate multiplication cryptarithms")
private boolean multModel = false;

@Option(name = "-ctry", usage = "country code for doubly true cryptarithms)")
private String countryCode = "EN";

Expand Down Expand Up @@ -88,6 +91,14 @@ public final void setLightPropagation(final boolean lightPropagation) {
this.lightPropagation = lightPropagation;
}

public final boolean isMultModel() {
return multModel;
}

public final void setMultModel(boolean multModel) {
this.multModel = multModel;
}

@Override
public String toString() {
return super.toString() + "\nc LANG " + langCode + "\nc THREADS " + nthreads + "\nc LIGHT_MOD " + lightModel
Expand Down
35 changes: 25 additions & 10 deletions src/main/java/cryptator/gen/CryptaGenModel.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,13 @@ class CryptaMemberPair implements ICryptaGenSolver {

protected final CryptaMemberLen left;

protected final CryptaMemberElt right;
protected final AbstractCryptaGenModel right;

protected CryptaMemberPair(CryptaMemberLen left, CryptaMemberLen right) {
super();
this.left = left;
this.right = right;
}

public CryptaMemberPair(final Model model, final String[] words, final String prefix, boolean useMemberLen) {
super();
Expand Down Expand Up @@ -49,7 +55,7 @@ public final CryptaMemberLen getLeft() {
return left;
}

public final CryptaMemberElt getRight() {
public final AbstractCryptaGenModel getRight() {
return right;
}

Expand All @@ -59,16 +65,29 @@ public void buildModel() {
postSymBreakLengthConstraint();
}

private void postSymBreakLengthConstraint() {
protected void postSymBreakLengthConstraint() {
left.getMaxLength().le(right.getMaxLength()).post();
}

protected final void postDisjunctionConstraints(final BoolVar[] v) {
final BoolVar[] l = getLeft().getWordVars();
final BoolVar[] r = getRight().getWordVars();
for (int i = 0; i < v.length; i++) {
l[i].add(r[i]).eq(v[i]).post();
}
}

protected void postMaxLengthConstraint(final IntVar maxLen) {
getModel().max(maxLen, getLeft().getMaxLength(), getRight().getMaxLength()).post();

}

public final void postHeavyConstraints(final int base) {
left.postLentghSumConstraints(right.getMaxLength(), base);
}

@Override
public final ICryptaNode recordCryptarithm() {
public ICryptaNode recordCryptarithm() {
return GenerateUtil.recordAddition(left, right);
}

Expand All @@ -91,16 +110,12 @@ public void buildModel() {

@Override
protected void postMaxLengthConstraints() {
model.max(maxLength, addition.getLeft().getMaxLength(), addition.getRight().getMaxLength()).post();
addition.postMaxLengthConstraint(maxLength);
}

@Override
protected void postWordConstraints() {
final BoolVar[] l = addition.getLeft().getWordVars();
final BoolVar[] r = addition.getRight().getWordVars();
for (int i = 0; i < vwords.length; i++) {
l[i].add(r[i]).eq(vwords[i]).post();
}
addition.postDisjunctionConstraints(vwords);
}

public void postMinLeftCountConstraints(final int base) {
Expand Down
140 changes: 140 additions & 0 deletions src/main/java/cryptator/gen/CryptaGenMult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/*
* This file is part of cryptator, https://github.com/arnaud-m/cryptator
*
* Copyright (c) 2023, 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.gen;

import org.chocosolver.solver.Model;
import org.chocosolver.solver.expression.discrete.arithmetic.ArExpression;
import org.chocosolver.solver.variables.BoolVar;
import org.chocosolver.solver.variables.IntVar;

import cryptator.specs.ICryptaGenSolver;
import cryptator.specs.ICryptaNode;

class CryptaMemberMult extends CryptaMemberPair {

public CryptaMemberMult(Model model, String[] words, String prefix) {
super(new CryptaMemberLen(model, words, prefix + "L_"), new CryptaMemberLen(model, words, prefix + "R_"));
}

@Override
public void buildModel() {
super.buildModel();
left.getWordCount().ge(1).post();
right.getWordCount().ge(1).post();
}

@Override
public ICryptaNode recordCryptarithm() {
return GenerateUtil.recordMultiplication(left, right);
}

@Override
protected void postSymBreakLengthConstraint() {
super.postSymBreakLengthConstraint();
// getModel().ifThen(left.getMaxLength().eq(right.getMaxLength()).decompose(),
// getModel().lexChainLess(right.getWordVars(), left.getWordVars()));
left.getMaxLength().eq(right.getMaxLength()).imp(left.getWordCount().ge(right.getWordCount())).post();
}

public void postMultHeavyConstraints() {
final IntVar sumL = getModel().sum("L_sumLength", left.lengths);
final IntVar sumR = getModel().sum("R_sumLength", ((CryptaMemberLen) right).lengths);

final ArExpression minL = sumL.sub(left.getWordCount()).add(1);
final ArExpression minR = sumR.sub(right.getWordCount()).add(1);
minL.le(sumR).post();
minR.le(sumL).post();

// FIXME sumL.le(8).post();
// FIXME sumR.le(8).post();

}

}

public class CryptaGenMult extends AbstractCryptaListModel implements ICryptaGenSolver {

private final CryptaMemberMult multiplication;

public CryptaGenMult(final String[] words) {
super(new Model("Generate-Multiplication"), words);
multiplication = new CryptaMemberMult(model, words, "");
}

@Override
public void buildModel() {
super.buildModel();
multiplication.buildModel();
}

@Override
protected void postMaxLengthConstraints() {
multiplication.postMaxLengthConstraint(maxLength);
maxLength.le(10).post();
}

@Override
protected void postWordConstraints() {
multiplication.postDisjunctionConstraints(vwords);
}

public void postMinLeftCountConstraints(final int base) {
multiplication.postMultHeavyConstraints();
}

public void postFixedRightMemberConstraint() {
final BoolVar[] vars = multiplication.getRight().getWordVars();
vars[vars.length - 1].eq(1).post();
multiplication.getRight().getWordCount().eq(1).post();
}

private int getDoublyCoeff(int i) {
double coeff = Math.log(i) / Math.log((getN() - 1));
return (int) Math.round(1000 * coeff);

}

public void postDoublyTrueConstraint(final int lb) {
final int n = getN();
final IntVar sumL = model.intVar("L_SUMLOG", getDoublyCoeff(lb), getDoublyCoeff(n - 1));
final IntVar sumR = model.intVar("R_SUMLOG", getDoublyCoeff(lb), getDoublyCoeff(n - 1));

final IntVar[] lvars = new IntVar[n + 1];
System.arraycopy(multiplication.getLeft().getWordVars(), 0, lvars, 0, n);
lvars[n] = sumL;

final IntVar[] rvars = new IntVar[n + 1];
System.arraycopy(multiplication.getRight().getWordVars(), 0, rvars, 0, n);
rvars[n] = sumR;

final int[] coeffs = new int[n + 1];
coeffs[0] = IntVar.MIN_INT_BOUND;
for (int i = 1; i < n; i++) {
coeffs[i] = getDoublyCoeff(i);
}
coeffs[n] = -1;

model.scalar(lvars, coeffs, "=", 0).post();
model.scalar(rvars, coeffs, "=", 0).post();

sumL.sub(sumR).abs().mul(2).le(wordCount).post();
}

@Override
public final ICryptaNode recordCryptarithm() {
return multiplication.recordCryptarithm();
}

@Override
public String toString() {
return GenerateUtil.recordString(multiplication.getLeft(), " * ") + " = "
+ GenerateUtil.recordString(multiplication.getRight(), " * ");
}

}
23 changes: 22 additions & 1 deletion src/main/java/cryptator/gen/CryptaListGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,23 @@ ICryptaGenSolver buildAdditionSolver() {
return gen;
}

ICryptaGenSolver buildMultiplicationSolver() {
final CryptaGenMult gen = new CryptaGenMult(words.getWords());
gen.buildModel();
gen.postWordCountConstraints(Math.max(config.getMinLeftOperands(), 2) + 1, config.getMaxLeftOperands() + 1);
gen.postMaxSymbolCountConstraint(config.getArithmeticBase());
if (!config.isLightPropagation()) {
gen.postMinLeftCountConstraints(config.getArithmeticBase());
}
if (words.hasRightMember()) {
gen.postFixedRightMemberConstraint();
}
if (words.isDoublyTrue()) {
gen.postDoublyTrueConstraint(words.getLB());
}
return gen;
}

ICryptaGenSolver buildCrosswordSolver() {
final CryptaGenCrossword gen = new CryptaGenCrossword(config.getGridSize(), words.getWords(),
config.isLightModel());
Expand All @@ -92,7 +109,11 @@ ICryptaGenSolver buildCrosswordSolver() {
}

private ICryptaGenSolver buildModel() {
return config.getGridSize() > 0 ? buildCrosswordSolver() : buildAdditionSolver();
if (config.getGridSize() > 0) {
return buildCrosswordSolver();
} else {
return config.isMultModel() ? buildMultiplicationSolver() : buildAdditionSolver();
}
}

private Consumer<ICryptaNode> buildConsumer(final IChocoModel gen,
Expand Down
8 changes: 8 additions & 0 deletions src/main/java/cryptator/gen/GenerateUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,12 @@ public static ICryptaNode recordAddition(final ICryptaGenModel left, final ICryp
return reduceOperation(CryptaOperator.EQ, recordAddition(left), recordAddition(right));
}

public static ICryptaNode recordMultiplication(final ICryptaGenModel model) {
return reduceOperation(CryptaOperator.MUL, leafStream(model));
}

public static ICryptaNode recordMultiplication(final ICryptaGenModel left, final ICryptaGenModel right) {
return reduceOperation(CryptaOperator.EQ, recordMultiplication(left), recordMultiplication(right));
}

}
51 changes: 47 additions & 4 deletions src/test/java/cryptator/GenerateTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,13 @@ private void testGenerate(final int expectedSolCount, final WordArray wordArray,

private void testGenerate(final int expectedSolCount, final OptionalInt expectedCandCount,
final WordArray wordArray, int gridSize) throws CryptaModelException {
configure(0, false, false);
configure(gridSize, false, false);
testGenerate(expectedSolCount, expectedCandCount, wordArray);
configure(0, false, true);
configure(gridSize, false, true);
testGenerate(expectedSolCount, expectedCandCount, wordArray);
configure(0, true, false);
configure(gridSize, true, false);
testGenerate(expectedSolCount, expectedCandCount, wordArray);
configure(0, false, true);
configure(gridSize, false, true);
testGenerate(expectedSolCount, expectedCandCount, wordArray);
}

Expand All @@ -77,6 +77,16 @@ private void testHeavyGenerate(final int expectedSolCount, final OptionalInt exp
testGenerate(expectedSolCount, expectedCandCount, wordArray);
}

private void testMultGenerate(final int expectedSolCount, final WordArray wordArray) throws CryptaModelException {
JULogUtil.configureSilentLoggers();
config.setMultModel(true);
configure(0, false, false);
testGenerate(expectedSolCount, OptionalInt.empty(), wordArray);
configure(0, false, true);
testGenerate(expectedSolCount, OptionalInt.empty(), wordArray);
config.setMultModel(false);
}

@Test
public void testSendMoreMoney() throws CryptaModelException {
final WordArray words = new WordArray(Arrays.asList("send", "more", "money"), null);
Expand Down Expand Up @@ -149,4 +159,37 @@ public void testCrossword3() throws CryptaModelException {
testHeavyGenerate(2, OptionalInt.empty(), words, 3);
}

@Test
public void testMult1() throws CryptaModelException {
WordArray words = new WordArray(Arrays.asList("mad", "man", "asylum"), null);
testMultGenerate(0, words);
}

@Test
public void testMult2() throws CryptaModelException {
config.setArithmeticBase(9);
WordArray words = new WordArray(Arrays.asList("alfred", "e", "neuman"), null);
testMultGenerate(2, words);
config.setArithmeticBase(10);
}

@Test
public void testMult3() throws CryptaModelException {
WordArray words = new WordArray(Arrays.asList("nora", "l", "aron"), null);
testMultGenerate(2, words);
}

@Test
public void testMult4() throws CryptaModelException {
WordArray words = new WordArray(Arrays.asList("ba", "cba", "dcba"), null);
testMultGenerate(1, words);
}

@Test
public void testMult5() throws CryptaModelException {
// FIXME should be 2
WordArray words = new WordArray(Arrays.asList("north", "south", "east", "west"), null);
testMultGenerate(4, words);
}

}
Loading