Source code for pm4py.algo.analysis.woflan.place_invariants.place_invariants
'''
PM4Py – A Process Mining Library for Python
Copyright (C) 2024 Process Intelligence Solutions UG (haftungsbeschränkt)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see this software project's root or
visit <https://www.gnu.org/licenses/>.
Website: https://processintelligence.solutions
Contact: info@processintelligence.solutions
'''
import numpy as np
[docs]
def compute_place_invariants(net):
"""
We compute the NUllspace of the incidence matrix and obtain the place-invariants.
:param net: Petri Net of which we want to know the place invariants.
:return: Set of place invariants of the given Petri Net.
"""
def compute_incidence_matrix(net):
"""
Given a Petri Net, the incidence matrix is computed. An incidence matrix has n rows (places) and m columns
(transitions).
:param net: Petri Net object
:return: Incidence matrix
"""
n = len(net.transitions)
m = len(net.places)
C = np.zeros((m, n))
i = 0
transition_list = sorted(list(net.transitions), key=lambda x: x.name)
place_list = sorted(list(net.places), key=lambda x: x.name)
while i < n:
t = transition_list[i]
for in_arc in t.in_arcs:
# arcs that go to transition
C[place_list.index(in_arc.source), i] -= 1
for out_arc in t.out_arcs:
# arcs that lead away from transition
C[place_list.index(out_arc.target), i] += 1
i += 1
return C
def rref(A, tol=1.0e-12):
m, n = A.shape
i, j = 0, 0
jb = []
while i < m and j < n:
# Find value and index of largest element in the remainder of
# column j
k = np.argmax(np.abs(A[i:m, j])) + i
p = np.abs(A[k, j])
if p <= tol:
# The column is negligible, zero it out
A[i:m, j] = 0.0
j += 1
else:
# Remember the column index
jb.append(j)
if i != k:
# Swap the i-th and k-th rows
A[[i, k], j:n] = A[[k, i], j:n]
# Divide the pivot row i by the pivot element A[i, j]
A[i, j:n] = A[i, j:n] / A[i, j]
# Subtract multiples of the pivot row from all the other rows
for k in range(m):
if k != i:
A[k, j:n] -= A[k, j] * A[i, j:n]
i += 1
j += 1
# Finished
return A, jb
def extract_basis_vectors(incidence_matrix):
"""
The name of the method describes what we want t achieve. We calculate the nullspace of the transposed identity matrix.
:param incidence_matrix: Numpy Array
:return: a collection of numpy arrays that form a base of transposed A
"""
# To have the same dimension as described as in
# https://www7.in.tum.de/~esparza/fcbook-middle.pdf and to get the
# correct nullspace, we have to transpose
A = np.transpose(incidence_matrix)
reduced, pivots = rref(A)
free_vars = [i for i in range(A.shape[1]) if i not in pivots]
basis = []
for free_var in free_vars:
vec = np.zeros(A.shape[1])
vec[free_var] = 1
for piv_row, piv_col in enumerate(pivots):
vec[piv_col] -= reduced[piv_row, free_var]
basis.append(vec.tolist())
z = [[] for k in range(len(basis))]
if basis:
for i in range(len(basis[0])):
for k in range(len(basis)):
z[k].append([basis[k][i]])
z = np.array(z)
return z
A = compute_incidence_matrix(net)
return extract_basis_vectors(A)