Skip to content

Commit ef3f864

Browse files
committed
Files for the CAISE 2012 paper added
1 parent 6f565ee commit ef3f864

File tree

5 files changed

+174362
-1
lines changed

5 files changed

+174362
-1
lines changed

README

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
Files relating to Techne solution finding.
1+
# Files relating to Techne solution finding.
2+
- convert_omni_seb.py - Takes an Omnigraffle file and turns it into input for Sebastiani GoalSolve and ATMS (.techne) files
3+
- drive_seb.py - Driver for the converter
4+
- pci-chelsea.graffle - Example from the CAISE 2012 paper
5+
26

37
Copyright 2010 Neil Ernst. Non-commercial use permitted, with the exception of file techne-atms.lisp, which has the following licence/copyright:
48

convert_omni_seb.py

+299
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,299 @@
1+
# -*- coding: utf-8 -*-
2+
from appscript import *
3+
import subprocess
4+
import re
5+
import settings as s #constants
6+
7+
class SebException(Exception):
8+
""" domain specific exception """
9+
pass
10+
11+
class ParseQGM():
12+
"""
13+
Author nernst
14+
Use py-appscript to
15+
convert OmniGraffle QGM models to Seb SAT syntax
16+
"""
17+
18+
def __init__(self, canvas_name=s.CANVAS_NAME):
19+
__graffle = app('OmniGraffle Professional 5')
20+
__doc = __graffle.documents[1] # the top document, note 1-indexed array
21+
self.__s = __doc.canvases[canvas_name].get()
22+
23+
self.__shapes = self.__s.shapes.get()
24+
self.__graphics = self.__s.graphics.get()
25+
self.__node_cnt = 0
26+
self.__opt_cnt = 0
27+
self.__rel_cnt = 0
28+
self.__true_rel_cnt = 0 # number of relations after AND/OR have been aggregated.
29+
self.__rel_types = ['AND', 'OR', '+', '-','++', '--', 'p', 'conflict']
30+
self.__src_map = {'AND':[], 'OR':[], '+':[], '-':[],'++':[], '--':[], 'p':[], 'conflict':[]} # store the node ids that are sources. #em dash is Unicode
31+
self.__contribs = ['+', '-', '++', '--']
32+
self.__opt_list = [] # a list of nodes which are optional
33+
self.__decomp_str = ''
34+
self.__contr_str = ''
35+
self.__des_str = ''
36+
self.__just_str = ''
37+
self.__mand_str = ''
38+
self.__conflict_str = ''
39+
self.__id_sebid_map = {}
40+
self.__atms_nodes_map = {'goal':[], 'task':[], 'domain-assumption':[], 'leaf':[]} # no quality-constraints
41+
self.__atms_nodes = []
42+
43+
def set_node_ids(self):
44+
"""pass 1 - set the node ids, zero indexed"""
45+
for element in self.__shapes:
46+
data = element.user_data.get()
47+
nodeid = element.id.get()
48+
if data != None:
49+
opt = data['optional'] == 'T'
50+
if not opt:
51+
# set seb_id
52+
data['seb_id'] = self.__node_cnt
53+
self.__id_sebid_map[self.__node_cnt] = nodeid
54+
element.user_data.set(data)
55+
self.__node_cnt += 1
56+
# set atms list
57+
# ideally do something like this, where we set the type from the graph
58+
node_type = data['node_type']
59+
node_name = 'r-' + str(data['seb_id']) # LISP does not allow ints as identifiers
60+
self.__atms_nodes_map[node_type].append(node_name)
61+
self.__atms_nodes.append(node_name)
62+
63+
#pass 2 - set the node ids for optional nodes. Must be named after the mandatory ones.
64+
# opt_cnt = self.__node_cnt
65+
# for element in self.__shapes:
66+
# opt = False
67+
# text = element.text.get()
68+
# data = element.user_data.get()
69+
# if data != None:
70+
# opt = data['optional'] == 'T'
71+
# if opt:
72+
# self.__opt_list.append(data['seb_id']) # build a list of nodes which are optional
73+
# # set seb_id
74+
# nodeid = element.id.get()
75+
# data['seb_id'] = opt_cnt
76+
# element.user_data.set(data)
77+
# self.__id_sebid_map[opt_cnt] = nodeid
78+
# opt_cnt += 1
79+
# self.__opt_cnt = opt_cnt - self.__node_cnt
80+
81+
def generate_atms(self):
82+
"""Generate .techne files for use with Techne ATMS implementation"""
83+
for element in self.__graphics:
84+
for key in self.__src_map.keys():
85+
self.__src_map[key] = [] # remove preceding iteration's data
86+
data = element.user_data.get()
87+
if element.class_.get() != k.shape or data == None: ## NOTA BENE!! This does nothing yet is necessary to prevent errors. ::shrug::
88+
#print 'not a node' #return #either a line, or a label
89+
continue
90+
# #print element.id.get()
91+
in_lines = element.incoming_lines.get()
92+
for l in in_lines:
93+
try:
94+
l_text = l.user_data.get()['rel_type'] # one of rel_types
95+
seb_id = l.source.user_data.get()['seb_id']
96+
# TODO only one possible rel_type per node. Check that.
97+
self.__src_map[l_text].append('r-' + str(seb_id)) # e.g. ['AND'][r-93,r-94...]
98+
except KeyError as ke:
99+
print ke, 'no rel_type on that line', l.id.get(), l.user_data.get()['rel_type']
100+
101+
#generate relations
102+
# AND/OR - the only node types with multiple srcs
103+
and_srcs = ' '.join(self.__src_map['AND'])
104+
or_srcs = self.__src_map['OR']
105+
conflict_srcs = self.__src_map['conflict'] # we are assuming there is only binary conflict
106+
if and_srcs != '':
107+
self.__just_str += '(assert-formula ' + 'r-' + data['seb_id'] + ' (list ' + and_srcs + ') :DA \"a just\" *rekb* )\n'
108+
if or_srcs != []:
109+
for node in or_srcs:
110+
self.__just_str += '(assert-formula ' + 'r-' + data['seb_id'] + ' (list ' + node + ' ) :DA \"a just\" *rekb* )\n'
111+
if conflict_srcs != []:
112+
self.__conflict_str += '(assert-formula (contradiction *rekb*) (list ' + conflict_srcs[0] + ' r-' + data['seb_id'] + ') :DA "hurt4" *rekb*)\n'
113+
114+
try:
115+
if data['mandatory'] == 'T':
116+
m_node_id = data['seb_id']
117+
self.__mand_str += '(assert-mandatory r-' + m_node_id + ' *rekb* T)\n'
118+
except KeyError as ke:
119+
pass
120+
121+
def generate_seb(self):
122+
opt = False
123+
for element in self.__graphics:
124+
for key in self.__src_map.keys():
125+
self.__src_map[key] = [] # remove preceding iteration's data
126+
data = element.user_data.get()
127+
# if element.class_.get() == k.line and data != None:
128+
# text = data['rel_type'] #AND/OR/+/- etc.
129+
if element.class_.get() != k.shape or data == None:
130+
#print 'not a node' #return #either a line, or a label
131+
continue
132+
#print element.id.get()
133+
opt = data['optional'] == 'T'
134+
if not opt:
135+
in_lines = element.incoming_lines.get()
136+
for l in in_lines:
137+
try:
138+
l_text = l.user_data.get()['rel_type'] # one of rel_types
139+
seb_id = l.source.user_data.get()['seb_id']
140+
self.__src_map[l_text].append(seb_id) # e.g. ['+'][93,94] ...
141+
except KeyError as ke:
142+
print ke, 'no rel_type on that line', l.id.get(), l.user_data.get()['rel_type']
143+
144+
#for non-optional nodes, generate relations
145+
rel_type = ''
146+
# purge optional nodes from list of sources for this sink
147+
for rel_type in self.__rel_types:
148+
tmp = []
149+
for x in self.__src_map[rel_type]: # all nodes which are sources
150+
if x not in self.__opt_list:
151+
tmp.append(x)
152+
self.__src_map[rel_type] = tmp
153+
154+
# AND/OR - the only node types with multiple srcs
155+
and_srcs = ' '.join(self.__src_map['AND'])
156+
or_srcs = ' '.join(self.__src_map['OR'])
157+
if and_srcs != '':
158+
self.__decomp_str += 'R AND ' + data['seb_id'] + ' ' + and_srcs + '\n'
159+
self.__true_rel_cnt += 1
160+
if or_srcs != '':
161+
self.__decomp_str += 'R OR ' + data['seb_id'] + ' ' + or_srcs + '\n'
162+
self.__true_rel_cnt += 1
163+
# contributions
164+
srcs = ''
165+
for c in self.__contribs:
166+
srcs = self.__src_map[c]
167+
if srcs != []:
168+
for src in srcs:
169+
self.__contr_str += 'R ' + c + ' ' + data['seb_id'] + ' ' + src + '\n'
170+
self.__true_rel_cnt += 1
171+
172+
if data['mandatory'] == 'T':
173+
m_node_id = data['seb_id']
174+
self.__des_str += 'TS ' + m_node_id + ' ;\n-PD ' + m_node_id + ' ;\n'
175+
# here we will add to the .des file the goal ids that must be "TS" and -PD
176+
177+
def get_optional_ids(self):
178+
""" return a list of graffle ids of nodes labeled as optional"""
179+
#print "num optional: ", self.__opt_cnt
180+
options_id_map = {}
181+
for option in self.__opt_list:
182+
# print option, options_id_map, self.__id_sebid_map
183+
# try:
184+
options_id_map[option] = self.__id_sebid_map[int(option)]
185+
# except KeyError:
186+
# print "option not in list of ids"
187+
# return None
188+
return options_id_map
189+
190+
def set_node_status(self, nodeid, op='to_mandatory'):
191+
""" given a nodeid, set the user data"""
192+
node_data = self.__s.shapes.ID(nodeid).user_data.get()
193+
if op == 'to_mandatory':
194+
node_data['optional'] = u'F'
195+
node_data['mandatory'] = u'T'
196+
elif op == 'to_optional':
197+
node_data['optional'] = u'T'
198+
node_data['mandatory'] = u'F'
199+
elif op == 'to_unknown':
200+
node_data['optional'] = u'F'
201+
node_data['mandatory'] = u'F'
202+
else:
203+
print "unrecognized node status operation", op
204+
self.__s.shapes.ID(nodeid).user_data.set(node_data)
205+
206+
def print_files(self):
207+
""" legacy support"""
208+
self.print_seb_files()
209+
self.print_atms_files()
210+
211+
def find_node_type(self, node):
212+
if node == '':
213+
return
214+
if node in self.__atms_nodes_map['goal']:
215+
return "GOAL"
216+
if node in self.__atms_nodes_map['task'] or node in self.__atms_nodes_map['leaf']:
217+
return "TASK"
218+
if node in self.__atms_nodes_map['domain-assumption']:
219+
return "GOAL"
220+
221+
def print_atms_files(self):
222+
atms_file = s.OUT_DIR + s.ATMS_FILE
223+
node_str = ''
224+
out = open(atms_file, 'w')
225+
header = '(load "/Users/nernst/Dropbox/research/src/tms/techne-atms.dx64fsl")\n(load "/Users/nernst/Dropbox/research/src/tms/techne-psm.lisp")\n(defvar *rekb* (create-rekb "pcidss"))\n(setq \n'
226+
out.write(header.encode('utf-8'))
227+
for node in self.__atms_nodes:
228+
node_type = self.find_node_type(node)
229+
node_str += '\t' + node + ' (declare-atomic nil \"'+ node + '\" :'+ node_type + ' *rekb*)\n'
230+
out.write(node_str.encode('utf-8'))
231+
footer = ')\n\n;;Justifications\n'
232+
out.write(footer.encode('utf-8'))
233+
out.write(self.__just_str.encode('utf-8'))
234+
out.write(u'\n;;Conflicts\n')
235+
out.write(self.__conflict_str.encode('utf-8'))
236+
out.write(self.__mand_str.encode('utf-8'))
237+
out.close()
238+
239+
def print_seb_files(self):
240+
out_file = s.OUT_DIR + s.GOAL_FILE
241+
des_file = s.OUT_DIR + s.DES_FILE
242+
243+
out = open(out_file, 'w')
244+
header = 'D ' + str(self.__node_cnt) + ' ' + str(self.__true_rel_cnt) + '\n'
245+
print header
246+
out.write(header.encode('utf-8'))
247+
for i in range(self.__node_cnt):
248+
out.write(u'N NO NO\n')
249+
out.write(self.__contr_str.encode('utf-8'))
250+
out.write(self.__decomp_str.encode('utf-8'))
251+
out.close()
252+
253+
des = open(des_file, 'w')
254+
des.write('\n')
255+
des.write(self.__des_str.encode('utf-8'))
256+
des.close()
257+
258+
def zero_counts(self):
259+
self.__node_cnt = 0
260+
self.__rel_cnt = 0
261+
self.__true_rel_cnt =0
262+
self.__opt_cnt = 0
263+
264+
def run_seb(self):
265+
""" run the Sebastiani algorithm (via Chaff) and return admissibility """
266+
267+
sat_dir = '/Users/nernst/Dropbox/research/projects/goal-reasoning/backward_prop/GRTool-Src/Solvers/src/Goalreasoner/lin/Goalsolve/'
268+
p = subprocess.Popen(args=[sat_dir + 'goalsolve', '-n', '/tmp/re2010.goal', '/tmp/re2010.des'],
269+
stdin=subprocess.PIPE, stderr=subprocess.PIPE, stdout=subprocess.PIPE,
270+
cwd=sat_dir)
271+
stdout, stderr = p.communicate()
272+
sat_pattern = re.compile('\+ The problem admits solutions \+')
273+
unsat_pattern = re.compile('\+ The problem admits no solution \+')
274+
chaff_err_pattern = re.compile('Wrong output from chaff')
275+
sat = sat_pattern.search(stderr)
276+
unsat = unsat_pattern.search(stderr)
277+
if unsat != None:
278+
print 'Error occured in SAT run or no admissible result'
279+
print stderr
280+
raise SebException
281+
else:
282+
return True
283+
284+
def negate_repl(matchobj):
285+
""" replace 'literal' with '-literal' and vice-versa"""
286+
neg, literal = matchobj.groups(1)
287+
if neg == '-':
288+
return literal
289+
else:
290+
return '-' + literal
291+
292+
def negate(sat_assign):
293+
""" given a satisfying assignment, return that assignment of the initial labels, negated"""
294+
new = re.sub('(-*)(\d+)', negate_repl, sat_assign)
295+
return new
296+
297+
if __name__ == '__main__':
298+
q = ParseQGM()
299+

drive_seb.py

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# -*- coding: utf-8 -*-
2+
3+
""" This is the Techne driver: takes an input file and options and calls appropriate API functions"""
4+
5+
from optparse import OptionParser, OptionGroup
6+
from convert_omni_seb import ParseQGM
7+
import option_parser
8+
import display_opt
9+
10+
usage = "usage: %prog [options]"
11+
version = "%prog 0.3_re"
12+
desc = "Evaluate Seb models"
13+
14+
parser = OptionParser(usage=usage, version=version, description=desc)
15+
parser.add_option("-q", "--quiet", dest="verbose",
16+
action="store_false", help="don't print status messages to stdout")
17+
parser.add_option("-v", "--verbose", dest="verbose",
18+
action="store_true", default=True, help="make lots of noise [default]")
19+
parser.add_option("-c", "--cost", dest="cost",
20+
action="store", default=0, help="define the maximum cost of a solution")
21+
parser.add_option("-r", "--run", dest="run_id",
22+
action="store", default=0, help="which run are we trying (valid: 0-7)")
23+
parser.add_option("-o", "--cost_increment", dest="increment",
24+
action="store", default=5, help="define the increment by which to adjust the cost threshold")
25+
group = OptionGroup(parser, "Arguments for dealing with optional elements")
26+
group.add_option("-u", "--use-naive", dest="use_naive", action="store_true", default=True,
27+
help="use naive parsing. O(2^N), not recommended.")
28+
group.add_option("-m", "--omax", dest='o_max', action='store',
29+
help="specify an upper bound on the number of options to try to satisfy")
30+
group.add_option("-n", "--omin", dest='o_min', action='store',
31+
help="specify a lower bound on the number of options to try to satisfy")
32+
group.add_option("-t", "--use_tabu", dest='use_tabu', action='store_true',
33+
help="use tabu search to find a local optimal set.")
34+
group.add_option("-p", "--use_moop", dest='use_moop', action='store_true',
35+
help="use multi-objective optimization to find optimal set. \
36+
Need cost figures attached to the optional nodes.") #cost= in graphical editor.
37+
parser.add_option_group(group)
38+
(options, args) = parser.parse_args()
39+
if options.verbose:
40+
pass
41+
if options.o_min != None:
42+
options.o_min = int(options.o_min)
43+
if options.o_max != None:
44+
options.o_max = int(options.o_max)
45+
46+
# identify if admissible
47+
#initial run with no options.
48+
parser = ParseQGM()
49+
parser.set_node_ids() #label the nodes with the appropriate ids for SEB
50+
parser.generate_seb() # the Sebastiani goal SAT approach
51+
parser.generate_atms() # the ATMS techne file
52+
parser.print_files() # save the mandatory/non-optional nodes to disk
53+
# admissible = parser.run_seb() # see if the result is admissible.
54+
# if admissible: print "was admissible"
55+
# else: print "not admissible"
56+
# parser.zero_counts()
57+
#load this run's options
58+
#mands, opts, prefs = display_opt.get_options(options.run_id)
59+
#print mands,opts, prefs
60+
#display_opt.clear_model() # set all nodes to plain
61+
#display_opt.set_graph(mands,opts) # set nodes to their appropriate status
62+
# generate option sets
63+
#if options.use_naive:
64+
# results = option_parser.naive_option(parser, opts, options.o_min, options.o_max)
65+
# print results
66+
67+
68+

0 commit comments

Comments
 (0)