Skip to content
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

Are there any plans to port PuzzleGeometry? #7

Open
ArhanChaudhary opened this issue Dec 1, 2024 · 30 comments
Open

Are there any plans to port PuzzleGeometry? #7

ArhanChaudhary opened this issue Dec 1, 2024 · 30 comments

Comments

@ArhanChaudhary
Copy link

title

@lgarron
Copy link
Member

lgarron commented Dec 1, 2024

Not at the moment, but this may become necessary to implement server-side 3D rendering in the future.

@ArhanChaudhary
Copy link
Author

Not at the moment, but this may become necessary to implement server-side 3D rendering in the future.

I see. I want to rewrite https://github.com/cubing/cubing.js/blob/main/src/cubing/puzzle-geometry/PuzzleGeometry.ts in Rust, and only the logic for generating the puzzle's permutation group (the data structure represented by the GAP output). The code isn't the easiest the follow, so do you have any advice on where I should start?

@lgarron
Copy link
Member

lgarron commented Dec 1, 2024

I don't really understand the code myself, so I can't really be helpful on that front.

Perhaps @rokicki can give you a breakdown of what you need?

@rokicki
Copy link

rokicki commented Dec 1, 2024

Let me know your points of confusion and I'll clarify in the code and/or in a separate document.

I did go through and document it pretty extensively at one point, but the functionality has grown so much since then that all the additional features are probably obscuring the core ideas, which are really pretty simple.

A rewrite (and generalization!) in any language would probably be a welcome thing. Right now only puzzles with the symmetry of the platonic solids are supported, and that's limiting. Also, no jumbling or shapeshifting is supported. It would be nice to support arbitrary external shapes, arbitrary cuts, jumbling, and so forth.

@lgarron
Copy link
Member

lgarron commented Dec 1, 2024

For what it's worth, if we do have a good Rust implementation we can easily swap cubing.js to use it.
I'd be happy to review code that lets us do this, but unfortunately I can't dedicate any time to design or writing such code right now.

@ArhanChaudhary
Copy link
Author

Let me know your points of confusion and I'll clarify in the code and/or in a separate document.

I did go through and document it pretty extensively at one point, but the functionality has grown so much since then that all the additional features are probably obscuring the core ideas, which are really pretty simple.

A rewrite (and generalization!) in any language would probably be a welcome thing. Right now only puzzles with the symmetry of the platonic solids are supported, and that's limiting. Also, no jumbling or shapeshifting is supported. It would be nice to support arbitrary external shapes, arbitrary cuts, jumbling, and so forth.

My specific use case suffices for the platonic solids. Perhaps there exists an older commit in the commit history https://github.com/cubing/cubing.js/commits/main/src/cubing/puzzle-geometry/PuzzleGeometry.ts that has a minimal implementation of the --ksolve and --gap flags for a puzzlegeometry definition. It would be much easier to port to Rust this way (my code will be open source as well).

@rokicki
Copy link

rokicki commented Dec 1, 2024

Here's a much older version (2018) with many fewer lines. The structure has not changed (although speed and functionality has been greatly expanded).

https://github.com/rokicki/work/blob/master/quat/PuzzleGeometry.js

Bringing it over to Rust might be challenging as I just willy-nilly share structures and depend on the garbage collector/etc. And frankly we probably want a Rust version as well, so there may be benefits to collaboratively doing it. But let's see if we can agree on the basic concepts first and go from there.

At the simplest level, I just model the outer shell and the cuts in 3D, using quaternions for the math, and considering points "equal" if they are within epsilon. I calculate what the cuts do to a single face, and then "multiply" that across all the faces. I then join "stickers" (parts of faces) into cubies (where a cubie may have 1 to 5 "stickers") and derive orientation. From there I "simulate" rotations to calculate the underlying group as a permutation/orientation representation.

@ArhanChaudhary
Copy link
Author

ArhanChaudhary commented Dec 2, 2024

Here's a much older version (2018) with many fewer lines. The structure has not changed (although speed and functionality has been greatly expanded).

Bringing it over to Rust might be challenging as I just willy-nilly share structures and depend on the garbage collector/etc. And frankly we probably want a Rust version as well, so there may be benefits to collaboratively doing it. But let's see if we can agree on the basic concepts first and go from there.

At the simplest level, I just model the outer shell and the cuts in 3D, using quaternions for the math, and considering points "equal" if they are within epsilon. I calculate what the cuts do to a single face, and then "multiply" that across all the faces. I then join "stickers" (parts of faces) into cubies (where a cubie may have 1 to 5 "stickers") and derive orientation. From there I "simulate" rotations to calculate the underlying group as a permutation/orientation representation.

Thanks for the reference and explanation. I will look over your code and try to translate it to Rust one-to-one. You mentioned in your 2018 version that the code isn't efficient, and that the new version is optimized for speed. Do you know which specific commits from the new version could be applied to the old version to improve its performance while maintaining its smaller and easier to work with file size? Additionally are there any major now-fixed bugs with the older version?

@ArhanChaudhary
Copy link
Author

I will stick to trying to port the current version for the sake of actually having types to work with. Raw JavaScript to Rust will take too much time to figure out.

@rokicki
Copy link

rokicki commented Dec 4, 2024 via email

@ArhanChaudhary
Copy link
Author

My port to Rust begins here, hopefully I don't run into too many issues.

@rokicki
Copy link

rokicki commented Jan 24, 2025 via email

@ArhanChaudhary
Copy link
Author

ArhanChaudhary commented Jan 24, 2025

Will ask away as needed! 😄

My plan for now is just to dumbly translate the existing cubing.js puzzlegeometry module into Rust. My work won't actually do anything innovative or particularly new, and will support just a subset of what the existing puzzlegeometry module supports. I will start off with porting the minimum amount of code needed to replicate the results of --gap --optimize --rotations <definition> (as the qter runtime represents twisty puzzles at the facelet level rather than the coordinate level) and then try to get that PRed over here. In the future, if I get to understand how the code works, I could try to think about how your aforementioned ideas would be implemented.

@ArhanChaudhary
Copy link
Author

I am also not entirely sure how the licensing works. Does rewriting puzzlegeometry and incorporating it into my codebase mean I would have to change the license from MIT to GPL? If so then I will gladly do so!

@lgarron
Copy link
Member

lgarron commented Jan 25, 2025

I am also not entirely sure how the licensing works. Does rewriting puzzlegeometry and incorporating it into my codebase mean I would have to change the license from MIT to GPL? If so then I will gladly do so!

If it's derived from ours, you can license your own code under any license compatible with either GPL or MPL.

However, in order to accept contributions from you we'd currently need your agreement to license under both GPL and MPL. (We may drop the GPL in favor of only MPL at some point, but we haven't yet made concrete plans to do so.) That doesn't have to be done directly in your repo, but it would certainly be simpler if it does.

@ArhanChaudhary
Copy link
Author

Sweet, I agree to the licensing terms. I've just changed my repo's license to GPL appropriately.

@ArhanChaudhary
Copy link
Author

I noticed that --vertexmoves does not generate output on non-tetrahedron twisty puzzles. It looks like output is specially ignored in this case, possibly erroneously. This is an issue because the input is arbitrary so the flag has a chance to not output anything if the description tetrahedron. Would this be an appropriate fix? If so then I can try to PR this into cubing.js! @rokicki

diff --git a/src/cubing/puzzle-geometry/PuzzleGeometry.ts b/src/cubing/puzzle-geometry/PuzzleGeometry.ts
index e581de16..04a2db7a 100644
--- a/src/cubing/puzzle-geometry/PuzzleGeometry.ts
+++ b/src/cubing/puzzle-geometry/PuzzleGeometry.ts
@@ -2129,9 +2129,9 @@ export class PuzzleGeometry {
         }
         r.push(parsedmove[5]);
       }
-    } else if (this.options.vertexMoves && !this.options.allMoves) {
+    } else {
       const msg = this.movesetgeos[k];
-      if (msg[1] !== msg[3]) {
+      if (this.options.vertexMoves && !this.options.allMoves && msg[1] !== msg[3]) {
         for (let i = 0; i < slices; i++) {
           if (msg[1] !== "v") {
             if (this.options.outerBlockMoves) {
@@ -2149,22 +2149,22 @@ export class PuzzleGeometry {
             r.push(1);
           }
         }
-      }
-    } else {
-      for (let i = 0; i <= slices; i++) {
-        if (!this.options.allMoves && i + i === slices) {
-          continue;
-        }
-        if (this.options.outerBlockMoves) {
-          if (i + i > slices) {
-            r.push([i, slices]);
+      } else {
+        for (let i = 0; i <= slices; i++) {
+          if (!this.options.allMoves && i + i === slices) {
+            continue;
+          }
+          if (this.options.outerBlockMoves) {
+            if (i + i > slices) {
+              r.push([i, slices]);
+            } else {
+              r.push([0, i]);
+            }
           } else {
-            r.push([0, i]);
+            r.push([i, i]);
           }
-        } else {
-          r.push([i, i]);
+          r.push(1);
         }
-        r.push(1);
       }
     }
     if (this.fixedCubie >= 0) {

@rokicki
Copy link

rokicki commented Jan 26, 2025 via email

@ArhanChaudhary
Copy link
Author

ArhanChaudhary commented Jan 26, 2025

I want a way to enforce vertex moves only if the puzzle is a tetrahedron, and ignore the flag if not. The way the flag is implemented currently does not allow this. Because puzzlegeometry works on an arbitrary description, for this to work as described I cannot rely on the flag and would instead have to manually check if the geometry is a tetrahedron and if so, disable it. This is annoying to do from the user end, so I think it makes sense for this to be done internally within puzzlegeometry.

@ArhanChaudhary
Copy link
Author

Another way to think about why this is useful is for programmatically running puzzlegeometry on a fixed set of settings as in twizzle explorer. This would make enforcing vertex moves only if the puzzle is a tetrahedron, and ignoring the flag otherwise very simple.

@rokicki
Copy link

rokicki commented Jan 26, 2025 via email

@ArhanChaudhary
Copy link
Author

The indentation made the diff look weird, here is the same thing without the indent. This does exactly what you suggested.

diff --git a/src/cubing/puzzle-geometry/PuzzleGeometry.ts b/src/cubing/puzzle-geometry/PuzzleGeometry.ts
index e581de16..26086132 100644
--- a/src/cubing/puzzle-geometry/PuzzleGeometry.ts
+++ b/src/cubing/puzzle-geometry/PuzzleGeometry.ts
@@ -2129,9 +2129,10 @@ export class PuzzleGeometry {
         }
         r.push(parsedmove[5]);
       }
-    } else if (this.options.vertexMoves && !this.options.allMoves) {
+    } else {
       const msg = this.movesetgeos[k];
-      if (msg[1] !== msg[3]) {
+      const isTetrahedron = msg[1] !== msg[3];
+      if (this.options.vertexMoves && isTetrahedron && !this.options.allMoves) {
         for (let i = 0; i < slices; i++) {
           if (msg[1] !== "v") {
             if (this.options.outerBlockMoves) {
@@ -2149,7 +2150,6 @@ export class PuzzleGeometry {
             r.push(1);
           }
         }
-      }
     } else {
       for (let i = 0; i <= slices; i++) {
         if (!this.options.allMoves && i + i === slices) {
@@ -2167,6 +2167,7 @@ export class PuzzleGeometry {
         r.push(1);
       }
     }
+    }
     if (this.fixedCubie >= 0) {
       const dep = this.keyface3(this.faces[this.cubies[this.fixedCubie][0]])[k];
       const newr = [];

I'm not exactly sure what const msg = this.movesetgeos[k] represents. I tested every PGPuzzle and msg[1] !== msg[3] was only true for tetrahedron puzzles, therefore I use that to test if it is a tetrahedron. The code was originally written this way so I conservatively used the same expression.

@ArhanChaudhary
Copy link
Author

Also I believe I just found a bug. The following only fails with --vertexmoves and only with the Jing pyraminx:

# /usr/local/Cellar/bun/1.1.36/bin/bun /Users/arhan/Desktop/cubing.js/src/bin/puzzle-geometry-bin.ts --gap --vertexmoves Jing pyraminx
602 |     movenameFamily = movenameFamily.toLowerCase();
603 |     if (bits[1] > 1) {
604 |       movenamePrefix = String(bits[1] + 1);
605 |     }
606 |   } else {
607 |     throw new Error(
                ^
error: We only support slice and outer block moves right now. 1
      at getmovename (/Users/arhan/Desktop/cubing.js/src/cubing/puzzle-geometry/PuzzleGeometry.ts:607:11)
      at getOrbitsDef (/Users/arhan/Desktop/cubing.js/src/cubing/puzzle-geometry/PuzzleGeometry.ts:2666:25)
      at writegap (/Users/arhan/Desktop/cubing.js/src/cubing/puzzle-geometry/PuzzleGeometry.ts:2243:21)
      at /Users/arhan/Desktop/cubing.js/src/bin/puzzle-geometry-bin.ts:275:20

Bun v1.1.36 (macOS arm64)```

@rokicki
Copy link

rokicki commented Jan 26, 2025 via email

@rokicki
Copy link

rokicki commented Jan 26, 2025 via email

@ArhanChaudhary
Copy link
Author

Another concern. describeSet from PermOriSet outputs locations in the KSolve definition in a different order compared to https://standards.cubing.net/draft/1/fundamental-cube-terms/#133-piece-location. For example on the 3x3:

Set CORNERS 8 3
# 1 F U R
# 2 B U R
# 3 B U L
# 4 F U L
...

To me, this is a priority to fix because locations like FUR looks bizarre.

@rokicki
Copy link

rokicki commented Jan 28, 2025 via email

@ArhanChaudhary
Copy link
Author

ArhanChaudhary commented Jan 28, 2025

Thanks, it looks to work now. 🎉

To elaborate, I would like for there to be a "move order priority" when outputting which moves affect each piece, like a Reid order but for any PG puzzle. For my specific use case, I intend to use the intersecting planes to uniquely identify each piece location for the solved-goto instruction in qter. For example I would like to see instead on the 3x3x3:

Set CORNERS 8 3
# 1 U F R
# 2 U R B
# 3 U B L
# 4 U L F
# 5 D R F
# 6 D L F
# 7 D L B
# 8 D B R

and a similar type of ordering on the 4x4x4 or any PG puzzle.

@rokicki
Copy link

rokicki commented Jan 28, 2025 via email

@ArhanChaudhary
Copy link
Author

That's understandable. My idea is to add another parameter to defaultnets that specifies a move priority of each face for every platonic solid. With the 3x3x3 it would be ["U", "L", "F", "R", "B", "D"]. Then over here https://github.com/cubing/cubing.js/blob/5929e97cce5703f79db11396649483dfd2e5a281/src/cubing/puzzle-geometry/PuzzleGeometry.ts#L934 I can post-process facenametoindex and faceindextoname to give the moves of the puzzle in that order.

I haven't yet looked at how slice moves are ordered so it could be more complicated than just that. But I am confident that I will eventually figure something out. I will keep you updated as I work on the port.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants