Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions demo/_showcase/ansys-qoi/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,57 @@ python main.py

**Outputs:**
- `outputs/predictions_YYYYMMDD_HHMMSS.csv`


### GPU Acceleration

By default, this workflow runs on CPU, which is sufficient for the included sample dataset (5 samples). For larger datasets or production workloads, GPU acceleration is recommended for the `qoi_train` and `qoi_inference` components.

#### Prerequisites

- NVIDIA GPU with CUDA support
- NVIDIA Container Toolkit installed on the host system

#### Configuration Steps

**1. Update Base Image**

Modify the `tesseract_config.yaml` file in both `qoi_train` and `qoi_inference` components to use a CUDA-enabled base image:

```yaml
build_config:
base_image: "nvidia/cuda:12.8.1-runtime-ubuntu24.04"
# ... rest of configuration
```

**2. Configure PyTorch with CUDA Support**

Update the `scripts/pyproject.toml` file in both components to install GPU-enabled PyTorch packages:

```toml
dependencies = [
"torch",
"torchvision",
# ... other dependencies
]

[[tool.uv.index]]
name = "pytorch-cu128"
url = "https://download.pytorch.org/whl/cu128"


[tool.uv.sources]
torch = { index = "pytorch-cu128" }
torchvision = { index = "pytorch-cu128" }
```

**3. Enable GPU Access During Execution**

When running the workflow, use the `--gpus` flag to grant GPU access to the containers:

```bash
tesseract run --gpus all qoi_train
tesseract run --gpus all qoi_inference
```

For more details on GPU configuration options, see the [Tesseract CLI documentation](https://docs.pasteurlabs.ai/projects/tesseract-core/latest/content/api/tesseract-cli.html#cmdoption-tesseract-run-gpus).
22 changes: 11 additions & 11 deletions demo/_showcase/ansys-qoi/qoi_dataset/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,24 @@

if __name__ == "__main__":
# Ensure output folder exists
local_outputs = Path(__file__).parent.resolve() / "outputs"
local_outputs.mkdir(parents=True, exist_ok=True)

here = Path("/tesseract/")
CONFIG = here / "inputs/config.yaml"
SIM_FOLDER = here / "inputs/Ansys_Runs"
here = Path(__file__).parent.resolve()
OUTPUT_DIR = here / "outputs"
DATASET_FOLDER = OUTPUT_DIR / "dataset"
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)

CONFIG = "config.yaml"
SIM_FOLDER = "Ansys_Runs"
DATASET_FOLDER = "dataset"

inputs = {
"config": str(CONFIG),
"sim_folder": str(SIM_FOLDER),
"dataset_folder": str(DATASET_FOLDER),
"config": CONFIG,
"sim_folder": SIM_FOLDER,
"dataset_folder": DATASET_FOLDER,
}

qoi_dataset = Tesseract.from_image(
"qoi_dataset",
volumes=["./inputs:/tesseract/inputs:ro", "./outputs:/tesseract/outputs:rw"],
input_path="./inputs",
output_path="./outputs",
)

with qoi_dataset:
Expand Down
21 changes: 6 additions & 15 deletions demo/_showcase/ansys-qoi/qoi_dataset/scripts/process/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,23 +90,14 @@ def sample_points_with_spheres(
) -> tuple[np.ndarray, np.ndarray | None]:
"""Sample points on a mesh, allocating more samples inside given spheres.

Parameters
----------
mesh : TriangleMesh
Full mesh to sample from.
n_points : int
Total number of points to return.
method : {"poisson", "uniform"}
Sampling method.
spheres : iterable of (center, radius)
Each center is a (3,) np.ndarray; radius is a float.
sphere_fraction : float in (0, 1]
Fraction of total samples to dedicate to all spheres combined.
The remainder is sampled over the full mesh.
Args:
mesh: Input triangle mesh
n_points:Number of points to sample
method: Sampling method, either 'poisson' or 'uniform'
spheres : Iterable of (center, radius), each center is a (3,) np.ndarray; radius is a float.
sphere_fraction : Fraction of total samples to dedicate to all spheres combined

Returns:
-------
tuple[np.ndarray, np.ndarray | None]
Tuple of (points, normals) where points is (N, 3) and normals is (N, 3) or None
"""
spheres = list(spheres)
Expand Down
5 changes: 3 additions & 2 deletions demo/_showcase/ansys-qoi/qoi_dataset/scripts/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ version = "0.1.0"
description = "Add your description here"
requires-python = ">=3.10, <3.14"
dependencies = [
"open3d>=0.18.0,<0.19.0",
"torch>=2.9.1",
"open3d",
"torch",
"pyyaml",
]

[tool.hatch.build.targets.wheel]
Expand Down
17 changes: 12 additions & 5 deletions demo/_showcase/ansys-qoi/qoi_dataset/tesseract_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@
from pydantic import BaseModel, Field
from torch.utils._pytree import tree_map

from tesseract_core.runtime.config import get_config
from tesseract_core.runtime.experimental import InputFileReference, OutputFileReference


class InputSchema(BaseModel):
"""Input schema for QoI dataset generation."""

config: str = Field(description="Configuration file")
config: InputFileReference = Field(description="Configuration file")

sim_folder: str = Field(
description="Folder path containing Ansys Fluent simulations with CAD files and QoI reports",
Expand All @@ -27,7 +30,7 @@ class InputSchema(BaseModel):
class OutputSchema(BaseModel):
"""Output schema for QoI dataset generation."""

data: list[str | Path] = Field(
data: list[OutputFileReference] = Field(
description="List of npz files containing point cloud data, simulation parameters and QoI (if available)",
)

Expand All @@ -36,10 +39,14 @@ def evaluate(inputs: Any) -> Any:
"""Process simulation data and generate NPZ dataset files."""
from process.npz import NPZProcessor

config = get_config()
input_base = Path(config.input_path)
output_base = Path(config.output_path)

processor = NPZProcessor(
root=Path(inputs["sim_folder"]),
out_dir=Path(inputs["dataset_folder"]),
config_path=Path(inputs["config"]),
root=input_base / inputs["sim_folder"],
out_dir=output_base / inputs["dataset_folder"],
config_path=input_base / inputs["config"],
)
processed_files = processor.build()

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1 @@
#torch
open3d
pyyaml
./scripts
Binary file modified demo/_showcase/ansys-qoi/qoi_inference/inputs/model.pkl
Binary file not shown.
Binary file modified demo/_showcase/ansys-qoi/qoi_inference/inputs/scaler.pkl
Binary file not shown.
25 changes: 12 additions & 13 deletions demo/_showcase/ansys-qoi/qoi_inference/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,24 @@

if __name__ == "__main__":
# Ensure output folder exists
local_outputs = Path(__file__).parent.resolve() / "outputs"
local_outputs.mkdir(parents=True, exist_ok=True)
here = Path(__file__).parent.resolve()
OUTPUT_DIR = here / "outputs"
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)

here = Path("/tesseract/")
CONFIG = here / "inputs/config.yaml"
DATASET_FOLDER = here / "inputs/dataset_inference"
TRAINED_MODEL = here / "inputs/model.pkl"
SCALER = here / "inputs/scaler.pkl"
CONFIG = "config.yaml"
DATASET_FOLDER = "dataset_inference"
TRAINED_MODEL = "model.pkl"
SCALER = "scaler.pkl"

inputs = {
"config": str(CONFIG),
"data_folder": str(DATASET_FOLDER),
"trained_model": str(TRAINED_MODEL),
"scaler": str(SCALER),
"config": CONFIG,
"data_folder": DATASET_FOLDER,
"trained_model": TRAINED_MODEL,
"scaler": SCALER,
}

qoi_inference = Tesseract.from_image(
"qoi_inference",
volumes=["./inputs:/tesseract/inputs:ro", "./outputs:/tesseract/outputs:rw"],
"qoi_inference", input_path="./inputs", output_path="./outputs"
)

with qoi_inference:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ version = "0.1.0"
description = "Add your description here"
requires-python = ">=3.10, <3.14"
dependencies = [
"open3d>=0.18.0,<0.19.0",
"torch>=2.9.1",
"open3d",
"torch",
"torchvision",
"pyyaml",
"scikit-learn",
]
Expand Down
26 changes: 14 additions & 12 deletions demo/_showcase/ansys-qoi/qoi_inference/tesseract_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,22 @@
from torch.utils._pytree import tree_map

from tesseract_core.runtime import Array, Float32
from tesseract_core.runtime.config import get_config
from tesseract_core.runtime.experimental import InputFileReference


class InputSchema(BaseModel):
"""Input schema for QoI model inference."""

config: str = Field(description="Configuration file")
config: InputFileReference = Field(description="Configuration file")

data_folder: str = Field(
description="Folder containing npz files with point cloud data and simulation parameters"
)
trained_model: str = Field(
trained_model: InputFileReference = Field(
description="Pickle file containing weights of trained model"
)
scaler: str = Field(
scaler: InputFileReference = Field(
description="Pickle file containing the scaling method for the dataset"
)

Expand All @@ -47,8 +49,12 @@ def evaluate(inputs: Any) -> Any:
from process.models import HybridPointCloudTreeModel
from process.scaler import ScalingPipeline

config_path = Path(inputs["config"])
data_folder_path = Path(inputs["data_folder"])
config = get_config()
input_base = Path(config.input_path)
output_base = Path(config.output_path)

config_path = input_base / inputs["config"]
data_folder_path = input_base / inputs["data_folder"]
files = [str(p.resolve()) for p in data_folder_path.glob("*.npz")]

data_files = [Path(f) for f in files]
Expand All @@ -58,12 +64,8 @@ def evaluate(inputs: Any) -> Any:
with open(inputs["config"]) as f:
config = yaml.safe_load(f)

# Create output directory
output_dir = Path("/tesseract/outputs")
output_dir.mkdir(parents=True, exist_ok=True)

# Load the scaling pipeline from saved pickle file
scaling_pipeline = ScalingPipeline.load(Path(inputs["scaler"]))
scaling_pipeline = ScalingPipeline.load(input_base / inputs["scaler"])

# Get all inference samples from the dataset
inference_samples = [raw_dataset[i] for i in range(len(raw_dataset))]
Expand All @@ -87,7 +89,7 @@ def evaluate(inputs: Any) -> Any:
# Load the trained model
print("Loading trained model...")
model = HybridPointCloudTreeModel()
model.load(inputs["trained_model"])
model.load(input_base / inputs["trained_model"])

# Make predictions
print("Making predictions...")
Expand All @@ -108,7 +110,7 @@ def evaluate(inputs: Any) -> Any:
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")

# Save as CSV
csv_path = output_dir / f"predictions_{timestamp}.csv"
csv_path = output_base / f"predictions_{timestamp}.csv"
predictions_array = qoi_predictions.numpy()

# Determine number of QoI outputs
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
pyyaml
./scripts
15 changes: 7 additions & 8 deletions demo/_showcase/ansys-qoi/qoi_train/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,16 @@

if __name__ == "__main__":
# Ensure output folder exists
local_outputs = Path(__file__).parent.resolve() / "outputs"
local_outputs.mkdir(parents=True, exist_ok=True)
here = Path(__file__).parent.resolve()
OUTPUT_DIR = here / "outputs"
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)

here = Path("/tesseract/")
CONFIG = here / "inputs/config.yaml"
DATASET_FOLDER = here / "inputs/dataset_reduced"
inputs = {"config": str(CONFIG), "data_folder": str(DATASET_FOLDER)}
CONFIG = "config.yaml"
DATASET_FOLDER = "dataset_reduced"
inputs = {"config": CONFIG, "data_folder": DATASET_FOLDER}

qoi_train = Tesseract.from_image(
"qoi_train",
volumes=["./inputs:/tesseract/inputs:ro", "./outputs:/tesseract/outputs:rw"],
"qoi_train", input_path="./inputs", output_path="./outputs"
)

with qoi_train:
Expand Down
5 changes: 3 additions & 2 deletions demo/_showcase/ansys-qoi/qoi_train/scripts/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ version = "0.1.0"
description = "Add your description here"
requires-python = ">=3.10, <3.14"
dependencies = [
"open3d>=0.18.0,<0.19.0",
"torch>=2.9.1",
"open3d",
"torch",
"torchvision",
"pyyaml",
"scikit-learn",
]
Expand Down
17 changes: 10 additions & 7 deletions demo/_showcase/ansys-qoi/qoi_train/tesseract_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@
from pydantic import BaseModel, Field
from torch.utils._pytree import tree_map

from tesseract_core.runtime.experimental import OutputFileReference
from tesseract_core.runtime.config import get_config
from tesseract_core.runtime.experimental import InputFileReference, OutputFileReference


class InputSchema(BaseModel):
"""Input schema for QoI model training."""

config: str = Field(description="Configuration file")
config: InputFileReference = Field(description="Configuration file")

data_folder: str = Field(
description="Folder containing npz files containing point cloud data information, "
Expand All @@ -44,8 +45,12 @@ def evaluate(inputs: Any) -> Any:
from process.train import train_hybrid_models

# Convert all inputs to Path objects (handles strings, InputFileReference, and Path)
config_path = Path(inputs["config"])
data_folder_path = Path(inputs["data_folder"])
config = get_config()
input_base = Path(config.input_path)
output_base = Path(config.output_path)

config_path = input_base / inputs["config"]
data_folder_path = input_base / inputs["data_folder"]
files = [str(p.resolve()) for p in data_folder_path.glob("*.npz")]

data_files = [Path(f) for f in files]
Expand All @@ -64,8 +69,6 @@ def evaluate(inputs: Any) -> Any:
)

# Create output directory
output_dir = Path("/tesseract/outputs")
output_dir.mkdir(parents=True, exist_ok=True)

# Create scaling pipeline from config
scaling_pipeline = ScalingPipeline(config_path)
Expand All @@ -79,7 +82,7 @@ def evaluate(inputs: Any) -> Any:
scaled_train, scaled_val, scaled_test
)

model_folder = output_dir / "models"
model_folder = output_base / "models"

hybrid_model_configs = config.get("hybrid_models", None)
hybrid_training_config = config.get("hybrid_training", {})
Expand Down
Loading
Loading