rubiks-cube-3x3-solver / src/symmetries.rs

raw file 17897 bytes
use crate::constants::*;
use crate::cubie::move_cubes;
use crate::cubie::{Corner::*, CubieCube, Edge::*};
use crate::error::Error;
use crate::{decode_table, write_table};

#[allow(non_camel_case_types)]
enum BS {
    ROT_URF3,
    ROT_F2,
    ROT_U4,
    MIRR_LR2,
}

/// 120° clockwise rotation around the long diagonal URF-DBL
const CC_ROT_URF3: CubieCube = CubieCube {
    cp: [URF, DFR, DLF, UFL, UBR, DRB, DBL, ULB],
    co: [1, 2, 1, 2, 2, 1, 2, 1],
    ep: [UF, FR, DF, FL, UB, BR, DB, BL, UR, DR, DL, UL],
    eo: [1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1],
};

/// 180° rotation around the axis through the F and B centers
const CC_ROT_F2: CubieCube = CubieCube {
    cp: [DLF, DFR, DRB, DBL, UFL, URF, UBR, ULB],
    co: [0, 0, 0, 0, 0, 0, 0, 0],
    ep: [DL, DF, DR, DB, UL, UF, UR, UB, FL, FR, BR, BL],
    eo: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
};

/// 90° clockwise rotation around the axis through the U and D centers
const CC_ROT_U4: CubieCube = CubieCube {
    cp: [UBR, URF, UFL, ULB, DRB, DFR, DLF, DBL],
    co: [0, 0, 0, 0, 0, 0, 0, 0],
    ep: [UB, UR, UF, UL, DB, DR, DF, DL, BR, FR, FL, BL],
    eo: [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1],
};

/// reflection at the plane through the U, D, F, B centers
const CC_MIRR_LR2: CubieCube = CubieCube {
    cp: [UFL, URF, UBR, ULB, DLF, DFR, DRB, DBL],
    co: [3, 3, 3, 3, 3, 3, 3, 3],
    ep: [UL, UF, UR, UB, DL, DF, DR, DB, FL, FR, BR, BL],
    eo: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
};

/// Tables to store symmetries.
#[derive(Debug, Clone)]
pub struct SymmetriesTables {
    pub bsc: [CubieCube; 4],
    pub sc: [CubieCube; 48],
    pub inv_idx: [u8; 48],
    pub mult_sym: Vec<usize>,
    pub conj_move: Vec<usize>,
    pub twist_conj: Vec<u16>,
    pub ud_edges_conj: Vec<u16>,
    pub flipslice_classidx: Vec<u16>,
    pub flipslice_sym: Vec<u8>,
    pub flipslice_rep: Vec<u32>,
    pub corner_classidx: Vec<u16>,
    pub corner_sym: Vec<u8>,
    pub corner_rep: Vec<u16>,
}

impl SymmetriesTables {
    pub fn new() -> Self {
        let flipslice = flipslice_syms().unwrap();
        let cornersyms = corner_syms().unwrap();
        Self {
            bsc: basicsc(),
            sc: sc(),
            inv_idx: inv_idx(),
            mult_sym: mult_sym(),
            conj_move: conj_move(),
            twist_conj: conj_twist().unwrap(),
            ud_edges_conj: conj_ud_edges().unwrap(),
            flipslice_classidx: flipslice.classidx,
            flipslice_sym: flipslice.sym,
            flipslice_rep: flipslice.rep,
            corner_classidx: cornersyms.classidx,
            corner_sym: cornersyms.sym,
            corner_rep: cornersyms.rep,
        }
    }
}

/// Permutations and orientation changes of the basic symmetries
fn basicsc() -> [CubieCube; 4] {
    let mut bsc = [CubieCube::default(); 4];
    bsc[BS::ROT_URF3 as usize] = CC_ROT_URF3.clone();
    bsc[BS::ROT_F2 as usize] = CC_ROT_F2.clone();
    bsc[BS::ROT_U4 as usize] = CC_ROT_U4.clone();
    bsc[BS::MIRR_LR2 as usize] = CC_MIRR_LR2.clone();
    bsc
}

/// Basic symmetries of the cube. All 48 cube symmetries can be generated by sequences of these 4 symmetries.
///
/// sc list, 48 CubieCubes will represent the 48 cube symmetries
pub fn sc() -> [CubieCube; 48] {
    let bsc = basicsc();
    let mut sc = [CubieCube::default(); 48];
    let mut cc = CubieCube::default(); // Identity cube
    let mut idx = 0;
    for _urf3 in 0..3 {
        for _f2 in 0..2 {
            for _u4 in 0..4 {
                for _lr2 in 0..2 {
                    sc[idx] = CubieCube {
                        cp: cc.cp,
                        co: cc.co,
                        ep: cc.ep,
                        eo: cc.eo,
                    };
                    idx += 1;
                    cc.multiply(bsc[BS::MIRR_LR2 as usize]);
                }
                cc.multiply(bsc[BS::ROT_U4 as usize]);
            }
            cc.multiply(bsc[BS::ROT_F2 as usize]);
        }
        cc.multiply(bsc[BS::ROT_URF3 as usize]);
    }
    sc
}

/// Fill the inv_idx array.
///
/// Indices for the inverse symmetries: sc[inv_idx[idx]] == sc[idx]^(-1)
pub fn inv_idx() -> [u8; 48] {
    let mut inv_idx_arr: [u8; 48] = [0; N_SYM];
    let sc = sc();
    for j in 0..N_SYM {
        for i in 0..N_SYM {
            let mut cc = CubieCube {
                cp: sc[j].cp,
                co: sc[j].co,
                ep: sc[j].ep,
                eo: sc[j].eo,
            };
            cc.corner_multiply(sc[i]);
            if cc.cp[URF as usize] == URF
                && cc.cp[UFL as usize] == UFL
                && cc.cp[ULB as usize] == ULB
            {
                inv_idx_arr[j] = i as u8;
                break;
            }
        }
    }
    inv_idx_arr
}

/// Generate the group table for the 48 cube symmetries.
fn mult_sym() -> Vec<usize> {
    let mut sym_mult = vec![0; N_SYM * N_SYM];
    let sc = sc();
    for i in 0..N_SYM {
        for j in 0..N_SYM {
            let mut cc = CubieCube {
                cp: sc[i].cp,
                co: sc[i].co,
                ep: sc[i].ep,
                eo: sc[i].eo,
            };
            cc.multiply(sc[j]);
            for k in 0..N_SYM {
                if cc == sc[k] {
                    // sc[i]*sc[j] == sc[k];
                    sym_mult[N_SYM * i + j] = k;
                    break;
                }
            }
        }
    }
    sym_mult
}

/// Generate the table for the conjugation of a move m by a symmetry s. conj_move[N_MOVE*s + m] = s*m*s^-1
fn conj_move() -> Vec<usize> {
    let sc = sc();
    let mc = move_cubes();
    let inv_idx = inv_idx();
    let mut move_conj = vec![0; N_MOVE * N_SYM];
    for s in 0..N_SYM {
        for m in ALL_MOVES {
            let mut ss = CubieCube {
                cp: sc[s].cp,
                co: sc[s].co,
                ep: sc[s].ep,
                eo: sc[s].eo,
            }; // copy cube
            ss.multiply(mc[m as usize]); // s*m
            ss.multiply(sc[inv_idx[s] as usize]); // s*m*s^-1
            for m2 in ALL_MOVES {
                if ss == mc[m2 as usize] {
                    move_conj[N_MOVE * s + m as usize] = m2 as usize;
                }
            }
        }
    }
    move_conj
}

/// Generate the phase 1 table for the conjugation of the twist t by a symmetry s. twist_conj[t, s] = s*t*s^-1 ####
fn conj_twist() -> Result<Vec<u16>, Error> {
    let sc = sc();
    let inv_idx = inv_idx();
    std::fs::create_dir_all("tables")?;
    let fname = "tables/conj_twist";
    let conj_table = std::fs::read(&fname).unwrap_or("".into());
    let mut twist_conj = vec![0; N_TWIST * N_SYM_D4H];
    if conj_table.is_empty() {
        println!("On the first run, several tables will be created. This may take a few minutes.");
        println!(
            "All tables are stored in tables/{}.\n\nCreating {} table...\n",
            fname, fname
        );
        for t in 0..N_TWIST {
            let mut cc = CubieCube::default();
            cc.set_twist(t as u16);
            for s in 0..N_SYM_D4H {
                let mut ss = CubieCube {
                    cp: sc[s].cp,
                    co: sc[s].co,
                    ep: sc[s].ep,
                    eo: sc[s].eo,
                }; // copy cube
                ss.corner_multiply(cc); // s*t
                ss.corner_multiply(sc[inv_idx[s] as usize]); // s*t*s^-1
                twist_conj[N_SYM_D4H * t + s] = ss.get_twist();
            }
        }
        write_table(fname, &twist_conj)?;
    } else {
        // println!("Loading {} table...", &fname);
        twist_conj = decode_table(&conj_table)?;
    }
    Ok(twist_conj)
}

/// Generate the phase 2 table for the conjugation of the URtoDB coordinate by a symmetrie.
fn conj_ud_edges() -> Result<Vec<u16>, Error> {
    let sc = sc();
    let inv_idx = inv_idx();
    std::fs::create_dir_all("tables")?;
    let fname = "tables/conj_ud_edges";
    let conj_table = std::fs::read(&fname).unwrap_or("".into());
    let mut ud_edges_conj = vec![0; N_UD_EDGES * N_SYM_D4H];
    if conj_table.is_empty() {
        println!("Creating {} table...", fname);
        for t in 0..N_UD_EDGES {
            if (t + 1) % 400 == 0 {
                print!("");
            }
            if (t + 1) % 32000 == 0 {
                println!();
            }
            let mut cc = CubieCube::default();
            cc.set_ud_edges(t);
            for s in 0..N_SYM_D4H {
                let mut ss = CubieCube {
                    cp: sc[s].cp,
                    co: sc[s].co,
                    ep: sc[s].ep,
                    eo: sc[s].eo,
                }; // copy cube
                ss.edge_multiply(cc); // s*t
                ss.edge_multiply(sc[inv_idx[s] as usize]); // s*t*s^-1
                ud_edges_conj[N_SYM_D4H * t + s] = ss.get_ud_edges();
            }
        }
        println!();
        write_table(fname, &ud_edges_conj)?;
    } else {
        // println!("Loading {} table...", &fname);
        ud_edges_conj = decode_table(&conj_table)?;
    }
    Ok(ud_edges_conj)
}

/// The tables to handle the symmetry reduced flip-slice coordinate in phase 1.
pub struct FlipSliceSyms {
    pub classidx: Vec<u16>,
    pub sym: Vec<u8>,
    pub rep: Vec<u32>,
}

/// Generate the tables to handle the symmetry reduced flip-slice coordinate in phase 1.
pub fn flipslice_syms() -> Result<FlipSliceSyms, Error> {
    let sc = sc();
    let inv_idx = inv_idx();
    std::fs::create_dir_all("tables")?;
    let fname1 = "tables/fs_classidx";
    let fname2 = "tables/fs_sym";
    let fname3 = "tables/fs_rep";
    let classidx_table = std::fs::read(&fname1).unwrap_or("".into());
    let sym_table = std::fs::read(&fname2).unwrap_or("".into());
    let rep_table = std::fs::read(&fname3).unwrap_or("".into());
    let mut flipslice_classidx = vec![65535; N_FLIP * N_SLICE]; // idx -> classidx
    let mut flipslice_sym = vec![0; N_FLIP * N_SLICE]; // idx -> symmetry
    let mut flipslice_rep = vec![0; N_FLIPSLICE_CLASS]; // classidx -> idx of representant
    if classidx_table.is_empty() {
        println!("Creating flipslice sym-tables...");
        let mut classidx = 0;
        let mut cc = CubieCube::default();
        for slc in 0..N_SLICE {
            cc.set_slice(slc as u16);
            for flip in 0..N_FLIP {
                cc.set_flip(flip as u16);
                let idx = N_FLIP * slc + flip;
                if (idx + 1) % 4000 == 0 {
                    print!(".");
                }
                if (idx + 1) % 320000 == 0 {
                    println!();
                }

                if flipslice_classidx[idx] == 65535 {
                    flipslice_classidx[idx] = classidx;
                    flipslice_sym[idx] = 0;
                    flipslice_rep[classidx as usize] = idx as u32;
                } else {
                    continue;
                }
                for s in 0..N_SYM_D4H {
                    // conjugate representant by all 16 symmetries
                    let si = inv_idx[s] as usize;
                    let mut ss = CubieCube {
                        cp: sc[si].cp,
                        co: sc[si].co,
                        ep: sc[si].ep,
                        eo: sc[si].eo,
                    }; // copy cube
                    ss.edge_multiply(cc);
                    ss.edge_multiply(sc[s]); // s^-1*cc*s
                    let idx_new = N_FLIP * ss.get_slice() as usize + ss.get_flip() as usize;
                    if flipslice_classidx[idx_new] == 65535 {
                        flipslice_classidx[idx_new] = classidx;
                        flipslice_sym[idx_new] = s as u8;
                    }
                }
                classidx += 1;
            }
        }
        println!();
        write_table(fname1, &flipslice_classidx)?;
        write_table(fname2, &flipslice_sym)?;
        write_table(fname3, &flipslice_rep)?;
    } else {
        // println!("Loading flipslice sym-tables...");
        flipslice_classidx = decode_table(&classidx_table)?;
        flipslice_sym = decode_table(&sym_table)?;
        flipslice_rep = decode_table(&rep_table)?;
    }
    Ok(FlipSliceSyms {
        classidx: flipslice_classidx,
        sym: flipslice_sym,
        rep: flipslice_rep,
    })
}

/// The tables to handle the symmetry reduced corner permutation coordinate in phase 2.
pub struct CornerSyms {
    pub classidx: Vec<u16>,
    pub sym: Vec<u8>,
    pub rep: Vec<u16>,
}

/// Generate the tables to handle the symmetry reduced corner permutation coordinate in phase 2.
pub fn corner_syms() -> Result<CornerSyms, Error> {
    let sc = sc();
    let inv_idx = inv_idx();
    std::fs::create_dir_all("tables")?;
    let fname1 = "tables/co_classidx";
    let fname2 = "tables/co_sym";
    let fname3 = "tables/co_rep";
    let classidx_table = std::fs::read(&fname1).unwrap_or("".into());
    let sym_table = std::fs::read(&fname2).unwrap_or("".into());
    let rep_table = std::fs::read(&fname3).unwrap_or("".into());
    let mut corner_classidx = vec![65535; N_CORNERS]; // idx -> classidx
    let mut corner_sym = vec![0; N_CORNERS]; // idx -> symmetry
    let mut corner_rep = vec![0; N_CORNERS_CLASS]; // classidx -> idx of representant
    if classidx_table.is_empty() {
        println!("Creating corner sym-tables...");
        let mut classidx = 0;
        let mut cc = CubieCube::default();
        for cp in 0..N_CORNERS {
            cc.set_corners(cp as u16);
            if (cp + 1) % 8000 == 0 {
                print!(".");
            }

            if corner_classidx[cp] == 65535 {
                corner_classidx[cp] = classidx;
                corner_sym[cp] = 0;
                corner_rep[classidx as usize] = cp as u16;
            } else {
                continue;
            }
            for s in 0..N_SYM_D4H {
                let si = inv_idx[s] as usize;
                // conjugate representant by all 16 symmetries
                let mut ss = CubieCube {
                    cp: sc[si].cp,
                    co: sc[si].co,
                    ep: sc[si].ep,
                    eo: sc[si].eo,
                }; // copy cube
                ss.corner_multiply(cc);
                ss.corner_multiply(sc[s]); // s^-1*cc*s
                let cp_new = ss.get_corners();
                if corner_classidx[cp_new as usize] == 65535 {
                    corner_classidx[cp_new as usize] = classidx;
                    corner_sym[cp_new as usize] = s as u8;
                }
            }
            classidx += 1;
        }
        println!();
        write_table(fname1, &corner_classidx)?;
        write_table(fname2, &corner_sym)?;
        write_table(fname3, &corner_rep)?;
    } else {
        // println!("Loading corner sym-tables...");
        corner_classidx = decode_table(&classidx_table)?;
        corner_sym = decode_table(&sym_table)?;
        corner_rep = decode_table(&rep_table)?;
    }
    Ok(CornerSyms {
        classidx: corner_classidx,
        sym: corner_sym,
        rep: corner_rep,
    })
}

#[cfg(test)]
mod test {
    use crate::symmetries::*;

    #[test]
    fn test_symcube() {
        let bsc = basicsc();
        let bsc2 = CubieCube {
            cp: [UBR, URF, UFL, ULB, DRB, DFR, DLF, DBL],
            co: [0, 0, 0, 0, 0, 0, 0, 0],
            ep: [UB, UR, UF, UL, DB, DR, DF, DL, BR, FR, FL, BL],
            eo: [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1],
        };
        assert_eq!(bsc[2], bsc2);

        let sc = sc();
        let sc22 = CubieCube {
            cp: [DFR, DLF, UFL, URF, DRB, DBL, ULB, UBR],
            co: [2, 1, 2, 1, 1, 2, 1, 2],
            ep: [FR, DF, FL, UF, BR, DB, BL, UB, DR, DL, UL, UR],
            eo: [0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0],
        };

        assert_eq!(sc[22], sc22);
    }

    #[test]
    fn test_inv_idx() {
        let inv_idx = inv_idx();
        let inv_idx_py: [u8; 48] = [
            0, 1, 6, 3, 4, 5, 2, 7, 8, 9, 10, 15, 12, 13, 14, 11, 32, 41, 18, 19, 40, 33, 30, 31,
            44, 37, 26, 27, 36, 45, 22, 23, 16, 21, 46, 35, 28, 25, 38, 43, 20, 17, 42, 39, 24, 29,
            34, 47,
        ];

        assert_eq!(inv_idx, inv_idx_py);
    }

    #[test]
    fn test_mult_sym() {
        let mult_sym = mult_sym();
        assert_eq!(mult_sym[344], 11);
    }

    #[test]
    fn test_conj_move() {
        let conj_move = conj_move();
        assert_eq!(conj_move[863], 15);
    }

    #[test]
    fn test_conj_twist() {
        let conj_twist_table = conj_twist().unwrap();
        assert_eq!(conj_twist_table.len(), 34992);
        assert_eq!(conj_twist_table[34991], 1174);
        assert_eq!(conj_twist_table[349], 135);
        assert_eq!(conj_twist_table[34], 7);
        assert_eq!(conj_twist_table[3], 0);
    }

    #[test]
    fn test_conj_ud_edges() {
        let conj_ud_edges = conj_ud_edges().unwrap();
        assert_eq!(conj_ud_edges.len(), 645120);
        assert_eq!(conj_ud_edges[645119], 19857);
        assert_eq!(conj_ud_edges[64511], 29351);
        assert_eq!(conj_ud_edges[645], 31);
        assert_eq!(conj_ud_edges[0], 0);
    }

    #[test]
    fn test_flipslice_syms() {
        let flipslice_syms = flipslice_syms().unwrap();
        assert_eq!(flipslice_syms.classidx.len(), 1013760);
        assert_eq!(flipslice_syms.sym.len(), 1013760);
        assert_eq!(flipslice_syms.rep.len(), 64430);

        assert_eq!(flipslice_syms.classidx[12345], 2315);
        assert_eq!(flipslice_syms.sym[12345], 6);
        assert_eq!(flipslice_syms.rep[12345], 42257);
    }

    #[test]
    fn test_corner_syms() {
        let cornersyms = corner_syms().unwrap();
        assert_eq!(cornersyms.classidx.len(), 40320);
        assert_eq!(cornersyms.sym.len(), 40320);
        assert_eq!(cornersyms.rep.len(), 2768);

        assert_eq!(cornersyms.classidx[12345], 1831);
        assert_eq!(cornersyms.sym[12345], 8);
        assert_eq!(cornersyms.rep[1234], 2335);
    }
}