Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,9 @@ dist/

.idea/
src/tyssue/_version.py

# closure-test rendered videos
tests/behaviors/output/

# notebook-generated demo media
notebooks/closure_demo_output/
3 changes: 3 additions & 0 deletions src/tyssue/behaviors/sheet/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ def merge_vertices(sheet):
collapse_edge(sheet, short[0], allow_two_sided=False)
short = sheet.edge_df[sheet.edge_df["length"] < d_min].index.to_numpy()
np.random.shuffle(short)

sheet.network_changed = True

return 0


Expand Down
109 changes: 59 additions & 50 deletions src/tyssue/core/history.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@ class History:
"""

def __init__(
self,
sheet,
save_every=None,
dt=None,
save_only=None,
extra_cols=None,
save_all=True,
self,
sheet,
save_every=None,
dt=None,
save_only=None,
extra_cols=None,
save_all=True
):
"""Creates a `SheetHistory` instance.

Expand All @@ -60,14 +60,14 @@ def __init__(
"""
if extra_cols is not None:
warnings.warn(
"extra_cols and save_all parameters are deprecated."
" Use save_only instead. "
)
"extra_cols and save_all parameters are deprecated. Use save_only instead. ")

extra_cols = {
k: list(sheet.datasets[k].columns) for k in sheet.datasets
}

if save_only is not None:
extra_cols = defaultdict(list, **save_only)
else:
extra_cols = {k: list(sheet.datasets[k].columns) for k in sheet.datasets}
extra_cols = defaultdict(list, **extra_cols)

self.sheet = sheet

Expand All @@ -80,41 +80,46 @@ def __init__(
self.save_every = None

self.datasets = {}
self.dicts = {}
self.columns = {}
vcols = sheet.coords + extra_cols["vert"]
vcols = list(set(vcols))
self.vcols = _filter_columns(vcols, sheet.vert_df.columns, "vertex")
_vert_h = sheet.vert_df[self.vcols].reset_index(drop=False)
if "time" not in self.vcols:
if not "time" in self.vcols:
_vert_h["time"] = 0
self.datasets["vert"] = _vert_h
self.dicts["vert"] = {}
self.columns["vert"] = self.vcols

fcols = extra_cols["face"]
self.fcols = _filter_columns(fcols, sheet.face_df.columns, "face")
_face_h = sheet.face_df[self.fcols].reset_index(drop=False)
if "time" not in self.fcols:
if not "time" in self.fcols:
_face_h["time"] = 0
self.datasets["face"] = _face_h
self.dicts["face"] = {}
self.columns["face"] = self.fcols

if sheet.cell_df is not None:
ccols = extra_cols["cell"]
self.ccols = _filter_columns(ccols, sheet.cell_df.columns, "cell")
_cell_h = sheet.cell_df[self.ccols].reset_index(drop=False)
if "time" not in self.ccols:
if not "time" in self.ccols:
_cell_h["time"] = 0
self.datasets["cell"] = _cell_h
self.dicts["cell"] = {}
self.columns["cell"] = self.ccols
extra_cols["edge"].append("cell")

ecols = ["srce", "trgt", "face"] + extra_cols["edge"]
ecols = list(set(ecols))
self.ecols = _filter_columns(ecols, sheet.edge_df.columns, "edge")
_edge_h = sheet.edge_df[self.ecols].reset_index(drop=False)
if "time" not in self.ecols:
if not "time" in self.ecols:
_edge_h["time"] = 0
self.datasets["edge"] = _edge_h
self.dicts["edge"] = {}
self.columns["edge"] = self.ecols

def __len__(self):
Expand Down Expand Up @@ -168,46 +173,52 @@ def record(self, time_stamp=None):
self.time += 1

if (self.save_every is None) or (
self.index % (int(self.save_every / self.dt)) == 0
self.index % (int(self.save_every / self.dt)) == 0
):
for element in self.datasets:
hist = self.datasets[element]
cols = self.columns[element]
df = self.sheet.datasets[element][cols].reset_index(drop=False)
if "time" not in cols:
times = pd.Series(np.ones((df.shape[0],)) * self.time, name="time")
df = pd.concat([df, times], ignore_index=False, axis=1, sort=False)
else:
df["time"] = self.time

if self.time in hist["time"]:
# erase previously recorded time point
hist = hist[hist["time"] != self.time]
# if "time" not in cols:
# times = pd.Series(np.ones((df.shape[0],)) * self.time, name="time")
# df = pd.concat([df, times], ignore_index=False, axis=1, sort=False)
# else:
df["time"] = self.time

hist = pd.concat([hist, df], ignore_index=True, axis=0, sort=False)
# if self.time in hist["time"]:
# # erase previously recorded time point
# hist = hist[hist["time"] != self.time]

self.datasets[element] = hist
self.dicts[element].update({f"{self.time}": df})

self.index += 1

def update_datasets(self):
"""Concatenate all datasets in self.datasets into self.datasets as pd.DataFrame objects
"""
for element in self.sheet.datasets:
self.datasets[element] = pd.concat(self.dicts[element].values(), ignore_index=True)

def retrieve(self, time):
"""Return datasets at time `time`.

If a specific dataset was not recorded at time time,
the closest record before that time is used.
"""
if time > self.datasets["vert"]["time"].values[-1]:
times = [float(_time) for _time in self.dicts["vert"].keys()]

if time > max(times):
warnings.warn(
"""
The time argument you requested is bigger than the maximum recorded time,
are you sure you passed the time stamp as parameter, and not an index ?
"""
)
t = times[np.argmin([np.abs(t1 - time) for t1 in times])]
sheet_datasets = {}
for element in self.datasets:
hist = self.datasets[element]
df = self.dicts[element][f"{t}"]
cols = self.columns[element]
df = _retrieve(hist, time)
df = df.set_index(element)[cols]
sheet_datasets[element] = df

Expand All @@ -229,15 +240,15 @@ def slice(self, start=0, stop=None, size=None, endpoint=True):
"""
if size is not None:
if stop is not None:
time_stamps = self.time_stamps[start : stop + int(endpoint)]
time_stamps = self.time_stamps[start: stop + int(endpoint)]
else:
time_stamps = self.time_stamps
indices = np.round(
np.linspace(0, time_stamps.size + 1, size, endpoint=True)
).astype(int)
times = time_stamps.take(indices.clip(max=time_stamps.size - 1))
elif stop is not None:
times = self.time_stamps[start : stop + int(endpoint)]
times = self.time_stamps[start: stop + int(endpoint)]
else:
times = self.time_stamps
return times
Expand Down Expand Up @@ -270,13 +281,13 @@ class HistoryHdf5(History):
"""

def __init__(
self,
sheet=None,
save_every=None,
dt=None,
save_only=None,
hf5file="",
overwrite=False,
self,
sheet=None,
save_every=None,
dt=None,
save_only=None,
hf5file="",
overwrite=False,
):
"""Creates a `HistoryHdf5` instance.

Expand Down Expand Up @@ -373,11 +384,10 @@ def time_stamps(self, element="vert"):
with pd.HDFStore(self.hf5file, "r") as file:
self._time_stamps = file.select("vert", columns=["time"])[
"time"
].unique()
].unique()

return self._time_stamps


def record(self, time_stamp=None, sheet=None):
"""Appends a copy of the sheet datasets to the history HDF file.

Expand All @@ -399,7 +409,6 @@ def record(self, time_stamp=None, sheet=None):
# invalidate _time_stamp cache:
self._time_stamps = np.empty((0,))


dtypes_ = {k: df.dtypes for k, df in self.sheet.datasets.items()}

for element, df in self.sheet.datasets.items():
Expand All @@ -418,7 +427,7 @@ def record(self, time_stamp=None, sheet=None):
)

if (self.save_every is None) or (
self.index % (int(self.save_every / self.dt)) == 0
self.index % (int(self.save_every / self.dt)) == 0
):
for element, df in self.sheet.datasets.items():
times = pd.Series(np.ones((df.shape[0],)) * self.time, name="time")
Expand All @@ -429,11 +438,11 @@ def record(self, time_stamp=None, sheet=None):
kwargs["min_itemsize"] = {"segment": 8}
with pd.HDFStore(self.hf5file, "a") as store:
if (
element in store
and store.select(element, where=f"time == {self.time}")[
"time"
].shape[0]
> 0
element in store
and store.select(element, where=f"time == {self.time}")[
"time"
].shape[0]
> 0
):
store.remove(key=element, where=f"time == {self.time}")
store.append(key=element, value=df, **kwargs)
Expand Down
8 changes: 6 additions & 2 deletions src/tyssue/core/objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -663,7 +663,11 @@ def face_polygons(self, coords=None):
for c in coords:
self.edge_df["s" + c] = self.upcast_srce(self.vert_df[c])

polys = self.edge_df.groupby("face").apply(lambda df: df[scoords].to_numpy())
polys = (
self.edge_df[scoords + ["face"]]
.groupby("face")[scoords]
.apply(lambda df: df.to_numpy())
)
return polys

def validate(self):
Expand All @@ -677,7 +681,7 @@ def get_valid(self):
"""Set the 'is_valid' column to true if the faces are all closed polygons,
and the cells closed polyhedra.
"""
is_valid_face = self.edge_df.groupby("face").apply(_test_valid)
is_valid_face = self.edge_df.groupby("face").apply(_test_valid, include_groups=True)
is_valid = self.upcast_face(is_valid_face)
if "cell" in self.data_names:
is_valid_cell = self.edge_df.groupby("cell").apply(_is_closed_cell)
Expand Down
49 changes: 30 additions & 19 deletions src/tyssue/core/sheet.py
Original file line number Diff line number Diff line change
Expand Up @@ -468,28 +468,39 @@ def planar_sheet_3d(cls, identifier, nx, ny, distx, disty, noise=None):


def get_opposite(edge_df, raise_if_invalid=False):
"""
Returns the indices opposite to the edges in `edge_df`
"""
srce = edge_df["srce"].to_numpy()
trgt = edge_df["trgt"].to_numpy()
edge_idx = edge_df.index.to_numpy()

st_indexed = (
edge_df[["srce", "trgt"]].reset_index().set_index(["srce", "trgt"], drop=False)
)
flipped = st_indexed.index.swaplevel(0, 1)
flipped.names = ["srce", "trgt"]
try:
opposite = st_indexed.reindex(flipped)["edge"].values
except ValueError as e:
dup = flipped.duplicated()
warnings.warn(
"Duplicated (`srce`, `trgt`) values in edge_df, maybe sanitize your input"
)
opposite = st_indexed[~dup].reindex(flipped)["edge"].values
pairs = np.zeros(len(srce), dtype=[('f0', srce.dtype), ('f1', trgt.dtype)])
pairs['f0'], pairs['f1'] = srce, trgt

sort_idx = np.argsort(pairs)
sorted_pairs = pairs[sort_idx]

if np.any(sorted_pairs[1:] == sorted_pairs[:-1]):
warnings.warn("Duplicated (`srce`, `trgt`) values detected.")
if raise_if_invalid:
raise e
raise ValueError("Duplicated pairs detected")

# Find opposites
# Create the "target" pairs we are looking for: (trgt, srce)
flipped_pairs = np.zeros(len(srce), dtype=[('f0', srce.dtype), ('f1', trgt.dtype)])
flipped_pairs['f0'], flipped_pairs['f1'] = trgt, srce

# Find where flipped_pairs would fit into the sorted list of original pairs
match_indices = np.searchsorted(sorted_pairs, flipped_pairs)


valid_mask = (match_indices < len(pairs))
actual_match_mask = valid_mask.copy()
actual_match_mask[valid_mask] = (sorted_pairs[match_indices[valid_mask]] == flipped_pairs[valid_mask])

# Map back from the sorted index to the original edge index
opposite = np.full(len(srce), -1, dtype=edge_idx.dtype)
opposite[actual_match_mask] = edge_idx[sort_idx[match_indices[actual_match_mask]]]

opposite[np.isnan(opposite)] = -1
return opposite.astype(int)
return opposite


def get_outer_sheet(eptm):
Expand Down
7 changes: 5 additions & 2 deletions src/tyssue/draw/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
from mpl_toolkits.mplot3d.art3d import Line3DCollection, Poly3DCollection
import matplotlib.collections as mcollections
import numpy as np

from .ipv_draw import browse_history # noqa
from .ipv_draw import sheet_view as sheet_view_3d # noqa
from .plt_draw import create_gif, plot_forces, quick_edge_draw # noqa
from .plt_draw import create_gif, create_gif_3d, plot_forces, quick_edge_draw # noqa
from .plt_draw import sheet_view as sheet_view_2d # noqa

try:
Expand All @@ -9,7 +13,6 @@
print("vispy won't work")
sheet_view_vispy = None


def sheet_view(sheet, coords=["x", "y", "z"], ax=None, mode="2D", **draw_specs_kw):
"""Main plotting function in 2D or 3D.

Expand Down
Loading
Loading