import socket import time import os, sys from subprocess import * ########################################### # # # Session Control # # # #-----------------------------------------# def open_session(host, port, start_elvis=False, persist=True, save=False, s_location=""): """ This method establishes the connection between your Python application program and the ElVis display program. If ElVis is already running then you can send graph data to it. If ElVis is not running then this method will attempt to run "elvis". This assumes you have an executable named "elvis" in your path. module load elvis will set up your path on portal.pppl.gov Usage: open_session(host, port, start_elvis, persist, save, save_location) :param host: Name of computer that runs the ElVis program. :param port: Port number that ElVis listens to. Default is 7654. :param start_elvis: True - try to run ElVis. False - do not try to start ElVis. This is an optional argument. Default is False. :param persist: True - check for ELVIS_HOST and ELVIS_PORT environment variables. False - do not check for ElVis environment variables. :param save: True - write EML (ElVis Mark-Up Language) file of graph data. False - do not write the EML to a file :param s_location: Directory for writing the EML file. Default is ... :returns: Specifies which ElVis to send to. Creates session object and tests connection. Persists if no connection to ElVis can be made by checking environment variables and then ElVis defaults. Returns session object if successfully connected to ElVis. """ s = session(host, int(port), save, s_location) verbose = False if start_elvis == True: exists = test_connection(s, verbose, 1, 0) if exists: return s print "Launching ElVis on local host, port " + str(port) + "..." #s.process = Popen(['/usr/pppl/java/x64/jdk1.6.0_16/bin/java', '-jar', '-Delvis.port='+str(port), '/u/'+os.getlogin()+'/Elvis/elvis.jar'], stdout=open(os.devnull, "w")) s.process = Popen(['elvis'], stdout=open(os.devnull, "w")) success = test_connection(s, True) if not success and "ELVIS_PORT" in os.environ and "ELVIS_SERVER" in os.environ and persist: s.host = os.environ["ELVIS_SERVER"] s.port = int(os.environ["ELVIS_PORT"]) print "Checking for ElVis instance in environment-specified location..." success = test_connection(s, True) if not success and persist and host != os.environ["HOSTNAME"] and port != 7654: s.host = os.environ["HOSTNAME"] s.port = 7654 print "Checking for ElVis instance in ElVis-default location..." success = test_connection(s, True) if not success: s.host = host s.port = int(port) else: success = test_connection(s, True) if not success and "ELVIS_PORT" in os.environ and "ELVIS_SERVER" in os.environ and persist: s.host = os.environ["ELVIS_SERVER"] s.port = int(os.environ["ELVIS_PORT"]) print "Checking for ElVis instance in environment-specified location..." success = test_connection(s, True) if not success and persist and host != os.environ["HOSTNAME"] and port != 7654: s.host = os.environ["HOSTNAME"] s.port = 7654 print "Checking for ElVis instance in ElVis-default location..." success = test_connection(s, True) if not success: s.host = host s.port = int(port) return s def test_connection(session, verbose=False, maxattempt=6, waittime=3): """ Internal method for testing the connection to an ElVis session. :param session: Specifies which ElVis to connect to. :returns: True - connection was successful False - could not connect """ connected = False attempts = 0 if verbose: print "Testing connection to " + session.host + ", " + str(session.port) + "..." while not connected and attempts < maxattempt: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: sock.connect((session.host, session.port)) except Exception, e: if verbose: alert("Attempt #" + str(attempts+1) + ": Failure to connect! Exception type: %s" % (e)) attempts += 1 if attempts < maxattempt: time.sleep(waittime) continue connected = True sock.shutdown(socket.SHUT_RDWR) sock.close() if not connected: alert("Error: Could not connect to ElVis instance on host " + session.host + ", port " + str(session.port) + ".") elif verbose: print "Attempt #" + str(attempts+1) + ": Connection successful!" return connected def alert(msg): """Helper function to print a message to stderr.""" print >>sys.stderr, msg class session: """ Stores location data for an ElVis instance as well as save location data for generated EML files. Returned by open_session(). Used by draw methods. """ def __init__(self, host, port, save=False, s_location=""): self.host = host self.port = port self.process = None self.save = save self.s_location = s_location def __repr__(self): return str(self) def __str__(self): """Helper function to simplify viewing session data.""" out = sessions.s[i].name + "\n host:\t" + sessions.s[i].host + "\n port:\t" + str(sessions.s[i].port) + "\n local:\t", if sessions.s[i].process == None: out += "NO" else: out += "YES" return out def set_save(self, save, s_location="."): """Changing the save location and options for EML files generated by this interface.""" if save == True and s_location != ".": self.save = save self.s_location = s_location else: self.save = save def send(self, eml): """Sends a full newline-separated EML file via a socket to an ElVis instance. If save options are selected, saving of the file occurs here.""" if self.save: try: savefile = open(self.s_location, "w+") savefile.write(eml) savefile.close() except Exception, e: alert("Error: The following exception occured when trying to open/create file \"" + self.s_location + "\": %s" % (e)) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((self.host, self.port)) for line in eml.split("\n"): sock.send(line) sock.shutdown(socket.SHUT_RDWR) sock.close() def close(self): """Kills local ElVis subprocess, if interface launched ElVis instead of connecting to an existing instance.""" print "Session exiting... " exists = test_connection(s, False, 2, 1) if exists: self.process.kill() else: print "Error: Process does not exist! Cannot exit." #-----------------------------------------# # # # End Session Control # # # ########################################### ########################################### # # # ElVis Control # # # #-----------------------------------------# def draw_visual_state(session, visual_state): """Generates an EML file based on a visual state structure and send it via a socket connection to ElVis. :param session: Session object pointing to desired ElVis instance. :param visual_state: Visual State object describing a visual state for ElVis to load. :returns: Error codes:\n\tCode\tMeaning\n\t-1\tConnection failure.""" exists = test_connection(session, False, 2, 1) if not exists: alert("Error: Could not connect to ElVis instance on host " + session.host + ", port " + str(session.port) + ".") return -1 eml = "\n" eml += "\n" for window in visual_state.graph_windows: eml += "\n" for graph in window.graphs: eml += "\n" eml += "\n" eml += "\n" for label in graph.labels: eml += "\n" eml += "\n" eml += "\n" session.send(eml) return 1 def draw_graph(session, x, y, title, xtitle, ytitle): """ Send graph data of a single function, f(x), to ElVis for display. This method generates the EML and sends it over the socket connection. The x and y values can be floats, ints, or strings. :param session: Session object pointing to desired ElVis instance. :param x: List of X axis values (strings or floats). :param y: List of Y axis values (strings or floats). :param title: Graph title. :param xtitle: X axis title. :param ytitle: Y axis title. :returns: Error codes:\n\tCode\tMeaning\n\t-1\tConnection failure.\n\t-2\tList lengths not equal.\n\t-3\tNon-numerical data point.""" exists = test_connection(session, False, 2, 1) if not exists: alert("Error: Could not connect to ElVis instance on host " + session.host + ", port " + str(session.port) + ".") return -1 if len(x) != len(y): alert("Error: Mismatched list lengths.") return -2 eml = "\n" eml += "\n" eml += "\n" eml += "\n" eml += "\n" eml += "\n" eml += "\n" i = 0 while i < min(len(x), len(y)): try: float(x[i]) float(y[i]) except: alert("Error: Data point (" + x[i] + ", " + y[i] + ") not numerically defined.") return -3 eml += "\n" i += 1 eml += "\n" eml += "\n" eml += "\n" eml += "\n" session.send(eml) return 1 def draw_multigraph(session, xy, title, xtitle, ytitle, dataset_names): """ Send graph data of multiple functions (datasets), f(x), g(x), h(x), etc. to ElVis for display in a single graph. This method generates the EML and sends it over the socket connection. The x and y values can be floats, ints, or strings. The name of each dataset will be shown in the legend. :param session: Session object pointing to desired ElVis instance. :param xy: List of X and Y values. Should be formatted in tuples of tuples, where the first tuple contains x values, and every tuple after contains y values for each plot in the data. :param title: Graph title. :param xtitle: X axis title. :param ytitle: Y axis title. :param dataset_names: A list of names for each set of data (defined as separate Y-axis tuples in parameter 'xy'). :returns: Error codes:\n\tCode\tMeaning\n\t-1\tConnection failure.\n\t-2\tList lengths not equal.\n\t-3\tNon-numerical data point.""" exists = test_connection(session, False, 2, 1) if not exists: alert("Error: Could not connect to ElVis instance on host " + session.host + ", port " + str(session.port) + ".") return -1 if len(xy[0]) != len(xy[1]): alert("Error: Mismatched list lengths.") return -2 eml = "\n" eml += "\n" eml += "\n" eml += "\n" eml += "\n" eml += "\n" i,j = 0,1 while j < len(xy): eml += "\n" while i < len(xy[j]): try: float(xy[0][i]) float(xy[j][i]) except: alert("Error: Data point (" + xy[0][i] + ", " + xy[j][i] + ") not numerically defined.") return -3 eml += "\n" i += 1 eml += "\n" i = 0 j += 1 j = 0 eml += "\n" eml += "\n" eml += "\n" session.send(eml) return 1 def draw_indexed_graph(session, xy, title, xtitle, ytitle, times): """ Send graph data of multiple indices of a function, f(x,i), to ElVis for display in a single graph. The index is typically time, but it can be any parameter. This method generates the EML and sends it over the socket connection. The x and y values can be floats, ints or strings. The index value is drawn above the graph. :param session: Session object pointing to desired ElVis instance. :param xy: List of X and Y values. Should be formatted in tuples of tuples, where the first tuple contains x values, and every tuple after contains y values for each plot in the data. :param title: Graph title. :param xtitle: X axis title. :param ytitle: Y axis title. :param times: A list of time indicies (floats) for each set of data (defined as separate Y-axis tuples in parameter 'xy'). :returns: Error codes:\n\tCode\tMeaning\n\t-1\tConnection failure.\n\t-2\tList lengths not equal.\n\t-3\tNon-numerical data point.""" exists = test_connection(session, False, 2, 1) if not exists: alert("Error: Could not connect to ElVis instance on host " + session.host + ", port " + str(session.port) + ".") return -1 if len(xy[0]) != len(xy[1]): alert("Error: Mismatched list lengths.") return -2 eml = "\n" eml += "\n" eml += "\n" eml += "\n" eml += "\n" eml += "\n" i,j = 0,1 while j < len(xy): if times == None: eml += "\n" else: eml += "\n" while i < len(xy[j]): try: float(xy[0][i]) float(xy[j][i]) except: alert("Error: Data point (" + xy[0][i] + ", " + xy[j][i] + ") not numerically defined.") return -3 eml += "\n" i += 1 eml += "\n" i = 0 j += 1 j = 0 eml += "\n" eml += "\n" eml += "\n" session.send(eml) return 1 def draw_multigraph_file(session, filename, title, xtitle, ytitle, dataset_names): """ Send graph data of multiple functions (datasets), f(x), g(x), h(x), etc. read from a file to ElVis for display in a single graph. The file must be formatted in columns with the first column representing x values and subsequent columns representing y values for separate data sets. This method generates the EML and sends it over the socket connection. The x and y values can be floats, ints, or strings. The name of each dataset will be shown in the legend. :param session: Session object pointing to desired ElVis instance. :param filename: A path to a file that is formatted such that the first column of values are X values, and each subsequent column has y values for a different set of data. :param title: Graph title. :param xtitle: X axis title. :param ytitle: Y axis title. :param dataset_names: A list of names for each set of data (defined as separate Y-axis tuples in parameter 'xy'). :returns: Error codes:\n\tCode\tMeaning\n\t-1\tConnection failure.\n\t-2\tList lengths not equal.\n\t-3\tNon-numerical data point.\n\t-4\tCould not open/create file at path.""" try: f = open(filename, "r") except: alert("Error: Could not open file with path \"" + filename + "\".") return -4 fl = f.readlines() xy = [] for j in xrange(len(fl)): vals = fl[j].split(" ") for i in xrange(len(vals)): xy[i][j] = float(vals[i]) return draw_multigraph(session, xy, title, xtitle, ytitle, dataset_names) """ def draw_from_file(session, filepath): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) f = open(filepath, "r") fl = f.readlines() sock.connect((session.host, session.port)) for line in fl: sock.send(line.strip()) sock.shutdown(socket.SHUT_RDWR) sock.close() f.close() """ #-----------------------------------------# # # # End ElVis Control # # # ########################################### ########################################### # # # Visual State Control # # # #-----------------------------------------# class visual_state: """Top layer of a visual state object. Contains a list of graph windows.""" def __init__(self): self.graph_windows=[] def __getitem__(self, i): return self.graph_windows[i] class graph_window: """Each graph window contains a list of graphs, arranged in a grid.""" def __init__(self, name, nrow, ncol): self.name=name self.nrow=nrow self.ncol=ncol self.graphs=[] def __getitem__(self, i): return self.graphs[i] class graph: """Each graph contains its own labels and sets of data (it can have multiple plots).""" def __init__(self, name, ftype, plot_location, plot_width, plot_height, title, xlabel, ylabel, xlog="linear", ylog="linear"): self.name=name self.ftype=ftype self.plot_location=plot_location self.plot_width=plot_width self.plot_height=plot_height self.title=title self.xlabel=xlabel self.ylabel=ylabel self.xlog=xlog self.ylog=ylog self.show_legend=True self.datasets=[] self.labels=[] def __getitem__(self, i): return self.datasets[i] class fx: """ See http://w3.pppl.gov/elvis/elvispy for more information. The basic class for storing plots. Plots can be formatted in parallel x and y lists, or in a list of tuples, where the first tuple contains x values and subsequent tuples hold y values for each curve. """ def __init__(self, name, show_lines=True, show_points=True): self.name=name self.xy_points=[] self.x=[] self.y=[] self.show_lines=show_lines self.show_points=show_points class fxi(fx): """An extended version of the basic class for storing plots with indices indicating time added. Each indexed plot can have its own set of labels.""" def __init__(self, name, index, is_surface=False, show_lines=True, show_points=True): fx.__init__(self, name, show_lines=True, show_points=True) self.index=index self.is_surface=is_surface self.ilabels=[] class label: """A class for storing the basic data needed for a label in ElVis.""" def __init__(self, text, location, font, style, size, color): self.text=text self.location=location self.font=font self.style=style self.size=size self.color=color class index_label(label): """A subclass that is identical to its parent. It exists to provide a distinction between labels used in indexed plots and labels used for graphs.""" def __init__(self, text, location, font, style, size, color): label.__init__(self, text, location, font, style, size, color) #-----------------------------------------# # # # End Visual State Control # # # ###########################################