Design
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
Pathobjects (from Python'spathlibmodule), 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;