• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

wexlergroup / FreeBird.jl / 24726273802

21 Apr 2026 01:52PM UTC coverage: 83.942% (+0.8%) from 83.174%
24726273802

push

github

web-flow
Merge pull request #128 from wexlergroup/feature/lattice-geometric-cluster-moves

Add geometric cluster moves for lattice systems

130 of 132 new or added lines in 4 files covered. (98.48%)

3 existing lines in 1 file now uncovered.

1861 of 2217 relevant lines covered (83.94%)

61284.88 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

73.3
/src/SamplingSchemes/nested_sampling.jl
1
"""
2
    mutable struct NestedSamplingParameters <: SamplingParameters
3

4
The `NestedSamplingParameters` struct represents the parameters used in the nested sampling scheme.
5

6
# Fields
7
- `mc_steps::Int64`: The number of total Monte Carlo moves to perform. For a parallel MC routine, this number will be distributed among workers.
8
If `mc_steps` is not divisible by the number of workers, the actual number of MC moves per worker will be `ceil(mc_steps / nworkers())`.
9
- `initial_step_size::Float64`: The initial step size, which is the fallback step size if MC routine fails to accept a move.
10
- `step_size::Float64`: The on-the-fly step size used in the sampling process.
11
- `step_size_lo::Float64`: The lower bound of the step size.
12
- `step_size_up::Float64`: The upper bound of the step size.
13
- `accept_range::Tuple{Float64, Float64}`: The range of acceptance rates for adjusting the step size.
14
e.g. (0.25, 0.75) means that the step size will decrease if the acceptance rate is below 0.25 and increase if it is above 0.75.
15
- `fail_count::Int64`: The number of failed MC moves in a row.
16
- `allowed_fail_count::Int64`: The maximum number of failed MC moves allowed before resetting the step size.
17
- `energy_perturbation::Float64`: The perturbation value used to adjust the energy of the walkers.
18
- `random_seed::Int64`: The seed for the random number generator.
19
- `cluster_p::Float64`: Current cluster growth probability for geometric cluster moves (mutable runtime state).
20
- `cluster_accepted::Float64`: Accepted cluster moves in the current adjustment window.
21
- `cluster_total::Float64`: Total cluster moves attempted in the current adjustment window.
22
- `cluster_p_history::Vector{Float64}`: Trajectory of cluster_p values after each adaptive adjustment.
23
- `cluster_accept_history::Vector{Float64}`: Acceptance rate at each adaptive adjustment.
24
- `cluster_adjust_iterations::Vector{Int}`: NS iteration index at each adaptive adjustment.
25
"""
26
mutable struct NestedSamplingParameters <: SamplingParameters
27
    mc_steps::Int64
60✔
28
    initial_step_size::Float64
29
    step_size::Float64
30
    step_size_lo::Float64
31
    step_size_up::Float64
32
    accept_range::Tuple{Float64, Float64}
33
    fail_count::Int64
34
    allowed_fail_count::Int64
35
    energy_perturbation::Float64
36
    random_seed::Int64
37
    cluster_p::Float64
38
    cluster_accepted::Float64
39
    cluster_total::Float64
40
    cluster_p_history::Vector{Float64}
41
    cluster_accept_history::Vector{Float64}
42
    cluster_adjust_iterations::Vector{Int}
43
end
44

45
function NestedSamplingParameters(;
75✔
46
            mc_steps::Int64=200,
47
            initial_step_size::Float64=0.01,
48
            step_size::Float64=0.1,
49
            step_size_lo::Float64=1e-6,
50
            step_size_up::Float64=1.0,
51
            accept_range::Tuple{Float64, Float64}=(0.25, 0.75),
52
            fail_count::Int64=0,
53
            allowed_fail_count::Int64=100,
54
            energy_perturbation::Float64=1e-12,
55
            random_seed::Int64=1234,
56
            cluster_p::Float64=0.3,
57
            cluster_accepted::Float64=0.0,
58
            cluster_total::Float64=0.0,
59
            cluster_p_history::Vector{Float64}=Float64[],
60
            cluster_accept_history::Vector{Float64}=Float64[],
61
            cluster_adjust_iterations::Vector{Int}=Int[],
62
            )
63
    NestedSamplingParameters(mc_steps, initial_step_size, step_size, step_size_lo, step_size_up, accept_range, fail_count, allowed_fail_count, energy_perturbation, random_seed, cluster_p, cluster_accepted, cluster_total, cluster_p_history, cluster_accept_history, cluster_adjust_iterations)
60✔
64
end
65

66
"""
67
    LatticeNestedSamplingParameters(;
68
            mc_steps::Int64=100,
69
            energy_perturbation::Float64=1e-12,
70
            fail_count::Int64=0,
71
            allowed_fail_count::Int64=10,
72
            random_seed::Int64=1234,
73
            cluster_p::Float64=0.3,
74
            )
75
A convenience constructor for `NestedSamplingParameters` with default values suitable for lattice systems.
76
"""
77
function LatticeNestedSamplingParameters(;
50✔
78
            mc_steps::Int64=100,
79
            energy_perturbation::Float64=1e-12,
80
            fail_count::Int64=0,
81
            allowed_fail_count::Int64=10,
82
            random_seed::Int64=1234,
83
            cluster_p::Float64=0.3,
84
            )
85
    NestedSamplingParameters(mc_steps=mc_steps, fail_count=fail_count, allowed_fail_count=allowed_fail_count, energy_perturbation=energy_perturbation, random_seed=random_seed, cluster_p=cluster_p)
40✔
86
end
87

88

89
"""
90
    abstract type MCRoutine
91

92
An abstract type representing a Monte Carlo routine.
93

94
Currently, the following concrete types are supported:
95
- `MCRandomWalkMaxE`: A type for generating a new walker by performing a random walk for decorrelation on the
96
highest-energy walker.
97
- `MCRandomWalkClone`: A type for generating a new walker by cloning an existing walker and performing a random walk
98
for decorrelation.
99
- `MCNewSample`: A type for generating a new walker from a random configuration. Currently, it is intended to use 
100
this routine for lattice gas systems.
101
- `MCMixedMoves`: A type for generating a new walker by performing random walks and swapping atoms. Currently, it is
102
intended to use this routine for multi-component systems. The actual number of random walks and swaps to perform is
103
determined by the weights of the fields `walks_freq` and `swaps_freq`. See [`MCMixedMoves`](@ref).
104
- `MCRejectionSampling`: A type for generating a new walker by performing rejection sampling. Currently, it is intended
105
to use this routine for lattice gas systems.
106
- `MCDistributed`: A type for generating new walkers by performing random walks for decorrelation in parallel using Distributed.jl.
107
This routine supports multiple culling walkers and multiple decorrelation walkers. See [`MCDistributed`](@ref).
108
"""
109
abstract type MCRoutine end
110

111
"""
112
    abstract type MCRoutineParallel <: MCRoutine
113
(Internal) An abstract type representing a parallel Monte Carlo routine.
114
"""
115
abstract type MCRoutineParallel <: MCRoutine end
116

117
"""
118
    struct MCRandomWalkMaxE <: MCRoutine
119
A type for generating a new walker by performing a random walk for decorrelation on the highest-energy walker.
120
"""
121
struct MCRandomWalkMaxE <: MCRoutine 
122
    dims::Vector{Int64}
123
    function MCRandomWalkMaxE(dims::Vector{Int64}=[1, 2, 3])
65✔
124
        new(dims)
104✔
125
    end
126
end
127

128
"""
129
    struct MCRandomWalkClone <: MCRoutine
130
A type for generating a new walker by cloning an existing walker and performing a random walk for decorrelation.
131
"""
132
struct MCRandomWalkClone <: MCRoutine 
133
    dims::Vector{Int64}
134
    function MCRandomWalkClone(;dims::Vector{Int64}=[1, 2, 3])
45✔
135
        new(dims)
36✔
136
    end
137
end
138

139
"""
140
    struct MCRandomWalkCloneParallel <: MCRoutineParallel
141
A type for generating a new walker by cloning an existing walker and performing a random walk for decorrelation in parallel.
142
"""
143
struct MCRandomWalkCloneParallel <: MCRoutineParallel
144
    dims::Vector{Int64}
145
    function MCRandomWalkCloneParallel(;dims::Vector{Int64}=[1, 2, 3])
15✔
146
        new(dims)
12✔
147
    end
148
end
149

150
"""
151
    struct MCDistributed <: MCRoutineParallel
152
A type for generating new walkers by performing random walks for decorrelation in parallel using Distributed.jl.
153
# Fields
154
- `n_cull::Int64`: The number of lowest-energy walkers to cull (replace) in each iteration. The default is 1.
155
- `n_decorr::Int64`: The number of walkers to use for decorrelation (random walks). The default is `nworkers() - 1`.
156
- `dims::Vector{Int64}`: The dimensions along which to perform the random walks.
157
"""
158
struct MCDistributed <: MCRoutineParallel
159
    n_cull::Int64
160
    n_decorr::Int64
161
    dims::Vector{Int64}
162
    function MCDistributed(;n_cull::Int64=1, n_decorr::Int64=nworkers()-1, dims::Vector{Int64}=[1, 2, 3])
×
163
        if n_cull + n_decorr != nworkers()
×
164
            error("n_cull + n_decorr must be equal to the number of workers: $(nworkers())")
×
165
        end
166
        @info "Distributed nested sampling initiated: n_cull: $n_cull, n_decorr: $n_decorr, total workers: $(n_cull + n_decorr)"
×
167
        new(n_cull, n_decorr, dims)
×
168
    end
169
end
170

171
"""
172
    MCRandomWalkMaxEParallel <: MCRoutineParallel
173
A type for generating a new walker by performing a random walk for decorrelation on the highest-energy walker(s) in parallel.
174
"""
175
struct MCRandomWalkMaxEParallel <: MCRoutineParallel
176
    dims::Vector{Int64}
177
    function MCRandomWalkMaxEParallel(;dims::Vector{Int64}=[1, 2, 3])
15✔
178
        new(dims)
12✔
179
    end
180
end
181

182
"""
183
    struct MCNewSample <: MCRoutine
184
A type for generating a new walker from a random configuration. Currently, it is intended to use this routine for lattice gas systems.
185
"""
186
struct MCNewSample <: MCRoutine end
24✔
187

188
"""
189
    struct MCMixedMoves <: MCRoutine
190
A type for generating a new walker by performing a mix of random walks, atom swaps, and/or geometric cluster moves.
191
For atomistic systems, the `walks_freq` and `swaps_freq` fields control the ratio of random walks to atom swaps.
192
For lattice systems, `walks_freq` and `clusters_freq` control the ratio of local swap moves to geometric cluster moves.
193

194
# Fields
195
- `walks_freq::Int`: The frequency of random walks (atomistic) or local swaps (lattice) to perform.
196
- `swaps_freq::Int`: The frequency of atom swaps to perform (atomistic only).
197
- `clusters_freq::Int`: The frequency of geometric cluster moves to perform (lattice only, default 0).
198
- `initial_cluster_p::Float64`: Starting growth probability for cluster moves (default 0.3).
199
- `target_cluster_accept::Float64`: Target acceptance rate for adaptive cluster p tuning (default 0.3).
200
- `cluster_adjust_interval::Int`: Number of NS iterations between cluster p adjustments (default 50).
201
- `cluster_p_floor::Float64`: Lower bound for adaptive cluster p (default 0.01).
202
- `cluster_p_ceiling::Float64`: Upper bound for adaptive cluster p (default 1.0).
203
"""
204
mutable struct MCMixedMoves <: MCRoutine
205
    walks_freq::Int
40✔
206
    swaps_freq::Int
207
    clusters_freq::Int
208
    initial_cluster_p::Float64
209
    target_cluster_accept::Float64
210
    cluster_adjust_interval::Int
211
    cluster_p_floor::Float64
212
    cluster_p_ceiling::Float64
213
end
214

215
# Backward-compatible constructor: MCMixedMoves(5, 1)
216
function MCMixedMoves(walks_freq::Int, swaps_freq::Int)
16✔
217
    MCMixedMoves(walks_freq, swaps_freq, 0, 0.3, 0.3, 50, 0.01, 1.0)
16✔
218
end
219

220
# Keyword constructor for lattice use
221
function MCMixedMoves(;
30✔
222
    walks_freq::Int=1,
223
    swaps_freq::Int=0,
224
    clusters_freq::Int=1,
225
    initial_cluster_p::Float64=0.3,
226
    target_cluster_accept::Float64=0.3,
227
    cluster_adjust_interval::Int=50,
228
    cluster_p_floor::Float64=0.01,
229
    cluster_p_ceiling::Float64=1.0,
230
)
231
    MCMixedMoves(walks_freq, swaps_freq, clusters_freq,
24✔
232
                 initial_cluster_p, target_cluster_accept, cluster_adjust_interval,
233
                 cluster_p_floor, cluster_p_ceiling)
234
end
235

236
"""
237
    struct MCMixedMovesParallel <: MCRoutineParallel
238
A type for generating a new walker by performing random walks and swapping atoms in parallel. Currently, it is intended to use this routine for
239
multi-component systems. The actual number of random walks and swaps to perform is determined by the weights of the fields `walks_freq` and `swaps_freq`.
240
For example, if `walks_freq=4` and `swaps_freq=1`, then the probability of performing a random walk is 4/5, and the probability of performing a swap is 1/5.
241

242
# Fields
243
- `walks_freq::Int`: The frequency of random walks to perform.
244
- `swaps_freq::Int`: The frequency of atom swaps to perform.
245
"""
246
mutable struct MCMixedMovesParallel <: MCRoutineParallel
247
    walks_freq::Int
248
    swaps_freq::Int
249
end
250

251
"""
252
    struct MCRejectionSampling <: MCRoutine
253
A type for generating a new walker by performing rejection sampling. Currently, it is intended to use this routine for lattice gas systems.
254
"""
255
struct MCRejectionSampling <: MCRoutine end
4✔
256

257
"""
258
    sort_by_energy!(liveset::LJAtomWalkers)
259

260
Sorts the walkers in the liveset by their energy in descending order.
261

262
# Arguments
263
- `liveset::LJAtomWalkers`: The liveset of walkers to be sorted.
264

265
# Returns
266
- `liveset::LJAtomWalkers`: The sorted liveset.
267
"""
268
function sort_by_energy!(liveset::AbstractLiveSet)
420✔
269
    sort!(liveset.walkers, by = x -> x.energy, rev=true)
2,218✔
270
    # println("after sort ats[1].system_data.energy: ", ats[1].system_data.energy)
271
    return liveset
420✔
272
end
273

274
"""
275
    update_iter!(liveset::AtomWalkers)
276

277
Update the iteration count for each walker in the liveset.
278

279
# Arguments
280
- `liveset::AtomWalkers`: The set of walkers to update.
281

282
"""
283
function update_iter!(liveset::AbstractLiveSet)
390✔
284
    for at in liveset.walkers
391✔
285
        at.iter += 1
1,416✔
286
    end
1,416✔
287
end
288

289
"""
290
    estimate_temperature(n_walker::Int, n_cull::Int, ediff::Float64)
291
Estimate the temperature for the nested sampling algorithm from dlog(ω)/dE.
292
"""
293
function estimate_temperature(n_walkers::Int, n_cull::Int, ediff::Float64, iter::Int=1)
×
294
    ω = (n_cull / (n_walkers + n_cull)) * (n_walkers / (n_walkers + n_cull))^iter
×
295
    β = log(ω) / ediff
×
296
    kb = 8.617333262145e-5 # eV/K
×
297
    T = 1 / (kb * β) # in Kelvin
×
298
    return T
×
299
end
300

301

302
"""
303
    nested_sampling_step!(liveset::AtomWalkers, ns_params::NestedSamplingParameters, mc_routine::MCRoutine)
304

305
Perform a single step of the nested sampling algorithm using the Monte Carlo random walk routine.
306

307
# Arguments
308
- `liveset::AtomWalkers`: The set of atom walkers.
309
- `ns_params::NestedSamplingParameters`: The parameters for nested sampling.
310
- `mc_routine::MCRoutine`: The Monte Carlo routine for generating new samples. See [`MCRoutine`](@ref).
311

312
# Returns
313
- `iter`: The iteration number after the step.
314
- `emax`: The highest energy recorded during the step.
315
- `liveset`: The updated set of atom walkers.
316
- `ns_params`: The updated nested sampling parameters.
317
"""
318
function nested_sampling_step!(liveset::AtomWalkers, ns_params::NestedSamplingParameters, mc_routine::MCRoutine; ns_iteration::Int=0)
224✔
319
    sort_by_energy!(liveset)
112✔
320
    ats = liveset.walkers
112✔
321
    lj = liveset.potential
112✔
322
    iter::Union{Missing,Int} = missing
112✔
323
    emax::Union{Missing,typeof(0.0u"eV")} = liveset.walkers[1].energy
112✔
324
    if mc_routine isa MCRandomWalkMaxE
112✔
325
        to_walk = deepcopy(ats[1])
192✔
326
    elseif mc_routine isa MCRandomWalkClone
16✔
327
        to_walk = deepcopy(rand(ats[2:end]))
24✔
328
    else
329
        error("Unsupported MCRoutine type: $mc_routine")
4✔
330
    end
331
    if length(mc_routine.dims) == 3
108✔
332
        accept, rate, at = MC_random_walk!(ns_params.mc_steps, to_walk, lj, ns_params.step_size, emax)
100✔
333
    elseif length(mc_routine.dims) == 2
8✔
334
        accept, rate, at = MC_random_walk_2D!(ns_params.mc_steps, to_walk, lj, ns_params.step_size, emax; dims=mc_routine.dims)
4✔
335
        # @info "Doing a 2D random walk"
336
    elseif length(mc_routine.dims) == 1
4✔
337
        error("Unsupported dimensions: $(mc_routine.dims)")
4✔
338
    end
339
    # accept, rate, at = MC_random_walk!(ns_params.mc_steps, to_walk, lj, ns_params.step_size, emax)
340
    # @info "iter: $(liveset.walkers[1].iter), acceptance rate: $(round(rate; sigdigits=4)), emax: $(round(typeof(1.0u"eV"), emax; sigdigits=10)), is_accepted: $accept, step_size: $(round(ns_params.step_size; sigdigits=4))"
341
    if accept
104✔
342
        push!(ats, at)
95✔
343
        popfirst!(ats)
95✔
344
        update_iter!(liveset)
95✔
345
        ns_params.fail_count = 0
95✔
346
        iter = liveset.walkers[1].iter
95✔
347
    else
348
        # @warn "Failed to accept MC move"
349
        emax = missing
9✔
350
        ns_params.fail_count += 1
9✔
351
    end
352
    adjust_step_size(ns_params, rate)
176✔
353
    return iter, emax, liveset, ns_params
104✔
354
end
355

356
"""
357
    nested_sampling_step!(liveset::AtomWalkers, ns_params::NestedSamplingParameters, mc_routine::MCRoutineParallel)
358
Perform a single step of the nested sampling algorithm using the parallel Monte Carlo random walk routine.
359
# Arguments
360
- `liveset::AtomWalkers`: The set of atom walkers.
361
- `ns_params::NestedSamplingParameters`: The parameters for nested sampling.
362
- `mc_routine::MCRoutineParallel`: The parallel Monte Carlo routine for generating new samples. See [`MCRoutineParallel`](@ref).
363
# Returns
364
- `iter`: The iteration number after the step.
365
- `emax`: The highest energy recorded during the step.
366
- `liveset`: The updated set of atom walkers.
367
- `ns_params`: The updated nested sampling parameters.
368
"""
NEW
369
function nested_sampling_step!(liveset::AtomWalkers, ns_params::NestedSamplingParameters, mc_routine::MCDistributed; ns_iteration::Int=0)
×
370
    sort_by_energy!(liveset)
×
371
    ats = liveset.walkers
×
372
    lj = liveset.potential
×
373
    iter::Union{Missing,Int} = missing
×
374
    emax::Union{Vector{Missing},Vector{typeof(0.0u"eV")}} = [liveset.walkers[i].energy for i in 1:nworkers()]
×
375

376
    to_walk_inds = sample(2:length(ats), nworkers(); replace=false)
×
377
    # println("to_walk_inds: ", to_walk_inds) # DEBUG
378
    
379
    to_walks = deepcopy.(ats[to_walk_inds])
×
380

381
    if length(mc_routine.dims) == 3
×
382
        random_walk_function = MC_random_walk!
×
383
    elseif length(mc_routine.dims) == 2
×
384
        random_walk_function = MC_random_walk_2D!
×
385
    else
386
        error("Unsupported dimensions: $(mc_routine.dims)")
×
387
    end
388

389
    mc_steps_per_worker = ceil(Int, ns_params.mc_steps / nworkers()) # distribute the total MC steps among workers
×
390

391
    walking = [remotecall(random_walk_function, workers()[i], mc_steps_per_worker, to_walk, lj, ns_params.step_size, emax[mc_routine.n_cull]) for (i,to_walk) in enumerate(to_walks)]
×
392
    walked = fetch.(walking)
×
393
    finalize.(walking) # finalize the remote calls, clear the memory
×
394

395
    accepted_rates = [x[2] for x in walked]
×
396
    rate = mean(accepted_rates)
×
397

398
    # sort!(walked, by = x -> x[3].energy, rev=true)
399
    # filter!(x -> x[1], walked) # remove the failed ones
400
    accepted_inds = findall(x -> x[1]==1, walked)
×
401

402
    if length(accepted_inds) < mc_routine.n_cull # if not enough accepted walkers
×
403
        ns_params.fail_count += 1
×
404
        emax = [missing]
×
405
        return iter, emax[end], liveset, ns_params
×
406
    else
407
        # pick one from the accepted ones
408
        picked = sample(accepted_inds, mc_routine.n_cull; replace=false)
×
409
        for (i, ind) in enumerate(picked)
×
410
            ats[i] = walked[ind][3]
×
411
        end
×
412
        # println("picked: ", picked) # DEBUG
413
        # remove the picked one from accepted_inds
414
        filter!(x -> x ∉ picked, accepted_inds)
×
415
        # println("remaining accepted_inds: ", accepted_inds) # DEBUG
416

417
        if !isempty(accepted_inds)
×
418
            for i in accepted_inds
×
419
                ats[to_walk_inds[i]] = walked[i][3]
×
420
                # println("Updating ats at index $(to_walk_inds[i])") # DEBUG
421
            end
×
422
        end
423
    end
424

425
    update_iter!(liveset)
×
426
    ns_params.fail_count = 0
×
427
    iter = liveset.walkers[1].iter
×
428

429
    adjust_step_size(ns_params, rate)
×
430
    return iter, emax[mc_routine.n_cull], liveset, ns_params
×
431
end
432

433
function nested_sampling_step!(liveset::AtomWalkers, ns_params::NestedSamplingParameters, mc_routine::MCRoutineParallel; ns_iteration::Int=0)
56✔
434
    sort_by_energy!(liveset)
28✔
435
    ats = liveset.walkers
28✔
436
    lj = liveset.potential
28✔
437
    iter::Union{Missing,Int} = missing
28✔
438
    emax::Union{Vector{Missing},Vector{typeof(0.0u"eV")}} = [liveset.walkers[i].energy for i in 1:nworkers()]
56✔
439

440
    if mc_routine isa MCRandomWalkMaxEParallel
28✔
441
        to_walk_inds = 1:nworkers()
8✔
442
    elseif mc_routine isa MCRandomWalkCloneParallel
24✔
443
        to_walk_inds = sample(2:length(ats), nworkers(); replace=false)
48✔
444
    end
445
    
446
    to_walks = deepcopy.(ats[to_walk_inds])
28✔
447

448
    if length(mc_routine.dims) == 3
28✔
449
        random_walk_function = MC_random_walk!
28✔
450
    elseif length(mc_routine.dims) == 2
×
451
        random_walk_function = MC_random_walk_2D!
×
452
    else
453
        error("Unsupported dimensions: $(mc_routine.dims)")
×
454
    end
455

456

457
    walking = [remotecall(random_walk_function, workers()[i], ns_params.mc_steps, to_walk, lj, ns_params.step_size, emax[end]) for (i,to_walk) in enumerate(to_walks)]
28✔
458
    walked = fetch.(walking)
28✔
459
    finalize.(walking) # finalize the remote calls, clear the memory
28✔
460

461
    accepted_rates = [x[2] for x in walked]
28✔
462
    rate = mean(accepted_rates)
28✔
463

464
    if prod([x[1] for x in walked]) == 0 # if any of the walkers failed
28✔
465
        ns_params.fail_count += 1
×
466
        emax = [missing]
×
467
        return iter, emax[end], liveset, ns_params
×
468
    end
469

470
    # sort!(walked, by = x -> x[3].energy, rev=true)
471
    # filter!(x -> x[1], walked) # remove the failed ones
472

473
    for (i, at) in enumerate(walked)
28✔
474
        ats[i] = at[3]
56✔
475
    end
56✔
476

477
    update_iter!(liveset)
28✔
478
    ns_params.fail_count = 0
28✔
479
    iter = liveset.walkers[1].iter
28✔
480

481
    adjust_step_size(ns_params, rate)
38✔
482
    return iter, emax[end], liveset, ns_params
28✔
483
end
484

485
function nested_sampling_step!(liveset::LJSurfaceWalkers, ns_params::NestedSamplingParameters, mc_routine::MCRoutineParallel; ns_iteration::Int=0)
16✔
486
    sort_by_energy!(liveset)
8✔
487
    ats = liveset.walkers
8✔
488
    lj = liveset.potential
8✔
489
    iter::Union{Missing,Int} = missing
8✔
490
    emax::Union{Vector{Missing},Vector{typeof(0.0u"eV")}} = [liveset.walkers[i].energy for i in 1:nworkers()]
16✔
491

492
    if mc_routine isa MCRandomWalkMaxEParallel
8✔
493
        to_walk_inds = 1:nworkers()
8✔
494
    elseif mc_routine isa MCRandomWalkCloneParallel
4✔
495
        to_walk_inds = sample(2:length(ats), nworkers(); replace=false)
8✔
496
    end
497
    
498
    to_walks = deepcopy.(ats[to_walk_inds])
8✔
499

500
    if length(mc_routine.dims) == 3
8✔
501
        random_walk_function = MC_random_walk!
8✔
502
    elseif length(mc_routine.dims) == 2
×
503
        random_walk_function = MC_random_walk_2D!
×
504
    else
505
        error("Unsupported dimensions: $(mc_routine.dims)")
×
506
    end
507

508

509
    walking = [remotecall(random_walk_function, workers()[i], ns_params.mc_steps, to_walk, lj, ns_params.step_size, emax[end], liveset.surface) for (i,to_walk) in enumerate(to_walks)]
8✔
510
    walked = fetch.(walking)
8✔
511
    finalize.(walking) # finalize the remote calls, clear the memory
8✔
512

513
    accepted_rates = [x[2] for x in walked]
8✔
514
    rate = mean(accepted_rates)
8✔
515

516
    if prod([x[1] for x in walked]) == 0 # if any of the walkers failed
8✔
517
        ns_params.fail_count += 1
3✔
518
        emax = [missing]
3✔
519
        return iter, emax[end], liveset, ns_params
3✔
520
    end
521

522
    # sort!(walked, by = x -> x[3].energy, rev=true)
523
    # filter!(x -> x[1], walked) # remove the failed ones
524

525
    for (i, at) in enumerate(walked)
5✔
526
        ats[i] = at[3]
10✔
527
    end
10✔
528

529
    update_iter!(liveset)
5✔
530
    ns_params.fail_count = 0
5✔
531
    iter = liveset.walkers[1].iter
5✔
532

533
    adjust_step_size(ns_params, rate)
5✔
534
    return iter, emax[end], liveset, ns_params
5✔
535
end
536

537
function nested_sampling_step!(liveset::LJSurfaceWalkers, ns_params::NestedSamplingParameters, mc_routine::MCRoutine; ns_iteration::Int=0)
32✔
538
    sort_by_energy!(liveset)
16✔
539
    ats = liveset.walkers
16✔
540
    lj = liveset.potential
16✔
541
    iter::Union{Missing,Int} = missing
16✔
542
    emax::Union{Missing,typeof(0.0u"eV")} = liveset.walkers[1].energy
16✔
543
    if mc_routine isa MCRandomWalkMaxE
16✔
544
        to_walk = deepcopy(ats[1])
8✔
545
    elseif mc_routine isa MCRandomWalkClone
12✔
546
        to_walk = deepcopy(rand(ats[2:end]))
16✔
547
    else
548
        error("Unsupported MCRoutine type: $mc_routine")
4✔
549
    end
550
    if length(mc_routine.dims) == 3
12✔
551
        accept, rate, at = MC_random_walk!(ns_params.mc_steps, to_walk, lj, ns_params.step_size, emax, liveset.surface)
8✔
552
    else
553
        error("Unsupported dimensions: $(mc_routine.dims)")
4✔
554
    end
555
    # accept, rate, at = MC_random_walk!(ns_params.mc_steps, to_walk, lj, ns_params.step_size, emax)
556
    # @info "iter: $(liveset.walkers[1].iter), acceptance rate: $(round(rate; sigdigits=4)), emax: $(round(typeof(1.0u"eV"), emax; sigdigits=10)), is_accepted: $accept, step_size: $(round(ns_params.step_size; sigdigits=4))"
557
    if accept
8✔
558
        push!(ats, at)
8✔
559
        popfirst!(ats)
8✔
560
        update_iter!(liveset)
8✔
561
        ns_params.fail_count = 0
8✔
562
        iter = liveset.walkers[1].iter
8✔
563
    else
564
        # @warn "Failed to accept MC move"
565
        emax = missing
×
566
        ns_params.fail_count += 1
×
567
    end
568
    adjust_step_size(ns_params, rate)
8✔
569
    return iter, emax, liveset, ns_params
8✔
570
end
571

572
"""
573
    nested_sampling_step!(liveset::AtomWalkers, ns_params::NestedSamplingParameters, mc_routine::MCMixedMoves)
574

575
Perform a single step of the nested sampling algorithm using the Monte Carlo mixed moves routine.
576
By default, this routine performs parallel decorrelation of multiple walkers.
577

578
Arguments
579
- `liveset::AtomWalkers`: The set of atom walkers.
580
- `ns_params::NestedSamplingParameters`: The parameters for nested sampling.
581
- `mc_routine::MCMixedMoves`: The Monte Carlo mixed moves routine.
582

583
Returns
584
- `iter`: The iteration number after the step.
585
- `emax`: The highest energy recorded during the step.
586
- `liveset`: The updated set of atom walkers.
587
- `ns_params`: The updated nested sampling parameters.
588

589
Note
590
- To invoke the parallel version of this routine, use `MCMixedMovesParallel` as the `mc_routine` argument.
591
"""
592
function nested_sampling_step!(liveset::AtomWalkers, ns_params::NestedSamplingParameters, mc_routine::MCMixedMoves; ns_iteration::Int=0)
8✔
593
    sort_by_energy!(liveset)
4✔
594
    ats = liveset.walkers
4✔
595
    lj = liveset.potential
4✔
596
    iter::Union{Missing,Int} = missing
4✔
597
    emax::Union{Missing,typeof(0.0u"eV")} = liveset.walkers[1].energy
4✔
598
    to_walk = deepcopy(rand(ats[2:end]))
8✔
599

600
    accept, rate, at = MC_mixed_moves!(ns_params.mc_steps, to_walk, lj, ns_params.step_size, emax, [mc_routine.walks_freq, mc_routine.swaps_freq])
8✔
601

602
    # accept, rate, at = MC_random_walk!(ns_params.mc_steps, to_walk, lj, ns_params.step_size, emax)
603
    # @info "iter: $(liveset.walkers[1].iter), acceptance rate: $(round(rate; sigdigits=4)), emax: $(round(typeof(1.0u"eV"), emax; sigdigits=10)), is_accepted: $accept, step_size: $(round(ns_params.step_size; sigdigits=4))"
604
    if accept
4✔
605
        push!(ats, at)
4✔
606
        popfirst!(ats)
4✔
607
        update_iter!(liveset)
4✔
608
        ns_params.fail_count = 0
4✔
609
        iter = liveset.walkers[1].iter
4✔
610
    else
611
        # @warn "Failed to accept MC move"
612
        emax = missing
×
613
        ns_params.fail_count += 1
×
614
    end
615
    adjust_step_size(ns_params, rate)
6✔
616

617
    return iter, emax, liveset, ns_params
4✔
618
end
619

NEW
620
function nested_sampling_step!(liveset::AtomWalkers, ns_params::NestedSamplingParameters, mc_routine::MCMixedMovesParallel; ns_iteration::Int=0)
×
621
    sort_by_energy!(liveset)
×
622
    ats = liveset.walkers
×
623
    lj = liveset.potential
×
624
    iter::Union{Missing,Int} = missing
×
625
    emax::Union{Vector{Missing},Vector{typeof(0.0u"eV")}} = [liveset.walkers[i].energy for i in 1:nworkers()]
×
626

627
    to_walk_inds = sample(2:length(ats), nworkers(); replace=false)
×
628
    # println("to_walk_inds: ", to_walk_inds) # DEBUG
629
    
630
    to_walks = deepcopy.(ats[to_walk_inds])
×
631

632
    walking = [remotecall(MC_mixed_moves!, workers()[i], ns_params.mc_steps, to_walk, lj, ns_params.step_size, emax[1], [mc_routine.walks_freq, mc_routine.swaps_freq]) for (i,to_walk) in enumerate(to_walks)]
×
633
    walked = fetch.(walking)
×
634
    finalize.(walking) # finalize the remote calls, clear the memory
×
635

636
    accepted_rates = [x[2] for x in walked]
×
637
    rate = mean(accepted_rates)
×
638

639
    # sort!(walked, by = x -> x[3].energy, rev=true)
640
    # filter!(x -> x[1], walked) # remove the failed ones
641
    accepted_inds = findall(x -> x[1]==1, walked)
×
642

643
    if length(accepted_inds) == 0 # if all of the walkers failed
×
644
        ns_params.fail_count += 1
×
645
        emax = [missing]
×
646
        return iter, emax[end], liveset, ns_params
×
647
    else
648
        # pick one from the accepted ones
649
        picked = rand(accepted_inds)
×
650
        ats[1] = walked[picked][3]
×
651
        # println("picked: ", picked) # DEBUG
652
        # remove the picked one from accepted_inds
653
        filter!(x -> x != picked, accepted_inds)
×
654
        # println("remaining accepted_inds: ", accepted_inds) # DEBUG
655

656
        if !isempty(accepted_inds)
×
657
            for i in accepted_inds
×
658
                ats[to_walk_inds[i]] = walked[i][3]
×
659
                # println("Updating ats at index $(to_walk_inds[i])") # DEBUG
660
            end
×
661
        end
662
    end
663

664
    update_iter!(liveset)
×
665
    ns_params.fail_count = 0
×
666
    iter = liveset.walkers[1].iter
×
667

668
    adjust_step_size(ns_params, rate)
×
669
    return iter, emax[1], liveset, ns_params
×
670
end
671

672
"""
673
    nested_sampling_step!(liveset::LatticeGasWalkers, ns_params::NestedSamplingParameters, mc_routine::MCMixedMoves)
674

675
Perform a single step of the nested sampling algorithm using a mix of geometric cluster moves and local swap moves.
676

677
The total `mc_steps` from `ns_params` are split between cluster moves and local swaps according to `clusters_freq` and
678
`walks_freq` in the `mc_routine`. Cluster moves use `geometric_cluster_swap!` with growth probability `ns_params.cluster_p`,
679
which is adaptively tuned to maintain `mc_routine.target_cluster_accept`. Local swaps use the standard `lattice_random_walk!`.
680

681
## Arguments
682
- `liveset::LatticeGasWalkers`: The liveset of lattice gas walkers.
683
- `ns_params::NestedSamplingParameters`: The parameters for nested sampling.
684
- `mc_routine::MCMixedMoves`: The mixed moves routine with cluster and local swap frequencies.
685

686
## Returns
687
- `iter`: The iteration number of the liveset after the step.
688
- `emax`: The maximum energy of the liveset after the step.
689
- `liveset::LatticeGasWalkers`: The updated liveset.
690
- `ns_params::NestedSamplingParameters`: The updated parameters.
691
"""
692
function nested_sampling_step!(liveset::LatticeGasWalkers,
264✔
693
                               ns_params::NestedSamplingParameters,
694
                               mc_routine::MCMixedMoves;
695
                               ns_iteration::Int=0)
696
    sort_by_energy!(liveset)
132✔
697
    ats = liveset.walkers
132✔
698
    h = liveset.hamiltonian
132✔
699
    iter::Union{Missing,Int} = missing
132✔
700
    emax::Union{Missing,Float64} = liveset.walkers[1].energy.val
132✔
701

702
    # Clone a random non-worst walker
703
    to_walk = deepcopy(rand(ats[2:end]))
264✔
704

705
    # Compute move counts from frequencies
706
    total_freq = mc_routine.walks_freq + mc_routine.clusters_freq
132✔
707
    n_local = round(Int, ns_params.mc_steps * mc_routine.walks_freq / max(total_freq, 1))
132✔
708
    n_cluster = ns_params.mc_steps - n_local
132✔
709

710
    # Apply cluster moves
711
    cluster_accepted = false
132✔
712
    cluster_rate = 0.0
132✔
713
    if n_cluster > 0
132✔
714
        cluster_accepted, cluster_rate, to_walk = MC_cluster_walk!(
132✔
715
            n_cluster, to_walk, h, emax, ns_params.cluster_p;
716
            energy_perturb=ns_params.energy_perturbation)
717
    end
718

719
    # Apply local swap moves
720
    local_accepted = false
132✔
721
    local_rate = 0.0
132✔
722
    if n_local > 0
132✔
723
        local_accepted, local_rate, to_walk = MC_random_walk!(
52✔
724
            n_local, to_walk, h, emax;
725
            energy_perturb=ns_params.energy_perturbation)
726
    end
727

728
    accept = cluster_accepted || local_accepted
132✔
729
    if accept
132✔
730
        push!(ats, to_walk)
129✔
731
        popfirst!(ats)
129✔
732
        update_iter!(liveset)
129✔
733
        ns_params.fail_count = 0
129✔
734
        iter = liveset.walkers[1].iter
129✔
735
    else
736
        emax = missing
3✔
737
        ns_params.fail_count += 1
3✔
738
    end
739

740
    # Accumulate cluster acceptance stats for adaptive tuning
741
    if n_cluster > 0
132✔
742
        ns_params.cluster_accepted += cluster_rate * n_cluster
132✔
743
        ns_params.cluster_total += n_cluster
132✔
744
        if mc_routine.cluster_adjust_interval > 0 &&
132✔
745
           ns_params.cluster_total >= mc_routine.cluster_adjust_interval * n_cluster
746
            window_rate = ns_params.cluster_accepted / max(ns_params.cluster_total, 1.0)
52✔
747
            adjust_cluster_p(ns_params, window_rate, ns_iteration;
52✔
748
                             target=mc_routine.target_cluster_accept,
749
                             floor=mc_routine.cluster_p_floor,
750
                             ceiling=mc_routine.cluster_p_ceiling)
751
            ns_params.cluster_accepted = 0.0
52✔
752
            ns_params.cluster_total = 0.0
52✔
753
        end
754
    end
755

756
    return iter, emax * unit(liveset.walkers[1].energy), liveset, ns_params
132✔
757
end
758

759
"""
760
    nested_sampling_step!(liveset::LatticeGasWalkers, ns_params::LatticeNestedSamplingParameters, mc_routine::MCRoutine)
761

762
Perform a single step of the nested sampling algorithm.
763

764
This function takes a `liveset` of lattice gas walkers, `ns_params` containing the parameters for nested sampling, and `mc_routine` representing the Monte Carlo
765
routine for generating new samples. It performs a single step of the nested sampling algorithm by updating the liveset with a new walker.
766

767
## Arguments
768
- `liveset::LatticeGasWalkers`: The liveset of lattice gas walkers.
769
- `ns_params::LatticeNestedSamplingParameters`: The parameters for nested sampling.
770
- `mc_routine::MCRoutine`: The Monte Carlo routine for generating new samples.
771

772
## Returns
773
- `iter`: The iteration number of the liveset after the step.
774
- `emax`: The maximum energy of the liveset after the step.
775
"""
776
function nested_sampling_step!(liveset::LatticeGasWalkers,
192✔
777
                               ns_params::NestedSamplingParameters,
778
                               mc_routine::MCRoutine;
779
                               ns_iteration::Int=0)
780
    sort_by_energy!(liveset)
96✔
781
    ats = liveset.walkers
96✔
782
    h = liveset.hamiltonian
96✔
783
    iter::Union{Missing,Int} = missing
96✔
784
    emax::Union{Missing,Float64} = liveset.walkers[1].energy.val
96✔
785
    if mc_routine isa MCRandomWalkMaxE
96✔
786
        to_walk = deepcopy(ats[1])
184✔
787
    elseif mc_routine isa MCRandomWalkClone
4✔
788
        to_walk = deepcopy(rand(ats[2:end]))
8✔
789
    else
790
        error("Unsupported MCRoutine type: $mc_routine")
×
791
    end
792
    accept, rate, at = MC_random_walk!(ns_params.mc_steps, to_walk, h, emax; energy_perturb=ns_params.energy_perturbation)
96✔
793

794
    # @info "iter: $(liveset.walkers[1].iter), acceptance rate: $rate, emax: $emax, is_accepted: $accept"
795
    if accept
96✔
796
        push!(ats, at)
91✔
797
        popfirst!(ats)
91✔
798
        update_iter!(liveset)
91✔
799
        ns_params.fail_count = 0
91✔
800
        iter = liveset.walkers[1].iter
91✔
801
    else
802
        # @warn "Failed to accept MC move"
803
        emax = missing
5✔
804
        ns_params.fail_count += 1
5✔
805
    end
806
    # adjust_step_size(ns_params, rate)
807
    return iter, emax * unit(liveset.walkers[1].energy), liveset, ns_params
96✔
808
end
809

810
"""
811
    nested_sampling_step!(liveset::LatticeGasWalkers, ns_params::LatticeNestedSamplingParameters, mc_routine::MCNewSample)
812

813
Perform a single step of the nested sampling algorithm.
814

815
This function takes a `liveset` of lattice gas walkers, `ns_params` containing the parameters for nested sampling, and `mc_routine` representing the Monte Carlo routine for generating new samples. It performs a single step of the nested sampling algorithm by updating the liveset with a new walker.
816

817
## Arguments
818
- `liveset::LatticeGasWalkers`: The liveset of lattice gas walkers.
819
- `ns_params::LatticeNestedSamplingParameters`: The parameters for nested sampling.
820
- `mc_routine::MCNewSample`: The Monte Carlo routine for generating new samples.
821

822
## Returns
823
- `iter`: The iteration number of the liveset after the step.
824
- `emax`: The maximum energy of the liveset after the step.
825
- `liveset::LatticeGasWalkers`: The updated liveset after the step.
826
- `ns_params::LatticeNestedSamplingParameters`: The updated nested sampling parameters after the step.
827
"""
828
function nested_sampling_step!(liveset::LatticeGasWalkers,
8✔
829
                               ns_params::NestedSamplingParameters,
830
                               mc_routine::MCNewSample;
831
                               ns_iteration::Int=0)
832
    sort_by_energy!(liveset)
4✔
833
    ats = liveset.walkers
4✔
834
    h = liveset.hamiltonian
4✔
835
    iter::Union{Missing,Int} = missing
4✔
836
    emax::Union{Missing,Float64} = liveset.walkers[1].energy.val
4✔
837

838
    to_walk = deepcopy(ats[1])
8✔
839

840
    accept, at = MC_new_sample!(to_walk, h, emax; energy_perturb=ns_params.energy_perturbation)
4✔
841

842
    # @info "iter: $(liveset.walkers[1].iter), emax: $emax, is_accepted: $accept"
843
    if accept
4✔
844
        push!(ats, at)
2✔
845
        popfirst!(ats)
2✔
846
        update_iter!(liveset)
2✔
847
        ns_params.fail_count = 0
2✔
848
        iter = liveset.walkers[1].iter
2✔
849
    else
850
        # @warn "Failed to accept MC move"
851
        emax = missing
2✔
852
        ns_params.fail_count += 1
2✔
853
    end
854
    # adjust_step_size(ns_params, rate)
855
    return iter, emax * unit(liveset.walkers[1].energy), liveset, ns_params
4✔
856
end
857

858

859
function nested_sampling_step!(liveset::LatticeGasWalkers,
8✔
860
                               ns_params::NestedSamplingParameters,
861
                               mc_routine::MCRejectionSampling;
862
                               ns_iteration::Int=0)
863
    sort_by_energy!(liveset)
4✔
864
    ats = liveset.walkers
4✔
865
    h = liveset.hamiltonian
4✔
866
    iter::Union{Missing,Int} = missing
4✔
867
    emax::Union{Missing,Float64} = liveset.walkers[1].energy.val
4✔
868

869
    to_walk = deepcopy(ats[1])
8✔
870

871
    accept, at = MC_rejection_sampling!(to_walk, h, emax; energy_perturb=ns_params.energy_perturbation)
4✔
872

873
    # @info "iter: $(liveset.walkers[1].iter), emax: $emax, is_accepted: $accept"
874
    if accept
4✔
875
        push!(ats, at)
4✔
876
        popfirst!(ats)
4✔
877
        update_iter!(liveset)
4✔
878
        ns_params.fail_count = 0
4✔
879
        iter = liveset.walkers[1].iter
4✔
880
    else
881
        # @warn "Failed to accept MC move"
882
        emax = missing
×
883
        ns_params.fail_count += 1
×
884
    end
885
    # adjust_step_size(ns_params, rate)
886
    return iter, emax * unit(liveset.walkers[1].energy), liveset, ns_params
4✔
887
end
888

889

890

891
"""
892
    nested_sampling(liveset::AbstractLiveSet, ns_params::NestedSamplingParameters, n_steps::Int64, mc_routine::MCRoutine; args...)
893

894
Perform a nested sampling loop for a given number of steps.
895

896
# Arguments
897
- `liveset::AbstractLiveSet`: The initial set of walkers.
898
- `ns_params::NestedSamplingParameters`: The parameters for nested sampling.
899
- `n_steps::Int64`: The number of steps to perform.
900
- `mc_routine::MCRoutine`: The Monte Carlo routine to use.
901

902
# Returns
903
- `df`: A DataFrame containing the iteration number and maximum energy for each step.
904
- `liveset`: The updated set of walkers.
905
- `ns_params`: The updated nested sampling parameters.
906
"""
907
function nested_sampling(liveset::AbstractLiveSet, 
44✔
908
                                ns_params::NestedSamplingParameters, 
909
                                n_steps::Int64, 
910
                                mc_routine::MCRoutine,
911
                                save_strategy::DataSavingStrategy)
912
    # Initialize cluster_p and reset counters from MCMixedMoves if applicable
913
    if mc_routine isa MCMixedMoves && mc_routine.clusters_freq > 0
44✔
914
        ns_params.cluster_p = mc_routine.initial_cluster_p
8✔
915
        ns_params.cluster_accepted = 0.0
8✔
916
        ns_params.cluster_total = 0.0
8✔
917
        empty!(ns_params.cluster_p_history)
8✔
918
        empty!(ns_params.cluster_accept_history)
8✔
919
        empty!(ns_params.cluster_adjust_iterations)
8✔
920
    end
921
    df = DataFrame(iter=Int[], emax=Float64[])
44✔
922
    for i in 1:n_steps
44✔
923
        print_info = i % save_strategy.n_info == 0
320✔
924
        write_walker_every_n(liveset.walkers[1], i, save_strategy)
320✔
925
        iter, emax, liveset, ns_params = nested_sampling_step!(liveset, ns_params, mc_routine; ns_iteration=i)
335✔
926
        @debug "n_step $i, iter: $iter, emax: $emax"
320✔
927
        if ns_params.fail_count >= ns_params.allowed_fail_count
320✔
UNCOV
928
            @warn "Failed to accept MC move $(ns_params.allowed_fail_count) times in a row. Reset step size!"
×
UNCOV
929
            ns_params.fail_count = 0
×
UNCOV
930
            ns_params.step_size = ns_params.initial_step_size
×
931
        end
932
        if !(iter isa typeof(missing))
320✔
933
            push!(df, (iter, emax.val))
380✔
934
        end
935
        print_message(i, iter, emax, ns_params.step_size, print_info, liveset)
623✔
936
        write_df_every_n(df, i, save_strategy)
320✔
937
        write_ls_every_n(liveset, i, save_strategy)
320✔
938
    end
596✔
939
    return df, liveset, ns_params
44✔
940
end
941

942
function print_message(i, iter, emax, step_size, print_info, liveset::LatticeWalkers)
208✔
943
    if print_info && !(iter isa typeof(missing))
208✔
944
        @info "iter: $(liveset.walkers[1].iter), emax: $(emax)"
36✔
945
    elseif print_info && iter isa typeof(missing)
172✔
946
        @info "MC move failed, step: $(i), emax: $(liveset.walkers[1].energy)"
4✔
947
    end
948
end
949

950
function print_message(i, iter, emax, step_size, print_info, liveset::AtomWalkers)
112✔
951
    if print_info && !(iter isa typeof(missing))
112✔
952
        @info "iter: $(liveset.walkers[1].iter), emax: $(emax.val), step_size: $(round(step_size; sigdigits=4))"
46✔
953
    elseif print_info && iter isa typeof(missing)
66✔
954
        @info "MC move failed, step: $(i), emax: $(liveset.walkers[1].energy.val), step_size: $(round(step_size; sigdigits=4))"
6✔
955
    end
956
end
957
    
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc