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 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..be69498 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,36 +62,85 @@ def _get_nodes(node_tags, node_coords, _=None) -> npt.NDArray: return nodes - @staticmethod def _get_elements( + self, 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. Similar to the element - dimension so 2 equals triangles. - 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, ...]. """ - elements = np.zeros(shape=(len(element_tags[1]), 3), dtype=int) + 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): - 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) + 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 + ) - return elements + 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] + + # 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[ + 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(current_element_tags), nodes_per_element), + dtype=int + ) + for index, element in zip(element_indices, elements): + elements_np[index] = element + + elements_per_type[element_type] = 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. @@ -80,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()) + + 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 @@ -122,41 +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.""" - pg_tags = self.get_nodes_by_physical_groups(needed_pg_names) - elements = self._get_elements(*gmsh.model.mesh.getElements()) - 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. - 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 - - if found_count > 1: - 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 c746ae7..6de14a0 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 @@ -22,15 +23,18 @@ class Mesh: mesh_file_path: str file_version: str parser: Union[GmshParser, CustomParser] + 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.mesh_file_path = file_path + self.element_order = element_order # Check gmsh version # If version2 -> custom gmsh parser @@ -46,9 +50,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() @@ -102,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. @@ -121,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/postprocessing.py b/plutho/postprocessing.py index 4511245..1b74d3e 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( @@ -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() - 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() - 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 94e10f6..2a350e8 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,91 +145,167 @@ def load_from_file(file_path: str): # -------- Local functions and integrals -------- -def local_shape_functions(*dimensions: float): - """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. +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. 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 """ - 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" - ) + L1 = 1-s-t + L2 = s + L3 = t + + match element_order: + case 1: + return np.array([L1, L2, L3]) + case 2: + 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( + [ + 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 " + f"{element_order}" + ) + -def gradient_local_shape_functions(dim: int = 2): - """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) -> 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 """ - 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" - ) + match element_order: + case 1: + return np.array([ + [-1, 1, 0], # d_s + [-1, 0, 1] # d_t + ]) + 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: + 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 " + 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" ) - return np.dot(node_points, local_shape_functions(*dimensions)) + 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, - *dimensions: float + s: float, + t: float, + element_order: int ): """Calculates the B operator for the local coordinantes which is needed for voigt-notation. @@ -246,29 +314,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 = len(dimensions) + 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(*dimensions) - r = local_to_global_coordinates(node_points, *dimensions)[0] + 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) - dn = gradient_local_shape_functions(dim) + 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*nodes_per_element)) + for d in range(nodes_per_element): b[:, 2*d:2*d+2] = [ [ global_dn[0][d], 0 @@ -287,126 +356,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): - n = local_shape_functions(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, element_order) # 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() 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() + 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. @@ -415,17 +508,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(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. @@ -433,46 +531,129 @@ 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 + 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 Can this made faster using numpy? + sum = 0 + for i in range(len(weights)): + sum += weights[i]*func(points[i][0], points[i][1]) + + return sum -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)) + 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? + sum = 0 + for i in range(len(weights)): + sum += weights[i]*func(points[i]) + + return sum # -------- Boundary condition functions -------- @@ -629,10 +810,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. @@ -643,49 +825,45 @@ def create_local_element_data( Returns: List of LocalElementData objects. """ - local_element_data = [] - dn = gradient_local_shape_functions(2) - 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 7e118c8..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, \ - 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() - 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 5b8f5fc..b0f834a 100644 --- a/plutho/simulation/piezo_time.py +++ b/plutho/simulation/piezo_time.py @@ -9,10 +9,10 @@ # 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 + 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() + 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(2) - 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,30 @@ 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 +213,7 @@ class PiezoSimTime: q: npt.NDArray # Internal simulation data - local_elements: List[LocalElementData] + node_points: npt.NDArray def __init__( self, @@ -232,7 +234,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 +256,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 +265,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 +442,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 438b164..6473271 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, 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,15 @@ 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 +121,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 +131,7 @@ class ThermoPiezoSimTime: mech_loss: npt.NDArray # Internal simulation data - local_elements: List[LocalElementData] + node_points: npt.NDArray def __init__( self, @@ -139,7 +152,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 +176,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 +185,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 +313,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 +348,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 +425,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 +455,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 +497,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 +515,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() - 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_theta[global_p] += f_theta_e[local_p] diff --git a/plutho/simulation/thermo_time.py b/plutho/simulation/thermo_time.py index 4611517..67343b2 100644 --- a/plutho/simulation/thermo_time.py +++ b/plutho/simulation/thermo_time.py @@ -11,77 +11,92 @@ # Local libraries from .base import SimulationData, MeshData, \ - local_shape_functions, gradient_local_shape_functions, \ - local_to_global_coordinates, integral_m, LocalElementData, \ - quadratic_quadrature, line_quadrature, create_local_element_data, \ + local_shape_functions_2d, gradient_local_shape_functions_2d, \ + 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(s) - r = local_to_global_coordinates(node_points, s)[0] + 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() + 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): - n = local_shape_functions(s, t) - r = local_to_global_coordinates(node_points, s, t)[0] - return n*mech_loss*r + dn = gradient_local_shape_functions_2d(s, t, element_order) + jacobian = np.dot(node_points, dn.T) + jacobian_det = np.linalg.det(jacobian) - return quadratic_quadrature(inner) + 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 + + return quadratic_quadrature(inner, element_order) class ThermoSimTime: @@ -105,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] @@ -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() - # 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) - * self.material_manager.get_density(element) - * self.material_manager.get_heat_capacity(element) - * jacobian_det * 2 * np.pi + integral_m(node_points, element_order) + * self.material_manager.get_density(element_index) + * self.material_manager.get_heat_capacity(element_index) + * 2 * np.pi ) ktheta_e = ( integral_ktheta( node_points, - jacobian_inverted_t) - * self.material_manager.get_thermal_conductivity(element) - * jacobian_det * 2 * np.pi + element_order + ) + * self.material_manager.get_thermal_conductivity(element_index) + * 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() - 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, @@ -245,28 +211,27 @@ 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 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() - 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 ) @@ -426,7 +411,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 @@ -457,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. @@ -510,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 @@ -547,4 +534,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/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/pyproject.toml b/pyproject.toml index 2a233d8..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", @@ -51,4 +51,5 @@ plutho = { workspace = true } [dependency-groups] dev = [ "plutho", + "pytest>=8.4.1", ] diff --git a/tests/data/piezo_freq/q.npy b/tests/data/piezo_freq/q.npy index 3bf3155..a40a022 100644 Binary files a/tests/data/piezo_freq/q.npy and b/tests/data/piezo_freq/q.npy differ diff --git a/tests/data/piezo_freq/u.npy b/tests/data/piezo_freq/u.npy index f8389f1..e8751f2 100644 Binary files a/tests/data/piezo_freq/u.npy and b/tests/data/piezo_freq/u.npy differ diff --git a/tests/data/piezo_time/q.npy b/tests/data/piezo_time/q.npy index a0a585a..805b25b 100644 Binary files a/tests/data/piezo_time/q.npy and b/tests/data/piezo_time/q.npy differ diff --git a/tests/data/piezo_time/u.npy b/tests/data/piezo_time/u.npy index 67083bc..0be3d4b 100644 Binary files a/tests/data/piezo_time/u.npy and b/tests/data/piezo_time/u.npy differ diff --git a/tests/data/thermo_piezo_time/mech_loss.npy b/tests/data/thermo_piezo_time/mech_loss.npy index f73ba23..272087a 100644 Binary files a/tests/data/thermo_piezo_time/mech_loss.npy and b/tests/data/thermo_piezo_time/mech_loss.npy differ diff --git a/tests/data/thermo_piezo_time/q.npy b/tests/data/thermo_piezo_time/q.npy index 5e5dbdd..6dd67be 100644 Binary files a/tests/data/thermo_piezo_time/q.npy and b/tests/data/thermo_piezo_time/q.npy differ diff --git a/tests/data/thermo_piezo_time/u.npy b/tests/data/thermo_piezo_time/u.npy index 6aa166a..59cbec9 100644 Binary files a/tests/data/thermo_piezo_time/u.npy and b/tests/data/thermo_piezo_time/u.npy differ diff --git a/tests/test_simulations.py b/tests/test_fields.py similarity index 81% rename from tests/test_simulations.py rename to tests/test_fields.py index 424511f..f5b00f1 100644 --- a/tests/test_simulations.py +++ b/tests/test_fields.py @@ -10,8 +10,8 @@ # -------- Global variables -------- NUMBER_OF_TIME_STEPS = 1000 -MAX_ERROR = 1e-15 - +ATOL = 1e-15 +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 -------- @@ -52,9 +43,13 @@ 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) + plutho.Mesh.generate_rectangular_mesh( + mesh_path, + element_order=element_order + ) + mesh = plutho.Mesh(mesh_path, element_order) sim = plutho.SingleSimulation( tmp_path, @@ -68,6 +63,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, @@ -93,12 +91,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 @@ -113,9 +115,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) + plutho.Mesh.generate_rectangular_mesh( + mesh_path, + element_order=element_order + ) + mesh = plutho.Mesh(mesh_path, element_order) sim = plutho.SingleSimulation( tmp_path, @@ -186,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 ) @@ -207,9 +224,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) + plutho.Mesh.generate_rectangular_mesh( + mesh_path, + element_order=element_order + ) + mesh = plutho.Mesh(mesh_path, element_order) sim = plutho.SingleSimulation( tmp_path, @@ -271,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 ) @@ -294,9 +326,13 @@ 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) + plutho.Mesh.generate_rectangular_mesh( + mesh_path, + element_order=element_order + ) + mesh = plutho.Mesh(mesh_path, element_order) sim = plutho.SingleSimulation( tmp_path, @@ -371,7 +407,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): @@ -389,6 +425,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) ) @@ -398,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" @@ -419,19 +456,28 @@ 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), \ - "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( 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" }, ]