Customization#
This tutorial demonstrates how to create and use custom dimensional frameworks in PyDASA for specialized applications. You’ll learn how to define your own fundamental dimensions and apply dimensional analysis to non-traditional engineering domains.
We’ll analyze a queueing system (M/M/c/K) using a simplified custom framework with only Time and Structure dimensions, making it analogous to classic dimensionless analysis like the Reynolds number.
Introduction#
What You’ll Learn:
Custom Schema Definition - Create your own dimensional framework with custom FDUs
Variable Definition with Custom Dimensions - Define variables using your custom framework
Dimensional Analysis - Apply Buckingham Pi theorem to derive dimensionless groups
Data Generation - Generate systematic data from a queueing model
Monte Carlo Simulation - Run probabilistic analysis with grid-based data
Advanced Visualization - Create comprehensive 3D and 2D plots
Running Example: M/M/c/K Queue Model
We’ll analyze a queueing system with:
λ (lambda) - Arrival rate [requests/s]
K - Queue capacity [requests]
L - Average queue length [requests]
W - Average waiting time [s]
μ (mu) - Service rate [requests/s]
c - Number of servers [requests]
This will generate three key dimensionless coefficients:
Occupancy (θ = L/K) - Queue capacity utilization
Stall (σ = W·λ/L) - Service blocking indicator
Effective-Yield (η = K·λ/(c·μ)) - Resource utilization effectiveness
Stage 1: Custom Framework Definition#
Unlike built-in frameworks (PHYSICAL, SOFTWARE), custom frameworks let you define dimensions specific to your problem domain. Here we create a minimal 2-dimensional framework for queueing analysis.
Step 1.1: Import Required Libraries#
# PyDASA imports
import pydasa
from pydasa.workflows.phenomena import AnalysisEngine
from pydasa.elements.parameter import Variable
from pydasa.dimensional.vaschy import Schema
# For visualization and data handling
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import itertools
print(f"PyDASA Version: {pydasa.__version__}")
Step 1.2: Define Custom Dimensional Framework#
Create a schema with only two fundamental dimensional units (FDUs):
# Define simplified FDU list (only T and S)
fdu_list = [
{
"_idx": 0,
"_sym": "T",
"_fwk": "CUSTOM",
"description": "Temporal measurements",
"_unit": "s",
"_name": "Time"
},
{
"_idx": 1,
"_sym": "S",
"_fwk": "CUSTOM",
"description": "System capacity and structural resources",
"_unit": "requests",
"_name": "Structure"
}
]
# Create schema
schema = Schema(_fwk="CUSTOM", _fdu_lt=fdu_list)
print("=== Simplified Custom Framework Created ===")
print(f"Framework: {schema.fwk}")
print(f"Number of FDUs: {len(schema._fdu_lt)}")
print("\nFundamental Dimensional Units:")
for fdu in schema._fdu_lt:
print(f"\t{fdu._sym} ({fdu._name}): {fdu._unit} - {fdu.description}")
Expected Output:
=== Simplified Custom Framework Created ===
Framework: CUSTOM
Number of FDUs: 2
Fundamental Dimensional Units:
T (Time): s - Temporal measurements
S (Structure): requests - System capacity and structural resources
Important Notes:
Each FDU must have unique
_idxand_sym_fwkmust be set to"CUSTOM"for custom frameworksUse descriptive names and units for clarity
Step 1.3: Define Queue Variables#
Define 6 variables using only T and S dimensions:
# Define simplified variables (only using T and S dimensions)
variables_dict = {
# INPUT VARIABLES
"\\lambda": {
"_idx": 0,
"_sym": "\\lambda",
"_alias": "lambda",
"_fwk": "CUSTOM",
"_cat": "IN",
"_name": "Arrival Rate",
"description": "Request arrival rate",
"relevant": True,
"_dims": "S*T^-1",
"_units": "req/s",
"_setpoint": 100.0,
"_std_setpoint": 100.0,
"_std_min": 100.0,
"_std_max": 500.0,
"_step": 10.0,
},
"K": {
"_idx": 1,
"_sym": "K",
"_alias": "K",
"_fwk": "CUSTOM",
"_cat": "IN",
"_name": "Queue Capacity",
"description": "Maximum system capacity",
"relevant": True,
"_dims": "S",
"_units": "requests",
"_setpoint": 10.0,
"_std_setpoint": 10.0,
},
# OUTPUT VARIABLE
"L": {
"_idx": 2,
"_sym": "L",
"_alias": "L",
"_fwk": "CUSTOM",
"_cat": "OUT",
"_name": "Queue Length",
"description": "Average queue length",
"relevant": True,
"_dims": "S",
"_units": "requests",
"_setpoint": 0.9946,
"_std_setpoint": 0.9946,
},
# CONTROL VARIABLES
"W": {
"_idx": 3,
"_sym": "W",
"_alias": "W",
"_fwk": "CUSTOM",
"_cat": "CTRL",
"_name": "Waiting Time",
"description": "Average waiting time",
"relevant": True,
"_dims": "T",
"_units": "s",
"_setpoint": 0.005,
"_std_setpoint": 0.005,
},
"\\mu": {
"_idx": 4,
"_sym": "\\mu",
"_alias": "mu",
"_fwk": "CUSTOM",
"_cat": "CTRL",
"_name": "Service Rate",
"description": "Service rate per server",
"relevant": True,
"_dims": "S*T^-1",
"_units": "req/s",
"_setpoint": 400.0,
"_std_setpoint": 400.0,
"_std_min": 200.0,
"_std_max": 1000.0,
},
"c": {
"_idx": 5,
"_sym": "c",
"_alias": "c",
"_fwk": "CUSTOM",
"_cat": "CTRL",
"_name": "Servers",
"description": "Number of parallel servers",
"relevant": True,
"_dims": "S",
"_units": "requests",
"_setpoint": 1.0,
"_std_setpoint": 1.0,
"_std_min": 1.0,
"_std_max": 4.0,
"_std_mean": 2.0,
},
}
# Convert to Variable instances
variables = {
sym: Variable(**params) for sym, params in variables_dict.items()
}
print("=== Variables Defined ===")
print(f"Total variables: {len(variables)}")
print(f"Relevant for analysis: {sum(1 for v in variables.values() if v.relevant)}")
Variable Categories:
INPUT (2): λ (arrival rate), K (capacity)
OUTPUT (1): L (queue length)
CONTROL (3): W (waiting time), μ (service rate), c (servers)
Step 1.4: Create Analysis Engine#
# Create AnalysisEngine
engine = AnalysisEngine(
_idx=0,
_fwk="CUSTOM",
_schema=schema,
_name="Simple Queue Analysis",
description="Simplified M/M/c/K analysis with T and S dimensions only"
)
engine.variables = variables
print("=== Running Dimensional Analysis ===")
results = engine.run_analysis()
print(f"\nNumber of Pi Groups: {len(engine.coefficients)}")
print(f"Coefficients: {list(engine.coefficients.keys())}")
print("\n=== Dimensionless Coefficients ===")
for name, coeff in engine.coefficients.items():
print(f"{name}: {coeff.pi_expr}")
Expected Output:
=== Running Dimensional Analysis ===
Number of Pi Groups: 4
Coefficients: ['\\Pi_{0}', '\\Pi_{1}', '\\Pi_{2}', '\\Pi_{3}']
=== Dimensionless Coefficients ===
\Pi_{0}: L/K
\Pi_{1}: W*\lambda/L
\Pi_{2}: c/K
\Pi_{3}: \mu/\lambda
Step 1.5: Derive Meaningful Coefficients#
Transform the Pi groups into operationally meaningful coefficients:
# Get Pi coefficient keys
pi_keys = list(engine.coefficients.keys())
# Derive meaningful coefficients based on available Pi groups
print("=== Deriving Operational Coefficients ===")
# Derive Occupancy Coefficient: δ = Π₀ = L/K
delta_coeff = engine.derive_coefficient(
expr=f"{pi_keys[0]}",
symbol="\\delta",
name="Occupancy Coefficient",
description="δ = L/K - Queue occupancy ratio",
idx=-1
)
# Derive Stall Coefficient: σ = Π₁ = W·λ/L
sigma_coeff = engine.derive_coefficient(
expr=f"{pi_keys[1]}",
symbol="\\sigma",
name="Stall Coefficient",
description="σ = W·λ/L - Service stall/blocking indicator",
idx=-1
)
# Derive Efective-Utility Coefficient: η = Π₂⁻¹·Π₃⁻¹ = K·λ/(c·μ)
eta_coeff = engine.derive_coefficient(
expr=f"{pi_keys[2]}**(-1) * {pi_keys[3]}**(-1)",
symbol="\\eta",
name="Efective-Utility Coefficient",
description="η = K·λ/(c·μ) - Resource utilization effectiveness",
idx=-1
)
# Calculate numerical values using stored setpoints
delta_val = delta_coeff.calculate_setpoint()
sigma_val = sigma_coeff.calculate_setpoint()
eta_val = eta_coeff.calculate_setpoint()
# Occupancy Coefficient: L/K
print(f"Occupancy: θ = L/K = {delta_val:.4f}")
# Stall Coefficient: σ = W·λ/L
print(f"Stall: σ = W·λ/L = {sigma_val:.4f}")
# Effective-Yield Coefficient: η = K·λ/(c·μ)
print(f"Effective-Yield: η = K·λ/(c·μ) = {eta_val:.4f}")
Expected Output:
=== Deriving Operational Coefficients ===
Occupancy: θ = L/K = 0.0995
Stall: σ = W·λ/L = 0.5027
Effective-Yield: η = K·λ/(c·μ) = 2.5000
Stage 2: Generate Grid-Based Data#
Unlike the Reynolds number example which uses random distributions, queueing systems require systematic data generation through simulation.
Step 2.1: Import Queue Model#
from src.queueing import Queue
import itertools
import pandas as pd
Note
The Queue class implements M/M/c/K queueing theory calculations.
You’ll need a queueing library or custom implementation for this step.
Step 2.2: Define Grid Parameters#
Create a systematic grid of configurations:
print("=== Generating Grid Data ===")
# Grid parameters
K_values = [4, 8, 16] # Capacity values
c_values = [1.0, 2.0, 4.0] # Server counts
mu_values = [200.0, 500.0, 1000.0] # Service rates
# Lambda sweep parameters
lambda_zero = 100.0 # Starting arrival rate
lambda_step = 10.0 # Increment
RHO_THLD = 0.95 # Utilization threshold
# Generate configurations (3 × 3 × 3 = 27 configurations)
cfg_lt = list(itertools.product(K_values, c_values, mu_values))
print(f"Total configurations: {len(cfg_lt)}")
Expected Output:
=== Generating Grid Data ===
Total configurations: 27
Step 2.3: Generate Queue Data#
For each configuration, sweep arrival rate until utilization threshold:
# Create DataFrame
cols = list(variables.keys())
data_df = pd.DataFrame(columns=cols)
# Generate data
for idx, (K, c, mu) in enumerate(cfg_lt, 1):
print(f"\t-- Config {idx}/{len(cfg_lt)}: K={K}, c={c}, μ={mu} --")
lambda_t = lambda_zero
rho_t = 0.0
while rho_t < RHO_THLD:
# Calculate queue metrics for M/M/c/K model
q = Queue("M/M/s/K", lambda_t, mu, int(c), K)
q.calculate_metrics()
# Order must match cols = ["\\lambda", "K", "L", "W", "\\mu", "c"]
data_t = [lambda_t, K, q.avg_len, q.avg_wait, mu, c]
data_df.loc[len(data_df)] = data_t
rho_t = q.rho
lambda_t += lambda_step
print(f"Generated {len(data_df)} data points")
# Add data to variables
data = data_df.to_dict(orient="list")
for sym, var in variables.items():
if sym in data:
var.data = data[sym]
engine.variables = variables
print("Data injected into PyDASA engine!")
Expected Output:
=== Generating Grid Data ===
Total configurations: 27
-- Config 1/27: K=4, c=1.0, μ=200.0 --
-- Config 2/27: K=4, c=1.0, μ=500.0 --
...
-- Config 27/27: K=16, c=4.0, μ=1000.0 --
Generated 3150 data points
Data injected into PyDASA engine!
Critical Note:
The order in data_t must exactly match the column order in cols.
Misalignment will cause incorrect analysis results.
Step 2.4: Inspect Generated Data#
# Display statistical summary
print(data_df.describe())
Stage 3: Monte Carlo Simulation with Grid Data#
Run Monte Carlo simulation using the systematically generated data.
Step 3.1: Create Simulation Handler#
from pydasa.workflows.practical import MonteCarloSimulation
print("=== Running Monte Carlo Simulation ===")
mc_grid = MonteCarloSimulation(
_idx=0,
_fwk="CUSTOM",
_schema=schema,
_name="Simple Grid Queue Analysis",
_cat="DATA",
_experiments=len(data_df),
_variables=engine.variables,
_coefficients=engine.coefficients
)
mc_grid.run_simulation(iters=len(data_df))
print(f"Simulation complete: {mc_grid.experiments} experiments")
print(f"Results for: {list(mc_grid.results.keys())}")
Expected Output:
=== Running Monte Carlo Simulation ===
Simulation complete: 3150 experiments
Results for: ['\\Pi_{0}', '\\Pi_{1}', '\\Pi_{2}', '\\Pi_{3}', '\\delta', '\\sigma', '\\eta']
Step 3.2: Extract Simulation Results#
# Extract simulation data
all_keys = list(mc_grid.simulations.keys())
derived_keys = [k for k in all_keys if not k.startswith('\\Pi_')]
print("=== Extracting Simulation Results ===")
pi_data = {}
for pi_key in derived_keys:
print(f"Processing simulation: {pi_key}")
pi_sim_obj = mc_grid.get_simulation(pi_key)
pi_results = pi_sim_obj.extract_results()
for sym, var in pi_sim_obj.variables.items():
pi_data[sym] = var.data
pi_data[pi_key] = pi_results[pi_key]
# Get data arrays
print("\n=== Simulation Results Details ===")
if len(derived_keys) > 0:
theta_sim = pi_data[derived_keys[0]]
sigma_sim = pi_data[derived_keys[1]]
eta_sim = pi_data[derived_keys[2]]
lambda_data = np.array(pi_data["\\lambda"])
mu_data = np.array(pi_data["\\mu"])
c_data = np.array(pi_data["c"])
K_data = np.array(pi_data["K"])
print(f"Occupancy (θ): Mean = {np.mean(theta_sim):.4e}, Range = [{np.min(theta_sim):.4e}, {np.max(theta_sim):.4e}]")
print(f"Stall (σ): Mean = {np.mean(sigma_sim):.4e}, Range = [{np.min(sigma_sim):.4e}, {np.max(sigma_sim):.4e}]")
print(f"Effective-Yield (η): Mean = {np.mean(eta_sim):.4e}, Range = [{np.min(eta_sim):.4e}, {np.max(eta_sim):.4e}]")
Expected Output:
=== Extracting Simulation Results ===
Processing simulation: \theta
Processing simulation: \sigma
Processing simulation: \eta
=== Simulation Results Details ===
Occupancy (θ): Mean = 4.2156e-01, Range = [9.9463e-02, 9.5238e-01]
Stall (σ): Mean = 1.4532e+00, Range = [5.0271e-01, 7.1250e+00]
Effective-Yield (η): Mean = 7.5463e-01, Range = [6.2500e-02, 2.5000e+00]
Stage 4: Advanced Visualization#
Create comprehensive visualizations to understand dimensionless behavior across multiple configurations.
Step 4.1: 3D Yoly Diagram with 2D Projections#
Create a 2×2 grid showing 3D space and three 2D projections:
# Create comprehensive Yoly diagram with 3D and 2D projections in a 2x2 grid
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure(figsize=(20, 16), facecolor="white")
# Define grid specification for 2x2 layout
gs = fig.add_gridspec(2, 2, hspace=0.3, wspace=0.3)
# Create axes: [0,0] is 3D, others are 2D
axes = [
[fig.add_subplot(gs[0, 0], projection="3d"), fig.add_subplot(gs[0, 1])],
[fig.add_subplot(gs[1, 0]), fig.add_subplot(gs[1, 1])]
]
# Auxiliary lists for plot configuration
plot_types = ["3D", "2D", "2D", "2D"]
plot_titles = [
r"3D Space: $\boldsymbol{\theta}$ vs $\boldsymbol{\sigma}$ vs $\boldsymbol{\eta}$",
r"2D Plane: $\boldsymbol{\theta}$ vs $\boldsymbol{\sigma}$",
r"2D Plane: $\boldsymbol{\theta}$ vs $\boldsymbol{\eta}$",
r"2D Plane: $\boldsymbol{\sigma}$ vs $\boldsymbol{\eta}$"
]
x_labels = [
r"Occupancy ($\boldsymbol{\theta}$)",
r"Occupancy ($\boldsymbol{\theta}$)",
r"Occupancy ($\boldsymbol{\theta}$)",
r"Stall ($\boldsymbol{\sigma}$)"
]
y_labels = [
r"Stall ($\boldsymbol{\sigma}$)",
r"Stall ($\boldsymbol{\sigma}$)",
r"Effective-Yield ($\boldsymbol{\eta}$)",
r"Effective-Yield ($\boldsymbol{\eta}$)"
]
z_labels = [r"Effective-Yield ($\boldsymbol{\eta}$)", None, None, None]
# Data pairs for each subplot
data_pairs = [
(theta_sim, sigma_sim, eta_sim), # 3D plot
(theta_sim, sigma_sim, None), # theta vs sigma
(theta_sim, eta_sim, None), # theta vs eta
(sigma_sim, eta_sim, None) # sigma vs eta
]
Step 4.2: Configure Color and Marker Mapping#
Use color for server count and markers for service rate:
# Color map for server count (c) and marker map for service rates (μ)
c_data = np.array(variables["c"].data)
mu_data = np.array(variables["\\mu"].data)
K_data = np.array(variables["K"].data)
unique_c = np.unique(c_data)
unique_mu = np.unique(mu_data)
unique_K = np.unique(K_data)
# Colors for servers: red (1), orange (2), green (4)
color_map = {1.0: "red", 2.0: "orange", 4.0: "green"}
# Markers for service rate: triangle (slow), square (mid), circle (fast)
marker_map = {200: "^", 500: "s", 1000: "o"}
Step 4.3: Plot Data with Masks#
# Iterate over 2x2 grid
plot_idx = 0
for row in range(2):
for col in range(2):
ax = axes[row][col]
plot_type = plot_types[plot_idx]
x_data, y_data, z_data = data_pairs[plot_idx]
# Set white background for subplot
ax.set_facecolor("white")
# Track which (c, μ) combinations have been labeled for legend
labeled_combinations = set()
# Plot data points grouped by server count (c), service rate (μ), and capacity (K)
for c_val in unique_c:
for mu_val in unique_mu:
for K_val in unique_K:
# Create mask for this specific combination
mask = (np.abs(c_data - c_val) < 0.1) & (np.abs(mu_data - mu_val) < 0.1) & (np.abs(K_data - K_val) < 0.1)
if not np.any(mask):
continue
# Create label only once per (c, μ) combination
combo_key = (c_val, mu_val)
if combo_key not in labeled_combinations:
label = f"c={int(c_val)}, μ={int(mu_val)}"
labeled_combinations.add(combo_key)
else:
label = None # No label for subsequent K values
color = color_map.get(c_val, "gray")
marker = marker_map.get(mu_val, "o")
if plot_type == "3D":
# 3D scatter plot
ax.scatter(x_data[mask], y_data[mask], z_data[mask],
c=color, marker=marker, s=30, alpha=0.6,
edgecolors="grey", linewidths=0.1,
label=label)
# Add K value label at median position
mask_indices = np.where(mask)[0]
if len(mask_indices) > 0:
mid_idx = mask_indices[len(mask_indices)//2]
ax.text(x_data[mid_idx], y_data[mid_idx], z_data[mid_idx],
f"K={int(K_val)}", fontsize=8, color="black",
fontweight="bold", alpha=0.8)
else:
# 2D scatter plot
ax.scatter(x_data[mask], y_data[mask],
c=color, marker=marker, s=30, alpha=0.6,
edgecolors="grey", linewidths=0.1,
label=label)
# Apply plot-specific styling
if plot_type == "3D":
ax.set_zlabel(z_labels[plot_idx], fontsize=12, fontweight="bold", color="black")
ax.view_init(elev=30, azim=110)
ax.grid(True, color="dimgray", linewidth=1, linestyle="--", alpha=0.9)
else:
ax.grid(True, alpha=0.8, color="dimgray", linewidth=1.0, linestyle="--")
# Set labels and title
ax.set_xlabel(x_labels[plot_idx], fontsize=12, fontweight="bold", color="black")
ax.set_ylabel(y_labels[plot_idx], fontsize=12, fontweight="bold", color="black")
ax.set_title(plot_titles[plot_idx], fontsize=14, fontweight="bold", pad=10, color="black")
ax.legend(loc="best", fontsize=10, framealpha=0.9)
plot_idx += 1
# Add main title
fig.suptitle("Comprehensive Yoly Diagram: M/M/c/K Queue Analysis\n",
fontsize=16, fontweight="bold", y=0.98, color="black")
plt.show()
Visualization Shows:
3D Space: Complete relationship between all three coefficients
2D Projections: Detailed views of coefficient pairs
Color Coding: Server count (red=1, orange=2, green=4)
Markers: Service rate (triangle=slow, square=mid, circle=fast)
Labels: Capacity (K) values annotated on clusters
Tips and Best Practices#
Custom Framework Design
When creating custom frameworks, choose FDUs that:
Are truly fundamental to your domain
Cannot be expressed in terms of each other
Capture the essential physics/behavior
Keep the number minimal (2-4 FDUs typically sufficient)
Dimension String Format
Use proper syntax for custom dimensions:
# Good: Rate dimension [S/T]
"_dims": "S*T^-1"
# Good: Dimensionless
"_dims": "1"
# Bad: Mixing frameworks
"_dims": "M*S*T^-1" # Don't mix PHYSICAL (M) with CUSTOM (S)
Data Generation Strategies
For systematic analysis:
Grid Search: Exhaustive coverage of parameter space (as shown)
Latin Hypercube: Efficient sampling for large parameter spaces
Adaptive Sampling: Focus on regions of interest
Variable Ordering
Always ensure data alignment:
# Get column order
cols = list(variables.keys()) # ["\\lambda", "K", "L", "W", "\\mu", "c"]
# MUST match this order when adding data
data_t = [lambda_t, K, q.avg_len, q.avg_wait, mu, c]
Coefficient Derivation
Use mathematical expressions to combine Pi groups:
# Inversion
expr=f"{pi_keys[0]}**(-1)"
# Product
expr=f"{pi_keys[0]} * {pi_keys[1]}"
# Complex combination
expr=f"{pi_keys[2]}**(-1) * {pi_keys[3]}**(-1)"
Common Pitfalls#
1. Framework Mismatch
# Wrong: Using PHYSICAL framework with custom schema
engine = AnalysisEngine(_fwk="PHYSICAL", _schema=custom_schema)
# Correct: Framework must match schema
engine = AnalysisEngine(_fwk="CUSTOM", _schema=custom_schema)
2. Missing Schema Setup
# Wrong: Forgot to setup FDUs
schema = Schema(_fwk="CUSTOM", _fdu_lt=fdu_list, _idx=0)
# Missing: schema._setup_fdus()
# Correct: Always call setup
schema = Schema(_fwk="CUSTOM", _fdu_lt=fdu_list, _idx=0)
schema._setup_fdus()
3. Data Alignment Error
# Wrong order causes incorrect analysis
data_t = [lambda_t, K, mu, q.avg_wait, q.avg_len, c]
# This assigns mu to L, q.avg_wait to W, q.avg_len to mu!
# Correct order matching ["\\lambda", "K", "L", "W", "\\mu", "c"]
data_t = [lambda_t, K, q.avg_len, q.avg_wait, mu, c]
4. Forgetting Variable Data Injection
# Wrong: Data generated but not injected
data_df = pd.DataFrame(...)
mc_grid = MonteCarloSimulation(..., _variables=engine.variables)
# Variables don't have data yet!
# Correct: Inject data before simulation
data = data_df.to_dict(orient="list")
for sym, var in variables.items():
if sym in data:
var.data = data[sym]
engine.variables = variables
Summary#
Custom Framework Workflow:
Stage |
Key Actions |
|---|---|
1. Framework |
Define custom FDUs, create schema, define variables |
2. Analysis |
Run AnalysisEngine, derive meaningful coefficients |
3. Data Generation |
Generate systematic grid data from domain model |
4. Simulation |
Run Monte Carlo with grid data, extract results |
5. Visualization |
Create comprehensive multi-dimensional plots |
Key Differences from Standard Workflows:
Custom FDUs: Define your own fundamental dimensions
Domain-Specific: Tailor to non-traditional engineering domains
Grid Data: Systematic parameter sweeps vs. random distributions
Specialized Viz: Multi-configuration comparative analysis
When to Use Custom Frameworks:
Analyzing software/cyber systems (requests, packets, bits)
Economic/business problems (transactions, currency, time)
Biological systems (cells, molecules, reactions)
Any domain where PHYSICAL framework doesn’t apply naturally
Next Steps#
Now that you’ve mastered custom frameworks, explore:
Create multi-dimensional frameworks (3+ FDUs)
Integrate with domain-specific simulation tools
Compare custom vs. built-in framework results
Develop framework-specific visualization templates
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.