Add filaments postproc, improve units detection, add automatic map rules, add selection

This commit is contained in:
Noe Brucy
2020-10-16 17:44:34 +02:00
parent 013aab911e
commit e05477cb7a
7 changed files with 867 additions and 211 deletions
+394 -72
View File
@@ -4,16 +4,16 @@ import pspec_new
from baseprocessor import *
import pymses.utils.regions as reg
from pymses.filters import RegionFilter
import astropy.units as u
from fil_finder import FilFinder2D
import pickle
from skimage.morphology import medial_axis
# Getters
def mass_func(dset):
try:
dx = dset["dx"]
except:
dx = dset.get_sizes()
dx = dset["dx"]
return dset["rho"] * dx ** 3 # Mass function
@@ -47,7 +47,7 @@ def getter_rho(dset):
def getter_v_norm(dset):
v_norm = np.sqrt(np.sum(dset["Br"] ** 2, axis=1))
v_norm = np.sqrt(np.sum(dset["vel"] ** 2, axis=1))
return v_norm
@@ -82,15 +82,6 @@ def mean_by_bins(
# For each cell, bin_number contains the number of the bins it belongs to
bin_number = np.zeros(len(y))
# Go through the min value of x of each bin
for x_min in x_bins[1:-1]:
bin_number = bin_number + (x > x_min).astype(int)
# Compute the mean in each bin
y_mean = np.zeros(len(x_bins) - 1)
for i in range(len(y_mean)):
y_mean[i] = np.mean(y[bin_number == i])
# Get the center of each bin
if logbins:
centers = 10 ** (0.5 * (np.log10(x_bins[1:]) + np.log10(x_bins[:-1])))
@@ -100,6 +91,39 @@ def mean_by_bins(
return centers, y_mean
# Filament helpers
def find_center(distance, skeleton, i_center, j_center, i, j):
"""
Given a distance array, find the cells at a center of a filament at a given postion
"""
if skeleton[i, j]:
i_center[i, j], j_center[i, j] = i, j
return i, j
elif i_center[i, j] or j_center[i, j]:
return i_center[i, j], j_center[i, j]
else:
i_neigh = np.array([i - 1, i, i + 1])
i_neigh = i_neigh[(i_neigh > 0) & (i_neigh < distance.shape[0])]
j_neigh = np.array([j - 1, j, j + 1])
j_neigh = j_neigh[(j_neigh > 0) & (j_neigh < distance.shape[1])]
ii_neigh, jj_neigh = np.meshgrid(i_neigh, j_neigh)
d_neigh = distance[ii_neigh, jj_neigh]
ind_max = np.unravel_index(np.argmax(d_neigh), d_neigh.shape)
i_max, j_max = ii_neigh[ind_max], jj_neigh[ind_max]
if i_max == i and j_max == j:
i_center[i, j], j_center[i, j] = i, j
else:
i_center[i, j], j_center[i, j] = find_center(
distance, skeleton, i_center, j_center, i_max, j_max
)
return i_center[i, j], j_center[i, j]
# PostProcessor class
class PostProcessor(HDF5Container):
"""
This class enable to compute and save derived quantities from the raw output
@@ -110,6 +134,17 @@ class PostProcessor(HDF5Container):
_axes_h = {"x": "y", "y": "x", "z": "x"} # Associated horizontal axe
_axes_v = {"x": "z", "y": "z", "z": "y"} # Associated vertical axe
# Pymses unit key of amr fiels
unit_key = {
"rho": "unit_density",
"vel": "unit_velocity",
"Br": "unit_mag",
"Bl": "unit_mag",
"P": "unit_pressure",
"g": {"unit_gravpot": 1, "unit_length": -1},
"phi": "unit_gravpot",
}
G = 1.0 # Gravitational constant
cells_loaded = False
@@ -238,6 +273,12 @@ class PostProcessor(HDF5Container):
self.log_id = "[{}, {}] ".format(self.run, self.num)
if os.path.exists(self.path_out + "/filaments.pickle"):
with open(self.path_out + "/filaments.pickle", "rb") as f:
self.fil = pickle.load(f)
else:
self.fil = None
self.def_rules()
def load_cells(self):
@@ -290,22 +331,47 @@ class PostProcessor(HDF5Container):
"""
Returns the position in normalized units centered on the position of the star
"""
pos = dset.get_cell_centers()
pos = dset.points
pos = pos - (np.array(self.pp_params.disk.pos_star) / self.lbox)
return pos
def getter_vect_r(self, dset, name_vect):
""" Radial component of a vector """
r = self.getter_pos_disk(dset)[:, :, :2]
r = self.getter_pos_disk(dset)[:, :2]
ur = np.transpose((np.transpose(r) / np.sqrt(np.sum(r ** 2, axis=1))))
return np.einsum("ij, ij -> i", dset[name_vect][:, :2], ur)
def getter_vect_phi(self, dset, name_vect):
""" Azimuthal component of a vector """
r = self.getter_pos_disk(dset)[:, :2]
r_norm = np.sqrt(np.sum(r ** 2, axis=1))
rot = np.array([[0, -1], [1, 0]])
uphi = np.transpose(np.einsum("ij, kj -> ik", rot, r) / r_norm)
vect = dset[name_vect][:, :2]
return np.einsum("ij,ij -> i", vect, uphi)
def oct_getter_pos_disk(self, dset):
"""
Returns the position in normalized units centered on the position of the star
"""
pos = dset.get_cell_centers()
pos = pos - (np.array(self.pp_params.disk.pos_star) / self.lbox)
return pos
def oct_getter_vect_r(self, dset, name_vect):
""" Radial component of a vector """
r = self.oct_getter_pos_disk(dset)[:, :, :2]
ur = np.transpose(
(np.transpose(r, (2, 0, 1)) / np.sqrt(np.sum(r ** 2, axis=2))), (1, 2, 0)
)
return np.einsum("ikj, ikj -> ik", dset[name_vect][:, :, :2], ur)
def getter_vect_phi(self, dset, name_vect):
def oct_getter_vect_phi(self, dset, name_vect):
""" Azimuthal component of a vector """
r = self.getter_pos_disk(dset)[:, :, :2]
r = self.oct_getter_pos_disk(dset)[:, :, :2]
r_norm = np.sqrt(np.sum(r ** 2, axis=2))
rot = np.array([[0, -1], [1, 0]])
uphi = np.transpose(np.einsum("ij, klj -> ikl", rot, r) / r_norm, (1, 2, 0))
@@ -313,14 +379,14 @@ class PostProcessor(HDF5Container):
return np.einsum("ikj,ikj -> ik", vect, uphi)
def getter_vr(self, dset):
return self.getter_vect_r(dset, "vel")
def oct_getter_vr(self, dset):
return self.oct_getter_vect_r(dset, "vel")
def getter_vphi(self, dset):
def oct_getter_vphi(self, dset):
""" Azimuthal velocity """
return self.getter_vect_phi(dset, "vel")
return self.oct_getter_vect_phi(dset, "vel")
def _slice(self, getter, ax_los="z", z=0, unit=cst.none):
def _slice(self, getter, ax_los="z", z=0.0, unit=cst.none):
"""
Slice process function.
Return a slice of the source box.
@@ -343,6 +409,7 @@ class PostProcessor(HDF5Container):
-------
A numpy array containing the slice
"""
unit = self._get_units(unit)
op = ScalarOperator(getter, unit)
datamap = slicing.SliceMap(self._amr, self._cam[ax_los], op, z=z)
return datamap.map.T
@@ -356,6 +423,7 @@ class PostProcessor(HDF5Container):
If surf_qty is set (projection mode), mass_weighted is ignored
"""
unit = self._get_units(unit)
if surf_qty:
op = ScalarOperator(getter, unit)
else:
@@ -405,6 +473,7 @@ class PostProcessor(HDF5Container):
WARNING : This version only works on an uniform grid, need of a box version for AMR
Returns 1D array if getter returns a scalar quantity
"""
unit = self._get_units(unit)
self.load_cells()
if isinstance(axis, str):
axis = self._ax_nb[axis]
@@ -426,10 +495,10 @@ class PostProcessor(HDF5Container):
return df.groupby("axis").mean().values[:, 0]
def _vol_avg(self, getter, mass_weighted=True):
def _sum(self, getter, mass_weighted=True):
"""
Global volumic (or mass_weighted) average of the quantity returned by getter
Returns a scalar (or a vctor if the quantity returned by getter is a getter, eg. speed)
Global sum of the quantity returned by getter (variable must be extensive)
Returns a scalar (or a vector if the quantity returned by getter is a getter, eg. speed)
"""
self.load_cells()
value = getter(self.cells)
@@ -444,6 +513,24 @@ class PostProcessor(HDF5Container):
self.unload_cells()
return data
def _vol_avg(self, getter, mass_weighted=True):
"""
Global volumic (or mass_weighted) average of the quantity returned by getter
Returns a scalar (or a vector if the quantity returned by getter is a getter, eg. speed)
"""
self.load_cells()
value = getter(self.cells)
if mass_weighted:
weight = mass_func(self.cells)
else:
weight = vol_func(self.cells)
# Transpose (.T) is for vectorial values
data = np.sum((weight * value.T).T, axis=0) / np.sum(weight)
if self.pp_params.process.unload_cells:
self.unload_cells()
return data
def _vol_pdf(self, getter, bins=100, logbins=False, weight_func=vol_func):
self.load_cells()
data = getter(self.cells)
@@ -656,7 +743,7 @@ class PostProcessor(HDF5Container):
# Operator to compute the angular speed times rho
def omega_rho_func(dset):
pos = self.getter_pos_disk(dset)
pos = self.oct_getter_pos_disk(dset)
xx = pos[:, :, 0]
yy = pos[:, :, 1]
rc = np.sqrt(xx ** 2 + yy ** 2) # cylindrical radius
@@ -743,9 +830,17 @@ class PostProcessor(HDF5Container):
map_size = self.pp_params.pymses.map_size
pos_star = self.pp_params.disk.pos_star
x = np.linspace(im_extent[0], im_extent[1], map_size)
y = np.linspace(im_extent[2], im_extent[3], map_size)
# Physical size of cells
dx = (im_extent[1] - im_extent[0]) / map_size
dy = (im_extent[3] - im_extent[2]) / map_size
# Physical coordinates of the center of the cells
x = np.linspace(im_extent[0], im_extent[1], map_size) + 0.5 * dx
y = np.linspace(im_extent[2], im_extent[3], map_size) + 0.5 * dy
xx, yy = np.meshgrid(x, y)
# Physical radius
rr = np.sqrt((xx - pos_star[0]) ** 2 + (yy - pos_star[1]) ** 2)
return rr
@@ -810,14 +905,17 @@ class PostProcessor(HDF5Container):
fluct_map = self.save.get_node("/maps/fluct_" + name + "_" + ax_los).read()
rr = self.save.get_node("/maps/rr_" + ax_los).read()
mask_pdf = (rr > self.pp_params.disk.rmin_pdf) & (
rr < self.pp_params.disk.rmax_pdf
mask_pdf = (
(rr > self.pp_params.disk.rmin_pdf)
& (rr < self.pp_params.disk.rmax_pdf)
& (fluct_map > 0)
)
nb_cells = np.sum(mask_pdf.flatten())
values, edges = np.histogram(
np.log10(fluct_map[mask_pdf].flatten()),
self.pp_params.pdf.nb_bin,
range=self.pp_params.pdf.range,
weights=np.ones(nb_cells) / nb_cells,
)
centers = 0.5 * (edges[1:] + edges[:-1])
@@ -848,7 +946,7 @@ class PostProcessor(HDF5Container):
# Mean part
T_avg = self.save.get_node("/maps/avg_map_T_avg_z").read()
T_avg = self.save.get_node("/maps/avg_map_T_mwavg_z").read()
radial_bins = self.save.get_node("/radial/radial_bins_" + ax_los).read()
mean_bin_vr = self.save.get_node(
@@ -862,7 +960,9 @@ class PostProcessor(HDF5Container):
# Fluct part
def getter_alpha_num(dset):
r = np.sqrt(np.sum((self.lbox * self.getter_pos_disk(dset)) ** 2, axis=2))
r = np.sqrt(
np.sum((self.lbox * self.oct_getter_pos_disk(dset)) ** 2, axis=2)
)
bins = np.zeros(r.shape, dtype=int)
for r0 in radial_bins[1:]:
@@ -871,8 +971,8 @@ class PostProcessor(HDF5Container):
vr_mean = mean_bin_vr[bins]
vphi_mean = mean_bin_vphi[bins]
vr = self.getter_vr(dset)
vphi = self.getter_vphi(dset)
vr = self.oct_getter_vr(dset)
vphi = self.oct_getter_vphi(dset)
alpha = (vphi - vphi_mean) * (vr - vr_mean)
return alpha
@@ -889,15 +989,15 @@ class PostProcessor(HDF5Container):
"Map of the gravitational contribution to the Shakura&Sunaev alpha parameter for disks"
assert ax_los == "z"
T_avg = self.save.get_node("/maps/avg_map_T_avg_z").read()
T_avg = self.save.get_node("/maps/avg_map_T_mwavg_z").read()
coldens = self.save.get_node("/maps/avg_map_coldens_z").read()
def getter_alpha_grav(dset):
r2 = np.sum((self.lbox * self.getter_pos_disk(dset)) ** 2, axis=2)
r2 = np.sum((self.lbox * self.oct_getter_pos_disk(dset)) ** 2, axis=2)
e2 = (1.0 / 256.0) ** 2
gstar = -self.G * self.pp_params.disk.mass_star / (e2 + r2)
gr = self.getter_vect_r(dset, "g") - gstar
gphi = self.getter_vect_phi(dset, "g")
gr = self.oct_getter_vect_r(dset, "g") - gstar
gphi = self.oct_getter_vect_phi(dset, "g")
return gr * gphi / (4 * np.pi * self.G)
alpha_g = self._ax_avg(getter_alpha_grav, "z", unit=cst.none, surf_qty=True) / (
@@ -908,6 +1008,14 @@ class PostProcessor(HDF5Container):
alpha_g = (2.0 / 3) * alpha_g
return alpha_g
alpha_g = self._ax_avg(getter_alpha_grav, "z", unit=cst.none, surf_qty=True) / (
coldens * T_avg
)
# alpha
alpha_g = (2.0 / 3) * alpha_g
return alpha_g
def _sinks(self):
csv_name = (
self.path
@@ -950,7 +1058,139 @@ class PostProcessor(HDF5Container):
def _pspec(self):
outfile = self.path_out + "/pspec.h5"
pspec_new.pspec(repo=self.path, iouts=[self.num], outfile=outfile)
return outfile
return True
def _filaments(self):
datamap_name = self.pp_params.filaments.datamap
verbose = self.pp_params.filaments.verbose
rmin_frac = self.pp_params.filaments.rmin
rmax_frac = self.pp_params.filaments.rmax
size_thresh = self.pp_params.filaments.size_thresh
skel_thresh = self.pp_params.filaments.skel_thresh
branch_thresh = self.pp_params.filaments.branch_thresh
glob_thresh = self.pp_params.filaments.glob_thresh
datamap = self.save.get_node("/maps/" + datamap_name + "_z").read()
shape = datamap.shape
x = np.arange(shape[0]) - shape[0] / 2
y = np.arange(shape[1]) - shape[1] / 2
xx, yy = np.meshgrid(x, y)
rr = np.sqrt(xx ** 2 + yy ** 2)
rmin = int(rmin_frac * shape[0])
rmax = int(rmax_frac * shape[0])
mask = (rr >= rmin) & (rr <= rmax)
datamap[np.logical_not(mask)] = np.nan
self.fil = FilFinder2D(datamap, distance=1 * u.cm, beamwidth=1 * u.pix)
self.fil.preprocess_image(flatten_percent=95)
self.fil.create_mask(
verbose=verbose,
smooth_size=1 * u.pix,
adapt_thresh=2 * u.pix,
size_thresh=size_thresh * u.pix ** 2,
glob_thresh=glob_thresh,
)
self.fil.medskel(verbose=verbose)
self.fil.analyze_skeletons(
skel_thresh=skel_thresh * u.pix,
branch_thresh=branch_thresh * u.pix,
relintens_thresh=0.1,
)
self.fil.exec_rht()
self.fil.find_widths()
outfile = self.path_out + "/filaments.pickle"
with open(outfile, "wb") as f:
pickle.dump(self.fil, f, pickle.HIGHEST_PROTOCOL)
return True
def _filaments_center(self):
"""
Fill an array with center postion for each cell in a filament
"""
fil = self.fil
mask = fil.mask.copy()
_, distance = medial_axis(mask, return_distance=True)
skel = fil.skeleton
i_center = np.zeros(distance.shape, dtype=int)
j_center = np.zeros(distance.shape, dtype=int)
x_mask, y_mask = np.where(mask)
for k in range(len(x_mask)):
find_center(distance, skel, i_center, j_center, x_mask[k], y_mask[k])
return np.stack([i_center, j_center])
def _filaments_forces(self):
"""
Compute forces within a filament (for disks)
"""
GM = self.G * self.pp_params.disk.mass_star # Mass parameter
# Get mask for filaments
fil = self.fil
mask_fil = np.asarray(fil.mask.copy(), dtype=bool)
# Find center of filaments
i_center, j_center = self._filaments_center()
# Get slices and projections at z = 0
vphi = self.save.get_node("/maps/slice_velphi_z").read()
gr = self.save.get_node("/maps/slice_gr_z").read()
Pz = self.save.get_node("/maps/slice_P_z").read()
coldens = self.save.get_node("/maps/coldens_z").read()
vr = self.save.get_node("/maps/slice_velr_z").read()
# Get coordinates
im_extent = np.array(self.save.root.maps._v_attrs.im_extent) * self.lbox
map_size = self.pp_params.pymses.map_size
pos_star = self.pp_params.disk.pos_star
# Physical size of cells
dx = (im_extent[1] - im_extent[0]) / map_size
dy = (im_extent[3] - im_extent[2]) / map_size
# Physical coordinates of the center of the cells
x = np.linspace(im_extent[0], im_extent[1], map_size) + 0.5 * dx
y = np.linspace(im_extent[2], im_extent[3], map_size) + 0.5 * dy
xx, yy = np.meshgrid(x, y)
rr = np.sqrt((xx - pos_star[0]) ** 2 + (yy - pos_star[1]) ** 2)
# Rotational support
R = vphi ** 2 / rr
# Equilibrium
Gvrx, Gvry = np.gradient(vr)
gradvr = (xx * Gvrx + yy * Gvry) / rr
dvr = gradvr + vr * gradvr # Complete derivative
# Thermal support
GPx, GPy = np.gradient(Pz)
gradPr = (xx * GPx + yy * GPy) / rr
fP = gradPr / coldens
# Gravitational field
e2 = (1.0 / 512) ** 2
gstar = -GM / (rr ** 2 + e2)
# Substract gravitational field from the star
Rdisk = R + gstar
gdisk = gr - gstar
# Forces at the center of filaments
Rdisk_center = Rdisk[i_center, j_center]
gr_center = gdisk[i_center, j_center]
fP_center = fP[i_center, j_center]
dvr_center = dvr[i_center, j_center]
# Forces for the filaments equilibrium
Rfil = Rdisk - Rdisk_center
gfil = gdisk - gr_center
fPfil = fP - fP_center
dvr_fil = dvr - dvr_center
return {"gfil": gfil, "Rfil": Rfil, "fPfil": fPfil, "dvr": dvr_fil}
def def_rules(self):
@@ -967,7 +1207,7 @@ class PostProcessor(HDF5Container):
self,
partial(
self._ax_avg,
self.getter_vr,
self.oct_getter_vr,
mass_weighted=True,
unit=self.info["unit_velocity"],
),
@@ -979,7 +1219,7 @@ class PostProcessor(HDF5Container):
self,
partial(
self._ax_avg,
self.getter_vphi,
self.oct_getter_vphi,
mass_weighted=True,
unit=self.info["unit_velocity"],
),
@@ -987,31 +1227,7 @@ class PostProcessor(HDF5Container):
"/maps",
unit=self.info["unit_velocity"],
),
"rho_avg": Rule(
self,
partial(
self._ax_avg,
getter_rho,
mass_weighted=False,
unit=self.info["unit_density"],
),
"Ax mass-weighted averaged azimuthal density",
"/maps",
unit=self.info["unit_density"],
),
"P_avg": Rule(
self,
partial(
self._ax_avg,
getter_P,
mass_weighted=True,
unit=self.info["unit_pressure"],
),
"Ax mass-weighted averaged azimuthal pressure",
"/maps",
unit=self.info["unit_pressure"],
),
"T_avg": Rule(
"T_mwavg": Rule(
self,
partial(
self._ax_avg,
@@ -1031,7 +1247,7 @@ class PostProcessor(HDF5Container):
unit=cst.none,
dependencies=[
"avg_map_rho_avg",
"avg_map_T_avg",
"avg_map_T_mwavg",
"avg_map_vr",
"avg_map_vphi",
],
@@ -1043,7 +1259,7 @@ class PostProcessor(HDF5Container):
Shakura&Sunaev alpha parameter for disks",
"/maps",
unit=cst.none,
dependencies=["avg_map_coldens", "avg_map_T_avg"],
dependencies=["avg_map_coldens", "avg_map_T_mwavg"],
),
"rho": Rule(
self,
@@ -1139,6 +1355,27 @@ class PostProcessor(HDF5Container):
},
),
"pspec": Rule(self, self._pspec, "Power spectrum", "/hdf5"),
"filaments": Rule(
self,
self._filaments,
"Filaments",
"/datasets",
dependencies={self.pp_params.filaments.datamap: "z"},
),
"filaments_forces": Rule(
self,
self._filaments_forces,
"Filaments",
"/datasets",
dependencies={
"filaments": None,
"slice_velphi": "z",
"slice_gr": "z",
"slice_P": "z",
"coldens": "z",
"slice_velr": "z",
},
),
# Helpers
"radial_bins": Rule(self, self._radial_bins, "Radial bins", "/radial"),
"rr": Rule(self, self._rr, "Coordinate map", "/maps"),
@@ -1165,6 +1402,18 @@ class PostProcessor(HDF5Container):
"/hist",
unit=self.info["unit_density"],
),
"rho_pdf_mw": Rule(
self,
partial(
self._vol_pdf,
partial(simple_getter, "rho"),
weight_func=mass_func,
logbins=True,
),
"Global rho-PDF",
"/hist",
unit=self.info["unit_density"],
),
"T_pdf": Rule(
self,
partial(self._vol_pdf, getter_T, logbins=True),
@@ -1226,6 +1475,13 @@ class PostProcessor(HDF5Container):
"/globals",
unit=self.info["unit_time"],
),
"mass": Rule(
self,
partial(self._sum, mass_func),
"Total mass",
"/globals",
unit=self.info["unit_density"] * self.info["unit_length"] ** 3,
),
"mwa_speed": Rule(
self,
partial(self._vol_avg, partial(simple_getter, "vel")),
@@ -1261,7 +1517,8 @@ class PostProcessor(HDF5Container):
"rho_avg",
"P_avg",
"T_avg",
"alpha_disk",
"P_mwavg",
"T_mwavg" "alpha_disk",
"alpha_grav",
]
for name in averageables:
@@ -1312,7 +1569,72 @@ class PostProcessor(HDF5Container):
dependencies=[name, name_bin],
)
self._gen_rule_transform("fluct_coldens", np.max, "max", group="/globals")
self._gen_rule_transform("fluct_coldens", np.nanmax, "max", group="/globals")
# Generic rules directly from Ramses fields
for field in self.pp_params.pymses.variables:
def generic_rule(name, getter, unit, oct_getter=None):
if oct_getter is None:
oct_getter = getter
self.rules["slice_" + name] = Rule(
self,
partial(self._slice, getter, z=0.0, unit=unit),
"{} slice".format(name),
"/maps",
unit=unit,
)
self.rules[name + "_mwavg"] = Rule(
self,
partial(self._ax_avg, oct_getter, mass_weighted=True, unit=unit),
"Ax mass-weighted averaged {}".format(name),
"/maps",
unit=unit,
)
self.rules[name + "_avg"] = Rule(
self,
partial(self._ax_avg, oct_getter, mass_weighted=False, unit=unit),
"Ax averaged {}".format(name),
"/maps",
unit=unit,
)
# special for vectors
if field in ["g", "vel"]:
# Components
for i, dir in enumerate(["x", "y", "z"]):
generic_rule(
field + dir,
partial(vect_getter, field, i),
self.unit_key[field],
)
# Radial
generic_rule(
field + "r",
partial(self.getter_vect_r, name_vect=field),
self.unit_key[field],
oct_getter=self.oct_getter_vect_r,
)
# Othoradial
generic_rule(
field + "phi",
partial(self.getter_vect_phi, name_vect=field),
self.unit_key[field],
oct_getter=self.oct_getter_vect_phi,
)
# Norm
generic_rule(
field + "_norm", partial(norm_getter, field), self.unit_key[field]
)
else:
generic_rule(field, partial(simple_getter, field), self.unit_key[field])
super(PostProcessor, self).def_rules()