Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
640e639
Plotter functions_2D_3D
jerrypaulvarghese May 30, 2025
915a4be
Add files via upload
jerrypaulvarghese May 30, 2025
6454f82
Add 3D plotting support with fol.plotter.Plotter3D and inference modules
jerrypaulvarghese Jun 18, 2025
3d39122
added mixed sample training mechanical_box example with Plotter3D ge…
jerrypaulvarghese Jun 21, 2025
62ec897
Plotter3D tests have passed
jerrypaulvarghese Jun 24, 2025
ef4693b
Merge branch 'main' of https://github.com/RezaNajian/implicit_fol int…
jerrypaulvarghese Jun 24, 2025
1c8b7e0
Delete plotter directory
jerrypaulvarghese Jun 24, 2025
d2f4c5f
Add pyvista to install_requires for Plotter3D
jerrypaulvarghese Jun 24, 2025
8e2b2ce
Update ci.yml
jerrypaulvarghese Jun 24, 2025
80cb81a
Update ci.yml
jerrypaulvarghese Jun 24, 2025
57fccd5
Update ci.yml
jerrypaulvarghese Jun 24, 2025
45df6b9
Update ci.yml
jerrypaulvarghese Jun 24, 2025
1d45383
Update ci.yml
jerrypaulvarghese Jun 24, 2025
5423e3a
Update ci.yml
jerrypaulvarghese Jun 24, 2025
91fe89e
Update setup.py
jerrypaulvarghese Jun 24, 2025
4131deb
Update plotter.py
jerrypaulvarghese Jun 25, 2025
0a91c57
Merge pull request #6 from RezaNajian/main
jerrypaulvarghese Jul 6, 2025
02c8211
Update usefull_functions.py with Neww se of Dirchlet BCS
jerrypaulvarghese Jul 30, 2025
518401f
git pushMerge remote-tracking branch 'origin/main' into 3D_Tetra-ifo
jerrypaulvarghese Aug 28, 2025
6dabcb4
update
jerrypaulvarghese Aug 28, 2025
6276514
Add pyvista to dependencies in pyproject.toml
jerrypaulvarghese Aug 28, 2025
5f28fed
Update pyproject.toml
jerrypaulvarghese Aug 28, 2025
0126558
Update CI.yml
jerrypaulvarghese Aug 28, 2025
09bacce
Update test_plotter3d.py
jerrypaulvarghese Aug 28, 2025
470b3bf
Update test_plotter3d.py
jerrypaulvarghese Aug 28, 2025
7d7fa70
Delete tested_samples directory
jerrypaulvarghese Aug 29, 2025
82835dd
Update CI.yml
RezaNajian Sep 3, 2025
6a3dc1c
Update usefull_functions.py
jerrypaulvarghese Sep 7, 2025
c6ab729
Update usefull_functions.py with new Dirichlet BC helpers
jerrypaulvarghese Sep 7, 2025
2a386af
Update plotter.py with 2D plotter
jerrypaulvarghese Sep 12, 2025
ee212fa
Create Hyper_elastic_2d
jerrypaulvarghese Sep 16, 2025
cceecb5
Rename Hyper_elastic_2d.py to hyper_elasticity_2d.py
jerrypaulvarghese Sep 16, 2025
76d3bee
Compressible Neohookean
jerrypaulvarghese Sep 16, 2025
1ea0ab9
Merge branch 'main' into 3D_Tetra-ifol
jerrypaulvarghese Sep 16, 2025
0d3a94f
Merge remote-tracking branch 'origin/main' into 3D_Tetra-ifol
jerrypaulvarghese Nov 5, 2025
b069e54
Add comprehensive unit tests for Plotter2D class
jerrypaulvarghese Nov 5, 2025
4518c9c
Remove hyper_elasticity_2d.py from PR - not ready for merge
jerrypaulvarghese Nov 5, 2025
9fc1c68
Remove solver convergence plotting function
jerrypaulvarghese Nov 7, 2025
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
147 changes: 125 additions & 22 deletions fol/tools/plotter.py
Original file line number Diff line number Diff line change
Expand Up @@ -464,30 +464,133 @@ def render_sample_panels(self, idx: int):


# ----------------------------------------------------------------------
# Utility: solver convergence plot (kept as-is)
# PLotter2D for plotting 2D domains(most of the configs are transfered)
# ----------------------------------------------------------------------

def plot_solver_convergence(residual_norms_history, save_path=None, show=False):
class Plotter2D(Plotter3D):
"""
Plots the nonlinear solver residual norm vs. iteration for each sample.
Flat-mesh visualiser re-using Plotter3D’s config.

Args:
residual_norms_history: List of lists, each containing the residual norms for a sample.
save_path: If given, the plot is saved to this path.
show: If True, the plot is displayed.
• Keeps the 3-panel overview (FOL |U|, REF |U|, |ΔU|).
• Optional in-plane warping for the first two panels via
config["warp_factor_2d"] (float, default 0 = off).
"""
plt.figure(figsize=(8, 6))
for idx, residual_norms in enumerate(residual_norms_history):
plt.semilogy(residual_norms, '-o', label=f"Sample {idx}")
plt.xlabel("Iteration number", fontsize=12)
plt.ylabel("Residual norm (log scale)", fontsize=12)
plt.title("Nonlinear Solver Convergence (Neo-Hookean)", fontsize=14)
plt.grid(True, which='both', linestyle='--', linewidth=0.5)
plt.legend()
plt.tight_layout()
if save_path:
plt.savefig(save_path, dpi=300)
print(f"Convergence plot saved to: {save_path}")
if show:
plt.show()
plt.close()
# ----------------------------------------------------------------------
# Initialization
# ----------------------------------------------------------------------
def __init__(self, vtk_path: str, *, config: dict):
super().__init__(vtk_path=vtk_path, warp_factor=1.0, config=config)

# ensure mesh is flat in Z
if abs(self.mesh.bounds[-1] - self.mesh.bounds[-2]) > 1e-8:
raise ValueError("Plotter2D expects a flat 2-D mesh (z ≈ 0).")

self.do_clip = False
self.warp_factor_2d = float(self.config.get("warp_factor_2d", 0.0)) or None

self.u_fol_pre = self.name_map["u_fol_prefix"]
self.u_ref_pre = self.name_map["u_fe_prefix"]
self._find_best_sample_by_abs_error() # sets self.fields / self.best_id
# ----------------------------------------------------------------------
# Helpers:
# ----------------------------------------------------------------------
def _ensure_mag(self, vec_name: str) -> str:
"""Create <vec>_mag on-the-fly (2- or 3-component arrays)."""
if vec_name.endswith("_mag"):
return vec_name
arr = self.mesh[vec_name]
if arr.ndim == 2 and arr.shape[1] in (2, 3):
mag = f"{vec_name}_mag"
if mag not in self.mesh.point_data:
self.mesh.point_data[mag] = np.linalg.norm(arr, axis=1)
return mag
return vec_name

def _find_best_sample_by_abs_error(self):
# add magnitude fields for every abs_error_i vector
for k in list(self.mesh.point_data.keys()):
if re.match(r"abs_error_(\d+)$", k):
self._ensure_mag(k)

ids = [int(m.group(1)) for k in self.mesh.point_data
if (m := re.match(r"abs_error_(\d+)_mag$", k))]
if not ids:
raise ValueError("No abs_error_<i>_mag fields in the VTK.")

best_id, min_err = None, float("inf")
for i in ids:
l2 = np.linalg.norm(self.mesh[f"abs_error_{i}_mag"])
if l2 < min_err:
best_id, min_err = i, l2

self.best_id = best_id
self.fields = {
"U_FOL": f"{self.u_fol_pre}{best_id}",
"U_REF": f"{self.u_ref_pre}{best_id}",
"ERR": f"abs_error_{best_id}_mag",
}
print(f"[Plotter2D] eval_id={best_id} ‖error‖₂={min_err:.3e}")

# ----------------------------------------------------------------------
# Helper: rederer
# ----------------------------------------------------------------------
def render_panel(self, mesh_obj, field, clim, title, fname, show_edges=None):
if show_edges is None:
show_edges = self.show_edges_default
p = pv.Plotter(off_screen=True, window_size=self.config["window_size"])
p.add_mesh(mesh_obj, scalars=field, cmap=self.config["cmap"],
clim=clim, show_edges=show_edges, edge_color="white",
line_width=0.4, scalar_bar_args=self.shared_scalar_bar_args)
p.view_xy(); p.enable_parallel_projection()
p.camera.zoom(float(self.config["zoom"]))
p.show_axes = False
if title:
p.add_text(title, font_size=self.config["title_font_size"],
position="upper_edge")
out = os.path.join(self.output_dir, fname)
p.screenshot(out); p.close()
print(f"[Plotter2D] saved {fname}")

# helper to create a warped copy --------------------------------
def _warped_mesh(self, vec_field: str, mag_field: str):
m = self.mesh.copy(deep=True)
m.active_vectors_name = vec_field
w = m.warp_by_vector(factor=self.warp_factor_2d)
w[mag_field] = self.mesh[mag_field] # colour by |U|
return w
# ----------------------------------------------------------------------
# Render all panels here
# ----------------------------------------------------------------------
def render_all_panels(self):
fol_vec, ref_vec, err_sca = (self.fields[k] for k in ("U_FOL", "U_REF", "ERR"))
fol_mag, ref_mag = map(self._ensure_mag, (fol_vec, ref_vec))

# use warped meshes if requested
mesh_fol = self._warped_mesh(fol_vec, fol_mag) if self.warp_factor_2d else self.mesh
mesh_ref = self._warped_mesh(ref_vec, ref_mag) if self.warp_factor_2d else self.mesh

clim_u = [0.0, float(max(self.mesh[fol_mag].max(), self.mesh[ref_mag].max()))]
clim_e = [0.0, float(self.mesh[err_sca].max())]

panels = [
(mesh_fol, fol_mag, clim_u, "FOL |U|", "panel_fol.png"),
(mesh_ref, ref_mag, clim_u, "REF |U|", "panel_ref.png"),
(self.mesh, err_sca, clim_e, "Abs Error |ΔU|", "panel_err.png"),
]

for m, f, c, t, fn in panels:
self.render_panel(m, f, c, t, fn)

# stitch
import matplotlib.image as mpimg
fig, ax = plt.subplots(1, 3, figsize=(18, 6))
for a, (_, _, _, _, fn) in zip(ax, panels):
a.imshow(mpimg.imread(os.path.join(self.output_dir, fn))); a.axis("off")
out = os.path.join(self.output_dir,
self.config.get("output_image", "overview2d.png"))
plt.tight_layout(); plt.savefig(out, dpi=300); plt.close(fig)
print(f"[Plotter2D] overview saved → {out}")




Loading