Commit e75662ed authored by VIGNET Pierre's avatar VIGNET Pierre
Browse files

Merge branch 'pypi_packaging' into 'master'

Pypi packaging

See merge request !1
parents 030ce472 b1752153
......@@ -11,40 +11,51 @@ Official website: [link](http://cadbiom.genouest.org/)
CADBIOM is mainly developed in Python 2.7.
Before running Cadbiom, the following system packages have to be installed from
the distribution packages library (or similar, depending on your operating system):
the distribution packages library (or similar, depending on your operating system).<br>
**Most of these packages are already installed on basic GNU/Linux systems.**
### Debian-like systems (Ubuntu):
* python-gtksourceview2
* python2.7-dev
* libxml2-dev
* libxslt1-dev
* libgraphviz-dev
* python-glade2
* python-gtk2
* libxslt1-dev (Library providing the Gnome XSLT engine)
* pkg-config (Fix errors when installing pygraphviz and when config is not loaded)
* libgraphviz-dev (GUI layouts)
* python-gtk2 (GUI)
* python-glade2 (GUI)
* python-gtksourceview2 (GUI)
* python-tk (GUI)
Fix errors when installing pygraphviz and when config is not loaded:
You can install these dependencies with the following command:
* pkg-config
sudo apt-get install python-gtksourceview2 python2.7-dev libxml2-dev libxslt1-dev \
libxslt1-dev libgraphviz-dev pkg-config python-glade2 python-gtk2 python-tk
For Matplotlib installation:
### Red Hat-like systems (Fedora/CentOS)
* libpng12-dev
* libfreetype6-dev
* python-devel
* libxml-devel
* redhat-rpm-config (lxml config)
* libxslt-devel (Library providing the Gnome XSLT engine)
* graphviz-devel (GUI layouts)
* pygtk2 (GUI)
* pygtk2-libglade (GUI)
* pygtksourceview (GUI)
You can install these dependencies with the following command (on Debian systems):
You can install these dependencies with the following command:
sudo apt-get install python-gtksourceview2 python2.7-dev libxml2-dev libxslt1-dev \
libxslt1-dev libgraphviz-dev pkg-config python-glade2 python-gtk2 libpng12-dev libfreetype6-dev
sudo dnf install python-devel libxml-devel redhat-rpm-config libxslt-devel graphviz-devel \
pygtk2 pygtk2-libglade pygtksourceview
## Python requirements
When `cadbiom` library is installed, the following Python packages
will be installed from pypi repository:
**are automatically** installed from pypi repository:
* lxml
* networkx
* pygraphviz
* matplotlib
* numpy
* pycryptosat
......@@ -88,10 +99,10 @@ The location of this script may vary depending on your Linux distro
Cadbiom software requires a SAT solver which is
proposed as a Python wrapper by an independant library (pycryptosat).
*For now, an non official version of pycryptosat is on pypi and it is used by packages;
*For now, an non official version of pycryptosat is on pypi and it is used by packages;
Source of the fork if you want to compile it manually:
[Cryptominisat repository](https://github.com/msoos/cryptominisat/tree/5.0.1)*
[Cryptominisat repository](https://gitlab.inria.fr/pvignet/pycryptosat/tree/5.0.1_cmake_dev)*
Here you will find the installation commands of the pycryptosat package:
......
include README.md
\ No newline at end of file
......@@ -36,14 +36,6 @@ LOGGER = cm.logger()
def launch_researchs(args):
"""Parse arguments and launch Cadbiom search of MACs
(Minimal Activation Conditions).
- If there is no input file, there will be only one process.
- If an input file is given, there will be 1 process per line (per logical
formula on each line).
- all_macs: Solver will try to search all macs with 0 to
the maximum of steps allowed.
- continue: If there is a mac file from a previous work, last frontier
places will be reloaded.
"""
# Module import
......@@ -64,10 +56,6 @@ def launch_sort(args):
def parse_trajectories(args):
"""Parse a complete solution file and make a representation of trajectories.
The output is in graphml file format and is exported in 'graphs' directory.
.. note:: Requires the model file.
"""
# Module import
......@@ -87,8 +75,6 @@ def model_comp(args):
Check if the 2 given models have the same topology,
nodes & edges attributes/roles.
.. note:: You can export a graphml file for the 2 models.
"""
# Module import
......@@ -109,8 +95,6 @@ def model_infos(args):
"""Model informations.
Get number of nodes, edges, centralities (degree, closeness, betweenness).
.. note:: You can export a graphml file for model.
"""
# Module import
......@@ -128,10 +112,7 @@ def model_infos(args):
def merge_cams(args):
"""Merge solutions to a csv file.
.. note:: CSV file: <Final property formula>;<cam>
"""
"""Merge solutions to a csv file."""
# Module import
import solution_merge
......@@ -192,6 +173,7 @@ class ReadableDir(argparse.Action):
def main():
"""Argument parser"""
# parser configuration
parser = argparse.ArgumentParser(description=__doc__)
......@@ -206,27 +188,40 @@ def main():
# final_prop = "P"
# start_prop = None
# inv_prop = None
parser_input_file = subparsers.add_parser('compute_macs',
help=launch_researchs.__doc__)
parser_input_file = subparsers.add_parser(
'compute_macs',
help=launch_researchs.__doc__,
formatter_class=argparse.ArgumentDefaultsHelpFormatter
)
parser_input_file.add_argument('chart_file')
# parser_input_file.add_argument('final_prop')
# Get final_property alone OR an input_file containing multiple properties
group = parser_input_file.add_mutually_exclusive_group()
group.add_argument('final_prop', nargs='?')
group.add_argument('--input_file', action=ReadableFile, nargs='?')
# If input_file is set, we can compute all combinations of given elements
group.add_argument('--input_file', action=ReadableFile, nargs='?',
help="Without input file, there will be only one process. " + \
"While there will be 1 process per line (per logical formula " + \
"on each line)")
parser_input_file.add_argument('--output', action=ReadableDir, nargs='?',
default='result/', help="Output directory.")
# default: False
parser_input_file.add_argument('--combinations', action='store_true')
parser_input_file.add_argument('--steps', type=int, nargs='?', default=10)
parser_input_file.add_argument('--combinations', action='store_true',
help="If input_file is set, we can compute all combinations of " + \
"given elements on each line")
parser_input_file.add_argument('--steps', type=int, nargs='?', default=10,
help="Maximum of allowed steps to find macs")
# https://docs.python.org/dev/library/argparse.html#action
# all_macs to False by default
parser_input_file.add_argument('--all_macs', action='store_true')
parser_input_file.add_argument('--all_macs', action='store_true',
help="Solver will try to search all macs with 0 to the maximum of " + \
"allowed steps.")
# continue to False by default
parser_input_file.add_argument('--continue', action='store_true')
parser_input_file.add_argument('--continue', action='store_true',
help="Resume previous computations; if there is a mac file from " + \
"a previous work, last frontier places will be reloaded.")
parser_input_file.add_argument('--start_prop', nargs='?', default=None)
parser_input_file.add_argument('--inv_prop', nargs='?', default=None)
parser_input_file.add_argument('--output', action=ReadableDir, nargs='?',
default='result/')
parser_input_file.set_defaults(func=launch_researchs)
......@@ -234,59 +229,84 @@ def main():
# Solution file (complete or not)
parser_solutions_sort = subparsers.add_parser('sort_solutions',
help=launch_sort.__doc__)
parser_solutions_sort.add_argument('sol_file')
parser_solutions_sort.add_argument('sol_file',
help="Solution file (output of 'compute_macs' command).")
parser_solutions_sort.set_defaults(func=launch_sort)
# subparser: Representation of the trajectories of MACs in a complete file.
# Model file (xml : cadbiom language)
# Solution file (cam_complete)
parser_trajectories = subparsers.add_parser('parse_trajectories',
help=parse_trajectories.__doc__)
parser_trajectories.add_argument('chart_file')
parser_trajectories.add_argument('sol_file')
parser_trajectories = subparsers.add_parser(
'parse_trajectories',
help=parse_trajectories.__doc__,
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser_trajectories.add_argument('chart_file',
help="bcx model file.")
parser_trajectories.add_argument('sol_file',
help="Solution file (output of 'compute_macs' command).")
parser_trajectories.add_argument('--output', action=ReadableDir,
nargs='?', default='graphs/')
nargs='?', default='graphs/',
help="Output directory for graphml files.")
parser_trajectories.set_defaults(func=parse_trajectories)
# subparser: Merge solutions to a csv file
# Solution file (cam)
# Output (csv)
parser_merge_cams = subparsers.add_parser('merge_cams',
help=merge_cams.__doc__)
parser_merge_cams = subparsers.add_parser(
'merge_cams',
help=merge_cams.__doc__,
formatter_class=argparse.ArgumentDefaultsHelpFormatter
)
parser_merge_cams.add_argument('solutions_directory', nargs='?',
default='result/')
default='result/')
parser_merge_cams.add_argument('--output', nargs='?',
default='result/merged_cams.csv')
default='result/merged_cams.csv',
help="CSV file: <Final property formula>;<cam>")
parser_merge_cams.set_defaults(func=merge_cams)
# subparser: Model comparison
# 2 models
parser_model_comparison = subparsers.add_parser('model_comp',
help=model_comp.__doc__)
parser_model_comparison.add_argument('model_file_1')
parser_model_comparison.add_argument('model_file_2')
parser_model_comparison = subparsers.add_parser(
'model_comp',
help=model_comp.__doc__,
formatter_class=argparse.ArgumentDefaultsHelpFormatter
)
parser_model_comparison.add_argument('model_file_1',
help="bcx model file.")
parser_model_comparison.add_argument('model_file_2',
help="bcx model file.")
# Export graphs for the 2 models; default: false
parser_model_comparison.add_argument('--graphs', action='store_true')
parser_model_comparison.add_argument('--json', action='store_true')
parser_model_comparison.add_argument('--graphs', action='store_true',
help="Create 2 graphml files from the given models.")
parser_model_comparison.add_argument('--json', action='store_true',
help="Create a summary dumped into a json file.")
parser_model_comparison.add_argument('--output', action=ReadableDir,
nargs='?', default='graphs/')
nargs='?', default='graphs/',
help="Directory for created graphs files.")
parser_model_comparison.set_defaults(func=model_comp)
# subparser: Model infos
# 1 model
parser_model_infos = subparsers.add_parser('model_infos',
help=model_infos.__doc__)
parser_model_infos = subparsers.add_parser(
'model_infos',
help=model_infos.__doc__,
formatter_class=argparse.ArgumentDefaultsHelpFormatter
)
parser_model_infos.add_argument('model_file')
# Export graphs for the 2 models; default: false
parser_model_infos.add_argument('--graph', action='store_true')
parser_model_infos.add_argument('--json', action='store_true')
parser_model_infos.add_argument('--advanced', action='store_true')
parser_model_infos.add_argument('--graph', action='store_true',
help="Create a graphml file from the model.")
parser_model_infos.add_argument('--json', action='store_true',
help="Create a summary dumped into a json file.")
parser_model_infos.add_argument('--advanced', action='store_true',
help="Get centralities (degree, closeness, betweenness).")
parser_model_infos.add_argument('--output', action=ReadableDir,
nargs='?', default='graphs/')
nargs='?', default='graphs/',
help="Directory for created graphs files.")
parser_model_infos.set_defaults(func=model_infos)
......
......@@ -30,9 +30,14 @@ from collections import defaultdict
import networkx as nx
import itertools as it
import re
import matplotlib.pyplot as plt
import json
from logging import DEBUG
# Remove matplotlib dependency
# It is used on demand during the drawing of a graph
try:
import matplotlib.pyplot as plt
except ImportError:
pass
# Library imports
from cadbiom.models.guard_transitions.translators.chart_xml \
......@@ -149,14 +154,19 @@ def load_solutions(file):
def get_transitions(file, all_places=False):
"""Get all transitions in a file model (bcx format).
:param: Model in bcx format.
:type: <str>
:param arg1: Model in bcx format.
:param arg2: Ask to return all the places of the model.
(useful for get_frontier_places() that compute frontier places)
:type arg1: <str>
:type arg2: <bool>
:return: A dictionnary of events as keys, and transitions as values.
Since many transitions can define an event, values are lists.
Each transition is a tuple with: origin node, final node, attributes
like label and condition.
{u'h00': [('Ax', 'n1', {u'label': u'h00[]'}),]
:rtype: <dict <list <tuple <str>, <str>, <dict <str>: <str>>>>
If all_places is True, this func returns also a list of all places in
the model.
"""
parser = MakeModelFromXmlFile(file)
......@@ -178,9 +188,15 @@ def get_transitions(file, all_places=False):
# ori/ext entities, so we have to extract them and their respective
# conditions
if trans.event == '':
# null event without clock...
# null event without clock => StartNodes
# These nodes are used to resolve the problem of
# Strongly Connected Components (inactivated cycles in the graph)
# The nodes
# Avoids having SigConstExpr as event type in parse_event()
continue
# I create a transition (SCC-__start__?),
# and a node (__start__?) for this case.
trans.event = 'SCC-' + trans.ori.name
events = {trans.event: trans.condition}
elif re.match('_h_[0-9_\.]+', trans.event):
# 1 event (with 1 clock)
events = {trans.event: trans.condition}
......@@ -217,7 +233,14 @@ def get_transitions(file, all_places=False):
def get_frontier_places(transitions, all_places):
"""Return frontier places of a model (deducted from its transitions).
"""Return frontier places of a model (deducted from its transitions and
from all places of the model).
.. note:: why we use all_places from the model instead of
(input_places - output_places) to get frontier places ?
Because some nodes are only in conditions and not in transitions.
If we don't do that, these nodes are missing when we compute
valid paths from conditions.
:param arg1: Model's transitions.
{u'h00': [('Ax', 'n1', {u'label': u'h00[]'}),]
......@@ -665,7 +688,8 @@ def build_graph(solution, steps, transitions):
def draw_graph(output_dir, solution, solution_index, G,
transition_nodes, all_nodes,
edges_in_cond, edges):
edges_in_cond, edges,
matplotlib_export=False):
"""Draw graph with colors and export it to graphml format.
.. note:: Legend:
......@@ -702,13 +726,14 @@ def draw_graph(output_dir, solution, solution_index, G,
pos = nx.circular_layout(G)
# Legend of conditions in transition nodes
f = plt.figure(1)
ax = f.add_subplot(1,1,1)
text = '\n'.join(
node_dict['label'] for node_dict in unzip(transition_nodes)[1]
)
ax.text(0, 0, text, style='italic', fontsize=10,
bbox={'facecolor':'white', 'alpha':0.5, 'pad':10})
if matplotlib_export:
f = plt.figure(1)
ax = f.add_subplot(1,1,1)
text = '\n'.join(
node_dict['label'] for node_dict in unzip(transition_nodes)[1]
)
ax.text(0, 0, text, style='italic', fontsize=10,
bbox={'facecolor':'white', 'alpha':0.5, 'pad':10})
# Draw nodes:
# - red: frontier places (in solution variable),
......@@ -731,8 +756,12 @@ def draw_graph(output_dir, solution, solution_index, G,
# Color nodes
colors = [color_map(node) for node in G.nodes_iter()]
nx.draw(G, pos=pos, with_labels=True,
node_color=colors, node_size=1000, alpha=0.5, ax=ax)
if matplotlib_export:
nx.draw(G, pos=pos, with_labels=True,
node_color=colors, node_size=1000, alpha=0.5, ax=ax)
else:
nx.draw(G, pos=pos, with_labels=True,
node_color=colors, node_size=1000, alpha=0.5)
# Draw edges involved in transitions with conditions
edges_colors = [edge[2]['color'] for edge in edges_in_cond]
......@@ -746,9 +775,12 @@ def draw_graph(output_dir, solution, solution_index, G,
# Save & show
date = dt.datetime.now().strftime("%H-%M-%S")
# plt.legend()
# plt.savefig(output_dir + date + '_' + solution[:75] + ".svg", format="svg")
# plt.show()
if matplotlib_export:
plt.legend()
plt.savefig(
output_dir + date + '_' + solution[:75] + ".svg",
format="svg")
plt.show()
nx.write_graphml(
G,
......
......@@ -31,7 +31,7 @@ from setuptools import setup, find_packages
from setuptools.command.test import test as TestCommand
from sys import version_info
__PACKAGE_VERSION__ = "0.1.1"
__PACKAGE_VERSION__ = "0.1.2"
__LIBRARY_VERSION__ = "0.1.0"
deps = []
......@@ -74,7 +74,7 @@ setup(
long_description=open('README.md').read(),
# Official page
url = "https://gitlab.irisa.fr/0000B8EG/Cadbiom/tree/pypi_packaging",
url = "https://gitlab.inria.fr/pvignet/cadbiom",
# Metadata
classifiers=[
......@@ -92,7 +92,11 @@ setup(
'console_scripts': ['cadbiom_cmd = cadbiom_cmd.cadbiom_cmd:main'],
},
install_requires=deps + ["matplotlib<2", "cadbiom"],
install_requires=deps + ["cadbiom"],
# For graphs drawing manually
extras_require = {
'matplotlib': ["matplotlib<2"],
},
# Tests
tests_require=['pytest'],
......
include README.md
recursive-include cadbiom_gui/gt_gui/chart_glade/ *.glade
include cadbiom_gui/gt_gui/legend.png
include cadbiom_gui/gt_gui/images/*
......@@ -55,14 +55,13 @@ import pkg_resources
LOGGER = cm.logger()
BG_COLOR = "#FFFF99"
class ChartChecker(object):
"""
This class provide a gui interface for checking some queries
"""
def __init__(self, emvc, reporter):
def __init__(self, emvc, reporter, parent=None):
self.__emvc = emvc # edit mvc - link with charter
self.__reporter = reporter
self.__occ_form = OccurenceForm(emvc, reporter)
......@@ -75,11 +74,15 @@ class ChartChecker(object):
if (self.__main_window):
self.__main_window.connect("destroy", self.__on_destroy)
self.__main_window.set_position(gtk.WIN_POS_CENTER)
color = gtk.gdk.color_parse(BG_COLOR)
self.__main_window.modify_bg(gtk.STATE_NORMAL, color)
self.__main_window.resize(600, 300)
if parent:
# Set window above all windows
self.__main_window.set_transient_for(parent.main_window)
# Event on_escape key pressed
self.__main_window.connect('key_press_event', self.on_escape)
# register as auxiliary window
self.__emvc.win_register(self)
......@@ -101,6 +104,10 @@ class ChartChecker(object):
"""
self.__on_destroy(None)
def on_escape(self, widget, event):
"""On ESC key_press_event, destroy this window."""
if gtk.gdk.keyval_name(event.keyval) == "Escape":
self.__main_window.destroy()
class OccurenceForm(object):
......@@ -194,7 +201,7 @@ class OccurenceForm(object):
# get info
final_prop = self.property_entry.get_text()
if len(final_prop) == 0:
cancel_warn("Void property")
cancel_warn("Missing property")
return
inv_prop = self.inv_prop_entry.get_text()
start_prop = self.start_prop_entry.get_text()
......@@ -255,7 +262,7 @@ class OccurenceForm(object):
# get info
final_prop = self.property_entry.get_text()
if len(final_prop) == 0:
cancel_warn("Void property")
cancel_warn("Missing property")
return
inv_prop = self.inv_prop_entry.get_text()
start_prop = self.start_prop_entry.get_text()
......@@ -320,7 +327,7 @@ class OccurenceForm(object):
self.max_sol = max_sol
if len(self.lsol) == 0:
cancel_warn("No conditions")
cancel_warn("No solution")
return
# activate buttons
......@@ -353,16 +360,16 @@ class OccurenceForm(object):
"""
launch mac computations
"""
ask = confirm(None, 'This process takes time' + '\n' +
'You will not be able to use cadbiom.'+'\n' +
' Do you want to continue ?')
ask = confirm(None, 'This process takes time.\n' +
'You will not be able to use cadbiom.\n' +
'Do you want to continue ?')
if ask:
query = MCLSimpleQuery(self.start_prop,
self.inv_prop, self.property)
cam_list = self.mcla.mac_search(query, self.max_step)
if len(cam_list)==0 :
ok_warn("The solver returns an empty list" +
"\n"+" you should refine your query")
ok_warn("The solver could not find a MAC," +
"\n you should refine your query")
else :
CNSWindow(cam_list, self.__emvc,
self.__error_reporter, self)
......@@ -435,8 +442,6 @@ class SolutionWindow(object):
self.window.set_resizable(True)
height = gtk.gdk.screen_height()
height = int(height * 0.30)
color = gtk.gdk.color_parse(BG_COLOR)
self.window.modify_bg(gtk.STATE_NORMAL, color)
self.window.set_size_request(700, height)
......
......@@ -44,11 +44,12 @@
"""
Main gui controler with auxiliary classes
"""
import itertools as it
from string import ascii_uppercase
from math import sqrt
from gtk.gdk import Cursor, ARROW, BOTTOM_LEFT_CORNER, BOTTOM_RIGHT_CORNER,\
TOP_LEFT_CORNER, TOP_RIGHT_CORNER, LINE_ON_OFF_DASH
from graphics.drawing_style import Arrow
from cadbiom_gui.gt_gui.graphics.drawing_style import Arrow
import gtk
......@@ -124,6 +125,8 @@ class ChartControler(object):
self.signal_dict[sig] = []
self.edit_window = None
self.gen_name = self.nodes_names_generator()
def attach(self, signal, obs):
"""
Register an observer on a signal list
......@@ -353,27 +356,32 @@ class ChartControler(object):
xnode = self.m_vscreen_coord[0]
ynode = self.m_vscreen_coord[1]
if type == 'simple':
self.current_node = self.current_node.add_simple_node("...",
xnode, ynode)
self.current_node = self.current_node.add_simple_node(
next(self.gen_name),
xnode, ynode)
elif type == 'macro':
self.current_node = self.current_node.add_macro_subnode("...",
xnode,
ynode,