├── .gitignore ├── Examples ├── DarcyFlow │ ├── driver.py │ ├── triangles.txt │ └── vertices.txt ├── HodgeDecomposition │ ├── driver.py │ ├── mesh_example.elements │ ├── mesh_example.vertices │ └── mesh_example.xml ├── MakeMesh │ ├── ExpectedOutput │ │ ├── square_8.elements │ │ ├── square_8.vertices │ │ └── square_8.xml │ ├── make_mesh.py │ ├── s.txt │ ├── square_8.elements │ ├── square_8.vertices │ ├── square_8.xml │ └── v.txt ├── Ranking │ ├── data.txt │ └── driver.py ├── ResonantCavity │ ├── driver.py │ ├── triangles.txt │ └── vertices.txt └── RipsComplex │ ├── 300pts.mtx │ └── driver.py ├── LICENSE.txt ├── README.md ├── pydec └── pydec │ ├── __init__.py │ ├── dec │ ├── __init__.py │ ├── abstract_simplicial_complex.py │ ├── cochain.py │ ├── cube_array.py │ ├── info.py │ ├── regular_cube_complex.py │ ├── rips_complex.py │ ├── simplex_array.py │ ├── simplicial_complex.py │ └── tests │ │ ├── meshes │ │ ├── README.txt │ │ ├── cube.elements │ │ ├── cube.vertices │ │ ├── cube.xml │ │ ├── sphere3.elements │ │ ├── sphere3.vertices │ │ ├── sphere3.xml │ │ ├── torus_tet.elements │ │ ├── torus_tet.vertices │ │ └── torus_tet.xml │ │ ├── test_abstract_simplicial_complex.py │ │ ├── test_cochain.py │ │ ├── test_cube_array.py │ │ ├── test_meshes.py │ │ ├── test_rips_complex.py │ │ ├── test_simplex_array.py │ │ └── test_simplicial_complex.py │ ├── fem │ ├── __init__.py │ ├── info.py │ ├── innerproduct.py │ └── tests │ │ └── test_innerproduct.py │ ├── io │ ├── __init__.py │ ├── arrayio.py │ ├── complex_io.py │ ├── info.py │ ├── meshio.py │ ├── misc.py │ └── tests │ │ ├── test_arrayio.py │ │ └── test_misc.py │ ├── math │ ├── __init__.py │ ├── circumcenter.py │ ├── combinatorial.py │ ├── info.py │ ├── kd_tree.py │ ├── parity.py │ ├── tests │ │ ├── test_circumcenter.py │ │ ├── test_combinatorial.py │ │ ├── test_graph.py │ │ ├── test_kd_tree.py │ │ ├── test_parity.py │ │ └── test_volume.py │ └── volume.py │ ├── mesh │ ├── __init__.py │ ├── __init__.py~ │ ├── base_mesh.py │ ├── generation.py │ ├── info.py │ ├── ncube.py │ ├── regular_cube.py │ ├── simplex.py │ ├── subdivision.py │ └── tests │ │ ├── test_ncube.py │ │ ├── test_simplex.py │ │ └── test_subdivision.py │ ├── testing │ ├── __init__.py │ └── __init__.py~ │ ├── util │ ├── __init__.py │ ├── info.py │ ├── tests │ │ └── placeholder_tests.py │ └── util.py │ ├── version.py │ └── vis │ ├── __init__.py │ ├── draw.py │ ├── info.py │ └── tests │ └── placeholder_tests.py └── pyproject.toml /.gitignore: -------------------------------------------------------------------------------- 1 | # Git ignores for PyDEC. 2 | # Ported from original .svnignore 3 | 4 | *.pyc 5 | *.bak 6 | *.so 7 | *.swp 8 | dist 9 | build 10 | *.egg-info 11 | -------------------------------------------------------------------------------- /Examples/DarcyFlow/driver.py: -------------------------------------------------------------------------------- 1 | """ 2 | Darcy flow in a triangle mesh of a planar domain, with constant 3 | velocity prescribed on boundary. 4 | 5 | Reference : 6 | 7 | Numerical method for Darcy flow derived using Discrete Exterior Calculus 8 | A. N. Hirani, K. B. Nakshatrala, J. H. Chaudhry 9 | See arXiv:0810.3434v3 [math.NA] on http://arxiv.org/abs/0810.3434 10 | 11 | """ 12 | from numpy import zeros, sort, asarray, loadtxt, array, dot, \ 13 | concatenate, sign, vstack, argmax, nonzero 14 | from numpy.linalg import norm, det 15 | from scipy.sparse import bmat 16 | from scipy.sparse.linalg import spsolve 17 | from matplotlib.pylab import figure, gca, triplot, show 18 | from pydec import simplicial_complex, simplex_quivers, signed_volume 19 | 20 | velocity = array([1.,0]) 21 | # Read the mesh 22 | vertices = loadtxt('vertices.txt') 23 | triangles = loadtxt('triangles.txt', dtype='int') - 1 24 | # Make a simplicial complex from it 25 | sc = simplicial_complex((vertices,triangles)) 26 | # Nk is number of k-simplices 27 | N1 = sc[1].num_simplices 28 | N2 = sc[2].num_simplices 29 | # Permeability is k > 0 and viscosity is mu > 0 30 | k = 1; mu = 1 31 | # The matrix for the full linear system for Darcy in 2D, not taking into 32 | # account the boundary conditions, is : 33 | # [-(mu/k)star1 d1^T ] 34 | # [ d1 Z ] 35 | # where Z is a zero matrix of size N2 by N2. 36 | # The block sizes are 37 | # N1 X N1 N1 X N2 38 | # N2 X N1 N2 X N2 39 | 40 | d1 = sc[1].d; star1 = sc[1].star 41 | A = bmat([[(-mu/k)*sc[1].star, sc[1].d.T], 42 | [sc[1].d, None]], format='csr') 43 | b = zeros(N1 + N2) # RHS vector 44 | all_fluxes = zeros(N1) 45 | # Find boundary and internal edges 46 | boundary_edges = sc.boundary(); boundary_edges.sort() 47 | boundary_indices = list(sort([sc[1].simplex_to_index[e] 48 | for e in boundary_edges])) 49 | num_boundary_edges = len(boundary_indices) 50 | internal_edges = set(sc[1].simplex_to_index.keys()) - set(boundary_edges) 51 | internal_indices = list(sort([sc[1].simplex_to_index[e] 52 | for e in internal_edges])) 53 | num_internal_edges = sc[1].num_simplices - num_boundary_edges 54 | # Assume triangles oriented the same way so can look at any triangle 55 | s = sign(det(vertices[triangles[0,1:]] - vertices[triangles[0,0]])) 56 | for i, e in enumerate(boundary_edges): 57 | evector = (-1)**e.parity * (vertices[e[1]] - vertices[e[0]]) 58 | normal = array([-evector[1], evector[0]]) 59 | all_fluxes[boundary_indices[i]] = -s * (1/norm(evector)**2) * \ 60 | dot(velocity, normal) * \ 61 | abs(det(vstack((normal, evector)))) 62 | pressures = zeros(N2) 63 | # Adjust RHS for known fluxes and pressures 64 | b = b - A * concatenate((all_fluxes,pressures)) 65 | # Remove entries of b corresponding to boundary fluxes and known pressure 66 | # Pressure at the right most triangle circumcenter is assumed known 67 | pressure_indices = list(range(N1, N1+N2)) 68 | pressure_indices.remove(N1 + argmax(sc[2].circumcenter[:,0])) 69 | entries_to_keep = concatenate((internal_indices, pressure_indices)) 70 | b = b[entries_to_keep] 71 | # Remove corresponding rows and columns of A 72 | A = A[entries_to_keep][:,entries_to_keep] 73 | u = spsolve(A,b) 74 | fluxes = u[0:len(internal_indices)] 75 | pressures[array(pressure_indices)-N1] = u[len(internal_indices):] 76 | # Plot the pressures 77 | figure(); ax = gca(); 78 | ax.set_xlabel('x', fontsize=20) 79 | ax.set_ylabel('Pressure', fontsize=20) 80 | ax.plot(sc[2].circumcenter[:,0], pressures, 'ro', markersize=8, mec='r') 81 | # Draw a line of slope -1 through the known presure point 82 | xmax = max(sc[2].circumcenter[:,0]) 83 | xmin = min(sc[2].circumcenter[:,0]) 84 | ax.plot([xmin, xmax], [xmax - xmin, 0], 'k', linewidth=2) 85 | ax.legend(['DEC', 'Analytical'], numpoints=1) 86 | # Plot the triangles 87 | figure(); ax = gca() 88 | ax.triplot(vertices[:,0], vertices[:,1], triangles) 89 | # Insert the computed fluxes into vector of all fluxes 90 | all_fluxes[internal_indices] = fluxes 91 | # Whitney interpolate flux and sample at barycenters. Then 92 | # rotate 90 degrees in the sense opposite to triangle orientation 93 | v_bases,v_arrows = simplex_quivers(sc, all_fluxes) 94 | v_arrows = -s * vstack((-v_arrows[:,1], v_arrows[:,0])).T 95 | # Plot the resulting vector field at the barycenters 96 | ax.quiver(v_bases[:,0],v_bases[:,1],v_arrows[:,0],v_arrows[:,1], 97 | units='dots', width=1, scale=1./30) 98 | ax.axis('equal') 99 | ax.set_title('Flux interpolated using Whitney map \n' \ 100 | ' and visualized as velocity at barycenters\n') 101 | 102 | show() 103 | 104 | -------------------------------------------------------------------------------- /Examples/DarcyFlow/triangles.txt: -------------------------------------------------------------------------------- 1 | 14 2 61 2 | 58 6 93 3 | 93 7 98 4 | 115 9 149 5 | 56 13 72 6 | 44 1 62 7 | 5 6 58 8 | 7 8 98 9 | 57 46 140 10 | 24 3 60 11 | 34 4 59 12 | 98 8 115 13 | 61 15 75 14 | 114 19 151 15 | 55 23 73 16 | 15 16 75 17 | 13 14 72 18 | 2 15 61 19 | 95 17 132 20 | 1 5 62 21 | 17 18 132 22 | 56 49 129 23 | 18 19 114 24 | 23 24 73 25 | 19 20 151 26 | 60 25 76 27 | 113 29 152 28 | 54 33 74 29 | 3 25 60 30 | 25 26 76 31 | 136 45 176 32 | 96 27 130 33 | 27 28 130 34 | 33 34 74 35 | 4 35 59 36 | 59 35 77 37 | 28 29 113 38 | 29 30 152 39 | 6 7 93 40 | 9 10 149 41 | 35 36 77 42 | 94 37 131 43 | 37 38 131 44 | 112 39 157 45 | 57 43 71 46 | 38 39 112 47 | 54 47 128 48 | 39 40 157 49 | 55 48 127 50 | 147 78 191 51 | 146 45 179 52 | 124 50 143 53 | 42 43 57 54 | 12 13 56 55 | 22 23 55 56 | 158 53 180 57 | 32 33 54 58 | 126 51 141 59 | 133 56 159 60 | 102 52 181 61 | 59 47 74 62 | 103 40 170 63 | 60 48 73 64 | 152 30 169 65 | 61 49 72 66 | 151 20 168 67 | 43 44 71 68 | 149 10 164 69 | 58 46 71 70 | 104 53 123 71 | 77 36 94 72 | 36 37 94 73 | 76 26 96 74 | 26 27 96 75 | 75 16 95 76 | 16 17 95 77 | 46 57 71 78 | 5 58 62 79 | 41 42 97 80 | 42 57 97 81 | 180 53 182 82 | 10 11 164 83 | 134 54 163 84 | 30 31 169 85 | 135 55 162 86 | 20 21 168 87 | 31 32 134 88 | 122 63 172 89 | 21 22 135 90 | 185 51 190 91 | 11 12 133 92 | 186 50 187 93 | 97 57 183 94 | 120 86 129 95 | 62 58 71 96 | 44 62 71 97 | 49 56 72 98 | 14 61 72 99 | 48 55 73 100 | 24 60 73 101 | 47 54 74 102 | 34 59 74 103 | 49 61 75 104 | 120 69 143 105 | 48 60 76 106 | 119 68 141 107 | 47 59 77 108 | 125 52 142 109 | 121 67 142 110 | 121 88 128 111 | 119 87 127 112 | 105 66 177 113 | 140 85 183 114 | 91 65 108 115 | 147 51 173 116 | 107 65 156 117 | 106 80 148 118 | 106 63 155 119 | 145 50 171 120 | 92 66 109 121 | 137 45 178 122 | 46 58 93 123 | 153 64 184 124 | 114 69 132 125 | 146 79 189 126 | 113 68 130 127 | 148 80 181 128 | 112 67 131 129 | 123 53 158 130 | 110 64 167 131 | 129 86 159 132 | 108 65 165 133 | 142 52 144 134 | 109 66 166 135 | 127 87 162 136 | 8 9 115 137 | 93 70 116 138 | 47 77 94 139 | 94 67 121 140 | 49 75 95 141 | 95 69 120 142 | 48 76 96 143 | 96 68 119 144 | 155 63 161 145 | 116 85 140 146 | 115 89 123 147 | 70 93 98 148 | 105 79 145 149 | 137 81 150 150 | 154 81 182 151 | 122 85 161 152 | 107 78 147 153 | 145 79 179 154 | 146 101 191 155 | 128 88 163 156 | 148 52 174 157 | 40 41 170 158 | 150 81 184 159 | 89 104 123 160 | 138 101 189 161 | 143 50 186 162 | 156 65 188 163 | 100 80 155 164 | 141 51 185 165 | 102 78 156 166 | 134 91 169 167 | 126 82 173 168 | 135 92 168 169 | 104 89 167 170 | 133 90 164 171 | 110 90 175 172 | 111 86 186 173 | 112 83 125 174 | 103 83 157 175 | 113 82 126 176 | 108 82 152 177 | 114 84 124 178 | 109 84 151 179 | 110 89 149 180 | 70 98 115 181 | 116 70 158 182 | 46 93 116 183 | 65 91 117 184 | 117 88 144 185 | 66 92 118 186 | 118 87 185 187 | 87 119 141 188 | 48 96 119 189 | 86 120 143 190 | 49 95 120 191 | 52 102 144 192 | 47 94 121 193 | 41 97 170 194 | 170 97 172 195 | 70 115 123 196 | 161 85 180 197 | 124 84 171 198 | 69 114 124 199 | 125 83 174 200 | 67 112 125 201 | 165 107 173 202 | 68 113 126 203 | 118 92 162 204 | 48 119 127 205 | 117 91 163 206 | 47 121 128 207 | 111 90 159 208 | 49 120 129 209 | 68 96 130 210 | 28 113 130 211 | 67 94 131 212 | 38 112 131 213 | 69 95 132 214 | 18 114 132 215 | 12 56 133 216 | 86 111 159 217 | 32 54 134 218 | 88 117 163 219 | 22 55 135 220 | 87 118 162 221 | 160 106 174 222 | 78 102 136 223 | 80 100 137 224 | 147 101 190 225 | 66 118 138 226 | 145 99 187 227 | 46 116 140 228 | 122 97 183 229 | 68 126 141 230 | 138 118 185 231 | 88 121 142 232 | 67 125 142 233 | 69 124 143 234 | 139 111 186 235 | 144 102 188 236 | 88 142 144 237 | 150 99 179 238 | 184 139 187 239 | 177 138 189 240 | 176 146 191 241 | 51 126 173 242 | 52 125 174 243 | 136 102 181 244 | 90 110 164 245 | 89 115 149 246 | 45 137 150 247 | 99 145 179 248 | 92 109 168 249 | 84 114 151 250 | 91 108 169 251 | 82 113 152 252 | 64 139 184 253 | 53 104 154 254 | 137 100 182 255 | 81 137 182 256 | 80 106 155 257 | 65 117 188 258 | 78 107 156 259 | 40 103 157 260 | 83 112 157 261 | 85 116 158 262 | 70 123 158 263 | 56 129 159 264 | 90 133 159 265 | 83 103 160 266 | 63 106 160 267 | 63 122 161 268 | 100 155 161 269 | 55 127 162 270 | 92 135 162 271 | 54 128 163 272 | 91 134 163 273 | 11 133 164 274 | 110 149 164 275 | 65 107 165 276 | 82 108 165 277 | 66 105 166 278 | 84 109 166 279 | 89 110 167 280 | 21 135 168 281 | 109 151 168 282 | 31 134 169 283 | 108 152 169 284 | 160 103 172 285 | 97 122 172 286 | 50 124 171 287 | 105 145 171 288 | 63 160 172 289 | 103 170 172 290 | 107 147 173 291 | 82 165 173 292 | 106 148 174 293 | 83 160 174 294 | 64 110 175 295 | 90 111 175 296 | 78 136 176 297 | 45 146 176 298 | 79 105 177 299 | 66 138 177 300 | 45 136 178 301 | 80 137 178 302 | 79 146 179 303 | 45 150 179 304 | 85 158 180 305 | 100 161 180 306 | 52 148 181 307 | 53 154 182 308 | 100 180 182 309 | 85 122 183 310 | 57 140 183 311 | 99 150 184 312 | 81 153 184 313 | 101 138 190 314 | 87 141 185 315 | 50 145 187 316 | 86 143 186 317 | 99 184 187 318 | 139 186 187 319 | 117 144 188 320 | 102 156 188 321 | 101 146 189 322 | 79 177 189 323 | 51 147 190 324 | 138 185 190 325 | 101 147 191 326 | 78 176 191 327 | 104 167 153 328 | 64 153 167 329 | 104 153 154 330 | 81 154 153 331 | 105 171 166 332 | 84 166 171 333 | 111 139 175 334 | 64 175 139 335 | 136 181 178 336 | 80 178 181 337 | -------------------------------------------------------------------------------- /Examples/DarcyFlow/vertices.txt: -------------------------------------------------------------------------------- 1 | -1.5707963267949e+00 -1.5707963267949e+00 2 | 1.5707963267949e+00 -1.5707963267949e+00 3 | 1.5707963267949e+00 1.5707963267949e+00 4 | -1.5707963267949e+00 1.5707963267949e+00 5 | -1.2851969946504e+00 -1.5707963267949e+00 6 | -9.9959766250584e-01 -1.5707963267949e+00 7 | -7.1399833036132e-01 -1.5707963267949e+00 8 | -4.2839899821679e-01 -1.5707963267949e+00 9 | -1.4279966607226e-01 -1.5707963267949e+00 10 | 1.4279966607226e-01 -1.5707963267949e+00 11 | 4.2839899821679e-01 -1.5707963267949e+00 12 | 7.1399833036132e-01 -1.5707963267949e+00 13 | 9.9959766250584e-01 -1.5707963267949e+00 14 | 1.2851969946504e+00 -1.5707963267949e+00 15 | 1.5707963267949e+00 -1.2851969946504e+00 16 | 1.5707963267949e+00 -9.9959766250584e-01 17 | 1.5707963267949e+00 -7.1399833036132e-01 18 | 1.5707963267949e+00 -4.2839899821679e-01 19 | 1.5707963267949e+00 -1.4279966607226e-01 20 | 1.5707963267949e+00 1.4279966607226e-01 21 | 1.5707963267949e+00 4.2839899821679e-01 22 | 1.5707963267949e+00 7.1399833036132e-01 23 | 1.5707963267949e+00 9.9959766250584e-01 24 | 1.5707963267949e+00 1.2851969946504e+00 25 | 1.2851969946504e+00 1.5707963267949e+00 26 | 9.9959766250584e-01 1.5707963267949e+00 27 | 7.1399833036132e-01 1.5707963267949e+00 28 | 4.2839899821679e-01 1.5707963267949e+00 29 | 1.4279966607226e-01 1.5707963267949e+00 30 | -1.4279966607226e-01 1.5707963267949e+00 31 | -4.2839899821679e-01 1.5707963267949e+00 32 | -7.1399833036132e-01 1.5707963267949e+00 33 | -9.9959766250584e-01 1.5707963267949e+00 34 | -1.2851969946504e+00 1.5707963267949e+00 35 | -1.5707963267949e+00 1.2851969946504e+00 36 | -1.5707963267949e+00 9.9959766250584e-01 37 | -1.5707963267949e+00 7.1399833036132e-01 38 | -1.5707963267949e+00 4.2839899821679e-01 39 | -1.5707963267949e+00 1.4279966607226e-01 40 | -1.5707963267949e+00 -1.4279966607226e-01 41 | -1.5707963267949e+00 -4.2839899821679e-01 42 | -1.5707963267949e+00 -7.1399833036132e-01 43 | -1.5707963267949e+00 -9.9959766250584e-01 44 | -1.5707963267949e+00 -1.2851969946504e+00 45 | 5.3804639787558e-04 6.5183191051434e-03 46 | -1.0776794790591e+00 -1.0913692420012e+00 47 | -1.1452799446275e+00 1.1350488018016e+00 48 | 1.1288013808513e+00 1.1513638355768e+00 49 | 1.1466005352741e+00 -1.1355299080426e+00 50 | 7.3561463731961e-01 -4.2610818545374e-01 51 | 4.0533138140781e-01 7.7426773191769e-01 52 | -7.0351375053933e-01 3.6828977312604e-01 53 | -3.4503986557309e-01 -7.5568660423878e-01 54 | -8.6931157270623e-01 1.3054994964146e+00 55 | 1.2996023488063e+00 8.7563266501509e-01 56 | 8.7655397132341e-01 -1.3072898280179e+00 57 | -1.2884800976119e+00 -8.7330025735635e-01 58 | -1.1553827795023e+00 -1.3468677926918e+00 59 | -1.3472183841835e+00 1.3459179093054e+00 60 | 1.3452105621022e+00 1.3480514538732e+00 61 | 1.3472309863375e+00 -1.3459297401208e+00 62 | -1.3830470173502e+00 -1.3863039747969e+00 63 | -8.9338040816334e-01 -2.7704048583697e-01 64 | 2.0030779903131e-01 -8.1546617954308e-01 65 | -2.5618109804159e-01 8.9045110933802e-01 66 | 8.4397225139735e-01 2.5791481747652e-01 67 | -1.1620981548862e+00 6.0090156891149e-01 68 | 5.9546651652341e-01 1.1714125655215e+00 69 | 1.1569439790688e+00 -6.0362902721501e-01 70 | -5.7329430432987e-01 -1.1436293272383e+00 71 | -1.3422299236802e+00 -1.1634595615637e+00 72 | 1.1352898433573e+00 -1.3907703308471e+00 73 | 1.3886684168254e+00 1.1379042510810e+00 74 | -1.1345398206219e+00 1.3905969289676e+00 75 | 1.3934384260542e+00 -1.1282582125054e+00 76 | 1.1249540725348e+00 1.3952048056568e+00 77 | -1.3936838668720e+00 1.1287105737469e+00 78 | -7.2971324784282e-02 4.8290942597957e-01 79 | 4.8303871141493e-01 5.5671689883874e-02 80 | -5.1204108584259e-01 -8.6950408078312e-02 81 | -6.3617798218521e-02 -4.9947678512246e-01 82 | 1.1087789550446e-01 1.1013095509110e+00 83 | -1.1475391353087e+00 1.1842225101262e-01 84 | 1.0787484939799e+00 -1.1820396499226e-01 85 | -8.1103760230864e-01 -7.1296242514887e-01 86 | 7.8488698039661e-01 -8.6803380620987e-01 87 | 8.3888499858433e-01 7.9284914248399e-01 88 | -7.6777548575808e-01 8.5834281480697e-01 89 | -9.4186756616889e-02 -1.0732982433562e+00 90 | 4.4648336310743e-01 -1.1052039127239e+00 91 | -4.0472848068083e-01 1.1118376268635e+00 92 | 1.0868356943348e+00 4.1236793769586e-01 93 | -8.4868562931569e-01 -1.2964649357778e+00 94 | -1.3155622150808e+00 8.6183861270090e-01 95 | 1.3138748078062e+00 -8.6097342967242e-01 96 | 8.5645123682317e-01 1.3187213471944e+00 97 | -1.2856156290154e+00 -5.4959353200835e-01 98 | -5.7221815504058e-01 -1.3737191446926e+00 99 | 3.2535324995758e-01 -3.2579718679623e-01 100 | -5.0333367474960e-01 -3.6347339515017e-01 101 | 3.5797861410776e-01 3.9809165351547e-01 102 | -3.8551187476661e-01 4.6100603648072e-01 103 | -1.2711802581594e+00 -1.0000691071555e-01 104 | -1.4916219339835e-01 -8.4917526937607e-01 105 | 7.0879631622836e-01 2.7762577486890e-02 106 | -7.8143245887169e-01 -7.9306847636431e-02 107 | -4.0887835892638e-02 7.4492869380039e-01 108 | -1.4231631825255e-01 1.1229180905733e+00 109 | 1.0989475565219e+00 1.4299265622408e-01 110 | 1.7911024144192e-01 -1.0739023097382e+00 111 | 5.5805062860837e-01 -8.9904625041283e-01 112 | -1.3185353865589e+00 3.3677568740156e-01 113 | 3.1382099040920e-01 1.3053446361759e+00 114 | 1.2957405858663e+00 -3.1851095516215e-01 115 | -2.9908492148240e-01 -1.2934005027819e+00 116 | -8.1631360406680e-01 -9.9826725239983e-01 117 | -5.1889724786546e-01 8.8812006858756e-01 118 | 8.4619599658673e-01 5.3961034688476e-01 119 | 8.4338196070757e-01 1.0442559995589e+00 120 | 1.0326144117100e+00 -8.5791694348752e-01 121 | -1.0284433703759e+00 8.5200067771441e-01 122 | -9.8970112664877e-01 -4.9955230874907e-01 123 | -3.3891973340525e-01 -9.9777496794293e-01 124 | 9.9930883150201e-01 -3.7946356921232e-01 125 | -1.0193457951990e+00 3.6501881811241e-01 126 | 3.6866919802380e-01 1.0221791806509e+00 127 | 1.0291134608145e+00 9.0517367170623e-01 128 | -8.9096349218361e-01 1.0427249570090e+00 129 | 8.9979737544087e-01 -1.0454520936666e+00 130 | 5.8546741888905e-01 1.3826378805380e+00 131 | -1.3864478370074e+00 5.9137761127274e-01 132 | 1.3798656356145e+00 -5.8848540136276e-01 133 | 5.7825033622185e-01 -1.3299260225939e+00 134 | -5.6047260668609e-01 1.3324026960160e+00 135 | 1.3226379805174e+00 5.6639371481152e-01 136 | -1.9941015178329e-01 2.3440942141747e-01 137 | -2.2485031326665e-01 -2.4201001145397e-01 138 | 6.0565290259905e-01 4.1709145296514e-01 139 | 4.1152164831339e-01 -7.2492826661725e-01 140 | -1.0140125908169e+00 -8.6864324505095e-01 141 | 6.1348976261336e-01 9.1374865890708e-01 142 | -8.8388615809932e-01 6.1819099499553e-01 143 | 8.9281176927168e-01 -6.3615945011891e-01 144 | -6.1257291900199e-01 6.5324449758212e-01 145 | 5.5499889655039e-01 -2.0410217781047e-01 146 | 2.5629152941002e-01 1.6935564629834e-01 147 | 1.9708991541486e-01 6.0315945470044e-01 148 | -6.7414815032483e-01 9.9244963794996e-02 149 | 1.4276326616128e-02 -1.3181470524272e+00 150 | 9.4250124027350e-02 -2.8878255420785e-01 151 | 1.3290653153621e+00 -1.9403605963142e-03 152 | 1.9518445453304e-04 1.3372771081816e+00 153 | 1.0921949708857e-02 -7.1395371505472e-01 154 | -1.7120603621447e-01 -6.7631827441326e-01 155 | -6.9140580522655e-01 -2.5084042303208e-01 156 | -2.3491136641041e-01 6.6567057098775e-01 157 | -1.3768011979473e+00 6.5231759542199e-02 158 | -5.6977021936028e-01 -8.7100172948954e-01 159 | 6.9213166891737e-01 -1.0896693995242e+00 160 | -1.0205564702726e+00 -8.2673698725921e-02 161 | -7.3824314067636e-01 -4.5279849834181e-01 162 | 1.0696909325818e+00 6.8256929519853e-01 163 | -6.6773489038672e-01 1.0903717810997e+00 164 | 3.0019686255583e-01 -1.3279699207036e+00 165 | -3.0723897610392e-02 9.4392944898231e-01 166 | 9.1231396979261e-01 2.2644983851790e-02 167 | 2.6578794517677e-02 -9.0165310357735e-01 168 | 1.3322087165368e+00 2.8223160000091e-01 169 | -2.7954605609710e-01 1.3418343107573e+00 170 | -1.3614990341977e+00 -3.0071878062308e-01 171 | 8.3328083578243e-01 -1.7720703726077e-01 172 | -1.1354284111098e+00 -3.0045288091936e-01 173 | 1.6775172805174e-01 8.6482625978645e-01 174 | -8.9265099959202e-01 1.3088275963760e-01 175 | 3.6244612280932e-01 -9.1781783675219e-01 176 | 3.7914182416686e-02 2.6125913657675e-01 177 | 6.1427708027494e-01 1.9747953059724e-01 178 | -2.8009627044658e-01 2.4031932383405e-02 179 | 2.8691853512781e-01 -9.7503848307650e-02 180 | -5.4358940487803e-01 -6.1400087528032e-01 181 | -4.5567507759896e-01 1.8447846598670e-01 182 | -3.0609395665921e-01 -5.2704258734315e-01 183 | -1.0692413820390e+00 -7.0814087337837e-01 184 | 2.0718847093717e-01 -5.4929085970956e-01 185 | 6.2648362797879e-01 6.7034361443357e-01 186 | 6.4273164967885e-01 -6.7370737758345e-01 187 | 4.8001435514921e-01 -4.8412468057422e-01 188 | -3.9822069273538e-01 7.1505183038128e-01 189 | 4.6946030401648e-01 2.4376975056702e-01 190 | 4.4011049134214e-01 5.7169449713601e-01 191 | 1.5283194952654e-01 3.7657023237795e-01 192 | -------------------------------------------------------------------------------- /Examples/HodgeDecomposition/driver.py: -------------------------------------------------------------------------------- 1 | """ 2 | Compute a harmonic 1-cochain basis for a square with 4 holes. 3 | 4 | """ 5 | from numpy import asarray, eye, outer, inner, dot, vstack 6 | from numpy.random import seed, rand 7 | from numpy.linalg import norm 8 | from scipy.sparse.linalg import cg 9 | from pydec import d, delta, simplicial_complex, read_mesh 10 | 11 | def hodge_decomposition(omega): 12 | """ 13 | For a given p-cochain \omega there is a unique decomposition 14 | 15 | \omega = d(\alpha) + \delta(\beta) (+) h 16 | 17 | for p-1 cochain \alpha, p+1 cochain \beta, and harmonic p-cochain h. 18 | 19 | This function returns (non-unique) representatives \beta, \gamma, and h 20 | which satisfy the equation above. 21 | 22 | Example: 23 | #decompose a random 1-cochain 24 | sc = SimplicialComplex(...) 25 | omega = sc.get_cochain(1) 26 | omega.[:] = rand(*omega.shape) 27 | (alpha,beta,h) = hodge_decomposition(omega) 28 | 29 | """ 30 | sc = omega.complex 31 | p = omega.k 32 | alpha = sc.get_cochain(p - 1) 33 | beta = sc.get_cochain(p + 1) 34 | 35 | # Solve for alpha 36 | A = delta(d(sc.get_cochain_basis(p - 1))).v 37 | b = delta(omega).v 38 | #alpha.v = cg( A, b, tol=1e-8 )[0] 39 | alpha.v = cg( A, b, rtol=1e-8 )[0] 40 | # TODO: check if rtol should be more or less stringent 41 | 42 | # Solve for beta 43 | A = d(delta(sc.get_cochain_basis(p + 1))).v 44 | b = d(omega).v 45 | #beta.v = cg( A, b, tol=1e-8 )[0] 46 | beta.v = cg( A, b, rtol=1e-8 )[0] 47 | # TODO: check if rtol should be more or less stringent 48 | 49 | # Solve for h 50 | h = omega - d(alpha) - delta(beta) 51 | 52 | return (alpha,beta,h) 53 | 54 | 55 | def ortho(A): 56 | """Separates the harmonic forms stored in the rows of A using a heuristic 57 | """ 58 | A = asarray(A) 59 | 60 | for i in range(A.shape[0]): 61 | j = abs(A[i]).argmax() 62 | 63 | v = A[:,j].copy() 64 | if A[i,j] > 0: 65 | v[i] += norm(v) 66 | else: 67 | v[i] -= norm(v) 68 | Q = eye(A.shape[0]) - 2 * outer(v,v) / inner(v,v) 69 | 70 | A = dot(Q,A) 71 | 72 | return A 73 | 74 | seed(1) # make results consistent 75 | 76 | # Read in mesh data from file 77 | mesh = read_mesh('mesh_example.xml') 78 | 79 | vertices = mesh.vertices 80 | triangles = mesh.elements 81 | 82 | # remove some triangle from the mesh 83 | triangles = triangles[list(set(range(len(triangles))) - set([30,320,21,198])),:] 84 | 85 | sc = simplicial_complex((vertices,triangles)) 86 | 87 | H = [] # harmonic forms 88 | 89 | # decompose 4 random 1-cochains 90 | for i in range(4): 91 | omega = sc.get_cochain(1) 92 | omega.v[:] = rand(*omega.v.shape) 93 | 94 | (beta,gamma,h) = hodge_decomposition(omega) 95 | 96 | h = h.v 97 | for v in H: 98 | h -= inner(v,h) * v 99 | h /= norm(h) 100 | 101 | H.append(h) 102 | 103 | H = ortho(vstack(H)) 104 | 105 | # plot the results 106 | from pylab import figure, title, quiver, axis, show 107 | from pydec import triplot, simplex_quivers 108 | 109 | for n,h in enumerate(H): 110 | figure() 111 | title('Harmonic 1-cochain #%d' % n) 112 | triplot(vertices,triangles) 113 | 114 | bases,dirs = simplex_quivers(sc,h) 115 | quiver(bases[:,0],bases[:,1],dirs[:,0],dirs[:,1]) 116 | axis('equal') 117 | 118 | show() 119 | 120 | -------------------------------------------------------------------------------- /Examples/HodgeDecomposition/mesh_example.elements: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hirani/pydec/930bb8167978a7b980fc547c8d9237de3e690292/Examples/HodgeDecomposition/mesh_example.elements -------------------------------------------------------------------------------- /Examples/HodgeDecomposition/mesh_example.vertices: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hirani/pydec/930bb8167978a7b980fc547c8d9237de3e690292/Examples/HodgeDecomposition/mesh_example.vertices -------------------------------------------------------------------------------- /Examples/HodgeDecomposition/mesh_example.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Examples/MakeMesh/ExpectedOutput/square_8.elements: -------------------------------------------------------------------------------- 1 | 4 2 | dims=8,3 3 | format=basic 4 | version=1.0 5 | dtype=int32 6 | 0 2 1 7 | 0 3 2 8 | 1 2 4 9 | 2 3 5 10 | 2 5 4 11 | 3 6 5 12 | 4 5 7 13 | 5 6 7 14 | -------------------------------------------------------------------------------- /Examples/MakeMesh/ExpectedOutput/square_8.vertices: -------------------------------------------------------------------------------- 1 | 4 2 | dims=8,2 3 | format=basic 4 | version=1.0 5 | dtype=float64 6 | -0.5 -0.5 7 | -0.5 0.5 8 | -0.03125 0.25 9 | 0 -0.5 10 | 0 0.5 11 | 0.03125 0.25 12 | 0.5 -0.5 13 | 0.5 0.5 14 | -------------------------------------------------------------------------------- /Examples/MakeMesh/ExpectedOutput/square_8.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Examples/MakeMesh/make_mesh.py: -------------------------------------------------------------------------------- 1 | """ 2 | Reads ascii vertex and element files, writes a pydec mesh and displays it 3 | """ 4 | 5 | import numpy 6 | from pydec import SimplicialMesh, write_mesh, read_mesh 7 | from matplotlib.pylab import triplot, show 8 | 9 | vertices = numpy.loadtxt("v.txt") 10 | elements = numpy.loadtxt("s.txt",dtype='int32') - 1 11 | mymesh = SimplicialMesh(vertices=vertices,indices=elements) 12 | write_mesh("square_8.xml",mymesh,format='basic') 13 | rmesh = read_mesh("square_8.xml") 14 | triplot(rmesh.vertices[:,0], rmesh.vertices[:,1], rmesh.indices) 15 | 16 | show() 17 | 18 | -------------------------------------------------------------------------------- /Examples/MakeMesh/s.txt: -------------------------------------------------------------------------------- 1 | 1 3 2 2 | 1 4 3 3 | 2 3 5 4 | 3 4 6 5 | 3 6 5 6 | 4 7 6 7 | 5 6 8 8 | 6 7 8 9 | -------------------------------------------------------------------------------- /Examples/MakeMesh/square_8.elements: -------------------------------------------------------------------------------- 1 | 4 2 | dims=8,3 3 | dtype=int32 4 | format=basic 5 | version=1.0 6 | 0 2 1 7 | 0 3 2 8 | 1 2 4 9 | 2 3 5 10 | 2 5 4 11 | 3 6 5 12 | 4 5 7 13 | 5 6 7 14 | -------------------------------------------------------------------------------- /Examples/MakeMesh/square_8.vertices: -------------------------------------------------------------------------------- 1 | 4 2 | dims=8,2 3 | dtype=float64 4 | format=basic 5 | version=1.0 6 | -0.5 -0.5 7 | -0.5 0.5 8 | -0.03125 0.25 9 | 0 -0.5 10 | 0 0.5 11 | 0.03125 0.25 12 | 0.5 -0.5 13 | 0.5 0.5 14 | -------------------------------------------------------------------------------- /Examples/MakeMesh/square_8.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Examples/MakeMesh/v.txt: -------------------------------------------------------------------------------- 1 | -5.0000000000000000e-01 -5.0000000000000000e-01 2 | -5.0000000000000000e-01 5.0000000000000000e-01 3 | -3.1250000000000000e-02 2.5000000000000000e-01 4 | 0.0000000000000000e+00 -5.0000000000000000e-01 5 | 0.0000000000000000e+00 5.0000000000000000e-01 6 | 3.1250000000000000e-02 2.5000000000000000e-01 7 | 5.0000000000000000e-01 -5.0000000000000000e-01 8 | 5.0000000000000000e-01 5.0000000000000000e-01 9 | -------------------------------------------------------------------------------- /Examples/Ranking/data.txt: -------------------------------------------------------------------------------- 1 | 8 1 -9 2 | 5 10 8 3 | 9 3 -23 4 | 6 9 13 5 | 2 9 23 6 | 8 7 3 7 | 1 10 9 8 | 6 5 11 9 | 2 8 14 10 | 0 1 3 11 | 3 4 -4 12 | 7 6 -1 13 | 1 4 1 14 | 4 2 20 15 | 10 2 -3 16 | 0 5 -9 17 | 7 10 4 18 | 9 0 -11 19 | 4 7 10 20 | 3 0 2 21 | 5 8 3 22 | 0 2 -23 23 | 5 4 14 24 | 7 1 -39 25 | 10 0 -2 26 | 8 3 10 27 | 4 6 -7 28 | 7 2 -27 29 | 1 5 1 30 | 3 10 17 31 | 9 8 -16 32 | 6 0 24 33 | 4 8 -14 34 | 5 7 16 35 | 1 6 18 36 | 10 4 -23 37 | 0 8 -15 38 | 6 2 -22 39 | 7 3 -13 40 | 9 5 -9 41 | 3 1 15 42 | 2 5 27 43 | 3 6 5 44 | 4 0 5 45 | 4 9 30 46 | 5 3 2 47 | 7 0 -24 48 | 1 2 15 49 | 10 6 -47 50 | 9 7 10 51 | 10 8 -6 52 | 2 3 17 53 | 9 10 -1 54 | 1 9 25 55 | 6 8 38 56 | -------------------------------------------------------------------------------- /Examples/Ranking/driver.py: -------------------------------------------------------------------------------- 1 | """ 2 | Least squares ranking on graphs. 3 | 4 | Reference : 5 | 6 | Least Squares Ranking on Graphs, Hodge Laplacians, Time Optimality, 7 | and Iterative Methods 8 | A. N. Hirani, K. Kalyanaraman, S. Watts 9 | See arXiv:1011.1716v1 [cs.NA] on http://arxiv.org/abs/1011.1716 10 | 11 | """ 12 | from numpy import loadtxt 13 | from pydec import abstract_simplicial_complex 14 | from scipy.sparse.linalg import lsqr 15 | 16 | # Load graph and edge values 17 | data = loadtxt('data.txt').astype(int) 18 | edges = data[:,:2] 19 | 20 | # Create abstract simplicial complex from edges 21 | asc = abstract_simplicial_complex([edges]) 22 | 23 | omega = data[:,-1] # pairwise comparisons 24 | B1 = asc.chain_complex()[1] # boundary matrix 25 | alpha = lsqr(B1.T, omega)[0] # solve least squares problem 26 | 27 | # Set the minimum to 0 28 | alpha = alpha - alpha.min() 29 | 30 | print(alpha) 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /Examples/ResonantCavity/driver.py: -------------------------------------------------------------------------------- 1 | """ 2 | Solve the resonant cavity problem with Whitney forms. 3 | 4 | References: 5 | Douglas N. Arnold and Richard S. Falk and Ragnar Winther 6 | "Finite element exterior calculus: from Hodge theory to numerical 7 | stability" 8 | Bull. Amer. Math. Soc. (N.S.), vol. 47, No. 2, pp. 281--354 9 | DOI : 10.1090/S0273-0979-10-01278-4 10 | 11 | """ 12 | from pydec import simplicial_complex, d, delta, whitney_innerproduct, \ 13 | simplex_quivers 14 | from numpy import loadtxt, real, zeros 15 | from scipy.linalg import eig 16 | from matplotlib.pylab import quiver, figure, triplot, show 17 | 18 | # Read in mesh data from files and construct complex 19 | vertices = loadtxt('vertices.txt', dtype=float) 20 | triangles = loadtxt('triangles.txt', dtype=int) 21 | sc = simplicial_complex((vertices,triangles)) 22 | 23 | # Construct stiffness and mass matrices 24 | K = sc[1].d.T * whitney_innerproduct(sc,2) * sc[1].d 25 | M = whitney_innerproduct(sc,1) 26 | 27 | # Eliminate Boundaries from matrices 28 | boundary_edges = sc.boundary() 29 | non_boundary_edges = set(sc[1].simplex_to_index.keys()) - set(boundary_edges) 30 | non_boundary_indices = [sc[1].simplex_to_index[e] for e in non_boundary_edges] 31 | 32 | # Eliminate boundary conditions 33 | K = K[non_boundary_indices,:][:,non_boundary_indices] 34 | M = M[non_boundary_indices,:][:,non_boundary_indices] 35 | 36 | # Compute eigenvalues and eigenvectors 37 | # (could use sparse eigenvalue solver instead) 38 | eigenvalues, eigenvectors = eig(K.todense(), M.todense()) 39 | 40 | # Plot eigenvalues 41 | NUM_EIGS = 50 # Number of eigenvalues to plot 42 | values = sorted([x for x in real(eigenvalues) if x > 1e-10])[0:NUM_EIGS] 43 | ax = figure().gca() 44 | ax.set_title('First ' + str(len(values)) + ' Eigenvalues\n\n') 45 | # ax.hold(True) 46 | ax.plot(values,'ko') 47 | 48 | # Plot the eigenvector 1-cochain as a vector field 49 | N = 2 # Which non-zero eigenvector to plot? 50 | non_zero_values = real(eigenvectors[:,list(eigenvalues).index(values[N])]) 51 | all_values = zeros((sc[1].num_simplices,)) 52 | all_values[non_boundary_indices] = non_zero_values 53 | bases, arrows = simplex_quivers(sc,all_values) 54 | ax = figure().gca() 55 | ax.set_title('Mode #' + str(N+1)) 56 | ax.quiver(bases[:,0],bases[:,1],arrows[:,0],arrows[:,1]) 57 | ax.triplot(sc.vertices[:,0], sc.vertices[:,1], sc.simplices) 58 | ax.axis('equal') 59 | 60 | show() 61 | 62 | -------------------------------------------------------------------------------- /Examples/ResonantCavity/triangles.txt: -------------------------------------------------------------------------------- 1 | 13 1 60 2 | 57 5 92 3 | 92 6 97 4 | 114 8 148 5 | 55 12 71 6 | 43 0 61 7 | 4 5 57 8 | 6 7 97 9 | 56 45 139 10 | 23 2 59 11 | 33 3 58 12 | 97 7 114 13 | 60 14 74 14 | 113 18 150 15 | 54 22 72 16 | 14 15 74 17 | 12 13 71 18 | 1 14 60 19 | 94 16 131 20 | 0 4 61 21 | 16 17 131 22 | 55 48 128 23 | 17 18 113 24 | 22 23 72 25 | 18 19 150 26 | 59 24 75 27 | 112 28 151 28 | 53 32 73 29 | 2 24 59 30 | 24 25 75 31 | 135 44 175 32 | 95 26 129 33 | 26 27 129 34 | 32 33 73 35 | 3 34 58 36 | 58 34 76 37 | 27 28 112 38 | 28 29 151 39 | 5 6 92 40 | 8 9 148 41 | 34 35 76 42 | 93 36 130 43 | 36 37 130 44 | 111 38 156 45 | 56 42 70 46 | 37 38 111 47 | 53 46 127 48 | 38 39 156 49 | 54 47 126 50 | 146 77 190 51 | 145 44 178 52 | 123 49 142 53 | 41 42 56 54 | 11 12 55 55 | 21 22 54 56 | 157 52 179 57 | 31 32 53 58 | 125 50 140 59 | 132 55 158 60 | 101 51 180 61 | 58 46 73 62 | 102 39 169 63 | 59 47 72 64 | 151 29 168 65 | 60 48 71 66 | 150 19 167 67 | 42 43 70 68 | 148 9 163 69 | 57 45 70 70 | 103 52 122 71 | 76 35 93 72 | 35 36 93 73 | 75 25 95 74 | 25 26 95 75 | 74 15 94 76 | 15 16 94 77 | 45 56 70 78 | 4 57 61 79 | 40 41 96 80 | 41 56 96 81 | 179 52 181 82 | 9 10 163 83 | 133 53 162 84 | 29 30 168 85 | 134 54 161 86 | 19 20 167 87 | 30 31 133 88 | 121 62 171 89 | 20 21 134 90 | 184 50 189 91 | 10 11 132 92 | 185 49 186 93 | 96 56 182 94 | 119 85 128 95 | 61 57 70 96 | 43 61 70 97 | 48 55 71 98 | 13 60 71 99 | 47 54 72 100 | 23 59 72 101 | 46 53 73 102 | 33 58 73 103 | 48 60 74 104 | 119 68 142 105 | 47 59 75 106 | 118 67 140 107 | 46 58 76 108 | 124 51 141 109 | 120 66 141 110 | 120 87 127 111 | 118 86 126 112 | 104 65 176 113 | 139 84 182 114 | 90 64 107 115 | 103 63 152 116 | 103 80 153 117 | 146 50 172 118 | 106 64 155 119 | 105 79 147 120 | 105 62 154 121 | 144 49 170 122 | 91 65 108 123 | 136 44 177 124 | 45 57 92 125 | 152 63 183 126 | 113 68 131 127 | 145 78 188 128 | 112 67 129 129 | 147 79 180 130 | 111 66 130 131 | 122 52 157 132 | 109 63 166 133 | 128 85 158 134 | 110 63 174 135 | 107 64 164 136 | 141 51 143 137 | 108 65 165 138 | 126 86 161 139 | 7 8 114 140 | 92 69 115 141 | 46 76 93 142 | 93 66 120 143 | 48 74 94 144 | 94 68 119 145 | 47 75 95 146 | 95 67 118 147 | 154 62 160 148 | 115 84 139 149 | 114 88 122 150 | 69 92 97 151 | 104 78 144 152 | 136 80 149 153 | 153 80 181 154 | 121 84 160 155 | 106 77 146 156 | 144 78 178 157 | 145 100 190 158 | 127 87 162 159 | 147 51 173 160 | 39 40 169 161 | 149 80 183 162 | 88 103 122 163 | 137 100 188 164 | 142 49 185 165 | 155 64 187 166 | 99 79 154 167 | 140 50 184 168 | 101 77 155 169 | 133 90 168 170 | 125 81 172 171 | 134 91 167 172 | 104 83 165 173 | 103 88 166 174 | 132 89 163 175 | 109 89 174 176 | 110 85 185 177 | 111 82 124 178 | 102 82 156 179 | 112 81 125 180 | 107 81 151 181 | 113 83 123 182 | 108 83 150 183 | 109 88 148 184 | 69 97 114 185 | 115 69 157 186 | 45 92 115 187 | 64 90 116 188 | 116 87 143 189 | 65 91 117 190 | 117 86 184 191 | 86 118 140 192 | 47 95 118 193 | 85 119 142 194 | 48 94 119 195 | 51 101 143 196 | 46 93 120 197 | 40 96 169 198 | 169 96 171 199 | 69 114 122 200 | 160 84 179 201 | 123 83 170 202 | 68 113 123 203 | 124 82 173 204 | 66 111 124 205 | 164 106 172 206 | 67 112 125 207 | 117 91 161 208 | 47 118 126 209 | 116 90 162 210 | 46 120 127 211 | 110 89 158 212 | 48 119 128 213 | 67 95 129 214 | 27 112 129 215 | 66 93 130 216 | 37 111 130 217 | 68 94 131 218 | 17 113 131 219 | 11 55 132 220 | 85 110 158 221 | 31 53 133 222 | 87 116 162 223 | 21 54 134 224 | 86 117 161 225 | 159 105 173 226 | 77 101 135 227 | 79 99 136 228 | 135 79 177 229 | 146 100 189 230 | 65 117 137 231 | 144 98 186 232 | 63 110 138 233 | 45 115 139 234 | 121 96 182 235 | 67 125 140 236 | 137 117 184 237 | 87 120 141 238 | 66 124 141 239 | 68 123 142 240 | 138 110 185 241 | 143 101 187 242 | 87 141 143 243 | 149 98 178 244 | 83 104 170 245 | 183 138 186 246 | 176 137 188 247 | 175 145 190 248 | 50 125 172 249 | 51 124 173 250 | 135 101 180 251 | 89 109 163 252 | 88 114 148 253 | 44 136 149 254 | 98 144 178 255 | 91 108 167 256 | 83 113 150 257 | 90 107 168 258 | 81 112 151 259 | 80 103 152 260 | 63 138 183 261 | 52 103 153 262 | 136 99 181 263 | 80 136 181 264 | 79 105 154 265 | 64 116 187 266 | 77 106 155 267 | 39 102 156 268 | 82 111 156 269 | 84 115 157 270 | 69 122 157 271 | 55 128 158 272 | 89 132 158 273 | 82 102 159 274 | 62 105 159 275 | 62 121 160 276 | 99 154 160 277 | 54 126 161 278 | 91 134 161 279 | 53 127 162 280 | 90 133 162 281 | 10 132 163 282 | 109 148 163 283 | 64 106 164 284 | 81 107 164 285 | 65 104 165 286 | 83 108 165 287 | 63 103 166 288 | 88 109 166 289 | 20 134 167 290 | 108 150 167 291 | 30 133 168 292 | 107 151 168 293 | 159 102 171 294 | 96 121 171 295 | 49 123 170 296 | 104 144 170 297 | 62 159 171 298 | 102 169 171 299 | 106 146 172 300 | 81 164 172 301 | 105 147 173 302 | 82 159 173 303 | 63 109 174 304 | 89 110 174 305 | 77 135 175 306 | 44 145 175 307 | 78 104 176 308 | 65 137 176 309 | 44 135 177 310 | 79 136 177 311 | 78 145 178 312 | 44 149 178 313 | 84 157 179 314 | 99 160 179 315 | 79 135 180 316 | 51 147 180 317 | 52 153 181 318 | 99 179 181 319 | 84 121 182 320 | 56 139 182 321 | 98 149 183 322 | 80 152 183 323 | 100 137 189 324 | 86 140 184 325 | 49 144 186 326 | 85 142 185 327 | 98 183 186 328 | 138 185 186 329 | 116 143 187 330 | 101 155 187 331 | 100 145 188 332 | 78 176 188 333 | 50 146 189 334 | 137 184 189 335 | 100 146 190 336 | 77 175 190 337 | -------------------------------------------------------------------------------- /Examples/ResonantCavity/vertices.txt: -------------------------------------------------------------------------------- 1 | -1.570796326794896558e+00 -1.570796326794896558e+00 2 | 1.570796326794896558e+00 -1.570796326794896558e+00 3 | 1.570796326794896558e+00 1.570796326794896558e+00 4 | -1.570796326794896558e+00 1.570796326794896558e+00 5 | -1.285196994650369851e+00 -1.570796326794896558e+00 6 | -9.995976625058432541e-01 -1.570796326794896558e+00 7 | -7.139983303613166576e-01 -1.570796326794896558e+00 8 | -4.283989982167899502e-01 -1.570796326794896558e+00 9 | -1.427996660722634648e-01 -1.570796326794896558e+00 10 | 1.427996660722632427e-01 -1.570796326794896558e+00 11 | 4.283989982167899502e-01 -1.570796326794896558e+00 12 | 7.139983303613166576e-01 -1.570796326794896558e+00 13 | 9.995976625058435872e-01 -1.570796326794896558e+00 14 | 1.285196994650369628e+00 -1.570796326794896558e+00 15 | 1.570796326794896558e+00 -1.285196994650369851e+00 16 | 1.570796326794896558e+00 -9.995976625058432541e-01 17 | 1.570796326794896558e+00 -7.139983303613166576e-01 18 | 1.570796326794896558e+00 -4.283989982167899502e-01 19 | 1.570796326794896558e+00 -1.427996660722634648e-01 20 | 1.570796326794896558e+00 1.427996660722632427e-01 21 | 1.570796326794896558e+00 4.283989982167899502e-01 22 | 1.570796326794896558e+00 7.139983303613166576e-01 23 | 1.570796326794896558e+00 9.995976625058435872e-01 24 | 1.570796326794896558e+00 1.285196994650369628e+00 25 | 1.285196994650369851e+00 1.570796326794896558e+00 26 | 9.995976625058432541e-01 1.570796326794896558e+00 27 | 7.139983303613166576e-01 1.570796326794896558e+00 28 | 4.283989982167899502e-01 1.570796326794896558e+00 29 | 1.427996660722634648e-01 1.570796326794896558e+00 30 | -1.427996660722632427e-01 1.570796326794896558e+00 31 | -4.283989982167899502e-01 1.570796326794896558e+00 32 | -7.139983303613166576e-01 1.570796326794896558e+00 33 | -9.995976625058435872e-01 1.570796326794896558e+00 34 | -1.285196994650369628e+00 1.570796326794896558e+00 35 | -1.570796326794896558e+00 1.285196994650369851e+00 36 | -1.570796326794896558e+00 9.995976625058432541e-01 37 | -1.570796326794896558e+00 7.139983303613166576e-01 38 | -1.570796326794896558e+00 4.283989982167899502e-01 39 | -1.570796326794896558e+00 1.427996660722634648e-01 40 | -1.570796326794896558e+00 -1.427996660722632427e-01 41 | -1.570796326794896558e+00 -4.283989982167899502e-01 42 | -1.570796326794896558e+00 -7.139983303613166576e-01 43 | -1.570796326794896558e+00 -9.995976625058435872e-01 44 | -1.570796326794896558e+00 -1.285196994650369628e+00 45 | -8.118308301341890755e-04 -6.946406800360100699e-03 46 | -1.079464795312983894e+00 -1.095331959600446936e+00 47 | -1.143748872826308327e+00 1.133254571778189668e+00 48 | 1.127614593932668496e+00 1.148381122526771492e+00 49 | 1.144285033912920024e+00 -1.130915891023488884e+00 50 | 7.357865231893168101e-01 -4.289864633395406024e-01 51 | 4.041089675494913824e-01 7.769433312058171559e-01 52 | -7.142047671915092710e-01 3.787341092915585961e-01 53 | -3.399026174739918083e-01 -7.424764569616427723e-01 54 | -8.762809630236517711e-01 1.306998250935197836e+00 55 | 1.300106105379469579e+00 8.804999000266701126e-01 56 | 8.779301438435366256e-01 -1.304931102019043498e+00 57 | -1.280891357699912358e+00 -8.726140820585561730e-01 58 | -1.156707483041327755e+00 -1.349024496214798141e+00 59 | -1.348909553989847021e+00 1.344912154558517869e+00 60 | 1.343240203435927205e+00 1.350192932444230998e+00 61 | 1.349152250826217925e+00 -1.343968008281884785e+00 62 | -1.385165849888463363e+00 -1.388462326958713744e+00 63 | -8.949936541793692690e-01 -2.693956984583510406e-01 64 | 2.182046839117837145e-01 -8.309172954475544381e-01 65 | -2.590700813975242989e-01 8.916946180842272307e-01 66 | 8.651504643045050402e-01 2.630803266597084855e-01 67 | -1.162715319641933087e+00 6.049718915956517895e-01 68 | 5.977979147142942207e-01 1.168569278122220778e+00 69 | 1.152287647627852296e+00 -6.035768929150923112e-01 70 | -5.746639954175203346e-01 -1.138627169569143449e+00 71 | -1.341171936232554485e+00 -1.165476525506575545e+00 72 | 1.131483515490267822e+00 -1.384089847281359198e+00 73 | 1.382649951335525706e+00 1.132793994849376151e+00 74 | -1.130908793000449553e+00 1.385368717228757873e+00 75 | 1.391459068517064690e+00 -1.123254887438755123e+00 76 | 1.122264488081204936e+00 1.393320365029125707e+00 77 | -1.391555148461683489e+00 1.124507269349577232e+00 78 | -7.867642154878339011e-02 4.760328062416400718e-01 79 | 5.063447973622641207e-01 5.469011918833763947e-02 80 | -4.764695915969595630e-01 -4.425882889383756652e-02 81 | -5.475151191886327984e-02 -5.374352004131099925e-01 82 | 1.111192734186332021e-01 1.105197718117541861e+00 83 | -1.151754533673709391e+00 1.212529602180132532e-01 84 | 1.037947314758432160e+00 -9.140303202481689371e-02 85 | -8.305666036877497049e-01 -7.154601364648558448e-01 86 | 7.750410662792711625e-01 -8.638174500501123454e-01 87 | 8.457913823498808270e-01 7.947256541522200735e-01 88 | -7.744381381382123841e-01 8.664302886858505914e-01 89 | -7.173521807634170022e-02 -1.078186171326531495e+00 90 | 4.293938204417704352e-01 -1.123355563999682172e+00 91 | -4.104280589158635917e-01 1.117253337337019925e+00 92 | 1.095445815160047909e+00 4.195021452219105940e-01 93 | -8.465807574902163291e-01 -1.300719077349441255e+00 94 | -1.323173608167877324e+00 8.601366910999208582e-01 95 | 1.321094960146869113e+00 -8.581302970925125395e-01 96 | 8.557970588816530277e-01 1.326548709524030922e+00 97 | -1.289589345049155567e+00 -5.469077705791017818e-01 98 | -5.731587577825721924e-01 -1.375447114608264965e+00 99 | 3.386366698904786365e-01 -3.247349753462075439e-01 100 | -4.963518133193140502e-01 -3.512834159223075514e-01 101 | 3.582006697596371581e-01 4.005403106751903586e-01 102 | -4.005321025198656515e-01 4.782892403398507075e-01 103 | -1.272932605434254238e+00 -1.067781918096204069e-01 104 | -7.777737058860240138e-02 -8.091607210002192963e-01 105 | 7.740386515092932962e-01 1.251165494393278489e-02 106 | -7.741043207917920332e-01 -5.754480039758358423e-02 107 | -4.112742414631703064e-02 7.454390876737546634e-01 108 | -1.471466165756354083e-01 1.126797522744982905e+00 109 | 1.102054948155894332e+00 1.633117987057628173e-01 110 | 1.856425026528311373e-01 -1.091686434298984043e+00 111 | 5.005326936266334403e-01 -8.920263197014767220e-01 112 | -1.320753821087682800e+00 3.316370558007615266e-01 113 | 3.181933618232986594e-01 1.310504916503889561e+00 114 | 1.290330486715353109e+00 -3.169309223358688365e-01 115 | -2.996719215334580633e-01 -1.293326595148597713e+00 116 | -8.229667354267152790e-01 -1.000329917638195676e+00 117 | -5.257007857730678912e-01 8.952745244270579050e-01 118 | 8.564714893654569172e-01 5.416935938897989855e-01 119 | 8.474480084595580331e-01 1.043993363310063316e+00 120 | 1.032085059709853203e+00 -8.555345610406391854e-01 121 | -1.032507087477268604e+00 8.574296151987419456e-01 122 | -9.996153983970895718e-01 -4.979738094093106859e-01 123 | -3.238897950099914325e-01 -9.891504276236792181e-01 124 | 9.905560240340900435e-01 -3.809340450867254035e-01 125 | -1.021957220710640657e+00 3.680380755391006176e-01 126 | 3.686454498770332933e-01 1.025033325919344485e+00 127 | 1.040600278467370687e+00 9.110508890512022395e-01 128 | -9.012111815120485980e-01 1.052969274543630096e+00 129 | 9.026083742791806142e-01 -1.049628441761922826e+00 130 | 5.825910940447160957e-01 1.389622491030319207e+00 131 | -1.389639363149263174e+00 5.877031100165404087e-01 132 | 1.381759525121651899e+00 -5.837037669695999131e-01 133 | 5.714884261033180701e-01 -1.333949772499766206e+00 134 | -5.653492641872377433e-01 1.335550502279319929e+00 135 | 1.325720070736770895e+00 5.700002661992716879e-01 136 | -2.391481735203405123e-01 1.952861799889144545e-01 137 | -2.103844881262482425e-01 -2.539648046309649354e-01 138 | 6.129851827728696190e-01 4.141666034461549684e-01 139 | 4.170202228338600325e-01 -6.812198246005148894e-01 140 | -1.021654729472128764e+00 -8.785434433926505582e-01 141 | 6.158237183350249166e-01 9.135910390225854272e-01 142 | -8.878347415880960547e-01 6.241394948021178335e-01 143 | 8.892151823379701447e-01 -6.315335222431240902e-01 144 | -6.192744263507394820e-01 6.629755445239696732e-01 145 | 5.688086109850934990e-01 -2.108602297255143532e-01 146 | 2.592474248068475928e-01 1.757590014092840214e-01 147 | 2.004605551698408761e-01 6.048391687100110881e-01 148 | -6.725248929220655203e-01 1.317406341385681245e-01 149 | 1.887171919033387921e-02 -1.324443422624759670e+00 150 | 1.004182174517405796e-01 -2.942334434708706037e-01 151 | 1.318846274909837302e+00 7.853235795651544726e-03 152 | -7.618182087533545985e-04 1.339165984675086962e+00 153 | 7.954082399164523476e-02 -6.813216188826870523e-01 154 | -1.959018464440268059e-01 -6.536318669812564153e-01 155 | -6.786628998492165721e-01 -2.333174008152207324e-01 156 | -2.383972903482288186e-01 6.655294065888675004e-01 157 | -1.377511458731576877e+00 6.898280872116133167e-02 158 | -5.754095779438416214e-01 -8.666920666692533581e-01 159 | 6.776233929013263380e-01 -1.094831463901755564e+00 160 | -1.022435846417170113e+00 -7.835578783073143816e-02 161 | -7.437421190356069411e-01 -4.462629950531853962e-01 162 | 1.078719464169996201e+00 6.866214782943838024e-01 163 | -6.763766542265797765e-01 1.097196394253467044e+00 164 | 2.968248391006591547e-01 -1.336604252910319479e+00 165 | -3.466617800858074611e-02 9.501744032654959593e-01 166 | 9.477201880627261765e-01 8.759786802255559168e-02 167 | 6.450909818861770562e-02 -9.537813157904500017e-01 168 | 1.332051412109630073e+00 2.890755370379113898e-01 169 | -2.831388362355011235e-01 1.344585892540345773e+00 170 | -1.369860871055989637e+00 -3.057590242851561668e-01 171 | 8.241143581341067170e-01 -2.190461348709431544e-01 172 | -1.142619713152329686e+00 -3.015160067305550085e-01 173 | 1.674894010286318569e-01 8.696606880391198890e-01 174 | -8.933337837408118487e-01 1.446337995733145421e-01 175 | 3.352288506456694295e-01 -9.856082238489359826e-01 176 | 1.851168345165559587e-02 2.459132102286308297e-01 177 | 6.495645313329179160e-01 1.990576613322769806e-01 178 | -2.321909466726455040e-01 -2.590072635196215972e-02 179 | 2.967312672754408021e-01 -1.003026312376032458e-01 180 | -5.509344531927041766e-01 -6.081640256126219501e-01 181 | -5.011404788959320511e-01 2.299896569386187528e-01 182 | -3.090728008939458982e-01 -5.243872676944120181e-01 183 | -1.086090033624601547e+00 -7.033651963489994108e-01 184 | 2.285396798128788431e-01 -5.462696334745843485e-01 185 | 6.303740711075592751e-01 6.695691493345162781e-01 186 | 6.369574154476645989e-01 -6.616592398285131571e-01 187 | 4.899358334054156328e-01 -4.750861959879610352e-01 188 | -4.093390038330306235e-01 7.216665354929093779e-01 189 | 4.786893678929649565e-01 2.491681422396633816e-01 190 | 4.418192101172460085e-01 5.738052115311009782e-01 191 | 1.517312689973358275e-01 3.818449573710228639e-01 192 | -------------------------------------------------------------------------------- /Examples/RipsComplex/300pts.mtx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hirani/pydec/930bb8167978a7b980fc547c8d9237de3e690292/Examples/RipsComplex/300pts.mtx -------------------------------------------------------------------------------- /Examples/RipsComplex/driver.py: -------------------------------------------------------------------------------- 1 | """ 2 | Detection of holes in coverage in an idealized abstraction of a sensor 3 | network. Hodge decomposition of a random cochain is used to compute a 4 | harmonic cochain. 5 | 6 | """ 7 | #from scipy import rand 8 | from numpy.random import rand 9 | from scipy.linalg import norm 10 | from scipy.sparse.linalg import cg 11 | 12 | from pydec import kd_tree, flatten, triplot, read_array 13 | from pydec.dec.rips_complex import rips_complex 14 | 15 | 16 | pts = read_array('300pts.mtx') # 300 random points in 2D 17 | rc = rips_complex( pts, 0.15 ) 18 | 19 | simplices = rc.simplices 20 | 21 | cmplx = rc.chain_complex() # boundary operators [ b0, b1, b2 ] 22 | b1 = cmplx[1].astype(float) # edge boundary operator 23 | b2 = cmplx[2].astype(float) # face boundary operator 24 | 25 | x = rand(b1.shape[1]) # random 1-chain 26 | # Decompose x using discrete Hodge decomposition 27 | alpha = cg( b1 * b1.T, b1 * x, rtol=1e-8)[0] # TODO: Was tol before June 2024. Is this change OK 28 | beta = cg( b2.T * b2, b2.T * x, rtol=1e-8)[0] 29 | h = x - (b1.T * alpha) - (b2 * beta) # harmonic component of x 30 | h /= abs(h).max() # normalize h 31 | 32 | 33 | #print 'norm( h )',norm(h) 34 | #print 'norm(b1 * h)', norm(b1 * h) #should be small 35 | #print 'norm(b2.T * h)', norm(b2.T * h) #should be small 36 | 37 | 38 | # plot the results 39 | from pylab import figure, title, plot, axis, xlim, ylim, show 40 | from pydec import triplot, lineplot 41 | 42 | # Nodes of the Rips complex 43 | figure() 44 | plot(pts[:,0],pts[:,1],'ko') 45 | axis('off') 46 | title('Nodes of the Complex') 47 | 48 | # Rips complex (2d triangle mesh) 49 | figure() 50 | title('The Rips Complex') 51 | triplot(pts,simplices[2]) 52 | 53 | # Harmonic 1-chain 54 | figure() 55 | title('Harmonic 1-chain of the Rips complex') 56 | lineplot(pts,simplices[1],linewidths=(15*abs(h)).clip(1,15) ) 57 | 58 | # fiddle with the figures 59 | for i in range(1,4): 60 | figure(i) 61 | axis('off') 62 | axis('equal') 63 | xlim(-0.1,1.1) 64 | ylim(-0.1,1.1) 65 | 66 | show() 67 | 68 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2008, PyDEC Developers 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials provided 14 | with the distribution. 15 | 16 | * Neither the name of the PyDEC Developers nor the names of any 17 | contributors may be used to endorse or promote products derived 18 | from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## **PyDEC: A Python Library for Discretizations of Exterior Calculus.** 2 | 3 | PyDEC is a Python library implementing Discrete Exterior Calculus (DEC) and lowest order finite element exterior calculus (FEEC) i.e., Whitney forms. The main functionality implemented: 4 | - simplicial complexes of dimension n embeded in dimension N >= n 5 | - abstract simplicial complexes (no embedding needed) 6 | - cubical complexes in all dimensions 7 | - boundary operators for all the above type of complexes 8 | - discrete exterior derivative (i.e., coboundary operator) 9 | - DEC discrete Hodge star (i.e., primal-dual diagonal mass matrix) 10 | - FEEC mass matrix for Whitney forms 11 | 12 | The code and companion paper include examples for numerically solving PDEs and computing cohomology: ACM Transactions on Mathematical Software, Vol. 39, No. 1, pp. 3:1-3:41, 2012, [DOI: 10.1145/2382585.2382588](http://dx.doi.org/10.1145/2382585.2382588). 13 | 14 | Installation: 15 | - `cd` to the folder where you cloned PyDEC 16 | - `pip install .` 17 | 18 | Note about installation: 19 | - There is another package called `pydec` which has nothing to do with this package. If you simply do `pip install pydec` then you will get that other package. PyDEC has existed since about 2008, but we were too slow in grabbing `pydec` name on pip :-( 20 | - So for now, please install by first cloning or downloading the source and then using the installation instructions above. 21 | 22 | -------------------------------------------------------------------------------- /pydec/pydec/__init__.py: -------------------------------------------------------------------------------- 1 | """PyDEC: Software and Algorithms for Discrete Exterior Calculus 2 | """ 3 | 4 | from .version import version as __version__ 5 | 6 | from .dec import * 7 | from .fem import * 8 | from .math import * 9 | from .io import * 10 | from .mesh import * 11 | from .util import * 12 | from .vis import * 13 | 14 | __all__ = list(filter(lambda s:not s.startswith('_'),dir())) 15 | #__all__ += ['test', '__version__'] 16 | __all__ += ['__version__'] 17 | 18 | # TODO: replace testing framework with pytest (?) since Tester of nose/numpy is deprecated 19 | #from pydec.testing import Tester 20 | #test = Tester().test 21 | -------------------------------------------------------------------------------- /pydec/pydec/dec/__init__.py: -------------------------------------------------------------------------------- 1 | "DEC data structures and algorithms" 2 | 3 | from .info import __doc__ 4 | 5 | from .rips_complex import * 6 | from .cochain import * 7 | from .simplicial_complex import * 8 | from .regular_cube_complex import * 9 | from .abstract_simplicial_complex import * 10 | 11 | __all__ = list(filter(lambda s:not s.startswith('_'), dir())) 12 | 13 | -------------------------------------------------------------------------------- /pydec/pydec/dec/abstract_simplicial_complex.py: -------------------------------------------------------------------------------- 1 | from numpy import zeros, ones, arange, array, asarray, hstack, vstack, empty, lexsort, atleast_2d 2 | from numpy import all as alltrue #temporary fix. Change above to np.* 3 | from scipy import sparse 4 | 5 | from .simplex_array import simplex_array_parity, simplex_array_boundary, simplex_array_searchsorted 6 | 7 | __all__ = ['abstract_simplicial_complex'] 8 | 9 | class abstract_simplicial_complex: 10 | def __init__(self, simplices): 11 | """Construct an abstract simplicial complex 12 | 13 | Parameters 14 | ---------- 15 | simplices : list of arrays 16 | Maximal simplices of each dimension 17 | TODO 18 | 19 | Examples 20 | -------- 21 | >>> from pydec.dec import abstract_simplicial_complex 22 | >>> from numpy import array 23 | >>> simplices = [array([[4]]), array([[0,3]])] 24 | >>> asc = abstract_simplicial_complex(simplices) 25 | 26 | TODO 27 | 28 | >>> print rc.simplices[0] 29 | >>> print rc.simplices[1] 30 | 31 | Notes 32 | ----- 33 | 34 | TODO explain input handling 35 | 36 | """ 37 | 38 | # convert array-like objects to arrays 39 | simplices = [atleast_2d(s) for s in simplices] 40 | 41 | # find top simplex dimension 42 | D = max([s.shape[1] for s in simplices]) - 1 43 | 44 | # convert simplices list to canonical simplex_array list representation 45 | old_simplices = simplices 46 | simplices = [None] * (D + 1) 47 | for s in old_simplices: 48 | simplices[s.shape[1] - 1] = s 49 | 50 | 51 | # top most simplex array 52 | s = simplices[-1].copy() 53 | 54 | parity = simplex_array_parity(s) 55 | s.sort() 56 | 57 | chain_complex = [None] * (D + 1) 58 | 59 | for d in range(D - 1, -1, -1): 60 | s,B = simplex_array_boundary(s,parity) 61 | 62 | if simplices[d] is not None: 63 | old_s = s 64 | 65 | # sort columns to ensure user-defined faces are in canonical format 66 | simplices[d].sort() 67 | 68 | # merge user-defined faces with boundary faces 69 | s = vstack((s,simplices[d])) 70 | 71 | # sort rows to bring equivalent elements together 72 | s = s[lexsort(s.T[::-1])] 73 | 74 | # find unique simplices 75 | mask = ~hstack((array([False]),alltrue(s[1:] == s[:-1],axis=1))) 76 | s = s[mask] 77 | 78 | # indices of the boundary faces in the full face array 79 | remap = simplex_array_searchsorted(s, old_s) 80 | 81 | # remap indices of boundary operator 82 | B = B.tocoo(copy=False) 83 | B = sparse.coo_matrix((B.data, (remap[B.row], B.col)), (s.shape[0], B.shape[1])) 84 | B = B.tocsr() 85 | 86 | # faces are already in canonical format, so parity is even 87 | parity = zeros(s.shape[0],dtype=s.dtype) 88 | 89 | simplices[d] = s 90 | chain_complex[d+1] = B 91 | 92 | # compute 0-simplices and boundary operator 93 | simplices[0] = arange(simplices[0].max() + 1).reshape(-1,1) 94 | chain_complex[0] = sparse.csr_matrix( (1,len(s)), dtype='uint8') 95 | 96 | # store the cochain complex 97 | Bn = chain_complex[-1] 98 | cochain_complex = [ B.T for B in chain_complex[1:] ] 99 | cochain_complex += [ sparse.csc_matrix( (1, Bn.shape[1]), dtype=Bn.dtype) ] 100 | 101 | # store the data members 102 | self.simplices = simplices 103 | self._chain_complex = chain_complex 104 | self._cochain_complex = cochain_complex 105 | 106 | def complex_dimension(self): 107 | return len(self.cochain_complex()) - 1 108 | 109 | def complex(self): 110 | return self.simplices 111 | 112 | def chain_complex(self): 113 | return self._chain_complex 114 | 115 | def cochain_complex(self): 116 | return self._cochain_complex 117 | 118 | 119 | -------------------------------------------------------------------------------- /pydec/pydec/dec/cochain.py: -------------------------------------------------------------------------------- 1 | __all__ = ['cochain','Cochain','d','star','delta','laplace_beltrami','laplace_derham'] 2 | 3 | from scipy import sparse 4 | from pydec.mesh import Simplex 5 | 6 | class cochain: 7 | """ 8 | Represents a cochain associated with a simplical complex 9 | 10 | The v member of the cochain is left uninitialized. This allows functions like 11 | d(.) and star(.) to operate on single cochains, or groups of cochains together. 12 | The values associated with each cochain are stored in the columns of v. This is 13 | especially useful when v is the identity, and thus represents a basis for all cochains. 14 | Applying operations to this cochain basis allows one to automatically compose the 15 | associated matrix operators. 16 | 17 | Use the get_cochain() and get_cochain_basis() members of the SimplicialComplex class to 18 | safely avoid issues with v. 19 | """ 20 | 21 | def __init__(self,complex,dimension,is_primal): 22 | self.complex = complex 23 | self.k = dimension 24 | self.n = complex.complex_dimension() 25 | self.is_primal = is_primal 26 | self.v = None 27 | def __add__(self,other): 28 | assert(self.k == other.k and self.complex == other.complex) 29 | f = cochain(self.complex,self.k,self.is_primal) 30 | f.v = self.v + other.v 31 | return f 32 | def __sub__(self,other): 33 | assert(self.k == other.k and self.complex == other.complex) 34 | f = cochain(self.complex,self.k,self.is_primal) 35 | f.v = self.v - other.v 36 | return f 37 | def __getitem__(self,key): 38 | if isinstance(key,Simplex): 39 | data = self.complex[len(key) - 1] 40 | index = data.simplex_to_index[key] 41 | value = self.v.__getitem__(index) 42 | if key.parity == data.simplex_parity[index]: 43 | return value 44 | else: 45 | return -value 46 | else: 47 | return self.v.__getitem__(key) 48 | def __setitem__(self,key,value): 49 | if isinstance(key,Simplex): 50 | data = self.complex[len(key) - 1] 51 | index = data.simplex_to_index[key] 52 | if key.parity == data.simplex_parity[index]: 53 | self.v.__setitem__(index,value) 54 | else: 55 | self.v.__setitem__(index,-value) 56 | else: 57 | self.v.__setitem__(key,value) 58 | 59 | def __str__(self): 60 | return 'cochain(k='+str(self.k) + ',n=' + str(self.n) + ',is_primal=' + str(self.is_primal) + '\n' + str(self.v) + ')' 61 | 62 | 63 | def d(f): 64 | """ 65 | Implements the discrete exterior derivative d(.) 66 | 67 | Accepts a cochain and returns the discrete d applied to the cochain 68 | """ 69 | if f.is_primal: 70 | df = cochain(f.complex, f.k + 1, f.is_primal) 71 | if f.k == -1: 72 | df.v = sparse.csr_matrix((f.complex[0].num_simplices,1)) * f.v 73 | elif f.k < -1 or f.k > f.n + 1: 74 | df.v = sparse.csr_matrix((1,1)) * f.v 75 | else: 76 | df.v = f.complex[f.k].d * f.v 77 | return df 78 | else: 79 | df = cochain(f.complex,f.k + 1,f.is_primal) 80 | if f.k == -1: 81 | df.v = sparse.csr_matrix((f.complex[f.n].num_simplices,1)) * f.v 82 | elif f.k < -1 or f.k > f.n + 1: 83 | df.v = sparse.csr_matrix((1,1)) * f.v 84 | else: 85 | df.v = f.complex[f.n - f.k].boundary * f.v 86 | df.v *= (-1) ** f.k 87 | return df 88 | 89 | def star(f): 90 | """ 91 | Implements the discrete Hodge star *(.) 92 | 93 | Accepts a cochain and returns the Hodge star applied to the cochain 94 | """ 95 | if f.k == -1 or f.k == f.n + 1: 96 | starf = cochain(f.complex, f.n - f.k, not f.is_primal) 97 | starf.v = f.v 98 | return starf 99 | elif f.is_primal: 100 | starf = cochain(f.complex, f.n - f.k, not f.is_primal) 101 | starf.v = f.complex[f.k].star * f.v 102 | return starf 103 | else: 104 | starf = cochain(f.complex, f.n - f.k, not f.is_primal) 105 | starf.v = f.complex[f.n - f.k].star_inv * f.v 106 | return starf 107 | 108 | def delta(f): 109 | """ 110 | Implements the discrete codifferental \delta(.) 111 | 112 | Accepts a cochain and returns the codifferental of the cochain 113 | """ 114 | sdsf = star(d(star(f))) 115 | sdsf.v *= (-1)**(f.n*(f.k-1)+1) 116 | return sdsf 117 | 118 | def laplace_derham(f): 119 | """ 120 | Implements the discrete Laplace-de Rham \del(.) 121 | 122 | Accepts a cochain and returns the Laplace-de Rham of the cochain 123 | 124 | """ 125 | return d(delta(f)) + delta(d(f)) 126 | 127 | def laplace_beltrami(f): 128 | """ 129 | Implements the discrete Laplace-Beltrami \del(.) = \delta d 130 | 131 | Accepts a cochain and returns the Laplace-Beltrami of the cochain 132 | 133 | In the case of 0-forms, the second term of the Laplace-de Rham d(\delta(.)) is 0 134 | so the Laplace-Beltrami and Laplace-de Rham will be the same. 135 | """ 136 | return delta(d(f)) 137 | 138 | 139 | 140 | #for backwards compatibility 141 | Cochain = cochain 142 | -------------------------------------------------------------------------------- /pydec/pydec/dec/cube_array.py: -------------------------------------------------------------------------------- 1 | from numpy import vstack, hstack, ones, arange, empty, array, \ 2 | lexsort, ravel, hsplit, empty, ascontiguousarray, ndim 3 | from numpy import all as alltrue #temporary fix. Change above to np.* 4 | 5 | from scipy.sparse import csr_matrix 6 | 7 | __all__ = ['cube_array_search', 'cube_array_boundary'] 8 | 9 | def cube_array_search(k_face_array,k_faces): 10 | """ 11 | Find the row indices (of s) corresponding to the 12 | cubes stored in the rows of cube array v. 13 | It is assumed that the rows of s are sorted in 14 | lexicographical order. 15 | 16 | Example: 17 | 18 | k_face_array = array([[0,0,0],[0,0,1],[0,1,0],[1,0,1]]) 19 | k_faces = array([[0,1,0],[0,0,1]]) 20 | cube_array_searchsorted(k_face_array,k_faces) 21 | 22 | Returns: 23 | 24 | array([2,1]) 25 | 26 | """ 27 | 28 | if ndim(k_face_array) != 2 or ndim(k_faces) != 2: 29 | raise ValueError('expected rank 2 arrays') 30 | 31 | if k_face_array.shape[1] != k_faces.shape[1]: 32 | raise ValueError('number of columns must agree') 33 | 34 | # a dense array used to lookup k_face_array row indices 35 | lookup_grid_dimensions = k_face_array.max(axis=0) + 1 36 | 37 | lookup_grid = empty(lookup_grid_dimensions,dtype=k_faces.dtype) 38 | lookup_grid[:] = -1 39 | lookup_grid[hsplit(k_face_array,k_face_array.shape[1])] = arange(k_face_array.shape[0],dtype=k_faces.dtype).reshape((-1,1)) 40 | row_indices = lookup_grid[hsplit(k_faces,k_faces.shape[1])].reshape((-1)) 41 | 42 | return row_indices 43 | 44 | 45 | def cube_array_boundary(cubes,dim): 46 | """ 47 | Compute the faces and boundary operator for a regular grid of N-cubes 48 | """ 49 | cube_dim = dim 50 | top_dim = cubes.shape[1] - cube_dim 51 | num_cubes = cubes.shape[0] 52 | num_faces = 2*num_cubes*cube_dim 53 | 54 | faces = empty((num_faces,cubes.shape[1]+1),dtype='int32') 55 | 56 | # Use simplex orientation to determine sign of boundary faces 57 | for i in range(cube_dim): 58 | # Faces originating at cube origin 59 | rows = faces[(2*i+0)*num_cubes:(2*i+1)*num_cubes] 60 | rows[:,:top_dim+i ] = cubes[:,:top_dim+i ] 61 | rows[:, top_dim+i:-2] = cubes[:, top_dim+i+1:] 62 | rows[:,-2] = arange(num_cubes) 63 | rows[:,-1] = (-1)**(i+1) 64 | 65 | # Faces originating at other corners of cube 66 | rows = faces[(2*i+1)*num_cubes:(2*i+2)*num_cubes] 67 | rows[:,:top_dim+i ] = cubes[:,:top_dim+i ] 68 | rows[:, top_dim+i:-2] = cubes[:, top_dim+i+1:] 69 | rows[:,-2] = arange(num_cubes) 70 | rows[:,-1] = (-1)**(i+2) 71 | rows[arange(rows.shape[0]),cubes[:,top_dim+i]] += 1 72 | 73 | #sort rows 74 | faces = faces[lexsort([faces[:,i] for i in reversed(range(faces.shape[1]-2))])] 75 | 76 | #find unique faces 77 | face_mask = -hstack((array([False]),alltrue(faces[1:,:-2] == faces[:-1,:-2],axis=1))) 78 | 79 | unique_faces = faces[face_mask,:-2].copy() 80 | 81 | #compute CSR representation for boundary operator 82 | indptr = hstack((arange(num_faces)[face_mask],array([num_faces]))) 83 | indices = ascontiguousarray(faces[:,-2]) 84 | data = ascontiguousarray(faces[:,-1].astype('int8')) 85 | 86 | shape = (len(unique_faces),num_cubes) 87 | boundary_operator = csr_matrix((data,indices,indptr), shape ) 88 | 89 | return unique_faces,boundary_operator 90 | -------------------------------------------------------------------------------- /pydec/pydec/dec/info.py: -------------------------------------------------------------------------------- 1 | """ 2 | DEC data structures 3 | ============= 4 | 5 | Docs should follow this format: 6 | 7 | 8 | Scipy 2D sparse matrix module. 9 | 10 | Original code by Travis Oliphant. 11 | Modified and extended by Ed Schofield and Robert Cimrman. 12 | 13 | There are four available sparse matrix types: 14 | (1) csc_matrix: Compressed Sparse Column format 15 | (2) csr_matrix: Compressed Sparse Row format 16 | (3) lil_matrix: List of Lists format 17 | (4) dok_matrix: Dictionary of Keys format 18 | 19 | To construct a matrix efficiently, use either lil_matrix (recommended) or 20 | dok_matrix. The lil_matrix class supports basic slicing and fancy 21 | indexing with a similar syntax to NumPy arrays. 22 | 23 | To perform manipulations such as multiplication or inversion, first 24 | convert the matrix to either CSC or CSR format. The lil_matrix format is 25 | row-based, so conversion to CSR is efficient, whereas conversion to CSC 26 | is less so. 27 | 28 | Example: 29 | Construct a 10x1000 lil_matrix and add some values to it: 30 | >>> from scipy import sparse, linsolve 31 | >>> from numpy import linalg 32 | >>> from numpy.random import rand 33 | >>> A = sparse.lil_matrix((1000, 1000)) 34 | >>> A[0, :100] = rand(100) 35 | >>> A[1, 100:200] = A[0, :100] 36 | >>> A.setdiag(rand(1000)) 37 | 38 | Now convert it to CSR format and solve (A A^T) x = b for x: 39 | >>> A = A.tocsr() 40 | >>> b = rand(1000) 41 | >>> x = linsolve.spsolve(A * A.T, b) 42 | 43 | Convert it to a dense matrix and solve, and check that the result 44 | is the same: 45 | >>> A_ = A.todense() 46 | >>> x_ = linalg.solve(A_ * A_.T, b) 47 | >>> err = linalg.norm(x-x_) 48 | 49 | Now we can print the error norm with: 50 | print "Norm error =", err 51 | It should be small :) 52 | 53 | """ 54 | 55 | postpone_import = 1 56 | -------------------------------------------------------------------------------- /pydec/pydec/dec/regular_cube_complex.py: -------------------------------------------------------------------------------- 1 | __all__ = ['regular_cube_complex'] 2 | 3 | from numpy import ones, ndim 4 | import scipy 5 | 6 | from pydec.mesh import regular_cube_mesh 7 | from .cube_array import cube_array_boundary 8 | 9 | class regular_cube_complex(list): 10 | """ 11 | Represents the complex for a regular_cube_mesh 12 | """ 13 | class complex_data: 14 | pass 15 | 16 | def __init__(self,mesh): 17 | if not isinstance(mesh,regular_cube_mesh): 18 | raise ValueError('expected a regular_cube_mesh') 19 | 20 | self.mesh = mesh 21 | 22 | self.__construct_hierarchy() 23 | 24 | self.vertices = self[0].cube_array.astype(float) 25 | 26 | #self.__test() 27 | 28 | def __repr__(self): 29 | output = "" 30 | output += "regular_cube_complex:\n" 31 | output += " Shape: " + str(self.mesh.bitmap.shape) + "\n" 32 | output += " Complex:\n" 33 | for i in reversed(range(len(self))): 34 | output += " %10d: %2d-D cubes\n" % (self[i].cube_array.shape[0],i) 35 | return output 36 | 37 | def __test(self): 38 | #test boundary operator 39 | for prev,next in zip(self[:-1],self[1:]): 40 | assert((prev.boundary*next.boundary).nnz == 0) 41 | 42 | def chain_complex(self): 43 | return [lvl.boundary for lvl in self] 44 | 45 | def cochain_complex(self): 46 | return [lvl.boundary.T.tocsr() for lvl in self[1:]] + \ 47 | [scipy.sparse.csr_matrix((1,self[-1].cube_array.shape[0]),dtype='int8')] 48 | 49 | def complex_dimension(self): 50 | return self.mesh.dimension() 51 | 52 | def embedding_dimension(self): 53 | return self.mesh.dimension() 54 | 55 | def __construct_hierarchy(self): 56 | for i in range(self.complex_dimension() + 1): 57 | self.append(self.complex_data()) 58 | 59 | self[-1].cube_array = self.mesh.cube_array() 60 | 61 | for i in reversed(range(self.complex_dimension())): 62 | faces,boundary = cube_array_boundary(self[i+1].cube_array,i+1) 63 | self[i ].cube_array = faces 64 | self[i+1].boundary = boundary 65 | 66 | self[0].boundary = scipy.sparse.csr_matrix((1,self[0].cube_array.shape[0]),dtype='int8') 67 | -------------------------------------------------------------------------------- /pydec/pydec/dec/rips_complex.py: -------------------------------------------------------------------------------- 1 | from numpy import ones, arange, array, asarray, hstack, empty, lexsort 2 | from scipy.sparse import csr_matrix, csc_matrix 3 | 4 | from pydec.mesh.simplex import simplex 5 | from pydec.math import kd_tree 6 | from pydec.util import flatten 7 | 8 | from .simplex_array import simplex_array_searchsorted 9 | 10 | __all__ = ['rips_complex'] 11 | 12 | class rips_complex: 13 | def __init__(self, vertices, delta): 14 | """Construct a Rips complex 15 | 16 | Construct a Rips for an array of vertices and a radius delta. 17 | 18 | Parameters 19 | ---------- 20 | vertices : array_like 21 | An N-by-D array of vertex coordinates where N is the 22 | number of vertices and D is the dimension of the space 23 | delta : float 24 | Radius used to determine the connectivity of the Rips complex. 25 | Vertices which are separated by no more than distance delta 26 | are connected by an edge in the 1-skeleton of the Rips complex. 27 | 28 | Examples 29 | -------- 30 | >>> from pydec.dec import rips_complex 31 | >>> from numpy import array 32 | >>> vertices = array([[0,0],[2,0],[2,2],[0,2],[1,1]], dtype='float') 33 | >>> delta = 2.1 34 | >>> rc = rips_complex(vertices, delta) 35 | >>> print rc.simplices[0] 36 | [[0] 37 | [1] 38 | [2] 39 | [3] 40 | [4]] 41 | >>> print rc.simplices[1] 42 | [[0 1] 43 | [0 3] 44 | [0 4] 45 | [1 2] 46 | [1 4] 47 | [2 3] 48 | [2 4] 49 | [3 4]] 50 | >>> print rc.simplices[2] 51 | [[0 1 4] 52 | [0 3 4] 53 | [1 2 4] 54 | [2 3 4]] 55 | 56 | """ 57 | 58 | vertices = asarray(vertices) 59 | 60 | # construct kD-Tree for spatial queries 61 | tree = kd_tree(vertices) 62 | 63 | # for each vertex, compute all vertices within distance r 64 | L = [tree.in_sphere(v, delta) for v in vertices] 65 | 66 | # convert list of list structure into array of edges 67 | i = arange(len(vertices)).repeat( [len(x) for x in L] ) 68 | j = array( flatten(L) ) 69 | edges = hstack((i.reshape(-1,1),j.reshape(-1,1))) 70 | 71 | # compute simplices of the Rips complex from edges (1-skeleton of Rips complex) 72 | simplices = rips_simplices(vertices.shape[0], edges, vertices.shape[1]) 73 | 74 | # compute boundary operators of the Rips complex 75 | chain_complex = rips_chain_complex(simplices) 76 | 77 | # store the cochain complex 78 | Bn = chain_complex[-1] 79 | cochain_complex = [ B.T for B in chain_complex[1:] ] 80 | cochain_complex += [ csr_matrix( (1, Bn.shape[1]), dtype=Bn.dtype) ] 81 | 82 | # store the data members 83 | self.vertices = vertices 84 | self.simplices = simplices 85 | self._chain_complex = chain_complex 86 | self._cochain_complex = cochain_complex 87 | 88 | def complex_dimension(self): 89 | return len(self.cochain_complex()) - 1 90 | 91 | def embedding_dimension(self): 92 | return self.vertices.shape[1] 93 | 94 | def complex(self): 95 | return self.simplices 96 | 97 | def chain_complex(self): 98 | return self._chain_complex 99 | 100 | def cochain_complex(self): 101 | return self._cochain_complex 102 | 103 | 104 | def rips_chain_complex(simplices): 105 | """Construct the boundary operators for the Rips complex 106 | 107 | Constructs the boundary operators for the Rips complex for 108 | a given a list of simplex arrays. 109 | 110 | Parameters 111 | ---------- 112 | simplices : list of arrays 113 | List of length D, where simplices[i] is the simplex 114 | array representing the i-dimensional simplices. 115 | 116 | Returns 117 | ------- 118 | Bs : list of sparse matrices 119 | List of length D + 1 where Bs[i] is the boundary operator 120 | for the i-dimensional simplices. 121 | 122 | Examples 123 | -------- 124 | TODO 125 | 126 | """ 127 | Bs = [] 128 | 129 | dtype = 'float32' 130 | 131 | simplices = [asarray(x) for x in simplices] 132 | 133 | B0 = csc_matrix( (1, len(simplices[0]) ), dtype=dtype ) 134 | Bs.append( B0 ) 135 | 136 | if len(simplices) == 1: 137 | B1 = csc_matrix( (len(simplices[0]), 1), dtype=dtype ) 138 | Bs.append(B1) 139 | return Bs 140 | 141 | B1 = empty( 2*len(simplices[1]), dtype=dtype ) 142 | B1[0::2] = -1 143 | B1[1::2] = 1 144 | B1 = csc_matrix( ( B1, simplices[1].ravel(), arange(0, 2*(len(simplices[1]) + 1), 2)), \ 145 | shape=(len(simplices[0]), len(simplices[1])) ) 146 | Bs.append( B1 ) 147 | 148 | for f,s in zip(simplices[1:-1],simplices[2:]): 149 | k = f.shape[1] 150 | 151 | faces = empty(( len(s), (k+1), k),dtype=s.dtype) 152 | for i in range(k+1): 153 | faces[:,i,:i] = s[:, : i ] 154 | faces[:,i,i:] = s[:,i+1: ] 155 | 156 | indptr = arange(0, (k+1)*(len(s) + 1), k+1) 157 | indices = simplex_array_searchsorted(f, faces.reshape(-1,k) ) 158 | data = empty( faces.shape[:-1], dtype=dtype ) 159 | for i in range(k+1): 160 | data[:,i] = (-1)**i 161 | data = data.ravel() 162 | 163 | Bs.append( csc_matrix( (data,indices,indptr), shape=(len(f),len(s)) ) ) 164 | 165 | return Bs 166 | 167 | 168 | 169 | def rips_simplices(num_vertices, edges, k): 170 | """Compute simplices of the Rips complex 171 | 172 | Returns a list of simplex arrays representing 173 | the 0 to k-dimensional simplices of the Rips complex. 174 | 175 | Parameters 176 | ---------- 177 | num_verticles : integer 178 | Number of vertices/points/nodes in the Rips complex 179 | edges : array_like 180 | An N-by-2 array containing the edges of the Rips complex 181 | k : integer 182 | Maximum simplex dimension 183 | 184 | Returns 185 | ------- 186 | simplices : list of arrays 187 | List of length k+1, where simplices[i] is the simplex 188 | array representing the i-dimensional simplices. 189 | 190 | Examples 191 | -------- 192 | >>> from numpy import array 193 | >>> edges = array([[0,1],[0,2],[1,0],[1,2],[2,0],[2,1]]) 194 | >>> simplices = rips_simplices(3, edges, 2) 195 | >>> print simplices[0] 196 | [[0] 197 | [1] 198 | [2]] 199 | >>> print simplices[1] 200 | [[0 1] 201 | [0 2] 202 | [1 2]] 203 | >>> print simplices[2] 204 | [[0 1 2]] 205 | 206 | """ 207 | 208 | edges = asarray(edges, dtype='int32') 209 | 210 | simplices = [] 211 | 212 | if len(edges.shape) != 2 and edges.shape[1] != 2: 213 | raise ValueError('expected N by 2 array for argument edges') 214 | 215 | # node simplices 216 | simplices.append( arange(num_vertices, dtype=edges.dtype).reshape(-1,1) ) 217 | 218 | # no work to do 219 | if k == 0: return simplices 220 | 221 | edges = edges[edges[:,0] < edges[:,1]] # orient edges 222 | if edges.shape[0] != 0: 223 | edges = edges[lexsort( edges.T[::-1] )] # sort edges 224 | simplices.append( edges ) 225 | 226 | if k == 1 or edges.shape[0] == 0: return simplices 227 | 228 | # E[i,j] == 1 iff (i,j) is an edge and i < j 229 | E = csr_matrix( (ones(len(edges), dtype='int8'), edges.T), shape=(num_vertices,num_vertices)) 230 | 231 | k_faces = edges 232 | 233 | for n in range(1,k): 234 | if k_faces.shape[0] == 0: 235 | break 236 | 237 | indptr = arange(0, (n+1) * (len(k_faces)+1), (n+1) ) 238 | indices = k_faces.ravel() 239 | data = ones(len(indices), dtype='int8') 240 | 241 | F = csr_matrix((data, indices, indptr), shape=(len(k_faces), num_vertices)) 242 | 243 | # FE[i,j] == n+1 if all vertices in face i are adjacent to vertex j 244 | FE = F*E 245 | FE.data[FE.data != n+1] = 0 246 | FE.eliminate_zeros() 247 | FE.sort_indices() 248 | 249 | row = FE.tocoo(copy=False).row 250 | k_faces = hstack( (k_faces[row],FE.indices.reshape(-1,1)) ) 251 | 252 | if len(k_faces) == 0: 253 | return simplices 254 | 255 | simplices.append( k_faces ) 256 | 257 | return simplices 258 | 259 | -------------------------------------------------------------------------------- /pydec/pydec/dec/simplex_array.py: -------------------------------------------------------------------------------- 1 | __all__ = ['simplex_array_searchsorted','simplex_array_boundary','simplex_array_parity'] 2 | 3 | 4 | from numpy import ravel, zeros, ones, arange, empty, array, lexsort, \ 5 | hstack, vstack, ndim, bincount, cumsum, ascontiguousarray, zeros_like, \ 6 | concatenate, asarray 7 | from numpy import all as alltrue #temporary fix. Change above to np.* 8 | from scipy.sparse import csr_matrix 9 | 10 | def simplex_array_searchsorted(s, v): 11 | """Find the row indices (of s) corresponding to the simplices stored 12 | in the rows of simplex array v. The rows of s must be stored in 13 | lexicographical order. 14 | 15 | Example 16 | ------- 17 | 18 | >>> from numpy import array 19 | >>> s = array([[0,1],[0,2],[1,2],[1,3]]) 20 | >>> v = array([[1,2],[0,2]]) 21 | >>> simplex_array_searchsorted(s,v) 22 | array([2, 1]) 23 | 24 | """ 25 | 26 | s = asarray(s) 27 | v = asarray(v) 28 | 29 | if ndim(s) != 2 or ndim(v) != 2: 30 | raise ValueError('expected rank 2 arrays') 31 | 32 | if s.shape[1] != v.shape[1]: 33 | raise ValueError('number of columns must agree') 34 | 35 | # compute row indices by sorting both arrays together 36 | Ns = s.shape[0] 37 | Nv = v.shape[0] 38 | 39 | perm = lexsort(vstack((s,v))[:,::-1].T) 40 | 41 | flags = concatenate( (ones(Ns,dtype=int),zeros(Nv,dtype=int)) ) 42 | indices = empty(Ns+Nv, dtype=int) 43 | indices[perm] = cumsum(flags[perm]) 44 | indices = indices[Ns:].copy() 45 | indices -= 1 46 | 47 | return indices 48 | 49 | 50 | 51 | 52 | def simplex_array_parity(s): 53 | """Compute the relative parity of an array of simplices 54 | """ 55 | s = s.copy() 56 | 57 | M,N = s.shape 58 | 59 | # number of transpositions used to sort the 60 | # indices of each simplex (row of s) 61 | trans = zeros_like(s[:,0]) 62 | seq = arange(M) 63 | 64 | for i in range(N-1): 65 | pos = s.argmin(axis=1) 66 | s[seq,pos] = s[:,0] 67 | pos.clip(0,1,pos) 68 | trans += pos 69 | s = s[:,1:] 70 | 71 | trans %= 2 #compute parity 72 | 73 | return trans 74 | 75 | 76 | 77 | 78 | def simplex_array_boundary(s,parity): 79 | """ 80 | Compute the boundary faces and boundary operator of an 81 | array of simplices with given simplex parities 82 | 83 | E.g. 84 | 85 | For a mesh with two triangles [0,1,2] and [1,3,2], the second 86 | triangle has opposite parity relative to sorted order. 87 | 88 | simplex_array_boundary(array([[0,1,2],[1,2,3]]),array([0,1])) 89 | 90 | """ 91 | #TODO handle edge case as special case 92 | 93 | num_simplices = s.shape[0] 94 | faces_per_simplex = s.shape[1] 95 | num_faces = num_simplices * faces_per_simplex 96 | 97 | orientations = 1 - 2*parity 98 | 99 | #faces[:,:-2] are the indices of the faces 100 | #faces[:,-2] is the index of the simplex whose boundary produced the face 101 | #faces[:,-1] is the orientation of the face in the boundary of the simplex 102 | faces = empty((num_faces,s.shape[1]+1),dtype=s.dtype) 103 | for i in range(faces_per_simplex): 104 | rows = faces[num_simplices*i:num_simplices*(i+1)] 105 | 106 | rows[:, : i] = s[:, :i] 107 | rows[:,i :-2] = s[:,i+1: ] 108 | rows[:, -2 ] = arange(num_simplices) 109 | rows[:, -1 ] = ((-1)**i)*orientations 110 | 111 | #sort rows 112 | faces = faces[lexsort( faces[:,:-2].T[::-1] )] 113 | 114 | #find unique faces 115 | face_mask = ~hstack((array([False]), alltrue(faces[1:,:-2] == faces[:-1,:-2], axis=1))) 116 | 117 | unique_faces = faces[face_mask,:-2] 118 | 119 | #compute CSR representation for boundary operator 120 | csr_indptr = hstack((arange(num_faces)[face_mask],array([num_faces]))) 121 | csr_indices = ascontiguousarray(faces[:,-2]) 122 | csr_data = faces[:,-1].astype('int8') 123 | 124 | shape = (len(unique_faces),num_simplices) 125 | boundary_operator = csr_matrix((csr_data,csr_indices,csr_indptr), shape) 126 | 127 | return unique_faces,boundary_operator 128 | 129 | 130 | 131 | 132 | 133 | #################################### 134 | ## Fast C implementations 135 | #################################### 136 | # 137 | # 138 | #import scipy 139 | # 140 | #def simplex_array_searchsorted(s,v): 141 | # """ 142 | # Find the row indices (of s) corresponding to the 143 | # simplices stored in the rows of simplex array v. 144 | # It is assumed that the rows of s are sorted in 145 | # lexicographical order. 146 | # 147 | # Example: 148 | # 149 | # s = array([[0,1],[0,2],[1,2],[1,3]]) 150 | # v = array([[1,2],[0,2]]) 151 | # simplex_array_searchsorted(s,v) 152 | # 153 | # Returns: 154 | # 155 | # array([2,1]) 156 | # 157 | # """ 158 | # 159 | # if ndim(s) != 2 or ndim(v) != 2: 160 | # raise ValueError,'expected rank 2 arrays' 161 | # 162 | # if s.shape[1] != v.shape[1]: 163 | # raise ValueError,'number of columns must agree' 164 | # 165 | # s_row = s.shape[0] 166 | # v_row = v.shape[0] 167 | # s_col = s.shape[1] 168 | # s_max = int(s[:,0].max()) 169 | # 170 | # first_index = cumsum(hstack((array([0]),bincount(s[:,0])))) 171 | # indices = empty(v.shape[0],dtype=s.dtype) 172 | # 173 | # code = """ 174 | # #line 45 "simplex_array.py" 175 | # 176 | # for(int i = 0; i < v_row; i++){ 177 | # 178 | # int v_i0 = v(i,0); 179 | # 180 | # if (v(i,0) > s_max) 181 | # py::fail(PyExc_ValueError, "index exceeds expected range"); 182 | # 183 | # int row_start = first_index(v_i0); 184 | # int row_end = first_index(v_i0+1); 185 | # 186 | # int row_index = -1; 187 | # for(int k = row_start; k < row_end; k++){ 188 | # bool mismatch = false; 189 | # for(int j = 1; j < s_col; j++){ 190 | # if(v(i,j) != s(k,j)){ 191 | # mismatch = true; 192 | # break; 193 | # } 194 | # } 195 | # if(!mismatch){ 196 | # row_index = k; 197 | # break; 198 | # } 199 | # } 200 | # if (row_index == -1) 201 | # py::fail(PyExc_ValueError, "simplex not found"); 202 | # 203 | # indices(i) = row_index; 204 | # } 205 | # """ 206 | # 207 | # err = scipy.weave.inline(code, 208 | # ['s','v','first_index', 'indices', 's_row', 'v_row', 's_col','s_max'], 209 | # type_converters = scipy.weave.converters.blitz, 210 | # compiler = 'gcc') 211 | # 212 | # return indices 213 | # 214 | # 215 | # 216 | #def simplex_array_parity(s): 217 | # """ 218 | # Compute the relative parity of an array of simplices 219 | # """ 220 | # perms = s.argsort() 221 | # 222 | # 223 | # n_rows,n_cols = perms.shape 224 | # 225 | # parity = zeros(n_rows,dtype=perms.dtype) 226 | # seen = zeros(n_cols,dtype=perms.dtype) 227 | # 228 | # code = """ 229 | # #line 26 "relaxation.py" 230 | # 231 | # for(int i = 0; i < n_rows; i++){ 232 | # int num_cycles = 0; 233 | # for(int j = 0; j < n_cols; j++){ 234 | # 235 | # if(seen(j) == 1) continue; 236 | # 237 | # int k = j; 238 | # 239 | # num_cycles++; 240 | # while ( true ){ 241 | # seen(k) = 1; 242 | # k = perms(i,k); 243 | # if (j == k) break; 244 | # } 245 | # } 246 | # for(int j = 0; j < n_cols; j++){ seen(j) = 0; } //reset seen 247 | # parity(i) = (n_cols - num_cycles) % 2; 248 | # } 249 | # """ 250 | # 251 | # from time import clock 252 | # 253 | # 254 | # # compiler keyword only needed on windows with MSVC installed 255 | # err = scipy.weave.inline(code, 256 | # ['perms', 'parity', 'seen', 'n_rows', 'n_cols' ], 257 | # type_converters = scipy.weave.converters.blitz, 258 | # compiler = 'gcc') 259 | # 260 | # return parity 261 | 262 | 263 | -------------------------------------------------------------------------------- /pydec/pydec/dec/tests/meshes/README.txt: -------------------------------------------------------------------------------- 1 | These meshes are used to test the correctness of simplicial_complex 2 | 3 | sphere3.xml 4 | simplicial_mesh< 2D manifold, 3D embedding, 66 vertices, 128 elements > 5 | 6 | torus_tet.xml 7 | simplicial_mesh< 3D manifold, 3D embedding, 204 vertices, 592 elements > 8 | 9 | cube.xml 10 | simplicial_mesh< 2D manifold, 3D embedding, 8 vertices, 12 elements > 11 | 12 | -------------------------------------------------------------------------------- /pydec/pydec/dec/tests/meshes/cube.elements: -------------------------------------------------------------------------------- 1 | 6 2 | format=binary 3 | dtype=int32 4 | rank=2 5 | dims=12,3 6 | version=1.0 7 | type=ndarray 8 |  -------------------------------------------------------------------------------- /pydec/pydec/dec/tests/meshes/cube.vertices: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hirani/pydec/930bb8167978a7b980fc547c8d9237de3e690292/pydec/pydec/dec/tests/meshes/cube.vertices -------------------------------------------------------------------------------- /pydec/pydec/dec/tests/meshes/cube.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /pydec/pydec/dec/tests/meshes/sphere3.elements: -------------------------------------------------------------------------------- 1 | 6 2 | format=binary 3 | dtype=int32 4 | rank=2 5 | dims=128,3 6 | version=1.0 7 | type=ndarray 8 |    9 |    10 |  11 |    12 |   13 |    14 |  ! ! "!"#!!$#$!#%$&&&%'$$&'&$'&%(''('()*)*)+*, -,- ,.-+/**,/,*/.,+0//1.01/021-3-3-.34545465.7334743764.177861871285959569: ;":; :<;6=99:=:9=<:68==><8>=82>";##?%;?#;<?%@(()@)(@+)<A??@%A@?A+@<>AA0+>0A>20 -------------------------------------------------------------------------------- /pydec/pydec/dec/tests/meshes/sphere3.vertices: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hirani/pydec/930bb8167978a7b980fc547c8d9237de3e690292/pydec/pydec/dec/tests/meshes/sphere3.vertices -------------------------------------------------------------------------------- /pydec/pydec/dec/tests/meshes/sphere3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /pydec/pydec/dec/tests/meshes/torus_tet.elements: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hirani/pydec/930bb8167978a7b980fc547c8d9237de3e690292/pydec/pydec/dec/tests/meshes/torus_tet.elements -------------------------------------------------------------------------------- /pydec/pydec/dec/tests/meshes/torus_tet.vertices: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hirani/pydec/930bb8167978a7b980fc547c8d9237de3e690292/pydec/pydec/dec/tests/meshes/torus_tet.vertices -------------------------------------------------------------------------------- /pydec/pydec/dec/tests/meshes/torus_tet.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /pydec/pydec/dec/tests/test_abstract_simplicial_complex.py: -------------------------------------------------------------------------------- 1 | from pydec.testing import * 2 | 3 | from pydec.dec import abstract_simplicial_complex 4 | 5 | class TestAbstractSimplicialComplex(TestCase): 6 | def test_1d(self): 7 | # two edges 8 | s = [ [[0,1],[2,3]] ] 9 | asc = abstract_simplicial_complex(s) 10 | assert_equal(asc.simplices[0], [[0],[1],[2],[3]]) 11 | assert_equal(asc.simplices[1], [[0,1],[2,3]]) 12 | 13 | B0 = asc.chain_complex()[0].todense() 14 | B1 = asc.chain_complex()[1].todense() 15 | 16 | assert_equal(B0, [[0,0,0,0]]) 17 | assert_equal(B1, [[-1, 0], 18 | [ 1, 0], 19 | [ 0,-1], 20 | [ 0, 1]]) 21 | 22 | # two edges, two redundant vertices 23 | s = [ [[0],[2]], [[0,1],[2,3]] ] 24 | asc = abstract_simplicial_complex(s) 25 | assert_equal(asc.simplices[0], [[0],[1],[2],[3]]) 26 | assert_equal(asc.simplices[1], [[0,1],[2,3]]) 27 | 28 | B0 = asc.chain_complex()[0].todense() 29 | B1 = asc.chain_complex()[1].todense() 30 | 31 | assert_equal(B0, [[0,0,0,0]]) 32 | assert_equal(B1, [[-1, 0], 33 | [ 1, 0], 34 | [ 0,-1], 35 | [ 0, 1]]) 36 | 37 | # two edges, two maximal vertices 38 | s = [ [[2],[5]], [[0,1],[3,4]] ] 39 | asc = abstract_simplicial_complex(s) 40 | assert_equal(asc.simplices[0], [[0],[1],[2],[3],[4],[5]]) 41 | assert_equal(asc.simplices[1], [[0,1],[3,4]]) 42 | 43 | B0 = asc.chain_complex()[0].todense() 44 | B1 = asc.chain_complex()[1].todense() 45 | 46 | assert_equal(B0, [[0,0,0,0,0,0]]) 47 | assert_equal(B1, [[-1, 0], 48 | [ 1, 0], 49 | [ 0, 0], 50 | [ 0,-1], 51 | [ 0, 1], 52 | [ 0, 0]]) 53 | 54 | def test_2d(self): 55 | # one triangle 56 | s = [ [[0,1,2]] ] 57 | asc = abstract_simplicial_complex(s) 58 | assert_equal(asc.simplices[0], [[0],[1],[2]]) 59 | assert_equal(asc.simplices[1], [[0,1],[0,2],[1,2]]) 60 | assert_equal(asc.simplices[2], [[0,1,2]]) 61 | 62 | B0 = asc.chain_complex()[0].todense() 63 | B1 = asc.chain_complex()[1].todense() 64 | B2 = asc.chain_complex()[2].todense() 65 | 66 | assert_equal(B0, [[0,0,0]]) 67 | assert_equal(B1, [[-1,-1, 0], 68 | [ 1, 0,-1], 69 | [ 0, 1, 1]]) 70 | assert_equal(B2, [[ 1], 71 | [-1], 72 | [ 1]]) 73 | 74 | # one triangle, one redundant edge 75 | s = [ [[0,2]], [[0,1,2]] ] 76 | asc = abstract_simplicial_complex(s) 77 | assert_equal(asc.simplices[0], [[0],[1],[2]]) 78 | assert_equal(asc.simplices[1], [[0,1],[0,2],[1,2]]) 79 | assert_equal(asc.simplices[2], [[0,1,2]]) 80 | 81 | B0 = asc.chain_complex()[0].todense() 82 | B1 = asc.chain_complex()[1].todense() 83 | B2 = asc.chain_complex()[2].todense() 84 | 85 | assert_equal(B0, [[0,0,0]]) 86 | assert_equal(B1, [[-1,-1, 0], 87 | [ 1, 0,-1], 88 | [ 0, 1, 1]]) 89 | assert_equal(B2, [[ 1], 90 | [-1], 91 | [ 1]]) 92 | 93 | # one triangle, two maximal edges 94 | s = [ [[0,3],[4,5]], [[0,1,2]] ] 95 | asc = abstract_simplicial_complex(s) 96 | assert_equal(asc.simplices[0], [[0],[1],[2],[3],[4],[5]]) 97 | assert_equal(asc.simplices[1], [[0,1],[0,2],[0,3],[1,2],[4,5]]) 98 | assert_equal(asc.simplices[2], [[0,1,2]]) 99 | 100 | B0 = asc.chain_complex()[0].todense() 101 | B1 = asc.chain_complex()[1].todense() 102 | B2 = asc.chain_complex()[2].todense() 103 | 104 | assert_equal(B0, [[0,0,0,0,0,0]]) 105 | assert_equal(B1, [[-1,-1,-1, 0, 0], 106 | [ 1, 0, 0,-1, 0], 107 | [ 0, 1, 0, 1, 0], 108 | [ 0, 0, 1, 0, 0], 109 | [ 0, 0, 0, 0,-1], 110 | [ 0, 0, 0, 0, 1]]) 111 | assert_equal(B2, [[ 1], 112 | [-1], 113 | [ 0], 114 | [ 1], 115 | [ 0]]) 116 | 117 | 118 | -------------------------------------------------------------------------------- /pydec/pydec/dec/tests/test_cochain.py: -------------------------------------------------------------------------------- 1 | from pydec.testing import * 2 | 3 | from scipy import array, alltrue, array, sqrt, sparse 4 | 5 | from pydec.dec import simplicial_complex 6 | from pydec.dec.cochain import d, laplace_beltrami, laplace_derham, star 7 | from pydec.mesh.simplex import simplex 8 | 9 | 10 | all_cases = [] 11 | #unit line segments 12 | all_cases.append((array([[0],[1]]),array([[0,1]]))) 13 | all_cases.append((array([[1],[0]]),array([[1,0]]))) 14 | #regular triangle, edge length 1 15 | all_cases.append((array([[0,0],[1,0],[0.5,sqrt(3.0)/2.0]]),array([[0,1,2]]))) 16 | #regular tet, edge length sqrt(2) 17 | all_cases.append((array([[0, 0, 0], [0, 1, 1], [1, 0, 1], [1, 1, 0]]),array([[0,1,2,3]]))) 18 | # 4 triangles in a square 19 | all_cases.append((array([[0,0],[1,0],[1,1],[0,1],[0.5,0.5]]),array([[0,1,4],[1,2,4],[2,3,4],[0,4,3]]))) 20 | #two line segments 21 | all_cases.append((array([[0],[1],[2]]),array([[0,1],[1,2]]))) 22 | #three line segments 23 | all_cases.append((array([[0],[1],[2],[3]]),array([[0,1],[1,2],[2,3]]))) 24 | 25 | 26 | 27 | def test_three_edges(): 28 | V = array([[0],[1],[2],[3]]) 29 | S = array([[0,1],[1,2],[2,3]]) 30 | sc = simplicial_complex((V,S)) 31 | f0 = sc.get_cochain(0) 32 | 33 | assert_equal(f0.complex,sc) 34 | assert_equal(f0.k,0) 35 | assert_equal(f0.is_primal,True) 36 | assert_equal(f0.v,array([0,0,0,0])) 37 | 38 | f0.v[:] = 10 39 | f1 = d(f0) 40 | 41 | assert_equal(f1.complex,sc) 42 | assert_equal(f1.k,1) 43 | assert_equal(f1.is_primal,True) 44 | assert_equal(f1.v,array([0,0,0])) 45 | 46 | def test_get_set(): 47 | V = array([[0,0],[1,0],[0.5,sqrt(3.0)/2.0]]) 48 | S = array([[0,1,2]]) 49 | sc = simplicial_complex((V,S)) 50 | 51 | c0 = sc.get_cochain(0) 52 | assert_equal(c0[0],0) 53 | assert_equal(c0[0],0) 54 | assert_equal(c0[0],0) 55 | 56 | c0[simplex([0],parity=0)] = 10 57 | c0[simplex([1],parity=0)] = 20 58 | c0[simplex([2],parity=1)] = 30 59 | assert_equal(c0[simplex([0],parity=0)],10) 60 | assert_equal(c0[simplex([1],parity=1)],-20) 61 | assert_equal(c0[simplex([2],parity=1)],30) 62 | assert_equal(c0[0],10) 63 | assert_equal(c0[1],20) 64 | assert_equal(c0[2],-30) 65 | 66 | 67 | class TestCochainFunctions(TestCase): 68 | def test_d(self): 69 | for case in all_cases: 70 | sc = simplicial_complex(case) 71 | N = sc.complex_dimension() 72 | for p in range(sc.complex_dimension()): 73 | df = d(sc.get_cochain_basis(p)) 74 | assert_equal( (df.v - sc[p].d).nnz, 0) 75 | 76 | # test exactness 77 | assert_equal(d(df).v.nnz,0) 78 | 79 | def test_dual_d(self): 80 | for case in all_cases: 81 | sc = simplicial_complex(case) 82 | N = sc.complex_dimension() 83 | for p in range(sc.complex_dimension()): 84 | df = d(sc.get_cochain_basis(p, is_primal=False)) 85 | assert_equal( (df.v - (-1)**p*sc[N-p].boundary).nnz, 0) 86 | 87 | # test exactness 88 | assert_equal(d(df).v.nnz,0) 89 | 90 | 91 | def test_star(self): 92 | for case in all_cases: 93 | sc = simplicial_complex(case) 94 | N = sc.complex_dimension() 95 | for p in range(N): 96 | if not (sc[p].dual_volume > 0).all(): continue #skip non-wellcentered 97 | 98 | result = star(star(sc.get_cochain_basis(p))).v 99 | expected = (-1)**(p*(N - p)) * sparse.identity(sc[p].num_simplices) 100 | assert_almost_equal(result.todense(),expected.todense()) 101 | 102 | 103 | def test_delta(self): 104 | pass 105 | 106 | def test_laplacian(self): 107 | """Check that Laplace-Beltrami and Laplace-DeRham agree for 0-forms 108 | 109 | This makes sure that d() and star() work in the -1 and N+1 cases 110 | """ 111 | for case in all_cases: 112 | sc = simplicial_complex(case) 113 | f = sc.get_cochain_basis(0) 114 | assert_equal((laplace_beltrami(f) - laplace_derham(f)).v.nnz, 0) 115 | 116 | 117 | -------------------------------------------------------------------------------- /pydec/pydec/dec/tests/test_cube_array.py: -------------------------------------------------------------------------------- 1 | from pydec.testing import * 2 | 3 | from scipy import arange, array 4 | 5 | from pydec.dec.cube_array import * 6 | 7 | class test_cube_array_search(TestCase): 8 | def test_simple1(self): 9 | k_face_array = array([[0,0,0], 10 | [0,0,1], 11 | [0,1,0], 12 | [1,0,1]]) 13 | rows = array([0,2,1,1,3,0]) 14 | k_faces = k_face_array[rows, :] 15 | assert_equal(cube_array_search(k_face_array, k_faces), rows) 16 | 17 | def test_simple2(self): 18 | k_face_array = array([[0,1,0], 19 | [0,0,2], 20 | [0,2,0], 21 | [1,0,3]]) 22 | rows = array([0,2,1,1,3,0]) 23 | k_faces = k_face_array[rows, :] 24 | assert_equal(cube_array_search(k_face_array, k_faces), rows) 25 | 26 | def test_simple3(self): 27 | k_face_array = array([[0,1,0,1], 28 | [0,0,1,2], 29 | [0,2,0,2], 30 | [1,0,0,1]]) 31 | 32 | rows = array([2,1,0,0,3,1,2]) 33 | k_faces = k_face_array[rows,:] 34 | assert_equal(cube_array_search(k_face_array, k_faces), rows) 35 | 36 | 37 | class test_cube_array_boundary(TestCase): 38 | def test_boundary1_1D(self): 39 | """boundary_1 of 1D mesh [XXX]""" 40 | cubes = array([[0,0], 41 | [1,0], 42 | [2,0]]) 43 | dim = 1 44 | 45 | faces,boundary = cube_array_boundary(cubes, dim) 46 | 47 | assert_equal(faces, [[0], 48 | [1], 49 | [2], 50 | [3]] ) 51 | assert_equal(boundary.toarray(), [[-1, 0, 0], 52 | [ 1,-1, 0], 53 | [ 0, 1,-1], 54 | [ 0, 0, 1]] ) 55 | def test_boundary1_1D_with_holes(self): 56 | """boundary_1 of 1D mesh [X00X0X]""" 57 | cubes = array([[0,0], 58 | [3,0], 59 | [5,0]]) 60 | dim = 1 61 | 62 | faces,boundary = cube_array_boundary(cubes, dim) 63 | 64 | assert_equal(faces, [[0], 65 | [1], 66 | [3], 67 | [4], 68 | [5], 69 | [6]] ) 70 | assert_equal(boundary.toarray(), [[-1, 0, 0], 71 | [ 1, 0, 0], 72 | [ 0,-1, 0], 73 | [ 0, 1, 0], 74 | [ 0, 0,-1], 75 | [ 0, 0, 1]] ) 76 | 77 | def test_boundary1_2D(self): 78 | """boundary_1 of 2D mesh [[X]]""" 79 | cubes = array([[0,0,0], 80 | [0,0,1], 81 | [0,1,0], 82 | [1,0,1]]) 83 | dim = 1 84 | 85 | faces,boundary = cube_array_boundary(cubes, dim) 86 | 87 | assert_equal(faces, [[0,0], 88 | [0,1], 89 | [1,0], 90 | [1,1]] ) 91 | assert_equal(boundary.toarray(), [[-1,-1, 0, 0], 92 | [ 0, 1,-1, 0], 93 | [ 1, 0, 0,-1], 94 | [ 0, 0, 1, 1]] ) 95 | 96 | 97 | def test_boundary2_2D(self): 98 | """boundary_2 of 2D mesh [[XX],[XX]]""" 99 | cubes = array([[0,0,0,1], 100 | [0,1,0,1], 101 | [1,0,0,1], 102 | [1,1,0,1]]) 103 | dim = 2 104 | 105 | faces,boundary = cube_array_boundary(cubes, dim) 106 | 107 | assert_equal(faces, [[0,0,0], 108 | [0,0,1], 109 | [0,1,0], 110 | [0,1,1], 111 | [0,2,0], 112 | [1,0,0], 113 | [1,0,1], 114 | [1,1,0], 115 | [1,1,1], 116 | [1,2,0], 117 | [2,0,1], 118 | [2,1,1]] ) 119 | assert_equal(boundary.toarray(), [[ 1, 0, 0, 0], 120 | [-1, 0, 0, 0], 121 | [-1, 1, 0, 0], 122 | [ 0,-1, 0, 0], 123 | [ 0,-1, 0, 0], 124 | [ 0, 0, 1, 0], 125 | [ 1, 0,-1, 0], 126 | [ 0, 0,-1, 1], 127 | [ 0, 1, 0,-1], 128 | [ 0, 0, 0,-1], 129 | [ 0, 0, 1, 0], 130 | [ 0, 0, 0, 1]] ) 131 | 132 | def test_boundary2_2D_with_holes(self): 133 | """boundary_2 of 2D mesh [[X0X]]""" 134 | cubes = array([[0,0,0,1], 135 | [2,0,0,1]]) 136 | dim = 2 137 | 138 | faces,boundary = cube_array_boundary(cubes, dim) 139 | 140 | assert_equal(faces, [[0,0,0], 141 | [0,0,1], 142 | [0,1,0], 143 | [1,0,1], 144 | [2,0,0], 145 | [2,0,1], 146 | [2,1,0], 147 | [3,0,1]]) 148 | assert_equal(boundary.toarray(), [[ 1, 0], 149 | [-1, 0], 150 | [-1, 0], 151 | [ 1, 0], 152 | [ 0, 1], 153 | [ 0,-1], 154 | [ 0,-1], 155 | [ 0, 1]] ) 156 | 157 | 158 | 159 | def test_boundary2_3D(self): 160 | """boundary_2 of a 3D cube""" 161 | cubes = array([[0,0,0,0,1,2]]) 162 | dim = 3 163 | 164 | faces,boundary = cube_array_boundary(cubes, dim) 165 | 166 | assert_equal(faces, [[0,0,0,0,1], 167 | [0,0,0,0,2], 168 | [0,0,0,1,2], 169 | [0,0,1,0,1], 170 | [0,1,0,0,2], 171 | [1,0,0,1,2]] ) 172 | assert_equal(boundary.toarray(), [[-1], 173 | [ 1], 174 | [-1], 175 | [ 1], 176 | [-1], 177 | [ 1]] ) 178 | 179 | 180 | -------------------------------------------------------------------------------- /pydec/pydec/dec/tests/test_meshes.py: -------------------------------------------------------------------------------- 1 | from pydec.testing import * 2 | 3 | import os 4 | 5 | from scipy import random, arange, alltrue, array, rand, allclose, \ 6 | concatenate, zeros, shape, sparse, matrix, sqrt 7 | 8 | from pydec.dec import simplicial_complex 9 | from pydec.mesh import simplicial_mesh,simplex 10 | from pydec.io import read_mesh 11 | 12 | 13 | 14 | base_dir = os.path.split(__file__)[0] 15 | mesh_dir = os.path.join( base_dir, 'meshes') 16 | 17 | mesh_filenames = ['cube.xml','sphere3.xml','torus_tet.xml'] 18 | 19 | meshes = {} 20 | for name in mesh_filenames: 21 | meshes[name] = read_mesh(os.path.join(mesh_dir,name)) 22 | 23 | 24 | 25 | def test_exactness(): 26 | for mesh in meshes.itervalues(): 27 | sc = simplicial_complex(mesh) 28 | 29 | cmplx = sc.chain_complex() 30 | 31 | for B1,B2 in zip(cmplx[:-1],cmplx[1:]): 32 | assert_equal((B1*B2).nnz,0) 33 | 34 | def test_faces(): 35 | """check whether faces are correct""" 36 | for mesh in meshes.itervalues(): 37 | sc = simplicial_complex(mesh) 38 | 39 | for face_data,cell_data in zip(sc[:-1],sc[1:]): 40 | #compute all faces and store in a set 41 | 42 | boundary_set = set() 43 | for row in cell_data.simplices: 44 | boundary_set.update(simplex(row).boundary()) 45 | 46 | face_set = set([simplex(row) for row in face_data.simplices]) 47 | 48 | assert_equal(boundary_set, face_set) 49 | 50 | 51 | def test_boundary(): 52 | """check boundary operators (assumes correct faces)""" 53 | 54 | for mesh in meshes.itervalues(): 55 | sc = simplicial_complex(mesh) 56 | assert_equal(sc[0].boundary.shape,(1,sc[0].simplices.shape[0])) 57 | assert_equal(sc[0].boundary.nnz,0) 58 | 59 | for face_data,cell_data in zip(sc[:-1],sc[1:]): 60 | B = cell_data.boundary 61 | 62 | assert_equal(B.shape,(face_data.num_simplices,cell_data.num_simplices)) 63 | assert_equal(B.nnz,cell_data.num_simplices*(cell_data.dim+1)) 64 | 65 | for row,parity in zip(cell_data.simplices,cell_data.simplex_parity): 66 | s = simplex(row) 67 | 68 | s_index = cell_data.simplex_to_index[s] 69 | 70 | for f in s.boundary(): 71 | f_index = face_data.simplex_to_index[f] 72 | 73 | assert_equal(B[f_index,s_index],(-1)**(f.parity ^ int(parity))) 74 | 75 | -------------------------------------------------------------------------------- /pydec/pydec/dec/tests/test_rips_complex.py: -------------------------------------------------------------------------------- 1 | from pydec.testing import * 2 | 3 | import numpy 4 | from numpy import array, matrix 5 | from scipy import rand 6 | from scipy.linalg import norm 7 | 8 | from pydec.dec.rips_complex import rips_complex, rips_simplices, \ 9 | rips_chain_complex 10 | 11 | def ensure_complex_exactness(cmplx): 12 | for d1,d2 in zip(cmplx.cochain_complex()[:-1], cmplx.cochain_complex()[1:]): 13 | assert_equal( (d2*d1).nnz, 0 ) 14 | 15 | for b1,b2 in zip(cmplx.chain_complex()[:-1], cmplx.chain_complex()[1:]): 16 | assert_equal( (b1*b2).nnz, 0 ) 17 | 18 | for d,b in zip(cmplx.cochain_complex()[:-1], cmplx.chain_complex()[1:]): 19 | assert_equal( (d.T - b).nnz, 0 ) 20 | 21 | assert_equal( len(cmplx.chain_complex()), len(cmplx.cochain_complex()) ) 22 | 23 | 24 | class TestRipsSimplices(TestCase): 25 | def setUp(self): 26 | numpy.random.seed(0) 27 | 28 | def test_simple1(self): 29 | """example with 1 triangle""" 30 | edges = array([[0,1],[0,2],[1,2]]) 31 | 32 | expected = [ array([[0],[1],[2]]), 33 | array([[0,1],[0,2],[1,2]]), 34 | array([[0,1,2]]) ] 35 | 36 | result = rips_simplices(3, edges, 2) 37 | 38 | for r,e in zip(result,expected): 39 | assert_equal(r,e) 40 | 41 | def test_simple2(self): 42 | """example with 1 tet and 1 triangle""" 43 | edges = array([[0,1],[0,2],[0,3],[0,4],[0,5],[1,2],[1,5],[2,5],[3,4]]) 44 | 45 | expected = [ array([[0],[1],[2],[3],[4],[5]]), 46 | array([[0,1],[0,2],[0,3], 47 | [0,4],[0,5],[1,2], 48 | [1,5],[2,5],[3,4]]), 49 | array([[0,1,2],[0,1,5],[0,2,5],[0,3,4],[1,2,5]]), 50 | array([[0,1,2,5]]) ] 51 | 52 | result = rips_simplices(6, edges, 3) 53 | 54 | for r,e in zip(result,expected): 55 | assert_equal(r,e) 56 | 57 | def test_random_2d(self): 58 | N = 200 59 | R = 0.2 60 | 61 | pts = rand(N,2) 62 | rc = rips_complex( pts, R ) 63 | 64 | edges = set( [tuple(e) for e in rc.simplices[1]] ) 65 | 66 | for i in range(N): 67 | for j in range(i+1,N): 68 | if norm(pts[i] - pts[j]) < R: 69 | assert( (i,j) in edges ) 70 | else: 71 | assert( (i,j) not in edges ) 72 | 73 | for t in rc.simplices[2]: 74 | for i,j in [(0,1),(0,2),(1,2)]: 75 | assert( (t[i],t[j]) in edges ) 76 | 77 | ensure_complex_exactness(rc) 78 | 79 | def test_random_3d(self): 80 | N = 200 81 | R = 0.25 82 | 83 | pts = rand(N,3) 84 | rc = rips_complex( pts, R ) 85 | 86 | edges = set( [tuple(e) for e in rc.simplices[1]] ) 87 | 88 | for i in range(N): 89 | for j in range(i+1,N): 90 | if norm(pts[i] - pts[j]) < R: 91 | assert( (i,j) in edges ) 92 | else: 93 | assert( (i,j) not in edges ) 94 | 95 | for t in rc.simplices[2]: 96 | for i,j in [(0,1),(0,2),(1,2)]: 97 | assert( (t[i],t[j]) in edges ) 98 | 99 | for t in rc.simplices[3]: 100 | for i,j in [(0,1),(0,2),(0,3),(1,2),(1,3),(2,3)]: 101 | assert( (t[i],t[j]) in edges ) 102 | 103 | ensure_complex_exactness(rc) 104 | 105 | 106 | class TestRipsComplex(TestCase): 107 | def test_simple1(self): 108 | """example with 1 edge and 1 point""" 109 | 110 | simplices = [ array([[0],[1],[2]]), 111 | array([[0,2]]) ] 112 | 113 | expected = [ matrix([[0,0,0]]), 114 | matrix([[-1],[ 0],[ 1]]) ] 115 | 116 | result = rips_chain_complex(simplices) 117 | 118 | for r,e in zip(result,expected): 119 | assert_equal(r.todense(),e) 120 | 121 | def test_simple2(self): 122 | """example with 1 triangle""" 123 | 124 | simplices = [ array([[0],[1],[2]]), 125 | array([[0,1],[0,2],[1,2]]), 126 | array([[0,1,2]]) ] 127 | 128 | expected = [ matrix([[0,0,0]]), 129 | matrix([[-1,-1, 0], 130 | [ 1, 0,-1], 131 | [ 0, 1, 1]]), 132 | matrix([[ 1],[-1],[ 1]]) ] 133 | 134 | result = rips_chain_complex(simplices) 135 | 136 | for r,e in zip(result,expected): 137 | assert_equal(r.todense(),e) 138 | 139 | def test_simple3(self): 140 | """example with 2 triangles and 1 edge""" 141 | 142 | simplices = [ array([[0],[1],[2],[3],[4]]), 143 | array([[0, 1], 144 | [0, 2], 145 | [0, 3], 146 | [1, 2], 147 | [2, 3], 148 | [2, 4]]), 149 | array([[0, 1, 2], 150 | [0, 2, 3]]) ] 151 | 152 | expected = [ array([[ 0, 0, 0, 0, 0]]), 153 | array([[-1,-1,-1, 0, 0, 0], 154 | [ 1, 0, 0,-1, 0, 0], 155 | [ 0, 1, 0, 1,-1,-1], 156 | [ 0, 0, 1, 0, 1, 0], 157 | [ 0, 0, 0, 0, 0, 1]]), 158 | array([[ 1, 0], 159 | [-1, 1], 160 | [ 0,-1], 161 | [ 1, 0], 162 | [ 0, 1], 163 | [ 0, 0]]) ] 164 | 165 | result = rips_chain_complex( simplices ) 166 | 167 | for r,e in zip(result,expected): 168 | assert_equal(r.todense(),e) 169 | 170 | 171 | 172 | def test_simple4(self): 173 | """example with 1 triangle and 1 edge""" 174 | 175 | simplices = [ array([[0],[1],[2],[3]]), 176 | array([[0,1],[0,2],[0,3],[1,2]]), 177 | array([[0,1,2]]) ] 178 | 179 | expected = [ matrix([[ 0, 0, 0, 0]]), 180 | matrix([[-1,-1,-1, 0], 181 | [ 1, 0, 0,-1], 182 | [ 0, 1, 0, 1], 183 | [ 0, 0, 1, 0]]), 184 | matrix([[ 1],[-1],[ 0],[ 1]]) ] 185 | 186 | result = rips_chain_complex( simplices ) 187 | 188 | for r,e in zip(result,expected): 189 | assert_equal(r.todense(),e) 190 | 191 | -------------------------------------------------------------------------------- /pydec/pydec/dec/tests/test_simplex_array.py: -------------------------------------------------------------------------------- 1 | from pydec.testing import * 2 | 3 | from scipy import random,arange,alltrue,array 4 | 5 | from pydec.dec.simplex_array import simplex_array_boundary, \ 6 | simplex_array_parity, simplex_array_searchsorted 7 | from pydec.math.parity import relative_parity 8 | 9 | 10 | 11 | class TestSearchsorted(TestCase): 12 | def setUp(self): 13 | random.seed(0) 14 | 15 | 16 | def test_simple1(self): 17 | s = array([[0],[1],[4],[6],[7],[10]]) 18 | v = array([[6],[10],[0]]) 19 | 20 | result = simplex_array_searchsorted(s,v) 21 | expected = array([3,5,0]) 22 | 23 | assert_equal(result,expected) 24 | 25 | 26 | def test_simple2(self): 27 | s = array([[0,1],[0,2],[1,2],[1,3],[1,4],[3,4]]) 28 | v = array([[1,2],[0,2],[3,4]]) 29 | 30 | result = simplex_array_searchsorted(s,v) 31 | expected = array([2,1,5]) 32 | 33 | assert_equal(result,expected) 34 | 35 | 36 | def test_random(self): 37 | for n_row in [1,2,3,10,100,200]: 38 | for n_col in [1,2,3,4,5]: 39 | s = arange(n_row*n_col).reshape((n_row,n_col)) 40 | 41 | for n_searches in [1,2,3,n_row,2*n_row]: 42 | expected = random.randint(0,n_row,n_searches) 43 | 44 | v = s[expected,:] 45 | 46 | result = simplex_array_searchsorted(s,v) 47 | 48 | assert_equal(result,expected) 49 | 50 | 51 | 52 | 53 | 54 | class test_simplex_array_parity(TestCase): 55 | def setUp(self): 56 | random.seed(0) 57 | 58 | def test_simple(self): 59 | cases = [] 60 | cases.append(([[0]],[0])) 61 | cases.append(([[1]],[0])) 62 | cases.append(([[0],[1]],[0,0])) 63 | cases.append(([[0,1]],[0])) 64 | cases.append(([[1,0]],[1])) 65 | cases.append(([[0,1],[1,0]],[0,1])) 66 | cases.append(([[53,2]],[1])) 67 | cases.append(([[117,235]],[0])) 68 | cases.append(([[0,1,4]],[0])) 69 | cases.append(([[0,4,1]],[1])) 70 | cases.append(([[4,0,1]],[0])) 71 | cases.append(([[0,1,4],[0,4,1],[4,0,1]],[0,1,0])) 72 | 73 | for s,p in cases: 74 | assert_equal(simplex_array_parity(array(s)),array(p)) 75 | 76 | 77 | def test_parity(self): 78 | """ 79 | test parity with random data 80 | """ 81 | for n_col in range(1,7): 82 | for n_row in range(1,50): 83 | A = arange(n_col*n_row) 84 | random.shuffle(A) 85 | A = A.reshape((n_row,n_col)) 86 | 87 | s_parity = simplex_array_parity(A) 88 | 89 | for n,row in enumerate(A): 90 | assert_equal(s_parity[n],relative_parity(row,sorted(row))) 91 | 92 | 93 | class TestBoundary(TestCase): 94 | 95 | def test_simple(self): 96 | cases = [] 97 | cases.append(([[0,1]],[0],[[0],[1]],[[-1],[1]])) 98 | cases.append(([[0,1]],[1],[[0],[1]],[[1],[-1]])) 99 | cases.append(([[2,9]],[0],[[2],[9]],[[-1],[1]])) 100 | cases.append(([[2,9]],[1],[[2],[9]],[[1],[-1]])) 101 | cases.append(([[0,1,2]],[0],[[0,1],[0,2],[1,2]],[[1],[-1],[1]])) 102 | cases.append(([[0,1,2]],[1],[[0,1],[0,2],[1,2]],[[-1],[1],[-1]])) 103 | 104 | 105 | for simplex_array,parity,faces,boundary in cases: 106 | 107 | F,B = simplex_array_boundary(array(simplex_array),array(parity)) 108 | 109 | assert_equal(F,array(faces)) 110 | assert_equal(B.todense(),array(boundary)) 111 | 112 | -------------------------------------------------------------------------------- /pydec/pydec/fem/__init__.py: -------------------------------------------------------------------------------- 1 | "Finite Element matrix creation" 2 | 3 | from .info import __doc__ 4 | 5 | from .innerproduct import * 6 | 7 | __all__ = list(filter(lambda s:not s.startswith('_'),dir())) 8 | 9 | -------------------------------------------------------------------------------- /pydec/pydec/fem/info.py: -------------------------------------------------------------------------------- 1 | """ 2 | STUB 3 | ============= 4 | 5 | """ 6 | 7 | postpone_import = 1 8 | -------------------------------------------------------------------------------- /pydec/pydec/fem/innerproduct.py: -------------------------------------------------------------------------------- 1 | 2 | __all__ = ['barycentric_gradients','whitney_innerproduct','regular_cube_innerproduct'] 3 | 4 | from numpy import matrix,zeros,ones,eye,allclose, \ 5 | isreal,real,dot,concatenate,sqrt, \ 6 | arange,array,inner,vstack,atleast_2d,empty,tile, \ 7 | asarray,all,sum,hstack 8 | from scipy import sparse 9 | 10 | from scipy.special import factorial, comb 11 | from scipy.linalg import det,inv 12 | from scipy.sparse import coo_matrix 13 | 14 | from pydec.mesh import simplex 15 | from pydec.math.combinatorial import combinations 16 | from pydec.dec.simplex_array import simplex_array_searchsorted 17 | from pydec.dec.cube_array import cube_array_search,cube_array_boundary 18 | 19 | import scipy,numpy 20 | 21 | 22 | def barycentric_gradients(pts): 23 | """ 24 | Compute the gradients of the barycentric basis functions over a given simplex 25 | """ 26 | V = asarray(pts[1:] - pts[0]) 27 | 28 | ##all gradients except the first are computed 29 | grads = dot(inv(inner(V,V)),V) #safer, but slower: grads = scipy.linalg.pinv2(V).T 30 | 31 | ##since sum of all gradients is zero, simply compute the first from the others 32 | return vstack((atleast_2d(-numpy.sum(grads,axis=0)),grads)) 33 | 34 | 35 | def massmatrix_rowcols(complex,k): 36 | """ 37 | Compute the row and column arrays in the COO 38 | format of the Whitney form mass matrix 39 | """ 40 | simplices = complex[-1].simplices 41 | num_simplices = simplices.shape[0] 42 | p = complex.complex_dimension() 43 | 44 | if k == p: 45 | #top dimension 46 | rows = arange(num_simplices,dtype=simplices.dtype) 47 | cols = arange(num_simplices,dtype=simplices.dtype) 48 | return rows,cols 49 | 50 | k_faces = [tuple(x) for x in combinations(range(p+1),k+1)] 51 | 52 | faces_per_simplex = len(k_faces) 53 | num_faces = num_simplices*faces_per_simplex 54 | faces = empty((num_faces,k+1),dtype=simplices.dtype) 55 | 56 | for n,face in enumerate(k_faces): 57 | for m,i in enumerate(face): 58 | faces[n::faces_per_simplex,m] = simplices[:,i] 59 | 60 | #faces.sort() #we can't assume that the p-simplices are sorted 61 | 62 | indices = simplex_array_searchsorted(complex[k].simplices,faces) 63 | 64 | rows = tile(indices.reshape((-1,1)),(faces_per_simplex,)).flatten() 65 | cols = tile(indices.reshape((-1,faces_per_simplex)),(faces_per_simplex,)).flatten() 66 | 67 | return rows,cols 68 | 69 | 70 | 71 | def whitney_innerproduct(complex,k): 72 | """ 73 | For a given SimplicialComplex, compute a matrix representing the 74 | innerproduct of Whitney k-forms 75 | """ 76 | assert(k >= 0 and k <= complex.complex_dimension()) 77 | 78 | ## MASS MATRIX COO DATA 79 | rows,cols = massmatrix_rowcols(complex,k) 80 | data = empty(rows.shape) 81 | 82 | 83 | ## PRECOMPUTATION 84 | p = complex.complex_dimension() 85 | 86 | scale_integration = (factorial(k)**2)/((p + 2)*(p + 1)) 87 | 88 | k_forms = [tuple(x) for x in combinations(range(p+1),k)] 89 | k_faces = [tuple(x) for x in combinations(range(p+1),k+1)] 90 | 91 | num_k_forms = len(k_forms) 92 | num_k_faces = len(k_faces) 93 | 94 | k_form_pairs = [tuple(x) for x in combinations(k_forms,2)] + [(x,x) for x in k_forms] 95 | num_k_form_pairs = len(k_form_pairs) 96 | k_form_pairs_to_index = dict(zip(k_form_pairs,range(num_k_form_pairs))) 97 | k_form_pairs_to_index.update(zip([x[::-1] for x in k_form_pairs],range(num_k_form_pairs))) 98 | num_k_face_pairs = num_k_faces**2 99 | 100 | 101 | if k > 0: 102 | k_form_pairs_array = array(k_form_pairs) 103 | 104 | #maps flat vector of determinants to the flattened matrix entries 105 | dets_to_vals = scipy.sparse.lil_matrix((num_k_face_pairs,num_k_form_pairs)) 106 | 107 | k_face_pairs = [] 108 | for face1 in k_faces: 109 | for face2 in k_faces: 110 | row_index = len(k_face_pairs) 111 | 112 | k_face_pairs.append((face1,face2)) 113 | 114 | for n in range(k+1): 115 | for m in range(k+1): 116 | form1 = face1[:n] + face1[n+1:] 117 | form2 = face2[:m] + face2[m+1:] 118 | 119 | col_index = k_form_pairs_to_index[(form1,form2)] 120 | 121 | dets_to_vals[row_index,col_index] += (-1)**(n+m)*((face1[n] == face2[m]) + 1) 122 | 123 | k_face_pairs_to_index = dict(zip(k_face_pairs,range(num_k_faces**2))) 124 | dets_to_vals = dets_to_vals.tocsr() 125 | ## END PRECOMPUTATION 126 | 127 | 128 | ## COMPUTATION 129 | if k == 1: 130 | Fdet = lambda x : x #det for 1x1 matrices - extend 131 | else: 132 | # scipy.linalg.flinalg was removed in SciPy 1.13.1 133 | #Fdet, = scipy.linalg.flinalg.get_flinalg_funcs(('det',),(complex.vertices,)) 134 | Fdet = scipy.linalg.det 135 | 136 | 137 | 138 | dets = ones(num_k_form_pairs) 139 | 140 | 141 | for i,s in enumerate(complex[-1].simplices): 142 | 143 | # for k=1, dets is already correct i.e. dets[:] = 1 144 | if k > 0: 145 | # lambda_i denotes the scalar barycentric basis function of the i-th vertex of this simplex 146 | # d(lambda_i) is the 1 form (gradient) of the i-th scalar basis function within this simplex 147 | pts = complex.vertices[s,:] 148 | d_lambda = barycentric_gradients(pts) 149 | 150 | mtxs = d_lambda[k_form_pairs_array] # these lines are equivalent to: 151 | for n,(A,B) in enumerate(mtxs): # for n,(form1,form2) in enumerate(k_form_pairs): 152 | #dets[n] = Fdet(inner(A,B))[0] # dets[n] = det(dot(d_lambda[form1,:],d_lambda[form2,:].T)) 153 | dets[n] = Fdet(inner(A,B)) # dets[n] = det(dot(d_lambda[form1,:],d_lambda[form2,:].T)) 154 | 155 | volume = complex[-1].primal_volume[i] 156 | vals = dets_to_vals * dets 157 | vals *= volume * scale_integration #scale by the volume, barycentric weights, and account for the p! in each whitney form 158 | 159 | #put values into appropriate entries of the COO data array 160 | data[i*num_k_face_pairs:(i+1)*num_k_face_pairs] = vals 161 | 162 | 163 | #now rows,cols,data form a COOrdinate representation for the mass matrix 164 | shape = (complex[k].num_simplices,complex[k].num_simplices) 165 | return coo_matrix((data,(rows,cols)), shape).tocsr() 166 | 167 | 168 | 169 | 170 | def regular_cube_innerproduct(rcc,k): 171 | """ 172 | For a given regular_cube_complex, compute a matrix 173 | representing the k-form innerproduct. 174 | 175 | These elements are similar to Whitney forms, 176 | except using standard linear (bilinear,trilinear,..) 177 | elements for 0-forms. 178 | """ 179 | 180 | N = rcc.complex_dimension() 181 | 182 | #standard cube is [0,0,..,0] [0,1,...,N] 183 | standard_cube = atleast_2d(array([0]*N + range(N),dtype='i')) 184 | standard_k_faces = standard_cube 185 | for i in range(N,k,-1): 186 | standard_k_faces = cube_array_boundary(standard_k_faces,i)[0] 187 | 188 | 189 | k_faces_per_cube = standard_k_faces.shape[0] 190 | 191 | 192 | K = zeros((k_faces_per_cube,k_faces_per_cube)) #local stiffness matrix 193 | h = 1 194 | V = h**N #cube volume 195 | scale = V * (1/h)**2 * (1/3.0)**(N-k) 196 | for i,row_i in enumerate(standard_k_faces): 197 | for j,row_j in enumerate(standard_k_faces): 198 | if all(row_i[N:] == row_j[N:]): 199 | differences = (row_i[:N] != row_j[:N]) 200 | differences[row_i[N:]] = 0 201 | K[i,j] = scale * (1.0/2.0)**sum(differences) 202 | else: 203 | K[i,j] = 0 204 | 205 | 206 | CA = rcc[-1].cube_array[:,:N] 207 | num_cubes = CA.shape[0] 208 | 209 | k_faces = tile(hstack((CA,zeros((CA.shape[0],k),dtype=CA.dtype))),(1,k_faces_per_cube)).reshape((-1,N+k)) 210 | k_faces += tile(standard_k_faces,(num_cubes,1)) 211 | 212 | k_face_array = rcc[k].cube_array 213 | 214 | face_indices = cube_array_search(k_face_array,k_faces) 215 | 216 | rows = face_indices.repeat(k_faces_per_cube) 217 | cols = face_indices.reshape((-1,k_faces_per_cube)).repeat(k_faces_per_cube,axis=0).reshape((-1,)) 218 | data = K.reshape((1,-1)).repeat(num_cubes,axis=0).reshape((-1,)) 219 | 220 | # temporary memory cost solution - eliminate zeros from COO representation 221 | nz_mask = data != 0.0 222 | rows = rows[nz_mask] 223 | cols = cols[nz_mask] 224 | data = data[nz_mask] 225 | 226 | shape = (len(k_face_array),len(k_face_array)) 227 | return coo_matrix( (data,(rows,cols)), shape).tocsr() 228 | 229 | 230 | 231 | -------------------------------------------------------------------------------- /pydec/pydec/io/__init__.py: -------------------------------------------------------------------------------- 1 | "PyDEC mesh and array IO" 2 | 3 | from .info import __doc__ 4 | 5 | from .meshio import * 6 | from .arrayio import * 7 | 8 | __all__ = list(filter(lambda s:not s.startswith('_'),dir())) 9 | 10 | -------------------------------------------------------------------------------- /pydec/pydec/io/arrayio.py: -------------------------------------------------------------------------------- 1 | __all__ = ['read_array','write_array','read_header'] 2 | 3 | #from scipy.io import read_array,write_array 4 | from numpy import shape,ndim 5 | import numpy 6 | import scipy 7 | import sys 8 | 9 | 10 | class ArrayIOException(Exception): 11 | def __init__(self,msg=''): self.msg = msg 12 | def __str__(self): return self.msg 13 | 14 | class FileFormatError(ArrayIOException): pass 15 | 16 | 17 | class ArrayHeader(dict): 18 | def tostring(self): 19 | self['version'] = '1.0' 20 | output = str(len(self)) + '\n' 21 | for key,value in self.items(): 22 | output += key 23 | output += '=' 24 | output += str(value) 25 | output += '\n' 26 | return output 27 | 28 | 29 | 30 | #---user functions---#000000#FFFFFF------------------------------------------------------- 31 | def read_array(fid): 32 | """ 33 | Read an ndarray or sparse matrix from file. 34 | 35 | ASCII formats 36 | basic 37 | ndarray 38 | sparse 39 | BINARY formats 40 | ndarray 41 | sparse 42 | 43 | Notes: 44 | ndarray IO makes use to ndarray.tofile() and fromfile() 45 | 46 | The following sparse matrix formats are supported: 47 | csr_matrix 48 | csc_matrix 49 | coo_matrix 50 | """ 51 | 52 | if not hasattr(fid, "read"): fid = open(fid, "rb") 53 | 54 | header = read_header(fid) 55 | 56 | try: format = header['format'] 57 | except: raise FileFormatError('File format unspecified in file') 58 | if format not in ['', 'basic', 'ascii', 'binary']: 59 | raise FileFormatError('Unknown format: [' + format + ']') 60 | 61 | if format == 'basic': 62 | return read_basic(fid,header) 63 | else: 64 | try: 65 | array_type = header['type'] 66 | except KeyError: 67 | raise FileFormatError('Array type unspecified in file: ['+fid.name+']') 68 | 69 | if array_type == 'ndarray': 70 | return read_ndarray(fid,header) 71 | elif array_type == 'sparse': 72 | return read_sparse(fid, header) 73 | else: 74 | raise FileFormatError('Unknown array type: [' + array_type + ']') 75 | 76 | 77 | 78 | def write_array(fid,A,format='binary'): 79 | """ 80 | Write an ndarray or sparse matrix to a file 81 | 82 | format may be one of ['basic','ascii','binary'] 83 | 84 | basic 85 | - Most human readable 86 | - Only works for arrays of rank 1 and 2 87 | - Does not work for sparse matrices 88 | ascii 89 | - Somewhat human readable 90 | - Works for ndarrays and sparse matrices 91 | binary 92 | - Fastest format 93 | - Works for ndarrays and sparse matrices 94 | - Data stored in LittleEndian 95 | """ 96 | 97 | if format not in ['basic', 'ascii', 'binary']: 98 | raise ArrayIOException('Unknown format: ['+format+']') 99 | 100 | if not hasattr(fid, "read"): fid = open(fid,'wb') 101 | 102 | if type(A) is numpy.ndarray: 103 | A = numpy.ascontiguousarray(A) #strided arrays break in write 104 | if format == 'basic': 105 | if ndim(A) > 2: raise ArrayIOException('basic format only works for rank 1 or 2 arrays') 106 | write_basic(fid,A) 107 | else: 108 | write_ndarray(fid,A,format) 109 | elif scipy.sparse.isspmatrix(A): 110 | if format not in ['ascii', 'binary']: 111 | raise ArrayIOException('sparse matrices require ascii or binary format') 112 | write_sparse(fid,A,format) 113 | else: 114 | try: 115 | A = asarray(A) 116 | if format == 'basic': 117 | if ndim(A) > 2: raise ArrayIOException('basic format only works for rank 1 or 2 arrays') 118 | write_basic(fid,A) 119 | else: 120 | write_ndarray(fid,A,format) 121 | except: 122 | raise ArrayIOException('Unknown data type and unable to convert to numpy.ndarray') 123 | 124 | 125 | def read_header(fid): 126 | """ 127 | Read the header of an array file into a dictionary 128 | """ 129 | if not hasattr(fid, "read"): fid = open(fid, 'rb') 130 | 131 | first_line = fid.readline().decode() 132 | try: numlines = int(first_line) 133 | except: 134 | print('firstline error: ' + first_line) 135 | raise ArrayIOException() 136 | 137 | #numlines = int(fid.readline()) 138 | header = ArrayHeader() 139 | for i in range(numlines): 140 | line = fid.readline().decode().rstrip() 141 | parts = line.split('=') 142 | if len(parts) != 2: 143 | raise FileFormatError('File header error: line #' + str(i) + ' [' + line + ']') 144 | header[parts[0]] = parts[1] 145 | return header 146 | 147 | 148 | 149 | #---basic---#000000#FFFFFF------------------------------------------------------ 150 | def basic_header(A): 151 | header = ArrayHeader() 152 | header['dims'] = ','.join(list(map(str, A.shape))) 153 | header['dtype'] = A.dtype.name 154 | return header 155 | 156 | def read_basic(fid,header): 157 | try: dimensions = split_on_comma(header['dims']) 158 | except: raise FileFormatError('Unable to determine dims') 159 | 160 | #try: dtype = numpy.typeDict[header['dtype']] 161 | try: dtype = numpy.sctypeDict[header['dtype']] 162 | except: raise FileFormatError('Unable to determine dtype') 163 | 164 | if len(dimensions) != 2: raise FileFormatError('basic format only supports 2d arrays') 165 | if min(dimensions) < 1: raise FileFormatError('all dimensions must be positive') 166 | 167 | return numpy.fromfile(fid,dtype=dtype,count=numpy.prod(dimensions),sep=' ').reshape(dimensions) 168 | 169 | def write_basic(fid,A): 170 | A = numpy.atleast_2d(A) #force 1d arrays to 2d 171 | header = basic_header(A) 172 | header['format'] = 'basic' 173 | fid.write(header.tostring().encode('utf-8')) 174 | for row in A: 175 | row.tofile(fid,sep=' ',format='%.16g') 176 | fid.write(b'\n') 177 | 178 | 179 | 180 | #---ndarray---#000000#FFFFFF------------------------------------------------- 181 | def ndarray_header(A): 182 | header = ArrayHeader() 183 | header['type'] = 'ndarray' 184 | header['rank'] = ndim(A) 185 | header['dims'] = ','.join(map(str,A.shape)) 186 | header['dtype'] = A.dtype.name 187 | return header 188 | 189 | def read_ndarray(fid,header): 190 | try: rank = int(header['rank']) 191 | except: raise FileFormatError('Unable to determine rank') 192 | 193 | try: dims = split_on_comma(header['dims']) 194 | except: raise FileFormatError('Unable to determine dims') 195 | 196 | #try: dtype = numpy.typeDict[header['dtype']] 197 | try: dtype = numpy.sctypeDict[header['dtype']] 198 | except: raise FileFormatError('Unable to determine dtype') 199 | 200 | try: format = header['format'] 201 | except: raise FileFormatError('Unable to determine format') 202 | 203 | if len(dims) != rank or min(dims) < 0: 204 | raise FileFormatError('Invalid dims') 205 | 206 | if format == 'ascii': sep = ' ' 207 | else: sep = '' 208 | 209 | if format == 'ascii': 210 | return numpy.fromfile(fid,dtype=dtype,count=numpy.prod(dims),sep=' ').reshape(dims) 211 | else: 212 | A = numpy.fromfile(fid,dtype=dtype,count=numpy.prod(dims),sep='').reshape(dims) 213 | if sys.byteorder == 'big': 214 | A = A.byteswap(True) #in-place swap 215 | return A 216 | 217 | def write_ndarray(fid,A,format): 218 | header = ndarray_header(A) 219 | header['format'] = format 220 | fid.write(header.tostring()) 221 | 222 | if format == 'binary': 223 | if sys.byteorder == 'little': 224 | A.tofile(fid) 225 | else: 226 | A.byteswap().tofile(fid) 227 | elif format == 'ascii': 228 | A.tofile(fid,sep=' ',format='%.16g') 229 | if A.size > 0: fid.write('\n') #only introduce newline when something has been written 230 | else: 231 | raise ArrayIOException('Unknown file format: ['+format+']') 232 | 233 | 234 | 235 | #---sparse---#000000#FFFFFF----------------------------------------------------- 236 | supported_sparse_formats = ['csr','csc','coo'] 237 | 238 | def sparse_header(A): 239 | header = ArrayHeader() 240 | header['type'] = 'sparse' 241 | header['sptype'] = A.format 242 | header['dims'] = ','.join(map(str,A.shape)) 243 | return header 244 | 245 | def read_sparse(fid,header): 246 | try: dims = split_on_comma(header['dims']) 247 | except: raise FileFormatError('Unable to determine dims') 248 | 249 | try: format = header['sptype'] 250 | except: raise FileFormatError('Unable to determine sparse format') 251 | 252 | if len(dims) != 2 or min(dims) < 1: raise FileFormatError('Invalid dims') 253 | 254 | if header['sptype'] not in supported_sparse_formats: 255 | raise ArrayIOException('Only ' + str(supported_sparse_formats) + ' are supported') 256 | 257 | if header['sptype'] == 'csr': 258 | data = read_array(fid) 259 | colind = read_array(fid) 260 | indptr = read_array(fid) 261 | return scipy.sparse.csr_matrix((data, colind, indptr), dims) 262 | elif header['sptype'] == 'csc': 263 | data = read_array(fid) 264 | rowind = read_array(fid) 265 | indptr = read_array(fid) 266 | return scipy.sparse.csc_matrix((data, rowind, indptr), dims) 267 | elif header['sptype'] == 'coo': 268 | data = read_array(fid) 269 | row = read_array(fid) 270 | col = read_array(fid) 271 | return scipy.sparse.coo_matrix((data,(row,col)),dims) 272 | 273 | 274 | def write_sparse(fid,A,format): 275 | if A.format not in supported_sparse_formats: 276 | raise ArrayIOException('Only ' + str(supported_sparse_formats) + ' are supported') 277 | 278 | header = sparse_header(A) 279 | header['format'] = format 280 | fid.write(header.tostring()) 281 | 282 | if A.format == 'csr': 283 | write_array(fid, A.data, format) 284 | write_array(fid, A.indices, format) 285 | write_array(fid, A.indptr, format) 286 | elif A.format == 'csc': 287 | write_array(fid, A.data,format) 288 | write_array(fid, A.indices,format) 289 | write_array(fid, A.indptr,format) 290 | elif A.format == 'coo': 291 | write_array(fid, A.data, format) 292 | write_array(fid, A.row, format) 293 | write_array(fid, A.col, format) 294 | else: 295 | assert(false) 296 | 297 | #------Helper functions------------------------------------------------------------------------- 298 | def split_on_comma(to_parse): 299 | return list(map(int, to_parse.split(','))) 300 | 301 | -------------------------------------------------------------------------------- /pydec/pydec/io/complex_io.py: -------------------------------------------------------------------------------- 1 | #__all__ = ['matlab_to_complex','complex_to_matlab','sparse_to_ijv'] 2 | # 3 | #from pydec.dec import SimplicialComplex,d,star,delta 4 | #from pydec.dec.cochain import Cochain 5 | # 6 | ##from scipy import concatenate,random,rand,sparse,zeros,shape,Int,array,matrix,arange,ArrayType 7 | #from scipy import * 8 | #from scipy.io.mio import loadmat,savemat 9 | # 10 | # 11 | #def matlab_to_complex(filename,vertex_array_name = 'v',simplex_array_name='s'): 12 | # """ 13 | # Load a complex from a MAT file 14 | # SciPy only supports MAT v5, so save from Matlab with the -V4 option 15 | # 16 | # save filename var1 var2 -V4 17 | # """ 18 | # 19 | # dict = {} 20 | # loadmat(filename,dict) 21 | # v = dict[vertex_array_name] 22 | # s = dict[simplex_array_name] 23 | # 24 | # s = s.astype(int32) 25 | # s -= 1 26 | # 27 | # for name,arr in dict.iteritems(): 28 | # print name,shape(arr) 29 | # 30 | # 31 | # return SimplicialComplex(v,s) 32 | # 33 | #def complex_to_matlab(filename,complex): 34 | # """ 35 | # Write a complex and all associated operators to a MAT file 36 | # """ 37 | # 38 | # mat_dict = {} 39 | # 40 | # ## Export Operators in IJV format 41 | # for dim in range(complex.complex_dimension + 1): 42 | # primal_f = complex.get_cochain_basis(dim,True) 43 | # dual_f = complex.get_cochain_basis(dim,False) 44 | # 45 | # mat_dict['primal_d'+str(dim)] = sparse_to_ijv(d(primal_f).v) 46 | # mat_dict['dual_d'+str(complex.complex_dimension - dim)] = sparse_to_ijv(d(dual_f).v) 47 | # 48 | # mat_dict['primal_star'+str(dim)] = sparse_to_ijv(star(primal_f).v) 49 | # mat_dict['dual_star'+str(complex.complex_dimension - dim)] = sparse_to_ijv(star(dual_f).v) 50 | # 51 | # 52 | # ##Change 0-based indexing to 1-based 53 | # for ijv in mat_dict.itervalues(): 54 | # ijv[:,[0,1]] += 1 55 | # 56 | # 57 | # for dim in range(1,complex.complex_dimension): 58 | # i_to_s = complex[dim].index_to_simplex 59 | # s_to_i = complex[dim].simplex_to_index 60 | # 61 | # i2s = concatenate([ array([list(i_to_s[x])]) for x in sorted(i_to_s.keys())]) 62 | # i2s += 1 63 | # mat_dict['sigma'+str(dim)] = i2s.astype(float64) 64 | # 65 | # 66 | # ## 0 and N handled as special cases, 67 | # ## 0 because not all verticies are the face of some simplex 68 | # ## N because the topmost simplices may have an orientation that should be preserved 69 | # mat_dict['sigma0'] = array(matrix(arange(1,len(complex.vertices)+1)).transpose().astype(float64)) 70 | # mat_dict['sigma'+str(complex.complex_dimension)] = complex.simplices.astype(float64) + 1 71 | # 72 | # mat_dict['v'] = complex.vertices 73 | # mat_dict['s'] = complex.simplices.astype(float64) + 1 74 | # 75 | # 76 | # 77 | # 78 | # savemat(filename,mat_dict) 79 | # 80 | # 81 | #def sparse_to_ijv(sparse_matrix): 82 | # """ 83 | # Convert a sparse matrix to a ijv representation. 84 | # For a matrix with N non-zeros, a N by 3 matrix will be returned 85 | # 86 | # Row and Column indices start at 0 87 | # 88 | # If the row and column entries do not span the matrix dimensions, an additional 89 | # zero entry is added for the lower right corner of the matrix 90 | # """ 91 | # csr_matrix = sparse_matrix.tocsr() 92 | # ijv = zeros((csr_matrix.size,3)) 93 | # 94 | # max_row = -1 95 | # max_col = -1 96 | # for ii in xrange(csr_matrix.size): 97 | # ir, ic = csr_matrix.rowcol(ii) 98 | # data = csr_matrix.getdata(ii) 99 | # ijv[ii] = (ir,ic,data) 100 | # max_row = max(max_row,ir) 101 | # max_col = max(max_col,ic) 102 | # 103 | # 104 | # rows,cols = shape(csr_matrix) 105 | # if max_row != (rows - 1) or max_col != (cols - 1): 106 | # ijv = concatenate((ijv,array([[rows-1,cols-1,0]]))) 107 | # 108 | # return ijv 109 | # 110 | # 111 | # 112 | # 113 | #import unittest 114 | # 115 | #class Test_sparse_to_ijv(unittest.TestCase): 116 | # def setUp(self): 117 | # random.seed(0) #make tests repeatable 118 | # 119 | # def testsparse_to_ijv(self): 120 | # cases = [] 121 | # cases.append(((1,1),[(0,0)])) 122 | # cases.append(((1,3),[(0,0),(0,2)])) 123 | # cases.append(((7,1),[(5,0),(2,0),(4,0),(6,0)])) 124 | # cases.append(((5,5),[(0,0),(1,3),(0,4),(0,3),(3,2),(2,0),(4,3)])) 125 | # 126 | # for dim,l in cases: 127 | # s = sparse.lil_matrix(dim) 128 | # for r,c in l: 129 | # s[r,c] = 1 130 | # ijv = sparse_to_ijv(s) 131 | # 132 | # self.assertEqual(shape(ijv),(len(l),3)) 133 | # 134 | # for i,j,v in ijv: 135 | # self.assert_((i,j) in l) 136 | # self.assertEqual(v,1) 137 | # 138 | # 139 | #class TestFile(unittest.TestCase): 140 | # def setUp(self): 141 | # pass 142 | # 143 | # def testMatlab(self): 144 | # sc = matlab_to_complex("../resources/matlab/meshes/unitSqr14") 145 | # complex_to_matlab("/home/nathan/Desktop/unitSqr14_out",sc) 146 | # 147 | # 148 | # 149 | #if __name__ == '__main__': 150 | # unittest.main() 151 | -------------------------------------------------------------------------------- /pydec/pydec/io/info.py: -------------------------------------------------------------------------------- 1 | """ 2 | STUB 3 | ============= 4 | 5 | """ 6 | 7 | postpone_import = 1 8 | -------------------------------------------------------------------------------- /pydec/pydec/io/meshio.py: -------------------------------------------------------------------------------- 1 | __all__ = ['read_mesh','write_mesh'] 2 | 3 | from pydec.mesh import simplicial_mesh 4 | import pydec.io.arrayio 5 | #from xml.dom.ext import PrettyPrint 6 | from xml.dom import minidom 7 | from io import IOBase 8 | 9 | import os 10 | 11 | class PyMeshException(Exception): pass 12 | class PyMeshIOException(PyMeshException): pass 13 | 14 | 15 | 16 | mesh_str_type_pairs = [('simplicial_mesh',simplicial_mesh)] 17 | mesh_str_to_type = dict(mesh_str_type_pairs) 18 | mesh_type_to_str = dict([(t,s) for (s,t) in mesh_str_type_pairs]) 19 | 20 | 21 | 22 | def read_arrays(node_list,filepath): 23 | array_dict = dict() 24 | 25 | for node in node_list: 26 | file_node = node.getElementsByTagName('file')[0] 27 | file_name = str(file_node.attributes['name'].value) 28 | file_name = os.path.join(filepath,file_name) 29 | 30 | data = pydec.io.arrayio.read_array(file_name) 31 | 32 | array_dict[str(node.nodeName)] = data 33 | 34 | return array_dict 35 | 36 | 37 | 38 | def read_mesh(fid): 39 | """ 40 | Read a mesh from a given open file or filename. 41 | 42 | Examples: 43 | my_mesh = read_mesh('torus.xml') 44 | or 45 | fid = open('torus.xml') 46 | my_mesh = read_mesh(fid) 47 | 48 | """ 49 | if not hasattr(fid, "read"): fid = open(fid) 50 | 51 | xmldoc = minidom.parse(fid) 52 | 53 | mesh_node = xmldoc.firstChild 54 | 55 | if mesh_node.tagName != 'mesh': 56 | raise PyMeshIOException('Invalid XML root node') 57 | 58 | 59 | (filepath, filename) = os.path.split(fid.name) 60 | 61 | children = [child for child in xmldoc.firstChild.childNodes if child.nodeType == child.ELEMENT_NODE] 62 | 63 | array_dict = read_arrays(children, filepath) 64 | 65 | if mesh_node.hasAttribute('type'): 66 | mesh_str = str(mesh_node.attributes['type'].value) 67 | else: 68 | mesh_str = 'simplicial_mesh' 69 | 70 | mesh_type = mesh_str_to_type[mesh_str] 71 | 72 | return mesh_type(array_dict) 73 | 74 | 75 | def write_mesh(fid, mesh, format='binary'): 76 | """ 77 | Write a mesh to a given file or filename. 78 | 79 | 80 | Examples: 81 | write_mesh('torus.xml',my_mesh) 82 | or 83 | write_mesh('torus.xml',my_mesh,format='ascii') 84 | or 85 | fid = open('torus.xml') 86 | write_mesh(fid,my_mesh,format='basic') 87 | 88 | """ 89 | 90 | if not hasattr(fid, "read"): fid = open(fid, 'w') 91 | 92 | (filepath, filename) = os.path.split(fid.name) 93 | basename = filename.split('.')[0] 94 | 95 | 96 | xmldoc = minidom.Document() 97 | 98 | mesh_node = xmldoc.appendChild(xmldoc.createElement('mesh')) 99 | mesh_node.setAttribute('type', mesh_type_to_str[type(mesh)]) 100 | for key,value in mesh.items(): 101 | data_filename = basename + '.' + key 102 | 103 | data_node = mesh_node.appendChild(xmldoc.createElement(key)) 104 | data_file_node = data_node.appendChild(xmldoc.createElement('file')) 105 | data_file_node.setAttribute('name',data_filename) 106 | 107 | pydec.io.arrayio.write_array(os.path.join(filepath,data_filename),value,format) 108 | 109 | 110 | xmldoc.writexml(fid,indent='',addindent='\t',newl='\n') 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /pydec/pydec/io/misc.py: -------------------------------------------------------------------------------- 1 | __all__ = ['file_extension'] 2 | 3 | def file_extension(filename): 4 | """ 5 | Return the extension of a file (if any) 6 | """ 7 | parts = filename.split(".") 8 | if len(parts) >= 2: 9 | return parts[-1] 10 | else: 11 | return "" 12 | 13 | 14 | -------------------------------------------------------------------------------- /pydec/pydec/io/tests/test_arrayio.py: -------------------------------------------------------------------------------- 1 | from pydec.testing import * 2 | 3 | from scipy import arange, prod, reshape, rand, random, allclose, ndim, zeros 4 | from scipy.sparse import csr_matrix, csc_matrix, coo_matrix 5 | 6 | from pydec.io.arrayio import write_array, read_array 7 | 8 | 9 | #TODO replace with tempfile 10 | 11 | filename = '/tmp/pydec_arrayio_testfile.dat' 12 | 13 | class TestArrayIO(): 14 | def setUp(self): 15 | random.seed(0) #make tests repeatable 16 | 17 | def tearDown(self): 18 | import os 19 | os.remove(filename) 20 | 21 | def test_dense(self): 22 | sizes = [(2,2),(3,3),(5,1),(1,5)] 23 | sizes += [(2,2,2),(4,3,2),(1,1,5),(1,5,1),(5,1,1)] 24 | for dims in sizes: 25 | mats = [arange(prod(dims)).reshape(dims),rand(*dims)] 26 | for A in mats: 27 | formats = ['binary','ascii'] 28 | if ndim(A) <= 2: formats.append('basic') #use basic when possible 29 | for format in formats: 30 | write_array(filename,A,format=format) 31 | 32 | B = read_array(filename) 33 | assert_almost_equal(A,B,decimal=12) 34 | 35 | 36 | def test_sparse(self): 37 | sizes = [(2,2),(3,3),(1,10),(10,1),(10,10)] 38 | for dims in sizes: 39 | base_mats = [] 40 | base_mats.append((rand(*dims) < 0.5)*rand(*dims)) #random matrix with 50% nnz 41 | base_mats.append(zeros(dims)) #empty matrix 42 | base_mats.append(arange(prod(dims)).reshape(dims)) 43 | 44 | mats = [] 45 | for base_mat in base_mats: 46 | mats.append(csr_matrix(base_mat)) 47 | mats.append(csc_matrix(base_mat)) 48 | mats.append(coo_matrix(base_mat)) 49 | 50 | 51 | for A in mats: 52 | formats = ['binary','ascii'] 53 | for format in formats: 54 | write_array(filename,A,format=format) 55 | 56 | B = read_array(filename) 57 | assert_almost_equal(A.todense(),B.todense(),decimal=12) 58 | assert_equal(type(A),type(B)) 59 | 60 | -------------------------------------------------------------------------------- /pydec/pydec/io/tests/test_misc.py: -------------------------------------------------------------------------------- 1 | from pydec.testing import * 2 | 3 | from pydec.io.misc import * 4 | 5 | def test_file_extension(): 6 | cases = [] 7 | cases.append(("","")) 8 | cases.append(("a","")) 9 | cases.append(("a.txt","txt")) 10 | cases.append(("helloworld","")) 11 | cases.append(("somefile.exe","exe")) 12 | cases.append(("one.two.three","three")) 13 | cases.append(("one.","")) 14 | cases.append(("one..two..three","three")) 15 | cases.append(("a-b-.2.34,5.3","3")) 16 | 17 | for f,e in cases: 18 | assert_equal(file_extension(f),e) 19 | 20 | -------------------------------------------------------------------------------- /pydec/pydec/math/__init__.py: -------------------------------------------------------------------------------- 1 | "General math/geometry functionality" 2 | 3 | 4 | from .info import __doc__ 5 | 6 | from .combinatorial import * 7 | from .parity import * 8 | from .volume import * 9 | from .circumcenter import * 10 | from .kd_tree import * 11 | 12 | __all__ = list(filter(lambda s:not s.startswith('_'),dir())) 13 | 14 | -------------------------------------------------------------------------------- /pydec/pydec/math/circumcenter.py: -------------------------------------------------------------------------------- 1 | __all__ = ['is_wellcentered', 'circumcenter', 'circumcenter_barycentric'] 2 | 3 | from numpy import bmat, hstack, vstack, dot, sqrt, ones, zeros, sum, \ 4 | asarray 5 | from numpy.linalg import solve,norm 6 | 7 | def is_wellcentered(pts, tol=1e-8): 8 | """Determines whether a set of points defines a well-centered simplex. 9 | """ 10 | barycentric_coordinates = circumcenter_barycentric(pts) 11 | return min(barycentric_coordinates) > tol 12 | 13 | def circumcenter_barycentric(pts): 14 | """Barycentric coordinates of the circumcenter of a set of points. 15 | 16 | Parameters 17 | ---------- 18 | pts : array-like 19 | An N-by-K array of points which define an (N-1)-simplex in K dimensional space. 20 | N and K must satisfy 1 <= N <= K + 1 and K >= 1. 21 | 22 | Returns 23 | ------- 24 | coords : ndarray 25 | Barycentric coordinates of the circumcenter of the simplex defined by pts. 26 | Stored in an array with shape (K,) 27 | 28 | Examples 29 | -------- 30 | >>> from pydec.math.circumcenter import * 31 | >>> circumcenter_barycentric([[0],[4]]) # edge in 1D 32 | array([ 0.5, 0.5]) 33 | >>> circumcenter_barycentric([[0,0],[4,0]]) # edge in 2D 34 | array([ 0.5, 0.5]) 35 | >>> circumcenter_barycentric([[0,0],[4,0],[0,4]]) # triangle in 2D 36 | array([ 0. , 0.5, 0.5]) 37 | 38 | See Also 39 | -------- 40 | circumcenter_barycentric 41 | 42 | References 43 | ---------- 44 | Uses an extension of the method described here: 45 | http://www.ics.uci.edu/~eppstein/junkyard/circumcenter.html 46 | 47 | """ 48 | 49 | pts = asarray(pts) 50 | 51 | rows,cols = pts.shape 52 | 53 | assert(rows <= cols + 1) 54 | 55 | A = bmat( [[ 2*dot(pts,pts.T), ones((rows,1)) ], 56 | [ ones((1,rows)) , zeros((1,1)) ]] ) 57 | 58 | b = hstack((sum(pts * pts, axis=1),ones((1)))) 59 | x = solve(A,b) 60 | bary_coords = x[:-1] 61 | 62 | return bary_coords 63 | 64 | def circumcenter(pts): 65 | """Circumcenter and circumradius of a set of points. 66 | 67 | Parameters 68 | ---------- 69 | pts : array-like 70 | An N-by-K array of points which define an (N-1)-simplex in K dimensional space. 71 | N and K must satisfy 1 <= N <= K + 1 and K >= 1. 72 | 73 | Returns 74 | ------- 75 | center : ndarray 76 | Circumcenter of the simplex defined by pts. Stored in an array with shape (K,) 77 | radius : float 78 | Circumradius of the circumsphere that circumscribes the points defined by pts. 79 | 80 | Examples 81 | -------- 82 | >>> circumcenter([[0],[1]]) # edge in 1D 83 | (array([ 0.5]), 0.5) 84 | >>> circumcenter([[0,0],[1,0]]) # edge in 2D 85 | (array([ 0.5, 0. ]), 0.5) 86 | >>> circumcenter([[0,0],[1,0],[0,1]]) # triangle in 2D 87 | (array([ 0.5, 0.5]), 0.70710678118654757) 88 | 89 | See Also 90 | -------- 91 | circumcenter_barycentric 92 | 93 | References 94 | ---------- 95 | Uses an extension of the method described here: 96 | http://www.ics.uci.edu/~eppstein/junkyard/circumcenter.html 97 | 98 | """ 99 | pts = asarray(pts) 100 | bary_coords = circumcenter_barycentric(pts) 101 | center = dot(bary_coords,pts) 102 | radius = norm(pts[0,:] - center) 103 | return (center,radius) 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /pydec/pydec/math/combinatorial.py: -------------------------------------------------------------------------------- 1 | __all__ = ['combinations', 'permutations'] 2 | 3 | 4 | def combinations(L, n): 5 | """Generate combinations from a sequence of elements. 6 | 7 | Returns a generator object that iterates over all 8 | combinations of n elements from a sequence L. 9 | 10 | Parameters 11 | ---------- 12 | L : list-like 13 | Sequence of elements 14 | n : integer 15 | Number of elements to choose at a time 16 | 17 | Returns 18 | ------- 19 | A generator object 20 | 21 | Examples 22 | -------- 23 | >>> combinations([1, 2, 3, 4], 2) 24 | 25 | >>> list(combinations([1, 2, 3, 4], 2)) 26 | [[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]] 27 | >>> list(combinations([1, 2, 3, 4], 1)) 28 | [[1], [2], [3], [4]] 29 | >>> list(combinations([1, 2, 3, 4], 0)) 30 | [[]] 31 | >>> list(combinations([1, 2, 3, 4], 5)) 32 | [[]] 33 | >>> list(combinations(['a', 'b', 'c'], 2)) 34 | [['a', 'b'], ['a', 'c'], ['b', 'c']] 35 | 36 | Notes 37 | ----- 38 | The elements remain in the same order as in L 39 | 40 | """ 41 | if n==0 or n > len(L): 42 | yield [] 43 | else: 44 | for i in range(len(L)-n+1): 45 | for t in combinations(L[i+1:], n-1): 46 | yield [L[i]] + t 47 | 48 | def permutations(L): 49 | """Generate permutations from a sequence of elements. 50 | 51 | Returns a generator object that iterates over all 52 | permutations of elements in a sequence L. 53 | 54 | Parameters 55 | ---------- 56 | L : list-like 57 | Sequence of elements 58 | 59 | Returns 60 | ------- 61 | A generator object 62 | 63 | Examples 64 | -------- 65 | >>> permutations([1, 2, 3]) 66 | 67 | >>> list(permutations([1, 2, 3])) 68 | [[1, 2, 3], [2, 1, 3], [2, 3, 1], [1, 3, 2], [3, 1, 2], [3, 2, 1]] 69 | >>> list(permutations([1])) 70 | [[1]] 71 | >>> list(permutations([1, 2])) 72 | [[1, 2], [2, 1]] 73 | >>> list(permutations(['a', 'b', 'c'])) 74 | [['a', 'b', 'c'], ['b', 'a', 'c'], ['b', 'c', 'a'], ['a', 'c', 'b'], ['c', 'a', 'b'], ['c', 'b', 'a']] 75 | 76 | """ 77 | if len(L) == 1: 78 | yield [L[0]] 79 | elif len(L) >= 2: 80 | (h, t) = (L[0:1], L[1:]) 81 | for p in permutations(t): 82 | for i in range(len(p)+1): 83 | yield p[:i] + h + p[i:] 84 | 85 | -------------------------------------------------------------------------------- /pydec/pydec/math/info.py: -------------------------------------------------------------------------------- 1 | """ 2 | STUB 3 | ============= 4 | 5 | """ 6 | 7 | postpone_import = 1 8 | -------------------------------------------------------------------------------- /pydec/pydec/math/kd_tree.py: -------------------------------------------------------------------------------- 1 | __all__ = ['kd_tree'] 2 | 3 | from math import sqrt 4 | from heapq import heappush,heappop 5 | 6 | class kd_tree: 7 | class node: 8 | def point_distance(self,point): 9 | return sqrt(sum([ (a - b)**2 for (a,b) in zip(point,self.point)])) 10 | 11 | def separator_distance(self,point): 12 | return point[self.axis] - self.point[self.axis] 13 | 14 | def __repr__(self): 15 | output = "" 16 | return "kd_tree< %s points in %s-dimensions >"% (self.num_points,self.k) 17 | 18 | def __init__(self, points, values=None): 19 | """kD-Tree spatial data structure 20 | 21 | Parameters 22 | ---------- 23 | points : array-like 24 | An N-by-K array of N point coordinates in K dimensions 25 | 26 | Optional Parameters 27 | ------------------- 28 | values : array-like 29 | A sequence of N elements associated with the points. 30 | By default, the integers [0,1,...N-1] are used. 31 | 32 | Examples 33 | -------- 34 | >>> points = [[0,0],[1,0],[0,1],[1,1]] 35 | >>> values = ['A','B','C','D'] 36 | >>> kd = kd_tree(points, values) 37 | >>> kd 38 | kd_tree< 4 points in 2-dimensions > 39 | >>> kd.nearest([2,0]) 40 | 'B' 41 | >>> kd.nearest_n([2,0],2) 42 | ['B', 'D'] 43 | >>> kd.in_sphere([0.1,0.2], 1.1) 44 | ['A', 'C', 'B'] 45 | 46 | """ 47 | 48 | lengths = [len(p) for p in points] 49 | min_dim,max_dim = min(lengths),max(lengths) 50 | if min_dim != max_dim: 51 | raise ValueError('points must all have the same dimension') 52 | 53 | if values is None: 54 | values = range(len(points)) 55 | if len(points) != len(values): 56 | raise ValueError('points and values must have the same lengths') 57 | 58 | self.k = min_dim 59 | self.num_points = len(points) 60 | 61 | self.root = self.__build(zip(points,values),depth=0) 62 | 63 | def __build(self, pv_pairs, depth): 64 | if not pv_pairs: 65 | return None 66 | 67 | axis = depth % self.k #cycle axis 68 | 69 | pv_pairs = sorted(pv_pairs, key=lambda x: x[0][axis]) 70 | 71 | mid = len(pv_pairs) // 2 72 | 73 | node = self.node() 74 | node.axis = axis 75 | node.point = pv_pairs[mid][0] 76 | node.value = pv_pairs[mid][1] 77 | node.left_child = self.__build(pv_pairs[:mid], depth+1) 78 | node.right_child = self.__build(pv_pairs[mid+1:], depth+1) 79 | return node 80 | 81 | def nearest(self, point, max_dist=float('inf')): 82 | """Returns the value associated with the nearest points to a given location 83 | 84 | Parameters 85 | ---------- 86 | point : array-like 87 | Location in space, e.g. [1.5, 2.0] 88 | 89 | Optional Parameters 90 | ------------------- 91 | max_dist : float 92 | Ignore points farther than max_dist away from the query point. 93 | 94 | Returns 95 | ------- 96 | value : single element 97 | The value associated with the point nearest to the query point. 98 | Returns None if no points lie within max_dist of the query point 99 | or the tree is empty. 100 | 101 | """ 102 | 103 | x = self.nearest_n(point,n=1,max_dist=max_dist) #list with 0 or 1 elements 104 | 105 | if len(x) == 0: 106 | return None 107 | else: 108 | return x[0] 109 | 110 | def in_sphere(self, point, radius, max_points=None): 111 | """Returns the values of all points in a given sphere 112 | 113 | Parameters 114 | ---------- 115 | point : array-like 116 | Center of the sphere, e.g. [1.5, 2.0] 117 | radius : float 118 | Radius of the sphere, e.g. 0.3 119 | 120 | Optional Parameters 121 | ------------------- 122 | max_points : integer 123 | An upper-bound on the number of points to return. 124 | 125 | Returns 126 | ------- 127 | values : list 128 | List of values associated with all points in the sphere 129 | defined by point and radius. 130 | 131 | """ 132 | if max_points is None: 133 | max_points = float('inf') 134 | 135 | return self.nearest_n(point, n=max_points, max_dist=radius) 136 | 137 | 138 | def nearest_n(self, point, n, max_dist=float('inf')): 139 | """Returns the values of the nearest n points to a given location 140 | 141 | Parameters 142 | ---------- 143 | point : array-like 144 | Location in space, e.g. [1.5, 2.0] 145 | n : integer 146 | (Maximum) Number of values to return. Will return 147 | fewer than n values if the kd_tree contains fewer 148 | than n points. 149 | 150 | Optional Parameters 151 | ------------------- 152 | max_dist : float 153 | Ignore points farther than max_dist away from the query point. 154 | 155 | Returns 156 | ------- 157 | values : list 158 | List of values associated with the n nearest points to 159 | the query location. 160 | 161 | """ 162 | 163 | heap = [] 164 | self.__nearest_n(point, n, max_dist, self.root, heap) 165 | heap.sort() 166 | return [ node.value for (neg_dist,node) in reversed(heap) ] 167 | 168 | def __nearest_n(self,point,n,max_dist,current,heap): 169 | if current is None: 170 | return max_dist 171 | 172 | pt_dist = current.point_distance(point) #distance to this node's point 173 | sep_dist = current.separator_distance(point) #signed distance to this node's separating plane 174 | 175 | if pt_dist < max_dist: 176 | heappush(heap,(-pt_dist,current)) #add this point to the queue 177 | if len(heap) > n: 178 | heappop(heap) 179 | if len(heap) == n: 180 | max_dist = min(-heap[0][0],max_dist) 181 | 182 | 183 | if sep_dist < 0: 184 | max_dist = self.__nearest_n(point,n,max_dist,current.left_child,heap) 185 | else: 186 | max_dist = self.__nearest_n(point,n,max_dist,current.right_child,heap) 187 | 188 | if abs(sep_dist) < max_dist: 189 | #explore other subtree 190 | if sep_dist < 0: 191 | return self.__nearest_n(point,n,max_dist,current.right_child,heap) 192 | else: 193 | return self.__nearest_n(point,n,max_dist,current.left_child,heap) 194 | else: 195 | return max_dist 196 | 197 | ##def inorder(x): 198 | ## if x is not None: 199 | ## return inorder(x.left_child) + [x.value] + inorder(x.right_child) 200 | ## else: 201 | ## return [] 202 | 203 | -------------------------------------------------------------------------------- /pydec/pydec/math/parity.py: -------------------------------------------------------------------------------- 1 | __all__ = ['relative_parity','permutation_parity'] 2 | 3 | 4 | def relative_parity(A,B): 5 | """Relative parity between two lists 6 | 7 | Parameters 8 | ---------- 9 | A,B : lists of elements 10 | Lists A and B must contain permutations of the same elements. 11 | 12 | 13 | Returns 14 | ------- 15 | parity : integer 16 | The parity is 0 if A differs from B by an even number of 17 | transpositions and 1 otherwise. 18 | 19 | Examples 20 | -------- 21 | >>> relative_parity( [0,1], [0,1] ) 22 | 0 23 | >>> relative_parity( [0,1], [1,0] ) 24 | 1 25 | >>> relative_parity( [0,1,2], [0,1,2] ) 26 | 0 27 | >>> relative_parity( [0,1,2], [0,2,1] ) 28 | 1 29 | >>> relative_parity( ['A','B','C'], ['A','B','C'] ) 30 | 0 31 | >>> relative_parity( ['A','B','C'], ['A','C','B'] ) 32 | 1 33 | 34 | """ 35 | 36 | if len(A) != len(B): raise ValueError("B is not a permutation of A") 37 | 38 | # represent each element in B with its index in A and run permutation_parity() 39 | A_indices = dict(zip(A,range(len(A)))) 40 | 41 | if len(A_indices) != len(A): raise ValueError("A contains duplicate values") 42 | 43 | try: 44 | perm = [A_indices[x] for x in B] 45 | except KeyError: 46 | raise ValueError("B is not a permutation of A") 47 | 48 | return permutation_parity(perm, check_input=False) 49 | 50 | 51 | 52 | def permutation_parity(perm, check_input=True): 53 | """Parity of a permutation of the integers 54 | 55 | Parameters 56 | ---------- 57 | perm : list of integers 58 | List containing a permutation of the integers 0...N 59 | 60 | Optional Parameters 61 | ------------------- 62 | check_input : boolean 63 | If True, check whether the input is a valid permutation. 64 | 65 | Returns 66 | ------- 67 | parity : integer 68 | The parity is 0 if perm differs from range(len(perm)) by an 69 | even number of transpositions and 1 otherwise. 70 | 71 | Examples 72 | -------- 73 | >>> permutation_parity( [0,1,2] ) 74 | 0 75 | >>> permutation_parity( [0,2,1] ) 76 | 1 77 | >>> permutation_parity( [1,0,2] ) 78 | 1 79 | >>> permutation_parity( [1,2,0] ) 80 | 0 81 | >>> permutation_parity( [2,0,1] ) 82 | 0 83 | >>> permutation_parity( [0,1,3,2] ) 84 | 1 85 | 86 | """ 87 | 88 | n = len(perm) 89 | if check_input: 90 | rangen = range(n) 91 | if sorted(perm) != rangen: 92 | raise ValueError("Invalid input") 93 | 94 | # Decompose into disjoint cycles. We only need to 95 | # count the number of cycles to determine the parity 96 | num_cycles = 0 97 | seen = set() 98 | for i in range(n): 99 | if i in seen: 100 | continue 101 | num_cycles += 1 102 | j = i 103 | while True: 104 | assert j not in seen 105 | seen.add(j) 106 | j = perm[j] 107 | if j == i: 108 | break 109 | 110 | return (n - num_cycles) % 2 111 | 112 | -------------------------------------------------------------------------------- /pydec/pydec/math/tests/test_circumcenter.py: -------------------------------------------------------------------------------- 1 | from pydec.testing import * 2 | 3 | from scipy import random,rand,array,reshape,sqrt,sum,allclose 4 | 5 | from pydec.math.circumcenter import circumcenter,is_wellcentered 6 | 7 | class TestCircumcenter(TestCase): 8 | def setUp(self): 9 | random.seed(0) #make tests repeatable 10 | 11 | def test_is_well_centered(self): 12 | self.assert_(is_wellcentered(array([[1]]))) 13 | self.assert_(is_wellcentered(array([[0,1]]))) 14 | self.assert_(is_wellcentered(array([[0],[1]]))) 15 | self.assert_(is_wellcentered(array([[0,1],[1,0]]))) 16 | self.assert_(is_wellcentered(array([[0,0],[1,0],[0.5,sqrt(3)/2]]))) 17 | 18 | 19 | def test_spheres(self): 20 | """points on an N dimensional sphere with known center and radius""" 21 | for N in range(2,10): 22 | pts = rand(N+1,N) 23 | true_center = rand(N) 24 | true_radius = 1+10*rand() 25 | pts = pts/reshape(sqrt(sum(pts*pts,axis=1)),(N+1,1)) 26 | pts *= true_radius 27 | pts += true_center 28 | (center,radius) = circumcenter(pts) 29 | assert_almost_equal(center,true_center) 30 | assert_almost_equal(radius,true_radius) 31 | 32 | def test_random(self): 33 | """M random points in N dimensional space (where M <= N + 1)""" 34 | for N in range(1,10): 35 | for M in range(1,N+1): 36 | pts = rand(M,N) 37 | (center,radius) = circumcenter(pts) 38 | distances = sqrt(sum((pts - center)**2,axis=1)) 39 | assert_almost_equal(max(distances),min(distances)) 40 | 41 | 42 | -------------------------------------------------------------------------------- /pydec/pydec/math/tests/test_combinatorial.py: -------------------------------------------------------------------------------- 1 | from pydec.testing import * 2 | 3 | from scipy.special import factorial, comb 4 | 5 | from pydec.math.combinatorial import combinations, permutations 6 | 7 | def test_combinations(): 8 | for N in xrange(6): 9 | L = range(N) 10 | for K in xrange(N+1): 11 | C = list(combinations(L,K)) 12 | S = set([ frozenset(x) for x in C]) 13 | ##Check order 14 | assert_equal(C, sorted(C)) 15 | ##Check number of elements 16 | assert_equal(len(C), comb(N,K,exact=True)) 17 | ##Make sure each element is unique 18 | assert_equal(len(S), comb(N,K,exact=True)) 19 | 20 | 21 | def test_permutations(): 22 | assert_equal(list(permutations([])),[]) 23 | for N in xrange(1,6): 24 | L = range(N) 25 | P = list(permutations(L)) 26 | S = set([ frozenset(x) for x in P]) 27 | ##Check number of elements 28 | assert_equal(len(P), factorial(N,exact=True)) 29 | ##Check that there is only 1 unordered element 30 | assert_equal(len(S), 1) 31 | 32 | -------------------------------------------------------------------------------- /pydec/pydec/math/tests/test_graph.py: -------------------------------------------------------------------------------- 1 | #from pydec.testing import * 2 | # 3 | #import numpy 4 | #from scipy.sparse import coo_matrix, csr_matrix 5 | # 6 | #from pydec.math.graph import maximal_independent_set 7 | # 8 | # 9 | #class test_maximal_independent_set(TestCase): 10 | # def is_MIS(self,graph,mis): 11 | # mis = set(mis) 12 | # unmarked = set(range(graph.shape[0])) 13 | # graph = graph.tocoo() 14 | # 15 | # for i,j in zip(graph.row,graph.col): 16 | # if i == j: 17 | # continue #ignore self loops 18 | # 19 | # if i in mis and j in mis: 20 | # return False #not independent 21 | # 22 | # if i in mis and j in unmarked: 23 | # unmarked.remove(j) 24 | # if j in mis and i in unmarked: 25 | # unmarked.remove(i) 26 | # 27 | # return (unmarked == mis) #check maximality 28 | # 29 | # 30 | # 31 | # def check_simple(self): 32 | # """ 33 | # 2x2 regular mesh 34 | # """ 35 | # A = numpy.matrix([[ 4., -1., -1., 0.], 36 | # [-1., 4., 0., -1.], 37 | # [-1., 0., 4., -1.], 38 | # [ 0., -1., -1., 4.]]) 39 | # 40 | # graph = csr_matrix(A) 41 | # 42 | # assert_equal(True,self.is_MIS(graph,maximal_independent_set(graph))) 43 | # 44 | # 45 | # def check_random(self): 46 | # numpy.random.seed(0) 47 | # 48 | # def rand_sparse(m,n,nnz_per_row): 49 | # """ 50 | # Return a sparse csr with a given number of random nonzero entries per row. 51 | # 52 | # The actual number of nonzeros may be less than expected due to overwriting. 53 | # """ 54 | # nnz_per_row = min(n,nnz_per_row) 55 | # 56 | # rows = numpy.arange(m).repeat(nnz_per_row) 57 | # cols = numpy.random.random_integers(low=0,high=n-1,size=nnz_per_row*m) 58 | # vals = numpy.random.random_sample(m*nnz_per_row) 59 | # return coo_matrix((vals,(rows,cols)),(m,n)).tocsr() 60 | # 61 | # for n in [2,10,20,50,200]: 62 | # G = rand_sparse(n,n,min(n/2,10)) 63 | # G = G + G.T 64 | # assert_equal(True,self.is_MIS(G,maximal_independent_set(G))) 65 | # 66 | -------------------------------------------------------------------------------- /pydec/pydec/math/tests/test_kd_tree.py: -------------------------------------------------------------------------------- 1 | from pydec.testing import * 2 | 3 | from scipy import random, array, sqrt, sum 4 | 5 | from pydec.math.kd_tree import kd_tree 6 | 7 | 8 | def test_simple(): 9 | pts = [[0.0,0.0], 10 | [1.0,0.0], 11 | [1.0,1.0], 12 | [0.0,1.0], 13 | [0.5,0.5]] 14 | 15 | kdt = kd_tree(pts) 16 | 17 | assert_equal(kdt.nearest([2.0,2.0]), 2) 18 | assert_equal(kdt.nearest([0.1,0.2]), 0) 19 | assert_equal(kdt.nearest([0.9,0.0]), 1) 20 | assert_equal(kdt.nearest([0.4,0.6]), 4) 21 | 22 | def test_values(): 23 | pts = [[0.0,0.0], 24 | [1.0,0.0], 25 | [1.0,1.0], 26 | [0.0,1.0], 27 | [0.5,0.5]] 28 | vals = ['a','b','c','d','e'] 29 | 30 | kdt = kd_tree(pts,vals) 31 | 32 | assert_equal(kdt.nearest([2.0,2.0]), 'c') 33 | assert_equal(kdt.nearest([0.1,0.2]), 'a') 34 | assert_equal(kdt.nearest([0.9,0.0]), 'b') 35 | assert_equal(kdt.nearest([0.4,0.6]), 'e') 36 | 37 | def test_random(): 38 | random.seed(0) #make tests repeatable 39 | for dim in [1,2,3,4]: 40 | for num_pts in [1,2,5,10,50]: 41 | 42 | pts = (20*rand(num_pts,dim) - 10) 43 | 44 | kdt = kd_tree(pts.tolist()) 45 | 46 | for sample in (20*rand(5,dim) - 10).tolist(): 47 | #5 sample points per test 48 | distances = sqrt(sum((pts - sample)**2,axis=1)) 49 | sorted_pairs = sorted(zip(distances,range(len(pts)))) 50 | 51 | assert_equal(kdt.nearest(sample),sorted_pairs[0][1]) 52 | 53 | for n in [1,2,5]: 54 | assert_equal(kdt.nearest_n(sample,n),[x[1] for x in sorted_pairs[:n]]) 55 | 56 | -------------------------------------------------------------------------------- /pydec/pydec/math/tests/test_parity.py: -------------------------------------------------------------------------------- 1 | from pydec.testing import * 2 | 3 | from pydec.math.parity import permutation_parity, relative_parity 4 | 5 | cases = [] 6 | cases.append(([],0)) #for consistency 7 | cases.append(([0],0)) 8 | cases.append(([0,1],0)) 9 | cases.append(([1,0],1)) 10 | cases.append(([0,1,2],0)) 11 | cases.append(([0,2,1],1)) 12 | cases.append(([2,0,1],0)) 13 | cases.append(([2,1,0],1)) 14 | cases.append(([1,2,0],0)) 15 | cases.append(([1,0,2],1)) 16 | cases.append(([0,1,2,3],0)) 17 | cases.append(([0,1,3,2],1)) 18 | cases.append(([1,0,2,3],1)) 19 | cases.append(([1,2,0,3],0)) 20 | cases.append(([1,2,3,0],1)) 21 | cases.append(([2,1,3,0],0)) 22 | cases.append(([2,3,1,0],1)) 23 | cases.append(([3,2,1,0],0)) 24 | 25 | def test_permutation_parity(): 26 | for perm,parity in cases: 27 | assert_equal(permutation_parity(perm), parity) 28 | 29 | def test_relative_parity(): 30 | for perm,parity in cases: 31 | #check lists of integers 32 | assert_equal(relative_parity(perm,perm), 0) 33 | assert_equal(relative_parity(perm,range(len(perm))), parity) 34 | 35 | #check lists of strings 36 | L1 = map(str,perm) 37 | L2 = map(str,range(len(perm))) 38 | assert_equal(relative_parity(L1,L1), 0) 39 | assert_equal(relative_parity(L1,L2), parity) 40 | 41 | -------------------------------------------------------------------------------- /pydec/pydec/math/tests/test_volume.py: -------------------------------------------------------------------------------- 1 | from pydec.testing import * 2 | 3 | from scipy import fabs, random, rand, array, sqrt 4 | 5 | from pydec.math.volume import unsigned_volume, signed_volume 6 | 7 | 8 | def test_unsigned_volume(): 9 | cases = [] 10 | cases.append((array([[1]]), 1)) 11 | cases.append((array([[1],[10]]), 9)) 12 | cases.append((array([[0,0],[1,1]]), sqrt(2))) 13 | cases.append((array([[0,0],[0,1],[1,0]]), 1.0/2.0)) 14 | cases.append((array([[0,0],[0,1],[1,0]]), 1.0/2.0)) 15 | cases.append((array([[5,5],[5,6],[6,5]]), 1.0/2.0)) 16 | cases.append((array([[0,0,0],[0,0,1],[0,1,0]]), 1.0/2.0)) 17 | 18 | for s,v in cases: 19 | assert_almost_equal(unsigned_volume(s), v) 20 | 21 | def test_signed_volume(): 22 | cases = [] 23 | cases.append((array([[1],[2]]), 1)) 24 | cases.append((array([[5.5],[-10]]), -15.5)) 25 | cases.append((array([[0,0],[1,1],[1,0]]), -1.0/2.0)) 26 | cases.append((array([[0,0],[1,0],[1,1]]), 1.0/2.0)) 27 | cases.append((array([[0,0],[0,1],[1,0]]), -1.0/2.0)) 28 | cases.append((array([[5,5],[5,6],[6,5]]), -1.0/2.0)) 29 | cases.append((array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]]), 1.0/6.0)) 30 | 31 | for s,v in cases: 32 | assert_almost_equal(signed_volume(s), v) 33 | 34 | def test_both(): 35 | """signed and unsigned volumes should agree up to sign""" 36 | 37 | random.seed(0) #make tests repeatable 38 | for N in range(1,10): 39 | pts = rand(N+1,N) 40 | assert_almost_equal(fabs(signed_volume(pts)), unsigned_volume(pts)) 41 | 42 | -------------------------------------------------------------------------------- /pydec/pydec/math/volume.py: -------------------------------------------------------------------------------- 1 | __all__ = ['unsigned_volume','signed_volume'] 2 | 3 | from numpy import sqrt,inner,shape,asarray 4 | from scipy.special import factorial 5 | from scipy.linalg import det 6 | 7 | 8 | def unsigned_volume(pts): 9 | """Unsigned volume of a simplex 10 | 11 | Computes the unsigned volume of an M-simplex embedded in N-dimensional 12 | space. The points are stored row-wise in an array with shape (M+1,N). 13 | 14 | Parameters 15 | ---------- 16 | pts : array 17 | Array with shape (M+1,N) containing the coordinates 18 | of the (M+1) vertices of the M-simplex. 19 | 20 | Returns 21 | ------- 22 | volume : scalar 23 | Unsigned volume of the simplex 24 | 25 | Notes 26 | ----- 27 | Zero-dimensional simplices (points) are assigned unit volumes. 28 | 29 | 30 | Examples 31 | -------- 32 | >>> # 0-simplex point 33 | >>> unsigned_volume( [[0,0]] ) 34 | 1.0 35 | >>> # 1-simplex line segment 36 | >>> unsigned_volume( [[0,0],[1,0]] ) 37 | 1.0 38 | >>> # 2-simplex triangle 39 | >>> unsigned_volume( [[0,0,0],[0,1,0],[1,0,0]] ) 40 | 0.5 41 | 42 | 43 | References 44 | ---------- 45 | [1] http://www.math.niu.edu/~rusin/known-math/97/volumes.polyh 46 | 47 | """ 48 | 49 | pts = asarray(pts) 50 | 51 | M,N = pts.shape 52 | M -= 1 53 | 54 | if M < 0 or M > N: 55 | raise ValueError('array has invalid shape') 56 | 57 | if M == 0: 58 | return 1.0 59 | 60 | A = pts[1:] - pts[0] 61 | return sqrt(abs(det(inner(A,A))))/factorial(M) 62 | 63 | 64 | def signed_volume(pts): 65 | """Signed volume of a simplex 66 | 67 | Computes the signed volume of an M-simplex embedded in M-dimensional 68 | space. The points are stored row-wise in an array with shape (M+1,M). 69 | 70 | Parameters 71 | ---------- 72 | pts : array 73 | Array with shape (M+1,M) containing the coordinates 74 | of the (M+1) vertices of the M-simplex. 75 | 76 | Returns 77 | ------- 78 | volume : scalar 79 | Signed volume of the simplex 80 | 81 | 82 | Examples 83 | -------- 84 | >>> # 1-simplex line segment 85 | >>> signed_volume( [[0],[1]] ) 86 | 1.0 87 | >>> # 2-simplex triangle 88 | >>> signed_volume( [[0,0],[1,0],[0,1]] ) 89 | 0.5 90 | >>> # 3-simplex tetrahedron 91 | >>> signed_volume( [[0,0,0],[3,0,0],[0,1,0],[0,0,1]] ) 92 | 0.5 93 | 94 | References 95 | ---------- 96 | [1] http://www.math.niu.edu/~rusin/known-math/97/volumes.polyh 97 | 98 | """ 99 | 100 | pts = asarray(pts) 101 | 102 | M,N = pts.shape 103 | M -= 1 104 | 105 | if M != N: 106 | raise ValueError('array has invalid shape') 107 | 108 | A = pts[1:] - pts[0] 109 | return det(A)/factorial(M) 110 | -------------------------------------------------------------------------------- /pydec/pydec/mesh/__init__.py: -------------------------------------------------------------------------------- 1 | "PyDEC meshes" 2 | 3 | from .info import __doc__ 4 | 5 | from .simplex import * 6 | from .regular_cube import * 7 | from .generation import * 8 | from .subdivision import * 9 | from .ncube import * 10 | 11 | __all__ = list(filter(lambda s:not s.startswith('_'),dir())) 12 | 13 | # TODO: replace testing framework with pytest (?) since Tester is deprecated 14 | #from pydec.testing import Tester 15 | #test = Tester().test 16 | 17 | -------------------------------------------------------------------------------- /pydec/pydec/mesh/__init__.py~: -------------------------------------------------------------------------------- 1 | "PyDEC meshes" 2 | 3 | from .info import __doc__ 4 | 5 | from .simplex import * 6 | from .regular_cube import * 7 | from .generation import * 8 | from .subdivision import * 9 | from .ncube import * 10 | 11 | __all__ = list(filter(lambda s:not s.startswith('_'),dir())) 12 | 13 | from pydec.testing import Tester 14 | test = Tester().test 15 | 16 | -------------------------------------------------------------------------------- /pydec/pydec/mesh/base_mesh.py: -------------------------------------------------------------------------------- 1 | __all__ = ['base_mesh'] 2 | 3 | class base_mesh(dict): 4 | pass 5 | -------------------------------------------------------------------------------- /pydec/pydec/mesh/generation.py: -------------------------------------------------------------------------------- 1 | __all__ = ['simplicial_grid_2d','cube_grid'] 2 | 3 | from numpy import zeros,resize,arange,ravel,concatenate,matrix, \ 4 | transpose,prod,mgrid,ndindex,sum,array,cumprod,tile,ones 5 | import scipy 6 | 7 | 8 | def simplicial_grid_2d(n): 9 | """ 10 | Create an NxN 2d grid in the unit square 11 | 12 | The number of vertices along each axis is (N+1) for a total of (N+1)x(N+1) vertices 13 | 14 | A tuple (vertices,indices) of arrays is returned 15 | """ 16 | vertices = zeros(((n+1)**2,2)) 17 | vertices[:,0] = ravel(resize(arange(n+1),(n+1,n+1))) 18 | vertices[:,1] = ravel(transpose(resize(arange(n+1),(n+1,n+1)))) 19 | vertices /= n 20 | 21 | indices = zeros((2*(n**2),3),scipy.int32) 22 | 23 | 24 | t1 = transpose(concatenate((matrix(arange(n)),matrix(arange(1,n+1)),matrix(arange(n+2,2*n+2))),axis=0)) 25 | t2 = transpose(concatenate((matrix(arange(n)),matrix(arange(n+2,2*n+2)),matrix(arange(n+1,2*n+1))),axis=0)) 26 | first_row = concatenate((t1,t2)) 27 | 28 | for i in xrange(n): 29 | indices[(2*n*i):(2*n*(i+1)),:] = first_row + i*(n+1) 30 | 31 | return (vertices,indices) 32 | 33 | 34 | def cube_grid(dims): 35 | """ 36 | Return a regular nD-cube mesh with given shape. 37 | 38 | Eg. 39 | cube_grid_nd((2,2)) -> 2x2 - 2d mesh (x,y) 40 | cube_grid_nd((4,3,2)) -> 4x3x2 - 3d mesh (x,y,z) 41 | 42 | Eg. 43 | 44 | v,i = cube_grid_nd((2,1)) 45 | 46 | v = 47 | array([[ 0., 0.], 48 | [ 1., 0.], 49 | [ 2., 0.], 50 | [ 0., 1.], 51 | [ 1., 1.], 52 | [ 2., 1.]]) 53 | 54 | i = 55 | array([[[0, 3], 56 | [1, 4]], 57 | 58 | [[1, 4], 59 | [2, 5]]]) 60 | 61 | """ 62 | dims = tuple(dims) 63 | 64 | vert_dims = tuple(x+1 for x in dims) 65 | N = len(dims) 66 | 67 | vertices = zeros((prod(vert_dims),N)) 68 | grid = mgrid[tuple(slice(0,x,None) for x in reversed(vert_dims))] 69 | for i in range(N): 70 | vertices[:,i] = ravel(grid[N-i-1]) 71 | 72 | 73 | #construct one cube to be tiled 74 | cube = zeros((2,)*N,dtype='i') 75 | cycle = array([1] + list(cumprod(vert_dims)[:-1]),dtype='i') 76 | for i in ndindex(*((2,)*N)): 77 | cube[i] = sum(array(i) * cycle) 78 | cycle = array([1] + list(cumprod(vert_dims)[:-1]),dtype='i') 79 | 80 | 81 | #indices of all vertices which are the lower corner of a cube 82 | interior_indices = arange(prod(vert_dims)).reshape(tuple(reversed(vert_dims))).T 83 | interior_indices = interior_indices[tuple(slice(0,x,None) for x in dims)] 84 | 85 | indices = tile(cube,(prod(dims),) + (1,)*N) + interior_indices.reshape((prod(dims),) + (1,)*N) 86 | 87 | return (vertices,indices) 88 | -------------------------------------------------------------------------------- /pydec/pydec/mesh/info.py: -------------------------------------------------------------------------------- 1 | """ 2 | STUB 3 | ============= 4 | 5 | """ 6 | 7 | postpone_import = 1 8 | -------------------------------------------------------------------------------- /pydec/pydec/mesh/ncube.py: -------------------------------------------------------------------------------- 1 | __all__ = ['nCube','nCubeMesh','RegularCubeMesh'] 2 | 3 | 4 | from numpy import ndarray,array,asarray,ndim,bitwise_xor,eye,hstack,vstack,arange,zeros 5 | from .simplex import Simplex 6 | 7 | 8 | class RegularCubeMesh: 9 | """ 10 | A regular grid of hypercubes. 11 | 12 | 13 | Examples: 14 | 15 | # create a 2x2 cube mesh 16 | bitmap = ones((2,2),dtype='bool') 17 | c_mesh = RegularCubeMesh(bitmap) 18 | 19 | # creates a 3x3 cube mesh with a center hole 20 | bitmap = ones((3,3),dtype='bool') 21 | bitmap[1,1] = False 22 | c_mesh = RegularCubeMesh(bitmap) 23 | 24 | # creates a 10x10x10 cube mesh with a center hole 25 | bitmap = ones((10,10,10),dtype='bool') 26 | bitmap[5,5,5] = False 27 | c_mesh = RegularCubeMesh(bitmap) 28 | """ 29 | def __init__(self,bitmap): 30 | self.bitmap = asarray(bitmap,dtype='bool') 31 | 32 | def cube_array(self): 33 | """ 34 | Return a cube array that represents this mesh's bitmap 35 | """ 36 | cubes = vstack(self.bitmap.nonzero()).transpose() 37 | applied_zeroes = zeros((cubes.shape[0], ndim(self.bitmap)), dtype=cubes.dtype) 38 | cubes = hstack((cubes, applied_zeroes + arange(ndim(self.bitmap)))) 39 | 40 | return cubes 41 | 42 | def dimension(self): 43 | return ndim(self.bitmap) 44 | 45 | 46 | 47 | 48 | class nCube: 49 | def __init__(self,s,dtype='int32'): 50 | self.indices = array(s,dtype=dtype) 51 | assert(self.indices.shape == (1,) or self.indices.shape == (2,)*ndim(self.indices)) 52 | 53 | self.compute_corner_simplex() 54 | 55 | def compute_corner_simplex(self): 56 | if ndim(self.indices) < 2: 57 | self.corner_simplex = Simplex(self.indices) 58 | else: 59 | corner_value = self.indices.min() 60 | corner_index = (self.indices == corner_value).nonzero() 61 | 62 | rest = self.indices[[tuple(x) for x in bitwise_xor(eye(ndim(self.indices),dtype=int),array(corner_index))]] 63 | 64 | parity = sum(corner_index)[0] % 2 65 | 66 | self.corner_simplex = Simplex([corner_value] + rest.tolist(),parity) 67 | 68 | def __str__(self): 69 | return 'nCube(' + ndarray.__str__(self.indices) + ')' 70 | 71 | def __hash__(self): 72 | return hash(self.corner_simplex) 73 | 74 | def __eq__(self,other): 75 | return self.corner_simplex == other 76 | 77 | ## Ideas for boundary() 78 | ##In [17]: A.take([0],axis=0) 79 | ##Out[17]: 80 | ##array([[[0, 1], 81 | ## [2, 3]]]) 82 | ## 83 | ##In [18]: A.take([1],axis=0) 84 | ##Out[18]: 85 | ##array([[[4, 5], 86 | ## [6, 7]]]) 87 | def boundary(self): 88 | raise NotImplementedError 89 | 90 | 91 | def relative_orientation(self,other): 92 | """ 93 | Determine whether two cubes that represent the same 94 | face have the same orientation or opposite orientations 95 | 96 | Returns: 97 | False if same orientation 98 | True if opposite orientation 99 | """ 100 | if self.corner_simplex != other.corner_simplex: 101 | raise ValueError('Cubes do not share the same vertices') 102 | return self.corner_simplex.parity ^ other.corner_simplex.parity 103 | 104 | 105 | 106 | 107 | 108 | 109 | class nCubeMesh: 110 | def __init__(self,indices,vertices): 111 | self.indices = asarray(simplices,dtype='i') 112 | self.vertices = asarray(vertices, dtype='d') 113 | 114 | def manifold_dimension(self): 115 | if ndim(self.indices) >= 2: 116 | return ndim(self.indices) 117 | else: 118 | return self.indices.shape[1] 119 | 120 | def embedding_dimension(self): 121 | return self.vertices.shape[1] 122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /pydec/pydec/mesh/regular_cube.py: -------------------------------------------------------------------------------- 1 | __all__ = ['regular_cube_mesh'] 2 | 3 | from numpy import array,asarray,hstack,vstack,arange,zeros,ndim 4 | 5 | 6 | class regular_cube_mesh: 7 | """ 8 | A regular grid of hypercubes. 9 | 10 | 11 | Examples: 12 | 13 | # create a 2x2 cube mesh 14 | bitmap = ones((2,2),dtype='bool') 15 | c_mesh = regular_cube_mesh(bitmap) 16 | 17 | # creates a 3x3 cube mesh with a center hole 18 | bitmap = ones((3,3),dtype='bool') 19 | bitmap[1,1] = False 20 | c_mesh = regular_cube_mesh(bitmap) 21 | 22 | # creates a 10x10x10 cube mesh with a center hole 23 | bitmap = ones((10,10,10),dtype='bool') 24 | bitmap[5,5,5] = False 25 | c_mesh = regular_cube_mesh(bitmap) 26 | """ 27 | def __init__(self,bitmap): 28 | self.bitmap = asarray(bitmap,dtype='bool') 29 | 30 | def cube_array(self): 31 | """ 32 | Return a cube array that represents this mesh's bitmap 33 | """ 34 | cubes = vstack(self.bitmap.nonzero()).transpose().astype('int32') 35 | cubes = hstack((cubes,zeros((cubes.shape[0],ndim(self.bitmap)),dtype=cubes.dtype) + arange(ndim(self.bitmap),dtype=cubes.dtype))) 36 | 37 | return cubes 38 | 39 | def dimension(self): 40 | return ndim(self.bitmap) 41 | -------------------------------------------------------------------------------- /pydec/pydec/mesh/simplex.py: -------------------------------------------------------------------------------- 1 | __all__ = ['Simplex','SimplicialMesh','simplex','simplicial_mesh'] 2 | 3 | from pydec.math import signed_volume,relative_parity,combinations 4 | from .base_mesh import base_mesh 5 | 6 | 7 | from numpy import asarray 8 | import numpy,scipy 9 | 10 | 11 | class simplex(tuple): 12 | def __new__(cls,s,parity=0): 13 | obj = tuple.__new__(cls, sorted(s)) 14 | obj.parity = relative_parity(obj,s) ^ parity 15 | return obj 16 | 17 | def __repr__(self): 18 | return 'simplex(' + tuple.__repr__(self) + ',parity=' + str(self.parity) + ')' 19 | 20 | def boundary(self): 21 | """ 22 | A list of oriented simplicies in the boundary of this simplex 23 | """ 24 | return [ simplex(self[:n] + self[n+1:], (self.parity + n) % 2) for n in range(len(self)) ] 25 | 26 | 27 | 28 | class simplicial_mesh(base_mesh): 29 | """Simplicial mesh 30 | 31 | Can be instantiated in several ways: 32 | - simplicial_mesh(V,E) 33 | - where V and E are arrays of vertices and simplex indices 34 | - simplicial_mesh( D ) 35 | - where D is a dictionary with keys 'vertices' and 'elements' 36 | 37 | 38 | Examples 39 | ======== 40 | 41 | >>> from numpy import array 42 | >>> from pydec.mesh import simplicial_mesh 43 | >>> V = array([[0,0],[1,0],[0,1]]) # mesh vertices 44 | >>> E = array([[0,1,2]]) # mesh indices 45 | >>> simplicial_mesh(V,E) 46 | 47 | >>> D = {'vertices' : V, 'elements' : E} 48 | >>> simplicial_mesh(data) 49 | 50 | """ 51 | 52 | def __init__(self,*args,**kwargs): 53 | 54 | if len(args) == 2: 55 | #try to parse as (vertices,indices) 56 | V,I = args 57 | self['vertices'] = asarray(V) 58 | self['elements'] = asarray(I) 59 | elif len(kwargs) == 2: 60 | self['vertices'] = asarray(kwargs['vertices']) 61 | self['elements'] = asarray(kwargs['indices']) 62 | elif len(args) == 1 and isinstance(args[0],dict): 63 | base_mesh.update(self,args[0]) 64 | else: 65 | raise ValueError('unrecognized arguments') 66 | 67 | if numpy.ndim(self['elements']) != 2 or numpy.ndim(self['vertices']) != 2: 68 | raise ValueError('index and vertex arrays must have rank 2') 69 | 70 | if self['elements'].min() < 0 or self['elements'].max() > self['vertices'].shape[0]: 71 | raise ValueError('invalid index value') 72 | 73 | 74 | def __getattr__(self, attr): 75 | if attr == 'vertices': 76 | return self['vertices'] 77 | elif attr in ['indices', 'elements']: 78 | return self['elements'] 79 | 80 | return base_mesh.__getattr__(self, attr) 81 | 82 | def __setattr__(self,attr,value): 83 | if attr == 'vertices': 84 | self['vertices'] = value 85 | elif attr in ['indices','elements']: 86 | self['elements'] = value 87 | else: 88 | return base_mesh.__setattr__(self,attr,value) 89 | 90 | def __repr__(self): 91 | output = "" 92 | output += "simplicial_mesh< " + str(self.manifold_dimension()) + "D manifold, " 93 | output += str(self.embedding_dimension()) + "D embedding, " 94 | output += str(self['vertices'].shape[0]) + " vertices, " 95 | output += str(self['elements'].shape[0]) + " elements >\n" 96 | 97 | format_str = '\t%-16s %16s %10s\n' 98 | 99 | output += format_str % ('Data Names'.center(16),'Shape'.center(16),'Size (KB)'.center(16)) 100 | for k,v in self.iteritems(): 101 | output += format_str % (k,str(v.shape),str(v.nbytes/1024)) 102 | return output 103 | 104 | def manifold_dimension(self): 105 | return self['elements'].shape[1] - 1 106 | 107 | def embedding_dimension(self): 108 | return self['vertices'].shape[1] 109 | 110 | def boundary(self): 111 | """ 112 | Return a set() of the boundary simplices, i.e. the faces 113 | of the top level simplices that occur only once 114 | """ 115 | boundary_set = set() 116 | 117 | for row in self['elements']: 118 | s = simplex(row) 119 | for b in s.boundary(): 120 | if b in boundary_set: 121 | boundary_set.remove(b) #b has occured twice 122 | else: 123 | boundary_set.add(b) #first occurance of b 124 | 125 | return boundary_set 126 | 127 | def skeleton(self,p): 128 | """ 129 | Returns the p-skeleton (all the p-faces) of the mesh as a set() 130 | """ 131 | assert(0 <= p <= self.manifold_dimension()) 132 | 133 | skeleton_set = set() 134 | 135 | for row in self.indices: 136 | for b in combinations(row,p+1): 137 | skeleton_set.add(simplex(b)) 138 | 139 | return skeleton_set 140 | 141 | 142 | def orient(self): 143 | """ 144 | Orient this SimplicialMesh. If the manifold is of the same dimension as the 145 | embedding (e.g. triangle mesh in 2D, tet mesh in 3D) then the resultant mesh 146 | will be oriented so that the simplices have positive volume. 147 | 148 | If the mesh is not orientable an Exception is raised. 149 | """ 150 | 151 | 152 | if self.manifold_dimension() == 0: 153 | #0-dimensional manifold is alway oriented 154 | return 155 | 156 | if self.manifold_dimension() == self.embedding_dimension(): 157 | #orient w/ positive volumes 158 | num_flips = 0 159 | elements = self['elements'] 160 | vertices = self['vertices'] 161 | 162 | for row in elements: 163 | pts = vertices[row] 164 | if signed_volume(pts) < 0: 165 | num_flips += 1 166 | temp = row[0] 167 | row[0] = row[1] 168 | row[1] = temp 169 | print("Flipped", num_flips,"simplices") 170 | return 171 | 172 | raise NotImplementedError 173 | 174 | simplex_to_index = {} 175 | for index,row in enumerate(self['elements']): 176 | simplex_to_index[simplex(row)] = index 177 | 178 | faces = self.skeleton(self.manifold_dimension() - 1) 179 | face_to_simplex = dict.fromkeys(faces,set()) 180 | for simplex,index in simplex_to_index.iterkeys(): 181 | for b in simplex.boundary(): 182 | face_to_simplex[b].add(index) 183 | 184 | simplex_neigbors = [[]]*len(self['elements']) 185 | for simplex,index in simplex_to_index.iterkeys(): 186 | for b in simplex.boundary(): 187 | simplex_neigbors[index].append(face_to_simplex[b] - set([index])) 188 | 189 | print(simplex_neighbors) 190 | 191 | 192 | #for backwards compatibility 193 | Simplex = simplex 194 | SimplicialMesh = simplicial_mesh 195 | -------------------------------------------------------------------------------- /pydec/pydec/mesh/subdivision.py: -------------------------------------------------------------------------------- 1 | __all__ = ['loop_subdivision','triangulate_ncube'] 2 | 3 | from pydec.math.combinatorial import combinations 4 | from numpy import concatenate,matrix,ravel,array,ndim,vstack,hstack,zeros,arange,tile 5 | 6 | 7 | def loop_subdivision(vertices,simplices): 8 | """ 9 | Given a triangle mesh represented by the matrices (vertices,simplices), return 10 | new vertex and simplex arrays for the Loop subdivided mesh. 11 | """ 12 | 13 | #all edges in the mesh 14 | edges = set() 15 | 16 | for s in simplices: 17 | edges.update([frozenset(x) for x in combinations(ravel(s),2)]) 18 | 19 | edge_index_map = {} 20 | for n,e in enumerate(edges): 21 | edge_index_map[e] = len(vertices) + n 22 | 23 | edge_vertices = [] 24 | for e in edges: 25 | e0,e1 = sorted(e) 26 | edge_vertices.append(0.5*(vertices[e0] + vertices[e1])) 27 | 28 | new_vertices = concatenate((vertices,array(edge_vertices))) 29 | new_simplices = [] 30 | 31 | for n,s in enumerate(simplices): 32 | v0,v1,v2 = ravel(s) 33 | e01 = edge_index_map[frozenset((v0,v1))] 34 | e12 = edge_index_map[frozenset((v1,v2))] 35 | e20 = edge_index_map[frozenset((v2,v0))] 36 | 37 | new_simplices.append([v0,e01,e20]) 38 | new_simplices.append([v1,e12,e01]) 39 | new_simplices.append([v2,e20,e12]) 40 | new_simplices.append([e01,e12,e20]) 41 | 42 | new_simplices = array(new_simplices) 43 | 44 | return new_vertices,new_simplices 45 | 46 | 47 | 48 | 49 | 50 | 51 | def triangulate_ncube(vertices,indices): 52 | n_dims = ndim(indices) - 1 53 | n_cubes = indices.shape[0] 54 | n_verts = vertices.shape[0] 55 | 56 | if n_dims <= 1: 57 | #cube mesh only contains edges 58 | return vertices,indices 59 | 60 | 61 | 62 | 63 | 64 | if n_dims > 2: 65 | raise NotImplementedError('nCube meshes with n > 2 not supported') 66 | 67 | cell_centers = vertices[indices.reshape(n_cubes,-1)].mean(axis=1) 68 | 69 | n_faces = 2*n_dims*n_cubes 70 | 71 | faces = zeros((n_faces,) + (2,)*(n_dims-1),dtype=indices.dtype) 72 | 73 | for i in range(n_dims): 74 | s0 = [slice(None,None,None)]*(i+1) + [0] + [slice(None,None,None)]*(n_dims-i-1) 75 | s1 = [slice(None,None,None)]*(i+1) + [1] + [slice(None,None,None)]*(n_dims-i-1) 76 | 77 | faces[(2*i+0)*n_cubes:(2*i+1)*n_cubes] = indices[s0] 78 | faces[(2*i+1)*n_cubes:(2*i+2)*n_cubes] = indices[s1] 79 | 80 | #this seems to be the correct pattern 81 | if (n_dims-1-i) % 2 == n_dims % 2: 82 | #flip 1 83 | temp = faces[(2*i+1)*n_cubes:(2*i+2)*n_cubes,0].copy() 84 | faces[(2*i+1)*n_cubes:(2*i+2)*n_cubes,0] = faces[(2*i+1)*n_cubes:(2*i+2)*n_cubes,1] 85 | faces[(2*i+1)*n_cubes:(2*i+2)*n_cubes,1] = temp 86 | else: 87 | #flip 0 88 | temp = faces[(2*i+0)*n_cubes:(2*i+1)*n_cubes,0].copy() 89 | faces[(2*i+0)*n_cubes:(2*i+1)*n_cubes,0] = faces[(2*i+0)*n_cubes:(2*i+1)*n_cubes,1] 90 | faces[(2*i+0)*n_cubes:(2*i+1)*n_cubes,1] = temp 91 | 92 | 93 | face_vertices,face_indices = triangulate_ncube(vertices,faces) 94 | 95 | center_indices = (arange(n_cubes) + face_vertices.shape[0]).reshape((n_cubes,1)) 96 | center_indices = tile(center_indices,(face_indices.shape[0]/n_cubes,1)) 97 | 98 | new_vertices = vstack((face_vertices,cell_centers)) 99 | new_indices = hstack((center_indices,face_indices)) 100 | 101 | return new_vertices,new_indices 102 | 103 | 104 | -------------------------------------------------------------------------------- /pydec/pydec/mesh/tests/test_ncube.py: -------------------------------------------------------------------------------- 1 | from pydec.testing import * 2 | 3 | from numpy import arange, prod, array 4 | 5 | from pydec.mesh.ncube import nCube 6 | 7 | 8 | class test_relative_partiy(TestCase): 9 | def setUp(self): 10 | test_cases = [] 11 | test_cases.append(([0],0)) 12 | test_cases.append(([0,1],0)) 13 | test_cases.append(([1,0],1)) 14 | test_cases.append(([[0,1],[2,3]],0)) 15 | test_cases.append(([[1,3],[0,2]],0)) 16 | test_cases.append(([[3,2],[1,0]],0)) 17 | test_cases.append(([[2,0],[3,1]],0)) 18 | test_cases.append(([[2,3],[0,1]],1)) 19 | test_cases.append(([[0,2],[1,3]],1)) 20 | test_cases.append(([[1,0],[3,2]],1)) 21 | test_cases.append(([[3,1],[2,0]],1)) 22 | test_cases.append(([[3,1],[2,0]],1)) 23 | test_cases.append(([[[0,1],[2,3]],[[4,5],[6,7]]],0)) 24 | test_cases.append(([[[1,5],[3,7]],[[0,4],[2,6]]],0)) 25 | test_cases.append(([[[5,4],[7,6]],[[1,0],[3,2]]],0)) 26 | test_cases.append(([[[4,0],[6,2]],[[5,1],[7,3]]],0)) 27 | test_cases.append(([[[2,3],[6,7]],[[0,1],[4,5]]],0)) 28 | test_cases.append(([[[4,5],[0,1]],[[6,7],[2,3]]],0)) 29 | test_cases.append(([[[7,3],[5,1]],[[6,2],[4,0]]],0)) 30 | test_cases.append(([[[1,0],[5,4]],[[3,2],[7,6]]],0)) 31 | test_cases.append(([[[4,5],[6,7]],[[0,1],[2,3]]],1)) 32 | test_cases.append(([[[0,4],[2,6]],[[1,5],[3,7]]],1)) 33 | test_cases.append(([[[1,0],[3,2]],[[5,4],[7,6]]],1)) 34 | test_cases.append(([[[5,1],[7,3]],[[4,0],[6,2]]],1)) 35 | self.test_cases = test_cases 36 | 37 | 38 | def check_basic(self): 39 | """ 40 | Test permutations relative to the canonical order 41 | """ 42 | for B,result in self.test_cases: 43 | B = array(B) 44 | A = arange(prod(B.shape)).reshape(B.shape) 45 | 46 | A_cube = nCube(A) 47 | B_cube = nCube(B) 48 | 49 | assert_equal(A_cube.relative_orientation(A_cube),False) 50 | assert_equal(B_cube.relative_orientation(B_cube),False) 51 | assert_equal(A_cube.relative_orientation(B_cube),result) 52 | assert_equal(B_cube.relative_orientation(A_cube),result) 53 | 54 | def check_pairs(self): 55 | """ 56 | Test pairs of cubes against one another 57 | """ 58 | 59 | for B0,result0 in self.test_cases: 60 | B0 = array(B0) 61 | for B1,result1 in self.test_cases: 62 | B1 = array(B1) 63 | if B0.shape == B1.shape: 64 | B0_cube = nCube(B0) 65 | B1_cube = nCube(B1) 66 | 67 | assert_equal(B0_cube.relative_orientation(B1_cube),result0 ^ result1) 68 | assert_equal(B1_cube.relative_orientation(B0_cube),result0 ^ result1) 69 | 70 | 71 | def check_permutations(self): 72 | """ 73 | Test permuted versions of the canonical ordering 74 | """ 75 | for N in range(2,6): 76 | A = arange(2**N).reshape((2,)*N) 77 | B = A.copy() 78 | for i in range(1,N+1): 79 | Bcopy = B.copy() 80 | slice0 = [slice(None,None,None)]*(i-1) + [0] + [slice(None,None,None)]*(N-i) 81 | slice1 = [slice(None,None,None)]*(i-1) + [1] + [slice(None,None,None)]*(N-i) 82 | B[slice0] = Bcopy[slice1] 83 | B[slice1] = Bcopy[slice0] 84 | 85 | A_cube = nCube(A) 86 | B_cube = nCube(B) 87 | assert_equal(A_cube.relative_orientation(B_cube),i % 2) 88 | assert_equal(B_cube.relative_orientation(A_cube),i % 2) 89 | 90 | 91 | -------------------------------------------------------------------------------- /pydec/pydec/mesh/tests/test_simplex.py: -------------------------------------------------------------------------------- 1 | from pydec.testing import * 2 | 3 | from scipy import array 4 | 5 | from pydec.mesh.simplex import simplex,simplicial_mesh 6 | 7 | 8 | class test_simplicial_mesh(TestCase): 9 | def setUp(self): 10 | self.meshes = [] 11 | self.meshes.append((array([[0],[1]]),array([[0,1]]))) 12 | self.meshes.append((array([[0],[1],[2]]),array([[0,1],[1,2]]))) 13 | self.meshes.append((array([[0],[1],[2],[3]]),array([[0,1],[1,2],[2,3]]))) 14 | self.meshes.append((array([[0,0],[1,0],[0,1]]),array([[0,1],[1,2],[2,0]]))) 15 | self.meshes.append((array([[0,0],[1,0],[0,1]]),array([[0,1,2]]))) 16 | self.meshes.append((array([[0,0],[1,0],[0,1],[1,1]]),array([[0,1,2],[1,3,2]]))) 17 | self.meshes.append((array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]]),array([[0,1,2,3]]))) 18 | self.meshes.append((array([[0,0,0],[1,0,0],[0,1,0],[0,0,1],[1,1,1]]),array([[0,1,2,3],[1,2,3,4]]))) 19 | 20 | 21 | def check_boundary(self): 22 | """Test the boundary() method""" 23 | 24 | boundaries = [] 25 | boundaries.append([simplex([0],parity=1),simplex([1],parity=0)]) 26 | boundaries.append([simplex([0],parity=1),simplex([2],parity=0)]) 27 | boundaries.append([simplex([0],parity=1),simplex([3],parity=0)]) 28 | boundaries.append([]) 29 | boundaries.append([simplex([0,1]),simplex([1,2]),simplex([2,0])]) 30 | boundaries.append([simplex([0,1]),simplex([1,3]),simplex([3,2]),simplex([2,0])]) 31 | boundaries.append([simplex([1,0,2]),simplex([0,1,3]),simplex([2,0,3]),simplex([1,2,3])]) 32 | boundaries.append([simplex([1,0,2]),simplex([0,1,3]),simplex([2,0,3]),simplex([2,3,4]),simplex([1,4,3]),simplex([1,2,4])]) 33 | 34 | cases = zip(self.meshes,boundaries) 35 | 36 | for (v,s),b in cases: 37 | sm = simplicial_mesh(v,s) 38 | sb = sm.boundary() 39 | assert_equal(sb,set(b)) 40 | 41 | for x in sb: 42 | assert_equal(x.parity,b[b.index(x)].parity) 43 | 44 | 45 | 46 | def check_skeleton(self): 47 | """Test the skeleton() method""" 48 | 49 | assert_equal(simplicial_mesh(array([[0]]),array([[0]])).skeleton(0), \ 50 | set([simplex([0])])) 51 | assert_equal(simplicial_mesh(array([[0],[1]]),array([[0,1]])).skeleton(0), \ 52 | set([simplex([0]),simplex([1])])) 53 | assert_equal(simplicial_mesh(array([[0],[1]]),array([[0,1]])).skeleton(1), \ 54 | set([simplex([0,1])])) 55 | assert_equal(simplicial_mesh(array([[0],[1],[2]]),array([[0,1],[1,2]])).skeleton(0),\ 56 | set([simplex([0]),simplex([1]),simplex([2])])) 57 | assert_equal(simplicial_mesh(array([[0],[1],[2]]),array([[0,1],[1,2]])).skeleton(1),\ 58 | set([simplex([0,1]),simplex([1,2])])) 59 | assert_equal(simplicial_mesh(array([[0,0],[1,0],[0,1]]),array([[0,1,2]])).skeleton(1),\ 60 | set([simplex([0,1]),simplex([1,2]),simplex([2,0])])) 61 | 62 | 63 | -------------------------------------------------------------------------------- /pydec/pydec/mesh/tests/test_subdivision.py: -------------------------------------------------------------------------------- 1 | #import unittest 2 | # 3 | #from pydec.mesh import cube_grid,triangulate_ncube 4 | #from pydec.math.volume import unsigned_volume,signed_volume 5 | #from numpy import allclose 6 | # 7 | # 8 | #class TestSudivision(unittest.TestCase): 9 | # def setUp(self): 10 | # pass 11 | # 12 | # def test_triangulate_ncube(self): 13 | # for n in range(1,6): 14 | # v,i = cube_grid((1,)*n) 15 | # v,i = triangulate_ncube(v,i) 16 | # 17 | # unsigned_sum = 0 18 | # signed_sum = 0 19 | # for row in i: 20 | # unsigned_sum += unsigned_volume(v[row]) 21 | # signed_sum += signed_volume(v[row]) 22 | # 23 | # self.assert_(allclose(unsigned_sum,1)) 24 | # self.assert_(allclose(signed_sum,1)) 25 | # 26 | # 27 | -------------------------------------------------------------------------------- /pydec/pydec/testing/__init__.py: -------------------------------------------------------------------------------- 1 | # TODO: replace testing framework using pytest (?) since Tester from nose, via numpy is deprecated 2 | #from numpy.testing import Tester 3 | from numpy.testing import TestCase 4 | -------------------------------------------------------------------------------- /pydec/pydec/testing/__init__.py~: -------------------------------------------------------------------------------- 1 | #from numpy.testing import Tester 2 | from numpy.testing import TestCase 3 | -------------------------------------------------------------------------------- /pydec/pydec/util/__init__.py: -------------------------------------------------------------------------------- 1 | "General utility functions" 2 | 3 | from .info import __doc__ 4 | 5 | from .util import * 6 | 7 | __all__ = list(filter(lambda s:not s.startswith('_'),dir())) 8 | 9 | -------------------------------------------------------------------------------- /pydec/pydec/util/info.py: -------------------------------------------------------------------------------- 1 | """ 2 | STUB 3 | ============= 4 | 5 | """ 6 | 7 | postpone_import = 1 8 | -------------------------------------------------------------------------------- /pydec/pydec/util/tests/placeholder_tests.py: -------------------------------------------------------------------------------- 1 | # This is a placeholder file to make sure that the util/tests folder is commited. 2 | -------------------------------------------------------------------------------- /pydec/pydec/util/util.py: -------------------------------------------------------------------------------- 1 | __all__ = ['flatten'] 2 | 3 | 4 | def flatten(l, ltypes=(list, tuple)): 5 | """ 6 | Recursively flatten a list 7 | """ 8 | i = 0 9 | while i < len(l): 10 | if isinstance(l[i],ltypes) and not l[i]: # skip empty lists/tuples 11 | l.pop(i) 12 | continue 13 | while isinstance(l[i], ltypes): 14 | l[i:i+1] = list(l[i]) 15 | i += 1 16 | return l 17 | -------------------------------------------------------------------------------- /pydec/pydec/version.py: -------------------------------------------------------------------------------- 1 | version = '1.2.1' 2 | release=True 3 | -------------------------------------------------------------------------------- /pydec/pydec/vis/__init__.py: -------------------------------------------------------------------------------- 1 | from .draw import * 2 | -------------------------------------------------------------------------------- /pydec/pydec/vis/draw.py: -------------------------------------------------------------------------------- 1 | __all__ = ['triplot','lineplot','lineplot2','cube_quivers','simplex_quivers'] 2 | 3 | try: 4 | import matplotlib.collections 5 | import matplotlib.pylab 6 | except ImportError: 7 | import warnings 8 | warnings.warn("matplotlib not installed, some loss of functionality will result") 9 | 10 | 11 | from numpy import asarray,zeros,empty,average 12 | from pydec import barycentric_gradients,combinations,Simplex 13 | import numpy 14 | 15 | 16 | def triplot(vertices, indices, labels=False): 17 | """ 18 | Plot a 2D triangle mesh 19 | """ 20 | 21 | vertices,indices = asarray(vertices),asarray(indices) 22 | 23 | #3d tensor [triangle index][vertex index][x/y value] 24 | triangles = vertices[numpy.ravel(indices),:].reshape((indices.shape[0],3,2)) 25 | 26 | col = matplotlib.collections.PolyCollection(triangles) 27 | col.set_facecolor('grey') 28 | col.set_alpha(0.5) 29 | col.set_linewidth(1) 30 | 31 | #sub = subplot(111) 32 | sub = matplotlib.pylab.gca() 33 | sub.add_collection(col,autolim=True) 34 | matplotlib.pylab.axis('off') 35 | sub.autoscale_view() 36 | 37 | if labels: 38 | barycenters = numpy.average(triangles,axis=1) 39 | for n,bc in enumerate(barycenters): 40 | matplotlib.pylab.text(bc[0], bc[1], str(n), {'color' : 'k', 'fontsize' : 8, 41 | 'horizontalalignment' : 'center', 42 | 'verticalalignment' : 'center' 43 | }) 44 | 45 | #matplotlib.pylab.show() 46 | 47 | 48 | def lineplot2(tails,heads,labels=False,linewidths=1): 49 | #vertices,indices = asarray(vertices),asarray(indices) 50 | 51 | #3d tensor [segment index][vertex index][x/y value] 52 | #lines = vertices[numpy.ravel(indices),:].reshape((indices.shape[0],2,2)) 53 | 54 | data = empty((len(tails),2,2)) 55 | data[:,0,:] = tails 56 | data[:,1,:] = heads 57 | col = matplotlib.collections.LineCollection(data) 58 | col.set_color('k') 59 | col.set_linewidth(linewidths) 60 | 61 | #sub = subplot(111) 62 | sub = matplotlib.pylab.gca() 63 | sub.add_collection(col,autolim=True) 64 | matplotlib.pylab.axis('off') 65 | sub.autoscale_view() 66 | 67 | if labels: 68 | barycenters = numpy.average(lines,axis=1) 69 | for n,bc in enumerate(barycenters): 70 | matplotlib.pylab.text(bc[0], bc[1], str(n), {'color' : 'k', 'fontsize' : 8, 71 | 'horizontalalignment' : 'center', 72 | 'verticalalignment' : 'center' 73 | }) 74 | 75 | #matplotlib.pylab.show() 76 | 77 | 78 | def lineplot(vertices,indices,labels=False,linewidths=1): 79 | """ 80 | Plot 2D line segments 81 | """ 82 | vertices,indices = asarray(vertices),asarray(indices) 83 | 84 | #3d tensor [segment index][vertex index][x/y value] 85 | lines = vertices[numpy.ravel(indices),:].reshape((indices.shape[0],2,2)) 86 | 87 | col = matplotlib.collections.LineCollection(lines) 88 | col.set_color('k') 89 | col.set_linewidth(linewidths) 90 | 91 | #sub = subplot(111) 92 | sub = matplotlib.pylab.gca() 93 | sub.add_collection(col,autolim=True) 94 | matplotlib.pylab.axis('off') 95 | sub.autoscale_view() 96 | 97 | if labels: 98 | barycenters = numpy.average(lines,axis=1) 99 | for n,bc in enumerate(barycenters): 100 | matplotlib.pylab.text(bc[0], bc[1], str(n), {'color' : 'k', 'fontsize' : 8, 101 | 'horizontalalignment' : 'center', 102 | 'verticalalignment' : 'center' 103 | }) 104 | 105 | #matplotlib.pylab.show() 106 | 107 | 108 | 109 | def cube_quivers(cmplx,vals): 110 | N = cmplx.complex_dimension() 111 | quiver_dirs = zeros((cmplx[2].cube_array.shape[0],N)) 112 | 113 | edge_to_face = cmplx[2].boundary.T.tocsr() 114 | edge_to_face.data = numpy.abs(edge_to_face.data) 115 | 116 | num_edges = cmplx[1].cube_array.shape[0] 117 | 118 | for i in range(N): 119 | i_edges = (cmplx[1].cube_array[:,-1] == i) 120 | i_vals = zeros(num_edges) 121 | i_vals[i_edges] = vals[i_edges] 122 | 123 | quiver_dirs[:,i] = 0.5*(edge_to_face*i_vals) 124 | 125 | quiver_bases = cmplx[2].cube_array[:,:N] + 0.5 126 | 127 | return quiver_bases,quiver_dirs 128 | 129 | 130 | 131 | def simplex_quivers(sc,form): 132 | """ 133 | Sample a Whitney 1-form at simplex barycenters 134 | """ 135 | 136 | quiver_bases = average(sc.vertices[sc[-1].simplices],axis=1) 137 | quiver_dirs = zeros((sc[-1].num_simplices,sc.embedding_dimension())) 138 | 139 | s_to_i = sc[1].simplex_to_index 140 | 141 | for n,s in enumerate(sc[-1].simplices): 142 | verts = sorted(s) 143 | 144 | d_lambda = barycentric_gradients(sc.vertices[verts,:]) 145 | edges = [Simplex(x) for x in combinations(s,2)] 146 | indices = [s_to_i[x] for x in edges] 147 | values = [form[i] for i in indices] 148 | 149 | for e,v in zip(combinations(range(len(verts)),2),values): 150 | quiver_dirs[n,:] += v*(d_lambda[e[1]] - d_lambda[e[0]]) 151 | 152 | 153 | 154 | quiver_dirs /= (sc.complex_dimension() + 1) 155 | 156 | return quiver_bases,quiver_dirs 157 | 158 | 159 | 160 | ##from scipy import * 161 | ##from pydec import * 162 | ##from pylab import quiver,show 163 | 164 | ##v = array([[0,0],[1,0],[0,1]]) 165 | ##s = array([[0,1,2]]) 166 | 167 | ##sc = SimplicialComplex(v,s) 168 | 169 | ##b,d = simplex_quivers(sc,array([1.0,0.0,0.0])) 170 | 171 | 172 | ##quiver(b[:,0],b[:,1],d[:,0],d[:,1]) 173 | ##show() 174 | 175 | 176 | 177 | 178 | 179 | -------------------------------------------------------------------------------- /pydec/pydec/vis/info.py: -------------------------------------------------------------------------------- 1 | """ 2 | STUB 3 | ============= 4 | 5 | """ 6 | 7 | postpone_import = 1 8 | -------------------------------------------------------------------------------- /pydec/pydec/vis/tests/placeholder_tests.py: -------------------------------------------------------------------------------- 1 | # This is a placeholder file to make sure that the util/tests folder is commited. 2 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools", "wheel"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | 6 | [project] 7 | name = "pydec" 8 | #dynamic = ["version"] 9 | version = "1.2.1" 10 | description = "PyDEC: A Python Library for Discretizations of Exterior Calculus." 11 | #readme = {file = "README.rst", content-type = "text/x-rst"} 12 | authors = [ 13 | { name="Nathan Bell", email="wnbell@gmail.com" }, 14 | { name="Anil N. Hirani", email="anil.hirani@icloud.com" } 15 | ] 16 | maintainers = [ 17 | { name="Anil N. Hirani", email="anil.hirani@icloud.com" }, 18 | { name="Kaushik Kalyanaraman", email="kaushik@iiitd.ac.in" } 19 | ] 20 | requires-python = ">=3.9" 21 | keywords = ["DEC", "FEEC", "exterior calculus"] 22 | classifiers = [ 23 | "Development Status :: 5 - Production/Stable", 24 | "Intended Audience :: Developers", 25 | "Operating System :: OS Independent", 26 | "Programming Language :: Python", 27 | "Topic :: Scientific/Engineering :: Mathematics" 28 | ] 29 | 30 | [project.urls] 31 | "Homepage" = "https://github.com/hirani/pydec" 32 | "Bug Tracker" = "https://github.com/hirani/pydec/issues" 33 | 34 | #[tool.setuptools.dynamic] 35 | #version = {attr = "pydec.__version__"} 36 | 37 | #[tool.setuptools] 38 | #include-package-data = true 39 | 40 | [tool.setuptools.packages.find] 41 | where = ["pydec"] 42 | --------------------------------------------------------------------------------