Theory
Design Patterns
Let’s introduce the core design patterns behind Procs and Apps in NUREMICS.
Proc
A Proc can be seen as an algorithmic box which processes some input data and produces corresponding output data.
The input data typically fall into two main categories:
-
Input parameters: Scalar values such as
float
,int
,bool
, orstr
. -
Input paths: Files or folders provided as
Path
objects (from Python'spathlib
module), pointing to structured data on disk.
erDiagram
**Parameters** ||--|| **Inputs** : provides
**Paths** ||--|| **Inputs** : provides
**Parameters** {
float radius
int n_sides
}
**Paths** {
file title_file "txt"
}
As mentioned in the Handbook Overview, the algorithmic box of the Proc is a class composed of functions (Op) called sequentially within its __call__
method.
erDiagram
**Parameters** ||--|| **Inputs** : provides
**Paths** ||--|| **Inputs** : provides
**Inputs** ||--|| **PolygonGeometryProc** : feeds
**Parameters** {
float radius
int n_sides
}
**Paths** {
file title_file "txt"
}
**PolygonGeometryProc** {
op generate_polygon_shape
op plot_polygon_shape
}
Output data are typically expressed as Path
objects as well, corresponding to files or folders written to disk during the execution of the Proc.
erDiagram
**Parameters** ||--|| **Inputs** : provides
**Paths** ||--|| **Inputs** : provides
**Inputs** ||--|| **PolygonGeometryProc** : feeds
**PolygonGeometryProc** ||--|| **Outputs** : generates
**Parameters** {
float radius
int n_sides
}
**Paths** {
file title_file "txt"
}
**PolygonGeometryProc** {
op generate_polygon_shape
op plot_polygon_shape
}
**Outputs** {
file coords_file "csv"
file fig_file "png"
}
For the sake of example, let's define another Proc considering the same structure.
erDiagram
**Parameters** ||--|| **Inputs** : provides
**Paths** ||--|| **Inputs** : provides
**Inputs** ||--|| **ProjectileModelProc** : feeds
**ProjectileModelProc** ||--|| **Outputs** : generates
**Parameters** {
float gravity
float mass
}
**Paths** {
file velocity_file "json"
folder configs_folder "_"
file coords_file "csv"
}
**ProjectileModelProc** {
op simulate_projectile_motion
op calculate_analytical_trajectory
op compare_model_vs_analytical_trajectories
}
**Outputs** {
folder comp_folder "_"
}
App
A final end-user App can be built by plugging together previously implemented Procs, and specifying their sequential order of execution within the workflow.
flowchart RL
**PolygonGeometryProc** e1@--1--o **DEMO_APP**
**ProjectileModelProc** e2@--2--o **DEMO_APP**
**TrajectoryAnalysisProc** e3@--3--o **DEMO_APP**
**generate_polygon_shape** e4@--A--o **PolygonGeometryProc**
**plot_polygon_shape** e5@--B--o **PolygonGeometryProc**
**simulate_projectile_motion** e6@--A--o **ProjectileModelProc**
**calculate_analytical_trajectory** e7@--B--o **ProjectileModelProc**
**compare_model_vs_analytical_trajectories** e8@--C--o **ProjectileModelProc**
**plot_overall_model_vs_theory** e9@--A--o **TrajectoryAnalysisProc**
e1@{ animate: true }
e2@{ animate: true }
e3@{ animate: true }
e4@{ animate: true }
e5@{ animate: true }
e6@{ animate: true }
e7@{ animate: true }
e8@{ animate: true }
e9@{ animate: true }
Each Proc integrated into the App defines its own set of inputs and outputs, specific to its internal algorithmic logic. When these Procs are assembled into a workflow, the App itself exposes a higher-level set of inputs and outputs. These define the I/O interface presented to the end-user, who provides the necessary input data and retrieves the final results upon execution.
The assembly step is performed through a mapping between the internal I/O data of each Proc and the global I/O interface of the App. This mapping mechanism serves multiple purposes:
-
It defines which data are exposed to the end-user (and how they are displayed) and which remain internal to the workflow.
-
It manages the data dependencies between Procs, when the output of one Proc is used as input for another.
This notably ensures a coherent and seamless management of data across the workflow, while delivering a clean and focused I/O interface tailored to the user's needs.
The mapping between a Proc and the App starts by specifying which Proc input parameters are exposed to the end-user, and how they are labeled in the App input interface.
erDiagram
**DEMO_APP** ||--|| **user_params** : mapping
**user_params** ||--|| **PolygonGeometryProc** : mapping
**user_params** {
int n_sides "nb_sides"
}
The Proc input parameters that remain internal to the workflow are assigned fixed values directly within the mapping definition, without being exposed to the end-user.
erDiagram
**DEMO_APP** ||--|| **user_params** : mapping
**DEMO_APP** ||--|| **hard_params** : mapping
**user_params** ||--|| **PolygonGeometryProc** : mapping
**hard_params** ||--|| **PolygonGeometryProc** : mapping
**user_params** {
int n_sides "nb_sides"
}
**hard_params** {
float radius "0.5"
}
The Proc input paths that need to be provided by the end-user are specified by defining the expected file or folder names within the App input interface.
erDiagram
**DEMO_APP** ||--|| **user_params** : mapping
**DEMO_APP** ||--|| **hard_params** : mapping
**DEMO_APP** ||--|| **user_paths** : mapping
**user_params** ||--|| **PolygonGeometryProc** : mapping
**hard_params** ||--|| **PolygonGeometryProc** : mapping
**user_paths** ||--|| **PolygonGeometryProc** : mapping
**user_params** {
int n_sides "nb_sides"
}
**hard_params** {
float radius "0.5"
}
**user_paths** {
file title_file "plot_title.txt"
}
The Proc input paths can also be mapped to output paths produced by a previous Proc within the workflow (although this does not apply here, as we are currently focusing on the first Proc in the workflow).
erDiagram
**DEMO_APP** ||--|| **user_params** : mapping
**DEMO_APP** ||--|| **hard_params** : mapping
**DEMO_APP** ||--|| **user_paths** : mapping
**DEMO_APP** ||--|| **required_paths** : mapping
**user_params** ||--|| **PolygonGeometryProc** : mapping
**hard_params** ||--|| **PolygonGeometryProc** : mapping
**user_paths** ||--|| **PolygonGeometryProc** : mapping
**required_paths** ||--|| **PolygonGeometryProc** : mapping
**user_params** {
int n_sides "nb_sides"
}
**hard_params** {
float radius "0.5"
}
**user_paths** {
file title_file "plot_title.txt"
}
**required_paths** {
_ _ "_"
}
Finally, the Proc output paths are specified by indicating the name of the file(s) or folder(s) that will be written by the Proc during the workflow execution.
erDiagram
**DEMO_APP** ||--|| **user_params** : mapping
**DEMO_APP** ||--|| **hard_params** : mapping
**DEMO_APP** ||--|| **user_paths** : mapping
**DEMO_APP** ||--|| **required_paths** : mapping
**DEMO_APP** ||--|| **output_paths** : mapping
**user_params** ||--|| **PolygonGeometryProc** : mapping
**hard_params** ||--|| **PolygonGeometryProc** : mapping
**user_paths** ||--|| **PolygonGeometryProc** : mapping
**required_paths** ||--|| **PolygonGeometryProc** : mapping
**output_paths** ||--|| **PolygonGeometryProc** : mapping
**user_params** {
int n_sides "nb_sides"
}
**hard_params** {
float radius "0.5"
}
**user_paths** {
file title_file "plot_title.txt"
}
**required_paths** {
_ _ "_"
}
**output_paths** {
file coords_file "points_coordinates.csv"
file fig_file "polygon_shape.png"
}
Let's now assemble the second Proc to be executed by the App within the workflow, by establishing a dependency: the output data produced by the first Proc will serve as input data for this second one.
erDiagram
**DEMO_APP** ||--|| **user_params** : mapping
**DEMO_APP** ||--|| **hard_params** : mapping
**DEMO_APP** ||--|| **user_paths** : mapping
**DEMO_APP** ||--|| **required_paths** : mapping
**DEMO_APP** ||--|| **output_paths** : mapping
**user_params** ||--|| **ProjectileModelProc** : mapping
**hard_params** ||--|| **ProjectileModelProc** : mapping
**user_paths** ||--|| **ProjectileModelProc** : mapping
**required_paths** ||--|| **ProjectileModelProc** : mapping
**output_paths** ||--|| **ProjectileModelProc** : mapping
**user_params** {
float gravity "gravity"
float mass "mass"
}
**hard_params** {
_ _ "_"
}
**user_paths** {
file velocity_file "velocity.json"
folder configs_folder "configs"
}
**required_paths** {
file coords_file "points_coordinates.csv"
}
**output_paths** {
folder comp_folder "comparison"
}
Once all Procs have been assembled into the App, the final I/O interface presented to the end-user emerges.
flowchart LR
subgraph **INPUTS**
direction TB
subgraph **Paths**
direction LR
path1["plot_title.txt _(file)_"]
path2["velocity.json _(file)_"]
path3["configs _(folder)_"]
end
subgraph **Parameters**
direction LR
param1["nb_sides _(int)_"]
param2["gravity _(float)_"]
param3["mass _(float)_"]
end
end
subgraph **DEMO_APP**
direction RL
proc1["PolygonGeometryProc"]
proc2["ProjectileModelProc"]
end
subgraph **OUTPUTS**
direction RL
out1["points_coordinates.csv _(file)_"]
out2["polygon_shape.png _(file)_"]
out3["comparison _(folder)_"]
end
**INPUTS** --> **DEMO_APP**
**DEMO_APP** --> **OUTPUTS**
It is also insightful for the end-user to present this I/O interface by showing which INPUTS are used by each Proc of the App, and which OUTPUTS are written by each of them.
flowchart LR
subgraph **INPUTS**
direction TB
subgraph **Paths**
direction LR
path1["plot_title.txt _(file)_"]
end
subgraph **Parameters**
direction LR
param1["nb_sides _(int)_"]
end
end
subgraph **DEMO_APP**
direction RL
proc1["PolygonGeometryProc"]
end
subgraph **OUTPUTS**
direction RL
out1["points_coordinates.csv _(file)_"]
out2["polygon_shape.png _(file)_"]
end
**INPUTS** --> proc1
proc1 --> **OUTPUTS**
flowchart LR
subgraph **INPUTS**
direction TB
subgraph **Paths**
direction LR
path2["velocity.json _(file)_"]
path3["configs _(folder)_"]
out1["points_coordinates.csv _(file)_"]
end
subgraph **Parameters**
direction LR
param2["gravity _(float)_"]
param3["mass _(float)_"]
end
end
subgraph **DEMO_APP**
direction RL
proc2["ProjectileModelProc"]
end
subgraph **OUTPUTS**
direction RL
out3["comparison _(folder)_"]
end
**INPUTS** --> proc2
proc2 --> **OUTPUTS**
classDef blueBox fill:#d0e6ff,stroke:#339,stroke-width:1.5px;
class out1 blueBox;
Usability
The Apps built with NUREMICS come with a lean and pragmatic user interface by design. No flashy GUI, but instead, the focus is on simplicity and efficiency:
-
An input database that the operator completes by editing configuration files and uploading the required input files and folders.
-
A terminal interface that provides informative feedback at each execution, clearly indicating what the App is doing and what actions are expected from the operator.
-
An output database that stores all results in a well-structured and traceable folder hierarchy.
sequenceDiagram
actor Operator
Operator->>DEMO_APP: Execution
DEMO_APP->>INPUTS: Initialize database
DEMO_APP->>Operator: Terminal feedback
Operator->>INPUTS: Complete database
Operator->>DEMO_APP: Execution
DEMO_APP->>INPUTS: Read database
DEMO_APP->>OUTPUTS: Write database
DEMO_APP->>Operator: Terminal feedback
Operator->>OUTPUTS: Access results
This streamlined approach prioritizes clarity, control, and reproducibility, making each App built with NUREMICS well-suited for both direct interaction by end-users and seamless integration into larger software ecosystems. In such environments, NUREMICS can operate as a backend computational engine, interacting programmatically with other tools (such as web applications) that provide their own user interfaces.
Configuration
When running an App, the operator first defines a set of studies aimed at exploring the INPUTS space and analyzing the outcomes in the OUTPUTS space.
flowchart LR
Study_Shape
Study_Velocity
The operator then configures each study by selecting which inputs stay constant (Fixed) and which ones change (Variable) across the various experiments.
flowchart LR
subgraph Fixed1["**Fixed**"]
direction TB
subgraph Paths_Fixed1["**Paths**"]
direction LR
path1_1["plot_title.txt"]
path2_1["velocity.json"]
path3_1["configs"]
end
subgraph Parameter_Fixed1["**Parameters**"]
direction LR
param2_1["gravity"]
param3_1["mass"]
end
end
subgraph Variable1["**Variable**"]
direction TB
subgraph Paths_Variable1["**Paths**"]
direction LR
no_path["_"]
end
subgraph Parameter_Variable1["**Parameters**"]
direction LR
param1_1["nb_sides"]
end
end
Study_Shape --> Fixed1
Study_Shape --> Variable1
subgraph Fixed2["**Fixed**"]
direction TB
subgraph Paths_Fixed2["**Paths**"]
direction LR
path1_2["plot_title.txt"]
path3_2["configs"]
end
subgraph Parameter_Fixed2["**Parameters**"]
direction LR
param1_2["nb_sides"]
param2_2["gravity"]
param3_2["mass"]
end
end
subgraph Variable2["**Variable**"]
direction TB
subgraph Paths_Variable2["**Paths**"]
direction LR
path2_2["velocity.json"]
end
subgraph Parameter_Variable2["**Parameters**"]
direction LR
no_param["_"]
end
end
Study_Velocity --> Fixed2
Study_Velocity --> Variable2
Settings
To conduct experiments, the operator assigns values for both fixed and variable inputs: fixed inputs remain constant across all experiments (Common), while variable inputs are adjusted from one experiment to another (Test1, Test2, ...).
flowchart LR
Study_Shape --> Study1_Common["Common"]
Study_Shape --> Study1_Test1["Test1"]
Study_Shape --> Study1_Test2["Test2"]
Study_Shape --> Study1_Test3["..."]
Study1_Common --> common1_param2["gravity = ..."]
Study1_Common --> common1_param3["mass = ..."]
Study1_Common --> common1_input1["plot_title.txt _(uploaded)_"]
Study1_Common --> common1_input2["velocity.json _(uploaded)_"]
Study1_Common --> common1_input3["configs _(uploaded)_"]
Study1_Test1 --> test1_param1["nb_sides = ..."]
Study1_Test2 --> test2_param1["nb_sides = ..."]
Study1_Test3 --> test3_param1["nb_sides = ..."]
Study_Velocity --> Study2_Common["Common"]
Study_Velocity --> Study2_Test1["Test1"]
Study_Velocity --> Study2_Test2["Test2"]
Study_Velocity --> Study2_Test3["..."]
Study2_Common --> common2_param1["nb_sides = ..."]
Study2_Common --> common2_param2["gravity = ..."]
Study2_Common --> common2_param3["mass = ..."]
Study2_Common --> common2_input1["plot_title.txt _(uploaded)_"]
Study2_Common --> common2_input3["configs _(uploaded)_"]
Study2_Test1 --> test1_path2["velocity.json _(uploaded)_"]
Study2_Test2 --> test2_path2["velocity.json _(uploaded)_"]
Study2_Test3 --> test3_path2["velocity.json _(uploaded)_"]
Results
At the end of the execution, results are stored in a structured output tree, ready for review or further processing. The outputs are first organized by Proc, each of them writing its own result data. Within each Proc, the results are further subdivided by experiment (Test1, Test2, ...), ensuring a clear separation and traceability of outcomes across the entire study.
This organization is automatically determined based on how the study is configured by the operator. NUREMICS analyzes which input data are marked as fixed or variable, and how they connect to the internal workflow of the App. If a Proc directly depends on variable inputs, or indirectly through upstream dependencies, it will generate distinct outputs for each experiment. Otherwise, it will produce shared outputs only once.
This logic ensures that only the necessary parts of the workflow are repeated through experimentations, and that the output structure faithfully reflects the configuration of the study along with the internal dependencies within the workflow.
flowchart LR
Study_Shape --> Study1_PolygonGeometryProc["PolygonGeometryProc"]
Study_Shape --> Study1_ProjectileModelProc["ProjectileModelProc"]
Study1_PolygonGeometryProc --> Study1_PolygonGeometryProc_Test1["Test1"]
Study1_PolygonGeometryProc --> Study1_PolygonGeometryProc_Test2["Test2"]
Study1_PolygonGeometryProc --> Study1_PolygonGeometryProc_Test3["..."]
Study1_PolygonGeometryProc_Test1 --> Study1_PolygonGeometryProc_Test1_output1["points_coordinates.csv"]
Study1_PolygonGeometryProc_Test1 --> Study1_PolygonGeometryProc_Test1_output2["polygon_shape.png"]
Study1_PolygonGeometryProc_Test2 --> Study1_PolygonGeometryProc_Test2_output1["points_coordinates.csv"]
Study1_PolygonGeometryProc_Test2 --> Study1_PolygonGeometryProc_Test2_output2["polygon_shape.png"]
Study1_PolygonGeometryProc_Test3 --> Study1_PolygonGeometryProc_Test3_output1["points_coordinates.csv"]
Study1_PolygonGeometryProc_Test3 --> Study1_PolygonGeometryProc_Test3_output2["polygon_shape.png"]
Study1_ProjectileModelProc --> Study1_ProjectileModelProc_Test1["Test1"]
Study1_ProjectileModelProc --> Study1_ProjectileModelProc_Test2["Test2"]
Study1_ProjectileModelProc --> Study1_ProjectileModelProc_Test3["..."]
Study1_ProjectileModelProc_Test1 --> Study1_ProjectileModelProc_Test1_output3["comparison"]
Study1_ProjectileModelProc_Test2 --> Study1_ProjectileModelProc_Test2_output3["comparison"]
Study1_ProjectileModelProc_Test3 --> Study1_ProjectileModelProc_Test3_output3["comparison"]
Study_Velocity --> Study2_PolygonGeometryProc["PolygonGeometryProc"]
Study_Velocity --> Study2_ProjectileModelProc["ProjectileModelProc"]
Study2_PolygonGeometryProc --> Study2_PolygonGeometryProc_Common_output1["points_coordinates.csv"]
Study2_PolygonGeometryProc --> Study2_PolygonGeometryProc_Common_output2["polygon_shape.png"]
Study2_ProjectileModelProc --> Study2_ProjectileModelProc_Test1["Test1"]
Study2_ProjectileModelProc --> Study2_ProjectileModelProc_Test2["Test2"]
Study2_ProjectileModelProc --> Study2_ProjectileModelProc_Test3["..."]
Study2_ProjectileModelProc_Test1 --> Study2_ProjectileModelProc_Test1_output3["comparison"]
Study2_ProjectileModelProc_Test2 --> Study2_ProjectileModelProc_Test2_output3["comparison"]
Study2_ProjectileModelProc_Test3 --> Study2_ProjectileModelProc_Test3_output3["comparison"]