Skip to content

Commit

Permalink
Add input validation
Browse files Browse the repository at this point in the history
  • Loading branch information
LimbeckKat committed Nov 26, 2024
1 parent d465163 commit 8544107
Show file tree
Hide file tree
Showing 2 changed files with 143 additions and 31 deletions.
90 changes: 73 additions & 17 deletions magnipy/diversipy.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ def __init__(
An object that can be used to compute and compare the magnitude functions of multiple spaces.
"""

if not isinstance(Xs, list):
raise Exception(
"Xs needs to be a list of one or multiple datasets."
)

self._Xs = Xs

if method is None:
Expand All @@ -97,8 +102,21 @@ def __init__(
self._p = p
self._n_neighbors = n_neighbors
self._target_prop = target_prop
self._Mags = None

if ref_space is not None:
if not isinstance(ref_space, int):
raise Exception(
"ref_space needs to be an integer index corresponding to the index of the reference dataset in the list of input datasets."
)
else:
if ref_space >= len(Xs):
raise Exception(
"ref_space needs to be an integer index corresponding to the index of the reference dataset in the list of input datasets."
)

self._ref_space = ref_space

self._Mags = None
self._t_convs = None
self._MagAreas = None
self._MagDiffs = None
Expand All @@ -120,15 +138,58 @@ def set_ref_space(self, ref_space):
self._ref_space = ref_space
return None

def get_t_convs(self):
"""
Get the approximate convergence scales for all datasets.
"""
t_convs = []
for i, X in enumerate(self._Xs):
if self._Mags is not None:
Mag = self._Mags[i]
else:
Mag = Magnipy(
X,
ts=self._ts,
scale_finding="convergence",
target_prop=self._target_prop,
n_ts=2,
log_scale=False,
method=self._method,
metric=self._metric,
p=self._p,
one_point_property=True,
return_log_scale=False,
perturb_singularities=True,
recompute=False,
name=self._names[i],
positive_magnitude=False,
)
t_convs.append(Mag.get_t_conv())
# Mags.append(Mag)
# self._Mags = Mags
self._t_convs = t_convs
return t_convs

def get_common_scales(self, quantile=0.5):
"""
Determine the shared evaluation interval for the magnitude functions.
To do this, the convergence scales of the magnitude functions are computed and
the shared scales are determined as a quantile (e.g. the median) of the convergence scales for all datasets.
To do this, the approximate convergence scale of the reference dataset is computed
and used as the common cutoff scale to define the evaluation interval.
Otherwise, if no reference space is set the convergence scales of all magnitude
functions are computed and the shared evaluation scales are determined as a
quantile (e.g. the median) of the convergence scales for all datasets.
Parameters
----------
quantile : float
The quantile to use for determining the common scales.
By default 0.5 (median convergence scale).
"""
if self._t_convs is None:
t_convs = self.get_t_convs()

if self._ref_space is not None:
t_cut = self._Mags[self._ref_space].get_t_conv()
t_cut = self._t_convs[self._ref_space]
else:
if self._q is None:
quantile = 0.5
Expand All @@ -141,13 +202,13 @@ def get_common_scales(self, quantile=0.5):
log_scale=False,
one_point_property=True,
)
# self._ts = ts
self._t_cut = t_cut
return ts

def change_scales(self, ts=None, t_cut=None):
"""
Change the evaluation scales of the magnitude functions.
If no scales are given, the evaluation interval is reset to None.
Parameters
----------
Expand Down Expand Up @@ -178,22 +239,23 @@ def change_scales(self, ts=None, t_cut=None):
# │ Compute Magnitude Functions │
# ╰──────────────────────────────────────────────────────────╯

def _compute_magnitude(self):
def _compute_magnitude(self, quantile=0.5):
"""
Compute the magnitude functions for all datasets.
"""

t_convs = []
Mags = []
if self._ts is None:
t_convs = self.get_t_convs()
ts = self.get_common_scales(quantile=quantile)
self._ts = ts

Mags = []
for i, X in enumerate(self._Xs):
Mag = Magnipy(
X,
ts=self._ts,
ts=ts,
scale_finding="convergence",
target_prop=self._target_prop,
n_ts=2,
log_scale=False,
method=self._method,
metric=self._metric,
p=self._p,
Expand All @@ -204,14 +266,8 @@ def _compute_magnitude(self):
name=self._names[i],
positive_magnitude=False,
)
t_convs.append(Mag.get_t_conv())
Mags.append(Mag)
self._Mags = Mags
self._t_convs = t_convs

ts = self.get_common_scales()
Mags = self.change_scales(ts)
self._Mags = Mags
return Mags

def get_magnitude_functions(self):
Expand Down
84 changes: 70 additions & 14 deletions magnipy/magnipy.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def __init__(
ts : array_like, shape (`n_ts`, )
The scales at which to evaluate the magnitude functions. If None, the scales are computed automatically.
n_ts : int
The number of scales at which to evaluate the magnitude functions.
The number of scales at which to evaluate the magnitude functions. Computations are faster for fewer scales and more accurate for more scales.
log_scale : bool
Whether to use a log-scale for the evaluation scales.
return_log_scale : bool
Expand Down Expand Up @@ -120,7 +120,50 @@ def __init__(
A Magnipy object.
"""

### Check if the input matrix X is valid
if not isinstance(X, np.ndarray):
raise Exception("The input matrix must be a numpy array.")

### Check if the inputs used for scale-finding are valid
if isinstance(target_prop, float):
min_mag = 1 / X.shape[0]
if (target_prop < min_mag) | (target_prop > 1):
raise Exception(
f"The target proportion must be between {min_mag} and 1."
)
else:
raise Exception("The target proportion must be a float.")

self._proportion_scattered = target_prop
if (scale_finding != "scattered") & (scale_finding != "convergence"):
raise Exception(
"The scale finding method must be either 'scattered' or 'convergence'."
)
self._scale_finding = scale_finding

### Check if the evaluation scales are valid
self._ts = ts
if not isinstance(n_ts, int):
raise Exception("n_ts must be an integer.")
self._n_ts = n_ts

### Check if the adjacency matrix is valid
if Adj is not None:
if not isinstance(Adj, np.ndarray):
raise Exception("The adjacency matrix must be a numpy array.")
if Adj.shape[0] != X.shape[0]:
raise Exception(
"The adjacency matrix must have the same number of rows as the dataset."
)
if Adj.shape[1] != X.shape[0]:
raise Exception(
"The adjacency matrix must have the same number of columns as the dataset."
)

### Setting up the distance computations and the similarity matrix
self._Adj = Adj
self._metric = metric

if metric != "precomputed":
self._X = X

Expand All @@ -141,18 +184,19 @@ def compute_distances(X, X2, Adj=None):
self._target_value = target_prop * self._D.shape[0]
self._Z = similarity_matrix(self._D)
else:

if X.shape[0] != X.shape[1]:
raise Exception(
"The precomputed distance matrix must be square."
)

self._X = None
self._D = X
self._n = self._D.shape[0]
self._Z = similarity_matrix(self._D)
self._target_value = target_prop * self._D.shape[0]

self._proportion_scattered = target_prop
if (scale_finding != "scattered") & (scale_finding != "convergence"):
raise Exception(
"The scale finding method must be either 'scattered' or 'convergence'."
)

### Check if the method for computing the magnitude is valid and set up the magnitude computations
if method not in [
"cholesky",
"scipy",
Expand Down Expand Up @@ -182,22 +226,34 @@ def compute_mag(Z, ts, n_ts=n_ts, get_weights=False):
)

self._compute_mag = compute_mag

self._scale_finding = scale_finding
self._ts = ts
self._n_ts = n_ts
self._log_scale = log_scale
self._method = method
self._metric = metric
# self._p = p
# self._n_neighbors = n_neighbors

### Check if the boolean parameters are valid
for k, arg in enumerate(
[log_scale, return_log_scale, recompute, positive_magnitude]
):
arg_name = [
"log_scale",
"return_log_scale",
"recompute",
"positive_magnitude",
][k]
if not isinstance(arg, bool):
raise Exception(f"{arg_name} must be a boolean.")

self._log_scale = log_scale
self._one_point_property = one_point_property
self._perturb_singularities = perturb_singularities
# self._n_neighbors = n_neighbors
self._return_log_scale = return_log_scale
self._recompute = recompute
self._positive_magnitude = positive_magnitude

### Set the name of the Magnipy object
self._name = name

### Set the other parameters
self._magnitude = None
self._weights = None
self._magnitude_dimension_profile = None
Expand Down

0 comments on commit 8544107

Please sign in to comment.