How to Create a Model
In DAFNI, a Model is user-provided code, written in your preferred programming language, that accepts inputs and produces outputs. This flexibility supports a wide range of applications. For instance, the code could forecast traffic flows based on proposed road routes and projected vehicle growth profiles. One of the goals of DAFNI is to support the execution of a single model or integrate multiple models, such that the output of one model can be used as input for another. For example, routes generated by a traffic model could serve as input to another model forecasting vehicle numbers.
This guide provides two sample implementations in Python and C++. In subsequent sections, you will learn how to integrate these models into the DAFNI platform. The complete code examples are available in the DAFNI model examples in GitHub.
Example 1: Python - Generating Fibonacci Numbers
In this example, we create a Python model to generate Fibonacci numbers. The model reads the sequence length (and optional first two numbers) from environment variables, then writes the resulting sequence to an output file in the ./outputs
directory:
import os
import sys
import json
from pathlib import Path
# constants
SEQUENCE_LENGTH_DEFAULT = 20
SEQUENCE_F0_DEFAULT = 0
SEQUENCE_F1_DEFAULT = 1
OUTPUT_FOLDER = Path("./outputs/") # use while running locally
# OUTPUT_FOLDER = Path("/data/outputs/") # use while running in docker
def generate_fibonacci(length, f0=0, f1=1):
"""Generate Fibonacci sequence of given length starting with f0 and f1."""
sequence = [f0, f1]
for _ in range(length - 2):
sequence.append(sequence[-1] + sequence[-2])
return sequence
def get_env_var(name, default, cast=int):
"""Retrieve environment variable and cast to the specified type."""
try:
return cast(os.getenv(name, default))
except ValueError:
sys.exit(f"Error: {name} must be a {cast.__name__}")
def main():
print("Starting Fibonacci model.")
# retrieve input parameters from environment variables or use defaults
sequence_length = get_env_var("SEQUENCE_LENGTH", SEQUENCE_LENGTH_DEFAULT)
sequence_f0 = get_env_var("SEQUENCE_F0", SEQUENCE_F0_DEFAULT)
sequence_f1 = get_env_var("SEQUENCE_F1", SEQUENCE_F1_DEFAULT)
# generate Fibonacci sequence
sequence = generate_fibonacci(sequence_length, sequence_f0, sequence_f1)
# output results to file
OUTPUT_FOLDER.mkdir(parents=True, exist_ok=True)
output_file = OUTPUT_FOLDER / "sequence.json"
output_file.write_text(json.dumps({"sequence": sequence}))
print(f"Finished Fibonacci model.")
print(f"Sequence saved to {output_file}")
if __name__ == "__main__":
main()
Save the code in a file, e.g., fibonacci.py and run the script:
python fibonacci.py
By default, the script generates a sequence of 20 numbers and saves it to ./outputs/sequence.json
. To change the sequence length, use an environment variable:
export SEQUENCE_LENGTH=15
Try modifying other parameters (SEQUENCE_F0
and SEQUENCE_F1
) to observe their effects on the output.
Example 2: C++ - Generating Fibonacci Numbers
Below is the equivalent model implemented in C++:
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <cstdlib>
#include <filesystem>
#include <json/json.h>
// include a JSON library like jsoncpp
// (install via package manager or include the library manually)
namespace fs = std::filesystem;
// constants
const int SEQUENCE_LENGTH_DEFAULT = 20;
const int SEQUENCE_F0_DEFAULT = 0;
const int SEQUENCE_F1_DEFAULT = 1;
const std::string OUTPUT_FOLDER = "./outputs/"; // use while running locally
// const std::string OUTPUT_FOLDER = "/data/outputs/"; // use while running in docker
std::vector<int> generate_fibonacci(int length, int f0 = 0, int f1 = 1)
{
// Generate Fibonacci sequence of given length starting with f0 and f1.
std::vector<int> sequence{f0, f1};
for (int i = 2; i < length; ++i)
{
sequence.push_back(sequence[i - 1] + sequence[i - 2]);
}
return sequence;
}
int get_env_var(const std::string &name, int default_value)
{
// Retrieve environment variable and cast to the specified type.
const char *env_val = std::getenv(name.c_str());
if (env_val)
{
try
{
return std::stoi(env_val);
}
catch (const std::invalid_argument &)
{
std::cerr << "Error: " << name << " must be an integer" << std::endl;
std::exit(EXIT_FAILURE);
}
}
return default_value;
}
void save_to_file(const std::string &filepath, const std::vector<int> &sequence)
{
// Save sequence to JSON file
fs::create_directories(OUTPUT_FOLDER);
Json::Value root;
Json::StreamWriterBuilder writer;
root["sequence"] = Json::arrayValue;
for (int num : sequence)
{
root["sequence"].append(num);
}
std::ofstream file(filepath);
if (file.is_open())
{
file << Json::writeString(writer, root);
file.close();
std::cout << "Sequence saved to " << filepath << std::endl;
}
else
{
std::cerr << "Error: Unable to open file " << filepath << std::endl;
}
}
int main()
{
std::cout << "Starting Fibonacci model." << std::endl;
// retrieve input parameters from environment variables or use defaults
int sequence_length = get_env_var("SEQUENCE_LENGTH", SEQUENCE_LENGTH_DEFAULT);
int sequence_f0 = get_env_var("SEQUENCE_F0", SEQUENCE_F0_DEFAULT);
int sequence_f1 = get_env_var("SEQUENCE_F1", SEQUENCE_F1_DEFAULT);
// generate Fibonacci sequence
std::vector<int> sequence = generate_fibonacci(sequence_length, sequence_f0, sequence_f1);
// output results to file
save_to_file(OUTPUT_FOLDER + "sequence.json", sequence);
std::cout << "Finished Fibonacci model." << std::endl;
return 0;
}
Save the file in fibonacci.cpp
.
Install the required JSON library e.g., jsoncpp (we used Ubuntu Linux):
sudo apt update
sudo apt install libjsoncpp-dev
Compile and run the model:
g++ -std=c++17 -o fibonacci fibonacci.cpp -I/usr/include/jsoncpp -L/usr/lib/x86_64-linux-gnu -ljsoncpp
./fibonacci
By default, the script generates a sequence of fibonacci and saves it to ./outputs/sequence.json
. To change the sequence length, update the environment variables.
In the next section, we will demonstrate how to containerise these examples using Docker, preparing them for deployment on the DAFNI platform.