• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

astro-informatics / sleplet / 19771179384

28 Nov 2025 06:13PM UTC coverage: 65.272% (-0.06%) from 65.331%
19771179384

push

github

web-flow
Port to uv (#474)

12 of 29 new or added lines in 6 files covered. (41.38%)

2 existing lines in 2 files now uncovered.

2325 of 3562 relevant lines covered (65.27%)

3.92 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

60.0
/src/sleplet/_mesh_methods.py
1
import logging
6✔
2
import pathlib
6✔
3
import typing
6✔
4

5
import igl
6✔
6
import numpy as np
6✔
7
import numpy.typing as npt
6✔
8
import platformdirs
6✔
9
import scipy.sparse.linalg as LA_sparse  # noqa: N812
6✔
10
import tomli
6✔
11

12
import sleplet._data.setup_pooch
6✔
13
import sleplet._integration_methods
6✔
14

15
_data_path = pathlib.Path(__file__).resolve().parent / "_data"
6✔
16
_logger = logging.getLogger(__name__)
6✔
17

18

19
def average_functions_on_vertices_to_faces(
6✔
20
    faces: npt.NDArray[np.int_],
21
    functions_on_vertices: npt.NDArray[np.complex128 | np.float64],
22
) -> npt.NDArray[np.float64]:
23
    """
24
    Require all functions to be defined on faces
25
    this method handles an arbitrary number of functions.
26
    """
27
    _logger.info("converting function on vertices to faces")
×
28
    # handle the case of a 1D array
29
    array_is_1d = len(functions_on_vertices.shape) == 1
×
30
    if array_is_1d:
×
31
        functions_on_vertices = functions_on_vertices.reshape(1, -1)
×
32

33
    functions_on_faces = np.zeros((functions_on_vertices.shape[0], faces.shape[0]))
×
34
    for i, f in enumerate(functions_on_vertices):
×
35
        functions_on_faces[i] = igl.average_onto_faces(faces, f)
×
36

37
    # put the vector back in 1D form
38
    return functions_on_faces.reshape(-1) if array_is_1d else functions_on_faces
×
39

40

41
def create_mesh_region(
6✔
42
    mesh_config: dict[str, typing.Any],
43
    vertices: npt.NDArray[np.float64],
44
) -> npt.NDArray[np.bool_]:
45
    """Create a boolean region for the given mesh."""
46
    return (
6✔
47
        (vertices[:, 0] >= mesh_config["XMIN"])
48
        & (vertices[:, 0] <= mesh_config["XMAX"])
49
        & (vertices[:, 1] >= mesh_config["YMIN"])
50
        & (vertices[:, 1] <= mesh_config["YMAX"])
51
        & (vertices[:, 2] >= mesh_config["ZMIN"])
52
        & (vertices[:, 2] <= mesh_config["ZMAX"])
53
    )
54

55

56
def extract_mesh_config(mesh_name: str) -> dict[str, float | int | str]:
6✔
57
    """Read in the given mesh region settings file."""
58
    with pathlib.Path.open(
6✔
59
        _data_path / f"meshes_regions_{mesh_name}.toml",
60
        "rb",
61
    ) as f:
62
        return tomli.load(f)
6✔
63

64

65
def mesh_eigendecomposition(
6✔
66
    name: str,
67
    vertices: npt.NDArray[np.float64],
68
    faces: npt.NDArray[np.int_],
69
    *,
70
    number_basis_functions: int | None = None,
71
) -> tuple[npt.NDArray[np.float64], npt.NDArray[np.float64], int]:
72
    """
73
    Compute the eigendecomposition of the mesh represented
74
    as a graph if already computed then it loads the data.
75
    """
76
    # determine number of basis functions
77
    if number_basis_functions is None:
6✔
78
        number_basis_functions = vertices.shape[0] // 4
6✔
79
    msg = (
6✔
80
        f"finding {number_basis_functions}/{vertices.shape[0]} "
81
        f"basis functions of {name} mesh",
82
    )
83
    _logger.info(msg)
6✔
84

85
    # create filenames
86
    eigd_loc = f"meshes_laplacians_basis_functions_{name}_b{number_basis_functions}"
6✔
87
    eval_loc = f"{eigd_loc}_eigenvalues.npy"
6✔
88
    evec_loc = f"{eigd_loc}_eigenvectors.npy"
6✔
89

90
    try:
6✔
91
        eigenvalues = np.load(
6✔
92
            sleplet._data.setup_pooch.find_on_pooch_then_local(eval_loc),
93
        )
94
        eigenvectors = np.load(
6✔
95
            sleplet._data.setup_pooch.find_on_pooch_then_local(evec_loc),
96
        )
97
    except TypeError:
×
98
        laplacian = _mesh_laplacian(vertices, faces)
×
99
        eigenvalues, eigenvectors = LA_sparse.eigsh(
×
100
            laplacian,
101
            k=number_basis_functions,
102
            which="LM",
103
            sigma=0,
104
        )
105
        eigenvectors = _orthonormalise_basis_functions(vertices, faces, eigenvectors.T)
×
106
        _logger.info("saving binaries...")
×
NEW
107
        save_path = platformdirs.user_data_path()
×
NEW
108
        pathlib.Path(save_path).mkdir(parents=True, exist_ok=True)
×
NEW
109
        np.save(save_path / eval_loc, eigenvalues)
×
NEW
110
        np.save(save_path / evec_loc, eigenvectors)
×
111
    return eigenvalues, eigenvectors, number_basis_functions
6✔
112

113

114
def read_mesh(
6✔
115
    mesh_config: dict[str, float | int | str],
116
) -> tuple[npt.NDArray[np.float64], npt.NDArray[np.int_]]:
117
    """Read in the given mesh."""
118
    vertices, faces = igl.read_triangle_mesh(
6✔
119
        _data_path / f"meshes_polygons_{mesh_config['FILENAME']}",
120
    )
121
    return igl.upsample(vertices, faces, number_of_subdivs=mesh_config["UPSAMPLE"])
6✔
122

123

124
def _mesh_laplacian(
6✔
125
    vertices: npt.NDArray[np.float64],
126
    faces: npt.NDArray[np.int_],
127
) -> npt.NDArray[np.float64]:
128
    """Compute the cotagent mesh laplacian."""
129
    return -igl.cotmatrix(vertices, faces)
×
130

131

132
def _orthonormalise_basis_functions(
6✔
133
    vertices: npt.NDArray[np.float64],
134
    faces: npt.NDArray[np.int_],
135
    basis_functions: npt.NDArray[np.float64],
136
) -> npt.NDArray[np.float64]:
137
    """For computing the Slepian D matrix the basis functions must be orthonormal."""
138
    _logger.info("orthonormalising basis functions")
×
139
    factor = np.zeros(basis_functions.shape[0])
×
140
    for i, phi_i in enumerate(basis_functions):
×
141
        factor[i] = sleplet._integration_methods.integrate_whole_mesh(
×
142
            vertices,
143
            faces,
144
            phi_i,
145
            phi_i,
146
        )
147
    normalisation = np.sqrt(factor).reshape(-1, 1)
×
148
    return basis_functions / normalisation
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc