Tutorial#
This tutorial will guide you through a complete dimensional analysis workflow using PyDASA, from basic dimensional analysis to advanced sensitivity and Monte Carlo simulations.
We’ll use the Reynolds number and pipe flow as a practical example to demonstrate all four major PyDASA workflows.
Introduction#
What You’ll Learn:
AnalysisEngine - Perform dimensional analysis using Buckingham Pi theorem
SensitivityAnalysis - Understand which variables have the most influence
MonteCarloSimulation - Quantify uncertainty in your dimensionless coefficients
Data Export & Visualization - Use results with matplotlib, pandas, and other libraries
Running Example: Reynolds Number & Pipe Flow
We’ll analyze pipe flow, which depends on:
ρ (rho) - Fluid density [kg/m³]
v - Flow velocity [m/s]
D - Pipe diameter [m]
μ (mu) - Dynamic viscosity [Pa·s]
ΔP - Pressure drop [Pa]
L - Pipe length [m]
ε (epsilon) - Absolute roughness [m]
This will generate three key dimensionless numbers:
Reynolds Number (:math:`Re`) - Flow regime predictor
Darcy Friction Factor (f) - Pressure loss coefficient
Relative Roughness (ε/D) - Surface roughness effect
Stage 1: Dimensional Analysis with AnalysisEngine#
The AnalysisEngine workflow automatically derives dimensionless coefficients
from your physical variables using the Buckingham Pi theorem.
Step 1.1: Import PyDASA#
from pydasa.workflows.phenomena import AnalysisEngine
from pydasa.elements.parameter import Variable
import numpy as np
Step 1.2: Define Variables#
Define variables as dictionaries with all required attributes. Each variable needs:
_idx- Unique index_sym- Symbol (use LaTeX for Greek letters like"\\rho_{1}")_fwk- Framework ("PHYSICAL"uses M, L, T dimensions)_cat- Category:"IN"(input),"OUT"(output), or"CTRL"(control)relevant- Must beTrueto include in analysis_dims- Dimensional representation (e.g.,"M*L^-3"for density)_setpoint- Numerical value for calculations
variables_dict = {
# Fluid density: ρ [M/L^3]
"\\rho_{1}": {
"_idx": 0,
"_sym": "\\rho_{1}",
"_alias": "rho_1",
"_fwk": "PHYSICAL",
"_cat": "IN",
"_name": "Density",
"description": "Fluid density (water at 20°C)",
"relevant": True,
"_dims": "M*L^-3",
"_units": "kg/m³",
"_setpoint": 1000.0,
"_std_setpoint": 1000.0,
"_std_min": 990.0,
"_std_max": 1020.0,
},
# Velocity: v [L/T]
"v": {
"_idx": 1,
"_sym": "v",
"_fwk": "PHYSICAL",
"_cat": "IN",
"_name": "Velocity",
"description": "Flow velocity",
"relevant": True,
"_dims": "L*T^-1",
"_units": "m/s",
"_setpoint": 5.0,
"_std_setpoint": 5.0,
},
# Pipe diameter: D [L]
"D": {
"_idx": 2,
"_sym": "D",
"_fwk": "PHYSICAL",
"_cat": "IN",
"_name": "Pipe Diameter",
"relevant": True,
"_dims": "L",
"_units": "m",
"_setpoint": 0.05,
"_std_setpoint": 0.05,
},
# Dynamic viscosity: μ [M/(L·T)]
"\\mu": {
"_idx": 3,
"_sym": "\\mu",
"_fwk": "PHYSICAL",
"_cat": "OUT", # Exactly ONE output variable required
"_name": "Dynamic Viscosity",
"relevant": True,
"_dims": "M*L^-1*T^-1",
"_units": "Pa·s",
"_setpoint": 0.001002,
"_std_setpoint": 0.001002,
},
# Pressure drop: ΔP [M/(L·T^2)]
"P": {
"_idx": 4,
"_sym": "P",
"_fwk": "PHYSICAL",
"_cat": "CTRL",
"_name": "Pressure Drop",
"relevant": True,
"_dims": "M*L^-1*T^-2",
"_units": "Pa",
"_setpoint": 5000.0,
"_std_setpoint": 5000.0,
},
# Pipe length: L [L]
"L": {
"_idx": 5,
"_sym": "L",
"_fwk": "PHYSICAL",
"_cat": "CTRL",
"_name": "Pipe Length",
"relevant": True,
"_dims": "L",
"_units": "m",
"_setpoint": 10.0,
"_std_setpoint": 10.0,
},
# Absolute roughness: ε [L]
"\\varepsilon": {
"_idx": 6,
"_sym": "\\varepsilon",
"_fwk": "PHYSICAL",
"_cat": "CTRL",
"_name": "Absolute Roughness",
"relevant": True,
"_dims": "L",
"_units": "m",
"_setpoint": 0.000025,
"_std_setpoint": 0.000025,
}
}
# Convert to Variable objects
variables = {
sym: Variable(**params) for sym, params in variables_dict.items()
}
Important Notes:
Exactly ONE variable must have
"_cat": "OUT"All relevant variables must have
"relevant": TrueUse
"M","L","T"in_dimsfor PHYSICAL framework
Step 1.3: Create Analysis Engine#
# Create engine
engine = AnalysisEngine(
_idx=0,
_fwk="PHYSICAL",
_name="Pipe Flow Analysis",
description="Dimensional analysis for Reynolds number and friction factor"
)
# Add variables
engine.variables = variables
Step 1.4: Run Analysis#
# Execute dimensional analysis
results = engine.run_analysis()
print(f"Number of dimensionless groups: {len(engine.coefficients)}")
# Display results
for name, coeff in engine.coefficients.items():
print(f"{name}: {coeff.pi_expr}")
Expected Output:
Number of dimensionless groups: 4
\Pi_{0}: \mu/(\rho_{1}*v*D)
\Pi_{1}: L/D
\Pi_{2}: P/(\rho_{1}*v**2)
\Pi_{3}: \varepsilon/D
Step 1.5: Derive Meaningful Coefficients#
The original Pi groups may not be physically meaningful. Derive common coefficients:
# Get Pi coefficient keys
pi_keys = list(engine.coefficients.keys())
# Derive Reynolds Number: Re = 1/Π₀ = ρvD/μ
Re_coeff = engine.derive_coefficient(
expr=f"1/{pi_keys[0]}",
symbol="Re",
name="Reynolds Number",
description="Re = ρvD/μ - Predicts flow regime",
idx=-1
)
# Derive Darcy Friction Factor: f = 2(D/L)(ΔP/(ρv²)) = 2·(1/Π₁)·Π₂
f_coeff = engine.derive_coefficient(
expr=f"2*{pi_keys[1]}^-1 * {pi_keys[2]}",
symbol="Pd",
name="Pressure Drop Ratio",
description="Related to Darcy friction factor",
idx=-1
)
# Relative Roughness: ε/D = Π₃
rough_coeff = engine.derive_coefficient(
expr=f"{pi_keys[3]}",
symbol="\\epsilon/D",
name="Relative Roughness",
description="ε/D - Surface roughness effect",
idx=-1
)
# Calculate numerical values
Re_val = Re_coeff.calculate_setpoint()
f_val = f_coeff.calculate_setpoint()
rough_val = rough_coeff.calculate_setpoint()
print(f"\nReynolds Number: {Re_val:.2e}")
print(f"Pressure Drop Ratio: {f_val:.6f}")
print(f"Relative Roughness: {rough_val:.2e}")
Expected Output:
Reynolds Number: 2.49e+05
Pressure Drop Ratio: 0.040000
Relative Roughness: 5.00e-04
Stage 2: Sensitivity Analysis#
The SensitivityAnalysis workflow helps you understand which variables
most influence your dimensionless coefficients.
Step 2.1: Import and Create Analyzer#
from pydasa.workflows.influence import SensitivityAnalysis
# Create sensitivity analyzer
sensitivity = SensitivityAnalysis(
_idx=0,
_fwk="PHYSICAL",
_name="Reynolds Sensitivity",
_cat="SYM" # Symbolic analysis
)
# Configure with engine results
sensitivity.variables = engine.variables
sensitivity.coefficients = engine.coefficients
Step 2.2: Symbolic Sensitivity Analysis#
Compute partial derivatives at mean values:
# Run symbolic analysis
symbolic_results = sensitivity.analyze_symbolic(val_type="mean")
# Display for Reynolds Number
Re_sensitivity = symbolic_results["SEN_{Re}"]
print("Reynolds Number Sensitivity:")
for var, sens_val in Re_sensitivity.items():
if isinstance(sens_val, (int, float)):
print(f" ∂Re/∂{var}: {sens_val:+.4e}")
Expected Output:
Reynolds Number Sensitivity:
∂Re/∂ρ: +2.4900e+02
∂Re/∂v: +4.9800e+04
∂Re/∂D: +4.9800e+03
∂Re/∂μ: -2.4851e+08
Step 2.3: Numerical Sensitivity (FAST Method)#
Use variance-based FAST method for confidence intervals:
import random
np.random.seed(42)
random.seed(42)
# Run numerical analysis
numerical_results = sensitivity.analyze_numeric(n_samples=1000)
# Display FAST sensitivity indices
Re_fast = numerical_results["SEN_{Re}"]
print("\nFAST Sensitivity for Reynolds Number:")
print(f"{'Variable':<15} {'First-Order (S1)':<20} {'Total-Order (ST)':<20}")
print("-" * 55)
var_names = Re_fast["names"]
S1_vals = Re_fast["S1"]
ST_vals = Re_fast["ST"]
for i, var_name in enumerate(var_names):
print(f"{var_name:<15} {S1_vals[i]:<20.6f} {ST_vals[i]:<20.6f}")
Expected Output:
FAST Sensitivity for Reynolds Number:
Variable First-Order (S1) Total-Order (ST)
-------------------------------------------------------
\rho_{1} 0.333333 0.333333
v 0.333333 0.333333
D 0.333333 0.333333
\mu 0.000000 0.000000
Interpretation:
S1 (First-Order): Direct effect of variable
ST (Total-Order): Total effect including interactions
rho_{1}, v, D each contribute equally (~33.0 %) to Reynolds number
mu has zero variance (kept constant in this setup)
Stage 3: Monte Carlo Simulation#
The MonteCarloSimulation workflow quantifies uncertainty in your
dimensionless coefficients through probabilistic sampling.
Step 3.1: Configure Distributions#
Define probability distributions for each variable:
from pydasa.workflows.practical import MonteCarloSimulation
import random
# Set seeds for reproducibility
np.random.seed(42)
random.seed(42)
# Configure variable distributions
for var_sym, var in engine.variables.items():
if var_sym == "v":
# Vary velocity uniformly from 1.0 to 5.0 m/s
a, b = 1.0, 5.0
var._dist_type = "uniform"
var._dist_params = {"a": a, "b": b}
var._dist_func = lambda a=a, b=b: random.uniform(a, b)
elif var_sym == "D":
# Small variation in diameter
a, b = 0.049, 0.051
var._dist_type = "uniform"
var._dist_params = {"a": a, "b": b}
var._dist_func = lambda a=a, b=b: random.uniform(a, b)
elif var_sym == "\\rho_{1}":
# Density variation around water at 20°C
a, b = 980.0, 1020.0
var._dist_type = "uniform"
var._dist_params = {"a": a, "b": b}
var._dist_func = lambda a=a, b=b: random.uniform(a, b)
else:
# Keep other variables constant
cst = var.setpoint
var._dist_type = "constant"
var._dist_params = {"value": cst}
var._dist_func = lambda cst=cst: cst
Step 3.2: Run Monte Carlo Simulation#
# Create simulation handler
mc_handler = MonteCarloSimulation(
_idx=0,
_fwk="PHYSICAL",
_name="Reynolds Monte Carlo",
_cat="DIST",
_experiments=500,
_variables=engine.variables,
_coefficients=engine.coefficients
)
# Run simulation
mc_handler.run_simulation(iters=500)
print(f"Simulation complete: {mc_handler.experiments} experiments")
Step 3.3: Extract and Analyze Results#
# Get simulation keys
all_keys = list(mc_handler.simulations.keys())
derived_keys = [k for k in all_keys if not k.startswith('\\Pi_')]
# Extract results
pi_data = {}
for pi_key in derived_keys:
pi_sim_obj = mc_handler.get_simulation(pi_key)
pi_results = pi_sim_obj.extract_results()
pi_data[pi_key] = pi_results[pi_key]
# Get Reynolds number simulation data
Re_sim = pi_data["Re"]
# Display statistics
print(f"\nReynolds Number Statistics:")
print(f" Mean: {np.mean(Re_sim):.2e}")
print(f" Std Dev: {np.std(Re_sim):.2e}")
print(f" Min: {np.min(Re_sim):.2e}")
print(f" Max: {np.max(Re_sim):.2e}")
print(f" Range: {np.max(Re_sim) - np.min(Re_sim):.2e}")
Expected Output:
Reynolds Number Statistics:
Mean: 1.24e+05
Std Dev: 7.17e+04
Min: 1.96e+04
Max: 2.55e+05
Range: 2.35e+05
Stage 4: Data Export & Visualization#
PyDASA results can be exported to dictionaries for use with matplotlib, pandas, seaborn, and other data analysis libraries.
Step 4.1: Export to Dictionaries#
# Export coefficient data
coeff_export = {}
for name, coeff in engine.coefficients.items():
coeff_export[name] = {
"expression": str(coeff.pi_expr),
"value": coeff.calculate_setpoint(),
"variables": list(coeff.var_dims.keys()),
"exponents": list(coeff.var_dims.values())
}
# Export variable data
var_export = {}
for sym, var in engine.variables.items():
var_export[sym] = {
"name": var.name,
"dims": var.dims,
"units": var.units,
"setpoint": var.setpoint
}
Step 4.2: Use with Pandas#
import pandas as pd
import os
# Create data directory if it doesn't exist
os.makedirs("data", exist_ok=True)
# Create DataFrame from simulation results
sim_df = pd.DataFrame({
"Re": Re_sim,
"epsilon_D": pi_data["\\epsilon/D"],
"Pd": pi_data["Pd"]
})
# Statistical summary
print(sim_df.describe())
# Export to CSV
sim_df.to_csv("data/reynolds_simulation.csv", index=False)
print("CSV exported to data/reynolds_simulation.csv")
Step 4.3: Visualization with Matplotlib#
Histogram of Reynolds Number:
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(10, 6))
# Plot histogram
ax.hist(Re_sim, bins=50, color="steelblue",
alpha=0.7, edgecolor="black")
# Add flow regime boundaries
ax.axvline(2300, color="green", linestyle="--",
linewidth=2, label="Laminar limit")
ax.axvline(4000, color="red", linestyle="--",
linewidth=2, label="Turbulent limit")
# Labels and formatting
ax.set_xlabel("Reynolds Number (Re)", fontsize=12, fontweight="bold")
ax.set_ylabel("Frequency", fontsize=12, fontweight="bold")
ax.set_title(f"Reynolds Number Distribution (n={len(Re_sim)})",
fontsize=14, fontweight="bold")
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
Sensitivity Bar Chart:
fig, ax = plt.subplots(figsize=(10, 6))
# Get FAST results
Re_fast = numerical_results["SEN_{Re}"]
var_names = Re_fast["names"]
S1_vals = Re_fast["S1"]
# Create bar chart
y_pos = np.arange(len(var_names))
ax.barh(y_pos, S1_vals, color="steelblue",
alpha=0.8, edgecolor="black")
# Formatting
ax.set_yticks(y_pos)
ax.set_yticklabels([f"${name}$" for name in var_names])
ax.set_xlabel("First-Order Sensitivity Index (S1)",
fontsize=12, fontweight="bold")
ax.set_title("Variable Influence on Reynolds Number",
fontsize=14, fontweight="bold")
ax.grid(True, alpha=0.3, axis="x")
plt.tight_layout()
plt.show()
Scatter Plot Matrix:
from pandas.plotting import scatter_matrix
# Create scatter matrix
scatter_matrix(sim_df, figsize=(12, 12),
alpha=0.6, diagonal="kde")
plt.suptitle("Monte Carlo Simulation Results",
fontsize=16, fontweight="bold")
plt.tight_layout()
plt.show()
Step 4.4: Export for External Tools#
import json
import os
# Create data directory if it doesn't exist
os.makedirs("data", exist_ok=True)
# Export to JSON for web applications
export_data = {
"coefficients": coeff_export,
"variables": var_export,
"simulation": {
"n_samples": len(Re_sim),
"Re_mean": float(np.mean(Re_sim)),
"Re_std": float(np.std(Re_sim)),
"Re_data": Re_sim.tolist()
}
}
with open("data/pydasa_results.json", "w") as f:
json.dump(export_data, f, indent=2)
print("Results exported to data/pydasa_results.json")
Tips and Tricks#
This section provides essential guidelines and best practices to help you avoid common mistakes and get the most out of PyDASA’s features.
Variable Definition
Proper variable configuration is critical for successful dimensional analysis. Each variable requires specific attributes to function correctly in the PyDASA workflow.
Always set exactly ONE variable with
"_cat": "OUT"Include
"relevant": Truefor all variables in analysisProvide
_setpointand_std_setpointfor calculationsProvide
_std_minand_std_maxfor sensitivity analysis boundsUse descriptive
_nameanddescriptionfields
Dimensional Consistency
PyDASA automatically validates dimensional consistency across your variables. Understanding how to properly specify dimensions is essential for accurate analysis.
# PyDASA validates dimensions automatically
# Use FDUs like M (Mass), L (Length), T (Time) for the PHYSICAL framework
# Good: Velocity [L/T]
"_dims": "L*T^-1"
# Good: Density [M/L^3]
"_dims": "M*L^-3"
# Good: Force [M·L/T^2]
"_dims": "M*L*T^-2"
Distribution Configuration
When running Monte Carlo simulations, each variable needs a properly configured probability distribution. This determines how the variable varies during the simulation.
# For Monte Carlo, always set: # - _dist_type: "uniform", "normal", or "constant" # - _dist_params: dict with distribution parameters # - _dist_func: lambda function for sampling # Example: Uniform distribution var._dist_type = "uniform" var._dist_params = {"a": 1.0, "b": 5.0} var._dist_func = lambda: random.uniform(1.0, 5.0)
Common Pitfalls#
Missing Output Variable
# ❌ WRONG: No output variable variables = { "v": {"_cat": "IN", ...}, "D": {"_cat": "IN", ...} } # ✅ CORRECT: Exactly one output variables = { "v": {"_cat": "IN", ...}, "D": {"_cat": "IN", ...}, "\\mu": {"_cat": "OUT", ...} # One output }
Inconsistent Dimensions
# ❌ WRONG: Incorrect dimension for velocity "v": {"_dims": "M*L^-1", ...} # This is viscosity! # ✅ CORRECT: Velocity is L/T "v": {"_dims": "L*T^-1", ...}
Forgot to Set Distribution
# ❌ WRONG: Missing distribution for Monte Carlo mc_handler = MonteCarloSimulation(...) mc_handler.run_simulation() # Will fail! # ✅ CORRECT: Configure distributions first for var in engine.variables.values(): var._dist_type = "uniform" var._dist_params = {"a": min_val, "b": max_val} var._dist_func = lambda: random.uniform(min_val, max_val) mc_handler.run_simulation()
Ignoring Relevant Flag
# ❌ WRONG: Variable defined but not included "v": {"relevant": False, ...} # Will be ignored! # ✅ CORRECT: Include in analysis "v": {"relevant": True, ...} # Good reynolds_number = pi_groups[0] # Better Re = pi_groups[0] Re.name = "Reynolds Number" Re.symbol = "Re"
Summary#
Workflow Overview:
Stage |
Workflow |
Key Actions |
|---|---|---|
1 |
AnalysisEngine |
Define variables → Create engine → Run analysis → Derive coefficients |
2 |
SensitivityAnalysis |
Configure analyzer → Run symbolic analysis → Run FAST method → Interpret indices |
3 |
MonteCarloSimulation |
Set distributions → Run simulation → Extract results → Compute statistics |
4 |
Export & Visualize |
Export to dict/DataFrame → Create plots → Save results → Generate reports |
Next Steps#
Now that you’ve completed the tutorial, explore:
Create other Physical phenomena (e.g., heat transfer, mass transfer).
Use PyDASA for engineering design and optimization in your projects.
Experiment with different sensitivity methods and Monte Carlo configurations.
See also
Full Working Examples:
Notebooks:
Code:
Documentation and User Guides:
To understand PyDASA capabilities and internal technical details check:
User Guide - Available features and workflows.
Examples - More practical examples using PyDASA.
API Reference - Complete API reference.
Questions or Issues?
Visit the GitHub Issues Page for support.