'One level up
'******************************************************************************
'Subject: Multiplicative group of units modulo small N:
'         representing U(N) as a direct product of
'         canonical least-generator cyclic subgroups.
'Ref.   : D. Shanks, Solved and unsolved problems in number theory,
'         Chelsea, New York, 1978, pp. 83-98:
'        'Questions & answers concerning cycle graphs.'
'Author : Sjoerd.J.Schaper
'Date   : 07-03-2007
'Code   : QBasic, PDS, VBdos, FreeBasic (-lang qb)
'Link to an ANSI C implementation good for N < 2^16.
'There's a more efficient method for finding generators,
'like in ZnStruct.bas, but they won't be the positive smallest ones.
'******************************************************************************
'This program is copyright (c) 2007 by the author. It is made available as is,
'and no warranty - about the program, its performance, or its conformity to any
'specification - is given or implied. It may be used, modified, and distributed
'freely, as long as the original author is credited as part of the final work.
'******************************************************************************
DEFINT A-Z
TYPE Pls
   f AS INTEGER
   e AS INTEGER
END TYPE
DECLARE FUNCTION gcd (a, b)
'highest common divisor
DECLARE SUB Pwr (a, k)
'let a:= a ^ k
DECLARE FUNCTION modpwr (a, k)
'return a ^ k (mod N)
DECLARE FUNCTION order (a)
'return ord_N(a)
DECLARE FUNCTION triald (a, sw)
'primefactors(a),
'set sw = 1 to print
DECLARE FUNCTION invars (a)
'torsion invariants(a)
DECLARE SUB Sieve (Ui, N)
'sieve residues prime to N
DECLARE SUB Balc (p)
'print balanced residues,
'initialize by printing N
DECLARE SUB Cycle (Hi, q, sw)
'H:= cycle generated by q,
'set sw = 1 to print
DECLARE SUB Smult (Ki, j, q)
'recursive set multiplication
DECLARE FUNCTION dprod (Gi, Hi)
'direct product G:= G x H,
'return 1 if G is enlarged
DECLARE SUB Remove (Gi, Hi)
'mask out subgroup H in G
DECLARE SUB Build (v())
'build generator, and cycle
DECLARE SUB Combi (v(), i)
'recursive index combinations
DECLARE FUNCTION popchk (Gi)
'return population count(Gi)

CONST U = 0, G = 1, H = 2      '       groups
CONST PB = 32, Pm1 = PB - 1    '       Dword size
DIM SHARED bi(Pm1) AS LONG     '       bitmasks
DIM SHARED bv(8192) AS LONG    '       4 bitvectors
DIM SHARED pt(3), ge(6), cy(6) '       generators, invariant orders
DIM SHARED pr(5) AS Pls        '       primelist
DIM SHARED cx, sx, fx, N       '       bounds, modulus
DIM iv(6)                      '       index vector
bi(0) = 1
FOR t = 1 TO Pm1 - 1 '                 store bitmasks
   bi(t) = bi(t - 1) * 2
NEXT t: bi(t) = &H80000000
CLS : tim# = TIMER

begin:
PRINT
DO: INPUT " N"; Ni$
LOOP WHILE INSTR(Ni$, "'")
IF VAL(Ni$) > 32767 THEN GOTO begin
N = VAL(Ni$): IF N < 2 THEN GOTO eind
Verb = RIGHT$(Ni$, 1) = "c" '          suffix 'c' to print cycles

Balc N: cx = invars(N): fi = 1 '       factorize N, find invariants
IF cx = 0 THEN PRINT " phi = 1": GOTO begin
cx = cx - 1
FOR c = 0 TO cx: fi = fi * cy(c): NEXT
PRINT " phi ="; fi '                   order(U(N))

sx = (N - 1) \ PB + 1
FOR r = 0 TO 3: pt(r) = r * sx: NEXT ' initialize row pointers
sx = sx - 1

bv(pt(G)) = 0: Sieve U, N '            prepare G and U(N)
fx = triald(cy(0), 0) - 1 '            factorize group exponent

FOR c = 0 TO cx '                      find generators
   FOR s = 0 TO sx
      IF bv(s) THEN '                  unpack U(N) elements
         r = s * PB
         FOR t = 0 TO Pm1
            IF bv(s) AND bi(t) THEN
               q = r + t
               IF order(q) = cy(c) THEN
                  Cycle H, q, 0 '      invariant order

                  IF dprod(G, H) THEN ' valid factor H
                     Remove U, G
                     ge(c) = q: GOTO jump
                  END IF
               END IF
            END IF
         NEXT t
      END IF
   NEXT s
jump:
NEXT c

IF popchk(G) = fi THEN '               check if G holds the full
   PRINT "<generator> order" '         unit group of order phi(N)
   FOR c = 0 TO cx
      PRINT " <"; ge(c); ">"; cy(c) '  minimal generating set
   NEXT c
   '
   IF Verb THEN
      PRINT "(indices) {cycle}  order"
      Combi iv(), 0 '                  covering cycles
      IF popchk(G) THEN
         PRINT " fail: units left"
      END IF
   END IF
ELSE
   PRINT " fail: order < phi"
END IF

GOTO begin

eind:
PRINT : PRINT "Timer:"; CSNG(TIMER - tim#); " s"
SYSTEM

SUB Balc (p)
STATIC d, r
DIM s AS STRING
   IF p < N THEN
      IF p > d THEN
         s = STR$(p - N)
      ELSE
         s = STR$(p)
      END IF
      PRINT SPACE$(r - LEN(s)); s; " ";
   ELSE
      d = N \ 2: r = LEN(STR$(d))
      PRINT " N ="; N
   END IF
END SUB

SUB Build (v())
   q = 1
   FOR c = 0 TO cx
      p = modpwr(ge(c), v(c))
      q = CLNG(p) * q MOD N
   NEXT c
   '
   p = pt(G) + q \ PB '                assuming G holds U(N)
   IF bv(p) AND bi(q AND Pm1) THEN
      PRINT " (";
      FOR c = 0 TO cx
         PRINT STR$(v(c));
      NEXT c: PRINT " )";
      Cycle H, q, 1
      Remove G, H '                    clear < q > candidates
   END IF
END SUB

SUB Combi (v(), c)
   FOR i = 1 TO cy(c)
      v(c) = i
      IF c < cx THEN
         Combi v(), c + 1
      ELSE
         Build v()
      END IF
   NEXT i
END SUB

SUB Cycle (r, q, sw)
   Hr = pt(r)
   FOR s = 0 TO sx: bv(Hr + s) = 0: NEXT
   '
   IF sw THEN k = 0: PRINT " {";
   p = 1: DO
      p = CLNG(q) * p MOD N
      s = Hr + p \ PB
      bv(s) = bv(s) OR bi(p AND Pm1) ' store cycle
      IF sw THEN k = k + 1: Balc p
   LOOP UNTIL p = 1
   IF sw THEN PRINT "} "; k
END SUB

FUNCTION dprod (i, j)
DIM W AS LONG
dprod = 0
   Gr = pt(i): Hr = pt(j): Kr = pt(j + 1)
   '
   IF bv(Gr) = 0 THEN
      SWAP pt(i), pt(j) '              1st largest subgroup H
      dprod = 1: EXIT FUNCTION
   END IF
   '
   bv(Hr) = bv(Hr) AND NOT bi(1) '     clear {1}
   FOR s = 0 TO sx
      W = bv(Gr + s) '                 non-trivial intersection:
      IF W AND bv(Hr + s) THEN EXIT FUNCTION
      bv(Kr + s) = W OR bv(Hr + s) '   join 1G with 1H
   NEXT s
   bv(Gr) = bv(Gr) AND NOT bi(1)
   '
   Smult j + 1, j, 0 '                 product G x H/1G, /1H
   SWAP pt(i), pt(j + 1)
dprod = 1
END FUNCTION

FUNCTION gcd (x, y)
   a = x: b = y
   WHILE b
      c = a MOD b
      a = b: b = c
   WEND
gcd = a
END FUNCTION

FUNCTION invars (a)
invars = 0
   fx = triald(a, 1) '                 factorize a
   IF fx = 0 THEN EXIT FUNCTION
   fx = fx - 1

   j = 0: t = -1
   IF pr(0).f = 2 THEN
      j = 1
      IF pr(0).e > 1 THEN '            multiplicity of 2
         e = pr(0).e: t = t + 1: cy(t) = 2
         IF e > 2 THEN t = t + 1: cy(t) = bi(e - 2)
      END IF
   END IF
   '
   FOR i = j TO fx '                   odd prime power decomposition
      q = 1: t = t + 1: cy(t) = pr(i).f
      IF pr(i).e > 1 THEN
         e = pr(i).e: q = cy(t)
         IF e > 2 THEN Pwr q, e - 1
         cy(t) = cy(t) * q
      END IF
      cy(t) = cy(t) - q '              Eulerphi(p^ e)
   NEXT i
   '
   FOR i = t TO 1 STEP -1 '            Smith canonical form
      FOR j = i - 1 TO 0 STEP -1
         IF cy(j) MOD cy(i) THEN
            q = gcd(cy(i), cy(j))
            cy(j) = (cy(j) \ q) * cy(i)
            cy(i) = q
         END IF
      NEXT j
   NEXT i
   '
   FOR i = t + 1 TO 6: cy(i) = 0: NEXT
invars = t + 1
END FUNCTION

FUNCTION modpwr (a, k)
DIM b AS LONG
   p = 1: b = a: e = k
   WHILE e
      IF e AND 1 THEN p = b * p MOD N
      b = b * b MOD N: e = e \ 2
   WEND
modpwr = p
END FUNCTION

FUNCTION order (a)
   e = cy(0) '                         Carmichael lambda
   FOR i = 0 TO fx
      FOR j = 1 TO pr(i).e
         k = e \ pr(i).f
         IF modpwr(a, k) = 1 THEN '    reduce exponent
            e = k
         ELSE
            EXIT FOR
         END IF
      NEXT j
   NEXT i
order = e
END FUNCTION

FUNCTION popchk (r)
DIM W AS LONG: Gr = pt(r)
   k = 0
   FOR s = 0 TO sx
      IF bv(Gr + s) THEN
         W = bv(Gr + s)
         FOR t = 0 TO Pm1
            k = k + (((W AND bi(t)) <> 0) AND 1)
         NEXT t
      END IF
   NEXT s
popchk = k
END FUNCTION

SUB Pwr (a, k)
   p = 1: e = k
   WHILE e
      IF e AND 1 THEN p = p * a
      a = a * a: e = e \ 2
   WEND: a = p
END SUB

SUB Remove (i, j)
   Gr = pt(i): Hr = pt(j)
   FOR s = 0 TO sx
      bv(Gr + s) = bv(Gr + s) AND NOT bv(Hr + s)
   NEXT s
END SUB

SUB Sieve (r, N)
DIM W AS LONG
   sw = pr(0).f = 2
   j = 0: W = &HFFFFFFFF
   IF sw THEN j = 1: W = &HAAAAAAAA
   FOR s = 0 TO sx: bv(s) = W: NEXT '  initialize bitvector U
   bv(r) = bv(r) AND NOT 3
   '
   FOR i = j TO fx '                   sieve units
      p = pr(i).f: dp = p
      IF sw THEN dp = p + p
      DO
         s = p \ PB
         bv(s) = bv(s) AND NOT bi(p AND Pm1)
         IF p > N - dp THEN EXIT DO
         p = p + dp
      LOOP
   NEXT i
END SUB

SUB Smult (i, j, q)
DIM W AS LONG: Gr = pt(j)
   FOR s = 0 TO sx
      IF bv(Gr + s) THEN '             unpack set elements
         W = bv(Gr + s): r = s * PB
         FOR t = 0 TO Pm1
            IF W AND bi(t) THEN
               IF j > 1 THEN
                  Smult i, j - 1, r + t
               ELSE '                  orbit of q under G
                  p = CLNG(r + t) * q MOD N
                  k = pt(i) + p \ PB
                  bv(k) = bv(k) OR bi(p AND Pm1)
               END IF
            END IF
         NEXT t
      END IF
   NEXT s
END SUB

FUNCTION triald (a, sw)
triald = 0
   IF a < 2 THEN EXIT FUNCTION
   f = 2: df = 1: fl = 0
   b = a: t = -1
   DO
      e = 0: DO
         q = b \ f '                   trial division
         IF b - q * f THEN EXIT DO
         b = q: e = e + 1
      LOOP
      IF e THEN
         t = t + 1 '                   store prime and exponent
         pr(t).f = f: pr(t).e = e
      ELSEIF f > q THEN '              f > sqrt(a)
         IF b > 1 THEN
            t = t + 1 '                store coprime
            pr(t).f = b: pr(t).e = 1
         END IF
         EXIT DO
      END IF
      IF fl THEN
         df = 6 - df: f = f + df '     sieve multiples of 2 and 3
      ELSE
         f = f + df: df = df * 2: fl = f = 5
      END IF
   LOOP
   '
   IF sw AND (t > 0 OR pr(0).e > 1) THEN
      PRINT "     ";
      FOR i = 0 TO t
         e = pr(i).e: s$ = ""
         IF e > 1 THEN s$ = "^" + LTRIM$(STR$(e))
         IF i < t THEN s$ = s$ + ","
         PRINT LTRIM$(STR$(pr(i).f)) + s$;
      NEXT i: PRINT
   END IF
triald = t + 1
END FUNCTION
'