1- import numpy as np
2-
3- def CubeMap_torch (arr , multi_marker = True ):
4- import torch
5-
6- # Check if GPU is available and choose the device
7- device = torch .device ('cuda' if torch .cuda .is_available () else 'cpu' )
8-
9- # Convert the input array to a PyTorch tensor and move it to the selected device
10- arr_tensor = torch .tensor (arr , dtype = torch .int32 , device = device )
11-
12- nx , ny , nz = arr_tensor .shape
13- Nx , Ny , Nz = 2 * nx , 2 * ny , 2 * nz
14- cubemap = torch .zeros ((Nx , Ny , Nz ), dtype = torch .int32 , device = device )
15-
16- # Define markers
17- markers = (1 , 1 , 1 , 1 )
18- if multi_marker :
19- markers = (1 , 2 , 3 , 4 )
20-
21- # Set the vertices
22- cubemap [2 * arr_tensor .bool ()] = markers [0 ]
23-
24- # Convert indices for easier manipulation
25- i_indices = torch .arange (Nx , device = device )
26- j_indices = torch .arange (Ny , device = device )
27- k_indices = torch .arange (Nz , device = device )
28-
29- i_indices , j_indices , k_indices = torch .meshgrid (i_indices , j_indices , k_indices , indexing = 'ij' )
30-
31- # Define a mask to select non-zero values
32- mask = cubemap == 0
33-
34- # Edges
35- edge_mask_1 = (cubemap [(i_indices - 1 ) % Nx , j_indices , k_indices ] == markers [0 ]) & (cubemap [(i_indices + 1 ) % Nx , j_indices , k_indices ] == markers [0 ])
36- edge_mask_2 = (cubemap [i_indices , (j_indices - 1 ) % Ny , k_indices ] == markers [0 ]) & (cubemap [i_indices , (j_indices + 1 ) % Ny , k_indices ] == markers [0 ])
37- edge_mask_3 = (cubemap [i_indices , j_indices , (k_indices - 1 ) % Nz ] == markers [0 ]) & (cubemap [i_indices , j_indices , (k_indices + 1 ) % Nz ] == markers [0 ])
38-
39- cubemap [mask & (edge_mask_1 | edge_mask_2 | edge_mask_3 )] = markers [1 ]
40-
41- # Faces
42- face_mask_1 = (cubemap [(i_indices - 1 ) % Nx , j_indices , k_indices ] == markers [1 ]) & (cubemap [(i_indices + 1 ) % Nx , j_indices , k_indices ] == markers [1 ]) & \
43- (cubemap [i_indices , (j_indices - 1 ) % Ny , k_indices ] == markers [1 ]) & (cubemap [i_indices , (j_indices + 1 ) % Ny , k_indices ] == markers [1 ])
44- face_mask_2 = (cubemap [i_indices , (j_indices - 1 ) % Ny , k_indices ] == markers [1 ]) & (cubemap [i_indices , (j_indices + 1 ) % Ny , k_indices ] == markers [1 ]) & \
45- (cubemap [i_indices , j_indices , (k_indices - 1 ) % Nz ] == markers [1 ]) & (cubemap [i_indices , j_indices , (k_indices + 1 ) % Nz ] == markers [1 ])
46- face_mask_3 = (cubemap [i_indices , j_indices , (k_indices - 1 ) % Nz ] == markers [1 ]) & (cubemap [i_indices , j_indices , (k_indices + 1 ) % Nz ] == markers [1 ]) & \
47- (cubemap [(i_indices - 1 ) % Nx , j_indices , k_indices ] == markers [1 ]) & (cubemap [(i_indices + 1 ) % Nx , j_indices , k_indices ] == markers [1 ])
48-
49- cubemap [mask & (face_mask_1 | face_mask_2 | face_mask_3 )] = markers [2 ]
50-
51- # Cubes
52- cube_mask = (cubemap [(i_indices - 1 ) % Nx , j_indices , k_indices ] == markers [2 ]) & (cubemap [(i_indices + 1 ) % Nx , j_indices , k_indices ] == markers [2 ]) & \
53- (cubemap [i_indices , (j_indices - 1 ) % Ny , k_indices ] == markers [2 ]) & (cubemap [i_indices , (j_indices + 1 ) % Ny , k_indices ] == markers [2 ]) & \
54- (cubemap [i_indices , j_indices , (k_indices - 1 ) % Nz ] == markers [2 ]) & (cubemap [i_indices , j_indices , (k_indices + 1 ) % Nz ] == markers [2 ])
55-
56- cubemap [mask & cube_mask ] = markers [3 ]
57-
58- # Move the result back to the CPU and convert to numpy array
59- return cubemap .cpu ().numpy ()
1+ # ViteBetti.py
602
3+ import numpy as np
4+ import os
5+
6+ # --- Optional Numba Support ---
7+ try :
8+ from numba import jit
9+ numba_available = True
10+ except ImportError :
11+ numba_available = False
12+ # Define a dummy decorator if numba is not available
13+ def jit (func , * args , ** kwargs ):
14+ return func
15+
16+ # --- Optional Cython Support ---
17+ try :
18+ from .ViteBetti_cython import CubeMap as cython_CubeMap
19+ cython_available = True
20+ except ImportError :
21+ cython_CubeMap = None
22+ cython_available = False
23+
24+ # --- Optional Joblib Support ---
25+ try :
26+ from joblib import Parallel , delayed
27+ from multiprocessing import shared_memory
28+ joblib_available = True
29+ except ImportError :
30+ joblib_available = False
31+ Parallel , delayed , shared_memory = None , None , None
32+
33+ # --- Core Algorithm (Pure Python) ---
6134def CubeMap (arr , multi_marker = True ):
62- nx , ny , nz = arr .shape
63- Nx , Ny , Nz = 2 * nx ,2 * ny ,2 * nz #2*nx-1,2*ny-1,2*nz-1
64- cubemap = np .zeros ((Nx ,Ny ,Nz ))
65- markers = 1 , 1 , 1 , 1
66- if multi_marker : markers = 1 , 2 , 3 , 4
67- ## Vertices
68- for i in range (nx ):
69- for j in range (ny ):
70- for k in range (nz ):
71- if arr [i ,j ,k ]: cubemap [i * 2 ,j * 2 ,k * 2 ] = markers [0 ]
72-
73- ## Edges
74- for i in range (Nx ):
75- for j in range (Ny ):
76- for k in range (Nz ):
77- if cubemap [i ,j ,k ] == 0 :
78- if cubemap [(i - 1 ),j ,k ]== markers [0 ] and cubemap [(i + 1 )% Nx ,j ,k ]== markers [0 ]: cubemap [i ,j ,k ] = markers [1 ]
79- elif cubemap [i ,(j - 1 ),k ]== markers [0 ] and cubemap [i ,(j + 1 )% Ny ,k ]== markers [0 ]: cubemap [i ,j ,k ] = markers [1 ]
80- elif cubemap [i ,j ,(k - 1 )]== markers [0 ] and cubemap [i ,j ,(k + 1 )% Nz ]== markers [0 ]: cubemap [i ,j ,k ] = markers [1 ]
81-
82- ## Faces
83- for i in range (Nx ):
84- for j in range (Ny ):
85- for k in range (Nz ):
86- if cubemap [i ,j ,k ] == 0 :
87- if cubemap [(i - 1 ),j ,k ]== markers [1 ] and cubemap [(i + 1 )% Nx ,j ,k ]== markers [1 ] and cubemap [i ,(j - 1 ),k ]== markers [1 ] and cubemap [i ,(j + 1 )% Ny ,k ]== markers [1 ]: cubemap [i ,j ,k ] = markers [2 ]
88- elif cubemap [i ,(j - 1 ),k ]== markers [1 ] and cubemap [i ,(j + 1 )% Ny ,k ]== markers [1 ] and cubemap [i ,j ,(k - 1 )]== markers [1 ] and cubemap [i ,j ,(k + 1 )% Nz ]== markers [1 ]: cubemap [i ,j ,k ] = markers [2 ]
89- elif cubemap [i ,j ,(k - 1 )]== markers [1 ] and cubemap [i ,j ,(k + 1 )% Nz ]== markers [1 ] and cubemap [(i - 1 ),j ,k ]== markers [1 ] and cubemap [(i + 1 )% Nx ,j ,k ]== markers [1 ]: cubemap [i ,j ,k ] = markers [2 ]
90-
91- ## Cubes
92- for i in range (Nx ):
93- for j in range (Ny ):
94- for k in range (Nz ):
95- if cubemap [i ,j ,k ] == 0 :
96- if cubemap [(i - 1 ),j ,k ]== markers [2 ] and cubemap [(i + 1 )% Nx ,j ,k ]== markers [2 ]:
97- if cubemap [i ,(j - 1 ),k ]== markers [2 ] and cubemap [i ,(j + 1 )% Ny ,k ]== markers [2 ]:
98- if cubemap [i ,j ,(k - 1 )]== markers [2 ] and cubemap [i ,j ,(k + 1 )% Nz ]== markers [2 ]: cubemap [i ,j ,k ] = markers [3 ]
99-
100- return cubemap
101-
102- def EulerCharacteristic_seq (A ):
103- chi = 0
104- nx ,ny ,nz = A .shape
105- for x in range (nx ):
106- for y in range (ny ):
107- for z in range (nz ):
108- if (A [x ,y ,z ] == 1 ):
109- if (x + y + z )% 2 == 0 : chi += 1
110- else : chi -= 1
111- return chi
112-
113-
114- import jax
115- import jax .numpy as jnp
116-
117- @jax .jit
118- def CubeMap_jax (arr , multi_marker = True ):
35+ """
36+ Generates a cubical complex map from a binary 3D array.
37+ This is the pure Python implementation which serves as a fallback.
38+ """
39+ nx , ny , nz = arr .shape
40+ Nx , Ny , Nz = 2 * nx , 2 * ny , 2 * nz
41+ cubemap = np .zeros ((Nx , Ny , Nz ), dtype = np .int32 )
42+
11943 markers = (1 , 1 , 1 , 1 )
12044 if multi_marker :
12145 markers = (1 , 2 , 3 , 4 )
12246
123- nx , ny , nz = arr .shape
124- Nx , Ny , Nz = 2 * nx , 2 * ny , 2 * nz
125- cubemap = jnp .zeros ((Nx , Ny , Nz ), dtype = jnp .int32 )
47+ # Vertices (1)
48+ for i in range (nx ):
49+ for j in range (ny ):
50+ for k in range (nz ):
51+ if arr [i , j , k ]:
52+ cubemap [i * 2 , j * 2 , k * 2 ] = markers [0 ]
53+
54+ # Edges (2)
55+ for i in range (Nx ):
56+ for j in range (Ny ):
57+ for k in range (Nz ):
58+ if cubemap [i , j , k ] == 0 :
59+ # Check for neighbors along each axis using explicit periodic boundaries
60+ if cubemap [(i - 1 ) % Nx , j , k ] == markers [0 ] and cubemap [(i + 1 ) % Nx , j , k ] == markers [0 ]:
61+ cubemap [i , j , k ] = markers [1 ]
62+ elif cubemap [i , (j - 1 ) % Ny , k ] == markers [0 ] and cubemap [i , (j + 1 ) % Ny , k ] == markers [0 ]:
63+ cubemap [i , j , k ] = markers [1 ]
64+ elif cubemap [i , j , (k - 1 ) % Nz ] == markers [0 ] and cubemap [i , j , (k + 1 ) % Nz ] == markers [0 ]:
65+ cubemap [i , j , k ] = markers [1 ]
66+
67+ # Faces (3)
68+ for i in range (Nx ):
69+ for j in range (Ny ):
70+ for k in range (Nz ):
71+ if cubemap [i , j , k ] == 0 :
72+ if (cubemap [(i - 1 ) % Nx , j , k ] == markers [1 ] and cubemap [(i + 1 ) % Nx , j , k ] == markers [1 ] and
73+ cubemap [i , (j - 1 ) % Ny , k ] == markers [1 ] and cubemap [i , (j + 1 ) % Ny , k ] == markers [1 ]):
74+ cubemap [i , j , k ] = markers [2 ]
75+ elif (cubemap [i , (j - 1 ) % Ny , k ] == markers [1 ] and cubemap [i , (j + 1 ) % Ny , k ] == markers [1 ] and
76+ cubemap [i , j , (k - 1 ) % Nz ] == markers [1 ] and cubemap [i , j , (k + 1 ) % Nz ] == markers [1 ]):
77+ cubemap [i , j , k ] = markers [2 ]
78+ elif (cubemap [i , j , (k - 1 ) % Nz ] == markers [1 ] and cubemap [i , j , (k + 1 ) % Nz ] == markers [1 ] and
79+ cubemap [(i - 1 ) % Nx , j , k ] == markers [1 ] and cubemap [(i + 1 ) % Nx , j , k ] == markers [1 ]):
80+ cubemap [i , j , k ] = markers [2 ]
81+
82+ # Cubes (4)
83+ for i in range (Nx ):
84+ for j in range (Ny ):
85+ for k in range (Nz ):
86+ if cubemap [i , j , k ] == 0 :
87+ if (cubemap [(i - 1 ) % Nx , j , k ] == markers [2 ] and cubemap [(i + 1 ) % Nx , j , k ] == markers [2 ] and
88+ cubemap [i , (j - 1 ) % Ny , k ] == markers [2 ] and cubemap [i , (j + 1 ) % Ny , k ] == markers [2 ] and
89+ cubemap [i , j , (k - 1 ) % Nz ] == markers [2 ] and cubemap [i , j , (k + 1 ) % Nz ] == markers [2 ]):
90+ cubemap [i , j , k ] = markers [3 ]
12691
127- # Step 1: Vertices
128- coords = jnp .argwhere (arr == 1 )
129- vert_indices = coords * 2
130- cubemap = cubemap .at [tuple (vert_indices .T )].set (markers [0 ])
131-
132- # Step 2: Edges (use jnp.roll for periodic neighbor checking)
133- mask = cubemap == 0
134- m0 = markers [0 ]
135- m1 = markers [1 ]
136-
137- def edge_mask_axis (cmap , axis ):
138- left = jnp .roll (cmap , 1 , axis = axis )
139- right = jnp .roll (cmap , - 1 , axis = axis )
140- return (left == m0 ) & (right == m0 )
141-
142- edge_mask = edge_mask_axis (cubemap , 0 ) | edge_mask_axis (cubemap , 1 ) | edge_mask_axis (cubemap , 2 )
143- cubemap = jnp .where (mask & edge_mask , m1 , cubemap )
92+ return cubemap
14493
145- # Further steps: similar idea, but masks get more complex
94+ # --- Accelerated Versions ---
95+ CubeMap_numba = jit (CubeMap , nopython = True ) if numba_available else None
96+ CubeMap_cython = cython_CubeMap # Assigned from the import attempt above
97+
98+ # --- Joblib Parallel Implementation ---
99+ def _CubeMap_joblib_worker (shm_name , arr_shape , i_start , i_end , arr_for_vertices , markers , stage ):
100+ """Worker function for joblib with the complete and correct logic."""
101+ existing_shm = shared_memory .SharedMemory (name = shm_name )
102+ cubemap = np .ndarray (arr_shape , dtype = np .int32 , buffer = existing_shm .buf )
103+
104+ Nx , Ny , Nz = arr_shape
105+ nx , ny , nz = arr_for_vertices .shape
106+
107+ if stage == 'vertices' :
108+ for i in range (i_start , i_end ):
109+ for j in range (ny ):
110+ for k in range (nz ):
111+ if arr_for_vertices [i , j , k ]:
112+ cubemap [i * 2 , j * 2 , k * 2 ] = markers [0 ]
113+
114+ elif stage == 'edges' :
115+ for i in range (i_start , i_end ):
116+ for j in range (Ny ):
117+ for k in range (Nz ):
118+ if cubemap [i , j , k ] == 0 :
119+ # **FIX**: Added all elif branches
120+ if cubemap [(i - 1 ) % Nx , j , k ] == markers [0 ] and cubemap [(i + 1 ) % Nx , j , k ] == markers [0 ]:
121+ cubemap [i , j , k ] = markers [1 ]
122+ elif cubemap [i , (j - 1 ) % Ny , k ] == markers [0 ] and cubemap [i , (j + 1 ) % Ny , k ] == markers [0 ]:
123+ cubemap [i , j , k ] = markers [1 ]
124+ elif cubemap [i , j , (k - 1 ) % Nz ] == markers [0 ] and cubemap [i , j , (k + 1 ) % Nz ] == markers [0 ]:
125+ cubemap [i , j , k ] = markers [1 ]
126+
127+ elif stage == 'faces' :
128+ for i in range (i_start , i_end ):
129+ for j in range (Ny ):
130+ for k in range (Nz ):
131+ if cubemap [i , j , k ] == 0 :
132+ # **FIX**: Added all elif branches
133+ if (cubemap [(i - 1 ) % Nx , j , k ] == markers [1 ] and cubemap [(i + 1 ) % Nx , j , k ] == markers [1 ] and
134+ cubemap [i , (j - 1 ) % Ny , k ] == markers [1 ] and cubemap [i , (j + 1 ) % Ny , k ] == markers [1 ]):
135+ cubemap [i , j , k ] = markers [2 ]
136+ elif (cubemap [i , (j - 1 ) % Ny , k ] == markers [1 ] and cubemap [i , (j + 1 ) % Ny , k ] == markers [1 ] and
137+ cubemap [i , j , (k - 1 ) % Nz ] == markers [1 ] and cubemap [i , j , (k + 1 ) % Nz ] == markers [1 ]):
138+ cubemap [i , j , k ] = markers [2 ]
139+ elif (cubemap [i , j , (k - 1 ) % Nz ] == markers [1 ] and cubemap [i , j , (k + 1 ) % Nz ] == markers [1 ] and
140+ cubemap [(i - 1 ) % Nx , j , k ] == markers [1 ] and cubemap [(i + 1 ) % Nx , j , k ] == markers [1 ]):
141+ cubemap [i , j , k ] = markers [2 ]
142+
143+ elif stage == 'cubes' :
144+ for i in range (i_start , i_end ):
145+ for j in range (Ny ):
146+ for k in range (Nz ):
147+ if cubemap [i , j , k ] == 0 :
148+ # **FIX**: This was already complete
149+ if (cubemap [(i - 1 ) % Nx , j , k ] == markers [2 ] and cubemap [(i + 1 ) % Nx , j , k ] == markers [2 ] and
150+ cubemap [i , (j - 1 ) % Ny , k ] == markers [2 ] and cubemap [i , (j + 1 ) % Ny , k ] == markers [2 ] and
151+ cubemap [i , j , (k - 1 ) % Nz ] == markers [2 ] and cubemap [i , j , (k + 1 ) % Nz ] == markers [2 ]):
152+ cubemap [i , j , k ] = markers [3 ]
153+
154+ existing_shm .close ()
155+
156+
157+ def CubeMap_joblib (arr , multi_marker = True , n_jobs = - 1 ):
158+ """
159+ Generates a cubical complex map from a binary 3D array using joblib for parallelism.
160+ """
161+ if not joblib_available :
162+ raise ImportError ("Joblib or its dependencies are not installed. Cannot use 'joblib' backend." )
146163
147- return cubemap
164+ nx , ny , nz = arr .shape
165+ Nx , Ny , Nz = 2 * nx , 2 * ny , 2 * nz
166+ cubemap_shape = (Nx , Ny , Nz )
167+ markers = (1 , 2 , 3 , 4 ) if multi_marker else (1 , 1 , 1 , 1 )
168+
169+ shm = shared_memory .SharedMemory (create = True , size = np .dtype (np .int32 ).itemsize * Nx * Ny * Nz )
170+ shared_cubemap = np .ndarray (cubemap_shape , dtype = np .int32 , buffer = shm .buf )
171+ shared_cubemap [:] = 0
172+
173+ if n_jobs == - 1 :
174+ n_jobs = os .cpu_count ()
175+
176+ try :
177+ for stage_name , domain_size in [('vertices' , nx ), ('edges' , Nx ), ('faces' , Nx ), ('cubes' , Nx )]:
178+ chunk_size = (domain_size + n_jobs - 1 ) // n_jobs
179+ tasks = [
180+ delayed (_CubeMap_joblib_worker )(
181+ shm .name , cubemap_shape , i * chunk_size ,
182+ min ((i + 1 ) * chunk_size , domain_size ),
183+ arr , markers , stage_name
184+ )
185+ for i in range (n_jobs )
186+ ]
187+ Parallel (n_jobs = n_jobs )(tasks )
188+
189+ result_cubemap = np .copy (shared_cubemap )
190+
191+ finally :
192+ shm .close ()
193+ shm .unlink ()
194+
195+ return result_cubemap
0 commit comments