Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
f71a486
Merge branch 'devel/extract_polyhedron_class' into devel/array_data_d…
joscao Oct 24, 2023
56599d3
Provide has_data_dependency implementation
joscao Oct 24, 2023
6b3c6a2
Fix renaming
joscao Oct 24, 2023
54d1422
Deprecate use of bounds_of_one_d_system
joscao Oct 24, 2023
e82be39
Format
joscao Oct 24, 2023
b60d9c3
Provide tests for array data dpendency detection
joscao Oct 24, 2023
41870af
Provide showcase for has_data_dependency
joscao Oct 20, 2023
7fb00c8
Avoid cyclic imports
joscao Oct 25, 2023
47572da
fix type hinting problem for python3.8
joscao Oct 26, 2023
229c504
Update documentation style
joscao Oct 26, 2023
ae41751
Update docstring style
joscao Oct 26, 2023
58e0a98
Format
joscao Oct 26, 2023
9c27124
Testing for from_nested_loops
joscao Oct 27, 2023
adbddca
Fix import order
joscao Oct 27, 2023
d900bdc
Bugfix: gcd in python3.8 handles only two arguments
joscao Oct 25, 2023
7d08953
Fix docstrings and imports
joscao Oct 27, 2023
d9b97aa
Fix unnowkn function problem
joscao Oct 27, 2023
14633ac
Simplify and provide construct_affine_array_access_function_represent…
joscao Oct 27, 2023
91e072e
Make ortools optional package
joscao Oct 25, 2023
bbd68a2
Make ortools optional
joscao Oct 25, 2023
cb5895d
Provide optional dataflow_analysis
joscao Oct 27, 2023
ae41b3d
Replace match statement for python3.8
joscao Oct 27, 2023
a9cc369
Fix prefer [] over list()]
joscao Oct 27, 2023
9631956
Fix gcd python3.8 can only have 2 arguments problem
joscao Oct 27, 2023
a0f1295
No np.typing but numpy.typing
joscao Oct 27, 2023
603acf9
Simple for access function creation
joscao Oct 27, 2023
2102b16
Fix multi import
joscao Oct 27, 2023
e031348
Enhance documentation
joscao Oct 27, 2023
e7529a1
Tame yield_one_d_systems
joscao Nov 2, 2023
11103e4
Reduce noise & require check for empty left hand side
joscao Nov 2, 2023
d0fc2cf
Format
joscao Nov 2, 2023
7717a38
Example 35 shows working of independet variable test --> independt of…
joscao Nov 2, 2023
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
625 changes: 625 additions & 0 deletions example/06_array_data_dependency_detection.ipynb

Large diffs are not rendered by default.

480 changes: 480 additions & 0 deletions loki/analyse/analyse_array_data_dependency_detection.py

Large diffs are not rendered by default.

163 changes: 90 additions & 73 deletions loki/analyse/util_linear_algebra.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@
# granted to it by virtue of its status as an intergovernmental organisation
# nor does it submit to any jurisdiction.

from numpy import zeros_like, dot
from numpy import vstack, hstack
from numpy import all as np_all, sum as np_sum, isin as np_isin
import numpy as np

__all__ = [
"back_substitution",
Expand All @@ -21,75 +19,78 @@ def is_independent_system(matrix):
"""
Check if a linear system of equations can be split into independent one-dimensional problems.

Args:
matrix (numpy.ndarray): A rectangular matrix representing coefficients.
Parameters
----------
matrix : numpy.ndarray
A rectangular matrix representing coefficients.

Returns:
bool: True if the system can be split into independent one-dimensional problems, False otherwise.
Returns
-------
bool
True if the system can be split into independent one-dimensional problems, False otherwise.

This function checks whether a linear system of equations in the form of matrix x [operator] right_hand_side
Notes
-----
This function checks whether a linear system of equations in the form of `matrix [operator] right_hand_side`
can be split into independent one-dimensional problems. The number of problems is determined by the
number of variables (the row number of the matrix).

Each problem consists of a coefficient vector and a right-hand side. The system can be considered independent
if each row of the matrix has exactly one non-zero coefficient or no non-zero coefficients.
"""

return np_all(np_isin(np_sum(matrix != 0, axis=1), [0, 1]))
return np.all(np.isin(np.sum(matrix != 0, axis=1), [0, 1]))


def yield_one_d_systems(matrix, right_hand_side):
"""
Split a linear system of equations (<=, >= or ==) into independent one-dimensional problems.

Args:
matrix (numpy.ndarray): A rectangular matrix representing coefficients.
right_hand_side (numpy.ndarray): The right-hand side vector.

Yields:
tuple[single_dimensional_array, single_dimensional_array]:
A tuple containing a coefficient vector and the corresponding right-hand side.

This function takes a linear system of equations in the form of matrix x [operator] right_hand_side,
where "matrix" is a rectangular matrix, x is a vector of variables, and "right_hand_side" is
Split a linear system of equations (<=, >=, or ==) into independent one-dimensional problems.

Parameters
----------
matrix : numpy.ndarray
A rectangular matrix representing coefficients.
right_hand_side : numpy.ndarray
The right-hand side vector.

Yields
------
tuple[numpy.ndarray, numpy.ndarray]
A tuple containing a coefficient vector and the corresponding right-hand side.

Notes
-----
The independence of the problems is NOT explicitly checked; call `is_independent_system` before using this
function if unsure.

This function takes a linear system of equations in the form of `matrix [operator] right_hand_side`,
where "matrix" is a rectangular matrix, "x" is a vector of variables, and "right_hand_side" is
the right-hand side vector. It splits the system into assumed independent one-dimensional problems.

Each problem consists of a coefficient vector and a right-hand side. The number of problems is equal to the
number of variables (the row number of the matrix).

Note:
- The independence of the problems is not explicitly checked, call is_independent_system before using this
function if unsure.

Example:
```
Example
-------
```python
for A, b in yield_one_d_systems(matrix, right_hand_side):
# Solve the one-dimensional problem A * x = b
solution = solve_one_d_system(A, b)
```
"""
# drop completly empty rows
mask = ~np_all(hstack((matrix, right_hand_side)) == 0, axis=1)
matrix = matrix[mask]
right_hand_side = right_hand_side[mask]

# yield systems with empty left hand side (A) and non empty right hand side
mask = np_all(matrix == 0, axis=1)
if right_hand_side[mask].size == 0:
return

for A, b in zip(matrix[mask].T, right_hand_side[mask].T):
yield A, b
mask = np.all(matrix == 0, axis=1)
if right_hand_side[mask].size != 0:
for A in matrix[mask].T:
yield A.reshape((-1,1)), right_hand_side[mask]

matrix = matrix[~mask]
right_hand_side = right_hand_side[~mask]

if right_hand_side.size == 0:
return

for A, b in zip(matrix.T, right_hand_side.T):
mask = A != 0
yield A[mask], b[mask]
if right_hand_side.size != 0:
for A in matrix.T:
mask = A != 0
yield A[mask].reshape((-1,1)), right_hand_side[mask]


def back_substitution(
Expand All @@ -100,33 +101,42 @@ def back_substitution(
"""
Solve a linear system of equations using back substitution for an upper triangular square matrix.

Args:
upper_triangular_square_matrix (numpy.ndarray): An upper triangular square matrix (R).
right_hand_side (numpy.ndarray): A vector (y) on the right-hand side of the equation Rx = y.
division_operation (function, optional): A custom division operation function. Default is standard division (/).
Parameters
----------
upper_triangular_square_matrix : numpy.ndarray
An upper triangular square matrix (R).

right_hand_side : numpy.ndarray
A vector (y) on the right-hand side of the equation Rx = y.

Returns:
numpy.ndarray: The solution vector (x) to the system of equations Rx = y.
division_operation : function, optional
A custom division operation function. Default is standard division (/).

Returns
-------
numpy.ndarray
The solution vector (x) to the system of equations Rx = y.

Notes
-----
The function performs back substitution to find the solution vector x for the equation Rx = y,
where R is an upper triangular square matrix and y is a vector. The division_operation
function is used for division (e.g., for custom division operations).

Note:
- The function assumes that the upper right element of the upper_triangular_square_matrix (R)
is nonzero for proper back substitution.
The function assumes that the upper right element of the upper_triangular_square_matrix (R)
is nonzero for proper back substitution.
"""
R = upper_triangular_square_matrix
y = right_hand_side

x = zeros_like(y)
x = np.zeros_like(y)

assert R[-1, -1] != 0

x[-1] = divison_operation(y[-1], R[-1, -1])

for i in range(len(y) - 2, -1, -1):
x[i] = divison_operation((y[i] - dot(R[i, i + 1 :], x[i + 1 :])), R[i, i])
x[i] = divison_operation((y[i] - np.dot(R[i, i + 1 :], x[i + 1 :])), R[i, i])

return x

Expand All @@ -135,25 +145,32 @@ def generate_row_echelon_form(
A, conditional_check=lambda A: None, division_operator=lambda x, y: x / y
):
"""
Calculate the Row Echelon Form (REF) of a matrix A using Gaussian elimination.

Args:
A (numpy.ndarray): The input matrix for which RREF is to be calculated.
conditional_check (function, optional): A custom function to check conditions during the computation.
division_operation (function, optional): A custom division operation function. Default is standard division (/).

Returns:
numpy.ndarray: The REF of the input matrix A.

This function computes the Row Echelon Form (RREF) of a given matrix A using Gaussian elimination.
You can provide a custom conditional_check function to add checks or operations during the computation.
You can also specify a custom division_operation function for division (e.g., for custom division operations).

Note:
Calculate the Row Echelon Form (REF) of a matrix.

Parameters
----------
A : numpy.ndarray
The input matrix for which the REF is to be calculated.
conditional_check : function, optional
A custom function to check conditions during the computation.
division_operation : function, optional
A custom division operation function. Default is standard division (/).

Returns
-------
numpy.ndarray
The REF of the input matrix A.

Notes
-----
- If the input matrix has no rows or columns, it is already in REF, and the function returns itself.
- The function utilizes the specified division operation (default is standard division) for division.

Reference: https://math.stackexchange.com/questions/3073083/how-to-reduce-matrix-into-row-echelon-form-in-numpy
Reference
---------
https://math.stackexchange.com/a/3073117
for question:
https://math.stackexchange.com/questions/3073083/how-to-reduce-matrix-into-row-echelon-form-in-numpy
"""
# if matrix A has no columns or rows,
# it is already in REF, so we return itself
Expand All @@ -170,7 +187,7 @@ def generate_row_echelon_form(
# we perform REF on matrix from second column
B = generate_row_echelon_form(A[:, 1:], conditional_check, division_operator)
# and then add the first zero-column back
return hstack([A[:, :1], B])
return np.hstack([A[:, :1], B])

# if non-zero element happens not in the first row,
# we switch rows
Expand All @@ -190,4 +207,4 @@ def generate_row_echelon_form(
B = generate_row_echelon_form(A[1:, 1:], conditional_check, division_operator)

# we add first row and first (zero) column, and return
return vstack([A[:1], hstack([A[1:, :1], B])])
return np.vstack([A[:1], np.hstack([A[1:, :1], B])])
Loading