treillis 1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
treillis/__init__.py ADDED
@@ -0,0 +1,26 @@
1
+ """"
2
+ Treillis
3
+ ========
4
+ """
5
+ from ._generation import makepacking, frompacking, call, Mesh
6
+ from ._generation import Domain, Lattice, Density, Density2, shake
7
+ from ._generation import savebins, saveascii, saveh5, loadLattice, last_pack
8
+ from ._generation import cutbrick, cutsphere, cutout, cut_2Dshape, isolate
9
+ from ._generation import mapping, flat_bounds, add_elements
10
+ from ._generation import vec_dist
11
+
12
+ import os
13
+ style = os.path.abspath(os.path.dirname(__file__)
14
+ + r'/display/PYLAT.mplstyle')
15
+
16
+
17
+
18
+
19
+ __all__ = ["makepacking", "frompacking", "call", "Mesh",
20
+ 'Domain', 'Lattice', 'Density', 'Density2', 'shake',
21
+ 'saveascii', 'loadLattice', 'savebins', 'saveascii', 'saveh5',
22
+ 'last_pack',
23
+ 'cutbrick', 'cutsphere', 'cutout', 'cut_2Dshape', 'isolate',
24
+ 'mapping', 'flat_bounds', 'add_elements',
25
+ 'style',
26
+ 'vec_dist',]
@@ -0,0 +1,20 @@
1
+ """treillis's base package, to generate and modify lattices."""
2
+
3
+ from .packings import makepacking, frompacking
4
+ from .meshes import call, Mesh
5
+ from .lattices import Domain, Lattice, Density, Density2, shake
6
+ from .io import savebins, saveascii, saveh5, loadLattice, last_pack
7
+ from .cutting import cutbrick, cutsphere, cutout, cut_2Dshape, isolate
8
+ from .redraw import mapping, flat_bounds, add_elements
9
+ from .utils import vec_dist
10
+
11
+ from . import cutting, io, lattices, meshes, packings, redraw, utils
12
+
13
+ __all__ = ["makepacking", "frompacking", "call", "Mesh",
14
+ 'Domain', 'Lattice', 'Density', 'Density2', 'shake',
15
+ 'savebins', 'saveascii', 'saveh5', 'loadLattice',
16
+ 'last_pack',
17
+ 'cutbrick', 'cutsphere', 'cutout', 'cut_2Dshape', 'isolate',
18
+ 'mapping', 'flat_bounds', 'add_elements',
19
+ 'vec_dist',
20
+ ]
@@ -0,0 +1,401 @@
1
+ """
2
+ Cutting
3
+ =======
4
+
5
+ The objective of this module is to facilitate slicing into lattices.
6
+ """
7
+
8
+ #%% Package importation
9
+
10
+ import os
11
+ from copy import deepcopy
12
+ import numpy as np
13
+ from tqdm import trange
14
+ import ipyparallel as ipp
15
+
16
+ from scipy.spatial import Delaunay
17
+
18
+ from .lattices import Lattice
19
+
20
+
21
+ __all__ = ['cutbrick', 'cutsphere', 'cutout', 'cut_2Dshape', 'isolate']
22
+
23
+ #%% Small functions to reuse
24
+
25
+ def keep_elem(elements: np.ndarray, I_kept: np.ndarray):
26
+ """Keep only the elements linked to the nodes in I_kept, and renumber them to corrsepond to the indices of I_kept."""
27
+ elems = deepcopy(elements)
28
+ keep = np.array([np.prod(np.isin(e, I_kept)) for e in elems[:,:2]],
29
+ dtype=bool)
30
+ elems = elems[keep]
31
+
32
+ # Renumbering the elements
33
+ for i in range(len(I_kept)):
34
+ if np.isin(I_kept[i], elems[:,:2]).any():
35
+ elems[elems[:,0]==I_kept[i],0] = i
36
+ elems[elems[:,1]==I_kept[i],1] = i
37
+ return elems
38
+
39
+ def keep_elem_prop(elements: np.ndarray, param: np.ndarray, I_kept: np.ndarray):
40
+ """Keep only the element property for the elements linked to the nodes in I_kept."""
41
+ param = deepcopy(param)
42
+ keep = np.array([np.prod(np.isin(e, I_kept)) for e in elements[:,:2]],
43
+ dtype=bool)
44
+ param = param[keep]
45
+ return param
46
+
47
+ def isin(point, shape, sx, sy):
48
+ """Consider a node in the plot if it is surrounded by at least 2 white points."""
49
+ x = np.linspace(-sx/2,sx/2,shape.shape[1])
50
+ y = np.linspace(-sy/2,sy/2,shape.shape[0])
51
+
52
+ Ix = np.argmin(abs(point[0]-x))
53
+ Iy = np.argmin(abs(point[1]-y))
54
+
55
+ x0 = max(Ix-1, 0)
56
+ xf = min(Ix+1, shape.shape[1])
57
+
58
+ y0 = max(Iy-1, 0)
59
+ yf = min(Iy+1, shape.shape[0])
60
+
61
+ return np.sum(shape[y0:yf, x0:xf])>1
62
+
63
+ #%% Main functions
64
+ #%%% Basic shapes
65
+
66
+ def cutbrick(lattice: Lattice, xlims: list|None = None,
67
+ ylims: list|None = None, zlims: list|None = None):
68
+ """Create a sliced lattice from within limits (=[[x_min,x_max], [y_min,y_max], [z_min,z_max]])."""
69
+ nodes = deepcopy(lattice.nodes)
70
+ elems = deepcopy(lattice.elems)
71
+ I = np.arange(len(nodes))
72
+
73
+ if xlims is None: xlims = [-np.inf, np.inf]
74
+ if ylims is None: ylims = [-np.inf, np.inf]
75
+
76
+ if xlims[0]<-lattice.sizedom[0]/2 and xlims[1]>lattice.sizedom[0]/2\
77
+ and ylims[0]<-lattice.sizedom[1]/2 and ylims[1]>lattice.sizedom[1]/2:
78
+ if zlims is None: return lattice
79
+ elif zlims[0]<-lattice.sizedom[2]/2\
80
+ and zlims[1]>lattice.sizedom[2]/2: return lattice
81
+
82
+ # For safety: we recalculate the limits to be within the lattice
83
+ xlims = [max(xlims[0], -lattice.sizedom[0]/2),
84
+ min(xlims[1], lattice.sizedom[0]/2)]
85
+ ylims = [max(ylims[0], -lattice.sizedom[1]/2),
86
+ min(ylims[1], lattice.sizedom[1]/2)]
87
+
88
+
89
+ Ix = I[(nodes[:,0]>=xlims[0]) & (nodes[:,0]<=xlims[1])]
90
+ Iy = I[(nodes[:,1]>=ylims[0]) & (nodes[:,1]<=ylims[1])]
91
+
92
+ Ii = np.intersect1d(Ix, Iy)
93
+
94
+ if zlims is not None and lattice.dim==3:
95
+ zlims = [max(zlims[0], -lattice.sizedom[2]/2),
96
+ min(zlims[1], lattice.sizedom[2]/2)]
97
+ Iz = I[(nodes[:,2]>=zlims[0]) & (nodes[:,2]<=zlims[1])]
98
+ If = np.intersect1d(Ii, Iz)
99
+ else: If = Ii
100
+
101
+ # Redefining the parameters corresponding to the elems
102
+ ratio = keep_elem_prop(lattice.elems, lattice.aspect_ratio, If)
103
+ if isinstance(lattice.beam_section, np.ndarray):
104
+ section = keep_elem_prop(lattice.elems, lattice.beam_section, If)
105
+ else: section = lattice.beam_section
106
+
107
+ elems = keep_elem(elems, If)
108
+ return Lattice(lattice.nodes[If,:], elems, length=lattice.L,
109
+ inimesh=lattice.type, beam_section=section,
110
+ aspect_ratio=ratio)
111
+
112
+
113
+ def cutsphere(lattice: Lattice, pos: np.ndarray = None, rlim: float = None):
114
+ """Cut the lattice in a spherical shape at the given position and radius."""
115
+ if pos is not None:
116
+ pos = np.array(pos).reshape((1, lattice.dim))
117
+ else: pos = np.zeros_like(lattice.nodes)
118
+ nodes = deepcopy(lattice.nodes)
119
+ nodes -= pos
120
+ elems = deepcopy(lattice.elems)
121
+ I = np.arange(len(nodes))
122
+
123
+ if rlim is not None:
124
+ lmax = np.max(np.sqrt(np.sum(nodes**2, axis=1)))
125
+ if rlim<=lmax:
126
+ cond = np.sqrt(np.sum(nodes**2, axis=1))<=rlim
127
+ If = I[cond]
128
+
129
+ # Selecting the ROI
130
+ nodes = nodes[If,:]
131
+
132
+ # Redefining the parameters corresponding to the elems
133
+ ratio = keep_elem_prop(elems, lattice.aspect_ratio, If)
134
+ if isinstance(lattice.beam_section, np.ndarray):
135
+ section = keep_elem_prop(elems, lattice.beam_section, If)
136
+ else: section = lattice.beam_section
137
+
138
+ elems = keep_elem(elems, If)
139
+ return Lattice(nodes, elems, length=lattice.L,
140
+ inimesh=lattice.type, beam_section=section,
141
+ aspect_ratio=ratio, size = np.array([rlim]))
142
+
143
+ print("There is no change in the lattice.")
144
+ return lattice
145
+
146
+
147
+ def cutout(lattice: Lattice, position: np.ndarray, size: float|np.ndarray):
148
+ """
149
+ Cut out a brick or a spherical shape in the lattice.
150
+
151
+ Parameters
152
+ ----------
153
+ lattice : Lattice
154
+ Lattice to cut out.
155
+ position : np.ndarray
156
+ Position of the cut. Center of the sphere, or lower back left side of the brick.
157
+ size : float|np.ndarray
158
+ If only one value, radius of the sphere, if more values, respectively lengths along x, y, and z of the brick cutout.
159
+
160
+ Returns
161
+ -------
162
+ Lattice
163
+ Lattice with shape cut out.
164
+
165
+ Examples
166
+ --------
167
+ Cut out the center of a spherical lattice.
168
+
169
+ >>> import treillis
170
+ >>> lat = treillis.Lattice.initialize(10, 1, 'TriDT3D')
171
+ >>> cutlat = treillis.cutout(lat, [0,0,0], 8)
172
+ """
173
+ nodes = deepcopy(lattice.nodes)
174
+ elems = deepcopy(lattice.elems)
175
+ elmpos = np.concatenate((
176
+ nodes[elems[:,0].astype(int)]\
177
+ .reshape(elems.shape[0],lattice.dim,1),
178
+ nodes[elems[:,1].astype(int)]\
179
+ .reshape(elems.shape[0],lattice.dim,1)), axis=2)
180
+ shape = 'brick'
181
+
182
+ try: len(position) == lattice.dim
183
+ except: raise ValueError("Initial position cannot be a float.")
184
+ position = np.array(position)
185
+ if len(position) != lattice.dim:
186
+ raise ValueError("Lattice is ", lattice.dim,
187
+ "D, but initial position is ", len(position),
188
+ "D.")
189
+
190
+ if not isinstance(size, (list, np.ndarray)): shape = 'sphere'
191
+ elif len(size) == 1: shape = 'sphere'
192
+
193
+ I = np.arange(elmpos.shape[0])
194
+
195
+ if shape=='sphere':
196
+ cond = (np.sqrt(np.sum((elmpos[:,:,0]-position)**2, axis=1)) <= size)\
197
+ + (np.sqrt(np.sum((elmpos[:,:,1]-position)**2, axis=1)) <= size)
198
+ cond = cond.astype(bool)
199
+ else:
200
+ condx = (elmpos[:,0,:] >= position[0])\
201
+ * (elmpos[:,0,:]-position[0] < size[0])
202
+ condy = (elmpos[:,1,:] >= position[1])\
203
+ * (elmpos[:,1,:]-position[1] < size[1])
204
+
205
+ cond = condx * condy
206
+
207
+ if len(size) == 3:
208
+ if lattice.dim == 3:
209
+ condz = (elmpos[:,2,:] >= position[2])\
210
+ * (elmpos[:,2,:]-position[2] < size[2])
211
+ cond = cond * condz
212
+ else:
213
+ print("Warning: size is in 3D, but lattice is only 2D: third dimension will be ignored.")
214
+
215
+ cond = np.sum(cond, axis=1, dtype=bool)
216
+
217
+ if len(size) < len(position):
218
+ S = np.zeros(position.shape)
219
+ S[:len(size)] = size
220
+ size = S
221
+ else: size = np.array(size)
222
+
223
+ trans = (elmpos[:,:,0] < np.repeat(position.reshape(1, lattice.dim),
224
+ elmpos.shape[0], axis=0))\
225
+ * (elmpos[:,:,1] >= np.repeat(position.reshape(1, lattice.dim),
226
+ elmpos.shape[0], axis=0)\
227
+ + np.repeat(size.reshape(1, lattice.dim),
228
+ elmpos.shape[0], axis=0))
229
+ trans = np.sum(trans, axis=1, dtype=bool)
230
+
231
+ cond = cond + trans
232
+
233
+ keep = np.delete(I, cond)
234
+ elmpos = elmpos[keep]
235
+
236
+ ratio = lattice.aspect_ratio[keep]
237
+ if isinstance(lattice.beam_section, np.ndarray):
238
+ section = lattice.beam_section[keep]
239
+ else: section = lattice.beam_section
240
+
241
+ nodes = np.concatenate((elmpos[:,:,0], elmpos[:,:,1]), axis=0)
242
+ nodes = np.unique(nodes, axis=0)
243
+
244
+ Inodes = np.arange(nodes.shape[0])
245
+
246
+ elems = np.concatenate((
247
+ np.array([[Inodes[np.prod(nodes == e, axis=1, dtype=bool)]]
248
+ for e in elmpos[:,:,0]]),
249
+ np.array([[Inodes[np.prod(nodes == e, axis=1, dtype=bool)]]
250
+ for e in elmpos[:,:,1]])), axis=1)
251
+ elems = elems.reshape(elems.shape[0], 2)
252
+
253
+ return Lattice(nodes, elems, length=lattice.L,
254
+ inimesh=lattice.type, beam_section=section,
255
+ aspect_ratio=ratio, size = lattice.sizedom)
256
+
257
+
258
+
259
+
260
+ def isolate(lattice: Lattice):
261
+ """Clusterize a cut lattice in a list of sub-lattices."""
262
+ node_list = np.arange(lattice.nodes.shape[0])
263
+ Lattices = []
264
+
265
+ contacts = lattice.contacts_list
266
+
267
+ print("Getting the separate clusters")
268
+ while len(node_list) > 0:
269
+ c0 = node_list[0]
270
+ Lattices.append(np.array([c0]))
271
+ l = contacts[c0]
272
+ l = l[np.isin(l, Lattices[-1], invert=True)]
273
+ while len(l>0):
274
+ Lattices[-1] = np.concatenate((Lattices[-1], l))
275
+ Lattices[-1] = np.unique(Lattices[-1])
276
+
277
+ l = np.concatenate([contacts[i] for i in l])
278
+ l = np.unique(l)
279
+ l = l[np.isin(l, Lattices[-1], invert=True)]
280
+
281
+ print('Found', len(Lattices), 'clusters')
282
+ node_list = node_list[np.isin(node_list, Lattices[-1],
283
+ invert=True)]
284
+
285
+ if len(Lattices)==1:
286
+ print("No sub-lattice found")
287
+ return lattice
288
+
289
+ print("Creating the sub-lattices")
290
+ lat_list = []
291
+ for i in trange(len(Lattices)):
292
+ idx = Lattices[i]
293
+ mask_elm = np.prod(np.isin(
294
+ lattice.elems[:,:2], idx), axis=1, dtype=bool)
295
+
296
+ elm = keep_elem(lattice.elems, idx)
297
+
298
+ if type(lattice.beam_section)!=str:
299
+ beam = lattice.beam_section[mask_elm]
300
+ else: beam = lattice.beam_section
301
+
302
+ lat = Lattice(lattice.nodes[idx],
303
+ elm,
304
+ size=lattice.sizedom, length=lattice.L,
305
+ inimesh=lattice.type,
306
+ aspect_ratio=lattice.aspect_ratio[mask_elm],
307
+ beam_section=beam)
308
+
309
+ lat_list.append(lat)
310
+
311
+ return lat_list
312
+
313
+
314
+ #%%% Fun shapes
315
+ def cut_2Dshape(lattice: Lattice, shape: np.ndarray,
316
+ sx: float = None, sy: float = None):
317
+ """
318
+ Cutout a 2D shape described by a black-and-white image in a lattice.
319
+
320
+ By convention, the shape will be cutout in the XY plane.
321
+
322
+ Parameters
323
+ ----------
324
+ lattice : Lattice
325
+ The lattice you want to cut
326
+ shape : 2D np.ndarray
327
+ A black and white image encoded in 0s and 1s. By convention, the nodes
328
+ that are kept are in the
329
+ sx : float, optional
330
+ the size along the x-axis of the image. If not given, will be the
331
+ width of the lattice.
332
+ sy: float, optional
333
+ the size along the y-axis of the image. If not given, will be the height
334
+ of the lattice.
335
+
336
+ Returns
337
+ -------
338
+ Lattice
339
+
340
+ Examples
341
+ --------
342
+ Cutting a star-shape in a lattice.
343
+
344
+ >>> import numpy as np
345
+ >>> import treillis
346
+ >>> # create a lattice
347
+ >>> lat = treillis.Lattice.initialize([50, 50], 2, 'Vor', des=.1)
348
+ >>> # create the cutting template
349
+ >>> star = np.array([
350
+ >>> [0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0],
351
+ >>> [0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0],
352
+ >>> [0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0],
353
+ >>> [0,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,0,0],
354
+ >>> [0,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,0,0],
355
+ >>> [0,0,0,0,0,0,0,1,1,1,1,1,1,1,0,0,0,0,0,0,0],
356
+ >>> [0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0],
357
+ >>> [0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0],
358
+ >>> [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
359
+ >>> [0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0],
360
+ >>> [0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0],
361
+ >>> [0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0],
362
+ >>> [0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0],
363
+ >>> [0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0],
364
+ >>> [0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0],
365
+ >>> [0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0],
366
+ >>> [0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0],
367
+ >>> [0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0],
368
+ >>> [0,0,0,0,1,1,1,1,1,1,0,1,1,1,1,1,1,0,0,0,0],
369
+ >>> [0,0,0,1,1,1,1,1,0,0,0,0,0,1,1,1,1,1,0,0,0],
370
+ >>> [0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0]])
371
+ >>> starlat = treillis.cut_2Dshape(lat, star)
372
+
373
+ Because of the way the node position is implemented, the star will be
374
+ upside-down in the lattice.
375
+ """
376
+ nodes = deepcopy(lattice.nodes)
377
+ elems = deepcopy(lattice.elems)
378
+ if sx==0 or sy==0: raise ValueError("Cannot cut a 1D line in a lattice.")
379
+ if sx is None: sx = np.max(lattice.nodes[:,0])-np.min(lattice.nodes[:,0])
380
+ if sy is None: sy = np.max(lattice.nodes[:,1])-np.min(lattice.nodes[:,1])
381
+
382
+ I = np.arange(len(lattice.nodes))
383
+
384
+ I_ok = []
385
+ for i in range(len(lattice.nodes)):
386
+ if isin(lattice.nodes[i,:2], shape, sx, sy): I_ok.append(I[i])
387
+
388
+ nodes = nodes[I_ok,:]
389
+ # Redefining the parameters corresponding to the elems
390
+ ratio = keep_elem_prop(elems, lattice.aspect_ratio, I_ok)
391
+ if isinstance(lattice.beam_section, np.ndarray):
392
+ section = keep_elem_prop(elems, lattice.beam_section, I_ok)
393
+ else: section = lattice.beam_section
394
+
395
+ elems = keep_elem(elems, I_ok)
396
+ return Lattice(nodes, elems, length=lattice.L,
397
+ inimesh=lattice.type, beam_section=section,
398
+ aspect_ratio=ratio)
399
+
400
+
401
+