Spaces:
Running
Running
''' | |
This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de). | |
PM4Py is free software: you can redistribute it and/or modify | |
it under the terms of the GNU General Public License as published by | |
the Free Software Foundation, either version 3 of the License, or | |
(at your option) any later version. | |
PM4Py 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 General Public License for more details. | |
You should have received a copy of the GNU General Public License | |
along with PM4Py. If not, see <https://www.gnu.org/licenses/>. | |
''' | |
from enum import Enum | |
from typing import Optional, Dict, Any, Tuple, List | |
import numpy as np | |
from pm4py.objects.petri_net.utils import align_utils, petri_utils as petri_utils | |
from pm4py.objects.petri_net.utils.incidence_matrix import IncidenceMatrix | |
from pm4py.objects.petri_net.obj import PetriNet, Marking | |
from pm4py.util import exec_utils, constants | |
from pm4py.util.lp import solver | |
class Parameters(Enum): | |
CASE_ID_KEY = constants.PARAMETER_CONSTANT_CASEID_KEY | |
ACTIVITY_KEY = constants.PARAMETER_CONSTANT_ACTIVITY_KEY | |
COSTS = "costs" | |
INCIDENCE_MATRIX = "incidence_matrix" | |
A = "A_matrix" | |
FULL_BOOTSTRAP_REQUIRED = "full_bootstrap_required" | |
class MarkingEquationSolver(object): | |
def __init__(self, net: PetriNet, im: Marking, fm: Marking, | |
parameters: Optional[Dict[Any, Any]] = None): | |
""" | |
Constructor | |
Parameters | |
--------------- | |
net | |
Petri net | |
im | |
Initial marking | |
fm | |
Final marking | |
parameters | |
Parameters of the algorithm, including: | |
- Parameters.CASE_ID_KEY => attribute to use as case identifier | |
- Parameters.ACTIVITY_KEY => attribute to use as activity | |
- Parameters.COSTS => (if provided) the cost function (otherwise the default cost function is applied) | |
- Parameters.INCIDENCE_MATRIX => (if provided) the incidence matrix of the sync product net | |
- Parameters.A => (if provided) the A numpy matrix of the incidence matrix | |
- Parameters.FULL_BOOTSTRAP_REQUIRED => The preset/postset of places/transitions need to be inserted | |
""" | |
if parameters is None: | |
parameters = {} | |
costs = exec_utils.get_param_value(Parameters.COSTS, parameters, None) | |
if costs is None: | |
costs = align_utils.construct_standard_cost_function(net, align_utils.SKIP) | |
self.net = net | |
self.ini = im | |
self.fin = fm | |
self.costs = costs | |
self.incidence_matrix = exec_utils.get_param_value(Parameters.INCIDENCE_MATRIX, parameters, | |
IncidenceMatrix(self.net)) | |
self.Aeq = exec_utils.get_param_value(Parameters.A, parameters, np.asmatrix(self.incidence_matrix.a_matrix)) | |
self.full_bootstrap_required = exec_utils.get_param_value(Parameters.FULL_BOOTSTRAP_REQUIRED, parameters, True) | |
self.__build_entities() | |
self.__build_problem_components() | |
def __build_entities(self): | |
""" | |
Builds entities useful to define the marking equation | |
""" | |
transitions = self.incidence_matrix.transitions | |
self.inv_indices = {y: x for x, y in transitions.items()} | |
self.inv_indices = [self.inv_indices[i] for i in range(len(self.inv_indices))] | |
self.ini_vec = np.matrix(self.incidence_matrix.encode_marking(self.ini)).transpose() | |
self.fin_vec = np.matrix(self.incidence_matrix.encode_marking(self.fin)).transpose() | |
if self.full_bootstrap_required: | |
petri_utils.decorate_transitions_prepostset(self.net) | |
petri_utils.decorate_places_preset_trans(self.net) | |
def __build_problem_components(self): | |
""" | |
Builds the components needed to solve the marking equation | |
""" | |
self.beq = self.fin_vec - self.ini_vec | |
self.Aub = -np.eye(self.Aeq.shape[1]) | |
self.bub = np.zeros((self.Aeq.shape[1], 1)) | |
self.c = [self.costs[self.inv_indices[i]] for i in range(len(self.inv_indices))] | |
if solver.DEFAULT_LP_SOLVER_VARIANT == solver.CVXOPT_SOLVER_CUSTOM_ALIGN: | |
from cvxopt import matrix | |
self.Aeq_transf = matrix(self.Aeq.astype(np.float64)) | |
self.Aub_transf = matrix(self.Aub.astype(np.float64)) | |
self.c_transf = matrix([1.0 * x for x in self.c]) | |
else: | |
self.Aeq_transf = self.Aeq | |
self.Aub_transf = self.Aub | |
self.c_transf = self.c | |
def get_components(self) -> Tuple[Any, Any, Any, Any, Any]: | |
""" | |
Retrieve the components (Numpy matrixes) of the problem | |
Returns | |
--------------- | |
c | |
objective function | |
Aub | |
Inequalities matrix | |
bub | |
Inequalities vector | |
Aeq | |
Equalities matrix | |
beq | |
Equalities vector | |
""" | |
if solver.DEFAULT_LP_SOLVER_VARIANT == solver.CVXOPT_SOLVER_CUSTOM_ALIGN: | |
from cvxopt import matrix | |
self.beq_transf = matrix(self.beq.astype(np.float64)) | |
self.bub_transf = matrix(self.bub.astype(np.float64)) | |
else: | |
self.beq_transf = self.beq | |
self.bub_transf = self.bub | |
return self.c_transf, self.Aub_transf, self.bub_transf, self.Aeq_transf, self.beq_transf | |
def change_ini_vec(self, ini: Marking): | |
""" | |
Changes the initial marking of the synchronous product net | |
Parameters | |
-------------- | |
ini | |
Initial marking | |
""" | |
self.ini = ini | |
self.ini_vec = np.matrix(self.incidence_matrix.encode_marking(ini)).transpose() | |
self.beq = self.fin_vec - self.ini_vec | |
def get_x_vector(self, sol_points: List[int]) -> List[int]: | |
""" | |
Returns the x vector of the solution | |
Parameters | |
-------------- | |
sol_points | |
Solution of the integer problem | |
Returns | |
--------------- | |
x | |
X vector | |
""" | |
return sol_points | |
def get_h(self, sol_points: List[int]) -> int: | |
""" | |
Returns the value of the heuristics | |
Parameters | |
-------------- | |
sol_points | |
Solution of the integer problem | |
Returns | |
-------------- | |
h | |
Heuristics value | |
""" | |
return int(np.dot(sol_points, self.c)) | |
def get_activated_transitions(self, sol_points: List[int]) -> List[PetriNet.Transition]: | |
""" | |
Gets the transitions of the synchronous product net that are non-zero | |
in the solution of the marking equation | |
Parameters | |
-------------- | |
sol_points | |
Solution of the integer problem | |
Returns | |
-------------- | |
act_trans | |
Activated transitions | |
""" | |
act_trans = [] | |
for i in range(len(sol_points)): | |
for j in range(sol_points[i]): | |
act_trans.append(self.inv_indices[i]) | |
return act_trans | |
def solve(self) -> Tuple[int, List[int]]: | |
""" | |
Solves the marking equation, returning the heuristics and the x vector | |
Returns | |
------------- | |
h | |
Heuristics value | |
x | |
X vector | |
""" | |
c, Aub, bub, Aeq, beq = self.get_components() | |
return self.solve_given_components(c, Aub, bub, Aeq, beq) | |
def solve_given_components(self, c, Aub, bub, Aeq, beq): | |
""" | |
Solves the linear problem given the components | |
Parameters | |
-------------- | |
c | |
Objective vector | |
Aub | |
Inequalities matrix | |
bub | |
Inequalities vector | |
Aeq | |
Equalities matrix | |
beq | |
Equalities vector | |
Returns | |
------------- | |
h | |
Heuristics value | |
x | |
X vector | |
""" | |
if solver.DEFAULT_LP_SOLVER_VARIANT == solver.CVXOPT_SOLVER_CUSTOM_ALIGN and type(c) is list: | |
from cvxopt import matrix | |
Aub = matrix(Aub.astype(np.float64)) | |
bub = matrix(bub.astype(np.float64)) | |
Aeq = matrix(Aeq.astype(np.float64)) | |
beq = matrix(beq.astype(np.float64)) | |
c = matrix([1.0 * x for x in c]) | |
sol = solver.apply(c, Aub, bub, Aeq, beq, variant=solver.DEFAULT_LP_SOLVER_VARIANT) | |
sol_points = solver.get_points_from_sol(sol, variant=solver.DEFAULT_LP_SOLVER_VARIANT) | |
if sol_points is not None: | |
x = self.get_x_vector(sol_points) | |
x = [int(y) for y in x] | |
h = self.get_h(sol_points) | |
return h, x | |
return None, None | |
def get_firing_sequence(self, x: List[int]) -> Tuple[List[PetriNet.Transition], bool, int]: | |
""" | |
Gets a firing sequence from the X vector | |
Parameters | |
---------------- | |
x | |
X vector | |
Returns | |
---------------- | |
firing_sequence | |
Firing sequence | |
reach_fm | |
Boolean value that is true whether the firing sequence reaches the final marking | |
explained_events | |
Number of explaned events by the firing sequence | |
""" | |
activated_transitions = self.get_activated_transitions(x) | |
firing_sequence, reach_fm, explained_events = align_utils.search_path_among_sol(self.net, self.ini, | |
self.fin, | |
activated_transitions) | |
return firing_sequence, reach_fm, explained_events | |
def build(net: PetriNet, im: Marking, fm: Marking, | |
parameters: Optional[Dict[Any, Any]] = None) -> MarkingEquationSolver: | |
""" | |
Builds the marking equation out of a Petri net | |
Parameters | |
--------------- | |
net | |
Petri net | |
im | |
Initial marking | |
fm | |
Final marking | |
parameters | |
Parameters of the algorithm, including: | |
- Parameters.CASE_ID_KEY => attribute to use as case identifier | |
- Parameters.ACTIVITY_KEY => attribute to use as activity | |
- Parameters.COSTS => (if provided) the cost function (otherwise the default cost function is applied) | |
- Parameters.INCIDENCE_MATRIX => (if provided) the incidence matrix of the Petri net | |
- Parameters.A => (if provided) the A numpy matrix of the incidence matrix | |
- Parameters.FULL_BOOTSTRAP_REQUIRED => The preset/postset of places/transitions need to be inserted | |
""" | |
if parameters is None: | |
parameters = {} | |
return MarkingEquationSolver(net, im, fm, parameters=parameters) | |
def get_h_value(solver: MarkingEquationSolver, parameters: Optional[Dict[Any, Any]] = None) -> int: | |
""" | |
Gets the heuristics value from the marking equation | |
Parameters | |
-------------- | |
solver | |
Marking equation solver (class in this file) | |
parameters | |
Possible parameters of the algorithm | |
""" | |
return solver.solve()[0] | |