Skip to content
Open
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
42 changes: 36 additions & 6 deletions probinet/utils/matrix_operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,11 +158,21 @@ def normalize_nonzero_membership(u: np.ndarray, axis: Optional[int] = 1) -> np.n
u: ndarray
Numpy Matrix.
axis: Optional[int]
Axis along which the matrix should be normalized.
Axis along which the matrix is normalized. Default is 1.

Returns
-------
The matrix normalized by row.
np.ndarray
Normalized copy of u along axis. Sums equal to zero are
replaced with 1.0 before division to avoid divide-by-zero,
leaving the corresponding rows or columns unchanged.

Raises
------
numpy.AxisError
If axis is not a valid axis for u.
TypeError
If u is not compatible with the required NumPy operations.
"""

# Calculate the sum of elements along axis 1, keeping the same dimensions.
Expand All @@ -186,11 +196,17 @@ def transpose_matrix(M: np.ndarray) -> np.ndarray:
Parameters
----------
M : ndarray
Numpy matrix.
Input 2-D numpy array.

Returns
-------
Transpose of the matrix.
np.ndarray
Transpose of the matrix M with shape (M.shape[1], M.shape[0]).

Raises
------
ValueError
If M is not 2-D.
"""
# Return the transpose of a matrix
return np.einsum("ij->ji", M)
Expand All @@ -207,7 +223,15 @@ def transpose_tensor(M: np.ndarray) -> np.ndarray:

Returns
-------
Transpose version of M_aij, i.e. M_aji.
np.ndarray
Node-index swap on the last two dimensions (``aij -> aji``). Same
rank and shape as M; expects a tensor with at least 3
dimensions.

Raises
------
ValueError
If ``M`` has fewer than 3 dimensions or ``einsum`` fails.
"""

return np.einsum("aij->aji", M)
Expand All @@ -229,7 +253,13 @@ def Exp_ija_matrix(u: np.ndarray, v: np.ndarray, w: np.ndarray) -> np.ndarray:
Returns
-------
M : ndarray
Mean lambda0_ij for all entries.
Expected Poisson rates ``lambda0_ij`` for all node pairs, shape
(N, N) where N = u.shape[0].

Raises
------
ValueError
If u, v, and w have incompatible shapes for einsum.
"""

# Compute the outer product of matrices u and v, resulting in a 4D tensor M.
Expand Down
93 changes: 83 additions & 10 deletions probinet/utils/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,14 @@ def can_cast_to_int(string: Union[int, float, str]) -> bool:
Returns
-------
bool : bool
If True, the input can be converted to integer object.
If True, the input can be converted to an integer, False otherwise.

Raises
------
ValueError
Caught internally; the error is logged and False is returned.
TypeError
If the input cannot be coerced by int() (e.g. None).
"""

try:
Expand Down Expand Up @@ -58,6 +65,11 @@ def is_sparse(X: np.ndarray) -> bool:
Returns
-------
Boolean flag: true if the input tensor is sparse, false otherwise.

Raises
------
AttributeError
If X lacks .ndim, .size, or .nonzero().
"""

# Get the number of dimensions of the input tensor X.
Expand Down Expand Up @@ -89,6 +101,11 @@ def sptensor_from_dense_array(X: np.ndarray) -> COO:
-------
COO
Sparse tensor created from the dense array.

Raises
------
ValueError, TypeError
Propagated from NumPy if X is not a valid array.
"""
# Get the non-zero indices and values from the dense array
coords = np.array(X.nonzero())
Expand Down Expand Up @@ -117,6 +134,13 @@ def get_item_array_from_subs(A: np.ndarray, ref_subs: ArraySequence) -> np.ndarr
-------
np.ndarray
A 1-dimensional array containing the values of the tensor at the specified indices.

Raises
------
IndexError
If any index is out of bounds.
ValueError
If the arrays in ref_subs have mismatched lengths.
"""
return np.array([A[tuple(sub)] for sub in zip(*ref_subs)])

Expand All @@ -140,7 +164,13 @@ def check_symmetric(

Returns
-------
True if the matrix is symmetric, False otherwise.
True if matrix (a) (or every matrix in the list) equals its transpose
within rtol/atol; False otherwise.

Raises
------
ValueError
If shapes are incompatible for transpose comparison.
"""

if isinstance(a, list):
Expand All @@ -163,9 +193,14 @@ def build_edgelist(A: COO, layer: int) -> pd.DataFrame:
Returns
-------
pd.DataFrame
DataFrame containing the edgelist for the specified layer with columns 'source', 'target', and 'L<layer>'.
"""
One row per non-zero entry, with columns 'source', 'target',
and 'L<layer>' (e.g. 'L0').

Raises
------
AttributeError
If A has no .tocoo() method.
"""
# Convert the input sparse matrix A to COOrdinate format
A_coo = A.tocoo()

Expand Down Expand Up @@ -243,8 +278,19 @@ def write_adjacency(
Name of the column to consider as source of the edge.
alter : str
Name of the column to consider as target of the edge.
"""

Returns
-------
None
Writes a CSV to folder + fname.

Raises
------
IndexError
If G is empty (G[0] fails).
OSError
On file write failure.
"""
N = G[0].number_of_nodes()
L = len(G)
B = np.empty(shape=[len(G), N, N])
Expand Down Expand Up @@ -285,7 +331,8 @@ def create_design_matrix(
Returns
-------
X : DataFrame
Design matrix
Columns [nodeID, attr_name]; node labels from dict keys,
attributes from dict values.
"""
# Create a DataFrame from the metadata dictionary
X = pd.DataFrame.from_dict(metadata, orient="index", columns=[attr_name])
Expand Down Expand Up @@ -315,6 +362,19 @@ def save_design_matrix(
Path of the folder where to save the files.
fname : str
Name of the design matrix file.

Returns
-------
None
Saves CSV to folder/X_{perc_digits}.csv (filename derived from
str(perc)).

Raises
------
OSError
On file write failure.
IndexError
If perc string is too short for character indexing.
"""
# Construct the file path using f-string formatting and Path
file_path = Path(folder) / f"{fname}{str(perc)[0]}_{str(perc)[2]}.csv"
Expand Down Expand Up @@ -377,10 +437,16 @@ def log_and_raise_error(error_type: Type[BaseException], message: str) -> None:
message : str
The error message to be logged and included in the exception.

Returns
-------
None
This function never returns normally.

Raises
------
BaseException
An exception of the specified type with the given message.
error_type
Always raised after logging at ERROR level. Commonly
ValueError, RuntimeError, or NotImplementedError.
"""

# Log the error message
Expand All @@ -400,10 +466,16 @@ def flt(x: float, d: int = 3) -> float:
Number to be rounded.
d : int
Number of decimal places to round to.

Returns
-------
float
The input number rounded to the specified number of decimal places.
The input number rounded (round(x, d)) to the specified number of decimal places.

Raises
------
TypeError
If x is not roundable.
"""
return round(x, d)

Expand All @@ -420,6 +492,7 @@ def get_or_create_rng(rng: Optional[np.random.Generator] = None) -> np.random.Ge
Returns
-------
np.random.Generator
Initialized random number generator.
rng if not None; otherwise a new unseeded
np.random.default_rng().
"""
return rng if rng else np.random.default_rng()