Skip to main content

Building Single-File Source Code

Learn how to build programmatically single-source applications on the Signaloid Cloud Compute Engine

You can build a single-source application on the Signaloid Cloud Compute Engine via an API request. The example single-source C application below calculates how uncertainties in empirical model parameters affect the uncertainty distribution of the model's output, for a model of a physical process. The original source code lives in Brown-Ham model example. This code calculates the output of the model and prints the result to the standard output.

#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <uxhw.h>

static void
loadInputs(double * G, double * M, double * Rs, double * b, double * gamma, double * phi)
{
double empiricalTaylorFactorValues[] = {
3.2, 3.9, 4.1, 3.2, 3.8, 3.8, 2.1, 3.0, 1.9, 3.9,
2.3, 2.2, 3.2, 2.2, 3.9, 2.2, 1.9, 3.2, 3.9, 3.1,
};

*M = UxHwDoubleDistFromSamples(empiricalTaylorFactorValues, sizeof(empiricalTaylorFactorValues)/sizeof(double));
*G = UxHwDoubleUniformDist(6E10, 8E10);
*Rs = UxHwDoubleMixture(UxHwDoubleGaussDist(1E-8, 2E-9), UxHwDoubleGaussDist(3E-8, 2E-9), 0.5);
*b = 2.54E-10;
*gamma = UxHwDoubleUniformDist(0.15, 0.25);
*phi = UxHwDoubleUniformDist(0.3, 0.45);
}

int
main(int argc, char * argv[])
{
double G, M, Rs, b, gamma, phi, sigmaCMpa;

loadInputs(&G, &M, &Rs, &b, &gamma, &phi);

sigmaCMpa = ((M*gamma)/(2.0*b))*(sqrt((8.0*gamma*phi*Rs)/(M_PI*G*pow(b, 2))) - phi)/1000000;

printf("Alloy strength (σc)\t\t= %.1E MPa\n", sigmaCMpa);

return 0;
}

Prerequisites

To build a single-source application via the API, you need to have an API key to access the Signaloid Cloud API. Please refer to Authentication for more details.

Set up the build request

The Source Code build request object has the following schema:

// API token generated from signaloid.io/settings/api
const apiToken = process.env.SIGNALOID_KEY || "scce_yourSignaloidCloudApiKey";
const baseURL = process.env.SIGNALOID_URL || "https://api.signaloid.io";
// Setup HTTP client
const axios = require("axios");
const signaloidClient = axios.create({
baseURL: baseURL,
});

// Setup request headers
signaloidClient.defaults.headers["Authorization"] = `${apiToken}`;
signaloidClient.defaults.headers["Content-Type"] = "application/json";

type SourceCodeBuildRequest = {
Code: string;
Language: "C" | "C++" | "Fortran";
// Optional
CoreID?: string; // Using lowest precision C0 Athens core if not specified
TraceVariables?: TraceVariableRequest[]; // Used to trace variables on Reference core runs
DataSources?: DataSource[]; // Setting optional default data source for tasks started from this build
Arguments?: string; // Setting optional default arguments for tasks started from this build
};

type DataSource = {
ResourceID: string;
ResourceType: "Gateway" | "Bucket" | "SignaloidCloudStorage";
Location: string;
};

type TraceVariableRequest = {
File: string;
LineNumber: number;
Expression: string;
};

Define your source code as a string

caution

Correctly Escaping Strings for special characters: Ensure that the Code field of the task request object is a valid JSON string. This might require you to make sure special characters are escaped properly. Refer to the documentation for your host language for details on how to escape special characters in strings.

Note

Escaping special characters in JavaScript: In this application we have chosen to manually escape the \n characters as \\n in the C code. This will allow the printf() statement to correctly receive the \n character instructing it to print a new line as intended. An alternative approach would have been to define the source code as a raw JavaScript string and use JSON.stringify() to escape the string.

const applicationCode = `#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <uxhw.h>

static void
loadInputs(double * G, double * M, double * Rs, double * b, double * gamma, double * phi)
{
double empiricalTaylorFactorValues[] = {
3.2, 3.9, 4.1, 3.2, 3.8, 3.8, 2.1, 3.0, 1.9, 3.9,
2.3, 2.2, 3.2, 2.2, 3.9, 2.2, 1.9, 3.2, 3.9, 3.1,
};

*M = UxHwDoubleDistFromSamples(empiricalTaylorFactorValues, sizeof(empiricalTaylorFactorValues)/sizeof(double));
*G = UxHwDoubleUniformDist(6E10, 8E10);
*Rs = UxHwDoubleMixture(UxHwDoubleGaussDist(1E-8, 2E-9), UxHwDoubleGaussDist(3E-8, 2E-9), 0.5);
*b = 2.54E-10;
*gamma = UxHwDoubleUniformDist(0.15, 0.25);
*phi = UxHwDoubleUniformDist(0.3, 0.45);
}

int
main(int argc, char * argv[])
{
double G, M, Rs, b, gamma, phi, sigmaCMpa;

loadInputs(&G, &M, &Rs, &b, &gamma, &phi);

sigmaCMpa = ((M*gamma)/(2.0*b))*(sqrt((8.0*gamma*phi*Rs)/(M_PI*G*pow(b, 2))) - phi)/1000000;

printf("Alloy strength (σc)\\t\\t= %.1E MPa\\n", sigmaCMpa);

return 0;
}`;

You can use the Code field to specify the source code of your application as a string. The Language field specifies the programming language of the source code. The Arguments field is optional and specifies the default command-line arguments that the Signaloid Cloud Compute Engine will pass to the application, if they are not overriden using the Task object. The CoreID field is optional and if you omit it, the Signaloid Cloud Compute Engine will build your source code for the C0-XS+ core.

Build request for an uncertainty-tracking (UT) C0 or C0 Pro core

The following code snippet shows how to configure the Build object in order to build a single-file C source code application, i.e., the source code shown at the top of the page. The Code field specifies the source code and the Language field specifies the programming language of choice. The code snippet also explicitly sets the target Signaloid core using the CoreID field as well as the default command-line arguments using the Arguments field.

// Core ID of the C0-S+ core
const coreID = "cor_b21e4de9927158c1a5b603c2affb8a09";

const buildRequest: SourceCodeBuildRequest = {
Code: applicationCode,
Language: "C",
CoreID: coreID,
};

Build request for a C0 or C0 Pro Reference core

When running an application on the Reference microarchitecture on C0 or C0 Pro, the Signaloid Cloud Compute Engine needs to know which variables of your source code to trace. To show distributional output at the end of execution, the Signaloid Cloud Compute Engine needs to accumulate the particle values of variables across the different re-executions of the application. The traced variables can be of double or float base type, including typedef definitions to these types. See here for more details about what kinds of expressions you can trace.

Note

Use the TraceVariables element on Build request object to instruct the Signaloid Cloud Compute Engine about the variables to trace. Your source code may contain multiple variables with the same name, defined in different scopes of your source code. Because of this, the Signaloid Cloud Developer Platform needs to know the file and line number of the declaration of the variable that you wish to trace to be able to uniquely identify it.

The following code snippet shows how to configure the Build object in order to build a single-file C source code application for the C0 Reference core the source code shown at the top of the page. The TraceVariables element of the Build object, instructs the Signaloid Cloud Compute Engine to trace variable sigmaCMpa, in line 25 of file main.c. By convention, the file name of source code task in Signaloid Cloud Compute Engine is always main.c.

// Core ID of the C0-Reference core
const coreID = "cor_9a3efb0094405df5aeb61cf1f29606a0";

const buildRequest: SourceCodeBuildRequest = {
Code: applicationCode,
Arguments: "",
Language: "C",
CoreID: coreID,
TraceVariables: [{ Expression: "sigmaCMpa", File: "main.c", LineNumber: 25 }]
};

Submit the build to the API

Submit the build request to the API via a POST request to sourcecode/builds. The code snippet below shows how to submit the build request to the API using the axios library. Optionally you can set DataSources and Arguments to the post request. If not provided, it will default to the DataSources and Arguments you have set as default config during the build step.

console.log("Submitting the build to the API...");
let buildPostResponse;
try {
buildPostResponse = await signaloidClient.post("/sourcecode/builds", buildRequest);
if (buildPostResponse.data.BuildID) {
console.log(`...build successfully created with ID: ${buildPostResponse.data.BuildID}`);
}
/*
* response.data will contain the execution job response object
*/
} catch (error) {
console.error(error);
}

If the task is valid, and the API has accepted the request, the buildEndpointResponse variable will contain information about the task. The code snippet below shows the contents of the buildEndpointResponse variable after the task has been successfully created.

{
"data": {
"BuildID": "bld_003794881cd24297929f4b7f78f3b59e",
},
}

You can now use the BuildID to retrieve the status and outputs of the task from the API.