A “Hello, World!” program for digital twins

Joe Lorentz
DataThings
Published in
7 min readAug 27, 2024
Photo by Zach Graves on Unsplash

You might be familiar with the concept of a “Hello, World!” program. If not, basically such example programs are usually used to introduce programming languages or frameworks by providing the simplest possible fragment that a user can reproduce, run and get a first output (e.g. printing “Hello, World!”). In this article, we try to do something similar for presenting the concept of a digital twin powered by our programmable database, GreyCat!

A digital twin is a computer application that reflects an existing system from the real, physical world, within the virtual, digital domain. The goal of such a digital copy is to provide an easy way to access and analyze data coming from the system and act accordingly. With this in mind, it is evident that even a “Hello, World!” digital twin cannot be represented with a few lines of code as the reflected real world system needs to be complex enough to be able to play around and gain tangible insights.

Cyclocity

The physical system of choice for our “Hello, World!” digital twin, is the rental bike service of JCDecaux, also known as Cyclocity. Cyclocity offers rental bike services in many cities across Europe as well as Japan. Within one city, users can pick a bike at any station and deposit it again on a different station of that cluster (usual across one city, and it’s satellite locations).

Fortunately for us, JCDecaux offers an API that anyone can use to access live availability data for bikes at all their stations. We used this API to build a digital twin providing access to the live data via a map interface, as well as creating profiles of the usual availability at each station at different times of the week. The whole backend, written in GreyCat language (GCL), requires less than 500 lines of code and the majority of this is to allow importing CSV data as well as querying new data via the API.

Example of a rental bike station of JCDecaux (source)

Coding a digital twin powered by GreyCat

The code to run the application locally on your own machine is available on github. We will not go into all the details of the code but take a quick look at some of the important parts to showcase the simplicity of creating digital twins with GreyCat. The following code snippet models the relationships of the bike rental system:

use util;

var stations_by_geo: nodeGeo<node<Station>>;
// will contain all contracts indexed by contract name
var contracts_by_name: nodeIndex<String, node<Contract>>?;

type Station {
number: int;
position: geo;
// will contain all static information about the Station
// you will want to put information that is not important inside a node to avoid loading it into memory on every Station resolve
detail: node<StationDetail>;

// will contain all dynamic data of a station indexed by the last update timestamp
records: nodeTime<StationRecord>;

//The stations Contract, Belongs To relationship in sql
contract: node<Contract>;

// build up profile of the availability
profile: node<StationProfile>;
}

type StationDetail {
name: String;
address: String;
banking: bool;
bonus: bool;
}

type StationRecord {
status: StationStatus;
bike_stands: int;
available_bike_stands: int;
available_bikes: int;
}
enum StationStatus {
open("open");
closed("closed");
}

type Contract {
name: String;
commercial_name: String?;
country_code: String?;
cities: Array<String>?;

//will contain a list of refs to all stations of the contract
//indexed by the station's number attribute
stations: nodeIndex<int, node<Station>>;
}

Let’s dig into this a bit. First we import the util module, which contains the GaussianProfile type used for profiling. Next we declare two module variables, which have a special meaning in GCL. GreyCat is a programmable database that stores all data as a graph, and module variables serve as entry points to the graph. For example, we model the index of rental stations like:

var stations_by_geo: nodeGeo<node<Station>>;

The variable is of type nodeGeo, which is a GCL data structure that allows us to store any data type (here the custom type Station) indexed by geographic location (i.e. longitude and latitude). In our code we can then retrieve individual stations, given the geo, and access all information stored within the Station type in an object-oriented way. This is especially useful, as the front-end of this application features a map where the user can select stations by clicking on it. Alternatively, we could also create an integer or time based indexation in GCL, but that is a topic for another article. If you wish to learn more about the node concept of GreyCat already, you can check our documentation on the topic here. The remainder of the above code creates model of the data we get from the JCDecaux API. The semantics should be fairly easy for anyone familiar with any other object-oriented programming language.

Another important part of this digital twin, is the ability to profile the availability at each station, i.e. to condense the historical data into tangible insights that operators could use to improve the rental system and plan for future extensions. In the code above you can see that each Station has an attribute called profile of type node<StationProfile>.

Why putting it in a node? Because of performance! Whenever we put objects inside a node, we enable to only fetch a pointer (64 bit identifier) in a first step and only resolve (i.e. read from disk) the internal object (which might be huge) only when we actually need it. In this case, the profile can indeed become a rather big object, and we want to make sure that we do not resolve this every time we access the parent Station object. Below you can see the code of the StationProfile type:

type StationProfile {
//24 * 7 slots, one for each hour of the week, will help to
// visualize the station activity in a typical week
hourlyProfile: GaussianProfile;

fn updateProfile(t: time, data: StationRecord) {
if (data.bike_stands > 0) {
//convert the time to the correct slot
var quantizer = Quantizer::new();
quantizer.configure([DenseDim { min: 0, max: 6, step: 1 }, DenseDim { min: 0, max: 23, step: 1 }]);
var date = t.toDateUTC();
var slot = quantizer.quantize([date.dayOfWeek(), date.hours()]);

this.hourlyProfile.add(slot, data.available_bikes as float / data.bike_stands as float);
}
}

fn profileToTable(): Table {
var table = Table::new(24);

var endSlot = 24 * 7;
var startSlot = 0;

while (startSlot < endSlot) {
var row = startSlot / 24;
var col = startSlot % 24;

var val = this.hourlyProfile.avg(startSlot);

table.set(row, col, val);
startSlot++;
}
return table;
}

The StationProfile type features a GaussianProfile, a utility type offered by GreyCat, to collect data in slots and extract the properties of the underlying Gaussian distribution (e.g. mean, std, etc.). Here we use one slot for each hour of each weekday (total of 24*7=168). The StationProfile also provides functions to update the internal GaussianProfile and return it as a table which we use to display the profile on the user interface.

We will not dig further into the importer or front-end code here, but feel free to do so on your own! You will see that the front-end is written in typescript and uses some web components we offer with GreyCat to help plot data curves and profiles.

Digital twin in action!

If you downloaded the code and followed the setup, you should be able to see the front-end application in your browser. The app features two main elements. On the left we offer a map that shows all biking stations and visualizes the availability ratio of each station with a colored circle using the well-known viridis colormap (purple = empty, yellow = full). The demo data covers 2 weeks, and we offer a slider to move over this period in steps of 1 hour.

As a nice example, head over to the city of Lyon in the south-east of France and click the play button next to the slider to observe how citizens seem to use the rental bikes to head to the offices in the center of the city and ride back to the outer city after work!

Observing the bike availability at Lyon during one day

To confirm this observation, we can use the other main feature offered on the right side of the application window. Here we display the profile of a selected station which is build for the entire available data. See below the profile of one of the central stations near the train station. The profile clearly shows a generally high availability on weekdays during the usual office hours. Although, Monday seems to be an outlier. Maybe this is the preferred home-office day in the area? The availability going down towards noon could also indicate that bikes are redistributed, which is a service that JCDecaux is offering to cities.

Note that all timestamps are given in UTC, which also shifts the profile slots. We did not want to bloat this simple demo with another API access to convert to the respective local time zones of the station.

Weekly profile of one central station at Lyon

Wrap-up

This article presents an easy, “Hello, World!” style, digital twin for the rental bike system of JCDecaux. Maybe we invoked your interest in GreyCat and you are now thinking about all the great potential you could unlock by creating a digital twin and using it to analyze your system!

Feel free to play around and extend this demo and explore the vast features of GreyCat which we cannot cover fully in a single blog-post like this.

Don’t hesitate to contact us to get more information on our technology or discuss how we could empower your specific business case!

--

--