from pathlib import Path
import numpy as np
out_dir = Path("paraview_output")
out_dir.mkdir(exist_ok=True)
def vtk_values(values):
values = np.asarray(values)
if values.ndim == 1:
return " ".join(map(str, values.tolist()))
return " ".join(map(str, values.reshape(-1).tolist()))
def data_array(name, values, vtk_type="Float64", components=None):
component_attr = f' NumberOfComponents="{components}"' if components else ""
return (
f' <DataArray type="{vtk_type}" Name="{name}"{component_attr} format="ascii">\n'
f' {vtk_values(values)}\n'
f' </DataArray>'
)
def write_vtu(filename, points, triangles, temperature, heat_flux, material_id):
n_cells = len(triangles)
connectivity = triangles.reshape(-1)
offsets = 3 * np.arange(1, n_cells + 1, dtype=np.int64)
cell_types = np.full(n_cells, 5, dtype=np.uint8) # VTK_TRIANGLE
lines = [
'<?xml version="1.0"?>',
'<VTKFile type="UnstructuredGrid" version="0.1" byte_order="LittleEndian">',
' <UnstructuredGrid>',
f' <Piece NumberOfPoints="{len(points)}" NumberOfCells="{n_cells}">',
' <PointData Scalars="temperature" Vectors="heat_flux">',
data_array("temperature", temperature),
data_array("heat_flux", heat_flux, components=3),
' </PointData>',
' <CellData Scalars="material_id">',
data_array("material_id", material_id, vtk_type="Int32"),
' </CellData>',
' <Points>',
data_array("Points", points, components=3),
' </Points>',
' <Cells>',
data_array("connectivity", connectivity, vtk_type="Int64"),
data_array("offsets", offsets, vtk_type="Int64"),
data_array("types", cell_types, vtk_type="UInt8"),
' </Cells>',
' </Piece>',
' </UnstructuredGrid>',
'</VTKFile>',
]
filename.write_text("\n".join(lines) + "\n")
nx, ny = 42, 24
xs = np.linspace(0.0, 2.5, nx + 1)
ys = np.linspace(0.0, 1.0, ny + 1)
points_2d = np.array([(x, y) for y in ys for x in xs])
points = np.column_stack([points_2d, np.zeros(len(points_2d))])
def pid(i, j):
return j * (nx + 1) + i
triangles = []
cell_centers = []
for j in range(ny):
for i in range(nx):
quads = [pid(i, j), pid(i + 1, j), pid(i + 1, j + 1), pid(i, j + 1)]
xmid = xs[i : i + 2].mean()
ymid = ys[j : j + 2].mean()
# Leave a small circular void so that surface extraction and edges are visible.
if (xmid - 1.2) ** 2 + (ymid - 0.5) ** 2 < 0.16 ** 2:
continue
triangles.extend([[quads[0], quads[1], quads[2]], [quads[0], quads[2], quads[3]]])
cell_centers.extend([(xmid, ymid), (xmid, ymid)])
triangles = np.array(triangles, dtype=np.int64)
x = points[:, 0]
y = points[:, 1]
base_temperature = 1.0 - x / x.max() + 0.18 * np.exp(-45.0 * ((x - 1.2) ** 2 + (y - 0.5) ** 2))
material_id = np.array([1 if cx < 1.25 else 2 for cx, _ in cell_centers], dtype=np.int32)
written = []
for step, time in enumerate(np.linspace(0.0, 1.0, 6)):
oscillation = 0.08 * np.sin(2.0 * np.pi * time) * np.sin(np.pi * x / x.max()) * np.sin(np.pi * y)
temperature = base_temperature + oscillation
dtdx = -1.0 / x.max() + oscillation * (np.pi / x.max()) * np.cos(np.pi * x / x.max())
dtdy = oscillation * np.pi * np.cos(np.pi * y)
heat_flux = np.column_stack([-dtdx, -dtdy, np.zeros_like(dtdx)])
filename = out_dir / f"heat_demo_{step:03d}.vtu"
write_vtu(filename, points, triangles, temperature, heat_flux, material_id)
written.append((time, filename.name))
pvd_lines = [
'<?xml version="1.0"?>',
'<VTKFile type="Collection" version="0.1" byte_order="LittleEndian">',
' <Collection>',
]
for time, filename in written:
pvd_lines.append(f' <DataSet timestep="{time:.6f}" group="" part="0" file="{filename}"/>')
pvd_lines.extend([" </Collection>", "</VTKFile>"])
(out_dir / "heat_demo.pvd").write_text("\n".join(pvd_lines) + "\n")
print(f"Open this file in ParaView: {out_dir / 'heat_demo.pvd'}")
print(f"Wrote {len(written)} time steps with {len(points)} points and {len(triangles)} cells.")