Skip to content
Closed
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
28 changes: 27 additions & 1 deletion Tests/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,29 @@
import logging
import os
import warnings

TEST_DIRECTORY = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
TEST_DIRECTORY = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

PROJECT_DIRECTORY = os.getenv('PROJECT_DIR', os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

# get the logger instance
logger = logging.getLogger("pyVertexModel")

formatter = logging.Formatter(
"%(levelname)s [%(asctime)s] pyVertexModel: %(message)s",
datefmt="%Y/%m/%d %I:%M:%S %p",
)
console_handler = logging.StreamHandler()
console_handler.setFormatter(formatter)
logger.addHandler(console_handler)

logger.setLevel(logging.DEBUG)
logger.propagate = False


# Function to handle warnings
def warning_handler(message, category, filename, lineno, file=None, line=None):
logger.warning(f'{filename}:{lineno}: {category.__name__}: {message}')

# Set the warnings' showwarning function to the handler
warnings.showwarning = warning_handler
169 changes: 68 additions & 101 deletions Tests/test_vertexModel.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@
from src.pyVertexModel.algorithm.vertexModel import create_tetrahedra
from src.pyVertexModel.algorithm.vertexModelBubbles import build_topo, SeedWithBoundingBox, generate_first_ghost_nodes, \
delaunay_compute_entities, VertexModelBubbles
from src.pyVertexModel.algorithm.vertexModelVoronoiFromTimeImage import build_triplets_of_neighs, calculate_neighbours, \
VertexModelVoronoiFromTimeImage, add_tetrahedral_intercalations, build_2d_voronoi_from_image, \
populate_vertices_info, calculate_vertices, get_four_fold_vertices, divide_quartets_neighbours, process_image
from src.pyVertexModel.algorithm.vertexModelVoronoiFromTimeImage import build_triplets_of_neighs, \
VertexModelVoronoiFromTimeImage, add_tetrahedral_intercalations, get_four_fold_vertices, divide_quartets_neighbours, process_image
from src.pyVertexModel.geometry.degreesOfFreedom import DegreesOfFreedom
from src.pyVertexModel.util.utils import save_backup_vars

Expand Down Expand Up @@ -239,21 +238,6 @@ def test_build_triplets_of_neighs(self):
# Check if triplets of neighbours are correct
assert_matrix(triplets_of_neighs_test, mat_info['neighboursVertices'])

def test_calculate_neighbours(self):
"""
Test the calculate_neighbours function.
:return:
"""
# Load data
_, _, mat_info = load_data('calculate_neighbours_wingdisc.mat')

neighbours_test = calculate_neighbours(mat_info['labelledImg'], 2)

neighbours_expected = [np.concatenate(neighbours[0]) for neighbours in mat_info['imgNeighbours']]

# Check if the cells are initialized correctly
np.testing.assert_equal(neighbours_test[1:], neighbours_expected)

def test_obtain_initial_x_and_tetrahedra(self):
"""
Test the obtain_initial_x_and_tetrahedra function.
Expand All @@ -266,11 +250,11 @@ def test_obtain_initial_x_and_tetrahedra(self):
vModel_test = VertexModelVoronoiFromTimeImage(set_test)

file_name = 'LblImg_imageSequence.mat'
test_dir = 'Tests/data/%s' % file_name
test_dir = 'Tests/Tests_data/%s' % file_name
if exists(test_dir):
Twg_test, X_test = vModel_test.obtain_initial_x_and_tetrahedra(test_dir)
else:
Twg_test, X_test = vModel_test.obtain_initial_x_and_tetrahedra('data/%s' % file_name)
Twg_test, X_test = vModel_test.obtain_initial_x_and_tetrahedra('Tests_data/%s' % file_name)
Comment on lines 268 to +257
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests reference Tests/Tests_data/..., but fixtures are under the repository root Tests_data/. Because the fallback uses a relative path, this can fail when tests aren't executed from the repo root. Prefer using an absolute path such as os.path.join(TEST_DIRECTORY, 'Tests_data', file_name) for both branches (and remove the exists() check entirely).

Copilot uses AI. Check for mistakes.

# Check if the test and expected are the same
assert_matrix(Twg_test, mat_info_expected['Twg'] - 1)
Expand Down Expand Up @@ -326,83 +310,6 @@ def test_add_tetrahedra_intercalations(self):
# Check if the test and expected are the same
assert_matrix(Twg, mat_info_expected['Twg'])

def test_build_2d_voronoi_from_image(self):
"""
Test the build_2d_voronoi_from_image function.
:return:
"""
# Load data
_, _, mat_info = load_data('build_2d_voronoi_from_image_wingdisc.mat')
labelled_img = mat_info['labelledImg']
watershed_img = mat_info['watershedImg']
main_cells = mat_info['mainCells'][0]

# Test if initialize geometry function does not change anything
(triangles_connectivity, neighbours_network, cell_edges, vertices_location, border_cells,
border_of_border_cells_and_main_cells) = build_2d_voronoi_from_image(labelled_img, watershed_img, main_cells)

# Load expected
_, _, mat_info_expected = load_data('build_2d_voronoi_from_image_wingdisc_expected.mat')

# Assert
np.testing.assert_equal(triangles_connectivity, mat_info_expected['trianglesConnectivity'])
np.testing.assert_equal(neighbours_network, mat_info_expected['neighboursNetwork'])
np.testing.assert_equal([cell_edge+1 for cell_edge in cell_edges if cell_edge is not None], [cell_edge[0] for cell_edge in mat_info_expected['cellEdges']])
np.testing.assert_equal(border_cells, np.concatenate(mat_info_expected['borderCells']))
np.testing.assert_equal(border_of_border_cells_and_main_cells, mat_info_expected['borderOfborderCellsAndMainCells'][0])

def test_populate_vertices_info(self):
"""
Test the populate_vertices_info function.
:return:
"""
# Load data
_, _, mat_info = load_data('populate_vertices_info_wingdisc.mat')

# Load data
border_cells_and_main_cells = [border_cell[0] for border_cell in mat_info['borderCellsAndMainCells']]
labelled_img = mat_info['labelledImg']
img_neighbours_all = [np.concatenate(neighbours[0]) for neighbours in mat_info['imgNeighbours']]
main_cells = mat_info['mainCells'][0]
ratio = 2

img_neighbours_all.insert(0, None)

vertices_info_test = populate_vertices_info(border_cells_and_main_cells, img_neighbours_all, labelled_img,
main_cells, ratio)

vertices_info_expected_per_cell = [np.concatenate(vertices[0]) for vertices in mat_info['verticesInfo']['PerCell'][0][0] if len(vertices[0][0]) > 0]
vertices_info_expected_edges = [np.concatenate(vertices[0]) for vertices in mat_info['verticesInfo']['edges'][0][0] if len(vertices[0][0]) > 0]

# Assert
np.testing.assert_equal([vertices + 1 for vertices in vertices_info_test['PerCell'] if vertices is not None], vertices_info_expected_per_cell)
np.testing.assert_equal([np.concatenate(edges) + 1 for edges in vertices_info_test['edges'] if edges is not None], vertices_info_expected_edges)
np.testing.assert_equal(vertices_info_test['connectedCells'], mat_info['verticesInfo']['connectedCells'][0][0])

def test_calculate_vertices(self):
"""
Test the calculate_vertices function.
:return:
"""
# Load data
_, _, mat_info = load_data('calculate_vertices_wingdisc.mat')

# Load data
labelled_img = mat_info['labelledImg']
img_neighbours_all = [np.concatenate(neighbours[0]) for neighbours in mat_info['neighbours']]
ratio = 2

img_neighbours_all.insert(0, None)

# Test if initialize geometry function does not change anything
vertices_info_test = calculate_vertices(labelled_img, img_neighbours_all, ratio)

# Load expected
_, _, mat_info_expected = load_data('calculate_vertices_wingdisc_expected.mat')

# Assert
assert_matrix(vertices_info_test['connectedCells'], mat_info_expected['verticesInfo']['connectedCells'][0][0])

def test_get_four_fold_vertices(self):
"""
Test the get_four_fold_vertices function.
Expand Down Expand Up @@ -453,11 +360,11 @@ def test_process_image(self):
"""
# Process image
file_name = 'LblImg_imageSequence.mat'
test_dir = 'Tests/data/%s' % file_name
test_dir = 'Tests/Tests_data/%s' % file_name
if exists(test_dir):
_, imgStackLabelled_test = process_image(test_dir)
else:
_, imgStackLabelled_test = process_image('data/%s' % file_name)
_, imgStackLabelled_test = process_image('Tests_data/%s' % file_name)

# Load expected
_, _, mat_info_expected = load_data('process_image_wingdisc_expected.mat')
Expand All @@ -476,11 +383,11 @@ def test_initialize_voronoi_from_time_image(self):
# Test if initialize geometry function does not change anything
vModel_test = VertexModelVoronoiFromTimeImage(set_test)
file_name = 'voronoi_40cells.pkl'
test_dir = TEST_DIRECTORY + '/Tests/data/%s' % file_name
test_dir = TEST_DIRECTORY + '/Tests/Tests_data/%s' % file_name
if exists(test_dir):
vModel_test.set.initial_filename_state = test_dir
else:
vModel_test.set.initial_filename_state = 'data/%s' % file_name
vModel_test.set.initial_filename_state = 'Tests_data/%s' % file_name
Comment on lines 478 to +390
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test_dir is built as TEST_DIRECTORY + '/Tests/Tests_data/...', but the repository data directory is TEST_DIRECTORY/Tests_data. As written, the exists() check will always fail in a normal checkout, and the code relies on a relative 'Tests_data/...' path (CWD-dependent). Use os.path.join(TEST_DIRECTORY, 'Tests_data', file_name) consistently.

Copilot uses AI. Check for mistakes.

vModel_test.initialize()

Expand All @@ -496,3 +403,63 @@ def test_initialize_voronoi_from_time_image(self):
assert_array1D(g_test, mat_info['g'])
assert_matrix(K_test, mat_info['K'])

def test_weird_bug_should_not_happen(self):
"""
Test for a weird bug that should not happen.
:return:
"""
# Load data
vModel_test = load_data('vertices_going_wild.pkl')

# Run for 20 iterations. dt should not decrease to 1e-1
vModel_test.set.tend = vModel_test.t + 20 * vModel_test.set.dt0

# Update tolerance
vModel_test.set.dt_tolerance = 0.25

# Run the model
vModel_test.iterate_over_time()

# Check if it did not converge
self.assertFalse(vModel_test.didNotConverge)

def test_vertices_shouldnt_be_going_wild(self):
"""
Test for another weird bug that should not happen.
:return:
"""
# Load data
vModel_test = load_data('vertices_going_wild_2.pkl')

# Run for 10 iterations. dt should not decrease to 1e-1
vModel_test.set.tend = vModel_test.t + 20 * vModel_test.set.dt0

Comment on lines +434 to +436
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment says “Run for 10 iterations”, but tend is set to t + 20 * dt0 (20 time steps at dt0). Either adjust the comment or the tend calculation so the test intent is unambiguous.

Copilot uses AI. Check for mistakes.
# Update tolerance
vModel_test.set.dt_tolerance = 0.25

# Run the model
vModel_test.iterate_over_time()

# Check if it did not converge
self.assertFalse(vModel_test.didNotConverge)

def test_vertices_shouldnt_be_going_wild_3(self):
"""
Test for another weird bug that should not happen.
:return:
"""
# Load data
vModel_test = load_data('vertices_going_wild_3.pkl')

# Run for 10 iterations. dt should not decrease to 1e-1
vModel_test.set.tend = vModel_test.t + 20 * vModel_test.set.dt0

# Update tolerance
vModel_test.set.dt_tolerance = 0.25

# Run the model
vModel_test.iterate_over_time()

# Check if it did not converge
self.assertFalse(vModel_test.didNotConverge)

57 changes: 40 additions & 17 deletions Tests/tests.py
Original file line number Diff line number Diff line change
@@ -1,36 +1,59 @@
import logging
import os
import unittest
import warnings

import scipy.io
import numpy as np
from os.path import exists, abspath

from src.pyVertexModel.algorithm.vertexModelVoronoiFromTimeImage import VertexModelVoronoiFromTimeImage
from src.pyVertexModel.geometry.geo import Geo
from src.pyVertexModel.parameters.set import Set
from src.pyVertexModel.Kg import kg_functions
from src.pyVertexModel.util.utils import load_state

def load_data(file_name, return_geo=True):
test_dir = abspath('Tests/data/%s' % file_name)
if exists(test_dir):
mat_info = scipy.io.loadmat(test_dir)
else:
mat_info = scipy.io.loadmat('data/%s' % file_name)
test_dir = abspath('Tests/Tests_data/%s' % file_name)
if file_name.endswith('.mat'):
if exists(test_dir):
mat_info = scipy.io.loadmat(test_dir)
else:
mat_info = scipy.io.loadmat('Tests_data/%s' % file_name)
Comment on lines +16 to +21
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

load_data builds test_dir using abspath('Tests/Tests_data/...'), but the repository test fixtures live under the top-level Tests_data/ directory. This makes the helper depend on the current working directory and the fallback relative path, which can break when tests are run from a different CWD. Prefer building an absolute path from Tests.TEST_DIRECTORY (or Path(__file__).resolve()) and always loading from .../Tests_data/<file>.

Copilot uses AI. Check for mistakes.

if return_geo:
if 'Geo' in mat_info.keys():
geo_test = Geo(mat_info['Geo'])
if return_geo:
if 'Geo' in mat_info.keys():
geo_test = Geo(mat_info['Geo'])
else:
geo_test = None

if 'Set' in mat_info.keys():
set_test = Set(mat_info['Set'])
if set_test.OutputFolder.__eq__(b'') or set_test.OutputFolder is None:
set_test.OutputFolder = '../Result/Test'
else:
set_test = None
Comment on lines +29 to +34
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Normalize empty OutputFolder values ('' vs b'').

scipy.io.loadmat can return '' (str) as well as b'' (bytes). If '' slips through, later OutputFolder is not None checks can still trigger filesystem writes to CWD. Consider a broader empty check.

🛠️ Suggested fix
-                if set_test.OutputFolder.__eq__(b'') or set_test.OutputFolder is None:
-                    set_test.OutputFolder = '../Result/Test'
+                if (set_test.OutputFolder is None or
+                        (isinstance(set_test.OutputFolder, (bytes, str)) and len(set_test.OutputFolder) == 0)):
+                    set_test.OutputFolder = '../Result/Test'
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if 'Set' in mat_info.keys():
set_test = Set(mat_info['Set'])
if set_test.OutputFolder.__eq__(b'') or set_test.OutputFolder is None:
set_test.OutputFolder = '../Result/Test'
else:
set_test = None
if 'Set' in mat_info.keys():
set_test = Set(mat_info['Set'])
if (set_test.OutputFolder is None or
(isinstance(set_test.OutputFolder, (bytes, str)) and len(set_test.OutputFolder) == 0)):
set_test.OutputFolder = '../Result/Test'
else:
set_test = None
🤖 Prompt for AI Agents
In `@Tests/tests.py` around lines 26 - 31, The current check on
set_test.OutputFolder only compares to b'' and None, but scipy.io.loadmat may
return '' (str) or b'' (bytes) so empty values can slip through; update the
logic that sets set_test.OutputFolder (for the Set(...) result) to treat None,
b'' and '' as empty by normalizing bytes to str (or checking both types) and
then assigning '../Result/Test' when the normalized value is empty; reference
Set and set_test.OutputFolder to locate the code and ensure the check uses
isinstance or explicit comparisons for both b'' and '' before assigning the
default OutputFolder.

else:
geo_test = None
set_test = None

if 'Set' in mat_info.keys():
set_test = Set(mat_info['Set'])
if set_test.OutputFolder.__eq__(b'') or set_test.OutputFolder is None:
set_test.OutputFolder = '../Result/Test'
return geo_test, set_test, mat_info
elif file_name.endswith('.pkl'):
v_model = VertexModelVoronoiFromTimeImage(create_output_folder=False)
if exists(test_dir):
load_state(v_model, test_dir)
else:
set_test = None
load_state(v_model, 'Tests_data/%s' % file_name)

# Set parameters to avoid file output during tests
v_model.set.OutputFolder = None
v_model.set.export_images = False
v_model.set.VTK = False

return v_model
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

load_data returns a (geo, set, mat_info) tuple for .mat files but returns a VertexModel... instance for .pkl files. This polymorphic return type is easy to misuse (callers can accidentally unpack a .pkl result and fail at runtime). Consider splitting into load_mat_data(...) and load_model_state(...), or always returning a consistent tuple (e.g., (None, None, v_model) or (geo, set, extra) with a documented contract).

Suggested change
return v_model
return None, None, v_model

Copilot uses AI. Check for mistakes.
else:
geo_test = None
set_test = None
raise FileNotFoundError('File %s not found' % file_name)
Comment on lines 53 to +54
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Error message is misleading for unsupported file extensions.

FileNotFoundError with "not found" message is raised for files that don't end with .mat or .pkl, but this is actually an "unsupported file type" error, not a "file not found" error. This could mislead debugging efforts.

♻️ Suggested fix
     else:
-        raise FileNotFoundError('File %s not found' % file_name)
+        raise ValueError('Unsupported file type: %s (expected .mat or .pkl)' % file_name)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
else:
geo_test = None
set_test = None
raise FileNotFoundError('File %s not found' % file_name)
else:
raise ValueError('Unsupported file type: %s (expected .mat or .pkl)' % file_name)
🤖 Prompt for AI Agents
In `@Tests/tests.py` around lines 53 - 54, The raised FileNotFoundError for
non-.mat/.pkl inputs is misleading; change the exception to a more appropriate
type (e.g., ValueError or a custom UnsupportedFileTypeError) and update the
message to indicate an unsupported file extension including the offending
file_name or its extension; locate the raise statement that references file_name
in Tests/tests.py and replace it with a descriptive "Unsupported file type"
exception and message.



return geo_test, set_test, mat_info


def assert_matrix(k_expected, k):
Expand Down
Binary file added Tests_data/Geo_3x3_dofs_expected.mat
Binary file not shown.
Binary file added Tests_data/Geo_var_3x3_stretch.mat
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added Tests_data/Geo_var_cyst.mat
Binary file not shown.
Binary file added Tests_data/Geo_var_cyst_expectedResults_cell1.mat
Binary file not shown.
Binary file added Tests_data/LblImg_imageSequence.mat
Binary file not shown.
Binary file added Tests_data/Newton_Raphson_3x3_stretch.mat
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added Tests_data/Newton_Raphson_Iteration_wingdisc.mat
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added Tests_data/Newton_Raphson_wingdisc_expected.mat
Binary file not shown.
Binary file added Tests_data/add_and_rebuild_cells_wingdisc.mat
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added Tests_data/add_tetrahedra_wingdisc.mat
Binary file not shown.
Binary file added Tests_data/add_tetrahedra_wingdisc_expected.mat
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added Tests_data/build_cells_cyst.mat
Binary file not shown.
Binary file added Tests_data/build_cells_cyst_expected.mat
Binary file not shown.
Binary file added Tests_data/build_face_cyst.mat
Binary file not shown.
Binary file added Tests_data/build_face_cyst_expected.mat
Binary file not shown.
Binary file not shown.
Binary file added Tests_data/build_topo_expected.mat
Binary file not shown.
Binary file added Tests_data/build_triplets_wingdisc.mat
Binary file not shown.
Binary file added Tests_data/build_x_from_y_wingdisc.mat
Binary file not shown.
Binary file added Tests_data/build_x_from_y_wingdisc_expected.mat
Binary file not shown.
Binary file added Tests_data/calculate_neighbours_wingdisc.mat
Binary file not shown.
Binary file added Tests_data/calculate_vertices_wingdisc.mat
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added Tests_data/create_tetrahedra_wingdisc.mat
Binary file not shown.
Binary file added Tests_data/delaunay_compute_entities_expected.mat
Binary file not shown.
Binary file added Tests_data/delaunay_compute_entities_input.mat
Binary file not shown.
Binary file added Tests_data/delaunay_input_expected.mat
Binary file not shown.
Binary file added Tests_data/delaunay_output_cyst.mat
Binary file not shown.
Binary file added Tests_data/flipNM_cyst.mat
Binary file not shown.
Binary file added Tests_data/flipNM_recursive_cyst.mat
Binary file not shown.
Binary file added Tests_data/flipNM_recursive_cyst_expected.mat
Binary file not shown.
Binary file added Tests_data/generate_first_ghost_nodes_expected.mat
Binary file not shown.
Binary file added Tests_data/geo_cyst_expected_extrapolatedYs.mat
Binary file not shown.
Binary file added Tests_data/get_dofs_remodel.mat
Binary file not shown.
Binary file added Tests_data/get_four_fold_vertices_wingdisc.mat
Binary file not shown.
Binary file not shown.
Binary file added Tests_data/initialize_cells_cyst_expected.mat
Binary file not shown.
Binary file added Tests_data/initialize_voronoi_wingdisc.mat
Binary file not shown.
Binary file added Tests_data/kK_test.mat
Binary file not shown.
Binary file added Tests_data/line_search_cyst.mat
Binary file not shown.
Binary file added Tests_data/obtain_x_and_twg_wingdisc.mat
Binary file not shown.
Binary file added Tests_data/populate_vertices_info_wingdisc.mat
Binary file not shown.
Binary file added Tests_data/post_flip_wingdisc.mat
Binary file not shown.
Binary file added Tests_data/post_flip_wingdisc_2.mat
Binary file not shown.
Binary file added Tests_data/post_flip_wingdisc_2_expected.mat
Binary file not shown.
Binary file added Tests_data/post_flip_wingdisc_expected.mat
Binary file not shown.
Binary file added Tests_data/process_image_wingdisc_expected.mat
Binary file not shown.
Binary file added Tests_data/rebuild_stretch_3x3_expected.mat
Binary file not shown.
Binary file added Tests_data/rebuild_wingdisc_expected.mat
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added Tests_data/remove_tetrahedra_wingdisc.mat
Binary file not shown.
Binary file added Tests_data/seed_with_bbox_expected.mat
Binary file not shown.
Binary file added Tests_data/seed_with_bbox_input.mat
Binary file not shown.
Binary file added Tests_data/solve_remodelling_step_wingdisc.mat
Binary file not shown.
Binary file added Tests_data/test_gkDet.mat
Binary file not shown.
Binary file added Tests_data/vertices_going_wild.pkl
Binary file not shown.
Binary file added Tests_data/vertices_going_wild_2.pkl
Binary file not shown.
Binary file added Tests_data/vertices_going_wild_3.pkl
Binary file not shown.
Binary file added Tests_data/voronoi_40cells.pkl
Binary file not shown.
Binary file added Tests_data/wing_disc_110.mat
Binary file not shown.
Binary file added Tests_data/wing_disc_150.mat
Binary file not shown.
30 changes: 16 additions & 14 deletions src/pyVertexModel/algorithm/vertexModel.py
Original file line number Diff line number Diff line change
Expand Up @@ -429,9 +429,10 @@ def iterate_over_time(self):
updating measures, and checking for convergence.
:return:
"""
temp_dir = os.path.join(self.set.OutputFolder, 'images')
if not os.path.exists(temp_dir):
os.makedirs(temp_dir)
if self.set.OutputFolder is not None:
temp_dir = os.path.join(self.set.OutputFolder, 'images')
if not os.path.exists(temp_dir):
os.makedirs(temp_dir)

if self.set.Substrate == 1:
self.Dofs.GetDOFsSubstrate(self.geo, self.set)
Expand Down Expand Up @@ -533,7 +534,7 @@ def iteration_did_not_converged(self):
self.set.nu = 10 * self.set.nu0
else:
if (self.set.iter >= self.set.MaxIter and
(self.set.dt / self.set.dt0) > 1e-6):
(self.set.dt / self.set.dt0) > self.set.dt_tolerance):
Comment on lines 535 to +537
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Backward-compatibility risk: self.set.dt_tolerance is a new attribute. If a VertexModel is restored from an older .pkl where the Set instance predates this field, iteration_did_not_converged will raise AttributeError. Consider using getattr(self.set, 'dt_tolerance', 1e-6) here (or ensure Set defines it in a post-load hook) to keep old saved states runnable.

Copilot uses AI. Check for mistakes.
self.set.MaxIter = self.set.MaxIter0
Comment on lines 536 to 538
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Guard dt_tolerance for legacy pickles / invalid values.

Line 536 now dereferences self.set.dt_tolerance; pickled Set objects created before this field existed will raise AttributeError, and non‑positive values can lead to endless halving. Consider a safe default + validation.

🛠️ Suggested fix
-            if (self.set.iter >= self.set.MaxIter and
-                    (self.set.dt / self.set.dt0) > self.set.dt_tolerance):
+            dt_tol = getattr(self.set, "dt_tolerance", 1e-6)
+            if dt_tol <= 0:
+                logger.warning("Invalid dt_tolerance=%s; defaulting to 1e-6", dt_tol)
+                dt_tol = 1e-6
+            if (self.set.iter >= self.set.MaxIter and
+                    (self.set.dt / self.set.dt0) > dt_tol):
🤖 Prompt for AI Agents
In `@src/pyVertexModel/algorithm/vertexModel.py` around lines 536 - 538, The code
dereferences self.set.dt_tolerance directly which breaks for legacy pickled Set
objects and allows non‑positive values to cause endless halving; fix by reading
a validated tolerance first (e.g. tol = getattr(self.set, "dt_tolerance",
DEFAULT_DT_TOLERANCE)), clamp/replace any non‑positive or otherwise invalid
values (e.g. if tol is None or tol <= 0: tol = DEFAULT_DT_TOLERANCE), then use
that validated tol in the conditional that references dt_tolerance (the if block
that checks (self.set.dt / self.set.dt0) > ... and resets self.set.MaxIter).
Define a clear DEFAULT_DT_TOLERANCE constant or use an existing config value and
update the condition to use tol instead of self.set.dt_tolerance.

self.set.nu = self.set.nu0
self.set.dt = self.set.dt / 2
Expand Down Expand Up @@ -615,16 +616,17 @@ def save_v_model_state(self, file_name=None):
:param file_name:
:return:
"""
# Create VTK files for the current state
self.geo.create_vtk_cell(self.set, self.numStep, 'Edges')
self.geo.create_vtk_cell(self.set, self.numStep, 'Cells')
temp_dir = os.path.join(self.set.OutputFolder, 'images')
screenshot(self, temp_dir)
# Save Data of the current step
if file_name is None:
save_state(self, os.path.join(self.set.OutputFolder, 'data_step_' + str(self.numStep) + '.pkl'))
else:
save_state(self, os.path.join(self.set.OutputFolder, file_name + '.pkl'))
if self.set.OutputFolder is not None:
# Create VTK files for the current state
self.geo.create_vtk_cell(self.set, self.numStep, 'Edges')
self.geo.create_vtk_cell(self.set, self.numStep, 'Cells')
temp_dir = os.path.join(self.set.OutputFolder, 'images')
screenshot(self, temp_dir)
# Save Data of the current step
if file_name is None:
save_state(self, os.path.join(self.set.OutputFolder, 'data_step_' + str(self.numStep) + '.pkl'))
else:
save_state(self, os.path.join(self.set.OutputFolder, file_name + '.pkl'))

def reset_noisy_parameters(self):
"""
Expand Down
Loading
Loading