Skip to content
This repository has been archived by the owner on Aug 14, 2024. It is now read-only.

Commit

Permalink
Add scrambleanalysis subproject (#18)
Browse files Browse the repository at this point in the history
* Add project Scramble Analysis

* Add CubeHelper

* Add unit test for Scramble Analysis

* MVP for histogram

* Add parity check

* Count edges -1 for orientation

* WIP test for edge orientation

* Complete test for edge distribution

* Add test for random position of edges

* Add test for corner position

* Add test for corner orientation

* Different test for EO

* Actual calculation of the probability for i pairs misoriented

* Test for corner position again

* Complete the tests

* Do some cleaning, add unit tests

* Finish tests

* Cleaning a bit

* Remove test that mail fail in the future

* Move `scrambleanalysis` build structure to Gradle (#3)

* Remove legacy eclipse project files

* Move scrambleanalysis POM to Gradle build kts

* Add JUnit test library to Gradle

* Fix JUnit5 configuration discrepancy

* Move package org.thewca => org.worldcubeassociation.tnoodle

* Fix broken imports from gnehzr-migration

* Fix Gradle deprecations

* Code cleanup and reuse

* Run checks on CubeState instead of scramble strings

* Run RandomMoves test on more samples to guarantee skewed EO

Co-authored-by: Alexandre Campos <[email protected]>
  • Loading branch information
gregorbg and campos20 authored Aug 16, 2022
1 parent 1a97259 commit 6ed2322
Show file tree
Hide file tree
Showing 21 changed files with 935 additions and 4 deletions.
1 change: 1 addition & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ logback-classic = { module = "ch.qos.logback:logback-classic", version = "1.2.10
gwt-exporter = { module = "org.timepedia.exporter:gwtexporter", version = "2.5.1" }
junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit-jupiter" }
junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit-jupiter" }
apache-commons-math3 = { module = "org.apache.commons:commons-math3", version = "3.6.1" }

[plugins]
shadow = { id = "com.github.johnrengelman.shadow", version = "7.1.2" }
Expand Down
12 changes: 10 additions & 2 deletions min2phase/src/main/java/cs/min2phase/CubieCube.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import java.util.Arrays;

class CubieCube {
public class CubieCube {

/**
* 16 symmetries generated by S_F2, S_U4 and S_LR2
Expand Down Expand Up @@ -337,12 +337,20 @@ int verify() {
if (sum % 3 != 0) {
return -5;// twisted corner
}
if ((Util.getNParity(Util.getNPerm(ea, 12, true), 12) ^ Util.getNParity(getCPerm(), 8)) != 0) {
if ((getEdgeParityBit() ^ getCornerParityBit()) != 0) {
return -6;// parity error
}
return 0;// cube ok
}

public int getEdgeParityBit() {
return Util.getNParity(Util.getNPerm(ea, 12, true), 12);
}

public int getCornerParityBit() {
return Util.getNParity(getCPerm(), 8);
}

long selfSymmetry() {
CubieCube c = new CubieCube(this);
CubieCube d = new CubieCube();
Expand Down
4 changes: 2 additions & 2 deletions min2phase/src/main/java/cs/min2phase/Search.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public class Search {
protected int verbose;
protected int valid1;
protected boolean allowShorter = false;
protected CubieCube cc = new CubieCube();
public CubieCube cc = new CubieCube();
protected CubieCube[] urfCubieCube = new CubieCube[6];
protected CoordCube[] urfCoordCube = new CoordCube[6];
protected CubieCube[] phase1Cubie = new CubieCube[21];
Expand Down Expand Up @@ -240,7 +240,7 @@ public synchronized static void init() {
}
}

int verify(String facelets) {
public int verify(String facelets) {
int count = 0x000000;
byte[] f = new byte[54];
try {
Expand Down
1 change: 1 addition & 0 deletions scrambleanalysis/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/target/
26 changes: 26 additions & 0 deletions scrambleanalysis/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import configurations.Languages.attachRemoteRepositories
import configurations.Frameworks.configureJUnit5
import configurations.Languages.configureJava

description = "Scramble quality checker that performs statistical analyses"

attachRemoteRepositories()

plugins {
java
application
}

configureJava()

dependencies {
implementation(project(":scrambles"))
implementation(project(":min2phase"))
implementation(libs.apache.commons.math3)
}

configureJUnit5()

application {
mainClass.set("org.thewca.scrambleanalysis.App")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.worldcubeassociation.tnoodle.scrambleanalysis;

import org.worldcubeassociation.tnoodle.puzzle.CubePuzzle;
import org.worldcubeassociation.tnoodle.puzzle.ThreeByThreeCubePuzzle;
import org.worldcubeassociation.tnoodle.scrambles.InvalidScrambleException;

import java.util.List;

public class App {

public static void main(String[] args)
throws InvalidScrambleException, RepresentationException {

// to test your set of scrambles
// ArrayList<String> scrambles = ScrambleProvider.getScrambles(fileName);
// boolean passed = testScrambles(scrambles);

// Main test
int numberOfScrambles = 6500;
CubePuzzle puzzle = new ThreeByThreeCubePuzzle();

List<String> scrambles = ScrambleProvider.generateWcaScrambles(puzzle, numberOfScrambles);
List<CubePuzzle.CubeState> representations = ScrambleProvider.convertToCubeStates(scrambles);

boolean passed = CubeTest.testScrambles(representations);
System.out.println("\nMain test passed? " + passed);

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
package org.worldcubeassociation.tnoodle.scrambleanalysis;

import cs.min2phase.Search;
import cs.min2phase.SearchWCA;
import org.worldcubeassociation.tnoodle.puzzle.CubePuzzle;

import static org.worldcubeassociation.tnoodle.scrambleanalysis.utils.StringUtils.stringCompareIgnoringOrder;

public class CubeHelper {
// For 3x3 only.

private static final int edges = 12;
private static final int central = 4; // Index 4 represents the central sticker;
private static final int stickersPerFace = 9;

private static final int corners = 8;

// Refer to toFaceCube representation.
// For FB edge orientation, we only care about edges on U/D, Equator F/B.
// Also, this sets an order to edges, which will be reused
// UB, UL, UR, UF
// DF, DL, DR, DB
// FL, FR
// BR, BL
private static final int[] edgesIndex = {1, 3, 5, 7, // U edges index
28, 30, 32, 34, // D edges index
21, 23, // Equator front
48, 50 // Equator back
};

// Each edge has 2 stickers. This array represents, respectively, the index of
// the other attached sticker.
private static final int[] attachedEdgesIndex = {46, 37, 10, 19, // Attached to the U face.
25, 43, 16, 52, // Attached to the D face
41, 12, // Attached to Equator front
14, 39 // Attached to Equator back
};

// Again, an order to corners
// UBL, UBR, UFL, UFR,
// DFL, DFR, DBL, DBR
private static final int[] cornersIndex = {0, 2, 6, 8, // U corners
27, 29, 33, 35}; // D corners
private static final int[] cornersIndexClockWise = {36, 45, 18, 9, // U twist clockwise
44, 26, 53, 17, // D stickers
};
private static final int[] cornersIndexCounterClockWise = {47, 11, 38, 20, // U twists
24, 15, 42, 51}; // D twists

/**
* Count misoriented edges considering the FB axis.
*
* @param representation
* @return the number of misoriented edges in a cube.
* @throws RepresentationException
*/
public static int countMisorientedEdges(String representation) throws RepresentationException {
assert representation.length() == 54 : "Expected size: 54 = 6x9 stickers. Use cubeState.toFaceCube().";

int result = 0;
for (int i = 0; i < edges; i++) {
if (!isOrientedEdge(representation, i)) {
result++;
}
}
return result;
}

public static int countMisorientedEdges(CubePuzzle.CubeState cubeState) throws RepresentationException {
String representation = cubeState.toFaceCube();
return countMisorientedEdges(representation);
}

public static boolean isOrientedEdge(String representation, int index) throws RepresentationException {
char color;
char attachedColor;

char uColor = representation.charAt(central + 0 * stickersPerFace);
char rColor = representation.charAt(central + 1 * stickersPerFace);
// char fColor = representation.charAt(central + 2 * stickersPerFace);
char dColor = representation.charAt(central + 3 * stickersPerFace);
char lColor = representation.charAt(central + 4 * stickersPerFace);
// char bColor = representation.charAt(central + 5 * stickersPerFace);

color = representation.charAt(edgesIndex[index]);
attachedColor = representation.charAt(attachedEdgesIndex[index]);

if (color == uColor || color == dColor) {
return true;
}
if (color == rColor || color == lColor) {
return false;
}
// Now, we're left with f and b colors.
if (attachedColor == uColor || attachedColor == dColor) {
return false;
} else if (attachedColor == rColor || attachedColor == lColor) {
return true;
}

throw new RepresentationException();
}

/**
* Given a representation, returns a number that represents the orientation of a
* corner at index cornerIndex
*
* @param representation a representation of a cube.
* @param cornerIndex 0 &le; cornerIndex &lt; 8
* @return 0 if the corner is oriented. 1 if the corner is oriented clockwise.
* 2 if the corner is oriented counter clockwise.
* @throws RepresentationException
*/
public static int getCornerOrientationNumber(String representation, int cornerIndex)
throws RepresentationException {
char uColor = representation.charAt(central + 0 * stickersPerFace);
char dColor = representation.charAt(central + 3 * stickersPerFace);

int index = cornersIndex[cornerIndex];
int indexClockWise = cornersIndexClockWise[cornerIndex];
int indexCounterClockWise = cornersIndexCounterClockWise[cornerIndex];

char sticker = representation.charAt(index);
char stickerClockWise = representation.charAt(indexClockWise);
char stickerCounterClockWise = representation.charAt(indexCounterClockWise);

if (sticker == uColor || sticker == dColor) {
return 0;
} else if (stickerClockWise == uColor || stickerClockWise == dColor) {
return 1;
} else if (stickerCounterClockWise == uColor || stickerCounterClockWise == dColor) {
return 2;
}
throw new RepresentationException();
}

/**
* Sum of corner orientation. 0 for oriented, 1 for clockwise, 2 for counter
* clock wise.
*
* @param representation
* @return The sum of it.
* @throws RepresentationException
*/
public static int cornerOrientationSum(String representation) throws RepresentationException {
assert representation.length() == 54 : "Expected size: 54 = 6x9 stickers. Use cubeState.toFaceCube().";

int result = 0;

for (int i = 0; i < corners; i++) {
result += getCornerOrientationNumber(representation, i);
}

return result;
}

// Parity is the oddness of the number of two-swaps.
// Right now, we don't consider cases where corner and edge parity are uneven.
public static boolean hasParity(String faceletRepresentation) {
Search search = new SearchWCA();
int errors = search.verify(faceletRepresentation);

if (errors != 0) {
throw new RuntimeException("min2phase cannot handle the cube: Error " + errors);
}

int edgeParity = search.cc.getEdgeParityBit();
int cornerParity = search.cc.getCornerParityBit();

return edgeParity == 1 || cornerParity == 1;
}

// Actually, these next 2 methods did not need to be public, but it's for
// consistency with the
// getFinalLocationOfEdheSticker method.
public int[] getEdgesIndex() {
return edgesIndex;
}

public int[] getAttachedEdgesIndex() {
return attachedEdgesIndex;
}

/**
* Given a representation of a cube and the initial position of an edge (when
* solved), returns the final index position of that edge. The UB sticker is the
* first one on a toFaceCube representation, so call this 0 (index). Consider a
* U applied to a solved cube and let's call this repr. UB goes to UR, which is
* the 3rd edge in a representation (solved).
* getFinalLocationOfEdgeSticker(repr, 0) returns 2, which is 3rd sticker (0
* based).
* <p>
* UF is initially the 4th edge, so index 3. When an F is applied it goes to RF,
* which is the 6th edge on the toFaceCube representation, so this returns 5.
*
* @param representation: the final representation of a cube.
* @param i: the index, when solved, of a edge (0 for UB or BU, 1
* for UL or LU, 3 for UR...
* @return If the final position of a sticker is in UB (either U or B), it
* returns 0 (which is the index of UB in edgesIndex or
* attachedEdgesIndex). If the final position of a sticker is in UL
* (either U or L), it returns 1 (which is the index of UL in
* edgesIndex). etc.
* @throws RepresentationException
*/
public static int getFinalPositionOfEdge(String representation, int i) throws RepresentationException {
// Here, we are reusing the position of edges mentioned above.

if (representation.length() != 54) {
throw new IllegalArgumentException("Representation size must be 54.");
}
if (i < 0 || i >= edges) {
throw new IllegalArgumentException("Make sure 0 <= i <= 11.");
}

int initialEdgeIndex = edgesIndex[i];
int initialAttachedIndex = attachedEdgesIndex[i];

char initialColor = representation.charAt(central + initialEdgeIndex / stickersPerFace * stickersPerFace);
char initialAttachedColor = representation
.charAt(central + initialAttachedIndex / stickersPerFace * stickersPerFace);

for (int j = 0; j < edges; j++) {
char color = representation.charAt(edgesIndex[j]);
char attachedColor = representation.charAt(attachedEdgesIndex[j]);

if (color == initialColor && attachedColor == initialAttachedColor) {
return j;
}
if (color == initialAttachedColor && attachedColor == initialColor) {
return j;
}
}

throw new RepresentationException();
}

public static int getFinalPositionOfCorner(String representation, int i) throws RepresentationException {
// Here, we are reusing the position of edges mentioned above.

if (representation.length() != 54) {
throw new IllegalArgumentException("Representation size must be 54.");
}
if (i < 0 || i >= corners) {
throw new IllegalArgumentException("Make sure 0 <= i <= 7.");
}

int initialIndex = cornersIndex[i];
int initialClockWiseIndex = cornersIndexClockWise[i];
int initialCounterClockWiseIndex = cornersIndexCounterClockWise[i];

char initialColor = representation.charAt(initialIndex - initialIndex % stickersPerFace + central);
char initialClockWiseColor = representation
.charAt(initialClockWiseIndex - initialClockWiseIndex % stickersPerFace + central);
char initialCounterClockWiseColor = representation
.charAt(initialCounterClockWiseIndex - initialCounterClockWiseIndex % stickersPerFace + central);
String initial = "" + initialColor + initialClockWiseColor + initialCounterClockWiseColor;

for (int j = 0; j < corners; j++) {
char color = representation.charAt(cornersIndex[j]);
char clockWiseColor = representation.charAt(cornersIndexClockWise[j]);
char counterClockWiseColor = representation.charAt(cornersIndexCounterClockWise[j]);

String current = "" + color + clockWiseColor + counterClockWiseColor;

if (stringCompareIgnoringOrder(initial, current)) {
return j;
}
}

throw new RepresentationException();
}
}
Loading

0 comments on commit 6ed2322

Please sign in to comment.