@@ -305,6 +305,9 @@ impl TwistSystemProduct {
305305 stabilized_points. transform_by_group_element ( & named_point_action, deorbiter) ,
306306 orbit. gizmo_pole_distance ,
307307 ) ) ;
308+ if orbit. gizmo_pole_distance <= 0.0 {
309+ bail ! ( "stabilizer twist gizmo_pole_distance cannot be negative" )
310+ }
308311 }
309312
310313 let mut named_point_set_orbits: Vec < ( NamedPointSet , Float ) > = vec ! [ ] ;
@@ -323,6 +326,9 @@ impl TwistSystemProduct {
323326 . map ( |s| names. named_point_from_name ( s) )
324327 . try_collect ( ) ?;
325328 named_point_set_orbits. push ( ( NamedPointSet :: new ( points) ?, orbit. gizmo_pole_distance ) ) ;
329+ if orbit. gizmo_pole_distance <= 0.0 {
330+ bail ! ( "named point set gizmo_pole_distance cannot be negative" )
331+ }
326332 }
327333
328334 // Assemble jumble moves.
@@ -704,12 +710,7 @@ impl TwistSystemProduct {
704710
705711 build_ctx. push_task ( "computing stabilized coset" ) ;
706712 let coset = subgroup_solver
707- . solve ( & hypergroup:: ConstraintSet :: from_iter (
708- secondary
709- . iter ( )
710- . circular_tuple_windows ( )
711- . map ( |( from, to) | hypergroup:: Constraint { from, to } ) ,
712- ) )
713+ . solve ( & cycle_constraints ( secondary. iter ( ) ) )
713714 . ok_or_else ( || {
714715 eyre ! (
715716 "stabilizer twist {:?} imposes unsatisfiable constraints \
@@ -722,11 +723,11 @@ impl TwistSystemProduct {
722723
723724 build_ctx. push_task ( "computing unit twist transform" ) ;
724725 let unit_twist_transform = if secondary. is_empty ( ) {
725- unit_twist_transform ( & self . group , & coset, & [ first_axis_vector] )
726+ unit_twist_transform ( & self . group , & coset, & [ first_axis_vector. clone ( ) ] )
726727 } else {
727728 let secondary_vector = secondary. vector ( & self . named_point_vectors ) ;
728- let stabilized_vectors = & [ first_axis_vector, & secondary_vector] ;
729- unit_twist_transform ( & self . group , & coset, stabilized_vectors)
729+ let stabilized_vectors = [ first_axis_vector. clone ( ) , secondary_vector] ;
730+ unit_twist_transform ( & self . group , & coset, & stabilized_vectors)
730731 }
731732 . wrap_err_with ( || {
732733 format ! (
@@ -812,7 +813,7 @@ impl TwistSystemProduct {
812813fn unit_twist_transform (
813814 group : & IsometryGroup ,
814815 stabilizer_coset : & ConjugateCoset ,
815- stabilized_vectors : & [ & Vector ] ,
816+ stabilized_vectors : & [ Vector ] ,
816817) -> Result < UniqueMinimalClockwiseGenerator > {
817818 if stabilized_vectors. len ( ) + 2 != group. ndim ( ) as usize {
818819 bail ! ( "`stabilized_vectors` must have length ndim-2" ) ;
@@ -823,30 +824,20 @@ fn unit_twist_transform(
823824 . filter ( |& e| e != GroupElementId :: IDENTITY )
824825 . filter ( |& e| !group. is_reflection ( e) )
825826 . collect_vec ( ) ;
826- let order =
827- NonZeroI32 :: new ( nontrivial_rotations. len ( ) as i32 + 1 ) . ok_or_eyre ( "math is broken" ) ?;
828827 let ( mut min_group_element, min_rotation) = nontrivial_rotations
829828 . iter ( )
830829 . filter_map ( |& e| Some ( ( e, group. motor ( e) . normalize ( ) ?) ) )
831830 . max_by_float_key ( |( _e, m) | m. scalar ( ) . abs ( ) )
832831 . ok_or_eyre ( "empty coset" ) ?;
833- let arbitrary_nonparallel_vector = Vector :: unit (
834- ( 0 ..group. ndim ( ) )
835- . min_by_float_key ( |& i| {
836- stabilized_vectors
837- . iter ( )
838- . map ( |v| v. get ( i) . abs ( ) )
839- . max_float ( )
840- . unwrap_or ( 0.0 )
841- } )
842- . unwrap_or ( 0 ) ,
843- ) ;
832+ let arbitrary_perpendicular_vector =
833+ Vector :: arbitrary_perpendicular_to ( group. ndim ( ) , stabilized_vectors)
834+ . ok_or_eyre ( "stabilized vectors cannot span all of space" ) ?;
844835 let orientation = Matrix :: from_cols (
845836 std:: iter:: chain (
846- stabilized_vectors. iter ( ) . copied ( ) ,
837+ stabilized_vectors,
847838 [
848- & arbitrary_nonparallel_vector ,
849- & min_rotation. transform ( & arbitrary_nonparallel_vector ) ,
839+ & arbitrary_perpendicular_vector ,
840+ & min_rotation. transform ( & arbitrary_perpendicular_vector ) ,
850841 ] ,
851842 )
852843 . collect_vec ( ) , // Chain does not impl ExactSizeIterator
@@ -855,8 +846,90 @@ fn unit_twist_transform(
855846 if orientation > 0.0 {
856847 min_group_element = group. inverse ( min_group_element) ;
857848 }
858- Ok ( UniqueMinimalClockwiseGenerator {
859- element : min_group_element,
860- order,
861- } )
849+
850+ Ok ( dbg ! ( UniqueMinimalClockwiseGenerator :: new(
851+ group. abstract_group( ) ,
852+ min_group_element,
853+ ) ) )
854+ }
855+
856+ /// Constructs a constraint set for a cycle of points.
857+ fn cycle_constraints (
858+ points : impl Iterator < Item = NamedPoint > + Clone + ExactSizeIterator ,
859+ ) -> hypergroup:: ConstraintSet < NamedPoint > {
860+ hypergroup:: ConstraintSet :: from_iter (
861+ points
862+ . circular_tuple_windows ( )
863+ . map ( |( from, to) | hypergroup:: Constraint { from, to } ) ,
864+ )
865+ }
866+
867+ #[ cfg( test) ]
868+ mod tests {
869+ use hypergroup:: GeneratorId ;
870+ use hypermath:: APPROX ;
871+
872+ use super :: * ;
873+
874+ #[ test]
875+ fn test_unit_twist_transform ( ) -> Result < ( ) > {
876+ let h3 = hypergroup:: CoxeterMatrix :: H3 ( ) ;
877+ let group =
878+ hypergroup:: CoxeterMatrix :: direct_product ( & h3, & hypergroup:: CoxeterMatrix :: A ( 1 ) ?) ?
879+ . isometry_group ( ) ?;
880+
881+ let named_point_vectors: PerNamedPoint < Vector > = group
882+ . orbit_geometric (
883+ h3. mirror_basis ( ) ?. col ( 2 ) . to_vector ( ) ,
884+ hypergroup:: ORBIT_LIMIT ,
885+ ) ?
886+ . into_iter ( )
887+ . map ( |( _, v) | v)
888+ . collect ( ) ;
889+ let points: PerNamedPoint < Point > = named_point_vectors. map_ref ( |_, v| Point ( v. clone ( ) ) ) ;
890+
891+ let named_point_action = group. action_on_points ( & points) ?;
892+
893+ let w = Vector :: unit ( 3 ) ;
894+
895+ let subgroup_action = SubgroupAction :: from_subgroup_predicate ( & named_point_action, |e| {
896+ APPROX . eq ( & group. motor ( e) . transform ( & w) , & w) // stabilize W axis
897+ } ) ?;
898+ let mut subgroup_solver = SubgroupConstraintSolver :: new ( subgroup_action) ;
899+
900+ let g1 = group. generators ( ) [ GeneratorId ( 1 ) ] ;
901+ let g2 = group. generators ( ) [ GeneratorId ( 2 ) ] ;
902+
903+ let f = NamedPoint ( 0 ) ;
904+ let u = named_point_action. act ( g2, f) ;
905+ let r = named_point_action. act ( g1, u) ;
906+
907+ let mut check_unit_twist_transform = |stab : & [ NamedPoint ] , period : usize | -> Result < ( ) > {
908+ println ! ( "Testing setwise stabilizer {:?} with period {period}" , stab) ;
909+
910+ let secondary_vector: Vector = stab. iter ( ) . map ( |& p| & named_point_vectors[ p] ) . sum ( ) ;
911+ let stabilized_vectors = [ w. clone ( ) , secondary_vector] ;
912+
913+ // unit_twist_transform() should always produce a clockwise
914+ // rotation, regardless of the input cycle direction.
915+ let forward_coset = subgroup_solver
916+ . solve ( & cycle_constraints ( stab. iter ( ) . copied ( ) ) )
917+ . expect ( "unsat" ) ;
918+ let reverse_coset = subgroup_solver
919+ . solve ( & cycle_constraints ( stab. iter ( ) . rev ( ) . copied ( ) ) )
920+ . expect ( "unsat" ) ;
921+ let unit1 = unit_twist_transform ( & group, & forward_coset, & stabilized_vectors) ?;
922+ let unit2 = unit_twist_transform ( & group, & reverse_coset, & stabilized_vectors) ?;
923+ assert_eq ! ( group. abstract_group( ) . period( unit1. element) , period) ;
924+ assert_eq ! ( group. abstract_group( ) . period( unit2. element) , period) ;
925+ assert_eq ! ( unit1, unit2) ;
926+ Ok ( ( ) )
927+ } ;
928+
929+ check_unit_twist_transform ( & [ u] , 5 ) ?; // face twist
930+ check_unit_twist_transform ( & [ u, r] , 2 ) ?; // edge twist
931+ check_unit_twist_transform ( & [ u, r, f] , 3 ) ?; // vertex twist
932+
933+ Ok ( ( ) )
934+ }
862935}
0 commit comments