Package 'pjrt'

Title: R Interface to PJRT
Description: Provides an R interface to PJRT (Pluggable Jit RunTime), which compiles StableHLO and HLO programs with the XLA compiler and runs them on hardware backends such as CPU and GPU.
Authors: Sebastian Fischer [cre, aut] (ORCID: <https://orcid.org/0000-0002-9609-3197>), Daniel Falbel [aut] (ORCID: <https://orcid.org/0009-0006-0143-2392>), Hugh Graham [ctb] (ORCID: <https://orcid.org/0000-0001-9451-5010>), Daniel James [cph] (Author of the Boost.ContainerHash hash_combine copied in src/hash.h (BSL-1.0); see inst/COPYRIGHTS), The OpenXLA Authors [cph] (Authors of the PJRT C API and XLA FFI headers in inst/include and the XLA protobuf definitions in inst/proto (Apache-2.0); see inst/COPYRIGHTS), Christian Rau [cph] (Author of the half-precision library half.hpp bundled in src/half (MIT); see inst/COPYRIGHTS)
Maintainer: Sebastian Fischer <[email protected]>
License: MIT + file LICENSE
Version: 0.7.0
Built: 2026-11-10 17:49:53 UTC
Source: https://github.com/r-xla/pjrt

Help Index


pjrt: R Interface to PJRT

Description

Provides an R interface to PJRT (Pluggable Jit RunTime), which compiles StableHLO and HLO programs with the XLA compiler and runs them on hardware backends such as CPU and GPU.

Environment Variables

Configuration options provided by XLA

XLA provides various configuration options, but their documentation is scattered across various websites. The options include:

  • TF_CPP_MIN_LOG_LEVEL: Logging level for PJRT C++ API:

    • 0: shows info, warnings and errors

    • 1: shows warnings and errors

    • 2: shows errors

    • 3: shows nothing

  • XLA_FLAGS: See the openxla website for more information. Among these, the following flags control HLO dumping and are used by inspect_hlo() to inspect the XLA IR:

    • ⁠--xla_dump_to=<dir>⁠: Directory XLA writes its HLO dump files into.

    • --xla_dump_hlo_as_text: Dump the HLO modules as text (before and after optimizations).

    XLA reads XLA_FLAGS only once, before the first compilation, so set it at the start of a fresh R session, e.g. Sys.setenv(XLA_FLAGS = "--xla_dump_to=/tmp/hlo --xla_dump_hlo_as_text").

Configuration options provided by this package

  • PJRT_PLATFORM: Default platform to use, falls back to "cpu".

  • ⁠PJRT_PLUGIN_PATH_<PLATFORM>⁠: Path to custom plugin library file for a specific platform (e.g., PJRT_PLUGIN_PATH_CPU, PJRT_PLUGIN_PATH_CUDA). If set, the package will use this path instead of downloading the plugin.

  • ⁠PJRT_PLUGIN_URL_<PLATFORM>⁠: URL to download plugin from for a specific platform (e.g., PJRT_PLUGIN_URL_CPU, PJRT_PLUGIN_URL_CUDA). If set, overrides the default plugin download URL.

  • PJRT_INSTALL: Controls whether plugins may be downloaded automatically. Set this to "1" to always download without asking (e.g. in CI, scripts, or Docker builds), or to "0" to never download (the call errors with instructions instead). When unset, the package asks for confirmation in an interactive session and errors in a non-interactive one, so a script never triggers a surprise download.

  • PJRT_ZML_ARTIFACT_VERSION: Version of ZML artifacts to download. Only used for the CUDA plugin, which is downloaded from zml/pjrt-artifacts.

  • PJRT_CPU_DEVICE_COUNT: The number of CPU devices to use. Defaults to 1. This is primarily intended for testing purposes.

  • PJRT_CUDA_R_PACKAGE: Name of the R package providing CUDA libraries. Defaults to the value of pjrt.cuda. Set this to use a different CUDA toolkit package, but note that other versions may not work with the XLA plugin.

  • PJRT_DEBUG: If set (to any non-empty value), enables verbose debug output via cli::cli_inform().

Third-Party Licenses

The pjrt package itself is MIT-licensed. The CUDA backend dynamically loads NVIDIA software which is not bundled with pjrt, but downloaded from NVIDIA's official redistributable channels by the CUDA toolkit R package (e.g. pjrt.cuda) at install time. Its use is governed by the NVIDIA CUDA Toolkit EULA, with the exception of cuDNN, which is covered by the NVIDIA cuDNN SLA, and NCCL, which is covered by its own license. By installing or using the CUDA backend you accept those terms.

Author(s)

Maintainer: Sebastian Fischer [email protected] (ORCID)

Authors:

Other contributors:

  • Hugh Graham [email protected] (ORCID) [contributor]

  • Daniel James (Author of the Boost.ContainerHash hash_combine copied in src/hash.h (BSL-1.0); see inst/COPYRIGHTS) [copyright holder]

  • The OpenXLA Authors (Authors of the PJRT C API and XLA FFI headers in inst/include and the XLA protobuf definitions in inst/proto (Apache-2.0); see inst/COPYRIGHTS) [copyright holder]

  • Christian Rau (Author of the half-precision library half.hpp bundled in src/half (MIT); see inst/COPYRIGHTS) [copyright holder]

See Also

Useful links:


Convert a PJRTBuffer to an R Array

Description

Transfer buffer data from device to host and return an R array.

With await = FALSE, the transfer runs asynchronously: the array is returned immediately with its final type, dim and class, and reading its values blocks until the transfer has completed. Use is_ready() to check whether it has (non-blocking) and await() to block until it has. The values are then checked when they are first read, or by await().

check = "err" requires await = TRUE. With await = FALSE the check runs only once the values are first read, and that read can come from any code – print(), arithmetic, another package's C code. An error would then surface there, far from the as_array() call that caused it, and interrupt code that does not expect reading a vector to fail. A warning does neither, so check = "warn" and check = FALSE are allowed.

Usage

## S3 method for class 'PJRTBuffer'
as_array(x, check = "warn", await = TRUE, ...)

Arguments

x

(PJRTBuffer)
Buffer to convert.

check

(character(1) | FALSE)
How to report a materialized value that R's type cannot hold: "warn" (the default) warns and returns it anyway, "err" aborts (only with await = TRUE), and FALSE skips the scan altogether. TRUE is not accepted — with two levels of strictness it does not say which one is meant.

The cases scanned for are:

  • i32 / i64: any NA in the result. R's NA_integer_ shares the bit pattern INT_MIN; bit64's NA_integer64_ shares INT64_MIN. A legitimate device value at those bit patterns is indistinguishable from NA once materialized in R.

  • ui64: any negative value in the result. ui64 is stored as bit64::integer64 (signed 64-bit), which wraps values ⁠>= 2^63⁠ to negative — exactly 2^63 becomes NA_integer64_, anything above becomes a non-NA negative integer64.

Each case is a value the R type genuinely cannot hold, so the check has no false positives: it fires exactly when the returned vector would misrepresent the buffer.

No-op for float, boolean, and small/unsigned-32 integer dtypes — ui32 is stored as integer64 and has full headroom, so it cannot produce a wrapped or NA value.

await

(logical(1))
Whether to wait for the transfer to the host (the default). If FALSE, return immediately with an array whose values arrive asynchronously.

...

Additional arguments (unused).

Value

An R array (or vector for shape integer()).

See Also

is_ready(), await(), pjrt_execute()

Examples

buf <- pjrt_buffer(c(1.0, 2.0, 3.0, 4.0), shape = c(2, 2), dtype = "f32")
as_array(buf)

# Start the transfer, and only block when the values are read
result <- as_array(buf, await = FALSE)
is_ready(result)
result

Convert to PJRT Client

Description

Convert a platform name to a PJRT client or verify that an object is already a client.

Usage

as_pjrt_client(x)

Arguments

x

(PJRTClient | character(1))
Either a PJRT client object or a platform name (e.g., "cpu", "cuda").

Value

PJRTClient

Examples

# Convert from platform name
client <- as_pjrt_client("cpu")
client

Convert to PJRT Device

Description

Convert a platform name or device to a PJRT device object.

Usage

as_pjrt_device(x)

Arguments

x

(PJRTDevice | character(1) | NULL)
Either a PJRT device object, a platform name (e.g., "cpu", "cuda"), a device specification with index (e.g., "cpu:0", "cuda:1" for 0-based indexing), or NULL (defaults to first CPU device).

Value

PJRTDevice


Convert to PJRT Plugin

Description

Convert a platform name to a PJRT plugin or verify that an object is already a plugin.

Usage

as_pjrt_plugin(x)

Arguments

x

(any)
Object to convert to a PJRT plugin. Currently supports PJRTPlugin and character(1).

Value

PJRTPlugin

Examples

# Convert from platform name
plugin <- as_pjrt_plugin("cpu")
plugin

Convert PJRTElementType to string

Description

Get a (lowercase) string representation of a PJRT element type

Usage

## S3 method for class 'PJRTElementType'
as.character(x, ...)

Arguments

x

A PJRT element type object.

...

Additional arguments (unused).

Value

A string representation of the element type.


Await an async operation

Description

Block until the async operation is complete and return the object.

For a PJRTBuffer, waits for its computation or transfer. For an array returned by as_array(x, await = FALSE), waits for its transfer to the host and materializes the array. Any other R object is returned as is.

Usage

await(x, ...)

Arguments

x

An async value object.

...

Additional arguments (unused).

Value

The awaited object (invisibly).


Build Tree

Description

Captures the nesting structure of an object as an opaque RTree. Each leaf in the input corresponds to its position in the flat list produced by flatten(). The resulting tree can be passed to unflatten() to reconstruct the original structure from a flat list.

NULL is an empty node: it holds no leaf and contributes nothing to the flat list, but is preserved in the tree (so list(x, NULL) and list(NULL, x) have distinct trees).

The RTree and its operations are pjrt's R analog of JAX's pytree, which is where the idea comes from.

Usage

build_tree(x, is_leaf = NULL)

Arguments

x

(any)
Object whose structure to capture. Bare lists and objects of a class registered with register_tree_node() are recursed into; everything else is a leaf.

is_leaf

(function | NULL)
As for flatten(): a value it returns TRUE for is a leaf. Pass the same one to both, so that the tree and the flat list agree.

Value

A RTree.

See Also

flatten(), unflatten(), tree_size()

Examples

x <- list(a = 1, b = list(c = 2, d = 3))
tree <- build_tree(x)
tree_size(tree)

unflatten(tree, flatten(x))

Copy Buffer to Device

Description

Copy a PJRTBuffer to a different device. Returns a new buffer on the target device; the original buffer is unchanged.

If the buffer already lives in the requested device, no copy is performed.

When the target device belongs to a different client (e.g. copying from CPU to CUDA), the transfer is performed via a host roundtrip.

Usage

copy_buffer(buffer, device)

Arguments

buffer

(PJRTBuffer)
A PJRT buffer object.

device

(PJRTDevice | character(1))
The target device. A PJRTDevice object or a device specification (e.g., "cpu:0", "cpu:1", "cuda:0").

Value

A new PJRTBuffer on the target device.

Examples

buf <- pjrt_buffer(c(1, 2, 3), device = "cpu")
buf2 <- copy_buffer(buf, "cuda")
device(buf2)

Devices

Description

Get the addressable devices.

Usage

devices(x = NULL, ...)

Arguments

x

An object to get devices from: a PJRTClient, a PJRTLoadedExecutable, or NULL (default client).

...

Additional arguments (currently unused).

Value

list of PJRTDevice

Examples

# Create client (defaults to CPU)
client <- pjrt_client()
devices(client)

Native eager-dispatch fast path

Description

Dispatch a call through a dispatcher()'s executable cache, compiling on a miss. An input the dispatcher cannot execute is an error, named after the offending argument; there is no fallback for the caller to take.

Usage

dispatch(dispatcher, args)

dispatcher_size(dispatcher)

Arguments

dispatcher

(Dispatcher)
A dispatcher from dispatcher().

args

(list)
The (already evaluated) argument list of the call.

Value

dispatch() returns the call's result. With backend = "pjrt" that is the output buffers wrapped into "AnvlArray"s and re-nested by the compile callback's out_tree (see dispatcher()); with any other backend it is whatever the compiled closure returned.

dispatcher_size() returns the number of compiled executables the dispatcher currently caches.


Dispatcher

Description

A native fast path for repeatedly executing a compiled program on inputs of the same signature, intended to be used in anvl. It owns an executable cache keyed on the inputs' structure and abstract values, calling back into R to compile only on a cache miss.

dispatcher() creates a dispatcher for one program family; dispatch() runs a call through it and returns the call's result.

Usage

dispatcher(
  capacity,
  compile,
  static = character(),
  backend = "pjrt",
  move_inputs = FALSE,
  default_device = NULL,
  extractor = NULL,
  context = NULL
)

Arguments

capacity

(integer(1))
Maximum number of entries to cache; the least recently used one is evicted beyond that.

compile

(function)
Cache-miss callback, called as compile(info). info carries what the dispatch already derived from the call, so that the callback need not classify the inputs a second time:

  • args: the dispatched argument list,

  • in_tree: the RTree of args (see build_tree); its root's children are the arguments,

  • leaves: the flat leaf list (see flatten),

  • is_static: a logical() mask over leaves,

  • avals: per leaf, NULL if static, else the list(kind, dtype, shape) the cache key was built from. kind is "array" for one of the backend's arrays and "rdata" for a bare R literal or array – the two are different cache keys, because bare R data has no dtype of its own and the caller may compile it into a different program. dtype is a canonical dtype string ("f32", "i64", ...) for an "array" leaf, and for an "rdata" leaf its R storage type instead – its typeof(), so "double", "integer" or "logical". That is what the value is, not a dtype it is not yet: "double" is not "f64", and what the leaf is uploaded at is input_dtypes. shape is an integer(), empty for a scalar.

  • default_device: the device this call resolved because no array input named one – the device the cache key was built on, so compile must compile for it rather than resolve a default of its own. NULL when an array named the device, or under move_inputs.

  • context: what the context resolver returned for this call – the vector the cache key was built on, so compile must compile under it rather than resolve its own. NULL when the dispatcher has no context.

For backend = "pjrt" it must return a named list with:

  • exec: a pjrt_compiled executable,

  • client, device: the pjrt_client and the pjrt_device the entry is compiled for; bare R inputs are uploaded to that device,

  • out_tree: the RTree of the outputs (see build_tree),

  • out_avals: one aval per output leaf of out_tree, each a ⁠list(dtype = <string>, shape = <integer>)⁠. The outputs are wrapped from these.

  • const_arrays (optional): a list of PJRTBuffers prepended to the inputs,

  • phantom_specs (optional): a list of ⁠list(dtype = <string>, shape = <integer>)⁠, one per buffer to allocate fresh on every run and pass last, as an output-donation target,

  • implicit_inputs (optional): a list of list(get, dtype, shape), one per value the program takes that no argument supplies, e.g. a global RNG state. The program takes them after the call's own inputs (and before the phantoms). Before every run the engine calls get(), which returns a PJRTBuffer of dtype and shape; it is passed on, copied to the entry's device if it lives on another one. A value can so stay on the device from run to run, e.g. the ⁠$data⁠ of an implicit output that on_success keeps. An implicit input is not part of the cache key, which is resolved before the lookup, while only the program decides that it needs one: a buffer of another dtype or shape is an error. A caller whose implicit inputs can change type puts their types into context, so that a change compiles a new entry.

  • implicit_outputs (optional): a list of ⁠list(dtype = <string>, shape = <integer>)⁠, one per value the program returns beyond the out_tree's outputs, after them, e.g. an error code. They are not part of the call's result but are handed to on_success, which is then required.

  • on_success (optional): a function called with the list of the implicit outputs, wrapped like the other outputs, and the call's argument list args, after every run of the entry that was launched without an error, before the result is rebuilt from the out_tree (which runs the unflatten of a class registered with register_tree_node(), and may fail), e.g. to advance what an implicit input's get() reads, or an object among the arguments. Execution is asynchronous: the outputs may not be computed yet.

The program's inputs are, in order: const_arrays, the call's dynamic leaves, the implicit inputs, the phantoms. Its outputs are the leaves of out_tree followed by the implicit outputs; a count that disagrees with the executable's is an error on its first run.

Either kind of result may additionally carry:

  • uses_default_device: FALSE declares that the entry does not depend on info$default_device – its program fixes its own device, e.g. that of an array it closes over – so it is served whatever the default device is, rather than recompiled when the default changes. Default TRUE. Only read when info$default_device is not NULL.

  • input_dtypes: a character() with one entry per dynamic leaf, in order, naming the dtype that input is supplied at. NA leaves an input alone, and is the only valid entry for an array input: an array is supplied as it is, so declaring a dtype for one is an error rather than a no-op.

    With backend = "pjrt" every bare R leaf must name a dtype: bare R data has no dtype of its own, and only the compiled program knows what it is used as, so the engine uploads it at the dtype declared here and never guesses one. It is what lets a program that consumes an R double as f64 get the exact value rather than one rounded through f32 first. The "rdata" aval's dtype is the leaf's R storage type, so it is never an answer to this: the callback names a real dtype. The field may be omitted only for a call whose inputs are all arrays.

    Not every R storage type uploads at every dtype, and a pair that cannot be uploaded is rejected here rather than at execute time: a "double" input takes any dtype, an "integer" input any but "bool", and a "logical" input only "bool".

    Any other backend uploads nothing: r_fun gets each R value itself, so no entry could take effect and every one of them must be NA. A declared dtype is rejected there rather than accepted and ignored, for the same reason it is at an array input. The field is still length-checked, and may still be omitted entirely.

For any other backend it must return a named list with:

  • r_fun: a function called with the list of the call's dynamic leaves, in order and with an array leaf contributing its ⁠$data⁠, returning the call's finished value. Static leaves are not passed: they are constants of the closure compile just built, and a cache hit already proves the call's statics are identical() to the ones it was built from.

implicit_inputs, implicit_outputs and on_success are an error here: r_fun reads its own inputs and runs its own code.

static

(character())
Names of top-level arguments that are static. Every leaf of a static argument is keyed by its value and excluded from execution: it is a constant of the compiled entry. A static leaf must not be an "AnvlArray". Defaults to none. Values are compared with identical(num.eq = FALSE), i.e. numbers compare bitwise, which is what keeps a bit64::integer64 NA – stored as the bit pattern of -0 – from sharing a cache entry with 0.

backend

(character(1))
The ⁠$backend⁠ tag every "AnvlArray" input must carry, and the tag stamped on wrapped outputs. It also selects the execution engine (see Backends); anvl's quickr backend passes "quickr".

move_inputs

(logical(1))
If TRUE, each cache entry has a target device – the device its compile call returned – and the engine places every input on it at execute time; the cache key then carries no device, so inputs may arrive from any device. Default FALSE: the first array's device is the call's device, and a conflicting input is an error.

It is a policy flag, not a device: which device an entry targets is the compile callback's business, and need not be the same for every entry (anvl's jit(device = ) fixes one up front).

Placing an input is the engine's business, since only it knows what ⁠$data⁠ holds. With backend = "pjrt" an input living elsewhere is copied to the entry's device. With any other backend pjrt does nothing, so r_fun must place its own inputs – it receives only their ⁠$data⁠, not their ⁠$device⁠, so the placing has to be idempotent.

default_device

(function | NULL)
Called with no arguments to get the backend's current default device, whenever a call has no array input to read a device from. Its result is part of the cache key, so an entry compiled under one default device is never served after the default changes, unless compile declared the entry independent of it (uses_default_device = FALSE). Required unless move_inputs = TRUE, which fixes the device per entry.

With backend = "pjrt" it must return a pjrt_device, and devices are compared by the PJRT device they wrap. For any other backend, devices are compared by object identity first and by identical() as a fallback, so equal-but-distinct device objects count as one device. Interning them (one object per device, alive for the session) is therefore not required, but is recommended: an interned device resolves in one pointer comparison, and every distinct object a backend hands out stays alive for the dispatcher's lifetime.

extractor

(function | NULL)
Reads a non-"pjrt" array's metadata via the backend's accessors, called as extractor(leaf) and returning list(aval = list(dtype, shape), device, backend) – dtype a xlamisc DataType, shape an integer(). The aval's kind is not the extractor's to say: whatever it returns is an array leaf. Required for any backend other than "pjrt"; ignored for "pjrt" (see Backends).

context

(function | NULL)
Called with no arguments on every dispatch to get whatever the compiled program depends on beyond its inputs, such as anvl's current default dtypes or the types of the values implicit_inputs read. Must return a character() without NAs; its value is part of the cache key, so an entry compiled under one context is never served under another, and it reaches compile as info$context. Unlike default_device, which is consulted only when no array names a device, this is resolved for every call: any entry may depend on it. NULL (default) keys on the inputs alone.

Details

Each dispatch() call flattens the arguments (see flatten()) and builds a cache key from the argument tree, the call's device, the context and one entry per leaf: a dynamic leaf contributes its abstract value – its kind (one of the backend's arrays, or bare R data), its dtype and its shape – and a static leaf its value. On a hit the cached entry runs immediately; on a miss compile is called to produce a new entry, and once capacity entries are cached the least recently used one is evicted.

Inputs are validated before the cache is probed, and a rejection names the offending argument by its path in the argument tree. A dynamic leaf must be

  • an "AnvlArray" of the dispatcher's backend (an array of the "plain" backend, which captures a trace-time constant, is never an input), or

  • bare R data: an unclassed double, integer or logical vector that is either an is.array() value or a length-1 scalar other than NA.

Unless move_inputs = TRUE, all array inputs must also live on one device. compile is therefore never asked to validate: it is called only on a cache miss, and only for inputs already known to be executable.

Value

dispatcher() returns a Dispatcher, an external pointer to the native dispatcher and its cache.

Backends

The backend selects the execution engine, and everything backend-specific sits behind it:

  • backend = "pjrt" executes a compiled PJRT executable natively: array inputs contribute their ⁠$data⁠ buffer, bare R data is uploaded at the dtype the entry's input_dtypes declares for it, and the outputs are wrapped into "AnvlArray"s – lists of ⁠$data⁠ (the PJRTBuffer), ⁠$dtype⁠ (a xlamisc DataType), ⁠$shape⁠ and ⁠$backend⁠, with no ⁠$device⁠: the device is the buffer's own – and re-nested via out_tree, all without leaving C++.

  • any other backend calls the R closure r_fun that compile returned, which returns the call's finished value. Execution, output wrapping and input placement therefore stay under the backend's control. This is the path for any non-PJRT backend (e.g. anvl's "quickr").

Of an array input, the dispatcher reads only ⁠$data⁠ directly, plus ⁠$backend⁠ for "pjrt". For "pjrt" the dtype, shape and device are read off the PJRTBuffer; for any other backend all metadata comes from extractor. A backend is free to store it as fields or to compute it on demand.


Element Type

Description

Get the element type of a buffer.

Usage

elt_type(x)

Arguments

x

(PJRTBuffer)
Buffer.

Examples

buf <- pjrt_buffer(c(1.0, 2.0, 3.0))
elt_type(buf)

Flatten

Description

Recursively flattens a nested list into a single flat list containing only the leaf values, preserving left-to-right order.

A bare (class-less) list is recursed into; NULL contributes no leaves; an object of a class registered with register_tree_node() is recursed into through the children its flatten returns; everything else (a classed object, an atomic, a function, ...) is a leaf.

Use build_tree() to capture the nesting structure so it can be restored with unflatten().

Usage

flatten(x, is_leaf = NULL)

Arguments

x

(any)
Object to flatten.

is_leaf

(function | NULL)
Called on every value the walk meets other than NULL, the list x included; a value it returns TRUE for is a leaf, even a list or an object of a registered class, as with JAX's tree_flatten(is_leaf = ). Must return TRUE or FALSE. NULL (default) recurses into every list and registered object.

Value

list() containing the flattened values.

See Also

build_tree(), unflatten(), tree_size()

Examples

x <- list(a = 1, b = list(c = 2, d = 3))
flatten(x)

flatten(list(1:3, "hello"))

# stop at the list `b`
flatten(x, is_leaf = function(v) identical(v, x$b))

Flatten a Function

Description

Flattens a function taking in possibly nested RTree objects so it takes in flat inputs and return flat outputs (along the returned tree structure).

Usage

flatten_fun(f, in_tree)

Arguments

f

(function)
Function to wrap.

in_tree

(RTree)
Tree describing the (possibly nested) argument list of f, i.e. build_tree(list(arg1, arg2, ...)). The flat inputs to the wrapper follow this structure.

Value

A FlattenedFunction.

See Also

build_tree(), flatten(), unflatten()

Examples

f <- function(x) list(sum = x$a + x$b)
ff <- flatten_fun(f, build_tree(list(list(a = 1, b = 2))))
ff(3, 4)

Format Buffer Data

Description

Formats buffer data into a character vector of string representations of individual elements suitable for StableHLO.

Usage

format_buffer(buffer)

Arguments

buffer

(PJRTBuffer)
A PJRT buffer object.

Value

character() A character vector containing the formatted elements.

Examples

buf <- pjrt_buffer(c(1.5, 2.5, 3.5))
format_buffer(buf)

Inspect the HLO Source of a Program

Description

The result contains the program before and after optimization.

Usage

inspect_hlo(program, device = NULL)

Arguments

program

(PJRTProgram)
The program to inspect.

device

(NULL | PJRTDevice | character(1))
Device to compile for.

Value

A PJRTHloModuleSrcs

Enabling HLO dumping

Dumping is driven by the XLA compiler's own dump mechanism, enabled through the XLA_FLAGS environment variable. Two flags are required:

  • --xla_dump_hlo_as_text – dump the HLO in text form.

  • ⁠--xla_dump_to=<dir>⁠ – write the dump into ⁠<dir>⁠. inspect_hlo() reads the HLO back from the files XLA writes here.

XLA reads XLA_FLAGS once, before the first compilation in an R process, so both flags must be set at the very start of a fresh session, before any pjrt_compile() or pjrt_execute() call:

Sys.setenv(XLA_FLAGS = "--xla_dump_to=/tmp/hlo --xla_dump_hlo_as_text")
library(pjrt)
# ... build a program ...
inspect_hlo(prog)

Install PJRT Plugins

Description

Download and cache the PJRT plugins needed to run pjrt. The CPU plugin is always installed. The CUDA plugin is installed in addition when a CUDA-capable GPU is detected, or when cuda = TRUE is passed explicitly. Installing the CUDA plugin also installs the R package providing the CUDA libraries it links against (CUDA 13.3, shipped by pjrt.cuda by default, see PJRT_CUDA_R_PACKAGE in pjrt-package).

Plugins are otherwise downloaded lazily the first time a client is created, but the download requires user confirmation, unless the environment variable PJRT_INSTALL=1.

Usage

install_pjrt(cuda = NULL)

Arguments

cuda

(logical(1) | NULL)
Whether to also install the CUDA plugin. When NULL (the default), CUDA support is auto-detected: the CUDA plugin is installed when an NVIDIA GPU is available on a Linux machine (x86_64 or arm64).

Value

(character())
The platforms that were installed, invisibly.


Check if an async operation is ready

Description

Non-blocking check to see if an async operation has completed.

For a PJRTBuffer, whether its computation or transfer has finished. For an array returned by as_array(x, await = FALSE), whether its transfer to the host has finished. Any other R object is always ready.

Usage

is_ready(x, ...)

Arguments

x

An async value object.

...

Additional arguments (unused).

Value

TRUE if the operation has completed, FALSE otherwise.


Map Over a Tree

Description

Apply a function to each leaf of a (possibly nested) list, preserving the tree structure. Equivalent to flattening .x with flatten(), applying .f to each leaf, and reassembling with unflatten().

Usage

map_tree(.x, .f, ...)

Arguments

.x

(any)
A leaf or a (nested) list of leaves.

.f

(function)
Function to apply to each leaf of .x.

...

Additional arguments passed to .f.

Value

An object with the same nesting structure as .x, where each leaf is the result of .f(leaf, ...).

See Also

flatten(), build_tree(), unflatten()

Examples

map_tree(list(a = 1, b = list(c = 2, d = 3)), \(x) x + 1)

Create a PJRT Buffer

Description

Create a PJRT Buffer from an R object. Any numeric PJRT buffer is an array and 0-dimensional arrays are used as scalars. pjrt_buffer will create a array with dimensions (1) for a vector of length 1, while pjrt_scalar will create a 0-dimensional array for an R vector of length 1.

To create an empty buffer (at least one dimension must be 0), use pjrt_empty.

Important: Uploading a numeric vector at an integer element type rejects any value that type cannot hold: one outside its range, or a missing value, is an error rather than a wrapped or clamped result. A fractional value is not an error – it truncates toward zero, as as.integer() does – and the range is checked on the truncated value, so 255.7 still fits "ui8".

A missing value is not rejected where R's own NA and the element type already share a bit pattern and the vector travels zero-copy: an NA_integer_ at "i32" arrives as INT_MIN, and a bit64::integer64 NA at "i64" arrives as INT64_MIN. Both warn, and as_array() warns about them again on the way back, its check argument defaulting to "warn". At every other integer element type, and at "bool", a missing value is an error – including an integer64 NA at "ui64", where those same bits are the ordinary value 2^63.

At a floating-point element type a missing value is neither rejected nor warned about: it becomes NaN, from an NA_real_ and an NA_integer_ alike, as as.double() would give.

No other checks are performed when creating the buffer – a double too large for "f32", for instance, still becomes Inf.

pjrt_empty() allocates a buffer of the given shape and dtype with unspecified contents. The bytes should be treated as uninitialized — read them only after they have been written to (e.g. as a donated output of pjrt_execute()). Shapes with at least one zero-sized dimension are supported as a degenerate case (the buffer holds zero elements).

Usage

pjrt_buffer(data, dtype = NULL, device = NULL, shape = NULL, ...)

pjrt_scalar(data, dtype = NULL, device = NULL, ...)

pjrt_empty(dtype, shape, device = NULL)

Arguments

data

(any)
Data to convert to a PJRTBuffer.

dtype

(NULL | character(1) | DataType)
The type of the buffer. Currently supported types are:

  • "bool": boolean ("pred" and "i1" are accepted as aliases)

  • "{s,u}{8,16,32,64}": Signed and unsigned integer (for integer or double data).

  • "f{32,64}": Floating point (for double or integer data).

  • "bf16": bfloat16 (for double or integer data). Values are rounded to the nearest bfloat16 value, ties to even; the largest finite value is about 3.3895e38, and magnitudes at or above the overflow midpoint (about 3.3962e38) round to Inf. as_array() returns the exactly representable values as double.

  • "f16": IEEE 754 binary16 (for double or integer data). Values are rounded to the nearest binary16 value, ties to even; the largest finite value is 65504, and magnitudes at or above the overflow midpoint 65520 round to Inf. as_array() returns the exactly representable values as double. The default (NULL) depends on the method:

  • logical -> "bool"

  • integer -> "i32"

  • double -> "f32"

  • raw -> must be supplied

A double at an integer dtype is truncated toward zero, like as.integer() but without its 32-bit intermediate, so pjrt_buffer(2^40, dtype = "i64") stores 1099511627776 rather than overflowing. A value the dtype cannot hold is an error rather than a wrapped or clamped result, and the range is tested after truncation, so 255.7 still fits "ui8".

device

(NULL | PJRTDevice | character(1))
A PJRTDevice object or the name of the platform to use ("cpu", "cuda", ...), in which case the first device for that platform is used. The default is to use the CPU platform, but this can be configured via the PJRT_PLATFORM environment variable. A value the target dtype cannot hold is rejected whatever this is set to, so the flag only governs missing values.

shape

(NULL | integer())
The dimensions of the buffer. The default (NULL) is to infer them from the data if possible. The default (NULL) depends on the method.

...

(any)
Additional arguments. For raw types, this includes:

  • row_major: Whether to read the data in row-major format or column-major format. R uses column-major format.

Value

PJRTBuffer

Extractors

  • platform() -> character(1): for the platform name of the buffer ("cpu", "cuda", ...).

  • device() -> PJRTDevice: for the device of the buffer (also includes device number)

  • elt_type() -> PJRTElementType: for the element type of the buffer.

  • shape() -> integer(): for the shape of the buffer.

Converters

  • as_array() -> array | vector: for converting back to R (vector is only used for shape integer()).

  • as_raw() -> raw for a raw vector.

Reading and Writing

A buffer can also be serialized like any R object, e.g. with saveRDS(): its data is copied to the host, and unserializing it uploads the data to the default device (the first device of the PJRT_PLATFORM platform, "cpu" unless set) – not to the device it was saved from. A buffer saved on a GPU therefore also loads on a machine without one; use copy_buffer() to move it afterwards. A buffer referenced several times in one serialized object is written once, and loads as one buffer. A loaded buffer is a NULL external pointer until its first use (e.g. by as_array(), shape() or printing), which fills in its address. This requires serialization format version 3 (R's default); a buffer serialized under version 2 loads without its data.

Scalars

When calling this function on a vector of length 1, the resulting shape is 1L. To create a 0-dimensional buffer, use pjrt_scalar where the resulting shape is integer().

Examples

# Create a buffer from a numeric vector
buf <- pjrt_buffer(c(1, 2, 3, 4))
buf

# Create a buffer from a matrix
mat <- matrix(1:6, nrow = 2)
buf <- pjrt_buffer(mat)
buf

# Create an integer buffer from an array
arr <- array(1:8, dim = c(2, 2, 2))
buf <- pjrt_buffer(arr)


# Create a scalar (0-dimensional array)
scalar <- pjrt_scalar(42, dtype = "f32")
scalar


# Allocate an uninitialized 2x3 f32 buffer (contents are unspecified)
empty <- pjrt_empty(dtype = "f32", shape = c(2, 3))
empty

Create a Client

Description

Create a PJRT client for a specific device.

Usage

pjrt_client(platform = NULL, ...)

Arguments

platform

(character(1) | NULL)
Platform name (e.g., "cpu", "cuda"). If NULL, use PJRT_PLATFORM environment variable or default to "cpu".

...

Additional options passed to the PJRT client creation. For CPU clients, you can pass cpu_device_count to specify the number of CPU devices. You can also configure this via PJRT_CPU_DEVICE_COUNT environment variable.

Value

PJRTClient

Extractors

  • platform() for a character(1) representation of the platform.

  • devices() for a list of PJRTDevice objects.

Examples

# Create a client (defaults to CPU)
client <- pjrt_client()
client

Compile a Program

Description

Compile a PJRTProgram program into a PJRTExecutable.

Usage

pjrt_compile(
  program,
  compile_options = new_compile_options(),
  device = NULL,
  xla_options = list()
)

Arguments

program

(character(1))
A program to compile.

compile_options

(PJRTCompileOptions)
Compile options.

device

(NULL | PJRTDevice | character(1))
A PJRTDevice object or the name of the platform to use ("cpu", "cuda", ...), in which case the first device for that platform is used. The default is to use the CPU platform, but this can be configured via the PJRT_PLATFORM environment variable.

xla_options

(named list())
XLA compiler options for this compilation only. Each is named after a field of XLA's DebugOptions message (xla.proto), the names XLA_FLAGS takes without the leading ⁠--⁠, and XLA applies it on top of its defaults and of XLA_FLAGS. The type of a value decides how XLA reads it: a logical(1) sets a boolean option, an integer(1) an integer option (or an enum by number), a double(1) a floating-point option, and a character(1) is parsed by the option's type – "True" or "False", a number, the name of an enum value, or a comma-separated list for a repeated option, which replaces its whole value. So an integer option takes 3L or "3", but not 3, and a single-precision option takes a double(1) only if it is positive, so 0 or a negative value has to be given as a string, e.g. "0". XLA reports an unknown name or a value of the wrong type when compiling.

An option of map type, such as xla_backend_extra_options, takes a character(1) in XLA_FLAGS syntax: comma-separated entries key=value, or key for an empty value, e.g. "xla_cpu_use_multi_output_fusion". Its entries are added to those the plugin applies by default, so entries set through XLA_FLAGS are kept. XLA does not check the entries: it ignores a key it does not know, and some keys count by their presence alone, so "xla_cpu_use_multi_output_fusion=false" turns that option on as well. On the CPU, XLA hands a key that does not start with xla_ to LLVM, which ends the R process if it does not know the key.

XLA's overrides cannot set a map, so pjrt reads the plugin's DebugOptions, with XLA_FLAGS applied, back from a small program it compiles once per client, adds the entries and sends the whole set. That differs from a compilation without map options in a few ways: XLA reads some options only when a compilation sends such a set (with xla_dump_to, it then also writes the compilation's inputs, and it dumps the small program too), and with --xla_flags_reset=true a later change to XLA_FLAGS does not reach compilations with map options. Reading the set back needs a plugin that reports the compile options it applies, which the CPU plugin does and the CUDA plugin does not; there, set the option through XLA_FLAGS before the plugin is loaded.

Value

PJRTExecutable

Examples

# Create a simple program
src <- r"(
func.func @main(\%arg0: tensor<2xf32>) -> tensor<2xf32> {
  return \%arg0 : tensor<2xf32>
}
)"
prog <- pjrt_program(src = src)
exec <- pjrt_compile(prog)

# Write the optimized HLO of this compilation to a directory
dir <- tempfile()
exec <- pjrt_compile(prog, xla_options = list(xla_dump_to = dir))
list.files(dir)

# Keep XLA:CPU from duplicating cheap operations into the fusions that use them
exec <- pjrt_compile(
  prog,
  device = "cpu",
  xla_options = list(xla_backend_extra_options = "xla_cpu_use_multi_output_fusion")
)

Create a PJRT Device

Description

Create a PJRT Device from an R object.

Usage

pjrt_device(device)

Arguments

device

(any)
The device.

Value

PJRTDevice

Extractors

  • platform() for a character(1) representation of the platform.

Examples

# Show available devices for CPU client
devices(pjrt_client("cpu"))
# Create device 0 for CPU client
dev <- pjrt_device("cpu:0")
dev

Execute a PJRT program

Description

Execute a PJRT program with the given inputs and execution options. Returns immediately with PJRTBuffer object(s) that may not be ready yet.

Important: Arguments are passed by position and names are ignored.

Inputs can be PJRTBuffer objects, including buffers that are not yet ready. PJRT handles the dependencies internally.

Use await() to block until the result is ready. Use is_ready() to check if execution has completed (non-blocking). Use as_array(x, await = FALSE) to chain an async buffer-to-host transfer.

Usage

pjrt_execute(
  executable,
  ...,
  execution_options = NULL,
  simplify = TRUE,
  check = TRUE
)

Arguments

executable

(PJRTLoadedExecutable)
A PJRT program

...

(PJRTBuffer)
Inputs to the program. Named are ignored and arguments are passed in order.

execution_options

(PJRTExecuteOptions)
Optional execution options for configuring buffer donation and other settings.

simplify

(logical(1))
If TRUE (default), a single output is returned as a PJRTBuffer. If FALSE, a single output is returned as a list of length 1 containing a PJRTBuffer.

check

(logical(1))
If TRUE (default), validate the arguments (executable type, that all inputs are PJRTBuffers and unnamed, execution options, and simplify). Trusted callers on a hot dispatch path can pass FALSE to skip this validation; the caller is then responsible for guaranteeing that executable, the inputs, and execution_options are valid.

Value

PJRTBuffer | list of PJRTBuffers

See Also

await(), is_ready(), as_array()

Examples

# Create and compile a simple identity program
src <- r"(
func.func @main(
  \%x: tensor<2x2xf32>,
  \%y: tensor<2x2xf32>
) -> tensor<2x2xf32> {
  \%0 = "stablehlo.add"(\%x, \%y) : (tensor<2x2xf32>, tensor<2x2xf32>) -> tensor<2x2xf32>
  "func.return"(\%0): (tensor<2x2xf32>) -> ()
}
)"
prog <- pjrt_program(src = src)
exec <- pjrt_compile(prog)

# Execute with input
x <- pjrt_buffer(c(1.0, 2.0, 3.0, 4.0), shape = c(2, 2), dtype = "f32")
y <- pjrt_buffer(c(5, 6, 7, 8), shape = c(2, 2), dtype = "f32")
pjrt_execute(exec, x, y)

Create Execution Options

Description

Create execution options for configuring how a PJRT program is executed, including buffer donation settings. Important: It is not enough to only mark a buffer as donatable (not not donatable) during runtime. The program also needs to specify this during compile-time via input-output aliasing (StableHLO attribute tf.aliasing_output).

Usage

pjrt_execution_options(non_donatable_input_indices = integer(), launch_id = 0L)

Arguments

non_donatable_input_indices

(integer())
A vector of input buffer indices that should not be donated during execution (0-based). Buffer donation allows the runtime to reuse input buffers for outputs when possible, which can improve performance. However, if an input buffer is referenced multiple times or needs to be preserved, it should be marked as non-donatable.

launch_id

(integer(1))
An optional launch identifier for multi-device executions. This can be used to detect scheduling errors in multi-host programs.

Value

PJRTExecuteOptions

Examples

# Create default execution options
opts <- pjrt_execution_options()

# Mark buffer 0 as non-donatable
opts <- pjrt_execution_options(non_donatable_input_indices = 0L)

Create PJRT Plugin

Description

Create a PJRT plugin for a specific platform.

Usage

pjrt_plugin(platform)

Arguments

platform

(character(1))
Platform name (e.g., "cpu", "cuda").

Value

PJRTPlugin

Extractors

Examples

plugin <- pjrt_plugin("cpu")
plugin

Create a PJRTProgram

Description

Create a program from a string or file path.

Usage

pjrt_program(src = NULL, path = NULL, format = c("mlir", "hlo"))

Arguments

src

(character(1)) Source code.

path

(character(1)) Path to the program file.

format

(character(1)) One of "mlir" or "hlo".

Value

PJRTProgram

Examples

# Create a program from source
src <- "
func.func @main(%arg0: tensor<2xf32>) -> tensor<2xf32> {
  return %arg0 : tensor<2xf32>
}
"
prog <- pjrt_program(src = src)
prog

Register a Custom Call Handler

Description

Register an XLA FFI handler for use with stablehlo.custom_call.

Handlers are C/C++ functions defined using the XLA FFI API (see xla/ffi/api/ffi.h shipped in pjrt's ⁠inst/include/⁠). They are passed to this function as external pointers.

A handler declared with XLA_FFI_DEFINE_HANDLER_SYMBOL() is an ordinary ⁠extern "C"⁠ symbol, so it can be handed over without writing any glue: compile the file with ⁠R CMD SHLIB⁠ (or ship it in a package's ⁠src/⁠), then pass base::getNativeSymbolInfo()(...)$address.

Registration is deferred: if the PJRT plugin for a given platform is not yet loaded, the handler is queued and registered automatically when pjrt_plugin() loads it.

Usage

pjrt_register_custom_call(target_name, handler, .package = NULL)

Arguments

target_name

(character(1))
The target name used in stablehlo.custom_call @target_name(...).

handler

A named list of external pointers (externalptr) to XLA_FFI_Handlers, keyed by PJRT platform name (e.g., list(host = ptr) or list(host = cpu_ptr, cuda = cuda_ptr)). Native symbols obtained from base::getNativeSymbolInfo() are external pointers too and are accepted as-is.

.package

(character(1) or NULL)
The package registering this handler. When provided, handlers are automatically removed from the registry when the package unloads.

Value

NULL (invisibly).


Platform Name

Description

Get the platform name of a PJRT buffer.

Usage

platform(x, ...)

Arguments

x

(PJRTBuffer)
The buffer.

...

Additional arguments (unused).

Value

character(1)

Examples

buf <- pjrt_buffer(c(1, 2, 3))
platform(buf)

Platform Support

Description

Which PJRT backends are available on which operating system and CPU architecture.

The possible support levels are:

  • "yes": The plugin is available and fully supported.

  • "wsl2": Only available via the Windows Subsystem for Linux (WSL2), i.e. by running R inside Linux.

  • "no": No plugin is available.

Usage

platform_support()

Value

(data.frame())
One row per operating system and architecture, with character columns os, arch, and one column per backend (cpu, cuda) holding the support level.

Examples

platform_support()

Get Plugin Attributes

Description

Get the attributes of a PJRT plugin. This commonly includes:

  • xla_version

  • stablehlo_current_version

  • stablehlo_minimum_version

But the implementation depends on the plugin.

Usage

plugin_attributes(plugin)

Arguments

plugin

(PJRTPlugin | character(1))
The plugin (or platform name) to get the attributes of.

Value

named list()

Examples

plugin_attributes("cpu")

Create PJRT Client

Description

Create a PJRT client for a specific plugin and platform.

Usage

plugin_client_create(plugin, platform, options = list())

Arguments

plugin

(PJRTPlugin)
The plugin to create a client for.

platform

(character(1))
The platform to create a client for.

options

(list())
Additional options to pass to the client.

Value

PJRTClient


Check if Plugin is Downloaded

Description

Check if one more more plugin is already downloaded.

Usage

plugins_downloaded(platforms = NULL)

Arguments

platforms

(character())
Platform names.

Value

logical(1)

Examples

# Check if CPU plugin is downloaded
plugins_downloaded("cpu")

Map Over Multiple Trees

Description

Apply a function leaf-wise over several trees with the same structure. All trees in .l must have identical structure.

Usage

pmap_tree(.l, .f, ...)

Arguments

.l

(list)
A non-empty list of trees, all with the same structure.

.f

(function)
Function to call with one leaf from each tree (positional arguments, in the order given by .l).

...

Additional arguments passed to .f after the per-tree leaves.

Value

A tree with the same structure as .l[[1]], where each leaf is .f(leaf_1, leaf_2, ..., leaf_n, ...).

See Also

map_tree(), flatten(), unflatten()

Examples

pmap_tree(list(list(a = 1, b = 2), list(a = 10, b = 20)), `+`)

Print a PJRT Buffer

Description

Print a PJRTBuffer.

Usage

## S3 method for class 'PJRTBuffer'
print(
  x,
  max_rows = getOption("pjrt.print_max_rows", 30L),
  max_width = getOption("pjrt.print_max_width", 85L),
  max_rows_slice = getOption("pjrt.print_max_rows_slice", max_rows),
  header = TRUE,
  footer = NULL,
  ...
)

Arguments

x

(PJRTBuffer)
The buffer.

max_rows

(integer(1))
The maximum number of rows to print, excluding header and footer. Set to -1 for no limit.

max_width

(integer(1))
The maximum width (in characters) of the printed buffer. Set to negative values for no limit. Note that for very small values, the actual printed width might be slightly smaller as at least one column will be printed. Also, this limit only affects the printed rows containing the actual data, other rows might exceed the width.

max_rows_slice

(integer(1))
The maximum number of rows to print for each slice. Set to -1 for no limit.

header

(logical(1))
Whether to print the header.

footer

(NULL or character(1))
The footer line to print. If NULL (default), prints the standard ⁠[ <PLATFORM><TYPE>{<SHAPE>} ]⁠ summary. Use "" to suppress.

...

Additional arguments (unused).


Register a Class as a Tree Node

Description

Makes the objects of a class nodes of a tree rather than leaves: flatten() and build_tree() recurse into the children flatten returns, unflatten() rebuilds an object of the class from them with unflatten, and tree_repr() (and so the tree's printer) renders the node with repr. This is JAX's register_pytree_node(), plus the printer.

Besides its children, flatten returns the object's aux data: whatever else unflatten needs to rebuild it, such as a setting that is not an array. The class and the aux data are part of the tree's structure, compared with identical(), so two trees that differ in either are not equal and the dispatcher compiles them separately. Keep the aux data small and put the arrays among the children.

An object is a node of the first of its classes that is registered. A registered class cannot be registered again: to change its functions, unregister it with unregister_tree_node() and register it anew. A tree keeps the registration it was built under: it unflattens and prints its objects with that registration's functions even after the class is unregistered, and it is not equal to a tree built under another registration of the class, so the dispatcher never serves an entry compiled under the old functions.

Usage

register_tree_node(class, flatten, unflatten, repr)

unregister_tree_node(class)

Arguments

class

(character(1))
The class.

flatten

(function)
Takes an object of the class and returns list(children, aux): its children, a bare list whose names the tree keeps, and its aux data, any value (NULL if there is none).

unflatten

(function)
Takes the children, a list named as flatten names them, and the aux data, and returns an object of the class.

repr

(function)
Takes the children's tree_repr() strings, a character vector named as flatten names the children, and the aux data, and returns the node's string, a character(1), which tree_repr() shows as it is. Render nodes that differ in their aux data or their children's names differently, so that trees that are not equal print differently.

Value

NULL, invisibly.

See Also

flatten(), build_tree()

Examples

point <- function(x, y, unit) {
  structure(list(x = x, y = y, unit = unit), class = "point")
}
register_tree_node(
  "point",
  flatten = function(p) list(list(x = p$x, y = p$y), p$unit),
  unflatten = function(children, unit) point(children$x, children$y, unit),
  repr = function(children, unit) {
    sprintf("point[%s](%s)", unit, paste(children, collapse = ", "))
  }
)
flatten(list(point(1, 2, "cm"), 3))
tree <- build_tree(list(point(1, 2, "cm"), 3))
tree
unflatten(tree, list(10, 20, 30))
build_tree(point(1, 2, "cm")) == build_tree(point(1, 2, "in"))
unregister_tree_node("point")

Top-Level Child Kinds

Description

The node kind ("leaf", "list", "object", or "null"; see tree_root_kind()) of each top-level child of a list or object tree.

Usage

tree_child_kinds(tree)

Arguments

tree

(RTree)
A tree whose root is a list or object node.

Value

A character vector, one element per top-level child.

Examples

tree_child_kinds(build_tree(list(1, list(2), NULL)))

Top-Level Child Names

Description

The names of a list or object tree's top-level children (with "" for unnamed slots), or NULL if the root has no children or carries no names.

Usage

tree_child_names(tree)

Arguments

tree

(RTree)
A tree as returned by build_tree().

Value

A character vector of child names, or NULL when the root is not a list or object node or is unnamed. An empty but named list (structure(list(), names = character(0))) returns character(0), not NULL: the tree preserves the named/unnamed distinction (see tree_equal()).

Examples

tree_child_names(build_tree(list(a = 1, b = 2)))
tree_child_names(build_tree(list(1, 2)))

Top-Level Child Sizes

Description

The number of leaves under each top-level child of a list or object tree.

Usage

tree_child_sizes(tree)

Arguments

tree

(RTree)
A tree whose root is a list or object node.

Value

An integer vector, one element per top-level child.

Examples

tree_child_sizes(build_tree(list(a = 1, b = list(2, 3), c = NULL)))

Concatenate Trees Under a New Root

Description

Builds a new list tree whose children are the given trees, reassigning leaf indices contiguously across all children (in order).

Usage

tree_concat(children, names = NULL)

Arguments

children

(list of RTree)
The child trees.

names

(NULL | character())
Optional names for the children.

Value

A RTree.

Examples

combined <- tree_concat(
  list(build_tree(list(1, 2)), build_tree(3)),
  names = c("value", "grad")
)
tree_size(combined)

Difference Between Trees

Description

Walks two trees in parallel and returns the first path/subtree pair where they diverge, or NULL if they are structurally identical. The returned prefix follows tree_path() syntax; a and b are the tree_repr() strings of the diverging subtrees.

Usage

tree_diff(a, b)

Arguments

a, b

(RTree)
Trees to compare, as returned by build_tree().

Value

NULL if a and b are structurally identical, otherwise a list with elements prefix, a, and b.

See Also

build_tree(), tree_path(), tree_equal()

Examples

tree_diff(build_tree(list(a = 1)), build_tree(list(a = list(1, 2))))

Compare Tree Structures

Description

Structural equality of two trees: identical node kinds, child structure, leaf positions, names, and the classes and aux data of object nodes (see register_tree_node()).

a == b (and a != b) is shorthand for this, except that comparing a tree with a non-tree is FALSE instead of an error.

Usage

tree_equal(a, b)

Arguments

a, b

(RTree)
Trees to compare, as returned by build_tree().

Value

logical(1)

See Also

tree_diff() to locate the first divergence.

Examples

tree_equal(build_tree(list(a = 1)), build_tree(list(a = 2)))
tree_equal(build_tree(list(a = 1)), build_tree(list(b = 1)))

build_tree(list(a = 1)) == build_tree(list(a = 2))

Filter a Tree by Top-Level Names

Description

Keeps only the top-level children whose names match names (in tree order), reindexing the remaining leaves so they map to contiguous positions in a flat list. The kept children are put under a list root, also when the root of tree is an object node.

Usage

tree_filter_by_names(tree, names)

Arguments

tree

(RTree)
A tree whose root is a named list or object node, as returned by build_tree().

names

(character())
Names of children to keep.

Value

A RTree containing only the selected children.

See Also

build_tree(), unflatten()

Examples

x <- list(a = 1, b = 2, c = 3)
sub <- tree_filter_by_names(build_tree(x), c("a", "c"))
tree_size(sub)
unflatten(sub, x[c("a", "c")])

Structural Tree Hash

Description

A hash of a tree's structure – node kinds, child structure, leaf positions, names, and the class and aux data of each object of a class registered with register_tree_node() – consistent with tree_equal(): trees that are tree_equal() hash to the same value. Intended as a cache key for tree-structured dispatch within a session: a class folds in by the address of its registration, so the hash of a tree with registered objects differs between sessions.

Usage

tree_hash(tree)

Arguments

tree

(RTree)
A tree as returned by build_tree().

Value

character(1), the structural hash.

See Also

tree_equal(), build_tree()

Examples

tree_hash(build_tree(list(a = 1))) == tree_hash(build_tree(list(a = 2)))
tree_hash(build_tree(list(a = 1))) == tree_hash(build_tree(list(b = 1)))

Per-Leaf Mask From Top-Level Groups

Description

A logical mask over the leaves (in flat order) that is TRUE for every leaf sitting under a top-level child whose name is in groups. An empty groups yields an all-FALSE mask.

Usage

tree_leaf_mask(tree, groups)

Arguments

tree

(RTree)
A tree whose root is a list or object node.

groups

(character())
Top-level child names to mark.

Value

A logical vector of length tree_size(tree).

See Also

tree_child_names()

Examples

tree_leaf_mask(build_tree(list(a = 1, b = list(2, 3))), "b")

Tree Path

Description

Returns the human-readable path for a single leaf identified by its flat index (e.g. "a$b" or "[[2]]"). Only descends into the branch containing the target leaf, making it efficient for error reporting.

Usage

tree_path(tree, i)

Arguments

tree

(RTree)
A tree as returned by build_tree().

i

(integer(1))
The flat index of the leaf.

Value

character(1) ("" for a leaf at the root).

See Also

build_tree(), flatten()

Examples

tree_path(build_tree(list(a = 1, b = list(c = 2))), 2L)

Tree Representation

Description

A structural string for a tree: "*" for a leaf, "NULL" for an empty node, "list(...)" for list nodes, and for an object of a class registered with register_tree_node() whatever its repr makes of it. A named list is tagged "list<named>(...)", so that a list whose names are all empty is told apart from an unnamed one. The string is canonical – trees that are not tree_equal() print differently – as far as the reprs of their classes are, and except for trees built under two registrations of a class, which may print alike.

Usage

tree_repr(tree)

Arguments

tree

(RTree)
A tree as returned by build_tree().

Value

character(1)

Examples

tree_repr(build_tree(list(a = 1, b = list(2, NULL))))

Tree Root Node Kind

Description

The kind of a tree's root node: "leaf" for a single leaf, "list" for a list node, "object" for an object of a class registered with register_tree_node(), or "null" for the empty (NULL) node.

Usage

tree_root_kind(tree)

Arguments

tree

(RTree)
A tree as returned by build_tree().

Value

character(1)

Examples

tree_root_kind(build_tree(1))
tree_root_kind(build_tree(list(1)))
tree_root_kind(build_tree(NULL))

Tree Size

Description

Counts the number of leaf nodes in a tree. This equals the length of the flat list produced by flatten() on the original structure.

Usage

tree_size(tree)

Arguments

tree

(RTree)
A tree as returned by build_tree().

Value

integer(1)

See Also

build_tree(), flatten()

Examples

tree_size(build_tree(list(a = 1, b = list(c = 2, d = 3))))

Unflatten

Description

Reconstructs a nested structure from a flat list by using a tree previously created with build_tree().

Usage

unflatten(tree, x)

Arguments

tree

(RTree)
Tree describing the target structure, as returned by build_tree().

x

(list)
Flat list of leaf values, typically produced by flatten().

Value

The reconstructed nested structure (list or single value).

See Also

flatten(), build_tree()

Examples

x <- list(a = 1, b = list(c = 2, d = 3))
tree <- build_tree(x)

unflatten(tree, flatten(x))
unflatten(tree, list(10, 20, 30))