From eaa0e08f6789a02292ae4e3c5678e36db595b64f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20H=C3=B6lscher?= Date: Mon, 7 Jul 2025 16:26:46 +0200 Subject: [PATCH 01/16] Mesh parsing for higher order elements --- plutho/mesh/custom_mesh_parser.py | 25 ++++++-- plutho/mesh/gmsh_parser.py | 95 ++++++++++++++++++++++--------- plutho/mesh/mesh.py | 6 +- tests/test_simulations.py | 8 +-- 4 files changed, 94 insertions(+), 40 deletions(-) diff --git a/plutho/mesh/custom_mesh_parser.py b/plutho/mesh/custom_mesh_parser.py index d8474c3..14095e8 100644 --- a/plutho/mesh/custom_mesh_parser.py +++ b/plutho/mesh/custom_mesh_parser.py @@ -10,6 +10,10 @@ import numpy as np import numpy.typing as npt +# Local libraries +from .gmsh_parser import element_to_nodes_map, \ + element_to_boundary_nodes_map + class TokenType(Enum): SectionStart = "SectionStart", @@ -140,10 +144,14 @@ class CustomParser: nodes: npt.NDArray elements: List[Element] - def __init__(self, file_path: str): + element_order: int + + def __init__(self, file_path: str, element_order: int): if not os.path.isfile(file_path): raise IOError(f"Mesh file {file_path} not found.") + self.element_order = element_order + # Load text mesh_text = "" with open(file_path, "r", encoding="UTF-8") as fd: @@ -167,9 +175,10 @@ def get_mesh_nodes_and_elements(self) -> Tuple[npt.NDArray, npt.NDArray]: if element.type == 2: triangle_elements.append(element) + nodes_per_element = element_to_nodes_map[2, self.element_order] number_of_triangle_elements = len(triangle_elements) elements = np.zeros( - shape=(number_of_triangle_elements, 3), + shape=(number_of_triangle_elements, nodes_per_element), dtype=np.int64 ) @@ -235,11 +244,13 @@ def get_triangle_elements_by_physical_group( elements_pg = self.get_elements_by_physical_group(physical_group_name) _, elements = self.get_mesh_nodes_and_elements() + nodes_per_element = element_to_nodes_map[2, self.element_order] + # Find triangle elements, which share the same nodes triangle_elements = [] for element_pg in elements_pg: for element in elements: - if len(element) != 3: + if len(element) != nodes_per_element: continue found = True @@ -391,10 +402,12 @@ def _parse_elements(self): tags.append(int(token.value)) # Parse nodes - # element_type = 1 -> Line -> 2 Points - # element_type = 2 -> Triangle -> 3 Points + number_of_nodes = element_to_nodes_map[ + element_type, + self.element_order + ] nodes = [] - for _ in range(element_type+1): + for _ in range(number_of_nodes): token = self._next_token() expect_token(token, TokenType.TokenInt) # Subtract 1 because in gmsh the indices start at 1 diff --git a/plutho/mesh/gmsh_parser.py b/plutho/mesh/gmsh_parser.py index 94c1efa..bde3d5a 100644 --- a/plutho/mesh/gmsh_parser.py +++ b/plutho/mesh/gmsh_parser.py @@ -9,11 +9,28 @@ import numpy as np import numpy.typing as npt + +# First index represents the element type: +# 1: Line +# 2: Triangle +# Second index represents the element order +element_to_nodes_map = np.array([ + [-1, -1, -1, -1], + [-1, 2, 3, 4], + [-1, 3, 6, 10] +]) + +# Since its the same for lines and triangles, this map only has one row +# and the index represents the element order +element_to_boundary_nodes_map = np.array([-1, 2, 3, 4]) + + class GmshParser: """Class to use the gmsh python interface to read the mesh files. """ + element_order: int - def __init__(self, file_path): + def __init__(self, file_path, element_order): if not gmsh.is_initialized(): gmsh.initialize() @@ -22,6 +39,8 @@ def __init__(self, file_path): else: raise IOError(f"Mesh file {file_path} not found.") + self.element_order = element_order + @staticmethod def _get_nodes(node_tags, node_coords, _=None) -> npt.NDArray: """Internal function to return a list of the node coordinates based @@ -43,8 +62,8 @@ def _get_nodes(node_tags, node_coords, _=None) -> npt.NDArray: return nodes - @staticmethod def _get_elements( + self, element_types, element_tags, element_node_tags @@ -53,8 +72,7 @@ def _get_elements( getElements() api function. Parameters: - element_types: Element types from gmsh api. Similar to the element - dimension so 2 equals triangles. + element_types: Element types from gmsh api. element_tags: Tags of the element per element type. element_node_tags: Tags of the nodes per element tag. @@ -62,17 +80,37 @@ def _get_elements( A list of lists where each inner list contains the node indices for the triangle at the outer list index. """ - elements = np.zeros(shape=(len(element_tags[1]), 3), dtype=int) - for i, element_type in enumerate(element_types): - if element_type == 2: - # Only looking for 3-node triangle elements - current_element_tags = element_tags[i] - current_node_tags = element_node_tags[i] - for j, _ in enumerate(current_element_tags): - # 1 is subtracted because the indices in gmsh start with 1. - elements[j] = current_node_tags[3*j:3*(j+1)] - np.ones(3) - - return elements + nodes_per_element = element_to_nodes_map[2, self.element_order] + elements = [] + element_indices = [] + # Entity elements of theset dimension containing the smaller elements + # Since every entity consists of multiple elements iterate over all + # entities and extract the elements + for i, _ in enumerate(element_types): + current_element_tags = element_tags[i] + current_node_tags = element_node_tags[i] + # For each element of which the entity consists + for j, _ in enumerate(current_element_tags): + # 1 is subtracted because the indices in gmsh start with 1. + elements.append(current_node_tags[ + nodes_per_element*j:nodes_per_element*(j+1) + ] - np.ones(nodes_per_element) + ) + element_indices.append(j) + + if len(elements) == 0: + raise ValueError( + "Couldn't return elements because the list is empty." + ) + + elements_np = np.zeros( + shape=(len(elements), len(elements[0])), + dtype=int + ) + for index, element in zip(element_indices, elements): + elements_np[index] = element + + return elements_np def get_mesh_nodes_and_elements(self) -> Tuple[npt.NDArray, npt.NDArray]: """Creates the nodes and elements lists as used in the simulation. @@ -80,7 +118,7 @@ def get_mesh_nodes_and_elements(self) -> Tuple[npt.NDArray, npt.NDArray]: Returns: List of nodes and elements""" nodes = self._get_nodes(*gmsh.model.mesh.getNodes()) - elements = self._get_elements(*gmsh.model.mesh.getElements()) + elements = self._get_elements(*gmsh.model.mesh.getElements(dim=2)) return nodes, elements @@ -131,30 +169,31 @@ def get_elements_by_physical_groups( Returns: Dictionary where keys are the pg names and the values are a list of triangles of this physical group.""" + nodes_on_boundary = element_to_boundary_nodes_map[ + self.element_order + ] pg_tags = self.get_nodes_by_physical_groups(needed_pg_names) - elements = self._get_elements(*gmsh.model.mesh.getElements()) + elements = self._get_elements(*gmsh.model.mesh.getElements(dim=2)) triangle_elements = {} for pg_name, nodes in pg_tags.items(): current_triangle_elements = [] for check_element in elements: - # If at least 2 nodes of the check_element are inside of the - # nodes list of the current physical group, then the element - # is also part of the physical group. + # If at least {nodes_on_boundary} nodes of the check_element + # are inside of the nodes list of the current physical group, + # then the element is also part of the physical group. found_count = 0 - if check_element[0] in nodes: - found_count += 1 - if check_element[1] in nodes: - found_count += 1 - if check_element[2] in nodes: - found_count += 1 + for ce in check_element: + if ce in nodes: + found_count += 1 - if found_count > 1: + if found_count >= nodes_on_boundary: current_triangle_elements.append(check_element) triangle_elements[pg_name] = np.array( current_triangle_elements, - dtype=int) + dtype=int + ) return triangle_elements diff --git a/plutho/mesh/mesh.py b/plutho/mesh/mesh.py index c746ae7..088f87d 100644 --- a/plutho/mesh/mesh.py +++ b/plutho/mesh/mesh.py @@ -6,6 +6,7 @@ # Third party libraries import gmsh +import numpy as np import numpy.typing as npt # Local libraries @@ -26,6 +27,7 @@ class Mesh: def __init__( self, file_path: str, + element_order: int ): if not os.path.isfile(file_path): raise IOError(f"Mesh file {file_path} not found.") @@ -46,9 +48,9 @@ def __init__( self.file_version = version if version.startswith("2"): - self.parser = CustomParser(file_path) + self.parser = CustomParser(file_path, element_order) else: - self.parser = GmshParser(file_path) + self.parser = GmshParser(file_path, element_order) def get_mesh_nodes_and_elements(self) -> Tuple[npt.NDArray, npt.NDArray]: return self.parser.get_mesh_nodes_and_elements() diff --git a/tests/test_simulations.py b/tests/test_simulations.py index 424511f..f32c3d9 100644 --- a/tests/test_simulations.py +++ b/tests/test_simulations.py @@ -54,7 +54,7 @@ def test_thermo_time(tmp_path): # Create and load mesh; TODO maybe use smaller mesh size? mesh_path = os.path.join(tmp_path, "default_mesh.msh") plutho.Mesh.generate_rectangular_mesh(mesh_path) - mesh = plutho.Mesh(mesh_path) + mesh = plutho.Mesh(mesh_path, element_order=1) sim = plutho.SingleSimulation( tmp_path, @@ -115,7 +115,7 @@ def test_piezo_time(tmp_path, test=True): # Create and load mesh; TODO maybe use smaller mesh size? mesh_path = os.path.join(tmp_path, "default_mesh.msh") plutho.Mesh.generate_rectangular_mesh(mesh_path) - mesh = plutho.Mesh(mesh_path) + mesh = plutho.Mesh(mesh_path, element_order=1) sim = plutho.SingleSimulation( tmp_path, @@ -209,7 +209,7 @@ def test_piezo_freq(tmp_path, test=True): # Create and load mesh; TODO maybe use smaller mesh size? mesh_path = os.path.join(tmp_path, "default_mesh.msh") plutho.Mesh.generate_rectangular_mesh(mesh_path) - mesh = plutho.Mesh(mesh_path) + mesh = plutho.Mesh(mesh_path, element_order=1) sim = plutho.SingleSimulation( tmp_path, @@ -296,7 +296,7 @@ def test_thermo_piezo_time(tmp_path, test=True): # Create and load mesh; TODO maybe use smaller mesh size? mesh_path = os.path.join(tmp_path, "default_mesh.msh") plutho.Mesh.generate_rectangular_mesh(mesh_path) - mesh = plutho.Mesh(mesh_path) + mesh = plutho.Mesh(mesh_path, element_order=1) sim = plutho.SingleSimulation( tmp_path, From 85bb99a33f46b5bd08177b5c26153088b7404c08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20H=C3=B6lscher?= Date: Mon, 7 Jul 2025 17:12:58 +0200 Subject: [PATCH 02/16] Add separate shape functions for 1d and 2d --- plutho/postprocessing.py | 6 +- plutho/simulation/base.py | 116 ++++++++++++++-------- plutho/simulation/nonlinear/piezo_time.py | 4 +- plutho/simulation/piezo_time.py | 6 +- plutho/simulation/thermo_piezo_time.py | 4 +- plutho/simulation/thermo_time.py | 16 +-- 6 files changed, 93 insertions(+), 59 deletions(-) diff --git a/plutho/postprocessing.py b/plutho/postprocessing.py index 4511245..e70888f 100644 --- a/plutho/postprocessing.py +++ b/plutho/postprocessing.py @@ -7,7 +7,7 @@ # Local libraries from plutho.simulation.base import energy_integral_theta, \ - gradient_local_shape_functions + gradient_local_shape_functions_2d def calculate_impedance( @@ -84,7 +84,7 @@ def calculate_stored_thermal_energy( for time_index in range(theta.shape[1]): for element in elements: - dn = gradient_local_shape_functions() + dn = gradient_local_shape_functions_2d() node_points = np.array([ [nodes[element[0]][0], nodes[element[1]][0], @@ -112,7 +112,7 @@ def calculate_stored_thermal_energy( stored_energy = 0 for element in elements: - dn = gradient_local_shape_functions() + dn = gradient_local_shape_functions_2d() node_points = np.array([ [nodes[element[0]][0], nodes[element[1]][0], diff --git a/plutho/simulation/base.py b/plutho/simulation/base.py index 94e10f6..4d68c1b 100644 --- a/plutho/simulation/base.py +++ b/plutho/simulation/base.py @@ -153,7 +153,7 @@ def load_from_file(file_path: str): # -------- Local functions and integrals -------- -def local_shape_functions(*dimensions: float): +def local_shape_functions_1d(s): """Returns the local linear shape functions for the given coordinates for a line or a triangle. The dimension of the returned shape functions depends on the number of @@ -169,21 +169,12 @@ def local_shape_functions(*dimensions: float): npt.NDArray: Shape functions depending on the given parameters for a line or a triangle """ - dim = len(dimensions) - if dim == 1: - return np.array([1-dimensions[0], dimensions[0]]) - elif dim == 2: - return np.array([ - 1-dimensions[0]-dimensions[1], - dimensions[0], - dimensions[1] - ]) - else: - raise NotImplementedError( - "Function not implemented for dimensions of 3 and above" - ) + return np.array([ + 1-s, s + ]) -def gradient_local_shape_functions(dim: int = 2): + +def gradient_local_shape_functions_1d(): """Returns the gradient of the local shape functions. The dimension can be 1 for a line or 2 for a triangle. @@ -192,21 +183,58 @@ def gradient_local_shape_functions(dim: int = 2): where n is the number of shape functions. Since currently shape functions are always linear: dim=1 -> n=2 and dim=2 -> n=3 """ - if dim == 1: - print("dim=1") - return np.array([ - [-1, 1], - [0, 0] - ]) - elif dim == 2: - return np.array([ - [-1, 1, 0], - [-1, 0, 1] - ]) - else: - raise NotImplementedError( - "Function not implemented for dimensions of 3 and above" - ) + return np.array([ + [-1, 1], + [0, 0] + ]) + + +def local_shape_functions_2d(s, t, element_order=1): + """Returns the local linear shape functions for the given coordinates for a + line or a triangle. + The dimension of the returned shape functions depends on the number of + coordinates given. + + Parameters: + dimensions: Each parameter corresponds to one coordinate. If only one + parameter is given it resembles 2 shape functions along a line. If + two coordinates are given it resembles 3 shape functions for a + triangle. + + Returns: + npt.NDArray: Shape functions depending on the given parameters for a + line or a triangle + """ + match element_order: + case 1: + return np.array([1-s-t, s, t]) + case 2: + return np.array([]) + case 3: + return np.array([]) + case _: + raise ValueError( + "Shape functions not implemented for element order" + f"{element_order}" + ) + + return np.array([ + 1-s-t, s, t + ]) + +def gradient_local_shape_functions_2d(): + """Returns the gradient of the local shape functions. The dimension can be + 1 for a line or 2 for a triangle. + + Returns: + npt.NDArray: Gradient of each of the shape functions. Shape: (2,n), + where n is the number of shape functions. Since currently shape + functions are always linear: dim=1 -> n=2 and dim=2 -> n=3 + """ + return np.array([ + [-1, 1, 0], + [-1, 0, 1] + ]) def local_to_global_coordinates( @@ -231,13 +259,19 @@ def local_to_global_coordinates( " dimensions" ) - return np.dot(node_points, local_shape_functions(*dimensions)) + if dim == 1: + return np.dot(node_points, local_shape_functions_1d(*dimensions)) + elif dim == 2: + return np.dot(node_points, local_shape_functions_2d(*dimensions)) + else: + raise ValueError("{dim}d local shape functions not defined.") def b_operator_global( node_points: npt.NDArray, jacobian_inverted_t: npt.NDArray, - *dimensions: float + s: float, + t: float ): """Calculates the B operator for the local coordinantes which is needed for voigt-notation. @@ -254,14 +288,14 @@ def b_operator_global( Returns: B operator 4x6, for a u aligned like [u1_r, u1_z, u2_r, u2_z, ..]. """ - dim = len(dimensions) + dim = 2 # Get local shape functions and r (because of theta component) - n = local_shape_functions(*dimensions) - r = local_to_global_coordinates(node_points, *dimensions)[0] + n = local_shape_functions_2d(s, t) + r = local_to_global_coordinates(node_points, s, t)[0] # Get gradients of local shape functions (s, t) - dn = gradient_local_shape_functions(dim) + dn = gradient_local_shape_functions_2d() # Convert to gradients of (r, z) using jacobi matrix global_dn = np.dot(jacobian_inverted_t, dn) @@ -298,7 +332,7 @@ def integral_m(node_points: npt.NDArray): npt.NDArray: 3x3 M matrix for the given element. """ def inner(s, t): - n = local_shape_functions(s, t) + n = local_shape_functions_2d(s, t) # Since the simulation is axisymmetric it is necessary # to multiply with the radius in the integral @@ -367,7 +401,7 @@ def inner(s, t): s, t ) - dn = gradient_local_shape_functions() + dn = gradient_local_shape_functions_2d() global_dn = np.dot(jacobian_inverted_t, dn) r = local_to_global_coordinates(node_points, s, t)[0] @@ -395,7 +429,7 @@ def integral_kve( npt.NDArray: 3x3 KVe matrix for the given element. """ def inner(s, t): - dn = gradient_local_shape_functions() + dn = gradient_local_shape_functions_2d() global_dn = np.dot(jacobian_inverted_t, dn) r = local_to_global_coordinates(node_points, s, t)[0] @@ -417,7 +451,7 @@ def energy_integral_theta( [theta1, theta2, theta3]. """ def inner(s, t): - n = local_shape_functions(s, t) + n = local_shape_functions_2d(s, t) r = local_to_global_coordinates(node_points, s, t)[0] return np.dot(n.T, theta)*r @@ -644,7 +678,7 @@ def create_local_element_data( List of LocalElementData objects. """ local_element_data = [] - dn = gradient_local_shape_functions(2) + dn = gradient_local_shape_functions_2d() for element in elements: # Get node points of element in format # [x1 x2 x3] diff --git a/plutho/simulation/nonlinear/piezo_time.py b/plutho/simulation/nonlinear/piezo_time.py index 7e118c8..409072a 100644 --- a/plutho/simulation/nonlinear/piezo_time.py +++ b/plutho/simulation/nonlinear/piezo_time.py @@ -10,7 +10,7 @@ # Local libraries from .base import assemble, NonlinearType -from ..base import SimulationData, MeshData, gradient_local_shape_functions, \ +from ..base import SimulationData, MeshData, gradient_local_shape_functions_2d, \ LocalElementData from plutho.simulation.piezo_time import charge_integral_u, \ charge_integral_v @@ -328,7 +328,7 @@ def calculate_charge( q = 0 for element_index, element in enumerate(electrode_elements): - dn = gradient_local_shape_functions() + dn = gradient_local_shape_functions_2d() node_points = np.array([ [nodes[element[0]][0], nodes[element[1]][0], diff --git a/plutho/simulation/piezo_time.py b/plutho/simulation/piezo_time.py index 5b8f5fc..25bad72 100644 --- a/plutho/simulation/piezo_time.py +++ b/plutho/simulation/piezo_time.py @@ -9,7 +9,7 @@ # Local libraries from .base import SimulationData, MeshData, \ - gradient_local_shape_functions, \ + gradient_local_shape_functions_2d, \ local_to_global_coordinates, b_operator_global, integral_m, \ integral_ku, integral_kuv, integral_kve, apply_dirichlet_bc, \ line_quadrature, create_local_element_data, LocalElementData @@ -73,7 +73,7 @@ def charge_integral_v( """ def inner(s): r = local_to_global_coordinates(node_points, s, 0)[0] - dn = gradient_local_shape_functions() + dn = gradient_local_shape_functions_2d() global_dn = np.dot(jacobian_inverted_t, dn) return -np.dot(np.dot(permittivity_matrix, global_dn), v_e)*r @@ -102,7 +102,7 @@ def calculate_charge( q = 0 for element_index, element in enumerate(elements): - dn = gradient_local_shape_functions(2) + dn = gradient_local_shape_functions_2d() node_points = np.array([ [nodes[element[0]][0], nodes[element[1]][0], diff --git a/plutho/simulation/thermo_piezo_time.py b/plutho/simulation/thermo_piezo_time.py index 438b164..e07e22f 100644 --- a/plutho/simulation/thermo_piezo_time.py +++ b/plutho/simulation/thermo_piezo_time.py @@ -10,7 +10,7 @@ # Local libraries from .base import SimulationData, MeshData, \ - gradient_local_shape_functions, create_local_element_data, \ + gradient_local_shape_functions_2d, create_local_element_data, \ local_to_global_coordinates, b_operator_global, integral_m, \ integral_ku, integral_kuv, integral_kve, apply_dirichlet_bc, \ quadratic_quadrature, LocalElementData, calculate_volumes, \ @@ -512,7 +512,7 @@ def get_load_vector( f_theta = np.zeros(number_of_nodes) for element_index, element in enumerate(self.mesh_data.elements): - dn = gradient_local_shape_functions() + dn = gradient_local_shape_functions_2d() node_points = np.array([ [nodes[element[0]][0], nodes[element[1]][0], diff --git a/plutho/simulation/thermo_time.py b/plutho/simulation/thermo_time.py index 4611517..e40b6c3 100644 --- a/plutho/simulation/thermo_time.py +++ b/plutho/simulation/thermo_time.py @@ -10,8 +10,8 @@ from scipy import sparse # Local libraries -from .base import SimulationData, MeshData, \ - local_shape_functions, gradient_local_shape_functions, \ +from .base import SimulationData, MeshData, local_shape_functions_1d, \ + local_shape_functions_2d, gradient_local_shape_functions_2d, \ local_to_global_coordinates, integral_m, LocalElementData, \ quadratic_quadrature, line_quadrature, create_local_element_data, \ apply_dirichlet_bc, get_avg_temp_field_per_element @@ -31,7 +31,7 @@ def integral_heat_flux( Returns: npt.NDArray heat flux integral on each point.""" def inner(s): - n = local_shape_functions(s) + n = local_shape_functions_1d(s) r = local_to_global_coordinates(node_points, s)[0] return n*heat_flux*r @@ -54,7 +54,7 @@ def integral_ktheta( npt.NDArray: 3x3 Ktheta matrix for the given element. """ def inner(s, t): - dn = gradient_local_shape_functions() + dn = gradient_local_shape_functions_2d() global_dn = np.dot(jacobian_inverted_t, dn) r = local_to_global_coordinates(node_points, s, t)[0] @@ -77,7 +77,7 @@ def integral_theta_load( npt.NDArray: f vector value at the specific ndoe """ def inner(s, t): - n = local_shape_functions(s, t) + n = local_shape_functions_2d(s, t) r = local_to_global_coordinates(node_points, s, t)[0] return n*mech_loss*r @@ -154,7 +154,7 @@ def assemble(self): dtype=np.float64) for element in self.mesh_data.elements: - dn = gradient_local_shape_functions() + dn = gradient_local_shape_functions_2d() # Get node points of element in format # [x1 x2 x3] # [y1 y2 y3] where (xi, yi) are the coordinates for Node i @@ -211,7 +211,7 @@ def set_volume_heat_source( f = np.zeros(shape=(number_of_nodes, number_of_time_steps)) for time_step in range(number_of_time_steps): for element_index, element in enumerate(self.mesh_data.elements): - dn = gradient_local_shape_functions() + dn = gradient_local_shape_functions_2d() node_points = np.array([ [nodes[element[0]][0], nodes[element[1]][0], @@ -252,7 +252,7 @@ def set_constant_volume_heat_source( f = np.zeros(number_of_nodes) for element_index, element in enumerate(self.mesh_data.elements): - dn = gradient_local_shape_functions() + dn = gradient_local_shape_functions_2d() node_points = np.array([ [nodes[element[0]][0], nodes[element[1]][0], From 8e119feac79309f6e00d66c2b6f8234fbf519ef9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20H=C3=B6lscher?= Date: Wed, 9 Jul 2025 10:31:33 +0200 Subject: [PATCH 03/16] WIP Add higher order shape functions --- plutho/mesh/mesh.py | 2 + plutho/postprocessing.py | 78 +-- plutho/simulation/base.py | 460 +++++++++++------- plutho/simulation/nonlinear/base.py | 75 +-- .../simulation/nonlinear/piezo_stationary.py | 17 +- plutho/simulation/nonlinear/piezo_time.py | 107 +--- plutho/simulation/piezo_freq.py | 102 ++-- plutho/simulation/piezo_time.py | 123 +++-- plutho/simulation/thermo_piezo_time.py | 156 +++--- plutho/simulation/thermo_time.py | 215 ++++---- plutho/single_sim.py | 2 +- tests/test_simulations.py | 9 +- 12 files changed, 696 insertions(+), 650 deletions(-) diff --git a/plutho/mesh/mesh.py b/plutho/mesh/mesh.py index 088f87d..a5c5970 100644 --- a/plutho/mesh/mesh.py +++ b/plutho/mesh/mesh.py @@ -23,6 +23,7 @@ class Mesh: mesh_file_path: str file_version: str parser: Union[GmshParser, CustomParser] + element_order: int def __init__( self, @@ -33,6 +34,7 @@ def __init__( raise IOError(f"Mesh file {file_path} not found.") self.mesh_file_path = file_path + self.element_order = element_order # Check gmsh version # If version2 -> custom gmsh parser diff --git a/plutho/postprocessing.py b/plutho/postprocessing.py index e70888f..1b74d3e 100644 --- a/plutho/postprocessing.py +++ b/plutho/postprocessing.py @@ -59,6 +59,7 @@ def calculate_stored_thermal_energy( theta: npt.NDArray, nodes: npt.NDArray, elements: npt.NDArray, + element_order: int, heat_capacity: float, density: float ) -> Union[float, npt.NDArray]: @@ -77,33 +78,33 @@ def calculate_stored_thermal_energy( Returns: The stored energy either as float or list of floats. """ + points_per_element = int(1/2*(element_order+1)*(element_order+2)) if len(theta.shape) == 2: # Need to calculate for every time step stored_energies = np.zeros(theta.shape[1]) for time_index in range(theta.shape[1]): - for element in elements: - dn = gradient_local_shape_functions_2d() - node_points = np.array([ - [nodes[element[0]][0], - nodes[element[1]][0], - nodes[element[2]][0]], - [nodes[element[0]][1], - nodes[element[1]][1], - nodes[element[2]][1]] - ]) - jacobian = np.dot(node_points, dn.T) - jacobian_det = np.linalg.det(jacobian) - theta_e = np.array([ - theta[element[0], time_index], - theta[element[1], time_index], - theta[element[2], time_index] - ]) + for element_index, element in enumerate(elements): + node_points = np.zeros(shape=(2, points_per_element)) + for node_index in range(points_per_element): + node_points[:, node_index] = [ + nodes[element[node_index]][0], + nodes[element[node_index]][1] + ] + + theta_e = np.zeros(points_per_element) + for node_index in range(points_per_element): + theta_e[node_index] = theta[ + element[node_index], + time_index + ] + stored_energies[time_index] += energy_integral_theta( node_points, - theta_e - ) * 2 * np.pi * jacobian_det * heat_capacity * density + theta_e, + element_order + ) * 2 * np.pi * heat_capacity * density return stored_energies @@ -111,28 +112,27 @@ def calculate_stored_thermal_energy( # Only one time step stored_energy = 0 - for element in elements: - dn = gradient_local_shape_functions_2d() - node_points = np.array([ - [nodes[element[0]][0], - nodes[element[1]][0], - nodes[element[2]][0]], - [nodes[element[0]][1], - nodes[element[1]][1], - nodes[element[2]][1]] - ]) - jacobian = np.dot(node_points, dn.T) - jacobian_det = np.linalg.det(jacobian) - theta_e = np.array([ - theta[element[0]], - theta[element[1]], - theta[element[2]] - ]) + for element_index, element in enumerate(elements): + node_points = np.zeros(shape=(2, points_per_element)) + for node_index in range(points_per_element): + node_points[:, node_index] = [ + nodes[element[node_index]][0], + nodes[element[node_index]][1] + ] + + theta_e = np.zeros(points_per_element) + for node_index in range(points_per_element): + theta_e[node_index] = theta[element[node_index]] + stored_energy += energy_integral_theta( node_points, - theta_e - ) * 2 * np.pi * jacobian_det * heat_capacity * density + theta_e, + element_order + ) * 2 * np.pi * heat_capacity * density return stored_energy - return -1 + raise ValueError( + "Cannot calculate total stored energy for given " + f"{len(theta.shape)}-dimensional theta" + ) diff --git a/plutho/simulation/base.py b/plutho/simulation/base.py index 4d68c1b..49c766b 100644 --- a/plutho/simulation/base.py +++ b/plutho/simulation/base.py @@ -49,6 +49,7 @@ class MeshData: """Contains the mesh data is used in the simulation.""" nodes: npt.NDArray elements: npt.NDArray + element_order: int class ExcitationType(Enum): @@ -74,15 +75,6 @@ def asdict(self): return content -@dataclass -class LocalElementData(): - """Conatins the data of a single element""" - node_points: npt.NDArray - jacobian: npt.NDArray - jacobian_inverted_t: npt.NDArray - jacobian_det: float - - @dataclass class MaterialData: """Contains the plain material data. Some parameters can either be @@ -153,125 +145,142 @@ def load_from_file(file_path: str): # -------- Local functions and integrals -------- -def local_shape_functions_1d(s): - """Returns the local linear shape functions for the given coordinates for a - line or a triangle. - The dimension of the returned shape functions depends on the number of - coordinates given. - - Parameters: - dimensions: Each parameter corresponds to one coordinate. If only one - parameter is given it resembles 2 shape functions along a line. If - two coordinates are given it resembles 3 shape functions for a - triangle. - - Returns: - npt.NDArray: Shape functions depending on the given parameters for a - line or a triangle - """ - return np.array([ - 1-s, s - ]) - - -def gradient_local_shape_functions_1d(): - """Returns the gradient of the local shape functions. The dimension can be - 1 for a line or 2 for a triangle. - - Returns: - npt.NDArray: Gradient of each of the shape functions. Shape: (2,n), - where n is the number of shape functions. Since currently shape - functions are always linear: dim=1 -> n=2 and dim=2 -> n=3 - """ - return np.array([ - [-1, 1], - [0, 0] - ]) - - def local_shape_functions_2d(s, t, element_order=1): - """Returns the local linear shape functions for the given coordinates for a - line or a triangle. - The dimension of the returned shape functions depends on the number of - coordinates given. + """Returns the local linear shape functions based on a reference triangle + with corner points [(0,0), (1,0), (1,1)] for the given coordinates. Parameters: - dimensions: Each parameter corresponds to one coordinate. If only one - parameter is given it resembles 2 shape functions along a line. If - two coordinates are given it resembles 3 shape functions for a - triangle. + s: Local s parameter. + t: Local t parameter. + element_order: Order of the shape functions (1=linear, 2=quadratic, + 3=cubic). Returns: npt.NDArray: Shape functions depending on the given parameters for a line or a triangle """ + L1 = 1-s-t + L2 = s + L3 = t + match element_order: case 1: - return np.array([1-s-t, s, t]) + return np.array([L1, L2, L3]) case 2: - return np.array([]) + return np.array( + [ + L1*(2*L1-1), + L2*(2*L2-1), + L3*(2*L3-1), + 4*L1*L2, + 4*L2*L3, + 4*L3*L1 + ] + ) case 3: - return np.array([]) + return np.array( + [ + 0.5*L1*(3*L1-1)*(3*L1-2), + 0.5*L2*(3*L2-1)*(3*L2-2), + 0.5*L3*(3*L3-1)*(3*L3-2), + 9/2*L1*L2*(3*L1-1), + 9/2*L2*L1*(3*L2-1), + 9/2*L2*L3*(3*L2-1), + 9/2*L3*L2*(3*L3-1), + 9/2*L3*L1*(3*L3-1), + 9/2*L1*L3*(3*L1-1), + 27*L1*L2*L3 + ] + ) case _: raise ValueError( - "Shape functions not implemented for element order" + "Shape functions not implemented for element order " f"{element_order}" ) - return np.array([ - 1-s-t, s, t - ]) -def gradient_local_shape_functions_2d(): - """Returns the gradient of the local shape functions. The dimension can be - 1 for a line or 2 for a triangle. +def gradient_local_shape_functions_2d(s, t, element_order=1) -> npt.NDArray: + """Returns the gradient of the local shape functions. + + Parameters: + s: Local s parameter. + t: Local t parameter. + element_order: Order of the shape functions (1=linear, 2=quadratic, + 3=cubic). Returns: npt.NDArray: Gradient of each of the shape functions. Shape: (2,n), where n is the number of shape functions. Since currently shape functions are always linear: dim=1 -> n=2 and dim=2 -> n=3 """ - return np.array([ - [-1, 1, 0], - [-1, 0, 1] - ]) + match element_order: + case 1: + return np.array([ + [-1, 1, 0], + [-1, 0, 1] + ]) + case 2: + return np.array([ + [ # d_s + -3+4*t+4*s, + 4*s-1, + 0, + 4-8*s-4*t, + 4*t, + -4*t + ], + [ # d_t + -3+4*s+4*t, + 0, + 4*t-1, + -4*s, + 4*s, + 4-4*s-8*t + ] + ]) + case 3: + pass + + raise ValueError( + "Gradient of shape functions not implemented for element " + f"order {element_order}" + ) def local_to_global_coordinates( - node_points: npt.NDArray, - *dimensions: float + node_points: npt.NDArray, + s: float, + t: float, + element_order: int ) -> Any: """Transforms the local coordinates given by dimensions using the node points to the global coordinates r, z. + Parameters: node_points: For a line: [[x1, x2], [y1, y2]] and for a triangle: [[x1, x2, x3], [y1, y2, y3]] of - dimensions: Local coordinates + s: Local s coordinate. + t: Local t coordinate. + element_order: Order of the shape functions. Returns: npt.NDArray: Global coordinates [r, z] """ - dim = len(dimensions) - - if node_points.shape[1] != dim + 1: + if node_points.shape[1] != int(1/2*(element_order+1)*(element_order+2)): raise ValueError( "The given node point array does not fit the given number of" " dimensions" ) - if dim == 1: - return np.dot(node_points, local_shape_functions_1d(*dimensions)) - elif dim == 2: - return np.dot(node_points, local_shape_functions_2d(*dimensions)) - else: - raise ValueError("{dim}d local shape functions not defined.") + return np.dot(node_points, local_shape_functions_2d(s, t, element_order)) def b_operator_global( node_points: npt.NDArray, jacobian_inverted_t: npt.NDArray, s: float, - t: float + t: float, + element_order: int ): """Calculates the B operator for the local coordinantes which is needed for voigt-notation. @@ -280,29 +289,30 @@ def b_operator_global( Parameters: node_points: List of node points [[x1, x2, x3], [y1, y2, y3]] of one triangle. - s: Local coordinate. - t: Local coordinate. jacobian_inverted_t: Jacobian matrix inverted and transposed, needed for calculation of global derivatives. + s: Local coordinate. + t: Local coordinate. + element_order: Order of the shape functions. Returns: B operator 4x6, for a u aligned like [u1_r, u1_z, u2_r, u2_z, ..]. """ - dim = 2 + dim = int(1/2*(element_order+1)*(element_order+2)) # Get local shape functions and r (because of theta component) n = local_shape_functions_2d(s, t) - r = local_to_global_coordinates(node_points, s, t)[0] + r = local_to_global_coordinates(node_points, s, t, element_order)[0] # Get gradients of local shape functions (s, t) - dn = gradient_local_shape_functions_2d() + dn = gradient_local_shape_functions_2d(s, t, element_order) # Convert to gradients of (r, z) using jacobi matrix global_dn = np.dot(jacobian_inverted_t, dn) # Initialize and fill array - b = np.zeros(shape=(4, 2*(dim+1))) - for d in range(dim+1): + b = np.zeros(shape=(4, 2*dim)) + for d in range(dim): b[:, 2*d:2*d+2] = [ [ global_dn[0][d], 0 @@ -321,126 +331,150 @@ def b_operator_global( return b -def integral_m(node_points: npt.NDArray): +def integral_m(node_points: npt.NDArray, element_order: int): """Calculates the M integral. Parameters: node_points: List of node points [[x1, x2, x3], [y1, y2, y3]] of one triangle. + element_order: Order of the shape functions. Returns: npt.NDArray: 3x3 M matrix for the given element. """ def inner(s, t): + dn = gradient_local_shape_functions_2d(s, t, element_order) + jacobian = np.dot(node_points, dn.T) + jacobian_det = np.linalg.det(jacobian) + n = local_shape_functions_2d(s, t) # Since the simulation is axisymmetric it is necessary # to multiply with the radius in the integral # (for the theta component (azimuth)) - r = local_to_global_coordinates(node_points, s, t)[0] + r = local_to_global_coordinates(node_points, s, t, element_order)[0] # Get all combinations of shape function multiplied with each other - return np.outer(n, n)*r + return np.outer(n, n)*r*jacobian_det - return quadratic_quadrature(inner) + return quadratic_quadrature(inner, element_order) def integral_ku( node_points: npt.NDArray, - jacobian_inverted_t: npt.NDArray, - elasticity_matrix: npt.NDArray + elasticity_matrix: npt.NDArray, + element_order: int ): """Calculates the Ku integral Parameters: node_points: List of node points [[x1, x2, x3], [y1, y2, y3]] of one triangle. - jacobian_inverted_t: Jacobian matrix inverted and transposed, needed - for calculation of global derivatives. elasticity_matrix: Elasticity matrix for the current element (c matrix). + element_order: Order of the shape functions. Returns: npt.NDArray: 6x6 Ku matrix for the given element. """ def inner(s, t): + dn = gradient_local_shape_functions_2d(s, t, element_order) + jacobian = np.dot(node_points, dn.T) + jacobian_inverted_t = np.linalg.inv(jacobian).T + jacobian_det = np.linalg.det(jacobian) + b_op = b_operator_global( node_points, jacobian_inverted_t, s, - t + t, + element_order ) - r = local_to_global_coordinates(node_points, s, t)[0] + r = local_to_global_coordinates(node_points, s, t, element_order)[0] - return np.dot(np.dot(b_op.T, elasticity_matrix), b_op)*r + return np.dot(np.dot(b_op.T, elasticity_matrix), b_op)*r*jacobian_det - return quadratic_quadrature(inner) + return quadratic_quadrature(inner, element_order) def integral_kuv( node_points: npt.NDArray, - jacobian_inverted_t: npt.NDArray, - piezo_matrix: npt.NDArray + piezo_matrix: npt.NDArray, + element_order: int ): """Calculates the KuV integral. Parameters: node_points: List of node points [[x1, x2, x3], [y1, y2, y3]] of one triangle. - jacobian_inverted_t: Jacobian matrix inverted and transposed, needed - for calculation of global derivatives. piezo_matrix: Piezo matrix for the current element (e matrix). + element_order: Order of the shape functions. Returns: npt.NDArray: 6x3 KuV matrix for the given element. """ def inner(s, t): + dn = gradient_local_shape_functions_2d(s, t, element_order) + jacobian = np.dot(node_points, dn.T) + jacobian_inverted_t = np.linalg.inv(jacobian).T + jacobian_det = np.linalg.det(jacobian) + b_op = b_operator_global( node_points, jacobian_inverted_t, s, - t + t, + element_order ) - dn = gradient_local_shape_functions_2d() global_dn = np.dot(jacobian_inverted_t, dn) - r = local_to_global_coordinates(node_points, s, t)[0] + r = local_to_global_coordinates(node_points, s, t, element_order)[0] - return np.dot(np.dot(b_op.T, piezo_matrix.T), global_dn)*r + return np.dot(np.dot(b_op.T, piezo_matrix.T), global_dn)*r*jacobian_det - return quadratic_quadrature(inner) + return quadratic_quadrature(inner, element_order) def integral_kve( node_points: npt.NDArray, - jacobian_inverted_t: npt.NDArray, - permittivity_matrix: npt.NDArray + permittivity_matrix: npt.NDArray, + element_order: int ): """Calculates the KVe integral. Parameters: node_points: List of node points [[x1, x2, x3], [y1, y2, y3]] of one triangle. - jacobian_inverted_t: Jacobian matrix inverted and transposed, needed - for calculation of global derivatives. permittivity_matrix: Permittivity matrix for the current element (epsilon matrix). + element_order: Order of the shape functions. Returns: npt.NDArray: 3x3 KVe matrix for the given element. """ def inner(s, t): - dn = gradient_local_shape_functions_2d() + dn = gradient_local_shape_functions_2d(s, t, element_order) + jacobian = np.dot(node_points, dn.T) + jacobian_inverted_t = np.linalg.inv(jacobian).T + jacobian_det = np.linalg.det(jacobian) + global_dn = np.dot(jacobian_inverted_t, dn) - r = local_to_global_coordinates(node_points, s, t)[0] + r = local_to_global_coordinates(node_points, s, t, element_order)[0] - return np.dot(np.dot(global_dn.T, permittivity_matrix), global_dn)*r + return np.dot( + np.dot( + global_dn.T, + permittivity_matrix + ), + global_dn + )*r*jacobian_det - return quadratic_quadrature(inner) + return quadratic_quadrature(inner, element_order) def energy_integral_theta( node_points: npt.NDArray, - theta: npt.NDArray + theta: npt.NDArray, + element_order: int ): """Integrates the given element over the given theta field. @@ -449,17 +483,22 @@ def energy_integral_theta( one triangle. theta: List of the temperature field values of the points [theta1, theta2, theta3]. + element_order: Order of the shape functions. """ def inner(s, t): - n = local_shape_functions_2d(s, t) - r = local_to_global_coordinates(node_points, s, t)[0] + dn = gradient_local_shape_functions_2d(s, t, element_order) + jacobian = np.dot(node_points, dn.T) + jacobian_det = np.linalg.det(jacobian) + + n = local_shape_functions_2d(s, t, element_order) + r = local_to_global_coordinates(node_points, s, t, element_order)[0] - return np.dot(n.T, theta)*r + return np.dot(n.T, theta) * r * jacobian_det - return quadratic_quadrature(inner) + return quadratic_quadrature(inner, element_order) -def integral_volume(node_points: npt.NDArray): +def integral_volume(node_points: npt.NDArray, element_order: int): """Calculates the volume of the triangle given by the node points. HINT: Must be multiplied with 2*np.pi and the jacobian determinant in order to give the correct volume of any rotationsymmetric triangle. @@ -467,46 +506,138 @@ def integral_volume(node_points: npt.NDArray): Parameters: node_points: List of node points [[x1, x2, x3], [y1, y2, y3]] of one triangle. + element_order: Order of the shape functions. Returns: Float. Volume of the triangle. """ def inner(s, t): - r = local_to_global_coordinates(node_points, s, t)[0] - return r + dn = gradient_local_shape_functions_2d(s, t, element_order) + jacobian = np.dot(node_points, dn.T) + jacobian_det = np.linalg.det(jacobian) + + r = local_to_global_coordinates(node_points, s, t, element_order)[0] + + return r*jacobian_det - return quadratic_quadrature(inner) + return quadratic_quadrature(inner, element_order) -def quadratic_quadrature(func: Callable): +def quadratic_quadrature(func: Callable, element_order: int): """Integrates the given function of 2 variables using gaussian quadrature along 2 variables for a reference triangle. This gives exact results for linear shape functions. Parameters: func: Function which will be integrated. + element_order: Order of the shape functions. Returns: Integral of the given function. """ - weight_1 = 1/6 - weight_2 = 2/3 - return func(weight_1, weight_1)*weight_1 + \ - func(weight_2, weight_1)*weight_1 + \ - func(weight_1, weight_2)*weight_1 + #f = np.vectorize(func) + weights = [] + points = [] + + match element_order: + case 1 | 2: + w1 = 1/6 + w2 = 2/3 + weights = np.array([w1, w1, w1]) + points = np.array([ + [w1, w1], + [w2, w1], + [w1, w2] + ]) + case 3 | 4 | 5: + w1 = 155-np.sqrt(15) + w2 = 155+np.sqrt(15) + w3 = 2400 + weights = np.array([ + w1/w3, + w1/w3, + w1/w3, + w2/w3, + w2/w3, + w2/w3, + 9/80 + ]) + p1 = 6-np.sqrt(15) + p2 = 9+2*np.sqrt(15) + p3 = 6+np.sqrt(15) + p4 = 9-2*np.sqrt(15) + p5 = 21 + points = np.array([ + [p1/p5, p1/p5], + [p2/p5, p1/p5], + [p1/p5, p2/p5], + [p3/p5, p3/p5], + [p4/p5, p3/p5], + [p3/p5, p4/p5], + [1/3, 1/3] + ]) + case _: + raise NotImplementedError( + "No quadratic quadrature for element order " + f"{element_order} implemented" + ) + + # TODO + # Instead of a double for loop make a fast calculation using numpy + # The numpy calculations should be simular to + sum = 0 + for i in range(len(weights)): + for j in range(len(weights)): + sum += weights[i]*weights[j]*func(points[i][0], points[j][1]) + return sum + + # F = f(points[:, 0], points[:, 1]) + # return np.dot(np.dot(F, weights).T, weights) -def line_quadrature(func): +def line_quadrature(func: Callable, element_order: int): """Integrates the given function of 2 variables along one variable for a reference triangle. This gives exact results for linear shape functions. Parameters: - func: Function which will be integrated along r-axis + func: Function which will be integrated along r-axis. + element_order: Order of the shape functions. Returns: Integral of the given function along r-axis""" - return 0.5*(func(-1/2/np.sqrt(3)+1/2) + func(1/2/np.sqrt(3)+1/2)) + # f = np.vectorize(func) + weights = [] + points = [] + + match element_order: + case 1 | 2: + weights = np.array([1/2, 1/2]) + points = np.array([ + (3-np.sqrt(3))/6, + (3+np.sqrt(3))/6 + ]) + case 3 | 4 | 5: + weights = np.array([5/18, 8/18, 5/18]) + points = np.array([ + (5-np.sqrt(15))/10, + 1/2, + (5+np.sqrt(15))/5 + ]) + case _: + raise NotImplementedError( + "No linear quadrature for element order " + f"{element_order} implemented" + ) + + # TODO Make use of numpy + # return np.dot(f(points).T, weights) + + sum = 0 + for i in range(len(weights)): + sum += weights[i]*func(points[i]) + + return sum # -------- Boundary condition functions -------- @@ -663,10 +794,11 @@ def create_dirichlet_bc_nodes( [dirichlet_values_u, dirichlet_values_v] -def create_local_element_data( +def create_node_points( nodes: npt.NDArray, - elements: npt.NDArray -) -> List[LocalElementData]: + elements: npt.NDArray, + element_order: int +) -> npt.NDArray: """Create the local node data and the corresponding matrices for every element which are needed in many parts of the simulations. @@ -677,49 +809,45 @@ def create_local_element_data( Returns: List of LocalElementData objects. """ - local_element_data = [] - dn = gradient_local_shape_functions_2d() - for element in elements: + points_per_element = int(1/2*(element_order+1)*(element_order+2)) + + node_points = np.zeros( + shape=(len(elements), 2, points_per_element) + ) + + for element_index, element in enumerate(elements): # Get node points of element in format - # [x1 x2 x3] - # [y1 y2 y3] where (xi, yi) are the coordinates for Node i - node_points = np.array([ - [nodes[element[0]][0], - nodes[element[1]][0], - nodes[element[2]][0]], - [nodes[element[0]][1], - nodes[element[1]][1], - nodes[element[2]][1]] - ]) - jacobian = np.dot(node_points, dn.T) - jacobian_inverted_t = np.linalg.inv(jacobian).T - jacobian_det = np.linalg.det(jacobian) - local_element_data.append(LocalElementData( - node_points, - jacobian, - jacobian_inverted_t, - jacobian_det - )) + # [x1 x2 x3 ... xn] + # [y1 y2 y3 ... yn] where (xi, yi) are the coordinates for Node i + for node_index in range(points_per_element): + node_points[element_index, :, node_index] = [ + nodes[element[node_index]][0], + nodes[element[node_index]][1] + ] - return local_element_data + return node_points -def calculate_volumes(local_element_data: List[LocalElementData]): +def calculate_volumes(node_points: npt.NDArray, element_order): """Calculates the volume of each element. The element information is given by the local_element_data Parameters: - local_element_data: List of LocalElementData objects. + node_points: Node points of all elements for which the volume shall be + calculated. Returns: List of volumes of the elements. """ volumes = [] - for local_element in local_element_data: - node_points = local_element.node_points - jacobian_det = local_element.jacobian_det - volumes.append(integral_volume(node_points) * 2 * np.pi * jacobian_det) + number_of_elements = node_points.shape[0] + + for element_index in range(number_of_elements): + volumes.append( + integral_volume(node_points[element_index], element_order) + * 2 * np.pi + ) return volumes diff --git a/plutho/simulation/nonlinear/base.py b/plutho/simulation/nonlinear/base.py index e17c8a6..1516793 100644 --- a/plutho/simulation/nonlinear/base.py +++ b/plutho/simulation/nonlinear/base.py @@ -12,7 +12,7 @@ # Local libraries from ..base import MeshData, local_to_global_coordinates, b_operator_global, \ integral_m, integral_ku, integral_kuv, integral_kve, \ - create_local_element_data, quadratic_quadrature + create_node_points, quadratic_quadrature, gradient_local_shape_functions_2d from plutho.materials import MaterialManager @@ -78,7 +78,8 @@ def assemble( # Maybe the 2x2 matrix slicing is not very fast nodes = mesh_data.nodes elements = mesh_data.elements - local_elements = create_local_element_data(nodes, elements) + element_order = mesh_data.element_order + node_points = create_node_points(nodes, elements, element_order) number_of_nodes = len(nodes) mu = sparse.lil_matrix( @@ -103,11 +104,7 @@ def assemble( ) for element_index, element in enumerate(elements): - # Get local element nodes and matrices - local_element = local_elements[element_index] - node_points = local_element.node_points - jacobian_inverted_t = local_element.jacobian_inverted_t - jacobian_det = local_element.jacobian_det + current_node_points = node_points[element_index] # TODO Check if its necessary to calculate all integrals # --> Dirichlet nodes could be leaved out? @@ -116,42 +113,39 @@ def assemble( # Mutiply with 2*pi because theta is integrated from 0 to 2*pi mu_e = ( material_manager.get_density(element_index) - * integral_m(node_points) - * jacobian_det * 2 * np.pi + * integral_m(current_node_points, element_order) + * 2 * np.pi ) ku_e = ( integral_ku( - node_points, - jacobian_inverted_t, - material_manager.get_elasticity_matrix(element_index) - ) - * jacobian_det * 2 * np.pi + current_node_points, + material_manager.get_elasticity_matrix(element_index), + element_order + ) * 2 * np.pi ) kuv_e = ( integral_kuv( - node_points, - jacobian_inverted_t, - material_manager.get_piezo_matrix(element_index) - ) - * jacobian_det * 2 * np.pi + current_node_points, + material_manager.get_piezo_matrix(element_index), + element_order + ) * 2 * np.pi ) kve_e = ( integral_kve( - node_points, - jacobian_inverted_t, + current_node_points, material_manager.get_permittivity_matrix( element_index - ) - ) - * jacobian_det * 2 * np.pi + ), + element_order + ) * 2 * np.pi ) if nonlinear_type is NonlinearType.Custom: lu_e = ( integral_u_nonlinear( - node_points, - jacobian_inverted_t, - nonlinear_matrix - ) * jacobian_det * 2 * np.pi + current_node_points, + nonlinear_matrix, + element_order + ) * 2 * np.pi ) # Now assemble all element matrices @@ -227,8 +221,8 @@ def assemble( def integral_u_nonlinear( node_points: npt.NDArray, - jacobian_inverted_t: npt.NDArray, - nonlinear_elasticity_matrix: npt.NDArray + nonlinear_elasticity_matrix: npt.NDArray, + element_order: int ): """Calculates the Ku integral @@ -244,13 +238,26 @@ def integral_u_nonlinear( npt.NDArray: 6x6 Ku matrix for the given element. """ def inner(s, t): + dn = gradient_local_shape_functions_2d(s, t, element_order) + jacobian = np.dot(node_points, dn.T) + jacobian_inverted_t = np.linalg.inv(jacobian).T + jacobian_det = np.linalg.det(jacobian) + b_op = b_operator_global( - node_points, jacobian_inverted_t, + node_points, + jacobian_inverted_t, s, t, + element_order ) - r = local_to_global_coordinates(node_points, s, t)[0] + r = local_to_global_coordinates(node_points, s, t, element_order)[0] - return 1/2*np.dot(np.dot(b_op.T, nonlinear_elasticity_matrix), b_op)*r + return np.dot( + np.dot( + b_op.T, + nonlinear_elasticity_matrix + ), + b_op + ) * 1/2 * r * jacobian_det - return quadratic_quadrature(inner) + return quadratic_quadrature(inner, element_order) diff --git a/plutho/simulation/nonlinear/piezo_stationary.py b/plutho/simulation/nonlinear/piezo_stationary.py index cc48774..1321636 100644 --- a/plutho/simulation/nonlinear/piezo_stationary.py +++ b/plutho/simulation/nonlinear/piezo_stationary.py @@ -3,7 +3,7 @@ """ # Python standard libraries -from typing import List, Union, Tuple +from typing import Union, Tuple # Third party libraries import numpy as np @@ -14,7 +14,7 @@ # Local libraries from .base import assemble, NonlinearType -from ..base import MeshData, LocalElementData +from ..base import MeshData from plutho.materials import MaterialManager @@ -38,9 +38,6 @@ class NonlinearPiezoSimStationary: k: sparse.lil_array ln: sparse.lil_array - # Internal simulation data - local_elements: List[LocalElementData] - # Resulting fields u: npt.NDArray @@ -86,7 +83,7 @@ def solve_linear(self): # Calculate u using phi as load vector self.u = linalg.spsolve( - k.tocsc(), + k, f ) @@ -244,10 +241,10 @@ def calculate_tangent_matrix_hadamard( @staticmethod def apply_dirichlet_bc( - k: sparse.sparray, - ln: sparse.sparray, + k: sparse.lil_array, + ln: sparse.lil_array, nodes: npt.NDArray - ) -> Tuple[sparse.sparray, sparse.sparray]: + ) -> Tuple[sparse.csc_array, sparse.csc_array]: """Sets dirichlet boundary condition for the given matrix. Parameters: @@ -264,4 +261,4 @@ def apply_dirichlet_bc( k[node, node] = 1 - return k, ln + return k.tocsc(), ln.tocsc() diff --git a/plutho/simulation/nonlinear/piezo_time.py b/plutho/simulation/nonlinear/piezo_time.py index 409072a..e614b32 100644 --- a/plutho/simulation/nonlinear/piezo_time.py +++ b/plutho/simulation/nonlinear/piezo_time.py @@ -1,7 +1,7 @@ """Module for the simulation of nonlinear piezoelectric systems""" # Third party libraries -from typing import List +from typing import Tuple import numpy as np import numpy.typing as npt import scipy @@ -10,10 +10,8 @@ # Local libraries from .base import assemble, NonlinearType -from ..base import SimulationData, MeshData, gradient_local_shape_functions_2d, \ - LocalElementData -from plutho.simulation.piezo_time import charge_integral_u, \ - charge_integral_v +from ..base import SimulationData, MeshData +from plutho.simulation.piezo_time import calculate_charge from plutho.materials import MaterialManager @@ -42,7 +40,6 @@ class NonlinearPiezoSimTime: mechanical field. u: Mechanical field vector u(element, t). q: Charge q(t). - local_elements: List of element data for the local triangles. Parameters: mesh_data: MeshData object containing the mesh. @@ -65,9 +62,6 @@ class NonlinearPiezoSimTime: u: npt.NDArray q: npt.NDArray - # Internal simulation data - local_elements: List[LocalElementData] - # FEM matrices m: sparse.lil_array c: sparse.lil_array @@ -264,13 +258,13 @@ def residual(next_u, current_u, v, a, f): # Calculate charge if (electrode_elements is not None and electrode_elements.shape[0] > 0): - q[time_index] = NonlinearPiezoSimTime.calculate_charge( - u[:2*number_of_nodes, time_index+1], - u[2*number_of_nodes:, time_index+1], + q[time_index] = calculate_charge( + u[:, time_index+1], self.material_manager, electrode_elements, electrode_normals, - self.mesh_data.nodes + self.mesh_data.nodes, + self.mesh_data.element_order ) self.u = u @@ -303,86 +297,6 @@ def get_load_vector( return f - @staticmethod - def calculate_charge( - u: npt.NDArray, - phi: npt.NDArray, - material_manager: MaterialManager, - electrode_elements: npt.NDArray, - element_normals: npt.NDArray, - nodes: npt.NDArray - ) -> float: - """Calculates the charge using u and phi on the given electrode - elements. - - Parameters: - u: Mechanical displacement field. - phi: Electrical potential field. - material_manager: Contains the material data. - electrode_elements: Indices of the element on which the charge - is calculated. - nodes: All nodes from the mesh. - - Returns: - The charge on the given elements summed up.""" - q = 0 - - for element_index, element in enumerate(electrode_elements): - dn = gradient_local_shape_functions_2d() - node_points = np.array([ - [nodes[element[0]][0], - nodes[element[1]][0], - nodes[element[2]][0]], - [nodes[element[0]][1], - nodes[element[1]][1], - nodes[element[2]][1]] - ]) - - jacobian = np.dot(node_points, dn.T) - jacobian_inverted_t = np.linalg.inv(jacobian).T - jacobian_det = np.sqrt( - np.square(nodes[element[1]][0]-nodes[element[0]][0]) - + np.square(nodes[element[1]][1]-nodes[element[0]][1]) - ) - - u_e = np.array([ - u[2*element[0]], - u[2*element[0]+1], - u[2*element[1]], - u[2*element[1]+1], - u[2*element[2]], - u[2*element[2]+1] - ]) - phi_e = np.array([ - phi[element[0]], - phi[element[1]], - phi[element[2]] - ]) - - q_u = charge_integral_u( - node_points, - u_e, - material_manager.get_piezo_matrix(element_index), - jacobian_inverted_t - ) * 2 * np.pi * jacobian_det - q_v = charge_integral_v( - node_points, - phi_e, - material_manager.get_permittivity_matrix(element_index), - jacobian_inverted_t - ) * 2 * np.pi * jacobian_det - - # Now take the component normal to the line (outer direction) - q_u_normal = np.dot(q_u, element_normals[element_index]) - q_v_normal = np.dot(q_v, element_normals[element_index]) - - q += q_u_normal - q_v_normal - - if np.isnan(q): - print("Calculated charge is nan") - - return q - @staticmethod def apply_dirichlet_bc( m: sparse.lil_array, @@ -390,7 +304,12 @@ def apply_dirichlet_bc( k: sparse.lil_array, ln: sparse.lil_array, nodes: npt.NDArray - ): + ) -> Tuple[ + sparse.csc_array, + sparse.csc_array, + sparse.csc_array, + sparse.csc_array + ]: # TODO Parameters are not really ndarrays # Set rows of matrices to 0 and diagonal of K to 1 (at node points) diff --git a/plutho/simulation/piezo_freq.py b/plutho/simulation/piezo_freq.py index 96d0c31..24f17f3 100644 --- a/plutho/simulation/piezo_freq.py +++ b/plutho/simulation/piezo_freq.py @@ -10,8 +10,8 @@ # Local libraries from .base import MeshData, local_to_global_coordinates, b_operator_global, \ integral_m, integral_ku, integral_kuv, integral_kve, apply_dirichlet_bc, \ - create_local_element_data, LocalElementData, quadratic_quadrature, \ - calculate_volumes + create_node_points, quadratic_quadrature, \ + calculate_volumes, gradient_local_shape_functions_2d from .piezo_time import calculate_charge from ..materials import MaterialManager @@ -19,8 +19,8 @@ def loss_integral_scs( node_points: npt.NDArray, u_e: npt.NDArray, - jacobian_inverted_t: npt.NDArray, - elasticity_matrix: npt.NDArray + elasticity_matrix: npt.NDArray, + element_order: int ): """Calculate sthe integral of dS/dt*c*dS/dt over one triangle in the frequency domain for the given frequency. @@ -31,25 +31,37 @@ def loss_integral_scs( u_e: Displacement at this element [u1_r, u1_z, u_2r, u_2z, u_3r, u_3z]. angular_frequency: Angular frequency at which the loss shall be calculated. - jacobian_inverted_t: Jacobian matrix inverted and transposed, needed - for calculation of global derivates. - elasticity_matrix: Elasticity matrix for the current element (c matrix) + elasticity_matrix: Elasticity matrix for the current element + (c matrix). + element_order: Order of the shape functions. """ def inner(s, t): - r = local_to_global_coordinates(node_points, s, t)[0] + dn = gradient_local_shape_functions_2d(s, t, element_order) + jacobian = np.dot(node_points, dn.T) + jacobian_inverted_t = np.linalg.inv(jacobian).T + jacobian_det = np.linalg.det(jacobian) + b_opt = b_operator_global( node_points, jacobian_inverted_t, s, - t + t, + element_order ) + r = local_to_global_coordinates(node_points, s, t, element_order)[0] s_e = np.dot(b_opt, u_e) # return np.dot(dt_s.T, np.dot(elasticity_matrix.T, dt_s))*r - return np.dot(np.conjugate(s_e).T, np.dot(elasticity_matrix.T, s_e))*r + return np.dot( + np.conjugate(s_e).T, + np.dot( + elasticity_matrix.T, + s_e + ) + ) * r * jacobian_det - return quadratic_quadrature(inner) + return quadratic_quadrature(inner, element_order) class PiezoSimFreq: @@ -99,7 +111,7 @@ class PiezoSimFreq: mech_loss: npt.NDArray # Internal simulation data - local_elements: List[LocalElementData] + node_points: npt.NDArray def __init__( self, @@ -120,7 +132,8 @@ def assemble(self): # Maybe the 2x2 matrix slicing is not very fast nodes = self.mesh_data.nodes elements = self.mesh_data.elements - self.local_elements = create_local_element_data(nodes, elements) + element_order = self.mesh_data.element_order + self.node_points = create_node_points(nodes, elements, element_order) number_of_nodes = len(nodes) mu = sparse.lil_matrix( @@ -141,11 +154,7 @@ def assemble(self): ) for element_index, element in enumerate(self.mesh_data.elements): - # Get local element nodes and matrices - local_element = self.local_elements[element_index] - node_points = local_element.node_points - jacobian_inverted_t = local_element.jacobian_inverted_t - jacobian_det = local_element.jacobian_det + node_points = self.node_points[element_index] # TODO Check if its necessary to calculate all integrals # --> Dirichlet nodes could be leaved out? @@ -154,34 +163,34 @@ def assemble(self): # Mutiply with 2*pi because theta is integrated from 0 to 2*pi mu_e = ( self.material_manager.get_density(element_index) - * integral_m(node_points) - * jacobian_det * 2 * np.pi + * integral_m(node_points, element_order) + * 2 * np.pi ) ku_e = ( integral_ku( node_points, - jacobian_inverted_t, - self.material_manager.get_elasticity_matrix(element_index) + self.material_manager.get_elasticity_matrix(element_index), + element_order ) - * jacobian_det * 2 * np.pi + * 2 * np.pi ) kuv_e = ( integral_kuv( node_points, - jacobian_inverted_t, - self.material_manager.get_piezo_matrix(element_index) + self.material_manager.get_piezo_matrix(element_index), + element_order ) - * jacobian_det * 2 * np.pi + * 2 * np.pi ) kve_e = ( integral_kve( node_points, - jacobian_inverted_t, self.material_manager.get_permittivity_matrix( element_index - ) + ), + element_order ) - * jacobian_det * 2 * np.pi + * 2 * np.pi ) # Now assemble all element matrices @@ -259,6 +268,9 @@ def solve_frequency( frequencies = self.frequencies number_of_nodes = len(self.mesh_data.nodes) number_of_elements = len(self.mesh_data.elements) + element_order = self.mesh_data.element_order + points_per_element = int(1/2*(element_order+1)*(element_order+2)) + u = np.zeros( (3*number_of_nodes, len(frequencies)), dtype=np.complex128 @@ -276,7 +288,10 @@ def solve_frequency( self.dirichlet_nodes ) - volumes = calculate_volumes(self.local_elements) + volumes = calculate_volumes( + self.node_points, + self.mesh_data.element_order + ) print( f"Starting frequency simulation. There are {len(frequencies)} " @@ -305,37 +320,32 @@ def solve_frequency( self.material_manager, electrode_elements, electrode_normals, - self.mesh_data.nodes + self.mesh_data.nodes, + self.mesh_data.element_order, + True ) # Calculate mech_loss for every element for element_index, element in enumerate(self.mesh_data.elements): - # Get local element data - local_element = self.local_elements[element_index] - node_points = local_element.node_points - jacobian_inverted_t = local_element.jacobian_inverted_t - jacobian_det = local_element.jacobian_det + node_points = self.node_points[element_index] # Get field values - u_e = np.array([ - u[2*element[0], frequency_index], - u[2*element[0]+1, frequency_index], - u[2*element[1], frequency_index], - u[2*element[1]+1, frequency_index], - u[2*element[2], frequency_index], - u[2*element[2]+1, frequency_index]]) + u_e = np.zeros(2*points_per_element, dtype=np.complex128) + for i in range(points_per_element): + u_e[2*i] = u[2*element[i], frequency_index] + u_e[2*i+1] = u[2*element[i]+1, frequency_index] if calculate_mech_loss: mech_loss[element_index, frequency_index] = ( loss_integral_scs( node_points, u_e, - jacobian_inverted_t, self.material_manager.get_elasticity_matrix( element_index - ) + ), + element_order ) - * 2 * np.pi * jacobian_det + * 2 * np.pi * self.material_manager.get_alpha_k(element_index) * 1/volumes[element_index] # TODO Actually this must be multiplied with -1 diff --git a/plutho/simulation/piezo_time.py b/plutho/simulation/piezo_time.py index 25bad72..f9ca9d5 100644 --- a/plutho/simulation/piezo_time.py +++ b/plutho/simulation/piezo_time.py @@ -12,7 +12,7 @@ gradient_local_shape_functions_2d, \ local_to_global_coordinates, b_operator_global, integral_m, \ integral_ku, integral_kuv, integral_kve, apply_dirichlet_bc, \ - line_quadrature, create_local_element_data, LocalElementData + line_quadrature, create_node_points from ..materials import MaterialManager @@ -20,7 +20,7 @@ def charge_integral_u( node_points: npt.NDArray, u_e: npt.NDArray, piezo_matrix: npt.NDArray, - jacobian_inverted_t: npt.NDArray + element_order: int ): """Calculates the integral of eBu of the given element. @@ -30,31 +30,35 @@ def charge_integral_u( u_e: List of u values at the nodes of the triangle [u1_r, u1_z, u2_r, u2_z]. piezo_matrix: Piezo matrix for the current element (e matrix). - jacobian_inverted_t: Jacobian matrix inverted and transposed, needed - for calculation of global derivatives. + element_order: Order of the shape functions. Returns: Float: Integral of eBu of the current triangle. """ def inner(s): - r = local_to_global_coordinates(node_points, s, 0)[0] + dn = gradient_local_shape_functions_2d(s, 0, element_order) + jacobian = np.dot(node_points, dn.T) + jacobian_inverted_t = np.linalg.inv(jacobian).T + b_opt_global = b_operator_global( node_points, jacobian_inverted_t, s, - 0 + 0, + element_order ) + r = local_to_global_coordinates(node_points, s, 0, element_order)[0] return -np.dot(np.dot(piezo_matrix, b_opt_global), u_e)*r - return line_quadrature(inner) + return line_quadrature(inner, element_order) def charge_integral_v( node_points: npt.NDArray, v_e: npt.NDArray, permittivity_matrix: npt.NDArray, - jacobian_inverted_t: npt.NDArray + element_order: int ): """Calculates the integral of epsilonBVe of the given element. @@ -65,20 +69,22 @@ def charge_integral_v( [v1, v2]. permittivity_matrix: Permittivity matrix for the current element (e matrix). - jacobian_inverted_t: Jacobian matrix inverted and transposed, needed - for calculation of global derivatives. + element_order: Order of the shape functions. Returns: Float: Integral of epsilonBVe of the current triangle. """ def inner(s): - r = local_to_global_coordinates(node_points, s, 0)[0] - dn = gradient_local_shape_functions_2d() + dn = gradient_local_shape_functions_2d(s, 0, element_order) + jacobian = np.dot(node_points, dn.T) + jacobian_inverted_t = np.linalg.inv(jacobian).T + global_dn = np.dot(jacobian_inverted_t, dn) + r = local_to_global_coordinates(node_points, s, 0, element_order)[0] return -np.dot(np.dot(permittivity_matrix, global_dn), v_e)*r - return line_quadrature(inner) + return line_quadrature(inner, element_order) def calculate_charge( @@ -86,7 +92,9 @@ def calculate_charge( material_manager: MaterialManager, elements: npt.NDArray, element_normals: npt.NDArray, - nodes + nodes: npt.NDArray, + element_order: int, + is_complex: bool = False ) -> float: """Calculates the charge of the given elements. @@ -97,28 +105,23 @@ def calculate_charge( element_normals: List of element normal vectors (index corresponding to the elements list). nodes: All nodes used in the simulation. + element_order: Order of the shape functions. """ + points_per_element = int(1/2*(element_order+1)*(element_order+2)) number_of_nodes = len(nodes) q = 0 for element_index, element in enumerate(elements): - dn = gradient_local_shape_functions_2d() - node_points = np.array([ - [nodes[element[0]][0], - nodes[element[1]][0], - nodes[element[2]][0]], - [nodes[element[0]][1], - nodes[element[1]][1], - nodes[element[2]][1]], - ]) - # This calculation of the jacobian does only work for triangle elements - # and not for line elements, since for line elements the resulting - # jacobian is non invertible. - jacobian = np.dot(node_points, dn.T) - jacobian_inverted_t = np.linalg.inv(jacobian).T + node_points = np.zeros(shape=(2, points_per_element)) + for node_index in range(points_per_element): + node_points[:, node_index] = [ + nodes[element[node_index]][0], + nodes[element[node_index]][1] + ] # The integration is always done along the first two points of the # triangle. + # TODO Not true for element_order > 1 jacobian_det = np.sqrt( np.square(nodes[element[1]][0]-nodes[element[0]][0]) + np.square(nodes[element[1]][1]-nodes[element[0]][1]) @@ -129,31 +132,29 @@ def calculate_charge( # This is due to the charge calculation needing the derivatives of the # shape function (dN_i/dr and dN_i/dz) which are (+/-) 1 along the # whole triangle for all 3 points. - u_e = np.array([ - u[2*element[0]], - u[2*element[0]+1], - u[2*element[1]], - u[2*element[1]+1], - u[2*element[2]], - u[2*element[2]+1] - ]) - ve_e = np.array([ - u[element[0]+2*number_of_nodes], - u[element[1]+2*number_of_nodes], - u[element[2]+2*number_of_nodes] - ]) + # TODO Is this true? + if is_complex: + u_e = np.zeros(2*points_per_element, dtype=np.complex128) + ve_e = np.zeros(points_per_element, dtype=np.complex128) + else: + u_e = np.zeros(2*points_per_element) + ve_e = np.zeros(points_per_element) + for i in range(points_per_element): + u_e[2*i] = u[2*element[i]] + u_e[2*i+1] = u[2*element[i]+1] + ve_e[i] = u[element[i]+2*number_of_nodes] q_u = charge_integral_u( node_points, u_e, material_manager.get_piezo_matrix(element_index), - jacobian_inverted_t + element_order ) * 2 * np.pi * jacobian_det q_v = charge_integral_v( node_points, ve_e, material_manager.get_permittivity_matrix(element_index), - jacobian_inverted_t + element_order ) * 2 * np.pi * jacobian_det # Now take the component normal to the line (outer direction) @@ -211,7 +212,7 @@ class PiezoSimTime: q: npt.NDArray # Internal simulation data - local_elements: List[LocalElementData] + node_points: npt.NDArray def __init__( self, @@ -232,7 +233,8 @@ def assemble(self): # Maybe the 2x2 matrix slicing is not very fast nodes = self.mesh_data.nodes elements = self.mesh_data.elements - self.local_elements = create_local_element_data(nodes, elements) + element_order = self.mesh_data.element_order + self.node_points = create_node_points(nodes, elements, element_order) number_of_nodes = len(nodes) mu = sparse.lil_matrix( @@ -253,11 +255,7 @@ def assemble(self): ) for element_index, element in enumerate(self.mesh_data.elements): - # Get local element nodes and matrices - local_element = self.local_elements[element_index] - node_points = local_element.node_points - jacobian_inverted_t = local_element.jacobian_inverted_t - jacobian_det = local_element.jacobian_det + node_points = self.node_points[element_index] # TODO Check if its necessary to calculate all integrals # --> Dirichlet nodes could be leaved out? @@ -266,34 +264,34 @@ def assemble(self): # Mutiply with 2*pi because theta is integrated from 0 to 2*pi mu_e = ( self.material_manager.get_density(element_index) - * integral_m(node_points) - * jacobian_det * 2 * np.pi + * integral_m(node_points, element_order) + * 2 * np.pi ) ku_e = ( integral_ku( node_points, - jacobian_inverted_t, - self.material_manager.get_elasticity_matrix(element_index) + self.material_manager.get_elasticity_matrix(element_index), + element_order ) - * jacobian_det * 2 * np.pi + * 2 * np.pi ) kuv_e = ( integral_kuv( node_points, - jacobian_inverted_t, - self.material_manager.get_piezo_matrix(element_index) + self.material_manager.get_piezo_matrix(element_index), + element_order ) - * jacobian_det * 2 * np.pi + * 2 * np.pi ) kve_e = ( integral_kve( node_points, - jacobian_inverted_t, self.material_manager.get_permittivity_matrix( element_index - ) + ), + element_order ) - * jacobian_det * 2 * np.pi + * 2 * np.pi ) # Now assemble all element matrices @@ -443,7 +441,8 @@ def solve_time( self.material_manager, electrode_elements, electrode_normals, - self.mesh_data.nodes + self.mesh_data.nodes, + self.mesh_data.element_order ) if (time_index + 1) % 100 == 0: diff --git a/plutho/simulation/thermo_piezo_time.py b/plutho/simulation/thermo_piezo_time.py index e07e22f..551d72a 100644 --- a/plutho/simulation/thermo_piezo_time.py +++ b/plutho/simulation/thermo_piezo_time.py @@ -10,11 +10,11 @@ # Local libraries from .base import SimulationData, MeshData, \ - gradient_local_shape_functions_2d, create_local_element_data, \ + create_node_points, \ local_to_global_coordinates, b_operator_global, integral_m, \ integral_ku, integral_kuv, integral_kve, apply_dirichlet_bc, \ - quadratic_quadrature, LocalElementData, calculate_volumes, \ - get_avg_temp_field_per_element + quadratic_quadrature, calculate_volumes, \ + gradient_local_shape_functions_2d, get_avg_temp_field_per_element from .piezo_time import calculate_charge from .thermo_time import integral_ktheta, integral_theta_load from ..materials import MaterialManager @@ -26,8 +26,8 @@ def loss_integral_scs( u_e_t_minus_1: npt.NDArray, u_e_t_minus_2: npt.NDArray, delta_t: float, - jacobian_inverted_t: npt.NDArray, - elasticity_matrix: npt.NDArray + elasticity_matrix: npt.NDArray, + element_order: int ): """Calculates the integral of dS/dt*c*dS/dt over one triangle. Since foward difference quotient of second oder is used the last 2 values of e_u are @@ -47,14 +47,21 @@ def loss_integral_scs( for calculation of global derivatives. elasticity_matrix: Elasticity matrix for the current element (c matrix). + element_order: Order of the shape functions. """ def inner(s, t): - r = local_to_global_coordinates(node_points, s, t)[0] + dn = gradient_local_shape_functions_2d(s, t, element_order) + jacobian = np.dot(node_points, dn.T) + jacobian_inverted_t = np.linalg.inv(jacobian).T + jacobian_det = np.linalg.det(jacobian) + + r = local_to_global_coordinates(node_points, s, t, element_order)[0] b_opt = b_operator_global( node_points, jacobian_inverted_t, s, - t + t, + element_order ) s_e = np.dot(b_opt, u_e_t) @@ -62,9 +69,9 @@ def inner(s, t): s_e_t_minus_2 = np.dot(b_opt, u_e_t_minus_2) dt_s = (3*s_e-4*s_e_t_minus_1+s_e_t_minus_2)/(2*delta_t) - return np.dot(dt_s.T, np.dot(elasticity_matrix.T, dt_s))*r + return np.dot(dt_s.T, np.dot(elasticity_matrix.T, dt_s))*r*jacobian_det - return quadratic_quadrature(inner) + return quadratic_quadrature(inner, element_order) class ThermoPiezoSimTime: @@ -108,9 +115,9 @@ class ThermoPiezoSimTime: dirichlet_values: npt.NDArray # FEM matrices - m: npt.NDArray - c: npt.NDArray - k: npt.NDArray + m: sparse.lil_array + c: sparse.lil_array + k: sparse.lil_array # Resulting fields u: npt.NDArray @@ -118,7 +125,7 @@ class ThermoPiezoSimTime: mech_loss: npt.NDArray # Internal simulation data - local_elements: List[LocalElementData] + node_points: npt.NDArray def __init__( self, @@ -139,7 +146,8 @@ def assemble(self): # Maybe the 2x2 matrix slicing is not very fast nodes = self.mesh_data.nodes elements = self.mesh_data.elements - self.local_elements = create_local_element_data(nodes, elements) + element_order = self.mesh_data.element_order + self.node_points = create_node_points(nodes, elements, element_order) number_of_nodes = len(nodes) mu = sparse.lil_matrix( @@ -162,11 +170,7 @@ def assemble(self): dtype=np.float64) for element_index, element in enumerate(self.mesh_data.elements): - # Get local element nodes and matrices - local_element = self.local_elements[element_index] - node_points = local_element.node_points - jacobian_inverted_t = local_element.jacobian_inverted_t - jacobian_det = local_element.jacobian_det + node_points = self.node_points[element_index] # TODO Check if its necessary to calculate all integrals # --> Dirichlet nodes could be leaved out? @@ -175,45 +179,45 @@ def assemble(self): # Mutiply with 2*pi because theta is integrated from 0 to 2*pi mu_e = ( self.material_manager.get_density(element_index) - * integral_m(node_points) - * jacobian_det * 2 * np.pi + * integral_m(node_points, element_order) + * 2 * np.pi ) ku_e = ( integral_ku( node_points, - jacobian_inverted_t, - self.material_manager.get_elasticity_matrix(element_index)) - * jacobian_det * 2 * np.pi + self.material_manager.get_elasticity_matrix(element_index), + element_order + ) + * 2 * np.pi ) kuv_e = ( integral_kuv( node_points, - jacobian_inverted_t, - self.material_manager.get_piezo_matrix(element_index)) - * jacobian_det * 2 * np.pi + self.material_manager.get_piezo_matrix(element_index), + element_order + ) + * 2 * np.pi ) kve_e = ( integral_kve( node_points, - jacobian_inverted_t, self.material_manager.get_permittivity_matrix( element_index - ) + ), + element_order ) - * jacobian_det * 2 * np.pi + * 2 * np.pi ) ctheta_e = ( - integral_m(node_points) + integral_m(node_points, element_order) * self.material_manager.get_density(element_index) * self.material_manager.get_heat_capacity(element_index) - * jacobian_det * 2 * np.pi + * 2 * np.pi ) ktheta_e = ( - integral_ktheta( - node_points, - jacobian_inverted_t) + integral_ktheta(node_points, element_order) * self.material_manager.get_thermal_conductivity(element_index) - * jacobian_det * 2 * np.pi + * 2 * np.pi ) # Now assemble all element matrices @@ -303,6 +307,8 @@ def solve_time( delta_t = self.simulation_data.delta_t elements = self.mesh_data.elements nodes = self.mesh_data.nodes + element_order = self.mesh_data.element_order + points_per_element = int(1/2*(element_order+1)*(element_order+2)) number_of_elements = len(elements) number_of_nodes = len(nodes) @@ -336,7 +342,10 @@ def solve_time( dtype=np.float64 ) - volumes = calculate_volumes(self.local_elements) + volumes = calculate_volumes( + self.node_points, + self.mesh_data.element_order + ) if theta_start is not None: if len(theta_start) != number_of_nodes: @@ -410,39 +419,25 @@ def solve_time( self.material_manager, electrode_elements, electrode_normals, - nodes + nodes, + element_order ) # Calculate power_loss for element_index, element in enumerate(elements): - # Get local element data - local_element = self.local_elements[element_index] - node_points = local_element.node_points - jacobian_inverted_t = local_element.jacobian_inverted_t - jacobian_det = local_element.jacobian_det - - # Get field values at current element at time index - u_e = np.array([ - u[2*element[0], time_index+1], - u[2*element[0]+1, time_index+1], - u[2*element[1], time_index+1], - u[2*element[1]+1, time_index+1], - u[2*element[2], time_index+1], - u[2*element[2]+1, time_index+1]]) - u_e_t_minus_1 = np.array([ - u[2*element[0], time_index], - u[2*element[0]+1, time_index], - u[2*element[1], time_index], - u[2*element[1]+1, time_index], - u[2*element[2], time_index], - u[2*element[2]+1, time_index]]) - u_e_t_minus_2 = np.array([ - u[2*element[0], time_index-1], - u[2*element[0]+1, time_index-1], - u[2*element[1], time_index-1], - u[2*element[1]+1, time_index-1], - u[2*element[2], time_index-1], - u[2*element[2]+1, time_index-1]]) + node_points = self.node_points[element_index] + + # Get field values at current element at specific time index + u_e = np.zeros(2*points_per_element) + u_e_t_minus_1 = np.zeros(2*points_per_element) + u_e_t_minus_2 = np.zeros(2*points_per_element) + for i in range(points_per_element): + u_e[2*i] = u[2*element[i], time_index+1] + u_e[2*i+1] = u[2*element[i]+1, time_index+1] + u_e_t_minus_1[2*i] = u[2*element[i], time_index] + u_e_t_minus_1[2*i+1] = u[2*element[i]+1, time_index] + u_e_t_minus_2[2*i] = u[2*element[i], time_index-1] + u_e_t_minus_2[2*i+1] = u[2*element[i]+1, time_index-1] # The mech loss of the element is divided by the volume # because it must be a power density. @@ -454,12 +449,12 @@ def solve_time( u_e_t_minus_1, u_e_t_minus_2, delta_t, - jacobian_inverted_t, self.material_manager.get_elasticity_matrix( element_index - ) + ), + element_order ) - * 2 * np.pi * jacobian_det + * 2 * np.pi * self.material_manager.get_alpha_k(element_index) * 1/volumes[element_index] ) @@ -496,6 +491,8 @@ def get_load_vector( # For the temperature field the load vector represents the temperature # sources given by the mechanical losses. nodes = self.mesh_data.nodes + element_order = self.mesh_data.element_order + points_per_element = int(1/2*(element_order+1)*(element_order+2)) number_of_nodes = len(nodes) # Can be initialized to 0 because external load and volume charge @@ -512,21 +509,18 @@ def get_load_vector( f_theta = np.zeros(number_of_nodes) for element_index, element in enumerate(self.mesh_data.elements): - dn = gradient_local_shape_functions_2d() - node_points = np.array([ - [nodes[element[0]][0], - nodes[element[1]][0], - nodes[element[2]][0]], - [nodes[element[0]][1], - nodes[element[1]][1], - nodes[element[2]][1]] - ]) - jacobian = np.dot(node_points, dn.T) - jacobian_det = np.linalg.det(jacobian) + node_points = np.zeros(shape=(2, points_per_element)) + for node_index in range(points_per_element): + node_points[element_index, :, node_index] = [ + nodes[element[node_index]][0], + nodes[element[node_index]][1] + ] f_theta_e = integral_theta_load( node_points, - mech_loss_density[element_index])*2*np.pi*jacobian_det + mech_loss_density[element_index], + element_order + ) * 2 * np.pi for local_p, global_p in enumerate(element): f_theta[global_p] += f_theta_e[local_p] diff --git a/plutho/simulation/thermo_time.py b/plutho/simulation/thermo_time.py index e40b6c3..e2d888a 100644 --- a/plutho/simulation/thermo_time.py +++ b/plutho/simulation/thermo_time.py @@ -10,78 +10,97 @@ from scipy import sparse # Local libraries -from .base import SimulationData, MeshData, local_shape_functions_1d, \ +from .base import SimulationData, MeshData, \ local_shape_functions_2d, gradient_local_shape_functions_2d, \ - local_to_global_coordinates, integral_m, LocalElementData, \ - quadratic_quadrature, line_quadrature, create_local_element_data, \ + local_to_global_coordinates, integral_m, \ + quadratic_quadrature, line_quadrature, create_node_points, \ apply_dirichlet_bc, get_avg_temp_field_per_element from ..materials import MaterialManager def integral_heat_flux( node_points: npt.NDArray, - heat_flux: npt.NDArray + heat_flux: npt.NDArray, + element_order: int ): """Integrates the heat flux using the shape functions. Parameters: - node_points: List of node points [[x1, x2, ..], [y1, y2, ..]] - heat_flux: Heat flux at the points + node_points: List of node points [[x1, x2, ..], [y1, y2, ..]]. + heat_flux: Heat flux at the points. + element_order: Order of the shape functions. Returns: - npt.NDArray heat flux integral on each point.""" + npt.NDArray heat flux integral on each point. + """ def inner(s): - n = local_shape_functions_1d(s) - r = local_to_global_coordinates(node_points, s)[0] + dn = gradient_local_shape_functions_2d(s, 0, element_order) + jacobian = np.dot(node_points, dn.T) + jacobian_det = np.linalg.det(jacobian) + + n = local_shape_functions_2d(s, 0, element_order) + r = local_to_global_coordinates(node_points, s, 0, element_order)[0] return n*heat_flux*r - return line_quadrature(inner) + return line_quadrature(inner, element_order) def integral_ktheta( node_points: npt.NDArray, - jacobian_inverted_t: npt.NDArray + element_order: int ): """Calculates the Ktheta integral. Parameters: - node_points: List of node points [[x1, x2, ..], [y1, y2, ..]] - jacobian_inverted_t: Jacobian matrix inverted and transposed, needed - for calculation of global derivatives + node_points: List of node points [[x1, x2, ..], [y1, y2, ..]]. + element_order: Order of the shape functions. Returns: npt.NDArray: 3x3 Ktheta matrix for the given element. """ def inner(s, t): - dn = gradient_local_shape_functions_2d() + dn = gradient_local_shape_functions_2d(s, t, element_order) + jacobian = np.dot(node_points, dn.T) + jacobian_inverted_t = np.linalg.inv(jacobian).T + jacobian_det = np.linalg.det(jacobian) + global_dn = np.dot(jacobian_inverted_t, dn) - r = local_to_global_coordinates(node_points, s, t)[0] + r = local_to_global_coordinates(node_points, s, t, element_order)[0] - return np.dot(global_dn.T, global_dn)*r + return np.dot(global_dn.T, global_dn)*r*jacobian_det - return quadratic_quadrature(inner) + return quadratic_quadrature(inner, element_order) def integral_theta_load( - node_points: npt.NDArray, - mech_loss: float): + node_points: npt.NDArray, + mech_loss: float, + element_order: int +): """Returns the load value for the temperature field (f) for the specific element. Parameters: node_points: List of node points [[x1, x2, x3], [y1, y2, y3]] of one triangle. - point_loss: Loss power on each node (heat source) + point_loss: Loss power on each node (heat source). + element_order: Order of the shape functions. + Returns: npt.NDArray: f vector value at the specific ndoe """ def inner(s, t): + dn = gradient_local_shape_functions_2d(s, t, element_order) + jacobian = np.dot(node_points, dn.T) + jacobian_det = np.linalg.det(jacobian) + n = local_shape_functions_2d(s, t) - r = local_to_global_coordinates(node_points, s, t)[0] - return n*mech_loss*r + r = local_to_global_coordinates(node_points, s, t, element_order)[0] - return quadratic_quadrature(inner) + return n*mech_loss*r*jacobian_det + + return quadratic_quadrature(inner, element_order) class ThermoSimTime: @@ -112,7 +131,7 @@ class ThermoSimTime: convective_outer_temp: float # Internal simulation data - local_elements: List[LocalElementData] + node_points: npt.NDArray # Dirichlet data dirichlet_nodes: npt.NDArray @@ -143,7 +162,8 @@ def assemble(self): # Maybe the 2x2 matrix slicing is not very fast nodes = self.mesh_data.nodes elements = self.mesh_data.elements - self.local_elements = create_local_element_data(nodes, elements) + element_order = self.mesh_data.element_order + self.node_points = create_node_points(nodes, elements, element_order) number_of_nodes = len(nodes) c = sparse.lil_matrix( @@ -153,35 +173,22 @@ def assemble(self): (number_of_nodes, number_of_nodes), dtype=np.float64) - for element in self.mesh_data.elements: - dn = gradient_local_shape_functions_2d() - # Get node points of element in format - # [x1 x2 x3] - # [y1 y2 y3] where (xi, yi) are the coordinates for Node i - node_points = np.array([ - [nodes[element[0]][0], - nodes[element[1]][0], - nodes[element[2]][0]], - [nodes[element[0]][1], - nodes[element[1]][1], - nodes[element[2]][1]] - ]) - jacobian = np.dot(node_points, dn.T) - jacobian_inverted_t = np.linalg.inv(jacobian).T - jacobian_det = np.linalg.det(jacobian) + for element_index, element in enumerate(self.mesh_data.elements): + node_points = self.node_points[element_index] ctheta_e = ( - integral_m(node_points) + integral_m(node_points, element_order) * self.material_manager.get_density(element) * self.material_manager.get_heat_capacity(element) - * jacobian_det * 2 * np.pi + * 2 * np.pi ) ktheta_e = ( integral_ktheta( node_points, - jacobian_inverted_t) + element_order + ) * self.material_manager.get_thermal_conductivity(element) - * jacobian_det * 2 * np.pi + * 2 * np.pi ) # Now assemble all element matrices @@ -193,47 +200,6 @@ def assemble(self): self.c = c.tolil() self.k = k.tolil() - def set_volume_heat_source( - self, - mech_loss_density, - number_of_time_steps - ): - """Sets the excitation for the heat conduction simulation. - The mech_losses are set for every time step. - - Parameters: - mech_losses: The mechanical losses for each element for every - time step. - number_of_time_steps: Total number of time steps. - """ - nodes = self.mesh_data.nodes - number_of_nodes = len(nodes) - f = np.zeros(shape=(number_of_nodes, number_of_time_steps)) - for time_step in range(number_of_time_steps): - for element_index, element in enumerate(self.mesh_data.elements): - dn = gradient_local_shape_functions_2d() - node_points = np.array([ - [nodes[element[0]][0], - nodes[element[1]][0], - nodes[element[2]][0]], - [nodes[element[0]][1], - nodes[element[1]][1], - nodes[element[2]][1]] - ]) - jacobian = np.dot(node_points, dn.T) - jacobian_det = np.linalg.det(jacobian) - - # TODO Maybe the outer time step loop can be removed - f_theta_e = integral_theta_load( - node_points, - mech_loss_density[element_index, time_step] - ) * 2 * np.pi * jacobian_det - - for local_p, global_p in enumerate(element): - f[global_p, time_step] += f_theta_e[local_p] - - self.f += f - def set_constant_volume_heat_source( self, mech_loss_density: npt.NDArray, @@ -248,25 +214,24 @@ def set_constant_volume_heat_source( number_of_time_steps: Total number of time steps """ nodes = self.mesh_data.nodes + element_order = self.mesh_data.element_order number_of_nodes = len(nodes) - f = np.zeros(number_of_nodes) + points_per_element = int(1/2*(element_order+1)*(element_order+2)) + f = np.zeros(number_of_nodes) for element_index, element in enumerate(self.mesh_data.elements): - dn = gradient_local_shape_functions_2d() - node_points = np.array([ - [nodes[element[0]][0], - nodes[element[1]][0], - nodes[element[2]][0]], - [nodes[element[0]][1], - nodes[element[1]][1], - nodes[element[2]][1]] - ]) - jacobian = np.dot(node_points, dn.T) - jacobian_det = np.linalg.det(jacobian) + node_points = np.zeros(shape=(2, points_per_element)) + for node_index in range(points_per_element): + node_points[:, node_index] = [ + nodes[element[node_index]][0], + nodes[element[node_index]][1] + ] f_theta_e = integral_theta_load( node_points, - mech_loss_density[element_index])*2*np.pi*jacobian_det + mech_loss_density[element_index], + element_order + ) * 2 * np.pi for local_p, global_p in enumerate(element): f[global_p] += f_theta_e[local_p] @@ -280,7 +245,8 @@ def _calculate_convection_bc( boundary_elements: npt.NDArray, theta: npt.NDArray, alpha: float, - outer_temperature: float + outer_temperature: float, + element_order: int ) -> npt.NDArray: """Calculates the load vector for the convection boundary condition. The condition is calculated using the temeprature at the given @@ -296,27 +262,46 @@ def _calculate_convection_bc( alpha: Heat transfer coefficient. outer_temperature: Temperature outside of the model. """ - f = np.zeros(len(nodes)) + points_per_element = int(1/2*(element_order+1)*(element_order+2)) - for _, element in enumerate(boundary_elements): - node_points = np.array([ - [nodes[element[0]][0], - nodes[element[1]][0]], - [nodes[element[0]][1], - nodes[element[1]][1]] - ]) + f = np.zeros(len(nodes)) + for element_index, element in enumerate(boundary_elements): + node_points = np.zeros(shape=(2, points_per_element)) + for node_index in range(points_per_element): + node_points[element_index, :, node_index] = [ + nodes[element[node_index]][0], + nodes[element[node_index]][1] + ] + # TODO Only works for element_order > 1 jacobian_det = np.sqrt( np.square(nodes[element[1]][0]-nodes[element[0]][0]) + np.square(nodes[element[1]][1]-nodes[element[0]][1]) ) - theta_e = np.array([ - theta[element[0]], - theta[element[1]] - ]) - heat_flux = alpha*(theta_e-np.ones(2)*outer_temperature) + # Temperature at boundary + boundary_nodes = [] + match element_order: + case 1: + boundary_nodes = np.array([0, 1]) + case 2: + boundary_nodes = np.array([0, 3, 1]) + case 3: + boundary_nodes = np.array([0, 3, 4, 1]) + case _: + raise NotImplementedError( + "Convection boundary condition" + " not implemented for element order > 3" + ) + + theta_e = np.zeros(boundary_nodes.shape[0]) + for index, bnode in enumerate(boundary_nodes): + theta_e[index] = element[bnode] + + heat_flux = alpha*( + theta_e - np.ones(boundary_nodes.shape[0])*outer_temperature + ) f_e = ( - integral_heat_flux(node_points, heat_flux) + integral_heat_flux(node_points, heat_flux, element_order) * 2 * np.pi * jacobian_det ) diff --git a/plutho/single_sim.py b/plutho/single_sim.py index a69204a..3bbbdf6 100644 --- a/plutho/single_sim.py +++ b/plutho/single_sim.py @@ -86,7 +86,7 @@ def __init__( self.mesh = mesh nodes, elements = mesh.get_mesh_nodes_and_elements() - self.mesh_data = MeshData(nodes, elements) + self.mesh_data = MeshData(nodes, elements, mesh.element_order) self.material_manager = MaterialManager(len(elements)) self.charge_calculated = False self.mech_loss_calculated = False diff --git a/tests/test_simulations.py b/tests/test_simulations.py index f32c3d9..2fb0c33 100644 --- a/tests/test_simulations.py +++ b/tests/test_simulations.py @@ -52,9 +52,10 @@ def test_thermo_time(tmp_path): the simulation and compares it with the input energy. """ # Create and load mesh; TODO maybe use smaller mesh size? + element_order = 1 mesh_path = os.path.join(tmp_path, "default_mesh.msh") plutho.Mesh.generate_rectangular_mesh(mesh_path) - mesh = plutho.Mesh(mesh_path, element_order=1) + mesh = plutho.Mesh(mesh_path, element_order) sim = plutho.SingleSimulation( tmp_path, @@ -93,12 +94,16 @@ def test_thermo_time(tmp_path): sim.solver.theta[:, -1], nodes, elements, + element_order, sim.material_manager.get_heat_capacity(0), sim.material_manager.get_density(0) ) volume = np.sum( - plutho.simulation.base.calculate_volumes(sim.solver.local_elements) + plutho.simulation.base.calculate_volumes( + sim.solver.node_points, + element_order + ) ) input_energy = INPUT_POWER_DENSITY*volume From e9904064b6f696ca8090f3eee65f51c412ea47c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20H=C3=B6lscher?= Date: Wed, 9 Jul 2025 11:07:50 +0200 Subject: [PATCH 04/16] Remove useless jacobian --- plutho/simulation/base.py | 10 +++++----- plutho/simulation/piezo_time.py | 1 + plutho/simulation/thermo_piezo_time.py | 8 +++++++- plutho/simulation/thermo_time.py | 7 ++----- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/plutho/simulation/base.py b/plutho/simulation/base.py index 49c766b..bfc7292 100644 --- a/plutho/simulation/base.py +++ b/plutho/simulation/base.py @@ -298,7 +298,7 @@ def b_operator_global( Returns: B operator 4x6, for a u aligned like [u1_r, u1_z, u2_r, u2_z, ..]. """ - dim = int(1/2*(element_order+1)*(element_order+2)) + nodes_per_element = int(1/2*(element_order+1)*(element_order+2)) # Get local shape functions and r (because of theta component) n = local_shape_functions_2d(s, t) @@ -311,8 +311,8 @@ def b_operator_global( global_dn = np.dot(jacobian_inverted_t, dn) # Initialize and fill array - b = np.zeros(shape=(4, 2*dim)) - for d in range(dim): + b = np.zeros(shape=(4, 2*nodes_per_element)) + for d in range(nodes_per_element): b[:, 2*d:2*d+2] = [ [ global_dn[0][d], 0 @@ -493,7 +493,7 @@ def inner(s, t): n = local_shape_functions_2d(s, t, element_order) r = local_to_global_coordinates(node_points, s, t, element_order)[0] - return np.dot(n.T, theta) * r * jacobian_det + return np.dot(n.T, theta)*r*jacobian_det return quadratic_quadrature(inner, element_order) @@ -846,7 +846,7 @@ def calculate_volumes(node_points: npt.NDArray, element_order): for element_index in range(number_of_elements): volumes.append( integral_volume(node_points[element_index], element_order) - * 2 * np.pi + *2*np.pi ) return volumes diff --git a/plutho/simulation/piezo_time.py b/plutho/simulation/piezo_time.py index f9ca9d5..b0f834a 100644 --- a/plutho/simulation/piezo_time.py +++ b/plutho/simulation/piezo_time.py @@ -139,6 +139,7 @@ def calculate_charge( else: u_e = np.zeros(2*points_per_element) ve_e = np.zeros(points_per_element) + for i in range(points_per_element): u_e[2*i] = u[2*element[i]] u_e[2*i+1] = u[2*element[i]+1] diff --git a/plutho/simulation/thermo_piezo_time.py b/plutho/simulation/thermo_piezo_time.py index 551d72a..cfcff05 100644 --- a/plutho/simulation/thermo_piezo_time.py +++ b/plutho/simulation/thermo_piezo_time.py @@ -69,7 +69,13 @@ def inner(s, t): s_e_t_minus_2 = np.dot(b_opt, u_e_t_minus_2) dt_s = (3*s_e-4*s_e_t_minus_1+s_e_t_minus_2)/(2*delta_t) - return np.dot(dt_s.T, np.dot(elasticity_matrix.T, dt_s))*r*jacobian_det + return np.dot( + dt_s.T, + np.dot( + elasticity_matrix.T, + dt_s + ) + ) * r * jacobian_det return quadratic_quadrature(inner, element_order) diff --git a/plutho/simulation/thermo_time.py b/plutho/simulation/thermo_time.py index e2d888a..b552737 100644 --- a/plutho/simulation/thermo_time.py +++ b/plutho/simulation/thermo_time.py @@ -34,10 +34,6 @@ def integral_heat_flux( npt.NDArray heat flux integral on each point. """ def inner(s): - dn = gradient_local_shape_functions_2d(s, 0, element_order) - jacobian = np.dot(node_points, dn.T) - jacobian_det = np.linalg.det(jacobian) - n = local_shape_functions_2d(s, 0, element_order) r = local_to_global_coordinates(node_points, s, 0, element_order)[0] @@ -411,7 +407,8 @@ def solve_time( self.convective_b_e, theta[:, time_index], self.convective_alpha, - self.convective_outer_temp + self.convective_outer_temp, + self.mesh_data.element_order ) # Perform Newmark method From 762ab76ce2ef1bd28d8129c6cb4f6c0483fc8332 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20H=C3=B6lscher?= Date: Wed, 9 Jul 2025 13:45:43 +0200 Subject: [PATCH 05/16] Fix quadrature integration --- plutho/simulation/base.py | 19 +++++-------------- plutho/simulation/thermo_piezo_time.py | 2 +- tests/test_simulations.py | 6 ++++-- 3 files changed, 10 insertions(+), 17 deletions(-) diff --git a/plutho/simulation/base.py b/plutho/simulation/base.py index bfc7292..162cb83 100644 --- a/plutho/simulation/base.py +++ b/plutho/simulation/base.py @@ -347,7 +347,7 @@ def inner(s, t): jacobian = np.dot(node_points, dn.T) jacobian_det = np.linalg.det(jacobian) - n = local_shape_functions_2d(s, t) + n = local_shape_functions_2d(s, t, element_order) # Since the simulation is axisymmetric it is necessary # to multiply with the radius in the integral @@ -535,7 +535,6 @@ def quadratic_quadrature(func: Callable, element_order: int): Returns: Integral of the given function. """ - #f = np.vectorize(func) weights = [] points = [] @@ -582,17 +581,12 @@ def quadratic_quadrature(func: Callable, element_order: int): f"{element_order} implemented" ) - # TODO - # Instead of a double for loop make a fast calculation using numpy - # The numpy calculations should be simular to + # TODO Can this made faster using numpy? sum = 0 for i in range(len(weights)): - for j in range(len(weights)): - sum += weights[i]*weights[j]*func(points[i][0], points[j][1]) - return sum + sum += weights[i]*func(points[i][0], points[i][1]) - # F = f(points[:, 0], points[:, 1]) - # return np.dot(np.dot(F, weights).T, weights) + return sum def line_quadrature(func: Callable, element_order: int): @@ -606,7 +600,6 @@ def line_quadrature(func: Callable, element_order: int): Returns: Integral of the given function along r-axis""" - # f = np.vectorize(func) weights = [] points = [] @@ -630,9 +623,7 @@ def line_quadrature(func: Callable, element_order: int): f"{element_order} implemented" ) - # TODO Make use of numpy - # return np.dot(f(points).T, weights) - + # TODO Make use of numpy? sum = 0 for i in range(len(weights)): sum += weights[i]*func(points[i]) diff --git a/plutho/simulation/thermo_piezo_time.py b/plutho/simulation/thermo_piezo_time.py index cfcff05..6473271 100644 --- a/plutho/simulation/thermo_piezo_time.py +++ b/plutho/simulation/thermo_piezo_time.py @@ -517,7 +517,7 @@ def get_load_vector( for element_index, element in enumerate(self.mesh_data.elements): node_points = np.zeros(shape=(2, points_per_element)) for node_index in range(points_per_element): - node_points[element_index, :, node_index] = [ + node_points[:, node_index] = [ nodes[element[node_index]][0], nodes[element[node_index]][1] ] diff --git a/tests/test_simulations.py b/tests/test_simulations.py index 2fb0c33..62eebef 100644 --- a/tests/test_simulations.py +++ b/tests/test_simulations.py @@ -299,9 +299,10 @@ def test_thermo_piezo_time(tmp_path, test=True): last time step are compared with fixed results. """ # Create and load mesh; TODO maybe use smaller mesh size? + element_order = 1 mesh_path = os.path.join(tmp_path, "default_mesh.msh") plutho.Mesh.generate_rectangular_mesh(mesh_path) - mesh = plutho.Mesh(mesh_path, element_order=1) + mesh = plutho.Mesh(mesh_path, element_order) sim = plutho.SingleSimulation( tmp_path, @@ -376,7 +377,7 @@ def test_thermo_piezo_time(tmp_path, test=True): # Calculate total loss energy volumes = plutho.simulation.base.calculate_volumes( - sim.solver.local_elements + sim.solver.node_points, element_order ) power = np.zeros(NUMBER_OF_TIME_STEPS) for time_step in range(NUMBER_OF_TIME_STEPS): @@ -394,6 +395,7 @@ def test_thermo_piezo_time(tmp_path, test=True): theta[:, -1], nodes, elements, + element_order, sim.material_manager.get_heat_capacity(0), sim.material_manager.get_density(0) ) From 78e501ca4f046c541374d1027022c9a1a8ac4bb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20H=C3=B6lscher?= Date: Wed, 9 Jul 2025 15:25:59 +0200 Subject: [PATCH 06/16] Add third order shape function derivatives --- plutho/simulation/base.py | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/plutho/simulation/base.py b/plutho/simulation/base.py index 162cb83..a4c5189 100644 --- a/plutho/simulation/base.py +++ b/plutho/simulation/base.py @@ -216,8 +216,8 @@ def gradient_local_shape_functions_2d(s, t, element_order=1) -> npt.NDArray: match element_order: case 1: return np.array([ - [-1, 1, 0], - [-1, 0, 1] + [-1, 1, 0], # d_s + [-1, 0, 1] # d_t ]) case 2: return np.array([ @@ -239,7 +239,32 @@ def gradient_local_shape_functions_2d(s, t, element_order=1) -> npt.NDArray: ] ]) case 3: - pass + return np.array([ + [ # d_s + -13.5*s**2-27*s*t+18*s-13.5*t**2+18*t-5.5, + 13.5*s**2-9*s+1, + 0, + 40.5*s**2+54*s*t-45*s+13.5*t**2-22.5*t+9, + -40.5*s**2-27*s*t+36*s+4.5*t-4.5, + t*(27*s-4.5), + t*(13.5*t-4.5), + t*(4.5-13.5*t), + t*(27*s+27*t-22.5), + 27*t*(-2*s-t+1) + ], + [ # d_t + -13.5*s**2-27*s*t+18*s-13.5*t**2+18*t-5.5, + 0, + 13.5*t**2-9*t+1, + s*(27*s+27*t-22.5), + s*(4.5-13.5*s), + s*(13.5*s-4.5), + s*(27*t-4.5), + -27*s*t+4.5*s-40.5*t**2+36*t-4.5, + 13.5*s**2+54*s*t-22.5*s+40.5*t**2-45*t+9, + 27*s*(-s-2*t+1) + ] + ]) raise ValueError( "Gradient of shape functions not implemented for element " From 804a4336abc110e10c1b398469f06954db8659d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20H=C3=B6lscher?= Date: Thu, 10 Jul 2025 11:48:17 +0200 Subject: [PATCH 07/16] Update gmsh mesh parsing Update gmsh mesh parsing to work for shape functions with higher order. Fix additional bugs occuring for higher order shape functions. --- plutho/mesh/gmsh_parser.py | 159 ++++++++++++++++++++----------- plutho/mesh/mesh.py | 4 +- plutho/simulation/base.py | 6 +- plutho/simulation/thermo_time.py | 11 ++- tests/test_simulations.py | 26 +++-- 5 files changed, 133 insertions(+), 73 deletions(-) diff --git a/plutho/mesh/gmsh_parser.py b/plutho/mesh/gmsh_parser.py index bde3d5a..be69498 100644 --- a/plutho/mesh/gmsh_parser.py +++ b/plutho/mesh/gmsh_parser.py @@ -67,29 +67,57 @@ def _get_elements( element_types, element_tags, element_node_tags - ) -> npt.NDArray: - """Returns a list of elements used in the simulation from the gmsh - getElements() api function. + ) -> Dict[int, npt.NDArray]: + """ + For each given element type a list of elements is returned. Parameters: - element_types: Element types from gmsh api. - element_tags: Tags of the element per element type. - element_node_tags: Tags of the nodes per element tag. + element_types: List of element types for which the elements lists + are extracted. + element_tags: Tags of the elements per element type. + element_node_tags: Tags of the nodes of the elements per element + type. Returns: - A list of lists where each inner list contains the node indices for - the triangle at the outer list index. + A list of elements for each given element type: + List of elements [e1, e2, e3, ...] where each element consists of + its node tags: e1: [n1, n2, ...]. """ - nodes_per_element = element_to_nodes_map[2, self.element_order] - elements = [] - element_indices = [] - # Entity elements of theset dimension containing the smaller elements - # Since every entity consists of multiple elements iterate over all - # entities and extract the elements - for i, _ in enumerate(element_types): + elements_per_type = {} + + # Types of the given elements: 2D -> Triangle or 1D -> Line + # Iterate over all given types + for i, element_type in enumerate(element_types): + elements = [] + element_indices = [] + + # For each type get the order to calculate the number of nodes + # it consists of + _, dim, order, _, _, _ = \ + gmsh.model.mesh.getElementProperties( + element_type + ) + + match dim: + case 0: + nodes_per_element = 1 + case 1: + # Line element + nodes_per_element = order+1 + case 2: + # Triangle element + nodes_per_element = int(1/2*(order+1)*(order+2)) + case _: + raise ValueError("Only supporting 2D meshes") + + # The element tags and element_node_tags lists are nested lists + # which contain the element tags and node tags of the elements of + # the current type at the respective index current_element_tags = element_tags[i] current_node_tags = element_node_tags[i] - # For each element of which the entity consists + + # Now iterate over every element of the current type and extract + # the node indices for j, _ in enumerate(current_element_tags): # 1 is subtracted because the indices in gmsh start with 1. elements.append(current_node_tags[ @@ -98,19 +126,21 @@ def _get_elements( ) element_indices.append(j) - if len(elements) == 0: - raise ValueError( - "Couldn't return elements because the list is empty." + if len(elements) == 0: + raise ValueError( + "Couldn't return elements because the list is empty." + ) + + elements_np = np.zeros( + shape=(len(current_element_tags), nodes_per_element), + dtype=int ) + for index, element in zip(element_indices, elements): + elements_np[index] = element - elements_np = np.zeros( - shape=(len(elements), len(elements[0])), - dtype=int - ) - for index, element in zip(element_indices, elements): - elements_np[index] = element + elements_per_type[element_type] = elements_np - return elements_np + return elements_per_type def get_mesh_nodes_and_elements(self) -> Tuple[npt.NDArray, npt.NDArray]: """Creates the nodes and elements lists as used in the simulation. @@ -118,7 +148,20 @@ def get_mesh_nodes_and_elements(self) -> Tuple[npt.NDArray, npt.NDArray]: Returns: List of nodes and elements""" nodes = self._get_nodes(*gmsh.model.mesh.getNodes()) - elements = self._get_elements(*gmsh.model.mesh.getElements(dim=2)) + + el_types, el_tags, el_node_tags = gmsh.model.mesh.getElements(dim=2) + elements_per_type = self._get_elements( + el_types, el_tags, el_node_tags + ) + + if len(elements_per_type) != 1: + raise ValueError( + "The given mesh as more (or less) than one element type for " + "dim=2" + ) + + # There should be only one element type for dim=2 + elements = elements_per_type[el_types[0]] return nodes, elements @@ -160,42 +203,42 @@ def get_elements_by_physical_groups( self, needed_pg_names: List[str] ) -> Dict[str, npt.NDArray]: - """Returns the elements inside the given physical groups. + """Get all triangle elements for the given physical group. Parameters: needed_pg_names: List of names of physical groups for which the - triangles are returned. + triangle elements shall be returned. Returns: - Dictionary where keys are the pg names and the values are a list - of triangles of this physical group.""" - nodes_on_boundary = element_to_boundary_nodes_map[ - self.element_order - ] - pg_tags = self.get_nodes_by_physical_groups(needed_pg_names) - elements = self._get_elements(*gmsh.model.mesh.getElements(dim=2)) - triangle_elements = {} - for pg_name, nodes in pg_tags.items(): - current_triangle_elements = [] - for check_element in elements: - # If at least {nodes_on_boundary} nodes of the check_element - # are inside of the nodes list of the current physical group, - # then the element is also part of the physical group. - found_count = 0 - - for ce in check_element: - if ce in nodes: - found_count += 1 - - if found_count >= nodes_on_boundary: - current_triangle_elements.append(check_element) - - triangle_elements[pg_name] = np.array( - current_triangle_elements, - dtype=int - ) - - return triangle_elements + dictionary where the key is the physical group name and the value + is a list of elements for this pg. + """ + _, elements = self.get_mesh_nodes_and_elements() + pg_nodes = self.get_nodes_by_physical_groups(needed_pg_names) + element_order = self.element_order + nodes_per_line = element_order+1 + pg_elements = {} + + for pg_name in needed_pg_names: + nodes = pg_nodes[pg_name] + + # For every possible element, check if it contains 'enought' nodes + # from the physical group -> Then add it to the elements list + # TODO Rework this: Check if its also possible to return + # element lists containing line elements. + current_elements = [] + for element in elements: + count = 0 + for node_index in element: + if node_index in nodes: + count += 1 + + if count >= nodes_per_line: + current_elements.append(element) + + pg_elements[pg_name] = np.array(current_elements) + + return pg_elements def create_element_post_processing_view( self, diff --git a/plutho/mesh/mesh.py b/plutho/mesh/mesh.py index a5c5970..6de14a0 100644 --- a/plutho/mesh/mesh.py +++ b/plutho/mesh/mesh.py @@ -106,7 +106,8 @@ def generate_rectangular_mesh( width: float = 0.005, height: float = 0.001, mesh_size: float = 0.00015, - x_offset: float = 0 + x_offset: float = 0, + element_order: int = 1 ): """Creates a gmsh rectangular mesh given the width, height, the mesh size and the x_offset. @@ -125,6 +126,7 @@ def generate_rectangular_mesh( gmsh.initialize() gmsh.clear() + gmsh.option.setNumber("Mesh.ElementOrder", element_order) corner_points = [ [x_offset, 0], diff --git a/plutho/simulation/base.py b/plutho/simulation/base.py index a4c5189..2a350e8 100644 --- a/plutho/simulation/base.py +++ b/plutho/simulation/base.py @@ -145,7 +145,7 @@ def load_from_file(file_path: str): # -------- Local functions and integrals -------- -def local_shape_functions_2d(s, t, element_order=1): +def local_shape_functions_2d(s, t, element_order): """Returns the local linear shape functions based on a reference triangle with corner points [(0,0), (1,0), (1,1)] for the given coordinates. @@ -199,7 +199,7 @@ def local_shape_functions_2d(s, t, element_order=1): ) -def gradient_local_shape_functions_2d(s, t, element_order=1) -> npt.NDArray: +def gradient_local_shape_functions_2d(s, t, element_order) -> npt.NDArray: """Returns the gradient of the local shape functions. Parameters: @@ -326,7 +326,7 @@ def b_operator_global( nodes_per_element = int(1/2*(element_order+1)*(element_order+2)) # Get local shape functions and r (because of theta component) - n = local_shape_functions_2d(s, t) + n = local_shape_functions_2d(s, t, element_order) r = local_to_global_coordinates(node_points, s, t, element_order)[0] # Get gradients of local shape functions (s, t) diff --git a/plutho/simulation/thermo_time.py b/plutho/simulation/thermo_time.py index b552737..6b86834 100644 --- a/plutho/simulation/thermo_time.py +++ b/plutho/simulation/thermo_time.py @@ -91,7 +91,7 @@ def inner(s, t): jacobian = np.dot(node_points, dn.T) jacobian_det = np.linalg.det(jacobian) - n = local_shape_functions_2d(s, t) + n = local_shape_functions_2d(s, t, element_order) r = local_to_global_coordinates(node_points, s, t, element_order)[0] return n*mech_loss*r*jacobian_det @@ -174,8 +174,8 @@ def assemble(self): ctheta_e = ( integral_m(node_points, element_order) - * self.material_manager.get_density(element) - * self.material_manager.get_heat_capacity(element) + * self.material_manager.get_density(element_index) + * self.material_manager.get_heat_capacity(element_index) * 2 * np.pi ) ktheta_e = ( @@ -183,7 +183,7 @@ def assemble(self): node_points, element_order ) - * self.material_manager.get_thermal_conductivity(element) + * self.material_manager.get_thermal_conductivity(element_index) * 2 * np.pi ) @@ -207,7 +207,7 @@ def set_constant_volume_heat_source( Parameters: mech_loss_density: The mechanical losses for each element. They will be applied for every time step. - number_of_time_steps: Total number of time steps + number_of_time_steps: Total number of time steps. """ nodes = self.mesh_data.nodes element_order = self.mesh_data.element_order @@ -529,4 +529,5 @@ def solve_until_material_parameters_change( if self.material_manager.update_temperature( temp_field_per_element): return time_index+1 + return number_of_time_steps diff --git a/tests/test_simulations.py b/tests/test_simulations.py index 62eebef..c6f86f3 100644 --- a/tests/test_simulations.py +++ b/tests/test_simulations.py @@ -52,9 +52,12 @@ def test_thermo_time(tmp_path): the simulation and compares it with the input energy. """ # Create and load mesh; TODO maybe use smaller mesh size? - element_order = 1 + element_order = 2 mesh_path = os.path.join(tmp_path, "default_mesh.msh") - plutho.Mesh.generate_rectangular_mesh(mesh_path) + plutho.Mesh.generate_rectangular_mesh( + mesh_path, + element_order=element_order + ) mesh = plutho.Mesh(mesh_path, element_order) sim = plutho.SingleSimulation( @@ -69,6 +72,9 @@ def test_thermo_time(tmp_path): nodes, elements = mesh.get_mesh_nodes_and_elements() number_of_elements = len(elements) + print(f"Nodes {nodes}") + print(f"Elements {elements}") + sim.setup_thermo_time_domain( delta_t, NUMBER_OF_TIME_STEPS, @@ -118,9 +124,13 @@ def test_piezo_time(tmp_path, test=True): """Test function for the piezo time domain simulation. Tests the simulation for a triangular excitation.""" # Create and load mesh; TODO maybe use smaller mesh size? + element_order = 1 mesh_path = os.path.join(tmp_path, "default_mesh.msh") - plutho.Mesh.generate_rectangular_mesh(mesh_path) - mesh = plutho.Mesh(mesh_path, element_order=1) + plutho.Mesh.generate_rectangular_mesh( + mesh_path, + element_order=element_order + ) + mesh = plutho.Mesh(mesh_path, element_order) sim = plutho.SingleSimulation( tmp_path, @@ -212,9 +222,13 @@ def test_piezo_freq(tmp_path, test=True): displacement field and charge for a sinusoidal signal. """ # Create and load mesh; TODO maybe use smaller mesh size? + element_order = 1 mesh_path = os.path.join(tmp_path, "default_mesh.msh") - plutho.Mesh.generate_rectangular_mesh(mesh_path) - mesh = plutho.Mesh(mesh_path, element_order=1) + plutho.Mesh.generate_rectangular_mesh( + mesh_path, + element_order=element_order + ) + mesh = plutho.Mesh(mesh_path, element_order) sim = plutho.SingleSimulation( tmp_path, From f036c7ff04990883e07aac3aa6c18d2646c3c18c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20H=C3=B6lscher?= Date: Thu, 10 Jul 2025 14:51:54 +0200 Subject: [PATCH 08/16] Update tests --- plutho/simulation/thermo_time.py | 19 ++++++++++++------- tests/{test_simulations.py => test_fields.py} | 7 +++++-- 2 files changed, 17 insertions(+), 9 deletions(-) rename tests/{test_simulations.py => test_fields.py} (99%) diff --git a/plutho/simulation/thermo_time.py b/plutho/simulation/thermo_time.py index 6b86834..67343b2 100644 --- a/plutho/simulation/thermo_time.py +++ b/plutho/simulation/thermo_time.py @@ -120,6 +120,10 @@ class ThermoSimTime: theta: npt.NDArray f: npt.NDArray + # FEM Matrices + k: sparse.lil_array + c: sparse.lil_array + # Convective bc enable_convection_bc: bool convective_b_e: List[int] @@ -439,7 +443,7 @@ def solve_until_material_parameters_change( self, initial_theta_field: npt.NDArray, initial_time_step: int - ): + ) -> int: """Runs the simulation using the assembled c and k matrices as well as the set excitation. Calculates the temperature field and saves it in theta. @@ -492,12 +496,13 @@ def solve_until_material_parameters_change( # Add a convection boundary condition if it set if self.enable_convection_bc: f += self._calculate_convection_bc( - self.mesh_data.nodes, - self.convective_b_e, - self.theta[:, time_index], - self.convective_alpha, - self.convective_outer_temp - ) + self.mesh_data.nodes, + self.convective_b_e, + self.theta[:, time_index], + self.convective_alpha, + self.convective_outer_temp, + self.mesh_data.element_order + ) # Perform Newmark method # Predictor step diff --git a/tests/test_simulations.py b/tests/test_fields.py similarity index 99% rename from tests/test_simulations.py rename to tests/test_fields.py index c6f86f3..f23d166 100644 --- a/tests/test_simulations.py +++ b/tests/test_fields.py @@ -52,7 +52,7 @@ def test_thermo_time(tmp_path): the simulation and compares it with the input energy. """ # Create and load mesh; TODO maybe use smaller mesh size? - element_order = 2 + element_order = 1 mesh_path = os.path.join(tmp_path, "default_mesh.msh") plutho.Mesh.generate_rectangular_mesh( mesh_path, @@ -315,7 +315,10 @@ def test_thermo_piezo_time(tmp_path, test=True): # Create and load mesh; TODO maybe use smaller mesh size? element_order = 1 mesh_path = os.path.join(tmp_path, "default_mesh.msh") - plutho.Mesh.generate_rectangular_mesh(mesh_path) + plutho.Mesh.generate_rectangular_mesh( + mesh_path, + element_order=element_order + ) mesh = plutho.Mesh(mesh_path, element_order) sim = plutho.SingleSimulation( From 99774e574d6a806605aaf73fd4e6f2e9c1f75c5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20H=C3=B6lscher?= Date: Thu, 10 Jul 2025 16:25:52 +0200 Subject: [PATCH 09/16] Update test data Update test data due to small differences in the results. The difference are in the range of floating point errors. --- tests/data/piezo_freq/q.npy | Bin 144 -> 144 bytes tests/data/piezo_freq/u.npy | Bin 15392 -> 15392 bytes tests/data/piezo_time/q.npy | Bin 8128 -> 8128 bytes tests/data/piezo_time/u.npy | Bin 7760 -> 7760 bytes tests/data/thermo_piezo_time/mech_loss.npy | Bin 4544 -> 4544 bytes tests/data/thermo_piezo_time/q.npy | Bin 40128 -> 40128 bytes tests/data/thermo_piezo_time/u.npy | Bin 10304 -> 10304 bytes 7 files changed, 0 insertions(+), 0 deletions(-) diff --git a/tests/data/piezo_freq/q.npy b/tests/data/piezo_freq/q.npy index 3bf3155198f550bf63547f84b48cb18a5b63efbc..a40a02283eea5b384cfb606f9dc90c8046ff7c8f 100644 GIT binary patch delta 23 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 VrAq5IpPWJXBj*$=!O@sY{6Ag*SF`{C literal 15392 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 Q%_9CezC0?%)>J_JKV?W%v;Y7A diff --git a/tests/data/piezo_time/q.npy b/tests/data/piezo_time/q.npy index a0a585a1f82dac2beb360eae52b712cb2f18dade..805b25b3d9efa47ed6da9270176c024b07f68348 100644 GIT binary patch literal 8128 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 diff --git a/tests/data/piezo_time/u.npy b/tests/data/piezo_time/u.npy index 67083bcfc14ea7458717b7cf596966f3a3ad8833..0be3d4bcb33ecd5867db0beb9d419747963b33a2 100644 GIT binary patch literal 7760 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 literal 7760 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 diff --git a/tests/data/thermo_piezo_time/q.npy b/tests/data/thermo_piezo_time/q.npy index 5e5dbddb0e1e8b78c6734015e8899a4e6d23a630..6dd67bea8272c287b45acb4aa7d57de96181b1a4 100644 GIT binary patch literal 40128 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 literal 40128 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�%!B7gB3V<3tdP*m#fC%NgBUBZ!Jm%1sH!&r{L+x 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�J+V8}g`j*> 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 From 8786a666440dc123e8da0494bd3fc1ca4db87e84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20H=C3=B6lscher?= Date: Thu, 10 Jul 2025 19:41:00 +0200 Subject: [PATCH 10/16] Remove comments from test --- pyproject.toml | 1 + tests/test_fields.py | 7 -- uv.lock | 210 ++----------------------------------------- 3 files changed, 7 insertions(+), 211 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 2a233d8..0822220 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,4 +51,5 @@ plutho = { workspace = true } [dependency-groups] dev = [ "plutho", + "pytest>=8.4.1", ] diff --git a/tests/test_fields.py b/tests/test_fields.py index f23d166..355eaea 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -443,13 +443,6 @@ def test_thermo_piezo_time(tmp_path, test=True): uut_u = sim.solver.u[:, -1] uut_mech_loss = sim.solver.mech_loss[:, -1] - print("Test q:", test_q) - print("UUT q:", uut_q) - print("Test u:", test_u) - print("UUT u:", uut_u) - print("Test mech loss", test_mech_loss) - print("UUT mech loss", uut_mech_loss) - # Compare arrays assert compare_numpy_array(uut_q, test_q), "Charge is not equal" assert compare_numpy_array(uut_u, test_u), \ diff --git a/uv.lock b/uv.lock index 6ecec94..3f5a516 100644 --- a/uv.lock +++ b/uv.lock @@ -1,6 +1,6 @@ version = 1 revision = 2 -requires-python = ">=3.10" +requires-python = ">=3.10, <3.13" resolution-markers = [ "python_full_version >= '3.11'", "python_full_version < '3.11'", @@ -78,19 +78,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/77/1a/5eefc0ce04affb98af07bc05f3bac9094513c0e23b0562d64af46a06aae4/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f", size = 149565, upload-time = "2025-05-02T08:32:51.404Z" }, { url = "https://files.pythonhosted.org/packages/37/a0/2410e5e6032a174c95e0806b1a6585eb21e12f445ebe239fac441995226a/charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c", size = 98357, upload-time = "2025-05-02T08:32:53.079Z" }, { url = "https://files.pythonhosted.org/packages/6c/4f/c02d5c493967af3eda9c771ad4d2bbc8df6f99ddbeb37ceea6e8716a32bc/charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e", size = 105776, upload-time = "2025-05-02T08:32:54.573Z" }, - { url = "https://files.pythonhosted.org/packages/ea/12/a93df3366ed32db1d907d7593a94f1fe6293903e3e92967bebd6950ed12c/charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", size = 199622, upload-time = "2025-05-02T08:32:56.363Z" }, - { url = "https://files.pythonhosted.org/packages/04/93/bf204e6f344c39d9937d3c13c8cd5bbfc266472e51fc8c07cb7f64fcd2de/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", size = 143435, upload-time = "2025-05-02T08:32:58.551Z" }, - { url = "https://files.pythonhosted.org/packages/22/2a/ea8a2095b0bafa6c5b5a55ffdc2f924455233ee7b91c69b7edfcc9e02284/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", size = 153653, upload-time = "2025-05-02T08:33:00.342Z" }, - { url = "https://files.pythonhosted.org/packages/b6/57/1b090ff183d13cef485dfbe272e2fe57622a76694061353c59da52c9a659/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", size = 146231, upload-time = "2025-05-02T08:33:02.081Z" }, - { url = "https://files.pythonhosted.org/packages/e2/28/ffc026b26f441fc67bd21ab7f03b313ab3fe46714a14b516f931abe1a2d8/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", size = 148243, upload-time = "2025-05-02T08:33:04.063Z" }, - { url = "https://files.pythonhosted.org/packages/c0/0f/9abe9bd191629c33e69e47c6ef45ef99773320e9ad8e9cb08b8ab4a8d4cb/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", size = 150442, upload-time = "2025-05-02T08:33:06.418Z" }, - { url = "https://files.pythonhosted.org/packages/67/7c/a123bbcedca91d5916c056407f89a7f5e8fdfce12ba825d7d6b9954a1a3c/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", size = 145147, upload-time = "2025-05-02T08:33:08.183Z" }, - { url = "https://files.pythonhosted.org/packages/ec/fe/1ac556fa4899d967b83e9893788e86b6af4d83e4726511eaaad035e36595/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", size = 153057, upload-time = "2025-05-02T08:33:09.986Z" }, - { url = "https://files.pythonhosted.org/packages/2b/ff/acfc0b0a70b19e3e54febdd5301a98b72fa07635e56f24f60502e954c461/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", size = 156454, upload-time = "2025-05-02T08:33:11.814Z" }, - { url = "https://files.pythonhosted.org/packages/92/08/95b458ce9c740d0645feb0e96cea1f5ec946ea9c580a94adfe0b617f3573/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", size = 154174, upload-time = "2025-05-02T08:33:13.707Z" }, - { url = "https://files.pythonhosted.org/packages/78/be/8392efc43487ac051eee6c36d5fbd63032d78f7728cb37aebcc98191f1ff/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", size = 149166, upload-time = "2025-05-02T08:33:15.458Z" }, - { url = "https://files.pythonhosted.org/packages/44/96/392abd49b094d30b91d9fbda6a69519e95802250b777841cf3bda8fe136c/charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", size = 98064, upload-time = "2025-05-02T08:33:17.06Z" }, - { url = "https://files.pythonhosted.org/packages/e9/b0/0200da600134e001d91851ddc797809e2fe0ea72de90e09bec5a2fbdaccb/charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", size = 105641, upload-time = "2025-05-02T08:33:18.753Z" }, { url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" }, ] @@ -143,26 +130,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2a/8a/bebe5a3f68b484d3a2b8ffaf84704b3e343ef1addea528132ef148e22b3b/contourpy-1.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3d80b2c0300583228ac98d0a927a1ba6a2ba6b8a742463c564f1d419ee5b211e", size = 1380480, upload-time = "2025-04-15T17:38:06.7Z" }, { url = "https://files.pythonhosted.org/packages/34/db/fcd325f19b5978fb509a7d55e06d99f5f856294c1991097534360b307cf1/contourpy-1.3.2-cp312-cp312-win32.whl", hash = "sha256:90df94c89a91b7362e1142cbee7568f86514412ab8a2c0d0fca72d7e91b62912", size = 178489, upload-time = "2025-04-15T17:38:10.338Z" }, { url = "https://files.pythonhosted.org/packages/01/c8/fadd0b92ffa7b5eb5949bf340a63a4a496a6930a6c37a7ba0f12acb076d6/contourpy-1.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:8c942a01d9163e2e5cfb05cb66110121b8d07ad438a17f9e766317bcb62abf73", size = 223042, upload-time = "2025-04-15T17:38:14.239Z" }, - { url = "https://files.pythonhosted.org/packages/2e/61/5673f7e364b31e4e7ef6f61a4b5121c5f170f941895912f773d95270f3a2/contourpy-1.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:de39db2604ae755316cb5967728f4bea92685884b1e767b7c24e983ef5f771cb", size = 271630, upload-time = "2025-04-15T17:38:19.142Z" }, - { url = "https://files.pythonhosted.org/packages/ff/66/a40badddd1223822c95798c55292844b7e871e50f6bfd9f158cb25e0bd39/contourpy-1.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3f9e896f447c5c8618f1edb2bafa9a4030f22a575ec418ad70611450720b5b08", size = 255670, upload-time = "2025-04-15T17:38:23.688Z" }, - { url = "https://files.pythonhosted.org/packages/1e/c7/cf9fdee8200805c9bc3b148f49cb9482a4e3ea2719e772602a425c9b09f8/contourpy-1.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71e2bd4a1c4188f5c2b8d274da78faab884b59df20df63c34f74aa1813c4427c", size = 306694, upload-time = "2025-04-15T17:38:28.238Z" }, - { url = "https://files.pythonhosted.org/packages/dd/e7/ccb9bec80e1ba121efbffad7f38021021cda5be87532ec16fd96533bb2e0/contourpy-1.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de425af81b6cea33101ae95ece1f696af39446db9682a0b56daaa48cfc29f38f", size = 345986, upload-time = "2025-04-15T17:38:33.502Z" }, - { url = "https://files.pythonhosted.org/packages/dc/49/ca13bb2da90391fa4219fdb23b078d6065ada886658ac7818e5441448b78/contourpy-1.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:977e98a0e0480d3fe292246417239d2d45435904afd6d7332d8455981c408b85", size = 318060, upload-time = "2025-04-15T17:38:38.672Z" }, - { url = "https://files.pythonhosted.org/packages/c8/65/5245ce8c548a8422236c13ffcdcdada6a2a812c361e9e0c70548bb40b661/contourpy-1.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:434f0adf84911c924519d2b08fc10491dd282b20bdd3fa8f60fd816ea0b48841", size = 322747, upload-time = "2025-04-15T17:38:43.712Z" }, - { url = "https://files.pythonhosted.org/packages/72/30/669b8eb48e0a01c660ead3752a25b44fdb2e5ebc13a55782f639170772f9/contourpy-1.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c66c4906cdbc50e9cba65978823e6e00b45682eb09adbb78c9775b74eb222422", size = 1308895, upload-time = "2025-04-15T17:39:00.224Z" }, - { url = "https://files.pythonhosted.org/packages/05/5a/b569f4250decee6e8d54498be7bdf29021a4c256e77fe8138c8319ef8eb3/contourpy-1.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8b7fc0cd78ba2f4695fd0a6ad81a19e7e3ab825c31b577f384aa9d7817dc3bef", size = 1379098, upload-time = "2025-04-15T17:43:29.649Z" }, - { url = "https://files.pythonhosted.org/packages/19/ba/b227c3886d120e60e41b28740ac3617b2f2b971b9f601c835661194579f1/contourpy-1.3.2-cp313-cp313-win32.whl", hash = "sha256:15ce6ab60957ca74cff444fe66d9045c1fd3e92c8936894ebd1f3eef2fff075f", size = 178535, upload-time = "2025-04-15T17:44:44.532Z" }, - { url = "https://files.pythonhosted.org/packages/12/6e/2fed56cd47ca739b43e892707ae9a13790a486a3173be063681ca67d2262/contourpy-1.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e1578f7eafce927b168752ed7e22646dad6cd9bca673c60bff55889fa236ebf9", size = 223096, upload-time = "2025-04-15T17:44:48.194Z" }, - { url = "https://files.pythonhosted.org/packages/54/4c/e76fe2a03014a7c767d79ea35c86a747e9325537a8b7627e0e5b3ba266b4/contourpy-1.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0475b1f6604896bc7c53bb070e355e9321e1bc0d381735421a2d2068ec56531f", size = 285090, upload-time = "2025-04-15T17:43:34.084Z" }, - { url = "https://files.pythonhosted.org/packages/7b/e2/5aba47debd55d668e00baf9651b721e7733975dc9fc27264a62b0dd26eb8/contourpy-1.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c85bb486e9be652314bb5b9e2e3b0d1b2e643d5eec4992c0fbe8ac71775da739", size = 268643, upload-time = "2025-04-15T17:43:38.626Z" }, - { url = "https://files.pythonhosted.org/packages/a1/37/cd45f1f051fe6230f751cc5cdd2728bb3a203f5619510ef11e732109593c/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:745b57db7758f3ffc05a10254edd3182a2a83402a89c00957a8e8a22f5582823", size = 310443, upload-time = "2025-04-15T17:43:44.522Z" }, - { url = "https://files.pythonhosted.org/packages/8b/a2/36ea6140c306c9ff6dd38e3bcec80b3b018474ef4d17eb68ceecd26675f4/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:970e9173dbd7eba9b4e01aab19215a48ee5dd3f43cef736eebde064a171f89a5", size = 349865, upload-time = "2025-04-15T17:43:49.545Z" }, - { url = "https://files.pythonhosted.org/packages/95/b7/2fc76bc539693180488f7b6cc518da7acbbb9e3b931fd9280504128bf956/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6c4639a9c22230276b7bffb6a850dfc8258a2521305e1faefe804d006b2e532", size = 321162, upload-time = "2025-04-15T17:43:54.203Z" }, - { url = "https://files.pythonhosted.org/packages/f4/10/76d4f778458b0aa83f96e59d65ece72a060bacb20cfbee46cf6cd5ceba41/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc829960f34ba36aad4302e78eabf3ef16a3a100863f0d4eeddf30e8a485a03b", size = 327355, upload-time = "2025-04-15T17:44:01.025Z" }, - { url = "https://files.pythonhosted.org/packages/43/a3/10cf483ea683f9f8ab096c24bad3cce20e0d1dd9a4baa0e2093c1c962d9d/contourpy-1.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d32530b534e986374fc19eaa77fcb87e8a99e5431499949b828312bdcd20ac52", size = 1307935, upload-time = "2025-04-15T17:44:17.322Z" }, - { url = "https://files.pythonhosted.org/packages/78/73/69dd9a024444489e22d86108e7b913f3528f56cfc312b5c5727a44188471/contourpy-1.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e298e7e70cf4eb179cc1077be1c725b5fd131ebc81181bf0c03525c8abc297fd", size = 1372168, upload-time = "2025-04-15T17:44:33.43Z" }, - { url = "https://files.pythonhosted.org/packages/0f/1b/96d586ccf1b1a9d2004dd519b25fbf104a11589abfd05484ff12199cca21/contourpy-1.3.2-cp313-cp313t-win32.whl", hash = "sha256:d0e589ae0d55204991450bb5c23f571c64fe43adaa53f93fc902a84c96f52fe1", size = 189550, upload-time = "2025-04-15T17:44:37.092Z" }, - { url = "https://files.pythonhosted.org/packages/b0/e6/6000d0094e8a5e32ad62591c8609e269febb6e4db83a1c75ff8868b42731/contourpy-1.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:78e9253c3de756b3f6a5174d024c4835acd59eb3f8e2ca13e775dbffe1558f69", size = 238214, upload-time = "2025-04-15T17:44:40.827Z" }, { url = "https://files.pythonhosted.org/packages/33/05/b26e3c6ecc05f349ee0013f0bb850a761016d89cec528a98193a48c34033/contourpy-1.3.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fd93cc7f3139b6dd7aab2f26a90dde0aa9fc264dbf70f6740d498a70b860b82c", size = 265681, upload-time = "2025-04-15T17:44:59.314Z" }, { url = "https://files.pythonhosted.org/packages/2b/25/ac07d6ad12affa7d1ffed11b77417d0a6308170f44ff20fa1d5aa6333f03/contourpy-1.3.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:107ba8a6a7eec58bb475329e6d3b95deba9440667c4d62b9b6063942b61d7f16", size = 315101, upload-time = "2025-04-15T17:45:04.165Z" }, { url = "https://files.pythonhosted.org/packages/8f/4d/5bb3192bbe9d3f27e3061a6a8e7733c9120e203cb8515767d30973f71030/contourpy-1.3.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ded1706ed0c1049224531b81128efbd5084598f18d8a2d9efae833edbd2b40ad", size = 220599, upload-time = "2025-04-15T17:45:08.456Z" }, @@ -245,14 +212,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ae/44/a3a3b70d5709405f7525bb7cb497b4e46151e0c02e3c8a0e40e5e9fe030b/fonttools-4.58.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e38f687d5de97c7fb7da3e58169fb5ba349e464e141f83c3c2e2beb91d317816", size = 5037851, upload-time = "2025-06-13T17:24:35.034Z" }, { url = "https://files.pythonhosted.org/packages/21/cb/e8923d197c78969454eb876a4a55a07b59c9c4c46598f02b02411dc3b45c/fonttools-4.58.4-cp312-cp312-win32.whl", hash = "sha256:636c073b4da9db053aa683db99580cac0f7c213a953b678f69acbca3443c12cc", size = 2187428, upload-time = "2025-06-13T17:24:36.996Z" }, { url = "https://files.pythonhosted.org/packages/46/e6/fe50183b1a0e1018e7487ee740fa8bb127b9f5075a41e20d017201e8ab14/fonttools-4.58.4-cp312-cp312-win_amd64.whl", hash = "sha256:82e8470535743409b30913ba2822e20077acf9ea70acec40b10fcf5671dceb58", size = 2236649, upload-time = "2025-06-13T17:24:38.985Z" }, - { url = "https://files.pythonhosted.org/packages/d4/4f/c05cab5fc1a4293e6bc535c6cb272607155a0517700f5418a4165b7f9ec8/fonttools-4.58.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5f4a64846495c543796fa59b90b7a7a9dff6839bd852741ab35a71994d685c6d", size = 2745197, upload-time = "2025-06-13T17:24:40.645Z" }, - { url = "https://files.pythonhosted.org/packages/3e/d3/49211b1f96ae49308f4f78ca7664742377a6867f00f704cdb31b57e4b432/fonttools-4.58.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e80661793a5d4d7ad132a2aa1eae2e160fbdbb50831a0edf37c7c63b2ed36574", size = 2317272, upload-time = "2025-06-13T17:24:43.428Z" }, - { url = "https://files.pythonhosted.org/packages/b2/11/c9972e46a6abd752a40a46960e431c795ad1f306775fc1f9e8c3081a1274/fonttools-4.58.4-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fe5807fc64e4ba5130f1974c045a6e8d795f3b7fb6debfa511d1773290dbb76b", size = 4877184, upload-time = "2025-06-13T17:24:45.527Z" }, - { url = "https://files.pythonhosted.org/packages/ea/24/5017c01c9ef8df572cc9eaf9f12be83ad8ed722ff6dc67991d3d752956e4/fonttools-4.58.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b610b9bef841cb8f4b50472494158b1e347d15cad56eac414c722eda695a6cfd", size = 4939445, upload-time = "2025-06-13T17:24:47.647Z" }, - { url = "https://files.pythonhosted.org/packages/79/b0/538cc4d0284b5a8826b4abed93a69db52e358525d4b55c47c8cef3669767/fonttools-4.58.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2daa7f0e213c38f05f054eb5e1730bd0424aebddbeac094489ea1585807dd187", size = 4878800, upload-time = "2025-06-13T17:24:49.766Z" }, - { url = "https://files.pythonhosted.org/packages/5a/9b/a891446b7a8250e65bffceb248508587958a94db467ffd33972723ab86c9/fonttools-4.58.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:66cccb6c0b944496b7f26450e9a66e997739c513ffaac728d24930df2fd9d35b", size = 5021259, upload-time = "2025-06-13T17:24:51.754Z" }, - { url = "https://files.pythonhosted.org/packages/17/b2/c4d2872cff3ace3ddd1388bf15b76a1d8d5313f0a61f234e9aed287e674d/fonttools-4.58.4-cp313-cp313-win32.whl", hash = "sha256:94d2aebb5ca59a5107825520fde596e344652c1f18170ef01dacbe48fa60c889", size = 2185824, upload-time = "2025-06-13T17:24:54.324Z" }, - { url = "https://files.pythonhosted.org/packages/98/57/cddf8bcc911d4f47dfca1956c1e3aeeb9f7c9b8e88b2a312fe8c22714e0b/fonttools-4.58.4-cp313-cp313-win_amd64.whl", hash = "sha256:b554bd6e80bba582fd326ddab296e563c20c64dca816d5e30489760e0c41529f", size = 2236382, upload-time = "2025-06-13T17:24:56.291Z" }, { url = "https://files.pythonhosted.org/packages/0b/2f/c536b5b9bb3c071e91d536a4d11f969e911dbb6b227939f4c5b0bca090df/fonttools-4.58.4-py3-none-any.whl", hash = "sha256:a10ce13a13f26cbb9f37512a4346bb437ad7e002ff6fa966a7ce7ff5ac3528bd", size = 1114660, upload-time = "2025-06-13T17:25:13.321Z" }, ] @@ -357,34 +316,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/32/d8/014b89fee5d4dce157d814303b0fce4d31385a2af4c41fed194b173b81ac/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:085940635c62697391baafaaeabdf3dd7a6c3643577dde337f4d66eba021b2b8", size = 2338410, upload-time = "2024-12-24T18:29:39.991Z" }, { url = "https://files.pythonhosted.org/packages/bd/72/dfff0cc97f2a0776e1c9eb5bef1ddfd45f46246c6533b0191887a427bca5/kiwisolver-1.4.8-cp312-cp312-win_amd64.whl", hash = "sha256:01c3d31902c7db5fb6182832713d3b4122ad9317c2c5877d0539227d96bb2e50", size = 71853, upload-time = "2024-12-24T18:29:42.006Z" }, { url = "https://files.pythonhosted.org/packages/dc/85/220d13d914485c0948a00f0b9eb419efaf6da81b7d72e88ce2391f7aed8d/kiwisolver-1.4.8-cp312-cp312-win_arm64.whl", hash = "sha256:a3c44cb68861de93f0c4a8175fbaa691f0aa22550c331fefef02b618a9dcb476", size = 65424, upload-time = "2024-12-24T18:29:44.38Z" }, - { url = "https://files.pythonhosted.org/packages/79/b3/e62464a652f4f8cd9006e13d07abad844a47df1e6537f73ddfbf1bc997ec/kiwisolver-1.4.8-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1c8ceb754339793c24aee1c9fb2485b5b1f5bb1c2c214ff13368431e51fc9a09", size = 124156, upload-time = "2024-12-24T18:29:45.368Z" }, - { url = "https://files.pythonhosted.org/packages/8d/2d/f13d06998b546a2ad4f48607a146e045bbe48030774de29f90bdc573df15/kiwisolver-1.4.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a62808ac74b5e55a04a408cda6156f986cefbcf0ada13572696b507cc92fa1", size = 66555, upload-time = "2024-12-24T18:29:46.37Z" }, - { url = "https://files.pythonhosted.org/packages/59/e3/b8bd14b0a54998a9fd1e8da591c60998dc003618cb19a3f94cb233ec1511/kiwisolver-1.4.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:68269e60ee4929893aad82666821aaacbd455284124817af45c11e50a4b42e3c", size = 65071, upload-time = "2024-12-24T18:29:47.333Z" }, - { url = "https://files.pythonhosted.org/packages/f0/1c/6c86f6d85ffe4d0ce04228d976f00674f1df5dc893bf2dd4f1928748f187/kiwisolver-1.4.8-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34d142fba9c464bc3bbfeff15c96eab0e7310343d6aefb62a79d51421fcc5f1b", size = 1378053, upload-time = "2024-12-24T18:29:49.636Z" }, - { url = "https://files.pythonhosted.org/packages/4e/b9/1c6e9f6dcb103ac5cf87cb695845f5fa71379021500153566d8a8a9fc291/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc373e0eef45b59197de815b1b28ef89ae3955e7722cc9710fb91cd77b7f47", size = 1472278, upload-time = "2024-12-24T18:29:51.164Z" }, - { url = "https://files.pythonhosted.org/packages/ee/81/aca1eb176de671f8bda479b11acdc42c132b61a2ac861c883907dde6debb/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:77e6f57a20b9bd4e1e2cedda4d0b986ebd0216236f0106e55c28aea3d3d69b16", size = 1478139, upload-time = "2024-12-24T18:29:52.594Z" }, - { url = "https://files.pythonhosted.org/packages/49/f4/e081522473671c97b2687d380e9e4c26f748a86363ce5af48b4a28e48d06/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08e77738ed7538f036cd1170cbed942ef749137b1311fa2bbe2a7fda2f6bf3cc", size = 1413517, upload-time = "2024-12-24T18:29:53.941Z" }, - { url = "https://files.pythonhosted.org/packages/8f/e9/6a7d025d8da8c4931522922cd706105aa32b3291d1add8c5427cdcd66e63/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5ce1e481a74b44dd5e92ff03ea0cb371ae7a0268318e202be06c8f04f4f1246", size = 1474952, upload-time = "2024-12-24T18:29:56.523Z" }, - { url = "https://files.pythonhosted.org/packages/82/13/13fa685ae167bee5d94b415991c4fc7bb0a1b6ebea6e753a87044b209678/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fc2ace710ba7c1dfd1a3b42530b62b9ceed115f19a1656adefce7b1782a37794", size = 2269132, upload-time = "2024-12-24T18:29:57.989Z" }, - { url = "https://files.pythonhosted.org/packages/ef/92/bb7c9395489b99a6cb41d502d3686bac692586db2045adc19e45ee64ed23/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:3452046c37c7692bd52b0e752b87954ef86ee2224e624ef7ce6cb21e8c41cc1b", size = 2425997, upload-time = "2024-12-24T18:29:59.393Z" }, - { url = "https://files.pythonhosted.org/packages/ed/12/87f0e9271e2b63d35d0d8524954145837dd1a6c15b62a2d8c1ebe0f182b4/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7e9a60b50fe8b2ec6f448fe8d81b07e40141bfced7f896309df271a0b92f80f3", size = 2376060, upload-time = "2024-12-24T18:30:01.338Z" }, - { url = "https://files.pythonhosted.org/packages/02/6e/c8af39288edbce8bf0fa35dee427b082758a4b71e9c91ef18fa667782138/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:918139571133f366e8362fa4a297aeba86c7816b7ecf0bc79168080e2bd79957", size = 2520471, upload-time = "2024-12-24T18:30:04.574Z" }, - { url = "https://files.pythonhosted.org/packages/13/78/df381bc7b26e535c91469f77f16adcd073beb3e2dd25042efd064af82323/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e063ef9f89885a1d68dd8b2e18f5ead48653176d10a0e324e3b0030e3a69adeb", size = 2338793, upload-time = "2024-12-24T18:30:06.25Z" }, - { url = "https://files.pythonhosted.org/packages/d0/dc/c1abe38c37c071d0fc71c9a474fd0b9ede05d42f5a458d584619cfd2371a/kiwisolver-1.4.8-cp313-cp313-win_amd64.whl", hash = "sha256:a17b7c4f5b2c51bb68ed379defd608a03954a1845dfed7cc0117f1cc8a9b7fd2", size = 71855, upload-time = "2024-12-24T18:30:07.535Z" }, - { url = "https://files.pythonhosted.org/packages/a0/b6/21529d595b126ac298fdd90b705d87d4c5693de60023e0efcb4f387ed99e/kiwisolver-1.4.8-cp313-cp313-win_arm64.whl", hash = "sha256:3cd3bc628b25f74aedc6d374d5babf0166a92ff1317f46267f12d2ed54bc1d30", size = 65430, upload-time = "2024-12-24T18:30:08.504Z" }, - { url = "https://files.pythonhosted.org/packages/34/bd/b89380b7298e3af9b39f49334e3e2a4af0e04819789f04b43d560516c0c8/kiwisolver-1.4.8-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:370fd2df41660ed4e26b8c9d6bbcad668fbe2560462cba151a721d49e5b6628c", size = 126294, upload-time = "2024-12-24T18:30:09.508Z" }, - { url = "https://files.pythonhosted.org/packages/83/41/5857dc72e5e4148eaac5aa76e0703e594e4465f8ab7ec0fc60e3a9bb8fea/kiwisolver-1.4.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:84a2f830d42707de1d191b9490ac186bf7997a9495d4e9072210a1296345f7dc", size = 67736, upload-time = "2024-12-24T18:30:11.039Z" }, - { url = "https://files.pythonhosted.org/packages/e1/d1/be059b8db56ac270489fb0b3297fd1e53d195ba76e9bbb30e5401fa6b759/kiwisolver-1.4.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7a3ad337add5148cf51ce0b55642dc551c0b9d6248458a757f98796ca7348712", size = 66194, upload-time = "2024-12-24T18:30:14.886Z" }, - { url = "https://files.pythonhosted.org/packages/e1/83/4b73975f149819eb7dcf9299ed467eba068ecb16439a98990dcb12e63fdd/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7506488470f41169b86d8c9aeff587293f530a23a23a49d6bc64dab66bedc71e", size = 1465942, upload-time = "2024-12-24T18:30:18.927Z" }, - { url = "https://files.pythonhosted.org/packages/c7/2c/30a5cdde5102958e602c07466bce058b9d7cb48734aa7a4327261ac8e002/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f0121b07b356a22fb0414cec4666bbe36fd6d0d759db3d37228f496ed67c880", size = 1595341, upload-time = "2024-12-24T18:30:22.102Z" }, - { url = "https://files.pythonhosted.org/packages/ff/9b/1e71db1c000385aa069704f5990574b8244cce854ecd83119c19e83c9586/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d6d6bd87df62c27d4185de7c511c6248040afae67028a8a22012b010bc7ad062", size = 1598455, upload-time = "2024-12-24T18:30:24.947Z" }, - { url = "https://files.pythonhosted.org/packages/85/92/c8fec52ddf06231b31cbb779af77e99b8253cd96bd135250b9498144c78b/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:291331973c64bb9cce50bbe871fb2e675c4331dab4f31abe89f175ad7679a4d7", size = 1522138, upload-time = "2024-12-24T18:30:26.286Z" }, - { url = "https://files.pythonhosted.org/packages/0b/51/9eb7e2cd07a15d8bdd976f6190c0164f92ce1904e5c0c79198c4972926b7/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:893f5525bb92d3d735878ec00f781b2de998333659507d29ea4466208df37bed", size = 1582857, upload-time = "2024-12-24T18:30:28.86Z" }, - { url = "https://files.pythonhosted.org/packages/0f/95/c5a00387a5405e68ba32cc64af65ce881a39b98d73cc394b24143bebc5b8/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b47a465040146981dc9db8647981b8cb96366fbc8d452b031e4f8fdffec3f26d", size = 2293129, upload-time = "2024-12-24T18:30:30.34Z" }, - { url = "https://files.pythonhosted.org/packages/44/83/eeb7af7d706b8347548313fa3a3a15931f404533cc54fe01f39e830dd231/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:99cea8b9dd34ff80c521aef46a1dddb0dcc0283cf18bde6d756f1e6f31772165", size = 2421538, upload-time = "2024-12-24T18:30:33.334Z" }, - { url = "https://files.pythonhosted.org/packages/05/f9/27e94c1b3eb29e6933b6986ffc5fa1177d2cd1f0c8efc5f02c91c9ac61de/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:151dffc4865e5fe6dafce5480fab84f950d14566c480c08a53c663a0020504b6", size = 2390661, upload-time = "2024-12-24T18:30:34.939Z" }, - { url = "https://files.pythonhosted.org/packages/d9/d4/3c9735faa36ac591a4afcc2980d2691000506050b7a7e80bcfe44048daa7/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:577facaa411c10421314598b50413aa1ebcf5126f704f1e5d72d7e4e9f020d90", size = 2546710, upload-time = "2024-12-24T18:30:37.281Z" }, - { url = "https://files.pythonhosted.org/packages/4c/fa/be89a49c640930180657482a74970cdcf6f7072c8d2471e1babe17a222dc/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:be4816dc51c8a471749d664161b434912eee82f2ea66bd7628bd14583a833e85", size = 2349213, upload-time = "2024-12-24T18:30:40.019Z" }, { url = "https://files.pythonhosted.org/packages/1f/f9/ae81c47a43e33b93b0a9819cac6723257f5da2a5a60daf46aa5c7226ea85/kiwisolver-1.4.8-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e7a019419b7b510f0f7c9dceff8c5eae2392037eae483a7f9162625233802b0a", size = 60403, upload-time = "2024-12-24T18:30:41.372Z" }, { url = "https://files.pythonhosted.org/packages/58/ca/f92b5cb6f4ce0c1ebfcfe3e2e42b96917e16f7090e45b21102941924f18f/kiwisolver-1.4.8-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:286b18e86682fd2217a48fc6be6b0f20c1d0ed10958d8dc53453ad58d7be0bf8", size = 58657, upload-time = "2024-12-24T18:30:42.392Z" }, { url = "https://files.pythonhosted.org/packages/80/28/ae0240f732f0484d3a4dc885d055653c47144bdf59b670aae0ec3c65a7c8/kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4191ee8dfd0be1c3666ccbac178c5a05d5f8d689bbe3fc92f3c4abec817f8fe0", size = 84948, upload-time = "2024-12-24T18:30:44.703Z" }, @@ -429,26 +360,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352, upload-time = "2024-10-18T15:21:20.971Z" }, { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097, upload-time = "2024-10-18T15:21:22.646Z" }, { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601, upload-time = "2024-10-18T15:21:23.499Z" }, - { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" }, - { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" }, - { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" }, - { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" }, - { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" }, - { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" }, - { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" }, - { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" }, - { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" }, - { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" }, - { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" }, - { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" }, - { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" }, - { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" }, - { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" }, - { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" }, - { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" }, - { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" }, - { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" }, - { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" }, ] [[package]] @@ -487,18 +398,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c4/91/ba0ae1ff4b3f30972ad01cd4a8029e70a0ec3b8ea5be04764b128b66f763/matplotlib-3.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed70453fd99733293ace1aec568255bc51c6361cb0da94fa5ebf0649fdb2150a", size = 8601321, upload-time = "2025-05-08T19:10:14.47Z" }, { url = "https://files.pythonhosted.org/packages/d2/88/d636041eb54a84b889e11872d91f7cbf036b3b0e194a70fa064eb8b04f7a/matplotlib-3.10.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dbed9917b44070e55640bd13419de83b4c918e52d97561544814ba463811cbc7", size = 9406972, upload-time = "2025-05-08T19:10:16.569Z" }, { url = "https://files.pythonhosted.org/packages/b1/79/0d1c165eac44405a86478082e225fce87874f7198300bbebc55faaf6d28d/matplotlib-3.10.3-cp312-cp312-win_amd64.whl", hash = "sha256:cf37d8c6ef1a48829443e8ba5227b44236d7fcaf7647caa3178a4ff9f7a5be05", size = 8067954, upload-time = "2025-05-08T19:10:18.663Z" }, - { url = "https://files.pythonhosted.org/packages/3b/c1/23cfb566a74c696a3b338d8955c549900d18fe2b898b6e94d682ca21e7c2/matplotlib-3.10.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9f2efccc8dcf2b86fc4ee849eea5dcaecedd0773b30f47980dc0cbeabf26ec84", size = 8180318, upload-time = "2025-05-08T19:10:20.426Z" }, - { url = "https://files.pythonhosted.org/packages/6c/0c/02f1c3b66b30da9ee343c343acbb6251bef5b01d34fad732446eaadcd108/matplotlib-3.10.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3ddbba06a6c126e3301c3d272a99dcbe7f6c24c14024e80307ff03791a5f294e", size = 8051132, upload-time = "2025-05-08T19:10:22.569Z" }, - { url = "https://files.pythonhosted.org/packages/b4/ab/8db1a5ac9b3a7352fb914133001dae889f9fcecb3146541be46bed41339c/matplotlib-3.10.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:748302b33ae9326995b238f606e9ed840bf5886ebafcb233775d946aa8107a15", size = 8457633, upload-time = "2025-05-08T19:10:24.749Z" }, - { url = "https://files.pythonhosted.org/packages/f5/64/41c4367bcaecbc03ef0d2a3ecee58a7065d0a36ae1aa817fe573a2da66d4/matplotlib-3.10.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a80fcccbef63302c0efd78042ea3c2436104c5b1a4d3ae20f864593696364ac7", size = 8601031, upload-time = "2025-05-08T19:10:27.03Z" }, - { url = "https://files.pythonhosted.org/packages/12/6f/6cc79e9e5ab89d13ed64da28898e40fe5b105a9ab9c98f83abd24e46d7d7/matplotlib-3.10.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:55e46cbfe1f8586adb34f7587c3e4f7dedc59d5226719faf6cb54fc24f2fd52d", size = 9406988, upload-time = "2025-05-08T19:10:29.056Z" }, - { url = "https://files.pythonhosted.org/packages/b1/0f/eed564407bd4d935ffabf561ed31099ed609e19287409a27b6d336848653/matplotlib-3.10.3-cp313-cp313-win_amd64.whl", hash = "sha256:151d89cb8d33cb23345cd12490c76fd5d18a56581a16d950b48c6ff19bb2ab93", size = 8068034, upload-time = "2025-05-08T19:10:31.221Z" }, - { url = "https://files.pythonhosted.org/packages/3e/e5/2f14791ff69b12b09e9975e1d116d9578ac684460860ce542c2588cb7a1c/matplotlib-3.10.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:c26dd9834e74d164d06433dc7be5d75a1e9890b926b3e57e74fa446e1a62c3e2", size = 8218223, upload-time = "2025-05-08T19:10:33.114Z" }, - { url = "https://files.pythonhosted.org/packages/5c/08/30a94afd828b6e02d0a52cae4a29d6e9ccfcf4c8b56cc28b021d3588873e/matplotlib-3.10.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:24853dad5b8c84c8c2390fc31ce4858b6df504156893292ce8092d190ef8151d", size = 8094985, upload-time = "2025-05-08T19:10:35.337Z" }, - { url = "https://files.pythonhosted.org/packages/89/44/f3bc6b53066c889d7a1a3ea8094c13af6a667c5ca6220ec60ecceec2dabe/matplotlib-3.10.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68f7878214d369d7d4215e2a9075fef743be38fa401d32e6020bab2dfabaa566", size = 8483109, upload-time = "2025-05-08T19:10:37.611Z" }, - { url = "https://files.pythonhosted.org/packages/ba/c7/473bc559beec08ebee9f86ca77a844b65747e1a6c2691e8c92e40b9f42a8/matplotlib-3.10.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6929fc618cb6db9cb75086f73b3219bbb25920cb24cee2ea7a12b04971a4158", size = 8618082, upload-time = "2025-05-08T19:10:39.892Z" }, - { url = "https://files.pythonhosted.org/packages/d8/e9/6ce8edd264c8819e37bbed8172e0ccdc7107fe86999b76ab5752276357a4/matplotlib-3.10.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6c7818292a5cc372a2dc4c795e5c356942eb8350b98ef913f7fda51fe175ac5d", size = 9413699, upload-time = "2025-05-08T19:10:42.376Z" }, - { url = "https://files.pythonhosted.org/packages/1b/92/9a45c91089c3cf690b5badd4be81e392ff086ccca8a1d4e3a08463d8a966/matplotlib-3.10.3-cp313-cp313t-win_amd64.whl", hash = "sha256:4f23ffe95c5667ef8a2b56eea9b53db7f43910fa4a2d5472ae0f72b64deab4d5", size = 8139044, upload-time = "2025-05-08T19:10:44.551Z" }, { url = "https://files.pythonhosted.org/packages/3d/d1/f54d43e95384b312ffa4a74a4326c722f3b8187aaaa12e9a84cdf3037131/matplotlib-3.10.3-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:86ab63d66bbc83fdb6733471d3bff40897c1e9921cba112accd748eee4bce5e4", size = 8162896, upload-time = "2025-05-08T19:10:46.432Z" }, { url = "https://files.pythonhosted.org/packages/24/a4/fbfc00c2346177c95b353dcf9b5a004106abe8730a62cb6f27e79df0a698/matplotlib-3.10.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:a48f9c08bf7444b5d2391a83e75edb464ccda3c380384b36532a0962593a1751", size = 8039702, upload-time = "2025-05-08T19:10:49.634Z" }, { url = "https://files.pythonhosted.org/packages/6a/b9/59e120d24a2ec5fc2d30646adb2efb4621aab3c6d83d66fb2a7a182db032/matplotlib-3.10.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb73d8aa75a237457988f9765e4dfe1c0d2453c5ca4eabc897d4309672c8e014", size = 8594298, upload-time = "2025-05-08T19:10:51.738Z" }, @@ -552,26 +451,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783, upload-time = "2025-05-17T21:36:56.883Z" }, { url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506, upload-time = "2025-05-17T21:37:07.368Z" }, { url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190, upload-time = "2025-05-17T21:37:26.213Z" }, - { url = "https://files.pythonhosted.org/packages/f9/5c/6657823f4f594f72b5471f1db1ab12e26e890bb2e41897522d134d2a3e81/numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", size = 20867828, upload-time = "2025-05-17T21:37:56.699Z" }, - { url = "https://files.pythonhosted.org/packages/dc/9e/14520dc3dadf3c803473bd07e9b2bd1b69bc583cb2497b47000fed2fa92f/numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", size = 14143006, upload-time = "2025-05-17T21:38:18.291Z" }, - { url = "https://files.pythonhosted.org/packages/4f/06/7e96c57d90bebdce9918412087fc22ca9851cceaf5567a45c1f404480e9e/numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", size = 5076765, upload-time = "2025-05-17T21:38:27.319Z" }, - { url = "https://files.pythonhosted.org/packages/73/ed/63d920c23b4289fdac96ddbdd6132e9427790977d5457cd132f18e76eae0/numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", size = 6617736, upload-time = "2025-05-17T21:38:38.141Z" }, - { url = "https://files.pythonhosted.org/packages/85/c5/e19c8f99d83fd377ec8c7e0cf627a8049746da54afc24ef0a0cb73d5dfb5/numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", size = 14010719, upload-time = "2025-05-17T21:38:58.433Z" }, - { url = "https://files.pythonhosted.org/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", size = 16526072, upload-time = "2025-05-17T21:39:22.638Z" }, - { url = "https://files.pythonhosted.org/packages/b2/6c/04b5f47f4f32f7c2b0e7260442a8cbcf8168b0e1a41ff1495da42f42a14f/numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", size = 15503213, upload-time = "2025-05-17T21:39:45.865Z" }, - { url = "https://files.pythonhosted.org/packages/17/0a/5cd92e352c1307640d5b6fec1b2ffb06cd0dabe7d7b8227f97933d378422/numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", size = 18316632, upload-time = "2025-05-17T21:40:13.331Z" }, - { url = "https://files.pythonhosted.org/packages/f0/3b/5cba2b1d88760ef86596ad0f3d484b1cbff7c115ae2429678465057c5155/numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", size = 6244532, upload-time = "2025-05-17T21:43:46.099Z" }, - { url = "https://files.pythonhosted.org/packages/cb/3b/d58c12eafcb298d4e6d0d40216866ab15f59e55d148a5658bb3132311fcf/numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", size = 12610885, upload-time = "2025-05-17T21:44:05.145Z" }, - { url = "https://files.pythonhosted.org/packages/6b/9e/4bf918b818e516322db999ac25d00c75788ddfd2d2ade4fa66f1f38097e1/numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", size = 20963467, upload-time = "2025-05-17T21:40:44Z" }, - { url = "https://files.pythonhosted.org/packages/61/66/d2de6b291507517ff2e438e13ff7b1e2cdbdb7cb40b3ed475377aece69f9/numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", size = 14225144, upload-time = "2025-05-17T21:41:05.695Z" }, - { url = "https://files.pythonhosted.org/packages/e4/25/480387655407ead912e28ba3a820bc69af9adf13bcbe40b299d454ec011f/numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", size = 5200217, upload-time = "2025-05-17T21:41:15.903Z" }, - { url = "https://files.pythonhosted.org/packages/aa/4a/6e313b5108f53dcbf3aca0c0f3e9c92f4c10ce57a0a721851f9785872895/numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8", size = 6712014, upload-time = "2025-05-17T21:41:27.321Z" }, - { url = "https://files.pythonhosted.org/packages/b7/30/172c2d5c4be71fdf476e9de553443cf8e25feddbe185e0bd88b096915bcc/numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", size = 14077935, upload-time = "2025-05-17T21:41:49.738Z" }, - { url = "https://files.pythonhosted.org/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", size = 16600122, upload-time = "2025-05-17T21:42:14.046Z" }, - { url = "https://files.pythonhosted.org/packages/12/75/ee20da0e58d3a66f204f38916757e01e33a9737d0b22373b3eb5a27358f9/numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", size = 15586143, upload-time = "2025-05-17T21:42:37.464Z" }, - { url = "https://files.pythonhosted.org/packages/76/95/bef5b37f29fc5e739947e9ce5179ad402875633308504a52d188302319c8/numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", size = 18385260, upload-time = "2025-05-17T21:43:05.189Z" }, - { url = "https://files.pythonhosted.org/packages/09/04/f2f83279d287407cf36a7a8053a5abe7be3622a4363337338f2585e4afda/numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", size = 6377225, upload-time = "2025-05-17T21:43:16.254Z" }, - { url = "https://files.pythonhosted.org/packages/67/0e/35082d13c09c02c011cf21570543d202ad929d961c02a147493cb0c2bdf5/numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", size = 12771374, upload-time = "2025-05-17T21:43:35.479Z" }, { url = "https://files.pythonhosted.org/packages/9e/3b/d94a75f4dbf1ef5d321523ecac21ef23a3cd2ac8b78ae2aac40873590229/numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", size = 21040391, upload-time = "2025-05-17T21:44:35.948Z" }, { url = "https://files.pythonhosted.org/packages/17/f4/09b2fa1b58f0fb4f7c7963a1649c64c4d315752240377ed74d9cd878f7b5/numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", size = 6786754, upload-time = "2025-05-17T21:44:47.446Z" }, { url = "https://files.pythonhosted.org/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", size = 16643476, upload-time = "2025-05-17T21:45:11.871Z" }, @@ -609,28 +488,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d4/75/5baed8cd867eabee8aad1e74d7197d73971d6a3d40c821f1848b8fab8b84/numpy-2.3.0-cp312-cp312-win32.whl", hash = "sha256:e6648078bdd974ef5d15cecc31b0c410e2e24178a6e10bf511e0557eed0f2570", size = 6318285, upload-time = "2025-06-07T14:43:02.052Z" }, { url = "https://files.pythonhosted.org/packages/bc/49/d5781eaa1a15acb3b3a3f49dc9e2ff18d92d0ce5c2976f4ab5c0a7360250/numpy-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:0898c67a58cdaaf29994bc0e2c65230fd4de0ac40afaf1584ed0b02cd74c6fdd", size = 12732594, upload-time = "2025-06-07T14:43:21.071Z" }, { url = "https://files.pythonhosted.org/packages/c2/1c/6d343e030815c7c97a1f9fbad00211b47717c7fe446834c224bd5311e6f1/numpy-2.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:bd8df082b6c4695753ad6193018c05aac465d634834dca47a3ae06d4bb22d9ea", size = 9891498, upload-time = "2025-06-07T14:43:36.332Z" }, - { url = "https://files.pythonhosted.org/packages/73/fc/1d67f751fd4dbafc5780244fe699bc4084268bad44b7c5deb0492473127b/numpy-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5754ab5595bfa2c2387d241296e0381c21f44a4b90a776c3c1d39eede13a746a", size = 20889633, upload-time = "2025-06-07T14:44:06.839Z" }, - { url = "https://files.pythonhosted.org/packages/e8/95/73ffdb69e5c3f19ec4530f8924c4386e7ba097efc94b9c0aff607178ad94/numpy-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d11fa02f77752d8099573d64e5fe33de3229b6632036ec08f7080f46b6649959", size = 14151683, upload-time = "2025-06-07T14:44:28.847Z" }, - { url = "https://files.pythonhosted.org/packages/64/d5/06d4bb31bb65a1d9c419eb5676173a2f90fd8da3c59f816cc54c640ce265/numpy-2.3.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:aba48d17e87688a765ab1cd557882052f238e2f36545dfa8e29e6a91aef77afe", size = 5102683, upload-time = "2025-06-07T14:44:38.417Z" }, - { url = "https://files.pythonhosted.org/packages/12/8b/6c2cef44f8ccdc231f6b56013dff1d71138c48124334aded36b1a1b30c5a/numpy-2.3.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4dc58865623023b63b10d52f18abaac3729346a7a46a778381e0e3af4b7f3beb", size = 6640253, upload-time = "2025-06-07T14:44:49.359Z" }, - { url = "https://files.pythonhosted.org/packages/62/aa/fca4bf8de3396ddb59544df9b75ffe5b73096174de97a9492d426f5cd4aa/numpy-2.3.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:df470d376f54e052c76517393fa443758fefcdd634645bc9c1f84eafc67087f0", size = 14258658, upload-time = "2025-06-07T14:45:10.156Z" }, - { url = "https://files.pythonhosted.org/packages/1c/12/734dce1087eed1875f2297f687e671cfe53a091b6f2f55f0c7241aad041b/numpy-2.3.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:87717eb24d4a8a64683b7a4e91ace04e2f5c7c77872f823f02a94feee186168f", size = 16628765, upload-time = "2025-06-07T14:45:35.076Z" }, - { url = "https://files.pythonhosted.org/packages/48/03/ffa41ade0e825cbcd5606a5669962419528212a16082763fc051a7247d76/numpy-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d8fa264d56882b59dcb5ea4d6ab6f31d0c58a57b41aec605848b6eb2ef4a43e8", size = 15564335, upload-time = "2025-06-07T14:45:58.797Z" }, - { url = "https://files.pythonhosted.org/packages/07/58/869398a11863310aee0ff85a3e13b4c12f20d032b90c4b3ee93c3b728393/numpy-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e651756066a0eaf900916497e20e02fe1ae544187cb0fe88de981671ee7f6270", size = 18360608, upload-time = "2025-06-07T14:46:25.687Z" }, - { url = "https://files.pythonhosted.org/packages/2f/8a/5756935752ad278c17e8a061eb2127c9a3edf4ba2c31779548b336f23c8d/numpy-2.3.0-cp313-cp313-win32.whl", hash = "sha256:e43c3cce3b6ae5f94696669ff2a6eafd9a6b9332008bafa4117af70f4b88be6f", size = 6310005, upload-time = "2025-06-07T14:50:13.138Z" }, - { url = "https://files.pythonhosted.org/packages/08/60/61d60cf0dfc0bf15381eaef46366ebc0c1a787856d1db0c80b006092af84/numpy-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:81ae0bf2564cf475f94be4a27ef7bcf8af0c3e28da46770fc904da9abd5279b5", size = 12729093, upload-time = "2025-06-07T14:50:31.82Z" }, - { url = "https://files.pythonhosted.org/packages/66/31/2f2f2d2b3e3c32d5753d01437240feaa32220b73258c9eef2e42a0832866/numpy-2.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:c8738baa52505fa6e82778580b23f945e3578412554d937093eac9205e845e6e", size = 9885689, upload-time = "2025-06-07T14:50:47.888Z" }, - { url = "https://files.pythonhosted.org/packages/f1/89/c7828f23cc50f607ceb912774bb4cff225ccae7131c431398ad8400e2c98/numpy-2.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:39b27d8b38942a647f048b675f134dd5a567f95bfff481f9109ec308515c51d8", size = 20986612, upload-time = "2025-06-07T14:46:56.077Z" }, - { url = "https://files.pythonhosted.org/packages/dd/46/79ecf47da34c4c50eedec7511e53d57ffdfd31c742c00be7dc1d5ffdb917/numpy-2.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0eba4a1ea88f9a6f30f56fdafdeb8da3774349eacddab9581a21234b8535d3d3", size = 14298953, upload-time = "2025-06-07T14:47:18.053Z" }, - { url = "https://files.pythonhosted.org/packages/59/44/f6caf50713d6ff4480640bccb2a534ce1d8e6e0960c8f864947439f0ee95/numpy-2.3.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:b0f1f11d0a1da54927436505a5a7670b154eac27f5672afc389661013dfe3d4f", size = 5225806, upload-time = "2025-06-07T14:47:27.524Z" }, - { url = "https://files.pythonhosted.org/packages/a6/43/e1fd1aca7c97e234dd05e66de4ab7a5be54548257efcdd1bc33637e72102/numpy-2.3.0-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:690d0a5b60a47e1f9dcec7b77750a4854c0d690e9058b7bef3106e3ae9117808", size = 6735169, upload-time = "2025-06-07T14:47:38.057Z" }, - { url = "https://files.pythonhosted.org/packages/84/89/f76f93b06a03177c0faa7ca94d0856c4e5c4bcaf3c5f77640c9ed0303e1c/numpy-2.3.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:8b51ead2b258284458e570942137155978583e407babc22e3d0ed7af33ce06f8", size = 14330701, upload-time = "2025-06-07T14:47:59.113Z" }, - { url = "https://files.pythonhosted.org/packages/aa/f5/4858c3e9ff7a7d64561b20580cf7cc5d085794bd465a19604945d6501f6c/numpy-2.3.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:aaf81c7b82c73bd9b45e79cfb9476cb9c29e937494bfe9092c26aece812818ad", size = 16692983, upload-time = "2025-06-07T14:48:24.196Z" }, - { url = "https://files.pythonhosted.org/packages/08/17/0e3b4182e691a10e9483bcc62b4bb8693dbf9ea5dc9ba0b77a60435074bb/numpy-2.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f420033a20b4f6a2a11f585f93c843ac40686a7c3fa514060a97d9de93e5e72b", size = 15641435, upload-time = "2025-06-07T14:48:47.712Z" }, - { url = "https://files.pythonhosted.org/packages/4e/d5/463279fda028d3c1efa74e7e8d507605ae87f33dbd0543cf4c4527c8b882/numpy-2.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d344ca32ab482bcf8735d8f95091ad081f97120546f3d250240868430ce52555", size = 18433798, upload-time = "2025-06-07T14:49:14.866Z" }, - { url = "https://files.pythonhosted.org/packages/0e/1e/7a9d98c886d4c39a2b4d3a7c026bffcf8fbcaf518782132d12a301cfc47a/numpy-2.3.0-cp313-cp313t-win32.whl", hash = "sha256:48a2e8eaf76364c32a1feaa60d6925eaf32ed7a040183b807e02674305beef61", size = 6438632, upload-time = "2025-06-07T14:49:25.67Z" }, - { url = "https://files.pythonhosted.org/packages/fe/ab/66fc909931d5eb230107d016861824f335ae2c0533f422e654e5ff556784/numpy-2.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ba17f93a94e503551f154de210e4d50c5e3ee20f7e7a1b5f6ce3f22d419b93bb", size = 12868491, upload-time = "2025-06-07T14:49:44.898Z" }, - { url = "https://files.pythonhosted.org/packages/ee/e8/2c8a1c9e34d6f6d600c83d5ce5b71646c32a13f34ca5c518cc060639841c/numpy-2.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:f14e016d9409680959691c109be98c436c6249eaf7f118b424679793607b5944", size = 9935345, upload-time = "2025-06-07T14:50:02.311Z" }, { url = "https://files.pythonhosted.org/packages/6a/a2/f8c1133f90eaa1c11bbbec1dc28a42054d0ce74bc2c9838c5437ba5d4980/numpy-2.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:80b46117c7359de8167cc00a2c7d823bdd505e8c7727ae0871025a86d668283b", size = 21070759, upload-time = "2025-06-07T14:51:18.241Z" }, { url = "https://files.pythonhosted.org/packages/6c/e0/4c05fc44ba28463096eee5ae2a12832c8d2759cc5bcec34ae33386d3ff83/numpy-2.3.0-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:5814a0f43e70c061f47abd5857d120179609ddc32a613138cbb6c4e9e2dbdda5", size = 5301054, upload-time = "2025-06-07T14:51:27.413Z" }, { url = "https://files.pythonhosted.org/packages/8a/3b/6c06cdebe922bbc2a466fe2105f50f661238ea223972a69c7deb823821e7/numpy-2.3.0-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:ef6c1e88fd6b81ac6d215ed71dc8cd027e54d4bf1d2682d362449097156267a2", size = 6817520, upload-time = "2025-06-07T14:51:38.015Z" }, @@ -687,28 +544,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/96/ae/ca0099a3995976a9fce2f423166f7bff9b12244afdc7520f6ed38911539a/pillow-11.2.1-cp312-cp312-win32.whl", hash = "sha256:1d535df14716e7f8776b9e7fee118576d65572b4aad3ed639be9e4fa88a1cad3", size = 2332309, upload-time = "2025-04-12T17:48:17.885Z" }, { url = "https://files.pythonhosted.org/packages/7c/18/24bff2ad716257fc03da964c5e8f05d9790a779a8895d6566e493ccf0189/pillow-11.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:14e33b28bf17c7a38eede290f77db7c664e4eb01f7869e37fa98a5aa95978941", size = 2676768, upload-time = "2025-04-12T17:48:19.655Z" }, { url = "https://files.pythonhosted.org/packages/da/bb/e8d656c9543276517ee40184aaa39dcb41e683bca121022f9323ae11b39d/pillow-11.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:21e1470ac9e5739ff880c211fc3af01e3ae505859392bf65458c224d0bf283eb", size = 2415087, upload-time = "2025-04-12T17:48:21.991Z" }, - { url = "https://files.pythonhosted.org/packages/36/9c/447528ee3776e7ab8897fe33697a7ff3f0475bb490c5ac1456a03dc57956/pillow-11.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fdec757fea0b793056419bca3e9932eb2b0ceec90ef4813ea4c1e072c389eb28", size = 3190098, upload-time = "2025-04-12T17:48:23.915Z" }, - { url = "https://files.pythonhosted.org/packages/b5/09/29d5cd052f7566a63e5b506fac9c60526e9ecc553825551333e1e18a4858/pillow-11.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b0e130705d568e2f43a17bcbe74d90958e8a16263868a12c3e0d9c8162690830", size = 3030166, upload-time = "2025-04-12T17:48:25.738Z" }, - { url = "https://files.pythonhosted.org/packages/71/5d/446ee132ad35e7600652133f9c2840b4799bbd8e4adba881284860da0a36/pillow-11.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bdb5e09068332578214cadd9c05e3d64d99e0e87591be22a324bdbc18925be0", size = 4408674, upload-time = "2025-04-12T17:48:27.908Z" }, - { url = "https://files.pythonhosted.org/packages/69/5f/cbe509c0ddf91cc3a03bbacf40e5c2339c4912d16458fcb797bb47bcb269/pillow-11.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d189ba1bebfbc0c0e529159631ec72bb9e9bc041f01ec6d3233d6d82eb823bc1", size = 4496005, upload-time = "2025-04-12T17:48:29.888Z" }, - { url = "https://files.pythonhosted.org/packages/f9/b3/dd4338d8fb8a5f312021f2977fb8198a1184893f9b00b02b75d565c33b51/pillow-11.2.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:191955c55d8a712fab8934a42bfefbf99dd0b5875078240943f913bb66d46d9f", size = 4518707, upload-time = "2025-04-12T17:48:31.874Z" }, - { url = "https://files.pythonhosted.org/packages/13/eb/2552ecebc0b887f539111c2cd241f538b8ff5891b8903dfe672e997529be/pillow-11.2.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:ad275964d52e2243430472fc5d2c2334b4fc3ff9c16cb0a19254e25efa03a155", size = 4610008, upload-time = "2025-04-12T17:48:34.422Z" }, - { url = "https://files.pythonhosted.org/packages/72/d1/924ce51bea494cb6e7959522d69d7b1c7e74f6821d84c63c3dc430cbbf3b/pillow-11.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:750f96efe0597382660d8b53e90dd1dd44568a8edb51cb7f9d5d918b80d4de14", size = 4585420, upload-time = "2025-04-12T17:48:37.641Z" }, - { url = "https://files.pythonhosted.org/packages/43/ab/8f81312d255d713b99ca37479a4cb4b0f48195e530cdc1611990eb8fd04b/pillow-11.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fe15238d3798788d00716637b3d4e7bb6bde18b26e5d08335a96e88564a36b6b", size = 4667655, upload-time = "2025-04-12T17:48:39.652Z" }, - { url = "https://files.pythonhosted.org/packages/94/86/8f2e9d2dc3d308dfd137a07fe1cc478df0a23d42a6c4093b087e738e4827/pillow-11.2.1-cp313-cp313-win32.whl", hash = "sha256:3fe735ced9a607fee4f481423a9c36701a39719252a9bb251679635f99d0f7d2", size = 2332329, upload-time = "2025-04-12T17:48:41.765Z" }, - { url = "https://files.pythonhosted.org/packages/6d/ec/1179083b8d6067a613e4d595359b5fdea65d0a3b7ad623fee906e1b3c4d2/pillow-11.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:74ee3d7ecb3f3c05459ba95eed5efa28d6092d751ce9bf20e3e253a4e497e691", size = 2676388, upload-time = "2025-04-12T17:48:43.625Z" }, - { url = "https://files.pythonhosted.org/packages/23/f1/2fc1e1e294de897df39fa8622d829b8828ddad938b0eaea256d65b84dd72/pillow-11.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:5119225c622403afb4b44bad4c1ca6c1f98eed79db8d3bc6e4e160fc6339d66c", size = 2414950, upload-time = "2025-04-12T17:48:45.475Z" }, - { url = "https://files.pythonhosted.org/packages/c4/3e/c328c48b3f0ead7bab765a84b4977acb29f101d10e4ef57a5e3400447c03/pillow-11.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8ce2e8411c7aaef53e6bb29fe98f28cd4fbd9a1d9be2eeea434331aac0536b22", size = 3192759, upload-time = "2025-04-12T17:48:47.866Z" }, - { url = "https://files.pythonhosted.org/packages/18/0e/1c68532d833fc8b9f404d3a642991441d9058eccd5606eab31617f29b6d4/pillow-11.2.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9ee66787e095127116d91dea2143db65c7bb1e232f617aa5957c0d9d2a3f23a7", size = 3033284, upload-time = "2025-04-12T17:48:50.189Z" }, - { url = "https://files.pythonhosted.org/packages/b7/cb/6faf3fb1e7705fd2db74e070f3bf6f88693601b0ed8e81049a8266de4754/pillow-11.2.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9622e3b6c1d8b551b6e6f21873bdcc55762b4b2126633014cea1803368a9aa16", size = 4445826, upload-time = "2025-04-12T17:48:52.346Z" }, - { url = "https://files.pythonhosted.org/packages/07/94/8be03d50b70ca47fb434a358919d6a8d6580f282bbb7af7e4aa40103461d/pillow-11.2.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63b5dff3a68f371ea06025a1a6966c9a1e1ee452fc8020c2cd0ea41b83e9037b", size = 4527329, upload-time = "2025-04-12T17:48:54.403Z" }, - { url = "https://files.pythonhosted.org/packages/fd/a4/bfe78777076dc405e3bd2080bc32da5ab3945b5a25dc5d8acaa9de64a162/pillow-11.2.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:31df6e2d3d8fc99f993fd253e97fae451a8db2e7207acf97859732273e108406", size = 4549049, upload-time = "2025-04-12T17:48:56.383Z" }, - { url = "https://files.pythonhosted.org/packages/65/4d/eaf9068dc687c24979e977ce5677e253624bd8b616b286f543f0c1b91662/pillow-11.2.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:062b7a42d672c45a70fa1f8b43d1d38ff76b63421cbbe7f88146b39e8a558d91", size = 4635408, upload-time = "2025-04-12T17:48:58.782Z" }, - { url = "https://files.pythonhosted.org/packages/1d/26/0fd443365d9c63bc79feb219f97d935cd4b93af28353cba78d8e77b61719/pillow-11.2.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4eb92eca2711ef8be42fd3f67533765d9fd043b8c80db204f16c8ea62ee1a751", size = 4614863, upload-time = "2025-04-12T17:49:00.709Z" }, - { url = "https://files.pythonhosted.org/packages/49/65/dca4d2506be482c2c6641cacdba5c602bc76d8ceb618fd37de855653a419/pillow-11.2.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f91ebf30830a48c825590aede79376cb40f110b387c17ee9bd59932c961044f9", size = 4692938, upload-time = "2025-04-12T17:49:02.946Z" }, - { url = "https://files.pythonhosted.org/packages/b3/92/1ca0c3f09233bd7decf8f7105a1c4e3162fb9142128c74adad0fb361b7eb/pillow-11.2.1-cp313-cp313t-win32.whl", hash = "sha256:e0b55f27f584ed623221cfe995c912c61606be8513bfa0e07d2c674b4516d9dd", size = 2335774, upload-time = "2025-04-12T17:49:04.889Z" }, - { url = "https://files.pythonhosted.org/packages/a5/ac/77525347cb43b83ae905ffe257bbe2cc6fd23acb9796639a1f56aa59d191/pillow-11.2.1-cp313-cp313t-win_amd64.whl", hash = "sha256:36d6b82164c39ce5482f649b437382c0fb2395eabc1e2b1702a6deb8ad647d6e", size = 2681895, upload-time = "2025-04-12T17:49:06.635Z" }, - { url = "https://files.pythonhosted.org/packages/67/32/32dc030cfa91ca0fc52baebbba2e009bb001122a1daa8b6a79ad830b38d3/pillow-11.2.1-cp313-cp313t-win_arm64.whl", hash = "sha256:225c832a13326e34f212d2072982bb1adb210e0cc0b153e688743018c94a2681", size = 2417234, upload-time = "2025-04-12T17:49:08.399Z" }, { url = "https://files.pythonhosted.org/packages/33/49/c8c21e4255b4f4a2c0c68ac18125d7f5460b109acc6dfdef1a24f9b960ef/pillow-11.2.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:9b7b0d4fd2635f54ad82785d56bc0d94f147096493a79985d0ab57aedd563156", size = 3181727, upload-time = "2025-04-12T17:49:31.898Z" }, { url = "https://files.pythonhosted.org/packages/6d/f1/f7255c0838f8c1ef6d55b625cfb286835c17e8136ce4351c5577d02c443b/pillow-11.2.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:aa442755e31c64037aa7c1cb186e0b369f8416c567381852c63444dd666fb772", size = 2999833, upload-time = "2025-04-12T17:49:34.2Z" }, { url = "https://files.pythonhosted.org/packages/e2/57/9968114457bd131063da98d87790d080366218f64fa2943b65ac6739abb3/pillow-11.2.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0d3348c95b766f54b76116d53d4cb171b52992a1027e7ca50c81b43b9d9e363", size = 3437472, upload-time = "2025-04-12T17:49:36.294Z" }, @@ -763,6 +598,7 @@ test = [ [package.dev-dependencies] dev = [ { name = "plutho" }, + { name = "pytest" }, ] [package.metadata] @@ -783,7 +619,10 @@ requires-dist = [ provides-extras = ["doc", "test"] [package.metadata.requires-dev] -dev = [{ name = "plutho", editable = "." }] +dev = [ + { name = "plutho", editable = "." }, + { name = "pytest", specifier = ">=8.4.1" }, +] [[package]] name = "pycodestyle" @@ -893,15 +732,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611, upload-time = "2024-08-06T20:32:38.898Z" }, { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591, upload-time = "2024-08-06T20:32:40.241Z" }, { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338, upload-time = "2024-08-06T20:32:41.93Z" }, - { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" }, - { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" }, - { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" }, - { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload-time = "2024-08-06T20:32:51.188Z" }, - { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload-time = "2024-08-06T20:32:53.019Z" }, - { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload-time = "2024-08-06T20:32:54.708Z" }, - { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" }, - { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" }, - { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" }, ] [[package]] @@ -990,24 +820,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/89/b1/fbb53137f42c4bf630b1ffdfc2151a62d1d1b903b249f030d2b1c0280af8/scipy-1.15.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6cfd56fc1a8e53f6e89ba3a7a7251f7396412d655bca2aa5611c8ec9a6784a1e", size = 36885140, upload-time = "2025-05-08T16:06:39.249Z" }, { url = "https://files.pythonhosted.org/packages/2e/2e/025e39e339f5090df1ff266d021892694dbb7e63568edcfe43f892fa381d/scipy-1.15.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0ff17c0bb1cb32952c09217d8d1eed9b53d1463e5f1dd6052c7857f83127d539", size = 39710549, upload-time = "2025-05-08T16:06:45.729Z" }, { url = "https://files.pythonhosted.org/packages/e6/eb/3bf6ea8ab7f1503dca3a10df2e4b9c3f6b3316df07f6c0ded94b281c7101/scipy-1.15.3-cp312-cp312-win_amd64.whl", hash = "sha256:52092bc0472cfd17df49ff17e70624345efece4e1a12b23783a1ac59a1b728ed", size = 40966184, upload-time = "2025-05-08T16:06:52.623Z" }, - { url = "https://files.pythonhosted.org/packages/73/18/ec27848c9baae6e0d6573eda6e01a602e5649ee72c27c3a8aad673ebecfd/scipy-1.15.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c620736bcc334782e24d173c0fdbb7590a0a436d2fdf39310a8902505008759", size = 38728256, upload-time = "2025-05-08T16:06:58.696Z" }, - { url = "https://files.pythonhosted.org/packages/74/cd/1aef2184948728b4b6e21267d53b3339762c285a46a274ebb7863c9e4742/scipy-1.15.3-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:7e11270a000969409d37ed399585ee530b9ef6aa99d50c019de4cb01e8e54e62", size = 30109540, upload-time = "2025-05-08T16:07:04.209Z" }, - { url = "https://files.pythonhosted.org/packages/5b/d8/59e452c0a255ec352bd0a833537a3bc1bfb679944c4938ab375b0a6b3a3e/scipy-1.15.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8c9ed3ba2c8a2ce098163a9bdb26f891746d02136995df25227a20e71c396ebb", size = 22383115, upload-time = "2025-05-08T16:07:08.998Z" }, - { url = "https://files.pythonhosted.org/packages/08/f5/456f56bbbfccf696263b47095291040655e3cbaf05d063bdc7c7517f32ac/scipy-1.15.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:0bdd905264c0c9cfa74a4772cdb2070171790381a5c4d312c973382fc6eaf730", size = 25163884, upload-time = "2025-05-08T16:07:14.091Z" }, - { url = "https://files.pythonhosted.org/packages/a2/66/a9618b6a435a0f0c0b8a6d0a2efb32d4ec5a85f023c2b79d39512040355b/scipy-1.15.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79167bba085c31f38603e11a267d862957cbb3ce018d8b38f79ac043bc92d825", size = 35174018, upload-time = "2025-05-08T16:07:19.427Z" }, - { url = "https://files.pythonhosted.org/packages/b5/09/c5b6734a50ad4882432b6bb7c02baf757f5b2f256041da5df242e2d7e6b6/scipy-1.15.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9deabd6d547aee2c9a81dee6cc96c6d7e9a9b1953f74850c179f91fdc729cb7", size = 37269716, upload-time = "2025-05-08T16:07:25.712Z" }, - { url = "https://files.pythonhosted.org/packages/77/0a/eac00ff741f23bcabd352731ed9b8995a0a60ef57f5fd788d611d43d69a1/scipy-1.15.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dde4fc32993071ac0c7dd2d82569e544f0bdaff66269cb475e0f369adad13f11", size = 36872342, upload-time = "2025-05-08T16:07:31.468Z" }, - { url = "https://files.pythonhosted.org/packages/fe/54/4379be86dd74b6ad81551689107360d9a3e18f24d20767a2d5b9253a3f0a/scipy-1.15.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f77f853d584e72e874d87357ad70f44b437331507d1c311457bed8ed2b956126", size = 39670869, upload-time = "2025-05-08T16:07:38.002Z" }, - { url = "https://files.pythonhosted.org/packages/87/2e/892ad2862ba54f084ffe8cc4a22667eaf9c2bcec6d2bff1d15713c6c0703/scipy-1.15.3-cp313-cp313-win_amd64.whl", hash = "sha256:b90ab29d0c37ec9bf55424c064312930ca5f4bde15ee8619ee44e69319aab163", size = 40988851, upload-time = "2025-05-08T16:08:33.671Z" }, - { url = "https://files.pythonhosted.org/packages/1b/e9/7a879c137f7e55b30d75d90ce3eb468197646bc7b443ac036ae3fe109055/scipy-1.15.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3ac07623267feb3ae308487c260ac684b32ea35fd81e12845039952f558047b8", size = 38863011, upload-time = "2025-05-08T16:07:44.039Z" }, - { url = "https://files.pythonhosted.org/packages/51/d1/226a806bbd69f62ce5ef5f3ffadc35286e9fbc802f606a07eb83bf2359de/scipy-1.15.3-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:6487aa99c2a3d509a5227d9a5e889ff05830a06b2ce08ec30df6d79db5fcd5c5", size = 30266407, upload-time = "2025-05-08T16:07:49.891Z" }, - { url = "https://files.pythonhosted.org/packages/e5/9b/f32d1d6093ab9eeabbd839b0f7619c62e46cc4b7b6dbf05b6e615bbd4400/scipy-1.15.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:50f9e62461c95d933d5c5ef4a1f2ebf9a2b4e83b0db374cb3f1de104d935922e", size = 22540030, upload-time = "2025-05-08T16:07:54.121Z" }, - { url = "https://files.pythonhosted.org/packages/e7/29/c278f699b095c1a884f29fda126340fcc201461ee8bfea5c8bdb1c7c958b/scipy-1.15.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:14ed70039d182f411ffc74789a16df3835e05dc469b898233a245cdfd7f162cb", size = 25218709, upload-time = "2025-05-08T16:07:58.506Z" }, - { url = "https://files.pythonhosted.org/packages/24/18/9e5374b617aba742a990581373cd6b68a2945d65cc588482749ef2e64467/scipy-1.15.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a769105537aa07a69468a0eefcd121be52006db61cdd8cac8a0e68980bbb723", size = 34809045, upload-time = "2025-05-08T16:08:03.929Z" }, - { url = "https://files.pythonhosted.org/packages/e1/fe/9c4361e7ba2927074360856db6135ef4904d505e9b3afbbcb073c4008328/scipy-1.15.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9db984639887e3dffb3928d118145ffe40eff2fa40cb241a306ec57c219ebbbb", size = 36703062, upload-time = "2025-05-08T16:08:09.558Z" }, - { url = "https://files.pythonhosted.org/packages/b7/8e/038ccfe29d272b30086b25a4960f757f97122cb2ec42e62b460d02fe98e9/scipy-1.15.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:40e54d5c7e7ebf1aa596c374c49fa3135f04648a0caabcb66c52884b943f02b4", size = 36393132, upload-time = "2025-05-08T16:08:15.34Z" }, - { url = "https://files.pythonhosted.org/packages/10/7e/5c12285452970be5bdbe8352c619250b97ebf7917d7a9a9e96b8a8140f17/scipy-1.15.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5e721fed53187e71d0ccf382b6bf977644c533e506c4d33c3fb24de89f5c3ed5", size = 38979503, upload-time = "2025-05-08T16:08:21.513Z" }, - { url = "https://files.pythonhosted.org/packages/81/06/0a5e5349474e1cbc5757975b21bd4fad0e72ebf138c5592f191646154e06/scipy-1.15.3-cp313-cp313t-win_amd64.whl", hash = "sha256:76ad1fb5f8752eabf0fa02e4cc0336b4e8f021e2d5f061ed37d6d264db35e3ca", size = 40308097, upload-time = "2025-05-08T16:08:27.627Z" }, ] [[package]] @@ -1221,16 +1033,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload-time = "2024-11-27T22:38:17.645Z" }, { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload-time = "2024-11-27T22:38:19.159Z" }, { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload-time = "2024-11-27T22:38:20.064Z" }, - { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708, upload-time = "2024-11-27T22:38:21.659Z" }, - { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582, upload-time = "2024-11-27T22:38:22.693Z" }, - { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543, upload-time = "2024-11-27T22:38:24.367Z" }, - { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691, upload-time = "2024-11-27T22:38:26.081Z" }, - { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170, upload-time = "2024-11-27T22:38:27.921Z" }, - { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530, upload-time = "2024-11-27T22:38:29.591Z" }, - { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666, upload-time = "2024-11-27T22:38:30.639Z" }, - { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954, upload-time = "2024-11-27T22:38:31.702Z" }, - { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724, upload-time = "2024-11-27T22:38:32.837Z" }, - { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383, upload-time = "2024-11-27T22:38:34.455Z" }, { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, ] From 41ca9aafcdc920b1db6514680e6d2122933959ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20H=C3=B6lscher?= Date: Fri, 11 Jul 2025 08:28:19 +0200 Subject: [PATCH 11/16] Update test assertions --- tests/test_fields.py | 83 ++++++++++++++++++++++++++++++-------------- 1 file changed, 56 insertions(+), 27 deletions(-) diff --git a/tests/test_fields.py b/tests/test_fields.py index 355eaea..f546b40 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -10,8 +10,8 @@ # -------- Global variables -------- NUMBER_OF_TIME_STEPS = 1000 -MAX_ERROR = 1e-15 - +ATOL = 1e-14 +RTOL = 1e-7 pic255 = plutho.MaterialData( **{ @@ -34,15 +34,6 @@ } ) -# -------- Helper functions -------- - - -def compare_numpy_array(a, b): - if a.shape != b.shape: - return False - - return np.allclose(a, b, atol=MAX_ERROR) - # -------- Test functions -------- @@ -201,19 +192,30 @@ def test_piezo_time(tmp_path, test=True): # Compare arrays # For displacement just take last time step. TODO Is this sufficient? - assert compare_numpy_array(uut_u, test_u), \ - "Displacement u is not equal" - assert compare_numpy_array(uut_q, test_q), "Charge is not equal" + np.testing.assert_allclose( + uut_u, + test_u, + rtol=RTOL, + atol=ATOL, + err_msg="Displacement u is not equal" + ) + np.testing.assert_allclose( + uut_q, + test_q, + rtol=RTOL, + atol=ATOL, + err_msg="Charge is not equal" + ) else: # Save results np.save( os.path.join(tmp_path, test_folder_name, "q.npy"), - sim.solver.q + uut_q ) np.save( os.path.join(tmp_path, test_folder_name, "u.npy"), - sim.solver.u[:, -1] + uut_u ) @@ -290,19 +292,30 @@ def test_piezo_freq(tmp_path, test=True): # Compare arrays # For displacement just take last time step. TODO Is this sufficient? - assert compare_numpy_array(uut_u, test_u), \ - "Displacement u is not equal" - assert compare_numpy_array(uut_q, test_q), "Charge is not equal" + np.testing.assert_allclose( + uut_u, + test_u, + rtol=RTOL, + atol=ATOL, + err_msg="Displacement u is not equal" + ) + np.testing.assert_allclose( + uut_q, + test_q, + rtol=RTOL, + atol=ATOL, + err_msg="Charge is not equal" + ) else: # Save test data np.save( os.path.join(tmp_path, test_folder_name, "q.npy"), - sim.solver.q + uut_q ) np.save( os.path.join(tmp_path, test_folder_name, "u.npy"), - sim.solver.u[:, -1] + uut_u ) @@ -422,7 +435,7 @@ def test_thermo_piezo_time(tmp_path, test=True): # Maybe the signals are not fully dissipated? assert np.abs(input_energy - total_loss_energy) < 1e-11, \ "Input energy does not equal total loss energy" - assert np.abs(total_loss_energy - stored_thermal_energy) < MAX_ERROR, \ + assert np.abs(total_loss_energy - stored_thermal_energy) < ATOL, \ "Total loss energy does not equal stored thermal energy" test_folder_name = "thermo_piezo_time" @@ -444,11 +457,27 @@ def test_thermo_piezo_time(tmp_path, test=True): uut_mech_loss = sim.solver.mech_loss[:, -1] # Compare arrays - assert compare_numpy_array(uut_q, test_q), "Charge is not equal" - assert compare_numpy_array(uut_u, test_u), \ - "Displacement u is not equal" - assert compare_numpy_array(uut_mech_loss, test_mech_loss), \ - "Mech loss is not equal" + np.testing.assert_allclose( + uut_u, + test_u, + rtol=RTOL, + atol=ATOL, + err_msg="Displacement u is not equal" + ) + np.testing.assert_allclose( + uut_q, + test_q, + rtol=RTOL, + atol=ATOL, + err_msg="Charge is not equal" + ) + np.testing.assert_allclose( + uut_mech_loss, + test_mech_loss, + rtol=RTOL, + atol=ATOL, + err_msg="Charge is not equal" + ) else: # Save data np.save( From 9519873087c72a2b6a1e3e3075a4bdbed331872a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20H=C3=B6lscher?= Date: Fri, 11 Jul 2025 08:48:56 +0200 Subject: [PATCH 12/16] Adjust ATOL --- tests/test_fields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_fields.py b/tests/test_fields.py index f546b40..1ddf3dc 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -10,7 +10,7 @@ # -------- Global variables -------- NUMBER_OF_TIME_STEPS = 1000 -ATOL = 1e-14 +ATOL = 1e-13 RTOL = 1e-7 pic255 = plutho.MaterialData( From 52ab44de99fabefe92c8956d06253cecc32685c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20H=C3=B6lscher?= Date: Mon, 14 Jul 2025 09:30:23 +0200 Subject: [PATCH 13/16] Update RTOL and ATOL --- tests/test_fields.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_fields.py b/tests/test_fields.py index 1ddf3dc..3856a6f 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -10,8 +10,8 @@ # -------- Global variables -------- NUMBER_OF_TIME_STEPS = 1000 -ATOL = 1e-13 -RTOL = 1e-7 +ATOL = 1e-12 +RTOL = 1e-6 pic255 = plutho.MaterialData( **{ From 21052ef803d82add417c35031679b5dcf7ff8578 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20H=C3=B6lscher?= Date: Mon, 14 Jul 2025 10:30:40 +0200 Subject: [PATCH 14/16] Github workflow test update python version --- .github/workflows/python-app.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 2ad4b0f..e581eeb 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -19,10 +19,10 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Set up Python 3.10 + - name: Set up Python 3.12.4 uses: actions/setup-python@v3 with: - python-version: "3.10" + python-version: "3.12.4" - name: Install dependencies run: | sudo apt-get update From 2973a2326333b5b514ffe72658f47a0b0716d18d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20H=C3=B6lscher?= Date: Mon, 14 Jul 2025 15:34:44 +0200 Subject: [PATCH 15/16] Set fixed gmsh version dependency --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 0822220..0a4ec32 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,7 @@ dependencies = [ "numpy", "matplotlib", "scipy>=1.12.0", - "gmsh", + "gmsh==4.13.1", "python-dotenv", "pyyaml", "flake8>=7.2.0", From 769bbb7e86bbed8f700e1a4b71834a4390ca4b56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20H=C3=B6lscher?= Date: Mon, 14 Jul 2025 15:44:32 +0200 Subject: [PATCH 16/16] Reset ATOL --- tests/test_fields.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_fields.py b/tests/test_fields.py index 3856a6f..f5b00f1 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -10,8 +10,8 @@ # -------- Global variables -------- NUMBER_OF_TIME_STEPS = 1000 -ATOL = 1e-12 -RTOL = 1e-6 +ATOL = 1e-15 +RTOL = 1e-7 pic255 = plutho.MaterialData( **{