Metadata-Version: 2.4
Name: cmslh5
Version: 0.1.4
Summary: Read, write, and analyse COMSOL HDF5 result files (.cmslh5)
Author: S. K. Sinha
License: MIT
Project-URL: Repository, https://github.com/sksinha/cmslh5
Keywords: comsol,hdf5,fem,post-processing,cfd
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Science/Research
Classifier: Topic :: Scientific/Engineering
Classifier: Topic :: Scientific/Engineering :: Physics
Classifier: Programming Language :: Python :: 3
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: h5py>=3.0
Requires-Dist: numpy>=1.22
Requires-Dist: scipy>=1.8
Provides-Extra: convert
Requires-Dist: mph>=1.2; extra == "convert"
Provides-Extra: plot
Requires-Dist: matplotlib>=3.5; extra == "plot"
Provides-Extra: all
Requires-Dist: cmslh5[convert,plot]; extra == "all"
Provides-Extra: dev
Requires-Dist: cmslh5[all]; extra == "dev"
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: pytest-cov; extra == "dev"
Requires-Dist: ruff; extra == "dev"
Dynamic: license-file

# cmslh5

**Read, write, and analyse COMSOL Multiphysics simulation results in HDF5.**

`cmslh5` is a Python toolkit that bridges COMSOL Multiphysics and the scientific Python ecosystem. It converts COMSOL `.mph` model files into a structured, open HDF5 archive (`.cmslh5`), then provides a rich API to extract, interpolate, compare, and analyse the results — without needing a COMSOL license for post-processing.

---

## Why?

COMSOL's native `.mph` format is proprietary and requires a running COMSOL server to read. This creates friction when you want to:

- Post-process results on a laptop without a COMSOL license
- Share simulation data with collaborators who don't have COMSOL
- Run automated analysis pipelines in Python
- Load results into ParaView, matplotlib, or Jupyter notebooks
- Compare results across mesh refinements or parameter sweeps
- Archive simulation data in a long-term, vendor-neutral format

`cmslh5` solves all of these by extracting everything into HDF5 once, then providing fast, license-free access forever.

---

## Features

| Feature | Description |
|---|---|
| **Convert** | Extract mesh, elements, physical groups, and all physics fields from `.mph` to `.cmslh5` |
| **Parallel extraction** | Split node range across multiple COMSOL worker processes for large models |
| **Read** | Query nodes, elements, domains, studies, timesteps, and field data |
| **Interpolate** | Temporal interpolation between stored timesteps; spatial IDW interpolation to arbitrary points |
| **Line extraction** | Sample any field along parametric curves defined by equations in x, y, z |
| **Point probes** | Evaluate any component at any spatial point and time |
| **Time histories** | Extract full time series at monitoring points |
| **Compare** | Diff two `.cmslh5` files field-by-field for regression testing |
| **Statistics** | Time-average, RMS, min/max, FFT at points |
| **Export** | Write to CSV, VTK (for ParaView via meshio) |
| **ParaView plugin** | Native ParaView reader with animation bar, study selection, domain colouring |

---

## Installation

```bash
# Core (reader, compare, stats, export) — no COMSOL needed
pip install cmslh5

# With COMSOL conversion support (requires mph + COMSOL license)
pip install cmslh5[convert]

# With plotting
pip install cmslh5[plot]

# Everything
pip install cmslh5[all]

# Development
git clone https://github.com/smartgeomechanics/cmslh5.git
cd cmslh5
pip install -e ".[dev]"
```

### Requirements

- **Python** ≥ 3.9
- **Core**: `h5py`, `numpy`, `scipy`
- **Conversion**: `mph` (+ COMSOL Multiphysics installation)
- **Plotting**: `matplotlib`
- **VTK export**: `meshio`

---

## Quick start

### 1. Convert a COMSOL model to HDF5

On your COMSOL server:

```bash
# Mesh, solve, and extract
cmslh5-convert model.mph --cores 8

# Extract from an already-solved model
cmslh5-convert Results_Model.mph --extract-results

# Extract only specific fields
cmslh5-convert Results_Model.mph --extract-results --fields Temperature Pressure Velocity
```

This produces a `.cmslh5` file that you can copy anywhere.

### 2. Read and analyse (no COMSOL needed)

```python
from cmslh5 import CmslH5Reader

reader = CmslH5Reader('Results_Model.cmslh5')
print(reader.summary())
```

Output:
```
File    : Results_Model.cmslh5
Size    : 847.3 MB

Mesh:
  Nodes       : 1,245,678
  BBox min    : [0, -0.025, 0]
  BBox max    : [0.5, 0.025, 0.025]
  Elements    : 7,123,456  (tet)
  Domains     : 1
    1 : Fluid

Studies: 1
  [0] "Time Dependent"  (time-dep, T=201)
       t : [0 .. 2] s
       Velocity          [vx, vy, vz]  unit=m/s  shape=(1245678, 3, 201)
       Pressure          [p]  unit=Pa  shape=(1245678, 1, 201)
       Temperature       [T]  unit=K  shape=(1245678, 1, 201)

Available components:
  Pressure        : p
  Temperature     : T
  Velocity        : vx, vy, vz
```

### 3. Extract data along a line

```python
# Temperature along the pipe centreline at t=1.0s
result = reader.extract_along_line(
    component='T',
    time=1.0,
    x_expr='s * 0.5',    # x sweeps from 0 to 0.5
    y_expr='0.0',         # constant y
    z_expr='0.0',         # constant z
    n_points=500,
)

z = result['coords'][:, 0]    # x-coordinates
T = result['values']           # temperature values
arc = result['arc_length']     # cumulative arc length
```

The `s` parameter goes from 0 to 1. Use any math expressions:

```python
# Circular path
x_expr='0.01 * cos(2*pi*s)'
y_expr='0.01 * sin(2*pi*s)'
z_expr='0.25'

# Non-uniform spacing
z_expr='0.5 * s**2'
```

### 4. Multiple components on the same line

```python
result = reader.extract_components_along_line(
    components=['vx', 'vy', 'vz', 'p', 'T'],
    time=1.0,
    x_expr='s * 0.5', y_expr='0.0', z_expr='0.0',
)

x   = result['coords'][:, 0]
vx  = result['data']['vx']['values']
p   = result['data']['p']['values']
T   = result['data']['T']['values']
```

### 5. Probe a single point

```python
p = reader.probe_point('p', time=0.5, point=[0.25, 0.0, 0.0])
print(f'Pressure: {p["value"]:.2f} {p["unit"]}')
print(f'Nearest mesh node: {p["nearest_distance"]:.2e} m away')
```

### 6. Time history at a monitoring point

```python
th = reader.extract_time_history('vx', point=[0.25, 0.0, 0.0])

import matplotlib.pyplot as plt
plt.plot(th['times'], th['values'])
plt.xlabel('Time [s]')
plt.ylabel(f'vx [{th["unit"]}]')
plt.show()
```

### 7. Statistical analysis

```python
from cmslh5.stats import time_average, fft_at_point, field_statistics

# Time-averaged temperature field
avg_T, unit = time_average(reader, 'T')

# FFT of pressure at a monitoring point
fft = fft_at_point(reader, 'p', point=[0.25, 0.0, 0.0])
print(f'Dominant frequency: {fft["dominant_freq"]:.2f} Hz')

# Spatial statistics at a given time
stats = field_statistics(reader, 'T', time=1.0)
print(f'T range: [{stats["min"]:.1f}, {stats["max"]:.1f}] {stats["unit"]}')
```

### 8. Compare two simulation results

```python
from cmslh5 import compare_files

diff = compare_files('coarse_mesh.cmslh5', 'fine_mesh.cmslh5')
print(diff['summary'])
```

Output:
```
Comparing: coarse_mesh.cmslh5  vs  fine_mesh.cmslh5
Time: 2.0 s
Mesh match: False  (A=312,456 nodes, B=1,245,678 nodes)

  T         PASS  max_abs=0.1234  max_rel=0.0004  rms=nan
  p         PASS  max_abs=12.34   max_rel=0.0012  rms=nan
  vx        FAIL  max_abs=0.0567  max_rel=0.0234  rms=nan

Overall: DIFFERENCES FOUND
```

### 9. Export to other formats

```python
from cmslh5.export import to_csv, to_vtk, line_to_csv

# Nodal fields to CSV
to_csv(reader, 'output.csv', components=['T', 'p'], time=1.0)

# Line extraction to CSV
result = reader.extract_along_line(component='T', time=1.0,
    x_expr='s*0.5', y_expr='0', z_expr='0')
line_to_csv('centreline_T.csv', result)

# VTK for ParaView (requires meshio)
to_vtk(reader, 'output.vtk', time=1.0)
```

### 10. ParaView plugin

```
1. pvpython -m pip install h5py
2. ParaView → Tools → Manage Plugins → Load New → select cmslh5/paraview/plugin.py
3. Tick "Auto Load"
4. File → Open → select .cmslh5 file
```

Features: study selection dropdown, animation bar with all timesteps, domain colouring via DomainId cell data.

---

## HDF5 file structure

```
/Model/
    Nodes/
        Id              int32   [N_nodes]
        Coordinates     float32 [N_nodes, 3]
    Elements/
        Id, Type, NumNodes, Connectivity
        DomainId, DomainName, DomainLegend/
    Physical_Groups/
        {dim}D_{label}/
            Id, Type, NumNodes, Connectivity

/Analysis/
    {idx}_{StudyLabel}/
        Times           float64 [T]
        Velocity        float32 [N, 3, T]
        Pressure        float32 [N, 1, T]
        Temperature     float32 [N, 1, T]
        Displacement    float32 [N, 3, T]
        Stress          float32 [N, 6, T]
        Strain          float32 [N, 6, T]
```

You can inspect any `.cmslh5` file with standard HDF5 tools:

```bash
h5dump -H Results_Model.cmslh5    # structure
h5ls -r Results_Model.cmslh5      # recursive listing
```

Or in Python:

```python
import h5py
with h5py.File('Results_Model.cmslh5', 'r') as f:
    f.visititems(lambda name, obj: print(name, obj.shape if hasattr(obj, 'shape') else ''))
```

---

## Available components

| Component | Field | Unit |
|---|---|---|
| `T` | Temperature | K |
| `p` | Pressure | Pa |
| `vx`, `vy`, `vz` | Velocity | m/s |
| `ux`, `uy`, `uz` | Displacement | m |
| `sx`, `sy`, `sz`, `sxy`, `sxz`, `syz` | Stress | Pa |
| `exx`, `eyy`, `ezz`, `exy`, `exz`, `eyz` | Strain | 1 |

Aliases: `'temperature'` → `T`, `'pressure'` → `p`, `'temp'` → `T`.

---

## Converter CLI reference

```
cmslh5-convert MODEL_FILE [OPTIONS]

positional:
  MODEL_FILE                   Path to COMSOL .mph file

options:
  -o, --output OUTPUT_FILE     Output .cmslh5 path
  --extract-results            Skip meshing/solving; extract from solved model
  --cores N                    CPU cores for COMSOL solver
  --studies TAG [TAG ...]      Restrict to specific study tags
  --fields FIELD [FIELD ...]   Fields to extract (e.g. Temperature Pressure)
  --chunk-size N               Nodes per extraction chunk (default: 200000)
  --workers N                  Parallel worker processes (default: 1)
  --comp COMP_TAG              Component tag (default: comp1)
  --no-start-server            Connect to external COMSOL server
  --host HOST                  COMSOL server host (default: localhost)
  --port PORT                  COMSOL server port (default: 2036)
```

### Performance guidelines

| Nodes | Workers | Chunk size | JVM heap | Approx. speedup |
|---|---|---|---|---|
| < 200k | 1 | 200,000 | 16 GB | baseline |
| 200k–1M | 1–2 | 200,000 | 32 GB | 1–1.5× |
| 1M–5M | 2–4 | 250,000–500,000 | 24 GB per worker | 2–3× |
| > 5M | 4–8 | 500,000 | 16–24 GB per worker | 3–6× |

The `--workers` flag splits the node range across independent COMSOL processes. Each worker needs its own JVM heap and COMSOL license token. The master process handles mesh/element writing and merges the worker outputs.

```bash
# Typical large-model invocation
export _JAVA_OPTIONS='-Xmx24G'
cmslh5-convert Results_LargeModel.mph \
    --extract-results \
    --workers 4 \
    --chunk-size 500000 \
    --fields Velocity Pressure Temperature
```

---

## Package structure

```
cmslh5/
├── __init__.py         # public API
├── reader.py           # CmslH5Reader class
├── convert.py          # .mph → .cmslh5 (requires mph); includes parallel worker mode
├── compare.py          # diff two files
├── stats.py            # time_average, fft_at_point, etc.
├── export.py           # to_csv, to_vtk
├── _fields.py          # field configs, component map (single source of truth)
├── _interp.py          # IDW + temporal interpolation
├── _expr.py            # parametric expression evaluator
├── cli/
│   └── convert_cli.py  # cmslh5-convert entry point
└── paraview/
    └── plugin.py       # ParaView Python plugin
```

---

## Contributing

Contributions are welcome. Please open an issue first to discuss what you'd like to change.

```bash
git clone https://github.com/smartgeomechanics/cmslh5.git
cd cmslh5
pip install -e ".[dev]"
pytest
```

---

## License

[MIT](LICENSE)

---

## Citation

If you use `cmslh5` in your research, please cite:

```bibtex
@software{cmslh5,
  author = {Sinha, S. K.},
  title = {cmslh5: Read, write, and analyse COMSOL simulation results in HDF5},
  url = {https://github.com/smartgeomechanics/cmslh5},
  year = {2025},
}
```
