cubing / cubing.js

🛠 A library for displaying and working with twisty puzzles. Also currently home to the code for Twizzle.
https://js.cubing.net/cubing/
GNU General Public License v3.0
232 stars 42 forks source link

[cubing.js] incomplete or buggy experimentalSimplify() behavior #274

Open trexrush opened 1 year ago

trexrush commented 1 year ago

Hi, Im building an algorithm showcase website that uses cubingjs for visualizations and case images. here is the link to my current repo

I use shorthands for common triggers like sune and pll parity, and for visualization I convert the triggers into a string with the moves, and use experimentalSimplify() to have cancellations.

Steps to reproduce the issue

the simplify function I use is configured like this:

export const simplifyAlg = (a: string): string => {
  return new Alg(a).experimentalSimplify({cancel: { directional: 'any-direction', puzzleSpecificModWrap: 'gravity' }}).toString()
}

I also have functions to convert triggers into algorithms but this is out of scope and unaffected by current cubingJS behavior.

Observed behaviour

Example 1

simplifyAlg("U 2R2 2R2") // U 2R4

Example 2

simplifyAlg("R2 U2' R2' U' R2 U2' R2' ".repeat(2)) // R2 U2' R2' U' R2 U4' R2' U' R2 U2' R2'

Example 3

console.log(simplifyAlg("Rw2 2R2")) // Rw2 2R2

🖼 Screenshots

No response

Expected behaviour

Example 1

simplifyAlg("U 2R2 2R2") // U

Example 2

In a megaminx context (existence of which I am not aware of)

simplifyAlg("R2 U2' R2' U' R2 U2' R2' ".repeat(2)) // R2 U2' R2' U' R2 U R2' U' R2 U2' R2'

In an NxN context

simplifyAlg("R2 U2' R2' U' R2 U2' R2' ".repeat(2)) // R2 U2' R2' U2' R2 U2' R2'

Example 3

console.log(simplifyAlg("Rw2 2R2")) // R2

Environment

node v17.8.0 cubingjs 0.35.19 package.json of current project

Additional info

There are 3 possible takeaways:

1) My implementation can be changed to produce correct output for the slice cases, and I hadnt found a correct way to do this.

2) This is a bug in the simplify code.

3) There isnt a way to simplify these kinds of puzzle-specific moves yet.

My guess is that its a mix of all of them, but I dont know! Which is why Im posting this issue.

trexrush commented 1 year ago

https://github.com/cubing/cubing.js/issues/231 found this issue on further investigation, let me to the right direction. Looking further it looks like the megaminx cancellation already exists using puzzleSpecificSimplifyOptions, didnt catch this since I was specifically looking at how SimplifyOptions are defined.

would this be how to implement megaminx cancellation? There isnt much documentation on QuantumMove. experimentalSimplify({cancel: { directional: 'any-direction' }, puzzleSpecificSimplifyOptions: { quantumMoveOrder: () => 5 }}

still not sure why example 1 fails, and my understanding is that example 3 hasnt been implemented yet as per this note.

lgarron commented 1 year ago

You found the right code! This is very experimental territory, though, because we don't yet know the final APIs for storing information about puzzles. For example, you can do this for 3x3x3:


import { Alg } from "cubing/alg";
import { cube3x3x3 } from "cubing/puzzles";

new Alg("R R2").experimentalSimplify({
  cancel: {
    directional: "any-direction",
  },
  puzzleSpecificSimplifyOptions: cube3x3x3.puzzleSpecificSimplifyOptions,
}).log()

But there isn't an equivalent for Megaminx yet. quantumMoveOrder: () => 5 will work unless you use rotations with order 2 or 3.

still not sure why example 1 fails

What are you trying to do? For Megaminx with quantumMoveOrder: () => 5 this should simplify to U 2R', and for cubes it should be U. I can confirm that they work as expected for me. Do you have any puzzle-specific code that doesn't match that behaviour?

and my understanding is that example 3 hasnt been implemented yet as per this note.

Correct, this is a general problem we haven't tackled yet.

There isnt much documentation on QuantumMove.

https://js.cubing.net/cubing/api/classes/alg.QuantumMove.html documents the class, but it's usually move convenient to use TypeScript types. Does your IDE show those?

trexrush commented 1 year ago

using quantumMoveOrder: () => 4 for the first example returns correct behavior.

I think what I was getting at was that I dont understand what a QuantumMove is, and how to customize the simplification PuzzleSpecificSimplifyOptions using the options in these interfaces:

interface PuzzleSpecificAxisSimplifyInfo {
    areQuantumMovesSameAxis: (quantumMove1: QuantumMove, quantumMove2: QuantumMove) => boolean;
    simplifySameAxisMoves: (moves: Move[], quantumMod: boolean) => Move[];
}
interface PuzzleSpecificSimplifyOptions {
    quantumMoveOrder?: (quantumMove: QuantumMove) => number;
    axis?: PuzzleSpecificAxisSimplifyInfo;
}

probably for now there is no need for me to dive too deep, since setting QuantumMoveOrder fixed most of the issues. Maybe a documentation issue, since its hard to reach the conclusion of using that option. But mainly the only thing missing is certain same-axis move simplifications (ie S x U == x D) for simultaneous vis and better simplification, which hasn't been implemented. So this issue can probably be relabeled as a question and closed.

lgarron commented 1 year ago

Yeah, those interfaces aren't meant for you to interact with unless you're implementing a custom puzzle — they're a draft for the way cubing.js will handle this automatically.

A QuantumMove is what gets combined with an amount for a Move. So 2-3r2 is a move with a quantum move of 2-3r and an amount of 2.

The issue comes with generalizing anything beyond a cube — almost every reasonable assumption is broken by an interesting puzzle. (And cuboids also break the assumption that the existence of R2 implies R.) However, many puzzles allow every move to be assigned to an axis where it commutes with all other same-axis moves, so this is the highest "bang for the buck" simplification I've started with.


Simplifying slices across rotations is definitely valuable for playback, since those happen in real solves. Do you have a use case in mind, and do you have any particular algorithm in mind for how to determine the "correct" simplification in all cases? Note that: