| Title: | Utilities to manage, estimate speed, optimize use of parallel clusters |
|---|---|
| Description: | Miscellaneous utilities developed by the Predictive Ecology Group (<http://predictiveecology.org>). |
| Authors: | Eliot J B McIntire [aut, cre] (ORCID: <https://orcid.org/0000-0002-6914-8316>), His Majesty the King in Right of Canada, as represented by the Minister of Natural Resources Canada [cph] |
| Maintainer: | Eliot J B McIntire <[email protected]> |
| License: | GPL-3 |
| Version: | 0.0.51 |
| Built: | 2026-10-15 06:19:32 UTC |
| Source: | https://github.com/PredictiveEcology/clusters |
PSOCK workers are reached over SSH, and the master reads results with unserialize() on the
socket. That read is bounded by parallelly's timeout, which we set to 30 days so that a
legitimately long computation is never cut off. The consequence is that if a worker's R process
dies after connecting, SSH holds the tunnel open, the socket never closes, and the master
blocks for up to 30 days – in practice, forever.
.sshKeepaliveOpts(extra = NULL, interval = 30L, countMax = 4L).sshKeepaliveOpts(extra = NULL, interval = 30L, countMax = 4L)
extra |
Character vector of additional SSH options to append. |
interval |
Seconds between keepalive probes. |
countMax |
Number of unanswered probes before SSH closes the connection. |
connectTimeout does not help: it only bounds the initial connect, which already succeeded.
ServerAliveInterval/ServerAliveCountMax close the door from the SSH side instead. SSH probes
an idle connection every ServerAliveInterval seconds and tears it down after
ServerAliveCountMax unanswered probes, so a dead worker surfaces as a closed connection and an
R error within ~2 minutes rather than hanging the run. A busy worker answers the probes, so long
computations are unaffected – the probes run at the SSH layer, not in R.
Observed 2026-09-19/20: two re-scores hung for hours after a worker died at startup with
Error in unserialize(node$con) inside workRSOCK, and the masters had to be killed by hand.
A character vector of SSH options for rshopts.
This includes copying files over to unique(cores) machines, then loading all objects from disk in each of the parallel cores.
clusterSetup( messagePrefix = "DEoptim_", itermax = 500, trace = TRUE, strategy = 3, initialpop = NULL, NP = NULL, cores, logPath, libPath, objsNeeded, pkgsNeeded, nCoresNeeded = 100, envir = parent.frame(), controlArgs = list() )clusterSetup( messagePrefix = "DEoptim_", itermax = 500, trace = TRUE, strategy = 3, initialpop = NULL, NP = NULL, cores, logPath, libPath, objsNeeded, pkgsNeeded, nCoresNeeded = 100, envir = parent.frame(), controlArgs = list() )
messagePrefix |
Character prefix for this cluster's messages, so concurrent builds can be told apart in a shared log. |
itermax |
Integer; maximum DEoptim iterations. See |
trace |
Logical or integer passed to |
strategy |
Integer in |
initialpop |
Optional matrix of starting parameter sets, one row per population member. |
NP |
Integer; number of population members. Defaults to DEoptim's own rule when |
cores |
Character vector of host names, repeated once per worker wanted on that host, or a number for localhost only. |
logPath |
Path for worker output ( |
libPath |
The project library. This is the master copy: it is mirrored to every host, and workers load from it. |
objsNeeded |
Character vector naming objects in |
pkgsNeeded |
Character vector of packages the workers must be able to load. Hosts that cannot are reported by |
nCoresNeeded |
Integer; how many workers to aim for across all hosts. |
envir |
Environment holding |
controlArgs |
Named list of further |
A list of items that can be passed to DEoptim.control()
Runs DEoptim::DEoptim() a few generations at a time (iterStep in ..., default 1),
starting each call from the previous call's population, and caches every chunk with
reproducible::Cache(), so a fit that is interrupted resumes from its last finished chunk.
Between chunks it can re-score the surviving population (options(clusters.deoptimRescoreEvery)),
draws progress figures with SpaDES.core::Plots() when figurePath is a path, and stops early
once the population has stopped improving (see .deoptimPopulationConverged()).
DEoptimIterative2( fn, lower, upper, control, ..., .plots, figurePath, cachePath, runName = 1, .verbose = TRUE )DEoptimIterative2( fn, lower, upper, control, ..., .plots, figurePath, cachePath, runName = 1, .verbose = TRUE )
fn |
The objective function, as for |
lower, upper
|
Parameter bounds, as for |
control |
A list of |
... |
Passed to |
.plots |
Passed to |
figurePath |
Folder for the progress figures, or |
cachePath |
Passed to the progress figures that read the cache. |
runName |
Distinguishes this fit's cached chunks from another fit's. |
.verbose |
Passed to |
A list with one DEoptim::DEoptim() result per chunk run, in order.
This is for cleaning up cases where an interrupted optimization is leading to multiple files for the same .runName. This will remove duplicates, keeping only the most recent.
dirNew( path, secsAgo = Inf, after = Sys.time() - secsAgo, pattern = "^(.+)\\_[[:digit:]]{6,8}.*\\.png" )dirNew( path, secsAgo = Inf, after = Sys.time() - secsAgo, pattern = "^(.+)\\_[[:digit:]]{6,8}.*\\.png" )
path |
A folder in which to search for duplicates |
secsAgo |
Numeric; consider files modified within this many seconds. Ignored if |
after |
A time; consider files modified after it. Defaults to |
pattern |
The regular expression to search for, to identify the files. This must have 1 set of parentheses (), as only the content between the () will be used for duplicate assessment, i.e., remove anything in the file that shouldn't be used. |
For side effects: removed files
Subtract live reservations from measured free cores
freeCoresLessReserved( nodes, path = getOption("clusters.reservationsPath"), loadWindowMinutes = 5, graceMinutes = 2 )freeCoresLessReserved( nodes, path = getOption("clusters.reservationsPath"), loadWindowMinutes = 5, graceMinutes = 2 )
nodes |
A data.frame with |
path |
Path to the reservations ledger; defaults to |
loadWindowMinutes |
The averaging window, in minutes, of the load average
|
graceMinutes |
Minutes after it is made during which a reservation counts in full. A reservation is recorded when the cluster has started, but its workers only load the hosts once objects are copied to them and the work begins (2.5-4 minutes for the FireSense spread fits). |
free_est already contains the load of clusters that have been
running for a while, so subtracting their whole reservation counts those cores
twice. A load average is exponentially damped: t minutes after a cluster's
load starts it shows 1 - exp(-t / loadWindowMinutes) of it. Each reservation is
therefore subtracted only by the share the average has not absorbed yet,
workers * exp(-max(age - graceMinutes, 0) / loadWindowMinutes): all of it for a
cluster built moments ago, which is what stops concurrent builds double-booking,
and almost none of it for a cluster that has been running for half an hour.
nodes with free_est reduced by the unabsorbed share of every live
reservation on that host, floored at zero, plus a reserved column (the
reserved workers, for reporting). Reservations held by this process count
too: a master that already built one cluster is genuinely using those cores
while it builds the next.
Takes the per-host timings from runTests() and fits, for each host, a
linear model of the slowest worker's time against the number of workers. The
models are then used to choose an allocation of Npops workers across the
hosts that finishes soonest, since a host whose per-worker time degrades
quickly is worth fewer workers than its core count suggests.
getHostCombination(outs, Npops = 100)getHostCombination(outs, Npops = 100)
outs |
A list of |
Npops |
Integer; total number of workers to allocate. |
A data.table with one row per host, giving the workers assigned and
the predicted time.
Lightweight wrapper for parallelly::makeClusterPSOCK
makeClusterPSOCK( workers, outfile = NULL, rscript_libs = .libPaths(), ..., rscript = NULL, default_packages = c("datasets", "utils", "grDevices", "graphics", "stats", "methods"), port = NULL, rshopts = .sshKeepaliveOpts(c("-o", "ForwardX11=no", "-o", "ExitOnForwardFailure=yes")), tries = 5L, delay = 5, renice = 20, revtunnel = TRUE )makeClusterPSOCK( workers, outfile = NULL, rscript_libs = .libPaths(), ..., rscript = NULL, default_packages = c("datasets", "utils", "grDevices", "graphics", "stats", "methods"), port = NULL, rshopts = .sshKeepaliveOpts(c("-o", "ForwardX11=no", "-o", "ExitOnForwardFailure=yes")), tries = 5L, delay = 5, renice = 20, revtunnel = TRUE )
workers |
The hostnames of workers (as a character vector) or the number of localhost workers (as a positive integer). |
outfile |
Optional log file path. |
rscript_libs |
Optional library paths for workers. |
... |
Additional arguments passed to parallelly::makeClusterPSOCK. |
rscript |
The command that starts a worker, passed to |
default_packages |
Packages R attaches in each worker; the default is parallelly's. |
port |
Optional port or port block start. |
rshopts |
Character vector of options passed to |
tries, delay
|
Maximum number of attempts done to launch each node
with |
renice |
A numerical 'niceness' (priority) to set for the worker processes. |
revtunnel |
If TRUE, a reverse SSH tunnel is set up for each worker such
that the worker R process sets up a socket connection to its local port
|
A worker command that starts with env (the OpenBLAS cap from .workerRscript(), or an
LD_LIBRARY_PATH prefix) is not recognised by parallelly as Rscript, so parallelly passes the
default packages as an R_DEFAULT_PACKAGES= assignment in front of the command. With renice
that assignment ends up after nice, which then fails to run it ("nice:
'R_DEFAULT_PACKAGES=...': No such file or directory"). For such a command the default packages
are put among env's own assignments instead.
A convenience for clusters whose hosts sit on one subnet: give the final octets and get back full addresses.
makeHosts(ips, ipbase = "10.20.0.")makeHosts(ips, ipbase = "10.20.0.")
ips |
Numeric or character vector of final octets, e.g. |
ipbase |
Character; everything before the final octet, with its trailing dot. |
Character vector of addresses.
makeHosts(c(11, 12))makeHosts(c(11, 12))
A PSOCK worker is a fresh R session, so it starts at terra's defaults no matter what
the master set: memfrac = 0.5, memmax = 16, todisk = FALSE. memfrac is a
fraction of the machine's total RAM and terra applies it per process, so a hundred
workers each sizing a working buffer at half of total RAM is not a policy anyone
chose – and it silently overrides whatever the master was careful to set.
mirrorTerraOptions(cl)mirrorTerraOptions(cl)
cl |
A cluster object, as returned by |
Called by clusterSetup() once the workers are up. Does nothing when terra is not
installed, on either side.
Invisibly, the named list of settings sent, or NULL if terra is unavailable
on the master.
if (requireNamespace("terra", quietly = TRUE)) { cl <- parallel::makeCluster(1L) terra::terraOptions(memfrac = 0, todisk = TRUE) mirrorTerraOptions(cl) parallel::clusterEvalQ(cl, terra::terraOptions(print = FALSE)$memfrac) # 0 parallel::stopCluster(cl) }if (requireNamespace("terra", quietly = TRUE)) { cl <- parallel::makeCluster(1L) terra::terraOptions(memfrac = 0, todisk = TRUE) mirrorTerraOptions(cl) parallel::clusterEvalQ(cl, terra::terraOptions(print = FALSE)$memfrac) # 0 parallel::stopCluster(cl) }
Polls every worker for its active-thread count and its memory use, and
redraws two aligned rows in place: threads on the first, used/totalGB on
the second. Interrupt it (Ctrl-C) to stop; it then prints the per-host peaks
it saw and returns them.
Neither probe needs clusters installed on the hosts. The functions are
re-homed in the global environment before being sent, so they travel whole
rather than as a reference to this package's namespace.
monitorCluster(cl, cores, pad = 2, interval = 1)monitorCluster(cl, cores, pad = 2, interval = 1)
cl |
A running cluster with one worker per host, in the same order as
|
cores |
Character vector of host names, in the cluster's worker order. |
pad |
Integer; spaces between columns. |
interval |
Numeric; seconds between polls. |
Invisibly, a list with threads (peak active threads per host) and
ram (peak memory used per host, GB). Returned on interrupt.
The display uses ANSI erase sequences to redraw in place. Redirect it to a file and you get one block per tick instead of a live display.
## Not run: hosts <- c("birds", "biomass", "camas") peaks <- monitorCluster(cores = hosts) # Ctrl-C to stop peaks$ram ## End(Not run)## Not run: hosts <- c("birds", "biomass", "camas") peaks <- monitorCluster(cores = hosts) # Ctrl-C to stop peaks$ram ## End(Not run)
This only works on non-linux operating systems, as it uses ps
numActiveThreads(pattern = "", minCPU = 50)numActiveThreads(pattern = "", minCPU = 50)
pattern |
An optional search pattern to look for when identifying threads that are active. If left at default, then all threads that are active will count. |
minCPU |
The minimum CPU (in percent , i.e.,0 to 100) that a thread must be using for it to count as actively being used. |
An integer representing the current number of threads that are being used
as a CPU% greater than minCPU. This can be used, e.g., with
parallelly::availableCores() to estimate the number of cores that are available
to be used.
This does not address memory or disk use issues.
## Not run: ## Runs until interrupted, so R CMD check must not execute it. # This will show the active number, updated every 0.5 seconds cat("Number Active CPUs right now:\n"); while(TRUE) { numAC <- clusters::numActiveThreads(); cat("\r"); cat(numAC); cat(" "); Sys.sleep(0.5) } ## End(Not run)## Not run: ## Runs until interrupted, so R CMD check must not execute it. # This will show the active number, updated every 0.5 seconds cat("Number Active CPUs right now:\n"); while(TRUE) { numAC <- clusters::numActiveThreads(); cat("\r"); cat(numAC); cat(" "); Sys.sleep(0.5) } ## End(Not run)
Launches a probe cluster (1 worker per host) via parallelly::makeClusterPSOCK()
using reverse SSH tunneling (revtunnel = TRUE) and sequential setup (on Rstudio),
parallel otherwise,
configures a per-user library on each worker (created via dir.create(recursive=TRUE)),
installs & loads pkgsNeeded in that user library, measures per-host capacity via
parallelly::availableCores() and parallelly::freeCores(), performs a
hyper-threading-aware allocation of up to total workers (β penalty beyond ~50% logical
occupancy), and optionally launches the final cluster with the computed worker counts.
This is intentionally minimal—no master argument, no diagnostics or fallbacks.
plan_psock_min( hosts, total = 100L, beta = 0.5, load_memory = "5min", fraction = 0.9, pkgsNeeded = c("parallelly", "future", "foreach"), user_lib_template = "~/.local/R/%v/library", install_repos = c("https://cloud.r-project.org", "https://cran.r-project.org"), rshopts = .sshKeepaliveOpts(), rscript = "Rscript", auto_stop = TRUE, logPath = NULL, libPath = NULL, build_final_cluster = TRUE )plan_psock_min( hosts, total = 100L, beta = 0.5, load_memory = "5min", fraction = 0.9, pkgsNeeded = c("parallelly", "future", "foreach"), user_lib_template = "~/.local/R/%v/library", install_repos = c("https://cloud.r-project.org", "https://cran.r-project.org"), rshopts = .sshKeepaliveOpts(), rscript = "Rscript", auto_stop = TRUE, logPath = NULL, libPath = NULL, build_final_cluster = TRUE )
hosts |
Character vector of hostnames reachable by SSH. |
total |
Integer total desired workers (default 100). |
beta |
Numeric in (0,1], HT penalty beyond 50% logical occupancy (default 0.5). |
load_memory |
Character; |
fraction |
Numeric ≥ 0; headroom scale for |
pkgsNeeded |
Character vector of packages to ensure on workers
(default |
user_lib_template |
Character; per-user library path template; |
install_repos |
Character vector of CRAN-like repos tried in order
(default |
rshopts |
Character vector of SSH options passed by parallelly. Defaults to
|
rscript |
Character; path to |
auto_stop |
Logical; auto-stop clusters on GC (default |
logPath |
Optional local file to append compact bootstrap / probe info. |
libPath |
Optional site library path to prepend and use if writable. |
build_final_cluster |
Logical; if |
A list with:
probe: per-host capacity & load,
allocation: per-host assignments (HT-aware),
workers: hostname vector repeated per assigned workers,
total_requested, total_assigned,
cluster (if built).
parallelly PSOCK cluster setup & worker startup controls: https://parallelly.futureverse.org/reference/makeClusterPSOCK.html availableCores / freeCores: https://cran.r-project.org/web/packages/parallelly/parallelly.pdf https://rdrr.io/cran/parallelly/man/freeCores.html Base parallel PSOCK notes (background): https://web.mit.edu/r/current/lib/R/library/parallel/html/makeCluster.html
## Not run: plan <- plan_psock_min( hosts = c("nodeA","nodeB","nodeC","nodeD"), total = 100, pkgsNeeded = c("parallelly","future","foreach"), user_lib_template = "~/.local/R/%v/library", build_final_cluster = TRUE ) plan$allocation parallel::stopCluster(plan$cluster) ## End(Not run)## Not run: plan <- plan_psock_min( hosts = c("nodeA","nodeB","nodeC","nodeD"), total = 100, pkgsNeeded = c("parallelly","future","foreach"), user_lib_template = "~/.local/R/%v/library", build_final_cluster = TRUE ) plan$allocation parallel::stopCluster(plan$cluster) ## End(Not run)
testMachine
Plot the outputs from testMachine
plotMachine( Ncores, coreTimes, estTotTime, optimisticTotTime, systemTimes, N = 100, nam, detectedCores )plotMachine( Ncores, coreTimes, estTotTime, optimisticTotTime, systemTimes, N = 100, nam, detectedCores )
Ncores |
Integer vector of worker counts that were timed. |
coreTimes |
List of per-core |
estTotTime |
List of estimated total times, one per entry of |
optimisticTotTime |
List of best-case total times, one per entry of |
systemTimes |
List of measured elapsed times, one per entry of |
N |
Integer; the workload multiplier the times are scaled to. |
nam |
Character; host name, used in the plot title. |
detectedCores |
Integer; what the host reports as its core count, drawn as a reference line. |
Reads ‘/proc/meminfo’ and returns used and total memory in gigabytes.
Used is MemTotal - MemAvailable, that is, everything the kernel cannot hand
out immediately, so page cache in active use counts as used.
ramUsageGB()ramUsageGB()
A list with used_gb and total_gb, both NA_real_ where
‘/proc/meminfo’ is absent (any non-Linux host) or unreadable.
ramUsageGB()ramUsageGB()
Tries the local (rsynced) copy first, then the original shared path. A failed read used to fall
through to reproducible::.unwrap() on a try-error, which produced objects whose terra
external pointers were dangling; the job then died much later with
"external pointer is not valid", an error saying nothing about a file transfer. Verifying here
keeps the error where the cause is.
readTransferredObjects(therePath, filenameForTransfer)readTransferredObjects(therePath, filenameForTransfer)
therePath |
Path to the worker-local copy of the transferred objects. |
filenameForTransfer |
Path to the original, used as a fallback. |
The list of transferred objects.
plan_psock_min() sizes a cluster from parallelly::freeCores(), which reads
a trailing load average (5 minutes by default). Two clusters built inside
that window therefore both observe the same idle cores and both claim them:
the load produced by the first has not yet reached the average the second
reads. Staggering the builds by more than the averaging window papers over
this; it does not fix it, and it forces callers to hand-tune both the stagger
and the number of concurrent jobs against the size of the fleet.
These functions keep a small ledger of what has actually been handed out, so
allocation is based on reservations rather than on a lagging measurement.
Each successful build records host -> workers under a reservation id owned
by the building process; freeCoresLessReserved() subtracts those from the
measured free cores. Releases are keyed by id, because one process may hold
several clusters at once.
The ledger is self-healing: every read drops entries whose owning process is no longer alive, so a master that is killed without releasing cannot leak a reservation forever.
reservationsPath(path = getOption("clusters.reservationsPath")) liveReservations(path = getOption("clusters.reservationsPath")) reserveCores( alloc, id = basename(tempfile("resv")), pid = Sys.getpid(), path = getOption("clusters.reservationsPath") ) releaseCores( id = NULL, pid = Sys.getpid(), path = getOption("clusters.reservationsPath") )reservationsPath(path = getOption("clusters.reservationsPath")) liveReservations(path = getOption("clusters.reservationsPath")) reserveCores( alloc, id = basename(tempfile("resv")), pid = Sys.getpid(), path = getOption("clusters.reservationsPath") ) releaseCores( id = NULL, pid = Sys.getpid(), path = getOption("clusters.reservationsPath") )
path |
Directory holding the ledger. Defaults to
|
alloc |
A data.frame with columns |
id |
Reservation id. One process may hold several clusters at once, so releases are keyed by reservation rather than by process. |
pid |
Owning process id; defaults to this process. |
reservationsPath() a file path; liveReservations() a data.frame
with columns id, pid, host, workers, created; reserveCores()
returns its reservation id; releaseCores() returns invisibly.
The ledger is a file, so it is shared by every process that can see that file
– which is what is needed when several worker panes on one machine each build
their own cluster over a shared fleet. If different machines build clusters
over the same fleet, point them all at one path on shared storage via
options(clusters.reservationsPath = ).
Uses vmstat (must be installed; it is by default on linux).
resourcesUsed(machines = "localhost", resource = "us")resourcesUsed(machines = "localhost", resource = "us")
machines |
Character vector of the name(s) of the PSOCK resource to
query, e.g., |
resource |
Character; which |
Returns the outputs from vmstat
This is for cleaning up cases where an interrupted optimization is leading to multiple files for the same .runName. This will remove duplicates, keeping only the most recent.
rmIncompleteDups( path, pattern = "^(.+)\\_[[:digit:]]{5,8}.*\\.png", delete = FALSE )rmIncompleteDups( path, pattern = "^(.+)\\_[[:digit:]]{5,8}.*\\.png", delete = FALSE )
path |
A folder in which to search for duplicates |
pattern |
The regular expression to search for, to identify the files. This must have 1 set of parentheses (), as only the content between the () will be used for duplicate assessment, i.e., remove anything in the file that shouldn't be used. |
delete |
Logical. Default |
For side effects: removed files
hosts
Runs test on each machine in hosts
runTests( hosts, repos = c("predictiveecology.r-universe.dev", getOption("repos")), clustersBranch = "main", RscriptPath = "/usr/local/bin/Rscript", Npops = 100 )runTests( hosts, repos = c("predictiveecology.r-universe.dev", getOption("repos")), clustersBranch = "main", RscriptPath = "/usr/local/bin/Rscript", Npops = 100 )
hosts |
Character vector of host names to test. |
repos |
Repositories to install from on each host. |
clustersBranch |
Branch of this package to install on the hosts. |
RscriptPath |
Path to |
Npops |
Integer; population size the timings are scaled to. |
a list; same as getHostCombination return.
The project library is rsynced to each host, but the system libraries those
compiled packages link against are not. A host missing one fails at load with
e.g. libtbb.so.12: cannot open shared object file – RcppParallel, and so
everything that depends on it. The obvious fix, sudo apt-get install, needs
root on every host, which is exactly what a shared research cluster tends not
to give you.
It is not needed. A shared library is just a file: copy it into a
user-writable directory on the host and point LD_LIBRARY_PATH at it. This
detects what is actually missing, ships only that, and returns the directory
so the caller can add it to the workers' environment.
shipSystemLibs(cl, hosts, libs, dest = "~/.local/lib/clusters", verbose = TRUE)shipSystemLibs(cl, hosts, libs, dest = "~/.local/lib/clusters", verbose = TRUE)
cl |
A running cluster with one worker per host (the probe cluster). |
hosts |
Character vector of host labels, in the same order as |
libs |
Library path(s) whose compiled code should be checked. |
dest |
Directory on each host to copy into. Must be user-writable;
defaults to |
verbose |
Logical; report what is shipped. |
Invisibly, a named list with dest (as given), destResolved (the
absolute directory on each host), ldPath (each host's existing
LD_LIBRARY_PATH), shipped (per host, the sonames copied) and unmet
(per host, anything still missing that this will not ship).
The core runtime – libc, libstdc++, libgcc_s, libm, libpthread,
libdl, librt and the loader itself. Those must be the host's own: they are
what everything else on the machine is already linked against, and overriding
them via LD_LIBRARY_PATH breaks far more than it fixes. If one of those is
genuinely missing, the host is not usable and should be reported, not patched.
Only when the master and the host agree on architecture and libc version is copying a binary between them sound. Hosts that disagree are skipped with a message rather than given a library that cannot load, or worse, one that can.
LD_LIBRARY_PATH
It must be in the worker's environment before R starts. glibc parses
LD_LIBRARY_PATH once at process startup, so Sys.setenv() from inside R –
which is what parallelly's rscript_envs does, via
Rscript -e 'Sys.setenv(...)' – has no effect on later dlopen(). Verified:
with the library shipped but only Sys.setenv(), library(fireSenseUtils)
still fails; with env LD_LIBRARY_PATH=... Rscript, it loads. Prefix the
worker command instead.
dirNew and tableFiles
Convenient wrapper.
summaryOutputFolder( path, pattern = "^.+hists/(.+)\\_iter.+\\_[[:digit:]]{6,8}.*\\.png" )summaryOutputFolder( path, pattern = "^.+hists/(.+)\\_iter.+\\_[[:digit:]]{6,8}.*\\.png" )
path |
A folder in which to search for duplicates |
pattern |
The regular expression to base the |
Tabulation of the files, based on the pattern.
pattern
Using a regular expression, with a single (.+) identifying the parts of
the filenames to keep, and therefore to base the table on. Everything
before
tableFiles( files, pattern = "^.+hists/(.+)\\_iter.+\\_[[:digit:]]{6,8}.*\\.png" )tableFiles( files, pattern = "^.+hists/(.+)\\_iter.+\\_[[:digit:]]{6,8}.*\\.png" )
files |
A vector of full filenames |
pattern |
The regular expression to base the |
Tabulation of the files, based on the pattern.
termsInDEoptimtermsInDEoptim
termsInDEoptim(fireSense_spreadFormula, thresh, numParams)termsInDEoptim(fireSense_spreadFormula, thresh, numParams)
fireSense_spreadFormula |
The formula to be submitted to |
thresh |
The threshold for accepting fits; e.g., from |
numParams |
The number of parameters (TODO: improve description) |
Test machines
testMachine(NcoresMax = parallel::detectCores(), N = 100, thinning = 5)testMachine(NcoresMax = parallel::detectCores(), N = 100, thinning = 5)
NcoresMax |
Integer; the largest worker count to time. |
N |
Integer; workload multiplier for the reported totals. |
thinning |
Integer; step between the worker counts that are timed. |
Unique per master process. This was a single shared /tmp/fireSense_SpreadFit for every job,
while the cleanup step removes dirname(therePath) recursively – so concurrent jobs deleted
each other's staged objects mid-read.
transferDirName()transferDirName()
A directory path, as a length-one character vector.
plan_psock_min() rsyncs the master's library to the same absolute path on
each host and prepends it. Three things can silently go wrong with that, and
each produces a worker that dies later rather than a build that fails now:
R version drift. A library built under one R major.minor is ignored by
another, which then falls back to the system library and cannot find the
packages at all. (plan_psock_min() has always checked this one.)
Package version drift. The rsync only carries the dependency closure of
pkgsNeeded, so anything outside it can differ between master and worker –
including the packages doing the orchestrating.
A library that is present but unloadable, typically a missing system
library (libtbb.so.12 for RcppParallel, libgdal.so.NN for sf/terra) or a
binary built against a different GDAL.
This checks all three against the master and reports every offending host at once, rather than failing on the first or – worse – succeeding and letting the workers die one at a time inside the objective function.
verifyClusterHosts(cl, hosts, pkgs, libs, action = c("stop", "drop"))verifyClusterHosts(cl, hosts, pkgs, libs, action = c("stop", "drop"))
cl |
A running cluster with one worker per host (the probe cluster). |
hosts |
Character vector of host labels, in the same order as |
pkgs |
Character vector of packages that a worker must be able to load. |
libs |
Library paths to prepend on each worker (the master's). |
action |
|
Character vector of hosts that passed, invisibly. With
action = "stop" that is always hosts.
DEoptim object pars
Make histograms of DEoptim object pars
visualizeDE(DE, cachePath, titles, lower, upper)visualizeDE(DE, cachePath, titles, lower, upper)
DE |
An object from a |
cachePath |
A |
titles |
Character vector of parameter names, one per histogram. |
lower |
Named numeric of lower bounds, used to fix each panel's x range. |
upper |
Named numeric of upper bounds, used to fix each panel's x range. |