supp-ran.lmt /size: 6981 b    last modification: 2021-10-28 13:51
1if not modules then modules = { } end modules ['supp-ran'] = {
2    version   = 1.001,
3    comment   = "companion to supp-ran.mkiv",
4    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5    copyright = "PRAGMA ADE / ConTeXt Development Team",
6    license   = "see context related readme files"
7}
8
9-- We cannot ask for the current seed, so we need some messy hack here.
10
11local report_system = logs.reporter("system","randomizer")
12
13local trace_random  = false  trackers.register("system.randomizer",         function(v) trace_random = v end)
14local trace_details = false  trackers.register("system.randomizer.details", function(v) trace_random = v trace_details = v end)
15
16local insert, remove = table.insert, table.remove
17
18local tonumber   = tonumber
19local sub        = string.sub
20local math       = math
21local context    = context
22local implement  = interfaces.implement
23
24local random     = math.random
25local randomseed = math.randomseed
26local round      = math.round
27local stack      = { }
28local last       = 1
29local maxcount   = 0x3FFFFFFF -- 2^30-1
30
31math.random = function(...)
32    local n = random(...)
33    if trace_details then
34        report_system("math %s",n)
35    end
36    return n
37end
38
39local function setrandomseedi(n)
40    if n <= 1 then
41        n = n * maxcount
42    elseif n < 1000 then
43        n = n * 1000
44    end
45    n = round(n)
46    randomseed(n)
47    last = random(0,maxcount) -- we need an initial value
48    if trace_details then
49        report_system("seed %s from %s",last,n)
50    elseif trace_random then
51        report_system("setting seed %s",n)
52    end
53end
54
55math.setrandomseedi = setrandomseedi
56
57local function getrandomnumber(min,max)
58    if min > max then
59        min, max = max, min
60    end
61    last = random(min,max)
62    if trace_details then
63        report_system("number %s",last)
64    end
65    return last
66end
67
68local function setrandomseed(n)
69    last = n
70    setrandomseedi(n)
71end
72
73local function getrandomseed()
74    return last
75end
76
77-- local function getmprandomnumber()
78--     last = random(0,4095)
79--     if trace_details then
80--         report_system("mp number %s",last)
81--     end
82--     return last
83-- end
84
85-- maybe stack
86
87local function pushrandomseed()
88 -- insert(stack,last) -- doesn't work okay
89    insert(stack,randomseed(last) or last)
90    if trace_random or trace_details then
91        report_system("pushing seed %s",last)
92    end
93end
94
95local function reuserandomseed(n)
96    local seed = stack[#stack]
97    if seed then
98        if trace_random or trace_details then
99            report_system("reusing seed %s",last)
100        end
101        randomseed(seed)
102    end
103end
104
105local function poprandomseed()
106    local seed = remove(stack)
107    if seed then
108        if trace_random or trace_details then
109            report_system("popping seed %s",seed)
110        end
111        randomseed(seed)
112    end
113end
114
115local function getrandom(where,...)
116    if type(where) == "string" then
117        local n = random(...)
118        if trace_details then
119            report_system("%s %s",where,n)
120        end
121        return n
122    else
123        local n = random(where,...)
124        if trace_details then
125            report_system("utilities %s",n)
126        end
127        return n
128    end
129end
130
131-- todo: also open up in utilities.randomizer.*
132
133implement { name = "getrandomnumber", actions = { getrandomnumber, context }, arguments = { "integer", "integer" } }
134implement { name = "getrandomdimen",  actions = { getrandomnumber, context }, arguments = { "dimen", "dimen" } }
135implement { name = "getrandomfloat",  actions = { getrandomnumber, context }, arguments = { "number", "number" } }
136implement { name = "getrandomseed",   actions = { getrandomseed,   context } }
137implement { name = "setrandomseed",   actions = setrandomseed, arguments = "integer" }
138implement { name = "pushrandomseed",  actions = pushrandomseed, public = true, }
139implement { name = "poprandomseed",   actions = poprandomseed, public = true, }
140implement { name = "reuserandomseed", actions = reuserandomseed, public = true, }
141
142-- fun stuff
143
144local newrepeatable, getrepeatable, getrepeatableseed, repeatable
145
146do
147
148    local default = environment.version or "context lmtx"
149    local hashed  = md5.HEX
150    ----- hashed  = sha2.HASH256
151    local list    = { }
152    local saved   = false
153
154    newrepeatable = function(name,seed)
155        if not name or name == "" then
156            name = "default"
157            seed = default
158        elseif not seed then
159            seed = default
160        end
161        if not saved then
162            saved = { }
163            job.variables.collected.repeatable = saved
164        end
165        saved[name] = seed
166        local hash = hashed(seed)
167        if trace_random then
168            report_system("repeatable %a with seed %a starts out as %a",name,seed,hash)
169        end
170        local func = function()
171            local n = tonumber(sub(hash,1,8),16)
172         -- local n = tonumber(sub(hash,1,15),16)
173            local r = n / 0xFFFFFFFF
174         -- local r = n / 0xFFFFFFFFFFFFFFF
175            hash = hashed(hash)
176            if trace_details then
177                report_system("repeatable %a moves on to %a giving %i and %0.9f",name,hash,n,r)
178            end
179            return r
180        end
181        list[name] = func
182        -- we need to delay this till we have job available
183        -- but we seldom call this so it's okay
184        return func
185    end
186
187    table.setmetatableindex(list,function(t,k)
188        local v = rawget(t,"default")
189        if not v then
190            v = newrepeatable("default",default)
191        end
192        t[k] = v
193        return v
194    end)
195
196    getrepeatable = function(name)
197        return list[name or "default"]
198    end
199
200    repeatable = function(name)
201        return list[name or "default"]()
202    end
203
204    getrepeatableseed = function(name)
205        local r = job.variables.collected.repeatable
206        return r and r[name or "default"] or default
207    end
208
209    implement {
210        name      = "newrepeatablerandom",
211        public    = true,
212        protected = true,
213        arguments = { "csnameunchecked", "argument" },
214        actions   = function(c,s)
215         -- local c = tokens.scanners.csname(true)
216         -- local s = tokens.scanners.argument()
217            implement {
218                name    = c,
219                public  = true,
220                actions = { newrepeatable(c,s), context },
221            }
222        end
223    }
224
225end
226
227-- public
228
229utilities.randomizer = {
230    setseedi          = setrandomseedi,
231    getnumber         = getrandomnumber,
232    setseed           = setrandomseed,
233    getseed           = getrandomseed,
234 -- getmpnumber       = getmprandomnumber,
235    pushseed          = pushrandomseed,
236    reuseseed         = reuserandomseed,
237    popseed           = poprandomseed,
238    get               = getrandom,
239    -- the original, only for testing
240 -- mathrandom        = random,
241    newrepeatable     = newrepeatable,
242    getrepeatable     = getrepeatable,
243    getrepeatableseed = getrepeatableseed,
244    repeatable        = repeatable,
245}
246