Ajout macro et tableur pour le tirage au sort publiquement vérifiable et équiprobable.
authorJulien Moutinho <julm@autogeree.net>
Sun, 19 Aug 2018 05:44:59 +0000 (07:44 +0200)
committerJulien Moutinho <julm@autogeree.net>
Sun, 19 Aug 2018 05:45:15 +0000 (07:45 +0200)
Makefile
tirages.ods [new file with mode: 0644]
tirages.pdf [new file with mode: 0644]
tirages.py

index be879bf93476df8525aab4e7108c8cb616c5cf03..502a8c04d35f9d6c14f333e0ea06959fa02bced6 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,16 +1,28 @@
 python:=python3
 
+# Start LibreOffice Calc in server-mode
 %.ods/localc:
-       localc --accept="socket,host=localhost,port=2002;urp;StarOffice.ServiceManager" $*.ods &
+       export PYSCRIPT_LOG_STDOUT=1
+       export PYSCRIPT_LOG_LEVEL=DEBUG
+       localc --accept="socket,host=localhost,port=2002;urp;StarOffice.ServiceManager" $*.ods
+
+# Put a macro into an Open Document Spreadsheet
 %.ods/pack_macro: %.py
        $(python) pack_macro.py $*.py $*.ods
+
+# Generate ballots for a Majority Judgment
 ballots:
        $(python) -c "import jugements; jugements.MakeBallots()"
        pdfnup --a4paper --nup 2x2 --outfile bulletins.pdf bulletins/*.pdf
+
+# Generate results of a Majority Judgment
 results:
        $(python) -c "import jugements; jugements.MakeResults()"
 
+# Join PDFs
 sécu-sauvage.pdf: présentation.pdf résultats.pdf
        pdfjoin --outfile $@ --paper a4paper --rotateoversize false $^
+
+# Watermark PDF
 %-prototype.pdf: %.pdf
        pdftk $*.pdf background prototype.pdf output $*-prototype.pdf
diff --git a/tirages.ods b/tirages.ods
new file mode 100644 (file)
index 0000000..125664b
Binary files /dev/null and b/tirages.ods differ
diff --git a/tirages.pdf b/tirages.pdf
new file mode 100644 (file)
index 0000000..1415847
Binary files /dev/null and b/tirages.pdf differ
index d8c98f463bafec5cf2dd2940b08136aea3241f47..c8e6a0c4154eaad0899d146ddac4ceb602995325 100644 (file)
@@ -3,16 +3,10 @@
 # License: GNU GPLv3 (or later, at your choice)
 
 from __future__ import unicode_literals
-import uno
-from com.sun.star.awt.MessageBoxType    import MESSAGEBOX, INFOBOX, WARNINGBOX, ERRORBOX, QUERYBOX
-from com.sun.star.awt.MessageBoxButtons import BUTTONS_OK, BUTTONS_OK_CANCEL, BUTTONS_YES_NO, BUTTONS_YES_NO_CANCEL, BUTTONS_RETRY_CANCEL, BUTTONS_ABORT_IGNORE_RETRY
-from com.sun.star.awt.MessageBoxResults import OK, YES, NO, CANCEL
-from com.sun.star.beans import PropertyValue
-from com.sun.star.sheet import CellFlags
 
 if 'XSCRIPTCONTEXT' in globals():
        def getModel():
-               return (XSCRIPTCONTEXT.getDocument(), XSCRIPTCONTEXT)
+               return (XSCRIPTCONTEXT.getDocument(), XSCRIPTCONTEXT.getComponentContext())
        def debug(msg):
                return
 else:
@@ -20,22 +14,36 @@ else:
        def debug(msg):
                print(msg)
 
-def MakeDraw(*args):
-       return
-
+def nAk(n,k):
+       """
+       nAk(n,k) retourne le nombre d’arrangements
+       de longueur k d’un ensemble de longueur n.
+       """
+       if n<0 or k<0 or n<k:
+               return None
+       r = 1
+       for i in range(n-k+1, n+1):
+               r *= i
+       #print("nAk%s = %i" % (str((n,k)), r))
+       return r
+def nAkLO(n,k):
+       return str(nAk(int(n), int(k)))
 def nCk(n,k):
        """
        nCk(n,k) retourne le nombre de combinaisons
        de longueur k d’un ensemble de longueur n.
        """
        if n<0 or k<0 or n<k:
-               raise ZeroDivisionError
+               return None
        if k > n // 2:
                k = n - k # more efficient and safe with smaller numbers
-       c = 1
-       for j in range(1,k+1):
-               c = c * (n-j+1) // j
-       return c
+       r = 1
+       for j in range(1, k+1):
+               r = r * (n-j+1) // j
+       #print("nCk%s = %i" % (str((n,k)), r))
+       return r
+def nCkLO(n,k):
+       return str(nCk(int(n), int(k)))
 def combinOfRank(n,k,rank):
        """
        combinOfRank(n, k, r) retourne les indices de permutation
@@ -48,8 +56,9 @@ def combinOfRank(n,k,rank):
        
        DOC: <http://www.site.uottawa.ca/~lucia/courses/5165-09/GenCombObj.pdf>, p.26
        """
+       #print("combinOfRank: "+str((n,k,rank)))
        if rank<0 or nCk(n,k)<rank:
-               raise ZeroDivisionError
+               return None
        i = 1
        j = 1
        c = []
@@ -57,20 +66,67 @@ def combinOfRank(n,k,rank):
                if i < k:
                        while True: # uptoRank
                                nbCombins = nCk(n-j, k-i)
-                               if nbCombins > rank:
+                               #print("i=%i j=%i rank=%i nCk(%i-%i,%i-%i)=%i" % (i,j,rank, n,j,k,i,nbCombins))
+                               if nbCombins <= rank:
+                                       j += 1
+                                       rank -= nbCombins
+                                       #print("rank -= %i" % nbCombins)
+                               else:
                                        c.append(j)
+                                       #print("c = "+str(c))
                                        i += 1
                                        j += 1
                                        break
-                               else:
-                                       j += 1
-                                       rank -= nbCombins
                elif i == k:
                        c.append(j+rank) # because when i == k, nbCombins is always 1
                        break
                else:
                        break
        return c
+def sequenceOfRank(n, k, rank):
+       """
+       sequenceOfRank(n, k, r) retourne les indices de permutation
+       de la combinaison de k entiers parmi [1..n]
+       au rang lexicographique r dans [0 .. nAk(n,k)-1].
+       
+       rankOfSequence(n, sequenceOfRank(n, k, r)) == r
+       sequenceOfRank(n, len(ns), rankOfSequence(n, ns)) == ns
+       
+       DOC: <http://www.site.uottawa.ca/~lucia/courses/5165-09/GenCombObj.pdf>, p.75-77
+       Improved to work on k-sequence of permutations instead of only full permutations.
+       """
+       try:
+               rank = int(rank)
+       except ValueError:
+               return None
+       p = []
+       a = nAk(n,k)
+       for i in range(1,k+1):
+               a //= n-(i-1) # optimized a = nAk(n-i, k-i)
+               d = rank // a # greatest multiple of a, lower or equal to r
+               rank = rank % a
+               p.append(d+1)
+       for i in range(k-1,-1,-1): # important: from right to left
+               for j in range(i+1,k):
+                       if p[j] >= p[i]:
+                               p[j] += 1 # promote the positions in the good interval
+       return p
+def checkSequence(n,ns):
+       """
+       checkSequence(n,ns) returns the range ns flattened
+       if each element is with 1 and n, without repetition.
+       """
+       p = []
+       for row in ns: # flatten range given by LO
+               for col in row:
+                       i = int(col)
+                       if not(1<=i and i<=n):
+                               return None
+                       if i in p: # duplicate
+                               return None
+                       p.append(i)
+       return p
+       
 def rankOfCombin(n,ns):
        """
        rankOfCombin(n, ns) retourne le rang lexicographique dans [0 .. nCk(n, length ns)-1]
@@ -85,11 +141,11 @@ def rankOfCombin(n,ns):
        rankOfCombin(n, combinOfRank(n, k, r)) == r
        combinOfRank(n, len(ns), rankOfCombin(n, ns)) == ns
        """
-       if not all(1<=x or x<=n for x in ns):
-               raise ZeroDivisionError
+       ns = checkSequence(n, ns)
+       if ns == None:
+               return None
+       ns.sort()
        k = len(ns)
-       if n<k:
-               raise ZeroDivisionError
        i = 1
        rank = 0
        x1 = 0
@@ -98,14 +154,40 @@ def rankOfCombin(n,ns):
                        rank += nCk(n-j, k-i)
                i += 1
                x1 = x
-       return rank
-
-def nbBits(n):
+       return str(rank)
+def rankOfSequence(n, ns):
+       """
+       rankOfSequence(n, ns) retourne le rang lexicographique dans [0 .. nAk(n, len(ns))-1]
+       de la combinaison ns d’entiers parmi [1..n].
+       
+       Compte le nombre d’arrangements précédant celui de rang r.
+       
+       DOC: <http://www.site.uottawa.ca/~lucia/courses/5165-09/GenCombObj.pdf>, pp.74
+       """
+       rank = 0
+       ns = checkSequence(n, ns)
+       if ns == None:
+               return None
+       k = len(ns)
+       for i in range(0,k):
+               nsi = ns[i]
+               #print("ns = "+str(ns))
+               #print("rank += %i * nAk(%i,%i)" % (nsi - 1, n - (i+1), k - (i+1)))
+               rank += (nsi - 1) * nAk(n - (i+1), k - (i+1))
+               for j in range(i+1,k):
+                       if nsi < ns[j]:
+                               ns[j] -= 1
+       return str(rank)
+def bitSize(n):
        """
-       nbBits(n) retourne le nombre de bits servant à encoder n.
+       bitSize(n) retourne le nombre de bits servant à encoder n.
        """
+       try:
+               n = int(n)
+       except ValueError:
+               return None
        if n<0:
-               raise ZeroDivisionError
+               return None
        r = 0
        while n > 0:
                r += 1
@@ -120,78 +202,72 @@ def equiprobableBits(n):
        
        map(equiprobableBits, range(0, 17+1)) == [0,1,1,2,2,2,2,3,3,3,3,3,3,3,3,4,4,4]
        """
-       b = nbBits(n)
+       b = bitSize(n)
        return (b if n == 2**b-1 else b-1)
 def bitsOfInteger(m, n):
        """
        bitsOfInteger(m, n) retourne les m premiers bits de poids faible
        encodant le nombre n.
        """
+       m = int(m)
+       n = int(n)
        if not(0<=m and 0<=n):
-               raise ZeroDivisionError
-       bs = []
+               return None
+       bs = ""
        while m > 0:
                (q,r) = (n//2, n%2)
-               bs.append(r==1)
+               bs = ("1" if r else "0") + bs
                m -= 1
                n = q
        return bs
-def randomOfCombin(n, k, ns):
+def interleave(cols):
        """
-       randomOfCombin(n, k, ns) retourne des bits équiprobables donnés
-       par la combinaison ns obtenue par tirage équiprobable
-       d’une combinaison de k entiers parmi [1 .. n].
-       
-       WARNING: aucun bit n’est extrait du tirage ns
-       dans le cas où ns a un rang lexicographique encodé par
-       un nombre de bits strictement supérieur à equiprobableBits(nCk(n, k)).
+       interleave(cols) return a string interleaving the characters
+       of the strings within cols.
        """
-       if not(0<=n and 0<=k and k<=n and all(1<=x and x<=n for x in ns)):
-               raise ZeroDivisionError
-       ns.sort()
-       rank = rankOfCombin(n, ns)
-       epBits = equiprobableBits(nCk(n,k))
-       return bitsOfInteger(epBits, rank) \
-               if nbBits(rank) <= epBits else []
-
-def randomOfFrenchLoto (n1,n2,n3,n4,n5, nc):
+       m = 0
+       for rows in cols:
+               for cell in rows:
+                       try:
+                               m = max(m, len(cell))
+                       except TypeError:
+                               return None
+       s = ""
+       for idx in range(0, m):
+               for rows in cols:
+                       for cell in rows:
+                               try:
+                                       s+=cell[idx]
+                               except IndexError:
+                                       continue
+       return s
+def inBase(base, digits):
+       acc = 0
+       for digit in digits:
+               acc = int(digit) + (base * acc)
+       return acc
+def randomIntegerOfBits(n, bs):
        """
-       randomOfFrenchLoto(n1,n2,n3,n4,n5, numComplementaire) retourne les bits équiprobables donnés
-       par un tirage du Lo to Français : https://www.fdj.fr/jeux/jeux-de-tirage/loto/resultats/.
-       
-       Il peut produire @23@ bits équiprobables :
-       sum(map(equiprobableBits, [nCk(49, 5), nCk(10, 1)]))
-       
-       randomOfFrenchLoto(1,2,3,4,5, 1)     == [False] * (20+3)
-       randomOfFrenchLoto(7,27,36,40,46, 8) == [True]  * (20+3)
-       
-       combinOfRank(49, 5, 2 ** equiprobableBits(nCk(49,5)) - 1) == [7,27,36,40,46]
-       combinOfRank(49, 5, 2 ** equiprobableBits(nCk(49,5)))     == [7,27,36,40,47]
-       randomOfFrenchLoto(7,27,36,40,46, 1) == [True] * 20 + [False] * 3
-       randomOfFrenchLoto(7,27,36,40,47, 1) == [False,False,False]
-       
-       combinOfRank(10, 1, 2 ** equiprobableBits(nCk(10, 1)) - 1) == [8]
-       combinOfRank(10, 1, 2 ** equiprobableBits(nCk(10, 1)))     == [9]
-       randomOfFrenchLoto(7,27,36,40,47, 8) == [True,True,True]
-       randomOfFrenchLoto(7,27,36,40,47, 9) == []
-       """
-       return \
-               randomOfCombin(49, 5, [n1,n2,n3,n4,n5]) + \
-               randomOfCombin(10, 1, [nc])
-def randomOfEuroMillions(n1,n2,n3,n4,n5, nc1,nc2):
-       """
-       https://www.fdj.fr/jeux/jeux-de-tirage/euromillions/resultats
-       """
-       return \
-               randomOfCombin(50, 5, [n1,n2,n3,n4,n5]) + \
-               randomOfCombin(11, 2, [nc1,nc2])
-def randomOfSwissLoto(n1,n2,n3,n4,n5,n6, nc):
-       return \
-               randomOfCombin(42, 6 [n1,n2,n3,n4,n5,n6]) + \
-               randomOfCombin( 6, 1 [nc])
-def randomOf6aus49(n1,n2,n3,n4,n5,n6, nc):
-       return \
-               randomOfCombin(49, 6, [n1,n2,n3,n4,n5,n6]) + \
-               randomOfCombin(10, 1, [nc])
+       randomIntegerOfBits(n, bs) retourne le premier entier i formé par les bits bs
+       qui a le potentiel d’atteindre un entier dans [0 .. n-1],
+       ou recommence en ignorant le premier bit si n <= i.
+       """
+       n = int(n)
+       if bs == None:
+               return None
+       #print("randomIntegerOfBits: "+str((n, bs)))
+       enough = bitSize(str(n - 1))
+       #print("randomIntegerOfBits: enough="+str(enough))
+       while True:
+               bits  = bs[0:enough]
+               given = len(bits)
+               bs_   = bs[enough:]
+               i     = inBase(2, bits)
+               if given < enough:
+                       return None
+               if n <= i:
+                       bs = bits[1:] + bs_
+               else:
+                       return str(i)
 
-g_exportedScripts = MakeDraw,
+g_exportedScripts = ()