R Code structure

Introduction

This document describes the OSPSuite-Rarrow-up-right package structure. The package provides OSPSuite functionality in the R programming language. This document covers the package elements, code structure, and components that interface between the OSPSuite .NET codebase and R.

OSPSuite-R communication with .NET

The OSPSuite-R package provides access to OSPSuite functionality implemented in .NET. The {rsharp} packagearrow-up-right enables communication between R and .NET using C++ as an intermediate layer. .NET communicates with C++ through a custom native host. C++ then communicates with R through the R .C interface. Use {rsharp} to load libraries compiled from the .NET code.

On the .NET side, the OSPSuite.R project in OSPSuite.Corearrow-up-right serves as the main entry point for R. This entry point provides access to required Core libraries, including OSPSuite.Core and OSPSuite.Infrastructure. To access PK-Sim functionality, use the separate entry point in the PK-Sim codebase: PKSim.Rarrow-up-right.

OSPSuite-R code structure

The package follows R package best practices for file and code structure. Unlike most R packages, OSPSuite-R uses an object-oriented design. This design reflects the object-oriented structure of PK-Sim and OSPSuite.Core in .NET.

Initializing the package

R loads package files alphabeticallyarrow-up-right, so zzz.Rarrow-up-right is evaluated last. This file calls .onLoad() to ensure all functions in other files are evaluated first. The zzz.R file checks that R is running the x64 version, then calls .initPackage(). The init-package.Rarrow-up-right file uses {rsharp} to call the OSPSuite-R package entry point in OSPSuite.Core.

Object oriented design and {rsharp} encapsulation

OSPSuite-R uses an object-oriented design. The package uses the R6arrow-up-right framework to create and work with objects. Calls through {rsharp} create objects in the .NET environment. Access these objects through getters, setters, and methods. Encapsulate objects passed from .NET to R in wrapper classes. The base wrapper class is DotNetWrapperarrow-up-right. All specific wrapper classes (for example, for simulations) inherit from DotNetWrapper.

The class handles basic object initialization. The initialize method (the R6 equivalent of a C# constructor) saves a reference to the .NET object internally:

DotNetWrapperarrow-up-right:

Wrapper classes encapsulate {rsharp} calls that work on objects. Users should never call {rsharp} directly. All {rsharp} calls are encapsulated in wrapper classes or their utility functions (see utilities files below).

Wrap each .NET class with a corresponding wrapper class. Define wrapper classes in separate files named after the R class. For example, the R Simulation class wraps an OSPSuite simulation and is defined in simulation.Rarrow-up-right.

The Simulation class derives from ObjectBase. ObjectBase extends DotNetWrapper by adding Name and Id properties:

simulation.Rarrow-up-right:

object-base.Rarrow-up-right:

Access simulation properties (for example, the Output Schema) through DotNetWrapper functionality:

simulation.Rarrow-up-right

R wrapper classes must implement a meaningful print function. Example:

simulation.Rarrow-up-right

Basic access to object methods and properties is often insufficient. Create utility functions for additional functionality and place them in separate utilities files. For example, see utilities-simulation.Rarrow-up-right. Utilities files contain R code that works on class objects. These files can also include {rsharp} calls to .NET functions that operate on objects. Don't place {rsharp} calls that only expose object properties or methods in utilities files. Put those in the R wrapper class.

By convention, prefix internal package functions with a dot. For example, use .runSingleSimulation instead of runSingleSimulation:

utilities-simulation.Rarrow-up-right

Communication between R and .NET has performance overhead. Minimize cross-language calls when possible.

Tasks and task caching

Tasks are reusable objects defined on the .NET side that provide functionality for other objects. Access tasks through Api.csarrow-up-right in OSPSuite.Core. The OSPSuite side creates tasks through the IoC containerarrow-up-right.

The following example shows how to use the hasDimension utility function to check if a dimension (provided as a string) is supported:

utilities-units.Rarrow-up-right:

The example calls the internal function .getNetTaskFromCache to retrieve the Dimension Task. To avoid repeated retrieval from .NET, tasks are cached on the R side. See get-net-task.Rarrow-up-right:

Tasks are cached in the tasksEnv[] list. If a task is not found in the cache, retrieve it from .NET through an {rsharp} call and add it to the cache for future use.

Tests

The OSPSuite-R package includes comprehensive tests. Find test code in testthatarrow-up-right. Tests ensure correct and consistent package functioning. They also serve as examples for understanding how objects are created and used.

Updating Core DLLs

The R package stores local copies of DLLs from OSPSuite.Core and PK-Sim in inst/lib/arrow-up-right. Update these DLLs when a newer version of the .NET codebase is released.

Run the update workflow

Use the update-core-files.yaml GitHub Actions workflow to update the Core DLLs. The workflow automates downloading files and creating a Pull Request.

To run the workflow:

  1. Open the workflow pagearrow-up-right on GitHub.

  2. Click Run workflow.

  3. Select the branch to run the workflow on (typically main).

  4. (Optional) Configure workflow inputs:

    • Branch name: Specify a custom branch name (default: update-core-files-YYYYMMDD-HHMMSS)

    • PR title: Specify the Pull Request title (default: "Update Core Files")

    • PR body: Specify the Pull Request description (default: auto-generated)

  5. Click Run workflow.

What the workflow does

The workflow performs these steps:

  1. Runs the R script .github/scripts/update_core_files.R to download core files from the latest PK-Sim build artifacts.

  2. Checks for changes in the inst/lib/ directory.

  3. If changes are detected:

    • Creates a new branch.

    • Commits the updated files.

    • Creates a Pull Request.

    • Builds SQLite libraries for macOS (arm64).

The workflow file is located at .github/workflows/update-core-files.yamlarrow-up-right.

Last updated