@mcmcjs/julia 0.6.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/fitlib.jl +94 -6
- package/dist/index.cjs +190 -56
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +21 -7
- package/dist/index.d.ts +21 -7
- package/dist/index.js +169 -36
- package/dist/index.js.map +1 -1
- package/dist/julia-env/Manifest.toml +1895 -0
- package/dist/julia-env/Project.toml +10 -0
- package/dist/worker.jl +8 -1
- package/package.json +3 -3
package/dist/fitlib.jl
CHANGED
|
@@ -96,6 +96,21 @@ end
|
|
|
96
96
|
|
|
97
97
|
to_namedtuple(data) = (; (Symbol(k) => v for (k, v) in data)...)
|
|
98
98
|
|
|
99
|
+
# Binds the data so a model can read a variable as a property (`data.y`) or by
|
|
100
|
+
# index (`data["y"]` / `data[:y]`), with `haskey`/`keys` supported, so models
|
|
101
|
+
# written in either idiom run unchanged. The underlying NamedTuple is reached
|
|
102
|
+
# via getfield to avoid colliding with a data variable also named `nt`.
|
|
103
|
+
struct ModelData
|
|
104
|
+
nt::NamedTuple
|
|
105
|
+
end
|
|
106
|
+
Base.getproperty(d::ModelData, k::Symbol) = getproperty(getfield(d, :nt), k)
|
|
107
|
+
Base.getindex(d::ModelData, k::Symbol) = getproperty(getfield(d, :nt), k)
|
|
108
|
+
Base.getindex(d::ModelData, k::AbstractString) = getproperty(getfield(d, :nt), Symbol(k))
|
|
109
|
+
Base.haskey(d::ModelData, k::Symbol) = haskey(getfield(d, :nt), k)
|
|
110
|
+
Base.haskey(d::ModelData, k::AbstractString) = haskey(getfield(d, :nt), Symbol(k))
|
|
111
|
+
Base.keys(d::ModelData) = keys(getfield(d, :nt))
|
|
112
|
+
Base.propertynames(d::ModelData) = propertynames(getfield(d, :nt))
|
|
113
|
+
|
|
99
114
|
# JuliaBUGS requires dense arrays with a concrete numeric eltype, but JSON parses
|
|
100
115
|
# them as Vector{Any} (and nested arrays as vectors of vectors). narrow promotes a
|
|
101
116
|
# numeric array to a concrete eltype and stacks equal-sized nested arrays into a
|
|
@@ -140,14 +155,83 @@ function chains_from_wire(wire, targets)
|
|
|
140
155
|
return MCMCChains.Chains(arr, syms, Dict(:parameters => syms); info = (; varname_to_symbol = vmap))
|
|
141
156
|
end
|
|
142
157
|
|
|
158
|
+
# Expand one sampler transition to canonical scalar leaves (theta -> theta[1], ...),
|
|
159
|
+
# the same names the final samples file uses, so streamed batches reconstruct it.
|
|
160
|
+
function flatten_draw(transition)
|
|
161
|
+
out = Pair{String,Float64}[]
|
|
162
|
+
for (vn, val) in pairs(transition.params)
|
|
163
|
+
for (leaf, leafval) in Turing.DynamicPPL.varname_and_value_leaves(vn, val)
|
|
164
|
+
push!(out, string(leaf) => Float64(leafval))
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
return out
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# A per-draw callback that emits {"mcmcjs":"draws",...} batches on PROGRESS_IO as
|
|
171
|
+
# sampling proceeds. AbstractMCMC passes the 1-based chain index as `chain_number`;
|
|
172
|
+
# each batch carries a per-chain monotonic seq, and a chain's batches concatenate
|
|
173
|
+
# (by leaf name) to the final samples file. Assumes the callback is not invoked
|
|
174
|
+
# concurrently across chains (true for the MCMCSerial path build_and_sample uses).
|
|
175
|
+
function draw_streamer(draws_per_chain::Int, batch_size::Int)
|
|
176
|
+
chain = Ref(-1)
|
|
177
|
+
seq = Ref(0)
|
|
178
|
+
names = String[]
|
|
179
|
+
buffer = Dict{String,Vector{Float64}}()
|
|
180
|
+
filled = Ref(0)
|
|
181
|
+
function emit(iteration)
|
|
182
|
+
filled[] == 0 && return
|
|
183
|
+
cols = Dict{String,Vector{Float64}}(n => copy(buffer[n]) for n in names)
|
|
184
|
+
println(
|
|
185
|
+
PROGRESS_IO[],
|
|
186
|
+
JSON.json(
|
|
187
|
+
Dict(
|
|
188
|
+
"mcmcjs" => "draws",
|
|
189
|
+
"chain" => chain[],
|
|
190
|
+
"seq" => seq[],
|
|
191
|
+
"iteration" => iteration,
|
|
192
|
+
"draws" => cols,
|
|
193
|
+
),
|
|
194
|
+
),
|
|
195
|
+
)
|
|
196
|
+
flush(PROGRESS_IO[])
|
|
197
|
+
seq[] += 1
|
|
198
|
+
for n in names
|
|
199
|
+
empty!(buffer[n])
|
|
200
|
+
end
|
|
201
|
+
filled[] = 0
|
|
202
|
+
end
|
|
203
|
+
return function (_rng, _model, _sampler, transition, _state, iteration; chain_number = 1, _kwargs...)
|
|
204
|
+
c = Int(chain_number) - 1
|
|
205
|
+
if c != chain[]
|
|
206
|
+
chain[] = c
|
|
207
|
+
seq[] = 0
|
|
208
|
+
end
|
|
209
|
+
leaves = flatten_draw(transition)
|
|
210
|
+
if isempty(names)
|
|
211
|
+
for (n, _) in leaves
|
|
212
|
+
push!(names, n)
|
|
213
|
+
buffer[n] = Float64[]
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
for (n, v) in leaves
|
|
217
|
+
push!(get!(buffer, n, Float64[]), v)
|
|
218
|
+
end
|
|
219
|
+
filled[] += 1
|
|
220
|
+
if filled[] >= batch_size || iteration >= draws_per_chain
|
|
221
|
+
emit(iteration)
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
143
226
|
# Build the model and sample in one call so both run in the latest world age:
|
|
144
227
|
# the model methods are defined by `include` at runtime, so a plain call from
|
|
145
228
|
# this (older) world would fail with "method too new". invokelatest fixes that.
|
|
146
|
-
# Returns Turing's default chain type, a FlexiChain.
|
|
147
|
-
function build_and_sample(entry, data, sampler, draws, chains, rng)
|
|
229
|
+
# Returns Turing's default chain type, a FlexiChain. With a callback, draws stream.
|
|
230
|
+
function build_and_sample(entry, data, sampler, draws, chains, rng; callback = nothing)
|
|
148
231
|
model = entry(data)
|
|
232
|
+
kw = callback === nothing ? NamedTuple() : (; callback)
|
|
149
233
|
return Logging.with_logger(JSONProgressLogger()) do
|
|
150
|
-
sample(rng, model, sampler, MCMCSerial(), draws, chains; progress = true)
|
|
234
|
+
sample(rng, model, sampler, MCMCSerial(), draws, chains; progress = true, kw...)
|
|
151
235
|
end
|
|
152
236
|
end
|
|
153
237
|
|
|
@@ -251,7 +335,7 @@ function handle_request(request)
|
|
|
251
335
|
|
|
252
336
|
wire = if mode == "predict"
|
|
253
337
|
backend == "turing" || error("predict is not yet supported for backend $backend")
|
|
254
|
-
model = Base.invokelatest(entry, predict_namedtuple(request["data"]))
|
|
338
|
+
model = Base.invokelatest(entry, ModelData(predict_namedtuple(request["data"])))
|
|
255
339
|
stage = "load_samples"
|
|
256
340
|
rchn = chains_from_wire(JSON.parsefile(request["samples"]), request["predict"]["targets"])
|
|
257
341
|
stage = "predict"
|
|
@@ -260,7 +344,7 @@ function handle_request(request)
|
|
|
260
344
|
error("no variables predicted; check that [predict].targets name unconditioned outcomes")
|
|
261
345
|
chains_to_wire(pp)
|
|
262
346
|
else
|
|
263
|
-
data = backend == "juliabugs" ? bugs_namedtuple(request["data"]) : to_namedtuple(request["data"])
|
|
347
|
+
data = backend == "juliabugs" ? bugs_namedtuple(request["data"]) : ModelData(to_namedtuple(request["data"]))
|
|
264
348
|
sampler, warmup = build_sampler(request["sampler"], backend)
|
|
265
349
|
draws = Int(request["sampler"]["draws"])
|
|
266
350
|
chains = Int(get(request["sampler"], "chains", 4))
|
|
@@ -270,7 +354,11 @@ function handle_request(request)
|
|
|
270
354
|
chn = Base.invokelatest(sample_bugs, model, sampler, warmup, draws, chains, rng)
|
|
271
355
|
chains_to_wire(chn)
|
|
272
356
|
else
|
|
273
|
-
|
|
357
|
+
cb = get(request, "stream_draws", false) ?
|
|
358
|
+
draw_streamer(draws, Int(get(request, "draw_batch_size", 25))) : nothing
|
|
359
|
+
chn = Base.invokelatest(
|
|
360
|
+
build_and_sample, entry, data, sampler, draws, chains, rng; callback = cb,
|
|
361
|
+
)
|
|
274
362
|
vnchain_to_wire(chn)
|
|
275
363
|
end
|
|
276
364
|
end
|