Skip to content

Practice

Use App

This section walks you through the usage of a NUREMICS App from the end-user's perspective. We will demonstrate how to interact with a ready-to-use App, including how to design studies, define experiments, provide inputs, run the App, and retrieve the expected outputs.


When launching the App, NUREMICS first provides the following terminal feedback:

  • A visual banner indicating the launch of a NUREMICS App.
  • A structured overview of the assembled workflow, with its constitutive Procs and Ops, and their order of execution within the App workflow.
  • A summary of the App's I/O interface. This summary includes all declared user parameters ("user_params") and user paths ("user_paths") required as inputs, along with the corresponding output files and folders that the App will generate. It serves as an explicit interface contract, allowing you to understand what data you need to provide and what results to expect.

👤🔄🖥️

                      ___
                     /\  \                      ___
     ___            /::\  \        ___         /\  \
    /\__\          /:/\:\  \      /\__\       /::\  \
   /::|  |        /::\~\:\  \    /::|  |     /:/\:\  \
  /:|:|  |       /:/\:\ \:\__\  /:|:|  |    /:/  \:\  \
 /:/|:|  |__     \/_|::\/:/  / /:/|:|__|__ /:/__/ \:\__\
/:/ |:| /\__\  ___  |:|::/  / /:/ |::::\__\\:\  \  \/__/
\/__|:|/:/  / /\__\ |:|\/__/  \/__/~~/:/  / \:\  \
    |:/:/  / /:/  / |:|  |  ___     /:/  /   \:\  \
    |::/  / /:/  /   \|__| /\  \   /:/  / ___ \:\__\ ___
    /:/  / /:/  /  ___    /::\  \ /:/  / /\  \ \/__//\  \
    \/__/ /:/__/  /\__\  /:/\:\  \\/__/  \:\  \    /::\  \
          \:\  \ /:/  / /::\~\:\  \      /::\__\  /:/\ \  \
           \:\  /:/  / /:/\:\ \:\__\  __/:/\/__/ _\:\~\ \  \
            \:\/:/  /  \:\~\:\ \/__/ /\/:/  /   /\ \:\ \ \__\
             \::/  /    \:\ \:\__\   \::/__/    \:\ \:\ \/__/
              \/__/      \:\ \/__/    \:\__\     \:\ \:\__\
                          \:\__\       \/__/      \:\/:/  /
                           \/__/                   \::/  /
                                                    \/__/
> APPLICATION <

| Workflow |
DEMO_APP_____
             |_____PolygonGeometryProc_____
             |                             |_____generate_polygon_shape
             |                             |_____plot_polygon_shape
             |
             |_____ProjectileModelProc_____
             |                             |_____simulate_projectile_motion
             |                             |_____calculate_analytical_trajectory
             |                             |_____compare_model_vs_analytical_trajectories
             |
             |_____TrajectoryAnalysisProc_____
                                              |_____plot_overall_model_vs_theory

> INPUTS <

| User Parameters |
> nb_sides (int)
> gravity (float)
> mass (float)

| User Paths |
> plot_title.txt
> velocity.json
> configs

> OUTPUTS <

> points_coordinates.csv
> polygon_shape.png
> comparison
> overall_comparisons.png

Specify Working Directory

If this is your first time launching a NUREMICS App, NUREMICS will prompt you to specify the working directory ("working_dir") for the App. This directory serves as the root location where all input/output data, logs, and results will be stored.

👤🔄🖥️

(X) Please define DEMO_APP "working_dir" in file :
> .../nuremics-labs/.nuremics/settings.json

As indicated in the terminal message, you must define this path in the settings.json file located in the .nuremics folder. This folder should reside at the root of your nuremics-labs repository.

{
    "default_working_dir": null,
    "apps": {
        "DEMO_APP": {
            "working_dir": "path/to/your/app/working_dir",
            "studies": []
        }
    }
}

If you've already launched a NUREMICS App before, but this is your first time launching this specific App, NUREMICS may have already registered a default_working_dir in the settings.json file based on a previous App. In that case, it will suggest using this same directory as the "working_dir" for the current App.

👤🔄🖥️

(!) Found "default_working_dir": path/to/your/previous/app/working_dir
Accept it as "working_dir" for DEMO_APP: [Y/n]

You can either accept the proposed path by pressing Y, or reject it with n and manually define a new one by editing the settings.json file.


A new folder named after the App is then automatically generated under the specified "working_dir". This folder acts as the root container for all the execution-related content generated by the App.

👤👁️💾

<working_dir>/
└── DEMO_APP/  #generated

Declare Studies

NUREMICS then prompts you to declare the different studies you want to carry out with the App.

👤🔄🖥️

> STUDIES <

(X) Please declare at least one study in file :
> .../nuremics-labs/.nuremics/settings.json

This must be declared in the same settings.json file located at the root of your nuremics-labs repository, as a list of identifiers corresponding to the different studies you want to carry out. You can declare as many studies as needed, each representing a self-contained parametric study.

{
    "default_working_dir": null,
    "apps": {
        "DEMO_APP": {
            "working_dir": "path/to/your/app/working_dir",
            "studies": [
                "Study_Shape",
                "Study_Velocity"
            ]
        }
    }
}

A studies.json file is then generated inside the App's "working_dir". This file serves as a centralized configuration hub for all declared studies.

👤👁️💾

<working_dir>/
└── DEMO_APP/
    └── studies.json  #generated

Configure Studies

NUREMICS then prompts you to configure the first study that you previously declared.

👤🔄🖥️

> STUDIES <

| Study_Shape |
(X) nb_sides not configured.
(X) gravity not configured.
(X) mass not configured.
(X) plot_title.txt not configured.
(X) velocity.json not configured.
(X) configs not configured.

(X) Please configure file :
> .../DEMO_APP/studies.json

You must now complete the studies.json file by specifying, for each input, whether it should remain fixed (false) or be allowed to vary (true) across the experiments that will later be defined within the study.

{
    "Study_Shape": {
        "execute": true,
        "user_params": {
            "nb_sides": true,
            "gravity": false,
            "mass": false
        },
        "user_paths": {
            "plot_title.txt": false,
            "velocity.json": false,
            "configs": false
        }
    },
    "Study_Velocity": {
        "execute": true,
        "user_params": {
            "nb_sides": null,
            "gravity": null,
            "mass": null
        },
        "user_paths": {
            "plot_title.txt": null,
            "velocity.json": null,
            "configs": null
        }
    }
}

NUREMICS then prompts that the first study is properly configured, but indicates that the second declared study still requires configuration.

👤🔄🖥️

> STUDIES <

| Study_Shape |
(V) nb_sides is variable.
(V) gravity is fixed.
(V) mass is fixed.
(V) plot_title.txt is fixed.
(V) velocity.json is fixed.
(V) configs is fixed.

| Study_Velocity |
(X) nb_sides not configured.
(X) gravity not configured.
(X) mass not configured.
(X) plot_title.txt not configured.
(X) velocity.json not configured.
(X) configs not configured.

(X) Please configure file :
> .../DEMO_APP/studies.json

The same work must therefore be done in the studies.json file to properly configure the study.

{
    "Study_Shape": {
        "execute": true,
        "user_params": {
            "nb_sides": true,
            "gravity": false,
            "mass": false
        },
        "user_paths": {
            "plot_title.txt": false,
            "velocity.json": false,
            "configs": false
        }
    },
    "Study_Velocity": {
        "execute": true,
        "user_params": {
            "nb_sides": false,
            "gravity": false,
            "mass": false
        },
        "user_paths": {
            "plot_title.txt": false,
            "velocity.json": true,
            "configs": false
        }
    }
}

NUREMICS finally prompts that all declared studies are properly configured.

👤🔄🖥️

> STUDIES <

| Study_Shape |
(V) nb_sides is variable.
(V) gravity is fixed.
(V) mass is fixed.
(V) plot_title.txt is fixed.
(V) velocity.json is fixed.
(V) configs is fixed.

| Study_Velocity |
(V) nb_sides is fixed.
(V) gravity is fixed.
(V) mass is fixed.
(V) plot_title.txt is fixed.
(V) velocity.json is variable.
(V) configs is fixed.


A dedicated folder for each study is then generated inside the App's "working_dir".

👤👁️💾

<working_dir>/
└── DEMO_APP/
    ├── studies.json
    ├── Study_Shape/     #generated
    └── Study_Velocity/  #generated

Set Input Data

Each study folder inside the App's "working_dir" now contains an initialized input database that you must complete to run your first experiments.

👤👁️💾

<working_dir>/
└── DEMO_APP/
    ├── studies.json
    ├── Study_Shape/
       ├── inputs.csv   #generated
       ├── inputs.json  #generated
       └── 0_inputs/    #generated
    └── Study_Velocity/
        ├── inputs.csv   #generated
        ├── inputs.json  #generated
        └── 0_inputs/    #generated

This input database contains:

  • inputs.csv: This is the main file for declaring the experiments you want to run in the study. This is also where you must set the input parameters defined as variable "user_params".
  • inputs.json: In this file, you must set the input parameters defined as fixed "user_params".
  • 0_inputs/: This folder must contain the input files and/or folders defined as "user_paths" (either fixed or variable).

NUREMICS first prompts you to set the fixed input data for the first declared study.

👤🔄🖥️

> SETTINGS <

| Study_Shape |
> Common : (X) gravity (X) mass (X) plot_title.txt (X) velocity.json (X) configs

(X) Please set inputs :
> .../DEMO_APP/Study_Shape/inputs.json
> .../DEMO_APP/Study_Shape/0_inputs/plot_title.txt
> .../DEMO_APP/Study_Shape/0_inputs/velocity.json
> .../DEMO_APP/Study_Shape/0_inputs/configs

{
    "gravity": -9.81,
    "mass": 0.1
}
2D polygon shape
{
    "v0": 15.0,
    "angle": 45.0
}
{
    "timestep": 0.01
}
{
    "fps": 60,
    "size": 700
}

All fixed input data have now been completed within the input database of the study.

👤👁️💾

<working_dir>/
└── DEMO_APP/
    ├── studies.json
    ├── Study_Shape/
       ├── inputs.csv
       ├── inputs.json
       └── 0_inputs/
           ├── plot_title.txt          ⬇️ #uploaded
           ├── velocity.json           ⬇️ #uploaded
           └── configs/                ⬇️ #uploaded
               ├── solver_config.json  ⬇️ #uploaded
               └── display_config.json ⬇️ #uploaded
    └── Study_Velocity/
        ├── inputs.csv
        ├── inputs.json
        └── 0_inputs/


NUREMICS then prompts that all fixed input data have been properly set, but indicates that datasets of variable input data still need to be declared in order to define the experiments to run.

👤🔄🖥️

> SETTINGS <

| Study_Shape |
> Common : (V) gravity (V) mass (V) plot_title.txt (V) velocity.json (V) configs

(X) Please declare at least one experiment in file :
> .../DEMO_APP/Study_Shape/inputs.csv

Let's first declare three experiments in the inputs.csv file.

ID,nb_sides,EXECUTE
Test1,,
Test2,,
Test3,,

NUREMICS now prompts that the three experiments have been declared, but is waiting for the variable input data to be set for each of them.

👤🔄🖥️

 > SETTINGS <

| Study_Shape |
> Common : (V) gravity (V) mass (V) plot_title.txt (V) velocity.json (V) configs
> Test1 : (X) nb_sides
> Test2 : (X) nb_sides
> Test3 : (X) nb_sides

(X) Please set inputs :
> .../DEMO_APP/Study_Shape/inputs.csv

Let’s thus set input values for each experiment in the inputs.csv file.

ID,nb_sides,EXECUTE
Test1,3,
Test2,4,
Test3,5,

NUREMICS finally prompts that all input data are properly set for the study.

👤🔄🖥️

> SETTINGS <

| Study_Shape |
> Common : (V) gravity (V) mass (V) plot_title.txt (V) velocity.json (V) configs
> Test1 : (V) nb_sides
> Test2 : (V) nb_sides
> Test3 : (V) nb_sides


The same work must be done to set the input data for the second declared study.

👤🔄🖥️

| Study_Velocity |
> Common : (X) nb_sides (X) gravity (X) mass (X) plot_title.txt (X) configs

(X) Please set inputs :
> .../DEMO_APP/Study_Velocity/inputs.json
> .../DEMO_APP/Study_Velocity/0_inputs/plot_title.txt
> .../DEMO_APP/Study_Velocity/0_inputs/configs

{
    "nb_sides": 5,
    "gravity": -9.81,
    "mass": 0.1
}
2D polygon shape
{
    "timestep": 0.01
}
{
    "fps": 60,
    "size": 700
}

👤👁️💾

<working_dir>/
└── DEMO_APP/
    ├── studies.json
    ├── Study_Shape/
       ├── inputs.csv
       ├── inputs.json
       └── 0_inputs/
           ├── plot_title.txt
           ├── velocity.json
           └── configs/
               ├── solver_config.json
               └── display_config.json
    └── Study_Velocity/
        ├── inputs.csv
        ├── inputs.json
        └── 0_inputs/
            ├── plot_title.txt          ⬇️ #uploaded
            └── configs/                ⬇️ #uploaded
                ├── solver_config.json  ⬇️ #uploaded
                └── display_config.json ⬇️ #uploaded

👤🔄🖥️

| Study_Velocity |
> Common : (V) nb_sides (V) gravity (V) mass (V) plot_title.txt (V) configs

(X) Please declare at least one experiment in file :
> .../DEMO_APP/Study_Velocity/inputs.csv

ID,EXECUTE
Test1,
Test2,
Test3,

👤🔄🖥️

| Study_Velocity |
> Common : (V) nb_sides (V) gravity (V) mass (V) plot_title.txt (V) configs
> Test1 : (X) velocity.json
> Test2 : (X) velocity.json
> Test3 : (X) velocity.json

(X) Please set inputs :
> .../DEMO_APP/Study_Velocity/0_inputs/0_datasets/Test1/velocity.json
> .../DEMO_APP/Study_Velocity/0_inputs/0_datasets/Test2/velocity.json
> .../DEMO_APP/Study_Velocity/0_inputs/0_datasets/Test3/velocity.json

{
    "v0": 15.0,
    "angle": 45.0
}
{
    "v0": 20.0,
    "angle": 45.0
}
{
    "v0": 20.0,
    "angle": 60.0
}

👤👁️💾

<working_dir>/
└── DEMO_APP/
    ├── studies.json
    ├── Study_Shape/
       ├── inputs.csv
       ├── inputs.json
       └── 0_inputs/
           ├── plot_title.txt
           ├── velocity.json
           └── configs/
               ├── solver_config.json
               └── display_config.json
    └── Study_Velocity/
        ├── inputs.csv
        ├── inputs.json
        └── 0_inputs/
            ├── plot_title.txt
            ├── configs/
               ├── solver_config.json
               └── display_config.json
            └── 0_datasets/            #generated
                ├── Test1/             #generated
                   └── velocity.json ⬇️ #uploaded
                ├── Test2/             #generated
                   └── velocity.json ⬇️ #uploaded
                └── Test3/             #generated
                    └── velocity.json ⬇️ #uploaded


NUREMICS finally prompts that all input data are properly set for all declared studies.

👤🔄🖥️

> SETTINGS <

| Study_Shape |
> Common : (V) gravity (V) mass (V) plot_title.txt (V) velocity.json (V) configs
> Test1 : (V) nb_sides
> Test2 : (V) nb_sides
> Test3 : (V) nb_sides

| Study_Velocity |
> Common : (V) nb_sides (V) gravity (V) mass (V) plot_title.txt (V) configs
> Test1 : (V) velocity.json
> Test2 : (V) velocity.json
> Test3 : (V) velocity.json

Get Results

At this stage, NUREMICS is ready to run all the defined studies and generate the corresponding results.

👤🔄🖥️

> RUNNING <

| Study_Shape | PolygonGeometryProc | Test1 |
> n_sides = 3
> radius = 0.5
> title_file = .../DEMO_APP/Study_Shape/0_inputs/plot_title.txt
>>> START
COMPLETED <<<

| Study_Shape | PolygonGeometryProc | Test2 |
> n_sides = 4
> radius = 0.5
> title_file = .../DEMO_APP/Study_Shape/0_inputs/plot_title.txt
>>> START
COMPLETED <<<

| Study_Shape | PolygonGeometryProc | Test3 |
> n_sides = 5
> radius = 0.5
> title_file = .../DEMO_APP/Study_Shape/0_inputs/plot_title.txt
>>> START
COMPLETED <<<

| Study_Shape | ProjectileModelProc | Test1 |
> gravity = -9.81
> mass = 0.1
> velocity_file = .../DEMO_APP/Study_Shape/0_inputs/velocity.json
> configs_folder = .../DEMO_APP/Study_Shape/0_inputs/configs
> coords_file = .../DEMO_APP/Study_Shape/1_PolygonGeometryProc/Test1/points_coordinates.csv
>>> START
COMPLETED <<<

| Study_Shape | ProjectileModelProc | Test2 |
> gravity = -9.81
> mass = 0.1
> velocity_file = .../DEMO_APP/Study_Shape/0_inputs/velocity.json
> configs_folder = .../DEMO_APP/Study_Shape/0_inputs/configs
> coords_file = .../DEMO_APP/Study_Shape/1_PolygonGeometryProc/Test2/points_coordinates.csv
>>> START
COMPLETED <<<

| Study_Shape | ProjectileModelProc | Test3 |
> gravity = -9.81
> mass = 0.1
> velocity_file = .../DEMO_APP/Study_Shape/0_inputs/velocity.json
> configs_folder = .../DEMO_APP/Study_Shape/0_inputs/configs
> coords_file = .../DEMO_APP/Study_Shape/1_PolygonGeometryProc/Test3/points_coordinates.csv
>>> START
COMPLETED <<<

| Study_Shape | TrajectoryAnalysisProc |
> comp_folder = comparison
>>> START
COMPLETED <<<

| Study_Velocity | PolygonGeometryProc |
> n_sides = 5
> radius = 0.5
> title_file = .../DEMO_APP/Study_Velocity/0_inputs/plot_title.txt
>>> START
COMPLETED <<<

| Study_Velocity | ProjectileModelProc | Test1 |
> gravity = -9.81
> mass = 0.1
> configs_folder = .../DEMO_APP/Study_Velocity/0_inputs/configs
> velocity_file = .../DEMO_APP/Study_Velocity/0_inputs/0_datasets/Test1/velocity.json
> coords_file = .../DEMO_APP/Study_Velocity/1_PolygonGeometryProc/points_coordinates.csv
>>> START
COMPLETED <<<

| Study_Velocity | ProjectileModelProc | Test2 |
> gravity = -9.81
> mass = 0.1
> configs_folder = .../DEMO_APP/Study_Velocity/0_inputs/configs
> velocity_file = .../DEMO_APP/Study_Velocity/0_inputs/0_datasets/Test2/velocity.json
> coords_file = .../DEMO_APP/Study_Velocity/1_PolygonGeometryProc/points_coordinates.csv
>>> START
COMPLETED <<<

| Study_Velocity | ProjectileModelProc | Test3 |
> gravity = -9.81
> mass = 0.1
> configs_folder = .../DEMO_APP/Study_Velocity/0_inputs/configs
> velocity_file = .../DEMO_APP/Study_Velocity/0_inputs/0_datasets/Test3/velocity.json
> coords_file = .../DEMO_APP/Study_Velocity/1_PolygonGeometryProc/points_coordinates.csv
>>> START
COMPLETED <<<

| Study_Velocity | TrajectoryAnalysisProc |
> comp_folder = comparison
>>> START
COMPLETED <<<

You can then access the results in the output database generated by NUREMICS within the App's "working_dir".

👤👁️💾

<working_dir>/
└── DEMO_APP/
    ├── studies.json
    ├── Study_Shape/
       ├── inputs.csv
       ├── inputs.json
       ├── 0_inputs/
          ├── plot_title.txt
          ├── velocity.json
          └── configs/
              ├── solver_config.json
              └── display_config.json
       ├── 1_PolygonGeometryProc/           #generated
          ├── Test1/                       #generated
             ├── points_coordinates.csv   #generated
             └── polygon_shape.png        #generated
          ├── Test2/                       #generated
             ├── points_coordinates.csv   #generated
             └── polygon_shape.png        #generated
          └── Test3/                       #generated
              ├── points_coordinates.csv   #generated
              └── polygon_shape.png        #generated
       ├── 2_ProjectileModelProc/           #generated
          ├── Test1/                       #generated
             └── comparison/              #generated
                 ├── results.xlsx         #generated
                 └── model_vs_theory.png  #generated
          ├── Test2/                       #generated
             └── comparison/              #generated
                 ├── results.xlsx         #generated
                 └── model_vs_theory.png  #generated
          └── Test3/                       #generated
              └── comparison/              #generated
                  ├── results.xlsx         #generated
                  └── model_vs_theory.png  #generated
       └── 3_TrajectoryAnalysisProc         #generated
           └── overall_comparisons.png      #generated
    └── Study_Velocity/
        ├── inputs.csv
        ├── inputs.json
        ├── 0_inputs/
           ├── plot_title.txt
           ├── configs/
              ├── solver_config.json
              └── display_config.json
           └── 0_datasets/
               ├── Test1/
                  └── velocity.json
               ├── Test2/
                  └── velocity.json
               └── Test3/
                   └── velocity.json
        ├── 1_PolygonGeometryProc/           #generated
           ├── points_coordinates.csv       #generated
           └── polygon_shape.png            #generated
        ├── 2_ProjectileModelProc/           #generated
           ├── Test1/                       #generated
              └── comparison/              #generated
                  ├── results.xlsx         #generated
                  └── model_vs_theory.png  #generated
           ├── Test2/                       #generated
              └── comparison/              #generated
                  ├── results.xlsx         #generated
                  └── model_vs_theory.png  #generated
           └── Test3/                       #generated
               └── comparison/              #generated
                   ├── results.xlsx         #generated
                   └── model_vs_theory.png  #generated
        └── 3_TrajectoryAnalysisProc         #generated
            └── overall_comparisons.png      #generated

Create App

Now that we've saw how to use a NUREMICS App as an end-user, it's now time to look under the hood and explore how the App is actually built. This section dives into the developer's side of NUREMICS, exposing how to define, organize, and structure a fully functional App.

You'll start by implementing your own Procs, which encapsulate domain-specific logic and computational tasks. Then, you’ll learn how to assemble these building blocks into a fully operational App.

Implement Procs

We start by defining the core building blocks of the App to be created: the Procs. Each Proc is a reusable item that encapsulates a specific piece of logic executed within the overall workflow. Internally, this logic can be further decomposed into elementary operations (Ops), implemented as individual functions (units) within the Proc itself.


To implement our first Proc, we begin by importing the Process base class from nuremics, which all custom Procs must inherit from. To make this inheritance simple and structured, we also import the attrs library, which helps define clean, data-driven Python classes.

import attrs
from nuremics import Process

We then declare our first Proc as a Python class named PolygonGeometryProc, inheriting from the Process base class. This marks it as a modular item of computation which can be executed within a NUREMICS workflow.

import attrs
from nuremics import Process

@attrs.define
class PolygonGeometryProc(Process):

We now declare the input data required by our PolygonGeometryProc, grouped into two categories: Parameters and Paths. Each input is defined using attrs.field() and marked with metadata={"input": True}.

This metadata is essential: it tells the NUREMICS framework that these attributes are expected as input data, ensuring they are properly tracked and managed throughout the workflow.

import attrs
from pathlib import Path
from nuremics import Process

@attrs.define
class PolygonGeometryProc(Process):

    # Parameters
    radius: float = attrs.field(init=False, metadata={"input": True})
    n_sides: int = attrs.field(init=False, metadata={"input": True})

    # Paths
    title_file: Path = attrs.field(init=False, metadata={"input": True}, converter=Path)

In addition to the previously declared input data, a Proc can also define internal variables: attributes used during the execution of its internal logic but not provided as input data.

These internal variables, like df_points in our example below, are declared without the metadata={"input": True} tag, signaling to the NUREMICS framework that they are not exposed to the workflow and will be set or computed within the Proc itself.

import attrs
import pandas as pd
from pathlib import Path
from nuremics import Process

@attrs.define
class PolygonGeometryProc(Process):

    # Parameters
    radius: float = attrs.field(init=False, metadata={"input": True})
    n_sides: int = attrs.field(init=False, metadata={"input": True})

    # Paths
    title_file: Path = attrs.field(init=False, metadata={"input": True}, converter=Path)

    # Internal
    df_points: pd.DataFrame = attrs.field(init=False)

The operations executed by the Proc are finally implemented as elementary functions (Ops), which are then sequentially called within the __call__() method to define the overall logic of the Proc.

import attrs
import pandas as pd
from pathlib import Path
from nuremics import Process

@attrs.define
class PolygonGeometryProc(Process):

    # Parameters
    radius: float = attrs.field(init=False, metadata={"input": True})
    n_sides: int = attrs.field(init=False, metadata={"input": True})

    # Paths
    title_file: Path = attrs.field(init=False, metadata={"input": True}, converter=Path)

    # Internal
    df_points: pd.DataFrame = attrs.field(init=False)

    def __call__(self):
        super().__call__()

        self.generate_polygon_shape()
        self.plot_polygon_shape()

    def generate_polygon_shape(self):
        # </> your code </>

    def plot_polygon_shape(self):
        # </> your code </>

Note that the Proc should at some point produce output data, typically in the form of files or folders generated during the execution of its Ops. To make these output data trackable by the NUREMICS framework, each must be registered in the self.output_paths dictionary using a label that is unique to the Proc (e.g., "coords_file", "fig_file").

Using the dictionary syntax self.output_paths["coords_file"] effectively declares an output variable named coords_file, which will later be instantiated by assigning it a specific file or folder name when integrating the Proc into a broader application workflow.

import attrs
import pandas as pd
from pathlib import Path
from nuremics import Process

@attrs.define
class PolygonGeometryProc(Process):

    # Parameters
    radius: float = attrs.field(init=False, metadata={"input": True})
    n_sides: int = attrs.field(init=False, metadata={"input": True})

    # Paths
    title_file: Path = attrs.field(init=False, metadata={"input": True}, converter=Path)

    # Internal
    df_points: pd.DataFrame = attrs.field(init=False)

    def __call__(self):
        super().__call__()

        self.generate_polygon_shape()
        self.plot_polygon_shape()

    def generate_polygon_shape(self):
        # </> your code </>
        file = self.output_paths["coords_file"]
        # </> Write file </>

    def plot_polygon_shape(self):
        # </> your code </>
        file = self.output_paths["fig_file"]
        # </> Write file </>

Even though Procs are not intended to be executed independently by end-users, they are still designed with the possibility to run out of the box. This allows developers to easily execute them during the development phase or when implementing dedicated unit tests for a specific Proc.

In such cases, it is important to set set_inputs=True when instantiating the Proc, to explicitly inform the NUREMICS framework that the input data are being provided manually, outside of any workflow context.

import attrs
import pandas as pd
from pathlib import Path
from nuremics import Process

@attrs.define
class PolygonGeometryProc(Process):

    # Parameters
    radius: float = attrs.field(init=False, metadata={"input": True})
    n_sides: int = attrs.field(init=False, metadata={"input": True})

    # Paths
    title_file: Path = attrs.field(init=False, metadata={"input": True}, converter=Path)

    # Internal
    df_points: pd.DataFrame = attrs.field(init=False)

    def __call__(self):
        super().__call__()

        self.generate_polygon_shape()
        self.plot_polygon_shape()

    def generate_polygon_shape(self):
        # </> your code </>
        file = self.output_paths["coords_file"]
        # </> Write file </>

    def plot_polygon_shape(self):
        # </> your code </>
        file = self.output_paths["fig_file"]
        # </> Write file </>

if __name__ == "__main__":

    # ================================================================== #
    #                      USER-DEFINED PARAMETERS                       #
    #              >>>>> TO BE EDITED BY THE OPERATOR <<<<<              #
    # ================================================================== #

    # Working directory
    working_dir = Path(r"...")

    # Input parameters
    radius = 0.5
    n_sides = 3

    # Input paths
    title_file = Path(r"...") / "plot_title.txt"

    # Output paths
    coords_file = "points_coordinates.csv"
    fig_file = "polygon_shape.png"

    # ================================================================== #

    # Go to working directory
    os.chdir(working_dir)

    # Create dictionary containing input data
    dict_inputs = {
        "radius": radius,
        "n_sides": n_sides,
        "title_file": title_file,
    }

    # Create process
    process = PolygonGeometryProc(
        dict_inputs=dict_inputs,
        set_inputs=True,
    )

    # Define output paths
    process.output_paths["coords_file"] = coords_file
    process.output_paths["fig_file"] = fig_file

    # Run process
    process()
    process.finalize()

Assemble Procs into App

Most of the development effort has already been carried out when implementing the individual Procs. The next step consists in assembling them into a coherent App, where each Proc is instantiated, connected, and orchestrated to form a complete, executable workflow.


We start by defining the name of our App.

APP_NAME = "DEMO_APP"

We then import the Application class from nuremics, which serves as the container and manager to define a workflow composed of multiple Procs.

from nuremics import Application

APP_NAME = "DEMO_APP"

We now import two Procs, PolygonGeometryProc and ProjectileModelProc, previously implemented. These will be the building blocks to assemble into our final App.

from nuremics import Application
from labs.procs.general.PolygonGeometryProc.item import PolygonGeometryProc
from labs.procs.general.ProjectileModelProc.item import ProjectileModelProc

APP_NAME = "DEMO_APP"

The source code of the App then adopts the structure of a standard Python script, which can both be executed directly or imported as a module. This is achieved by defining a main() function and guarding it with the typical if __name__ == "__main__": statement.

from nuremics import Application
from procs.general.PolygonGeometryProc.item import PolygonGeometryProc
from procs.general.ProjectileModelProc.item import ProjectileModelProc

APP_NAME = "DEMO_APP"

def main():
    # Application logic here

if __name__ == "__main__":
    main()

Inside the main() function, we define a list called workflow which contains the sequence of Procs to be executed, in the order specified. This list is made up of dictionaries, where each dictionary describes the assembly characteristics of each individual Proc into the App. This dictionary-based structure offers flexibility to easily add more parameters or options later by simply adding new keys to each dictionary in the workflow.

Let's first define the key "process" of each dictionary, which specifies the Proc class (previously imported, e.g., PolygonGeometryProc and ProjectileModelProc) to instantiate and execute within the App workflow.

from nuremics import Application
from procs.general.PolygonGeometryProc.item import PolygonGeometryProc
from procs.general.ProjectileModelProc.item import ProjectileModelProc

APP_NAME = "DEMO_APP"

def main():

    # --------------- #
    # Define workflow #
    # --------------- #
    workflow = [
        {
            "process": PolygonGeometryProc,
        },
        {
            "process": ProjectileModelProc,
        },
    ]

if __name__ == "__main__":
    main()

We now create an Application object app, which acts as the core engine of our App. This object is instantiated using the previously defined inputs:

  • app_name: the name of the App.

  • nuremics_dir: the root directory of your nuremics-labs repository.

  • workflow: the ordered list of Procs to run.

Once the Application object is created, calling app() launches the workflow execution of all the defined Procs.

import git
from pathlib import Path
from nuremics import Application
from procs.general.PolygonGeometryProc.item import PolygonGeometryProc
from procs.general.ProjectileModelProc.item import ProjectileModelProc

APP_NAME = "DEMO_APP"
repo = git.Repo(Path(__file__).resolve().parent, search_parent_directories=True)

def main():

    # --------------- #
    # Define workflow #
    # --------------- #
    workflow = [
        {
            "process": PolygonGeometryProc,
        },
        {
            "process": ProjectileModelProc,
        },
    ]

    # ------------------ #
    # Define application #
    # ------------------ #
    app = Application(
        app_name=APP_NAME,
        nuremics_dir=repo.working_tree_dir,
        workflow=workflow,
    )
    # Run it!
    app()

if __name__ == "__main__":
    main()

At this stage, we can start executing the App and see what's happen.

Note that NUREMICS performs a structural check of each Proc by inspecting its __call__ method. Specifically, it ensures that only functions (Ops) defined within the Proc class itself are called during execution. This design choice enforces a clean and self-contained structure for each Proc, where all internal logic remains encapsulated.

Let's consider a case where the developer does not adhere to this enforced structural rule, for instance, by injecting additional logic directly into the __call__ method of a Proc (in this example, in the ProjectileModelProc class).

    def __call__(self):
        super().__call__()

        some_variable = 2 # <-- External logic added here

        self.simulate_projectile_motion()
        self.calculate_analytical_trajectory()
        self.compare_model_vs_analytical_trajectories()

In this situation, NUREMICS will immediately raise a structural validation error and halt execution.

👤🔄🖥️

| Workflow |
DEMO_APP_____
             |_____PolygonGeometryProc_____
             |                             |_____generate_polygon_shape
             |                             |_____plot_polygon_shape
             |
             |_____ProjectileModelProc_____(X)

(X) Each process must only call its internal function(s):

    def __call__(self):
        super().__call__()

        self.operation1()
        self.operation2()
        self.operation3()
        ...


NUREMICS is then expected to display a summary of all required input/output data for each Proc, along with their current mapping status within the App.

At this stage, the system automatically verifies whether every required input/output data has been properly mapped within the App configuration.

If any input parameters are missing, they are explicitly listed, and the developer is prompted to define them using either the "user_params" or "hard_params" key.

👤🔄🖥️

| PolygonGeometryProc |
> Input Parameter(s) :
(float) radius  -----||----- Not defined (X)
(int)   n_sides -----||----- Not defined (X)

(X) Please define all input parameters either in "user_params" or "hard_params".

The input parameters of the Proc PolygonGeometryProc can be properly mapped within the App by defining the "user_params" and/or "hard_params" keys in its corresponding dictionary entry inside the workflow list.

import git
from pathlib import Path
from nuremics import Application
from procs.general.PolygonGeometryProc.item import PolygonGeometryProc
from procs.general.ProjectileModelProc.item import ProjectileModelProc

APP_NAME = "DEMO_APP"
repo = git.Repo(Path(__file__).resolve().parent, search_parent_directories=True)

def main():

    # --------------- #
    # Define workflow #
    # --------------- #
    workflow = [
        {
            "process": PolygonGeometryProc,
            "user_params": {
                "n_sides": "nb_sides",
            },
            "hard_params": {
                "radius": 0.5,
            },
        },
        {
            "process": ProjectileModelProc,
        },
    ]

    # ------------------ #
    # Define application #
    # ------------------ #
    app = Application(
        app_name=APP_NAME,
        nuremics_dir=repo.working_tree_dir,
        workflow=workflow,
    )
    # Run it!
    app()

if __name__ == "__main__":
    main()

When running the App again, NUREMICS detects that all required input parameters for PolygonGeometryProc have been successfully mapped. However, it now reports that one or more input paths are missing. These are explicitly listed, and the developer is prompted to define them using either the "user_paths" or "required_paths" key.

👤🔄🖥️

| PolygonGeometryProc |
> Input Parameter(s) :
(float) radius  -----||----- 0.5      (hard_params)
(int)   n_sides -----||----- nb_sides (user_params)
> Input Path(s) :
title_file -----||----- Not defined (X)

(X) Please define all input paths either in "user_paths" or "required_paths".

The input paths of the Proc PolygonGeometryProc can be properly mapped within the App by defining the "user_paths" and/or "required_paths" keys in its corresponding dictionary entry inside the workflow list.

import git
from pathlib import Path
from nuremics import Application
from procs.general.PolygonGeometryProc.item import PolygonGeometryProc
from procs.general.ProjectileModelProc.item import ProjectileModelProc

APP_NAME = "DEMO_APP"
repo = git.Repo(Path(__file__).resolve().parent, search_parent_directories=True)

def main():

    # --------------- #
    # Define workflow #
    # --------------- #
    workflow = [
        {
            "process": PolygonGeometryProc,
            "user_params": {
                "n_sides": "nb_sides",
            },
            "hard_params": {
                "radius": 0.5,
            },
            "user_paths": {
                "title_file": "plot_title.txt",
            },
        },
        {
            "process": ProjectileModelProc,
        },
    ]

    # ------------------ #
    # Define application #
    # ------------------ #
    app = Application(
        app_name=APP_NAME,
        nuremics_dir=repo.working_tree_dir,
        workflow=workflow,
    )
    # Run it!
    app()

if __name__ == "__main__":
    main()

When running the App again, NUREMICS detects that all required input paths for PolygonGeometryProc have been successfully mapped. However, it now reports that one or more output paths are missing. These are explicitly listed, and the developer is prompted to define them using the "output_paths" key.

👤🔄🖥️

| PolygonGeometryProc |
> Input Parameter(s) :
(float) radius  -----||----- 0.5      (hard_params)
(int)   n_sides -----||----- nb_sides (user_params)
> Input Path(s) :
title_file -----||----- plot_title.txt (user_paths)
> Input Analysis :
None.
> Output Path(s) :
coords_file -----||----- Not defined (X)
fig_file    -----||----- Not defined (X)

(X) Please define all output paths in "output_paths".

The output paths of the Proc PolygonGeometryProc can be properly mapped within the App by defining the "output_paths" key in its corresponding dictionary entry inside the workflow list.

In the same way, we also complete the mapping for the Proc ProjectileModelProc by providing all required entries: "user_params" and/or "hard_params", "user_paths" and/or "required_paths", "output_paths".

import git
from pathlib import Path
from nuremics import Application
from procs.general.PolygonGeometryProc.item import PolygonGeometryProc
from procs.general.ProjectileModelProc.item import ProjectileModelProc

APP_NAME = "DEMO_APP"
repo = git.Repo(Path(__file__).resolve().parent, search_parent_directories=True)

def main():

    # --------------- #
    # Define workflow #
    # --------------- #
    workflow = [
        {
            "process": PolygonGeometryProc,
            "user_params": {
                "n_sides": "nb_sides",
            },
            "hard_params": {
                "radius": 0.5,
            },
            "user_paths": {
                "title_file": "plot_title.txt",
            },
            "output_paths": {
                "coords_file": "points_coordinates.csv",
                "fig_file": "polygon_shape.png",
            },
        },
        {
            "process": ProjectileModelProc,
            "user_params": {
                "gravity": "gravity",
                "mass": "mass",
            },
            "user_paths": {
                "velocity_file": "velocity.json",
                "configs_folder": "configs",
            },
            "required_paths": {
                "coords_file": "points_coordinates.csv",
            },
            "output_paths": {
                "comp_folder": "comparison",
            },
        },
    ]

    # ------------------ #
    # Define application #
    # ------------------ #
    app = Application(
        app_name=APP_NAME,
        nuremics_dir=repo.working_tree_dir,
        workflow=workflow,
    )
    # Run it!
    app()

if __name__ == "__main__":
    main()

With all required mappings now properly defined for each Proc, the App can be executed without raising any errors. NUREMICS confirms that the full mapping is complete by prompting a summary for each Proc, indicating that all input parameters, input paths, and output paths have been successfully resolved.

👤🔄🖥️

| PolygonGeometryProc |
> Input Parameter(s) :
(float) radius  -----||----- 0.5      (hard_params)
(int)   n_sides -----||----- nb_sides (user_params)
> Input Path(s) :
title_file -----||----- plot_title.txt (user_paths)
> Input Analysis :
None.
> Output Path(s) :
coords_file -----||----- points_coordinates.csv (output_paths)
fig_file    -----||----- polygon_shape.png      (output_paths)

| ProjectileModelProc |
> Input Parameter(s) :
(float) gravity -----||----- gravity (user_params)
(float) mass    -----||----- mass    (user_params)
> Input Path(s) :
velocity_file  -----||----- velocity.json          (user_paths)
configs_folder -----||----- configs                (user_paths)
coords_file    -----||----- points_coordinates.csv (required_paths)
> Input Analysis :
None.
> Output Path(s) :
comp_folder -----||----- comparison (output_paths)


As the App has now been fully assembled, NUREMICS displays a clean summary of its I/O interface, as it will appear to the end-user. This summary includes all declared user parameters ("user_params") and user paths ("user_paths") required as inputs, along with the corresponding output files and folders that the App will generate. It serves as an explicit interface contract, allowing end-users to clearly understand what data they need to provide and what results to expect.

👤🔄🖥️

> INPUTS <

| User Parameters |
> nb_sides (int)
> gravity (float)
> mass (float)

| User Paths |
> plot_title.txt
> velocity.json
> configs

> OUTPUTS <

> points_coordinates.csv
> polygon_shape.png
> comparison


With all Procs implemented and properly assembled within the App, the development work is now complete. The developer’s responsibility ends here (excluding, of course, the implementation of unit tests to ensure long-term maintainability, which falls outside the scope of this tutorial).

The App is now fully functional and ready to be operated by end-users. From this point, users can interact with the App through its declared I/O interface, without needing to modify or understand the underlying code structure.


Explore NUREMICS in: