Skip to content

Commit f529bb7

Browse files
authored
Generation bug fix (#163)
* updated pybind11 * small setup cleanup * WIP face oriented edges * bibliography * version bump and bug fix in generation
1 parent 34d569e commit f529bb7

File tree

10 files changed

+2339
-2392
lines changed

10 files changed

+2339
-2392
lines changed

doc/bibliography/tyssue.bib

Lines changed: 2188 additions & 2385 deletions
Large diffs are not rendered by default.

setup.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@
2929
## Thanks to them!
3030
MAJOR = 0
3131
MINOR = 6
32-
MICRO = 1
33-
ISRELEASED = False
32+
MICRO = 2
33+
ISRELEASED = True
3434
VERSION = "%d.%d.%s" % (MAJOR, MINOR, MICRO)
3535

3636

tests/dynamics/test_effectors.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
LineTension,
88
SurfaceTension,
99
LengthElasticity,
10+
PerimeterElasticity,
1011
FaceContractility,
1112
FaceAreaElasticity,
1213
FaceVolumeElasticity,
@@ -17,6 +18,7 @@
1718

1819
sheet_effectors = [
1920
LengthElasticity,
21+
PerimeterElasticity,
2022
FaceAreaElasticity,
2123
FaceVolumeElasticity,
2224
LineTension,

tests/generation/test_shapes.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,3 +85,4 @@ def test_spherical_mono():
8585
mean_basal = rho[organo.vert_df["segment"] == "basal"].mean()
8686
np.testing.assert_almost_equal(mean_apical, radius, decimal=1)
8787
np.testing.assert_almost_equal(mean_basal, radius + height, decimal=1)
88+
assert np.all(organo.cell_df["vol"] > 0)

tests/geometry/test_sheet_geometry.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import os
2+
import numpy as np
23
import pandas as pd
34

5+
46
from tyssue import config, Sheet, SheetGeometry
57
from tyssue.generation import three_faces_sheet
68
from tyssue.io.hdf5 import load_datasets
@@ -50,3 +52,18 @@ def test_rod_update_height():
5052
SheetGeometry.update_all(sheet)
5153

5254
assert (sheet.vert_df.rho.mean() - 0.96074585429756632) ** 2 < TOLERANCE
55+
56+
57+
def test_get_phis():
58+
pth = os.path.join(stores_dir, "rod_sheet.hf5")
59+
datasets = load_datasets(pth)
60+
specs = config.geometry.rod_sheet()
61+
sheet = Sheet("rod", datasets, specs)
62+
SheetGeometry.update_all(sheet)
63+
sheet.edge_df["sphi"] = SheetGeometry.get_phis(sheet)
64+
65+
assert np.all(
66+
sheet.edge_df.sort_values(["face", "sphi"])
67+
.groupby("face")
68+
.apply(lambda df: np.roll(df["trgt"], 1) == df["srce"])
69+
)

tyssue/dynamics/effectors.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,46 @@ def gradient(eptm):
118118
return -grad, grad
119119

120120

121+
# From Mapeng Bi et al. https://doi.org/10.1038/nphys3471
122+
class PerimeterElasticity(AbstractEffector):
123+
124+
dimensions = units.line_elasticity
125+
magnitude = "perimeter_elasticity"
126+
label = "Perimeter Elasticity"
127+
element = "face"
128+
specs = {
129+
"face": {
130+
"is_alive": 1,
131+
"perimeter": 1.0,
132+
"perimeter_elasticity": 0.1,
133+
"prefered_perimeter": 3.81,
134+
}
135+
}
136+
137+
spatial_ref = "prefered_perimeter", units.length
138+
139+
@staticmethod
140+
def energy(eptm):
141+
return eptm.face_df.eval(
142+
"0.5 * is_alive"
143+
"* perimeter_elasticity"
144+
"* (perimeter - prefered_perimeter)** 2"
145+
)
146+
147+
@staticmethod
148+
def gradient(eptm):
149+
150+
gamma_ = eptm.face_df.eval(
151+
"perimeter_elasticity * is_alive" "* (perimeter - prefered_perimeter)"
152+
)
153+
gamma = eptm.upcast_face(gamma_)
154+
155+
grad_srce = -eptm.edge_df[eptm.ucoords] * to_nd(gamma, len(eptm.coords))
156+
grad_srce.columns = ["g" + u for u in eptm.coords]
157+
grad_trgt = -grad_srce
158+
return grad_srce, grad_trgt
159+
160+
121161
class FaceAreaElasticity(AbstractEffector):
122162

123163
dimensionless = False

tyssue/generation/modifiers.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,8 @@ def extrude(apical_datasets, method="homotecy", scale=0.3, vector=[0, 0, -1]):
138138
if not col in datasets[elem]:
139139
datasets[elem][col] = value
140140

141+
if (method == "normals") and (scale < 0):
142+
datasets["edge"][["srce", "trgt"]] = datasets["edge"][["trgt", "srce"]]
141143
return datasets
142144

143145

tyssue/geometry/planar_geometry.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import numpy as np
2-
32
from .base_geometry import BaseGeometry
43

54

tyssue/geometry/sheet_geometry.py

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import pandas as pd
33

44
from .planar_geometry import PlanarGeometry
5-
from .utils import rotation_matrix
5+
from .utils import rotation_matrix, rotation_matrices
66

77

88
class SheetGeometry(PlanarGeometry):
@@ -186,8 +186,8 @@ def face_rotation(sheet, face, psi=0):
186186
else:
187187
return np.dot(rotation_matrix(psi, [0, 0, 1]), r1)
188188

189-
@classmethod
190-
def face_projected_pos(cls, sheet, face, psi=0):
189+
@staticmethod
190+
def face_projected_pos(sheet, face, psi=0):
191191
"""Returns the position of a face vertices projected on a plane
192192
perpendicular to the face normal, and translated so that the face
193193
center is at the center of the coordinate system
@@ -216,7 +216,7 @@ def face_projected_pos(cls, sheet, face, psi=0):
216216
sheet.vert_df.loc[face_orbit.values, sheet.coords].values
217217
- sheet.face_df.loc[face, sheet.coords].values
218218
)
219-
u, s, rotation = np.linalg.svd(rel_pos.astype(np.float), full_matrices=False)
219+
_, _, rotation = np.linalg.svd(rel_pos.astype(np.float), full_matrices=False)
220220
# rotation = cls.face_rotation(sheet, face, psi=psi)
221221
if psi != 0:
222222
rotation = np.dot(rotation_matrix(psi, [0, 0, 1]), rotation)
@@ -225,6 +225,49 @@ def face_projected_pos(cls, sheet, face, psi=0):
225225
)
226226
return rot_pos
227227

228+
@staticmethod
229+
def face_rotations(sheet):
230+
"""Returns the (sheet.Ne, 3, 3) array of rotation matrices
231+
such that each rotation aligns the coordinate system along face normals
232+
233+
"""
234+
normals = sheet.edge_df.groupby("face")[sheet.ncoords].mean()
235+
normals = normals / np.linalg.norm(normals, axis=0)
236+
normals = sheet.upcast_face(normals)
237+
238+
n_xy = np.linalg.norm(normals[["nx", "ny"]])
239+
theta = -np.arctan2(n_xy, normals.nz)
240+
241+
direction = np.array(
242+
[normals.ny.to_numpy(), -normals.nx.to_numpy(), np.zeros(sheet.Ne)]
243+
).T
244+
rots = rotation_matrices(theta, direction)
245+
246+
return rots
247+
248+
@classmethod
249+
def get_phis(cls, sheet):
250+
"""Returns the 'latitude' of the vertices in the plane perpendicular
251+
to each face normal. For not-too-deformed faces, sorting vertices by this
252+
gives clockwize orientation.
253+
254+
I think not-too-deformed means starconvex here.
255+
256+
"""
257+
rots = cls.face_rotations(sheet)
258+
259+
rel_srce_pos = (
260+
sheet.edge_df[["sx", "sy", "sz"]]
261+
- sheet.edge_df[["fx", "fy", "fz"]].to_numpy()
262+
).to_numpy()
263+
rotated = np.einsum("ikj, ik -> ij", rots, rel_srce_pos)
264+
return np.arctan2(rotated[:, 1], rotated[:, 0])
265+
266+
@classmethod
267+
def sort_oriented_edges(cls, sheet):
268+
phis = cls.get_phis(sheet)
269+
sheet.edge_df = sheet.edge_df.sort_values(["face", phis]).reset_index()
270+
228271

229272
class ClosedSheetGeometry(SheetGeometry):
230273
"""Geometry for a closed 2.5D sheet.

tyssue/geometry/utils.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,43 @@ def rotation_matrix(angle, direction):
7474
]
7575
)
7676
return R
77+
78+
79+
def rotation_matrices(angle, direction):
80+
""" Return an (N, 3, 3) array of rotation matrices
81+
along N angles and N directions
82+
83+
84+
85+
Parameters
86+
----------
87+
angle : np.ndarray of shape (N,)
88+
array of rotation angles
89+
90+
directions : np.ndarray of shape (N, 3)
91+
array of rotation vectors
92+
93+
Returns
94+
-------
95+
rots : np.ndarray of shape (N, 3, 3)
96+
the array of rotation matrices
97+
"""
98+
direction = direction / np.linalg.norm(direction, axis=1)[:, None]
99+
100+
sint, cost = np.sin(angle), np.cos(angle)
101+
102+
rots = np.zeros((cost.size, 3, 3))
103+
for i in range(3):
104+
rots[:, i, i] = cost
105+
rots += np.einsum("ij, ik -> ijk", direction, direction) * (1 - cost[:, None, None])
106+
107+
direction *= sint[:, None]
108+
109+
rots[:, 0, 1] -= direction[:, 2]
110+
rots[:, 0, 2] += direction[:, 1]
111+
rots[:, 1, 0] += direction[:, 2]
112+
rots[:, 1, 2] -= direction[:, 0]
113+
rots[:, 2, 0] -= direction[:, 1]
114+
rots[:, 2, 1] += direction[:, 0]
115+
116+
return rots

0 commit comments

Comments
 (0)