def show(self, delay=20, iterations=0): r""" Show this animation. INPUT: - ``delay`` - (default: 20) delay in hundredths of a second between frames - ``iterations`` - integer (default: 0); number of iterations of animation. If 0, loop forever. .. note:: Currently this is done using an animated gif, though this could change in the future. This requires that either ffmpeg or the ImageMagick suite (in particular, the ``convert`` command) is installed. See also the :meth:`ffmpeg` method. EXAMPLES:: sage: a = animate([sin(x + float(k)) for k in srange(0,2*pi,0.7)], ... xmin=0, xmax=2*pi, figsize=[2,1]) sage: a.show() # optional -- ImageMagick The preceding will loop the animation forever. If you want to show only three iterations instead:: sage: a.show(iterations=3) # optional -- ImageMagick To put a half-second delay between frames:: sage: a.show(delay=50) # optional -- ImageMagick .. note:: If you don't have ffmpeg or ImageMagick installed, you will get an error message like this:: Error: Neither ImageMagick nor ffmpeg appears to be installed. Saving an animation to a GIF file or displaying an animation requires one of these packages, so please install one of them and try again. See www.imagemagick.org and www.ffmpeg.org for more information. """ if plot.DOCTEST_MODE: filename = tmp_filename(ext=".gif") self.gif(savefile=filename, delay=delay, iterations=iterations) return if plot.EMBEDDED_MODE: self.gif(delay=delay, iterations=iterations) else: filename = tmp_filename(ext=".gif") self.gif(delay=delay, savefile=filename, iterations=iterations) os.system("%s %s 2>/dev/null 1>/dev/null &" % (sage.misc.viewer.browser(), filename))
def is_functional(self): r""" Test whether ``lrs`` works on a trivial input. EXAMPLES:: sage: from sage.features.lrs import Lrs sage: Lrs().is_functional() # optional: lrslib FeatureTestResult('lrslib', True) """ from sage.misc.temporary_file import tmp_filename tf_name = tmp_filename() with open(tf_name, 'wb') as tf: tf.write("V-representation\nbegin\n 1 1 rational\n 1 \nend\nvolume") devnull = open(os.devnull, 'wb') command = ['lrs', tf_name] try: lines = subprocess.check_output(command, stderr=devnull) except subprocess.CalledProcessError as e: return FeatureTestResult(self, False, reason = "Call to `{command}` failed with exit code {e.returncode}.".format(command=" ".join(command), e=e)) expected = "Volume= 1" if lines.find(expected) == -1: return FeatureTestResult(self, False, reason = "Output of `{command}` did not contain the expected result `{expected}`.".format(command=" ".join(command),expected=expected)) return FeatureTestResult(self, True)
def lrs_redund(in_str, verbose=False): r""" To remove redundant inequalities from an H-representation or input points that are not vertices from a V-representation use the command 'redund' from lrslib. Input: lrs format in_str; Output: lrs format out_str; Copy and edit from ``def _volume_lrs(self, verbose=False)``, http://www.sagenb.org/src/geometry/polyhedron/base.py """ #if is_package_installed('lrslib') != True: # print 'You must install the optional lrs package ' \ # 'for this function to work' # raise NotImplementedError in_filename = tmp_filename() in_file = open(in_filename,'w') in_file.write(in_str) in_file.close() if verbose: print(in_str) redund_procs = Popen(['redund',in_filename],stdin = PIPE, stdout=PIPE, stderr=PIPE) out_str, err = redund_procs.communicate() if verbose: print(out_str) return out_str
def graphics_from_save(save_function, preferred_mime_types, allowed_mime_types=None, figsize=None, dpi=None): """ Helper function to construct a graphics file. INPUT: - ``save_function`` -- callable that can save graphics to a file and accepts options like :meth:`sage.plot.graphics.Graphics.save``. - ``preferred_mime_types`` -- list of mime types. The graphics output mime types in order of preference (i.e. best quality to worst). - ``allowed_mime_types`` -- set of mime types (as strings). The graphics types that we can display. Output, if any, will be one of those. - ``figsize`` -- pair of integers (optional). The desired graphics size in pixels. Suggested, but need not be respected by the output. - ``dpi`` -- integer (optional). The desired resolution in dots per inch. Suggested, but need not be respected by the output. OUTPUT: Return an instance of :class:`sage.structure.graphics_file.GraphicsFile` encapsulating a suitable image file. Image is one of the ``preferred_mime_types``. If ``allowed_mime_types`` is specified, the resulting file format matches one of these. Alternatively, this function can return ``None`` to indicate that textual representation is preferable and/or no graphics with the desired mime type can be generated. """ # Figure out best mime type mime = None if allowed_mime_types is None: mime = Mime.PNG else: # order of preference for m in preferred_mime_types: if m in allowed_mime_types: mime = m break if mime is None: return None # don't know how to generate suitable graphics # Generate suitable temp file filename = tmp_filename(ext=os.path.extsep + Mime.extension(mime)) # Call the save_function with the right arguments kwds = {} if figsize is not None: kwds['figsize'] = figsize if dpi is not None: kwds['dpi'] = dpi save_function(filename, **kwds) return GraphicsFile(filename, mime)
def is_functional(self): r""" Check whether ``theta`` works on a trivial example. EXAMPLES:: sage: from sage.features.csdp import CSDP sage: CSDP().is_functional() # optional: csdp FeatureTestResult('CSDP', True) """ from sage.misc.temporary_file import tmp_filename tf_name = tmp_filename() with open(tf_name, 'wb') as tf: tf.write("2\n1\n1 1") devnull = open(os.devnull, 'wb') command = ['theta', tf_name] try: lines = subprocess.check_output(command, stderr=devnull) except subprocess.CalledProcessError as e: return FeatureTestResult(self, False, reason = "Call to `{command}` failed with exit code {e.returncode}.".format(command=" ".join(command), e=e)) result = lines.strip().split('\n')[-1] match = re.match("^The Lovasz Theta Number is (.*)$", result) if match is None: return FeatureTestResult(self, False, reason = "Last line of the output of `{command}` did not have the expected format.".format(command=" ".join(command))) return FeatureTestResult(self, True)
def _launch_jmol(self): launch_script = tmp_filename(ext='.spt') with open(launch_script, 'w') as f: f.write('set defaultdirectory "{0}"\n'.format(self.filename())) f.write('script SCRIPT\n') from sage.env import SAGE_LOCAL JMOL = os.path.join(SAGE_LOCAL, 'bin', 'jmol') os.system('{0} {1} 2>/dev/null 1>/dev/null &' .format(JMOL, launch_script))
def _launch_jmol(self): launch_script = tmp_filename(ext=".spt") with open(launch_script, "w") as f: f.write('set defaultdirectory "{0}"\n'.format(self.filename())) f.write("script SCRIPT\n") from sage.env import SAGE_LOCAL JMOL = os.path.join(SAGE_LOCAL, "bin", "jmol") os.system("{0} {1} 2>/dev/null 1>/dev/null &".format(JMOL, launch_script))
def file_name(self, val=None): """ Show or set self.settings['file_name'] """ if val is not None: self.settings['file_name'] = val try: return self.settings['file_name'] except KeyError: return self.file_name(tmp_filename(ext=self.image_format()))
def filename(self, ext=None): """ Return the filename. INPUT: - ``ext`` -- string. The file extension. OUTPUT: Name of a file, most likely a temporary file. If ``ext`` is specified, the filename will have that extension. You must not modify the returned file. Its permissions are set to readonly to help with that. EXAMPLES:: sage: from sage.repl.rich_output.buffer import OutputBuffer sage: buf = OutputBuffer('test') sage: buf.filename() # random output '/home/user/.sage/temp/hostname/26085/tmp_RNSfAc' sage: os.path.isfile(buf.filename()) True sage: buf.filename(ext='txt') # random output '/home/user/.sage/temp/hostname/26085/tmp_Rjjp4V.txt' sage: buf.filename(ext='txt').endswith('.txt') True """ if ext is None: ext = '' elif not ext.startswith('.'): ext = '.' + ext if self._filename is None or not self._filename.endswith(ext): from sage.misc.temporary_file import tmp_filename output = tmp_filename(ext=ext) else: output = self._filename if self._filename is None: assert self._data is not None with open(output, 'wb') as f: f.write(self._data) self._filename = output elif self._filename != output: try: os.link(self._filename, output) except (OSError, AttributeError): import shutil shutil.copy2(self._filename, output) self._chmod_readonly(output) return output
def __call__(self, model, outfile='sage.png', verbose=1, block=True, extra_opts=''): """ This executes the tachyon program, given a scene file input. The default is to return the result as a PNG file called 'sage.png'. TESTS:: sage: from sage.interfaces.tachyon import TachyonRT sage: tgen = Tachyon() sage: tgen.texture('t1') sage: tgen.sphere((0,0,0),1,'t1') sage: tgen.str()[30:40] 'resolution' sage: t = TachyonRT() sage: import os sage: t(tgen.str(), outfile = os.devnull) tachyon ... Tachyon Parallel/Multiprocessor Ray Tracer... """ modelfile = tmp_filename(ext='.dat') open(modelfile,'w').write(model) opts = '' ext = outfile[-4:].lower() if ext == '.png': opts += ' -format PNG ' elif ext == '.tga': opts += ' -format TARGA ' elif ext == '.bmp': opts += ' -format BMP ' elif ext == '.ppm': opts += ' -format PPM ' elif ext == '.rgb': opts += ' -format RGB ' opts += ' -o %s '%outfile opts += ' ' + extra_opts + ' ' if verbose >= 2: opts += ' +V ' elif verbose == 0: opts += ' 1>/dev/null' cmd = 'tachyon %s %s; rm -f "%s"'%(modelfile,opts, modelfile) if not block: cmd = '( ' + cmd + ' ) &' if verbose: print cmd # One should always flush before system() sys.stdout.flush() sys.stderr.flush() os.system(cmd)
def preparse_file_named(name): r""" Preparse file named \code{name} (presumably a .sage file), outputting to a temporary file. Returns name of temporary file. """ from sage.misc.temporary_file import tmp_filename tmpfilename = tmp_filename(os.path.basename(name)) + '.py' out = open(tmpfilename, 'w') preparse_file_named_to_stream(name, out) out.close() return tmpfilename
def tex(self, filename=None, include_header=True): """ Writes the latex code to a file. INPUT: - ``filename`` -- string (default:``None``), the output filename. If ``None``, it saves the file in a temporary directory. - ``include_header`` -- bool (default:``True``) whether to include the header latex part. If ``False``, it prints only the tikzpicture part to the file. OUTPUT: string, path to tex file EXAMPLES:: sage: from slabbe import TikzPicture sage: V = [[1,0,1],[1,0,0],[1,1,0],[0,0,-1],[0,1,0],[-1,0,0],[0,1,1],[0,0,1],[0,-1,0]] sage: P = Polyhedron(vertices=V).polar() sage: s = P.projection().tikz([674,108,-731],112) sage: t = TikzPicture(s) sage: _ = t.tex() Write only the tikzpicture without header and begin/end document:: sage: _ = t.tex(include_header=False) Write to a given filename:: sage: from sage.misc.temporary_file import tmp_filename sage: filename = tmp_filename('temp','.tex') sage: _ = t.tex(filename) """ if filename is None: filename = tmp_filename('tikz_','.tex') else: filename = os.path.abspath(filename) if include_header: output = str(self) else: output = self.tikz_picture_code() with open(filename, 'w') as f: f.write(output) return filename
def runsnake(command): """ Graphical profiling with ``runsnake`` INPUT: - ``command`` -- the command to be run as a string. EXAMPLES:: sage: runsnake("list(SymmetricGroup(3))") # optional - runsnake ``command`` is first preparsed (see :func:`preparse`):: sage: runsnake('for x in range(1,4): print(x^2)') # optional - runsnake 1 4 9 :func:`runsnake` requires the program ``runsnake``. Due to non trivial dependencies (python-wxgtk, ...), installing it within the Sage distribution is unpractical. Hence, we recommend installing it with the system wide Python. On Ubuntu 10.10, this can be done with:: > sudo apt-get install python-profiler python-wxgtk2.8 python-setuptools > sudo easy_install RunSnakeRun See the ``runsnake`` website for instructions for other platforms. :func:`runsnake` further assumes that the system wide Python is installed in ``/usr/bin/python``. .. seealso:: - `The runsnake website <http://www.vrplumber.com/programming/runsnakerun/>`_ - ``%prun`` - :class:`Profiler` """ import cProfile import os from sage.misc.temporary_file import tmp_filename from sage.misc.misc import get_main_globals from sage.repl.preparse import preparse tmpfile = tmp_filename() cProfile.runctx(preparse(command.lstrip().rstrip()), get_main_globals(), locals(), filename=tmpfile) os.system("/usr/bin/python -E `which runsnake` %s &" % tmpfile)
def lcdd_rational(in_str, verbose=False): r""" Use the command ``lcdd_gmp`` from cddlib. Input: cdd format in_str; Output: cdd format out_str; """ in_filename = tmp_filename() in_file = open(in_filename,'w') in_file.write(in_str) in_file.close() if verbose: print(in_str) redund_procs = Popen(['lcdd_gmp',in_filename],stdin = PIPE, stdout=PIPE, stderr=PIPE) out_str, err = redund_procs.communicate() if verbose: print(out_str) return out_str
def test_write_to_file(): """ Test that libgap can write to files See :trac:`16502`, :trac:`15833`. EXAMPLES:: sage: from sage.libs.gap.test import test_write_to_file sage: test_write_to_file() """ fname = tmp_filename() message = "Ceci n'est pas une groupe" libgap.PrintTo(fname, message) with open(fname, 'r') as f: assert f.read() == message SystemFile = libgap.function_factory('StringFile') assert SystemFile(fname).sage() == message
def lrs_lrs(in_str, verbose=False): r""" Use the command 'lrs' from lrslib. Input: lrs format in_str; Output: lrs format out_str; """ #if is_package_installed('lrslib') != True: # print 'You must install the optional lrs package ' \ # 'for this function to work' # raise NotImplementedError in_filename = tmp_filename() in_file = open(in_filename,'w') in_file.write(in_str) in_file.close() if verbose: print(in_str) redund_procs = Popen(['lrs',in_filename],stdin = PIPE, stdout=PIPE, stderr=PIPE) out_str, err = redund_procs.communicate() if verbose: print(out_str) return out_str
def __init__(self, filename=None): """ Interface to the gperftools profiler INPUT: - ``filename`` -- string or ``None`` (default). The file name to log to. By default, a new temporary file is created. EXAMPLES:: sage: from sage.misc.gperftools import Profiler sage: Profiler() Profiler logging to ... """ if filename is None: from sage.misc.temporary_file import tmp_filename self._filename = tmp_filename(ext='.perf') else: self._filename = filename
def get_remote_file(filename, verbose=True): """ INPUT: - ``filename`` -- the URL of a file on the web, e.g., ``"http://modular.math.washington.edu/myfile.txt"`` - ``verbose`` -- whether to display download status OUTPUT: creates a file in the temp directory and returns the absolute path to that file. EXAMPLES:: sage: g = get_remote_file("http://sagemath.org/ack.html", verbose=False) # optional - internet sage: len(open(g).read()) # optional - internet; random 10198 """ if verbose: print("Attempting to load remote file: " + filename) from sage.misc.temporary_file import tmp_filename temp_name = tmp_filename() + '.' + os.path.splitext(filename)[1][1:] # IMPORTANT -- urllib takes a long time to load, # so do not import it in the module scope. # import compatible with py2 and py3 from six.moves.urllib.request import urlretrieve global cur cur = 0 if verbose: sys.stdout.write("Loading: [") sys.stdout.flush() urlretrieve(filename, temp_name, report_hook) print("]") else: urlretrieve(filename, temp_name) return temp_name
def compile_and_load(code): r""" INPUT: - ``code`` -- string containing code that could be in a .pyx file that is attached or put in a %cython block in the notebook. OUTPUT: a module, which results from compiling the given code and importing it EXAMPLES:: sage: module = sage.misc.cython.compile_and_load("def f(int n):\n return n*n") sage: module.f(10) 100 """ from sage.misc.temporary_file import tmp_filename file = tmp_filename(ext=".pyx") open(file,'w').write(code) from sage.server.support import cython_import return cython_import(file, create_local_c_file=False)
def has_latex(): """ Test if Latex is available. EXAMPLES:: sage: from sage.doctest.external import has_latex sage: has_latex() # random True """ from sage.misc.latex import _run_latex_, _latex_file_ from sage.misc.temporary_file import tmp_filename try: f = tmp_filename(ext='.tex') O = open(f, 'w') O.write(_latex_file_('2+3')) O.close() _run_latex_(f) return True except Exception: return False
def tmp_filename(): """ Return a temporary file. OUTPUT: String. The absolute filename of the temporary file. EXAMPLES:: sage: from sage.dev.misc import tmp_filename sage: tmp_filename().startswith(str(SAGE_TMP)) True """ try: from sage.misc.temporary_file import tmp_filename return tmp_filename() except ImportError: from tempfile import NamedTemporaryFile f = NamedTemporaryFile(dir=get_sage_tmp()) f.close() return f.name
def _is_present(self): r""" Run test code to determine whether the shared library is present. EXAMPLES:: sage: from sage.features import CythonFeature sage: empty = CythonFeature("empty", test_code="") sage: empty.is_present() FeatureTestResult('empty', True) """ from sage.misc.temporary_file import tmp_filename with open(tmp_filename(ext=".pyx"), 'w') as pyx: pyx.write(self.test_code) from sage.misc.cython import cython_import try: cython_import(pyx.name, verbose=-1) except CCompilerError: return FeatureTestResult(self, False, reason="Failed to compile test code.") except ImportError: return FeatureTestResult(self, False, reason="Failed to import test code.") except Exception: return FeatureTestResult(self, False, reason="Failed to run test code.") return FeatureTestResult(self, True, reason="Test code compiled and imported.")
def __call__(self, model, outfile='sage.png', verbose=1, extra_opts=''): """ This executes the tachyon program, given a scene file input. INPUT: - ``model`` -- string. The tachyon model. - ``outfile`` -- string, default ``'sage.png'``. The filename to save the model to. - ``verbose`` -- 0, 1, (default) or 2. The verbosity level. - ``extra_opts`` -- string (default: empty string). Extra options that will be appended to the tachyon commandline. EXAMPLES:: sage: from sage.interfaces.tachyon import TachyonRT sage: tgen = Tachyon() sage: tgen.texture('t1') sage: tgen.sphere((0,0,0),1,'t1') sage: tgen.str()[30:40] 'resolution' sage: t = TachyonRT() sage: import os sage: t(tgen.str(), outfile=os.devnull) tachyon ... Tachyon Parallel/Multiprocessor Ray Tracer... TESTS:: sage: from sage.env import SAGE_EXTCODE sage: filename = os.path.join(SAGE_EXTCODE, 'doctest', 'invalid', 'syntax_error.tachyon') sage: syntax_error = open(filename, 'r').read() sage: t(syntax_error, outfile=os.devnull) Traceback (most recent call last): ... RuntimeError: Tachyon Parallel/Multiprocessor Ray Tracer... ... Parser failed due to an input file syntax error. Aborting render. """ modelfile = tmp_filename(ext='.dat') open(modelfile, 'w').write(model) cmd = ['tachyon', modelfile] ext = outfile[-4:].lower() if ext == '.png': cmd += ['-format', 'PNG'] elif ext == '.tga': cmd += ['-format', 'TARGA'] elif ext == '.bmp': cmd += ['-format', 'BMP'] elif ext == '.ppm': cmd += ['-format', 'PPM'] elif ext == '.rgb': cmd += ['-format', 'RGB'] cmd += ['-o', outfile] cmd += extra_opts.split() if verbose >= 2: cmd += ['+V'] if verbose: print(' '.join(cmd)) import subprocess out = subprocess.check_output(cmd) if verbose >= 1: print(out) if out.rstrip().endswith('Aborting render.'): raise RuntimeError(out) if outfile != os.devnull and os.stat(outfile).st_size == 0: raise RuntimeError( 'tachyon did not abort but output file is empty')
def _launch_jmol(self): launch_script = tmp_filename(ext='.spt') with open(launch_script, 'w') as f: f.write('set defaultdirectory "{0}"\n'.format(self.filename())) f.write('script SCRIPT\n') os.system('jmol {0} 2>/dev/null 1>/dev/null &'.format(launch_script))
def gen_html_code(G, vertex_labels=False, edge_labels=False, vertex_partition=[], edge_partition=[], force_spring_layout=False, charge=-120, link_distance=30, link_strength=2, gravity=.04, vertex_size=7, edge_thickness=4): r""" Creates a .html file showing the graph using `d3.js <http://d3js.org/>`_. This function returns the name of the .html file. If you want to visualize the actual graph use :meth:`~sage.graphs.generic_graph.GenericGraph.show`. INPUT: - ``G`` -- the graph - ``vertex_labels`` (boolean) -- Whether to display vertex labels (set to ``False`` by default). - ``edge_labels`` (boolean) -- Whether to display edge labels (set to ``False`` by default). - ``vertex_partition`` -- a list of lists representing a partition of the vertex set. Vertices are then colored in the graph according to the partition. Set to ``[]`` by default. - ``edge_partition`` -- same as ``vertex_partition``, with edges instead. Set to ``[]`` by default. - ``force_spring_layout`` -- whether to take sage's position into account if there is one (see :meth:`~sage.graphs.generic_graph.GenericGraph.` and :meth:`~sage.graphs.generic_graph.GenericGraph.`), or to compute a spring layout. Set to ``False`` by default. - ``vertex_size`` -- The size of a vertex' circle. Set to `7` by default. - ``edge_thickness`` -- Thickness of an edge. Set to ``4`` by default. - ``charge`` -- the vertices' charge. Defines how they repulse each other. See `<https://github.com/mbostock/d3/wiki/Force-Layout>`_ for more information. Set to ``-120`` by default. - ``link_distance`` -- See `<https://github.com/mbostock/d3/wiki/Force-Layout>`_ for more information. Set to ``30`` by default. - ``link_strength`` -- See `<https://github.com/mbostock/d3/wiki/Force-Layout>`_ for more information. Set to ``2`` by default. - ``gravity`` -- See `<https://github.com/mbostock/d3/wiki/Force-Layout>`_ for more information. Set to ``0.04`` by default. EXAMPLES:: sage: graphs.RandomTree(50).show(method="js") # optional -- internet sage: g = graphs.PetersenGraph() sage: g.show(method = "js", vertex_partition=g.coloring()) # optional -- internet sage: graphs.DodecahedralGraph().show(method="js", force_spring_layout=True) # optional -- internet sage: graphs.DodecahedralGraph().show(method="js") # optional -- internet sage: g = digraphs.DeBruijn(2,2) sage: g.allow_multiple_edges(True) sage: g.add_edge("10","10","a") sage: g.add_edge("10","10","b") sage: g.add_edge("10","10","c") sage: g.add_edge("10","10","d") sage: g.add_edge("01","11","1") sage: g.show(method="js", vertex_labels=True,edge_labels=True, ....: link_distance=200,gravity=.05,charge=-500, ....: edge_partition=[[("11","12","2"),("21","21","a")]], ....: edge_thickness=4) # optional -- internet TESTS:: sage: from sage.graphs.graph_plot_js import gen_html_code sage: filename = gen_html_code(graphs.PetersenGraph()) """ directed = G.is_directed() multiple_edges = G.has_multiple_edges() # Associated an integer to each vertex v_to_id = {v: i for i, v in enumerate(G.vertices())} # Vertex colors color = {i: len(vertex_partition) for i in range(G.order())} for i, l in enumerate(vertex_partition): for v in l: color[v_to_id[v]] = i # Vertex list nodes = [] for v in G.vertices(): nodes.append({"name": str(v), "group": str(color[v_to_id[v]])}) # Edge colors. edge_color_default = "#aaa" color_list = rainbow(len(edge_partition)) edge_color = {} for i, l in enumerate(edge_partition): for e in l: u, v, label = e if len(e) == 3 else e+(None,) edge_color[u, v, label] = color_list[i] if not directed: edge_color[v, u, label] = color_list[i] # Edge list edges = [] seen = {} # How many times has this edge been seen ? for u, v, l in G.edges(): # Edge color color = edge_color.get((u, v, l), edge_color_default) # Computes the curve of the edge curve = 0 # Loop ? if u == v: seen[u, v] = seen.get((u, v), 0)+1 curve = seen[u, v]*10+10 # For directed graphs, one also has to take into accounts # edges in the opposite direction elif directed: if G.has_edge(v, u): seen[u, v] = seen.get((u, v), 0)+1 curve = seen[u, v]*15 else: if multiple_edges and len(G.edge_label(u, v)) != 1: # Multiple edges. The first one has curve 15, then # -15, then 30, then -30, ... seen[u, v] = seen.get((u, v), 0) + 1 curve = (1 if seen[u, v] % 2 else -1)*(seen[u, v]//2)*15 elif not directed and multiple_edges: # Same formula as above for multiple edges if len(G.edge_label(u, v)) != 1: seen[u, v] = seen.get((u, v), 0) + 1 curve = (1 if seen[u, v] % 2 else -1)*(seen[u, v]//2)*15 # Adding the edge to the list edges.append({"source": v_to_id[u], "target": v_to_id[v], "strength": 0, "color": color, "curve": curve, "name": str(l) if edge_labels else ""}) loops = [e for e in edges if e["source"] == e["target"]] edges = [e for e in edges if e["source"] != e["target"]] # Defines the vertices' layout if possible Gpos = G.get_pos() pos = [] if Gpos is not None and force_spring_layout is False: charge = 0 link_strength = 0 gravity = 0 for v in G.vertices(): x, y = Gpos[v] pos.append([x, -y]) # Encodes the data as a JSON string from json import JSONEncoder string = JSONEncoder().encode({"nodes": nodes, "links": edges, "loops": loops, "pos": pos, "directed": G.is_directed(), "charge": int(charge), "link_distance": int(link_distance), "link_strength": int(link_strength), "gravity": float(gravity), "vertex_labels": bool(vertex_labels), "edge_labels": bool(edge_labels), "vertex_size": int(vertex_size), "edge_thickness": int(edge_thickness)}) from sage.env import SAGE_EXTCODE js_code_file = open(SAGE_EXTCODE+"/graphs/graph_plot_js.html", 'r') js_code = js_code_file.read().replace("// HEREEEEEEEEEEE", string) js_code_file.close() # Writes the temporary .html file filename = tmp_filename(ext='.html') f = open(filename, 'w') f.write(js_code) f.close() return filename
def lovasz_theta(graph): r""" Return the value of Lovász theta-function of graph For a graph `G` this function is denoted by `\theta(G)`, and it can be computed in polynomial time. Mathematically, its most important property is the following: .. MATH:: \alpha(G)\leq\theta(G)\leq\chi(\overline{G}) with `\alpha(G)` and `\chi(\overline{G})` being, respectively, the maximum size of an :meth:`independent set <sage.graphs.graph.Graph.independent_set>` set of `G` and the :meth:`chromatic number <sage.graphs.graph.Graph.chromatic_number>` of the :meth:`complement <sage.graphs.generic_graph.GenericGraph.complement>` `\overline{G}` of `G`. For more information, see the :wikipedia:`Lovász_number`. .. NOTE:: - Implemented for undirected graphs only. Use to_undirected to convert a digraph to an undirected graph. - This function requires the optional package ``csdp``, which you can install with with ``sage -i csdp``. EXAMPLES:: sage: C=graphs.PetersenGraph() sage: C.lovasz_theta() # optional csdp 4.0 sage: graphs.CycleGraph(5).lovasz_theta() # optional csdp 2.236068 TEST:: sage: g = Graph() sage: g.lovasz_theta() # indirect doctest 0 """ n = graph.order() if n == 0: return 0 from networkx import write_edgelist from sage.misc.temporary_file import tmp_filename import os, subprocess from sage.env import SAGE_LOCAL from sage.misc.package import is_package_installed, PackageNotFoundError if not is_package_installed('csdp'): raise PackageNotFoundError("csdp") g = graph.relabel(inplace=False, perm=range(1,n+1)).networkx_graph() tf_name = tmp_filename() tf = open(tf_name, 'wb') tf.write(str(n)+'\n'+str(g.number_of_edges())+'\n') write_edgelist(g, tf, data=False) tf.close() lines = subprocess.check_output([os.path.join(SAGE_LOCAL, 'bin', 'theta'), tf_name]) return float(lines.split()[-1])
def graphics_from_save(self, save_function, save_kwds, file_extension, output_container, figsize=None, dpi=None): r""" Helper to construct graphics. This method can be used to simplify the implementation of a ``_rich_repr_`` method of a graphics object if there is already a function to save graphics to a file. INPUT: - ``save_function`` -- callable that can save graphics to a file and accepts options like :meth:`sage.plot.graphics.Graphics.save`. - ``save_kwds`` -- dictionary. Keyword arguments that are passed to the save function. - ``file_extension`` -- string starting with ``'.'``. The file extension of the graphics file. - ``output_container`` -- subclass of :class:`sage.repl.rich_output.output_basic.OutputBase`. The output container to use. Must be one of the types in :meth:`supported_output`. - ``figsize`` -- pair of integers (optional). The desired graphics size in pixels. Suggested, but need not be respected by the output. - ``dpi`` -- integer (optional). The desired resolution in dots per inch. Suggested, but need not be respected by the output. OUTPUT: Return an instance of ``output_container``. EXAMPLES:: sage: from sage.repl.rich_output import get_display_manager sage: dm = get_display_manager() sage: plt = plot(sin) sage: out = dm.graphics_from_save(plt.save, dict(), '.png', dm.types.OutputImagePng) sage: out OutputImagePng container sage: out.png.get().startswith(b'\x89PNG') True sage: out.png.filename() # random '/home/user/.sage/temp/localhost.localdomain/23903/tmp_pu5woK.png' """ import os if not file_extension.startswith(os.path.extsep): raise ValueError('file_extension must start with a period') if output_container not in self.supported_output(): raise OutputTypeException( 'output_container is not supported by backend') from sage.misc.temporary_file import tmp_filename filename = tmp_filename(ext=file_extension) # Call the save_function with the right arguments kwds = dict(save_kwds) if figsize is not None: kwds['figsize'] = figsize if dpi is not None: kwds['dpi'] = dpi save_function(filename, **kwds) from sage.repl.rich_output.buffer import OutputBuffer buf = OutputBuffer.from_file(filename) return output_container(buf)
def ffmpeg(self, savefile=None, show_path=False, output_format=None, ffmpeg_options='', delay=None, iterations=0, pix_fmt='rgb24'): r""" Returns a movie showing an animation composed from rendering the frames in self. This method will only work if ffmpeg is installed. See http://www.ffmpeg.org for information about ffmpeg. INPUT: - ``savefile`` - file that the mpeg gets saved to. .. warning: This will overwrite ``savefile`` if it already exists. - ``show_path`` - boolean (default: False); if True, print the path to the saved file - ``output_format`` - string (default: None); format and suffix to use for the video. This may be 'mpg', 'mpeg', 'avi', 'gif', or any other format that ffmpeg can handle. If this is None and the user specifies ``savefile`` with a suffix, say ``savefile='animation.avi'``, try to determine the format ('avi' in this case) from that file name. If no file is specified or if the suffix cannot be determined, 'mpg' is used. - ``ffmpeg_options`` - string (default: ''); this string is passed directly to ffmpeg. - ``delay`` - integer (default: None); delay in hundredths of a second between frames. The framerate is 100/delay. This is not supported for mpeg files: for mpegs, the frame rate is always 25 fps. - ``iterations`` - integer (default: 0); number of iterations of animation. If 0, loop forever. This is only supported for animated gif output and requires ffmpeg version 0.9 or later. For older versions, set ``iterations=None``. - ``pix_fmt`` - string (default: 'rgb24'); used only for gif output. Different values such as 'rgb8' or 'pal8' may be necessary depending on how ffmpeg was installed. Set ``pix_fmt=None`` to disable this option. If ``savefile`` is not specified: in notebook mode, display the animation; otherwise, save it to a default file name. Use :func:`sage.misc.misc.set_verbose` with ``level=1`` to see additional output. EXAMPLES:: sage: a = animate([sin(x + float(k)) for k in srange(0,2*pi,0.7)], ....: xmin=0, xmax=2*pi, figsize=[2,1]) sage: dir = tmp_dir() sage: a.ffmpeg(savefile=dir + 'new.mpg') # optional -- ffmpeg sage: a.ffmpeg(savefile=dir + 'new.avi') # optional -- ffmpeg sage: a.ffmpeg(savefile=dir + 'new.gif') # optional -- ffmpeg sage: a.ffmpeg(savefile=dir + 'new.mpg', show_path=True) # optional -- ffmpeg Animation saved to .../new.mpg. .. note:: If ffmpeg is not installed, you will get an error message like this:: Error: ffmpeg does not appear to be installed. Saving an animation to a movie file in any format other than GIF requires this software, so please install it and try again. See www.ffmpeg.org for more information. TESTS:: sage: a.ffmpeg(output_format='gif',delay=30,iterations=5) # optional -- ffmpeg """ if not self._have_ffmpeg(): msg = """Error: ffmpeg does not appear to be installed. Saving an animation to a movie file in any format other than GIF requires this software, so please install it and try again.""" raise OSError(msg) else: if savefile is None: if output_format is None: output_format = '.mpg' else: if output_format[0] != '.': output_format = '.'+output_format savefile = tmp_filename(ext=output_format) else: if output_format is None: suffix = os.path.splitext(savefile)[1] if len(suffix) > 0: output_format = suffix else: output_format = '.mpg' if not savefile.endswith(output_format): savefile += output_format early_options = '' if output_format == '.gif': # We try to set reasonable options for gif output. # # Older versions of ffmpeg (before 0.9, summer 2011) # use the option -loop_output instead of -loop. # Setting iterations=None is a way of preventing sage # from adding the -loop option. A separate # -loop_output option can be added with the # ffmpeg_options argument. if iterations is not None: loop_cmd = '-loop {0} '.format(iterations) else: loop_cmd = '' # A pix_fmt value is required for some but not all # ffmpeg installations. Setting pix_fmt=None will # prevent sage from adding this option, and it may be # controlled separately through ffmpeg_options. if pix_fmt is not None: pix_fmt_cmd = '-pix_fmt {0} '.format(pix_fmt) else: pix_fmt_cmd = '' ffmpeg_options += ' {0}{1}'.format(pix_fmt_cmd,loop_cmd) if delay is not None and output_format != '.mpeg' and output_format != '.mpg': early_options += ' -r %s ' % int(100/delay) savefile = os.path.abspath(savefile) pngdir = self.png() pngs = os.path.join(pngdir, "%08d.png") # For ffmpeg, it seems that some options, like '-g ... -r # ...', need to come before the input file names, while # some options, like '-pix_fmt rgb24', need to come # afterwards. Hence 'early_options' and 'ffmpeg_options' cmd = 'cd "%s"; sage-native-execute ffmpeg -y -f image2 %s -i %s %s %s' % (pngdir, early_options, pngs, ffmpeg_options, savefile) from subprocess import check_call, CalledProcessError, PIPE try: if sage.misc.misc.get_verbose() > 0: set_stderr = None else: set_stderr = PIPE sage.misc.misc.verbose("Executing '%s'" % cmd,level=1) sage.misc.misc.verbose("\n---- ffmpeg output below ----\n") check_call(cmd, shell=True, stderr=set_stderr) if show_path: print "Animation saved to file %s." % savefile except (CalledProcessError, OSError): print "Error running ffmpeg." raise
def gen_html_code(G, vertex_labels=False, edge_labels=False, vertex_partition=[], edge_partition=[], force_spring_layout=False, charge=-120, link_distance=30, link_strength=2, gravity=.04, vertex_size=7, edge_thickness=4): r""" Creates a .html file showing the graph using `d3.js <http://d3js.org/>`_. This function returns the name of the .html file. If you want to visualize the actual graph use :meth:`~sage.graphs.generic_graph.GenericGraph.show`. INPUT: - ``G`` -- the graph - ``vertex_labels`` (boolean) -- Whether to display vertex labels (set to ``False`` by default). - ``edge_labels`` (boolean) -- Whether to display edge labels (set to ``False`` by default). - ``vertex_partition`` -- a list of lists representing a partition of the vertex set. Vertices are then colored in the graph according to the partition. Set to ``[]`` by default. - ``edge_partition`` -- same as ``vertex_partition``, with edges instead. Set to ``[]`` by default. - ``force_spring_layout`` -- whether to take sage's position into account if there is one (see :meth:`~sage.graphs.generic_graph.GenericGraph.` and :meth:`~sage.graphs.generic_graph.GenericGraph.`), or to compute a spring layout. Set to ``False`` by default. - ``vertex_size`` -- The size of a vertex' circle. Set to `7` by default. - ``edge_thickness`` -- Thickness of an edge. Set to ``4`` by default. - ``charge`` -- the vertices' charge. Defines how they repulse each other. See `<https://github.com/mbostock/d3/wiki/Force-Layout>`_ for more information. Set to ``-120`` by default. - ``link_distance`` -- See `<https://github.com/mbostock/d3/wiki/Force-Layout>`_ for more information. Set to ``30`` by default. - ``link_strength`` -- See `<https://github.com/mbostock/d3/wiki/Force-Layout>`_ for more information. Set to ``2`` by default. - ``gravity`` -- See `<https://github.com/mbostock/d3/wiki/Force-Layout>`_ for more information. Set to ``0.04`` by default. EXAMPLES:: sage: graphs.RandomTree(50).show(method="js") # optional -- internet sage: g = graphs.PetersenGraph() sage: g.show(method = "js", vertex_partition=g.coloring()) # optional -- internet sage: graphs.DodecahedralGraph().show(method="js", force_spring_layout=True) # optional -- internet sage: graphs.DodecahedralGraph().show(method="js") # optional -- internet sage: g = digraphs.DeBruijn(2,2) sage: g.allow_multiple_edges(True) sage: g.add_edge("10","10","a") sage: g.add_edge("10","10","b") sage: g.add_edge("10","10","c") sage: g.add_edge("10","10","d") sage: g.add_edge("01","11","1") sage: g.show(method="js", vertex_labels=True,edge_labels=True, ....: link_distance=200,gravity=.05,charge=-500, ....: edge_partition=[[("11","12","2"),("21","21","a")]], ....: edge_thickness=4) # optional -- internet TESTS:: sage: from sage.graphs.graph_plot_js import gen_html_code sage: filename = gen_html_code(graphs.PetersenGraph()) """ directed = G.is_directed() multiple_edges = G.has_multiple_edges() # Associated an integer to each vertex v_to_id = {v: i for i, v in enumerate(G.vertices())} # Vertex colors color = {i: len(vertex_partition) for i in range(G.order())} for i, l in enumerate(vertex_partition): for v in l: color[v_to_id[v]] = i # Vertex list nodes = [] for v in G.vertices(): nodes.append({"name": str(v), "group": str(color[v_to_id[v]])}) # Edge colors. edge_color_default = "#aaa" color_list = rainbow(len(edge_partition)) edge_color = {} for i, l in enumerate(edge_partition): for e in l: u, v, label = e if len(e) == 3 else e + (None, ) edge_color[u, v, label] = color_list[i] if not directed: edge_color[v, u, label] = color_list[i] # Edge list edges = [] seen = {} # How many times has this edge been seen ? for u, v, l in G.edges(): # Edge color color = edge_color.get((u, v, l), edge_color_default) # Computes the curve of the edge curve = 0 # Loop ? if u == v: seen[u, v] = seen.get((u, v), 0) + 1 curve = seen[u, v] * 10 + 10 # For directed graphs, one also has to take into accounts # edges in the opposite direction elif directed: if G.has_edge(v, u): seen[u, v] = seen.get((u, v), 0) + 1 curve = seen[u, v] * 15 else: if multiple_edges and len(G.edge_label(u, v)) != 1: # Multiple edges. The first one has curve 15, then # -15, then 30, then -30, ... seen[u, v] = seen.get((u, v), 0) + 1 curve = (1 if seen[u, v] % 2 else -1) * (seen[u, v] // 2) * 15 elif not directed and multiple_edges: # Same formula as above for multiple edges if len(G.edge_label(u, v)) != 1: seen[u, v] = seen.get((u, v), 0) + 1 curve = (1 if seen[u, v] % 2 else -1) * (seen[u, v] // 2) * 15 # Adding the edge to the list edges.append({ "source": v_to_id[u], "target": v_to_id[v], "strength": 0, "color": color, "curve": curve, "name": str(l) if edge_labels else "" }) loops = [e for e in edges if e["source"] == e["target"]] edges = [e for e in edges if e["source"] != e["target"]] # Defines the vertices' layout if possible Gpos = G.get_pos() pos = [] if Gpos is not None and force_spring_layout is False: charge = 0 link_strength = 0 gravity = 0 for v in G.vertices(): x, y = Gpos[v] pos.append([x, -y]) # Encodes the data as a JSON string from json import JSONEncoder string = JSONEncoder().encode({ "nodes": nodes, "links": edges, "loops": loops, "pos": pos, "directed": G.is_directed(), "charge": int(charge), "link_distance": int(link_distance), "link_strength": int(link_strength), "gravity": float(gravity), "vertex_labels": bool(vertex_labels), "edge_labels": bool(edge_labels), "vertex_size": int(vertex_size), "edge_thickness": int(edge_thickness) }) from sage.env import SAGE_EXTCODE js_code_file = open(SAGE_EXTCODE + "/graphs/graph_plot_js.html", 'r') js_code = js_code_file.read().replace("// HEREEEEEEEEEEE", string) js_code_file.close() # Writes the temporary .html file filename = tmp_filename(ext='.html') f = open(filename, 'w') f.write(js_code) f.close() return filename
def lovasz_theta(graph): r""" Return the value of Lovász theta-function of graph For a graph `G` this function is denoted by `\theta(G)`, and it can be computed in polynomial time. Mathematically, its most important property is the following: .. MATH:: \alpha(G)\leq\theta(G)\leq\chi(\overline{G}) with `\alpha(G)` and `\chi(\overline{G})` being, respectively, the maximum size of an :meth:`independent set <sage.graphs.graph.Graph.independent_set>` set of `G` and the :meth:`chromatic number <sage.graphs.graph.Graph.chromatic_number>` of the :meth:`complement <sage.graphs.generic_graph.GenericGraph.complement>` `\overline{G}` of `G`. For more information, see the :wikipedia:`Lovász_number`. .. NOTE:: - Implemented for undirected graphs only. Use to_undirected to convert a digraph to an undirected graph. - This function requires the optional package ``csdp``, which you can install with with ``sage -i csdp``. EXAMPLES:: sage: C=graphs.PetersenGraph() sage: C.lovasz_theta() # optional csdp 4.0 sage: graphs.CycleGraph(5).lovasz_theta() # optional csdp 2.236068 TESTS:: sage: g = Graph() sage: g.lovasz_theta() # indirect doctest 0 """ n = graph.order() if n == 0: return 0 from networkx import write_edgelist from sage.misc.temporary_file import tmp_filename import os, subprocess from sage.env import SAGE_LOCAL from sage.misc.package import is_package_installed, PackageNotFoundError if not is_package_installed('csdp'): raise PackageNotFoundError("csdp") g = graph.relabel(inplace=False, perm=range(1, n + 1)).networkx_graph() tf_name = tmp_filename() tf = open(tf_name, 'wb') tf.write(str(n) + '\n' + str(g.number_of_edges()) + '\n') write_edgelist(g, tf, data=False) tf.close() lines = subprocess.check_output( [os.path.join(SAGE_LOCAL, 'bin', 'theta'), tf_name]) return float(lines.split()[-1])
def integrate(arg, polynomial=None, algorithm='triangulate', raw_output=False, verbose=False, **kwds): r""" Call to the function integrate from LattE integrale. INPUT: - ``arg`` -- a cdd or LattE description string. - ``polynomial`` -- multivariate polynomial or valid LattE polynomial description string. If given, the valuation parameter of LattE is set to integrate, and is set to volume otherwise. - ``algorithm`` -- (default: 'triangulate') the integration method. Use 'triangulate' for polytope triangulation or 'cone-decompose' for tangent cone decomposition method. - ``raw_output`` -- if ``True`` then return directly the output string from LattE. - ``verbose`` -- if ``True`` then return directly verbose output from LattE. - For all other options of the integrate program, consult the LattE manual. OUTPUT: Either a string (if ``raw_output`` if set to ``True``) or a rational. EXAMPLES:: sage: from sage.interfaces.latte import integrate sage: P = 2 * polytopes.cube() sage: x, y, z = polygen(QQ, 'x, y, z') Integrating over a polynomial over a polytope in either the H or V representation:: sage: integrate(P.cdd_Hrepresentation(), x^2*y^2*z^2, cdd=True) # optional - latte_int 4096/27 sage: integrate(P.cdd_Vrepresentation(), x^2*y^2*z^2, cdd=True) # optional - latte_int 4096/27 Computing the volume of a polytope in either the H or V representation:: sage: integrate(P.cdd_Hrepresentation(), cdd=True) # optional - latte_int 64 sage: integrate(P.cdd_Vrepresentation(), cdd=True) # optional - latte_int 64 Polynomials given as a string in LattE description are also accepted:: sage: integrate(P.cdd_Hrepresentation(), '[[1,[2,2,2]]]', cdd=True) # optional - latte_int 4096/27 TESTS:: Testing raw output:: sage: from sage.interfaces.latte import integrate sage: P = polytopes.cuboctahedron() sage: cddin = P.cdd_Vrepresentation() sage: x, y, z = polygen(QQ, 'x, y, z') sage: f = 3*x^2*y^4*z^6 + 7*y^3*z^5 sage: integrate(cddin, f, cdd=True, raw_output=True) # optional - latte_int '629/47775' Testing the ``verbose`` option to integrate over a polytope:: sage: ans = integrate(cddin, f, cdd=True, verbose=True, raw_output=True) # optional - latte_int This is LattE integrale ... ... Invocation: integrate --valuation=integrate --triangulate --redundancy-check=none --cdd --monomials=... /dev/stdin ... Testing triangulate algorithm:: sage: from sage.interfaces.latte import integrate sage: P = polytopes.cuboctahedron() sage: cddin = P.cdd_Vrepresentation() sage: integrate(cddin, algorithm='triangulate', cdd=True) # optional - latte_int 20/3 Testing convex decomposition algorithm:: sage: from sage.interfaces.latte import integrate sage: P = polytopes.cuboctahedron() sage: cddin = P.cdd_Vrepresentation() sage: integrate(cddin, algorithm='cone-decompose', cdd=True) # optional - latte_int 20/3 Testing raw output:: sage: from sage.interfaces.latte import integrate sage: P = polytopes.cuboctahedron() sage: cddin = P.cdd_Vrepresentation() sage: integrate(cddin, cdd=True, raw_output=True) # optional - latte_int '20/3' Testing polynomial given as a string in LattE description:: sage: from sage.interfaces.latte import integrate sage: P = polytopes.cuboctahedron() sage: integrate(P.cdd_Hrepresentation(), '[[3,[2,4,6]],[7,[0, 3, 5]]]', cdd=True) # optional - latte_int 629/47775 Testing the ``verbose`` option to compute the volume of a polytope:: sage: from sage.interfaces.latte import integrate sage: P = polytopes.cuboctahedron() sage: cddin = P.cdd_Vrepresentation() sage: ans = integrate(cddin, cdd=True, raw_output=True, verbose=True) # optional - latte_int This is LattE integrale ... ... Invocation: integrate --valuation=volume --triangulate --redundancy-check=none --cdd /dev/stdin ... """ from subprocess import Popen, PIPE from sage.misc.misc import SAGE_TMP from sage.rings.integer import Integer args = ['integrate'] got_polynomial = True if polynomial is not None else False if got_polynomial: args.append('--valuation=integrate') else: args.append('--valuation=volume') if algorithm=='triangulate': args.append('--triangulate') elif algorithm=='cone-decompose': args.append('--cone-decompose') if 'redundancy_check' not in kwds: args.append('--redundancy-check=none') for key,value in kwds.items(): if value is None or value is False: continue key = key.replace('_','-') if value is True: args.append('--{}'.format(key)) else: args.append('--{}={}'.format(key, value)) if got_polynomial: if not isinstance(polynomial, six.string_types): # transform polynomial to LattE description monomials_list = to_latte_polynomial(polynomial) else: monomials_list = str(polynomial) from sage.misc.temporary_file import tmp_filename filename_polynomial = tmp_filename() with open(filename_polynomial, 'w') as f: f.write(monomials_list) args += ['--monomials=' + filename_polynomial] args += ['/dev/stdin'] try: # The cwd argument is needed because latte # always produces diagnostic output files. latte_proc = Popen(args, stdin=PIPE, stdout=PIPE, stderr=(None if verbose else PIPE), cwd=str(SAGE_TMP)) except OSError: from sage.misc.package import PackageNotFoundError raise PackageNotFoundError('latte_int') ans, err = latte_proc.communicate(arg) ret_code = latte_proc.poll() if ret_code: if err is None: err = ", see error message above" else: err = ":\n" + err raise RuntimeError("LattE integrale program failed (exit code {})".format(ret_code) + err.strip()) ans = ans.splitlines() ans = ans[-5].split() assert(ans[0]=='Answer:') ans = ans[1] if raw_output: return ans else: from sage.rings.rational import Rational return Rational(ans)
def show(self, delay=20, iterations=0): r""" Show this animation. INPUT: - ``delay`` - (default: 20) delay in hundredths of a second between frames - ``iterations`` - integer (default: 0); number of iterations of animation. If 0, loop forever. .. note:: Currently this is done using an animated gif, though this could change in the future. This requires that either ffmpeg or the ImageMagick suite (in particular, the ``convert`` command) is installed. See also the :meth:`ffmpeg` method. EXAMPLES:: sage: a = animate([sin(x + float(k)) for k in srange(0,2*pi,0.7)], ... xmin=0, xmax=2*pi, figsize=[2,1]) sage: a.show() # optional -- ImageMagick The preceding will loop the animation forever. If you want to show only three iterations instead:: sage: a.show(iterations=3) # optional -- ImageMagick To put a half-second delay between frames:: sage: a.show(delay=50) # optional -- ImageMagick .. note:: If you don't have ffmpeg or ImageMagick installed, you will get an error message like this:: Error: Neither ImageMagick nor ffmpeg appears to be installed. Saving an animation to a GIF file or displaying an animation requires one of these packages, so please install one of them and try again. See www.imagemagick.org and www.ffmpeg.org for more information. """ if sage.doctest.DOCTEST_MODE: filename = tmp_filename(ext='.gif') self.gif(savefile=filename, delay=delay, iterations=iterations) return if plot.EMBEDDED_MODE: self.gif(delay=delay, iterations=iterations) else: filename = tmp_filename(ext='.gif') self.gif(delay=delay, savefile=filename, iterations=iterations) os.system('%s %s 2>/dev/null 1>/dev/null &' % (sage.misc.viewer.browser(), filename))
def __call__(self, program, complex, subcomplex=None, **kwds): """ Call a CHomP program to compute the homology of a chain complex, simplicial complex, or cubical complex. See :class:`CHomP` for full documentation. EXAMPLES:: sage: from sage.interfaces.chomp import CHomP sage: T = cubical_complexes.Torus() sage: CHomP()('homcubes', T) # indirect doctest, optional - CHomP {0: 0, 1: Z x Z, 2: Z} """ from sage.misc.temporary_file import tmp_filename from sage.homology.all import CubicalComplex, cubical_complexes from sage.homology.all import SimplicialComplex, Simplex from sage.homology.cubical_complex import Cube from sage.homology.chain_complex import HomologyGroup, ChainComplex from subprocess import Popen, PIPE from sage.rings.all import QQ, ZZ from sage.modules.all import VectorSpace, vector from sage.combinat.free_module import CombinatorialFreeModule if not have_chomp(program): raise OSError("Program %s not found" % program) verbose = kwds.get('verbose', False) generators = kwds.get('generators', False) extra_opts = kwds.get('extra_opts', '') base_ring = kwds.get('base_ring', ZZ) if extra_opts: extra_opts = extra_opts.split() else: extra_opts = [] # type of complex: cubical = False simplicial = False chain = False # CHomP seems to have problems with cubical complexes if the # first interval in the first cube defining the complex is # degenerate. So replace the complex X with [0,1] x X. if isinstance(complex, CubicalComplex): cubical = True edge = cubical_complexes.Cube(1) original_complex = complex complex = edge.product(complex) if verbose: print "Cubical complex" elif isinstance(complex, SimplicialComplex): simplicial = True if verbose: print "Simplicial complex" else: chain = True base_ring = kwds.get('base_ring', complex.base_ring()) if verbose: print "Chain complex over %s" % base_ring if base_ring == QQ: raise ValueError( "CHomP doesn't compute over the rationals, only over Z or F_p." ) if base_ring.is_prime_field(): p = base_ring.characteristic() extra_opts.append('-p%s' % p) mod_p = True else: mod_p = False # # complex # try: data = complex._chomp_repr_() except AttributeError: raise AttributeError( "Complex can not be converted to use with CHomP.") datafile = tmp_filename() f = open(datafile, 'w') f.write(data) f.close() # # subcomplex # if subcomplex is None: if cubical: subcomplex = CubicalComplex([complex.n_cells(0)[0]]) elif simplicial: m = re.search(r'\(([^,]*),', data) v = int(m.group(1)) subcomplex = SimplicialComplex([[v]]) else: # replace subcomplex with [0,1] x subcomplex. if cubical: subcomplex = edge.product(subcomplex) # # generators # if generators: genfile = tmp_filename() extra_opts.append('-g%s' % genfile) # # call program # if subcomplex is not None: try: sub = subcomplex._chomp_repr_() except AttributeError: raise AttributeError( "Subcomplex can not be converted to use with CHomP.") subfile = tmp_filename() f = open(subfile, 'w') f.write(sub) f.close() else: subfile = '' if verbose: print "Popen called with arguments", print[program, datafile, subfile] + extra_opts print print "CHomP output:" print # output = Popen([program, datafile, subfile, extra_opts], cmd = [program, datafile] if subfile: cmd.append(subfile) if extra_opts: cmd.extend(extra_opts) output = Popen(cmd, stdout=PIPE).communicate()[0] if verbose: print output print "End of CHomP output" print if generators: gens = open(genfile, 'r').read() if verbose: print "Generators:" print gens # # process output # # output contains substrings of one of the forms # "H_1 = Z", "H_1 = Z_2 + Z", "H_1 = Z_2 + Z^2", # "H_1 = Z + Z_2 + Z" if output.find('trivial') != -1: if mod_p: return {0: VectorSpace(base_ring, 0)} else: return {0: HomologyGroup(0, ZZ)} d = {} h = re.compile("^H_([0-9]*) = (.*)$", re.M) tors = re.compile("Z_([0-9]*)") # # homology groups # for m in h.finditer(output): if verbose: print m.groups() # dim is the dimension of the homology group dim = int(m.group(1)) # hom_str is the right side of the equation "H_n = Z^r + Z_k + ..." hom_str = m.group(2) # need to read off number of summands and their invariants if hom_str.find("0") == 0: if mod_p: hom = VectorSpace(base_ring, 0) else: hom = HomologyGroup(0, ZZ) else: rk = 0 if hom_str.find("^") != -1: rk_srch = re.search(r'\^([0-9]*)\s?', hom_str) rk = int(rk_srch.group(1)) rk += len(re.findall("(Z$)|(Z\s)", hom_str)) if mod_p: rk = rk if rk != 0 else 1 if verbose: print "dimension = %s, rank of homology = %s" % (dim, rk) hom = VectorSpace(base_ring, rk) else: n = rk invts = [] for t in tors.finditer(hom_str): n += 1 invts.append(int(t.group(1))) for i in range(rk): invts.append(0) if verbose: print "dimension = %s, number of factors = %s, invariants = %s" % ( dim, n, invts) hom = HomologyGroup(n, ZZ, invts) # # generators # if generators: if cubical: g = process_generators_cubical(gens, dim) if verbose: print "raw generators: %s" % g if g: module = CombinatorialFreeModule( base_ring, original_complex.n_cells(dim), prefix="", bracket=True) basis = module.basis() output = [] for x in g: v = module(0) for term in x: v += term[0] * basis[term[1]] output.append(v) g = output elif simplicial: g = process_generators_simplicial(gens, dim, complex) if verbose: print "raw generators: %s" % gens if g: module = CombinatorialFreeModule(base_ring, complex.n_cells(dim), prefix="", bracket=False) basis = module.basis() output = [] for x in g: v = module(0) for term in x: if complex._is_numeric(): v += term[0] * basis[term[1]] else: translate = complex._translation_from_numeric( ) simplex = Simplex( [translate[a] for a in term[1]]) v += term[0] * basis[simplex] output.append(v) g = output elif chain: g = process_generators_chain(gens, dim, base_ring) if verbose: print "raw generators: %s" % gens if g: if not mod_p: # sort generators to match up with corresponding invariant g = [ _[1] for _ in sorted(zip(invts, g), key=lambda x: x[0]) ] d[dim] = (hom, g) else: d[dim] = hom else: d[dim] = hom if chain: new_d = {} diff = complex.differential() if len(diff) == 0: return {} bottom = min(diff) top = max(diff) for dim in d: if complex._degree_of_differential == -1: # chain complex new_dim = bottom + dim else: # cochain complex new_dim = top - dim if isinstance(d[dim], tuple): # generators included. group = d[dim][0] gens = d[dim][1] new_gens = [] dimension = complex.differential(new_dim).ncols() # make sure that each vector is embedded in the # correct ambient space: pad with a zero if # necessary. for v in gens: v_dict = v.dict() if dimension - 1 not in v.dict(): v_dict[dimension - 1] = 0 new_gens.append(vector(base_ring, v_dict)) else: new_gens.append(v) new_d[new_dim] = (group, new_gens) else: new_d[new_dim] = d[dim] d = new_d return d
def export_image( self, targetfile, datafile, #name (path) of data file Jmol can read or script file telling it what to read or load datafile_cmd='script', #"script" or "load" image_type='PNG', #PNG, JPG, GIF figsize=5, **kwds): r""" This executes JmolData.jar to make an image file. INPUT: - targetfile -- the full path to the file where the image should be written. - datafile -- full path to the data file Jmol can read or text of a script telling Jmol what to read or load. - datafile_cmd -- (default ``'script'``) ``'load'`` or ``'script'`` should be ``"load"`` for a data file. - image_type -- (default ``"PNG"``) ``'PNG'`` ``'JPG'`` or ``'GIF'`` - figsize -- number (default 5) equal to (pixels/side)/100 OUTPUT: Image file, .png, .gif or .jpg (default .png) .. note:: Examples will generate an error message if a functional Java Virtual Machine (JVM) is not installed on the machine the Sage instance is running on. .. warning:: Programmers using this module should check that the JVM is available before making calls to avoid the user getting error messages. Check for the JVM using the function :meth:`is_jvm_available`, which returns True if a JVM is available. EXAMPLES: Use Jmol to load a pdb file containing some DNA from a web data base and make an image of the DNA. If you execute this in the notebook, the image will appear in the output cell:: sage: from sage.interfaces.jmoldata import JmolData sage: JData = JmolData() sage: script = "load =1lcd;display DNA;moveto 0.0 { -473 -713 -518 59.94} 100.0 0.0 0.0 {21.17 26.72 27.295} 27.544636 {0.0 0.0 0.0} -25.287832 64.8414 0.0;" sage: testfile = tmp_filename(ext="DNA.png") sage: JData.export_image(targetfile=testfile,datafile=script,image_type="PNG") # optional -- java internet sage: print os.path.exists(testfile) # optional -- java internet True Use Jmol to save an image of a 3-D object created in Sage. This method is used internally by plot3d to generate static images. This example doesn't have correct scaling:: sage: from sage.interfaces.jmoldata import JmolData sage: JData = JmolData() sage: D=dodecahedron() sage: from sage.misc.misc import SAGE_TMP sage: archive_name=os.path.join(SAGE_TMP, "archive.jmol.zip") sage: D.export_jmol(archive_name) #not scaled properly...need some more steps. sage: testfile = os.path.join(SAGE_TMP, "testimage.png") sage: script = 'set defaultdirectory "%s"\n script SCRIPT\n'%archive_name sage: JData.export_image(targetfile =testfile,datafile = script, image_type="PNG") # optional -- java sage: print os.path.exists(testfile) # optional -- java True """ # Set up paths, file names and scripts jmolpath = os.path.join(SAGE_LOCAL, "share", "jmol", "JmolData.jar") launchscript = "" if (datafile_cmd != 'script'): launchscript = "load " launchscript = launchscript + datafile imagescript = "write " + image_type + " " + targetfile + "\n" sizeStr = "%sx%s" % (figsize * 100, figsize * 100) # Scratch file for Jmol errors scratchout = tmp_filename(ext=".txt") with open(scratchout, 'w') as jout: # Now call the java application and write the file. env = dict(os.environ) env['LC_ALL'] = 'C' env['LANG'] = 'C' subprocess.call([ "java", "-Xmx512m", "-Djava.awt.headless=true", "-jar", jmolpath, "-iox", "-g", sizeStr, "-J", launchscript, "-j", imagescript ], stdout=jout, stderr=jout, env=env) if not os.path.isfile(targetfile): raise RuntimeError( "Jmol failed to create file %s, see %s for details" % (repr(targetfile), repr(scratchout))) os.unlink(scratchout)
def ehrhart_polynomial(self, verbose=False, dual=None, irrational_primal=None, irrational_all_primal=None, maxdet=None, no_decomposition=None, compute_vertex_cones=None, smith_form=None, dualization=None, triangulation=None, triangulation_max_height=None, **kwds): r""" Return the Ehrhart polynomial of this polyhedron. Let `P` be a lattice polytope in `\RR^d` and define `L(P,t) = \# (tP \cap \ZZ^d)`. Then E. Ehrhart proved in 1962 that `L` coincides with a rational polynomial of degree `d` for integer `t`. `L` is called the *Ehrhart polynomial* of `P`. For more information see the :wikipedia:`Ehrhart_polynomial`. INPUT: - ``verbose`` - (boolean, default to ``False``) if ``True``, print the whole output of the LattE command. The following options are passed to the LattE command, for details you should consult `the LattE documentation <https://www.math.ucdavis.edu/~latte/software/packages/latte_current/>`__: - ``dual`` - (boolean) triangulate and signed-decompose in the dual space - ``irrational_primal`` - (boolean) triangulate in the dual space, signed-decompose in the primal space using irrationalization. - ``irrational_all_primal`` - (boolean) Triangulate and signed-decompose in the primal space using irrationalization. - ``maxdet`` -- (integer) decompose down to an index (determinant) of ``maxdet`` instead of index 1 (unimodular cones). - ``no_decomposition`` -- (boolean) do not signed-decompose simplicial cones. - ``compute_vertex_cones`` -- (string) either 'cdd' or 'lrs' or '4ti2' - ``smith_form`` -- (string) either 'ilio' or 'lidia' - ``dualization`` -- (string) either 'cdd' or '4ti2' - ``triangulation`` - (string) 'cddlib', '4ti2' or 'topcom' - ``triangulation_max_height`` - (integer) use a uniform distribution of height from 1 to this number .. NOTE:: Any additional argument is forwarded to LattE's executable ``count``. All occurrences of '_' will be replaced with a '-'. ALGORITHM: This method calls the program ``count`` from LattE integrale, a program for lattice point enumeration (see https://www.math.ucdavis.edu/~latte/). EXAMPLES:: sage: P = Polyhedron(vertices=[(0,0,0),(3,3,3),(-3,2,1),(1,-1,-2)]) sage: p = P.ehrhart_polynomial() # optional - latte_int sage: p # optional - latte_int 7/2*t^3 + 2*t^2 - 1/2*t + 1 sage: p(1) # optional - latte_int 6 sage: len(P.integral_points()) 6 sage: p(2) # optional - latte_int 36 sage: len((2*P).integral_points()) 36 The unit hypercubes:: sage: from itertools import product sage: def hypercube(d): ....: return Polyhedron(vertices=list(product([0,1],repeat=d))) sage: hypercube(3).ehrhart_polynomial() # optional - latte_int t^3 + 3*t^2 + 3*t + 1 sage: hypercube(4).ehrhart_polynomial() # optional - latte_int t^4 + 4*t^3 + 6*t^2 + 4*t + 1 sage: hypercube(5).ehrhart_polynomial() # optional - latte_int t^5 + 5*t^4 + 10*t^3 + 10*t^2 + 5*t + 1 sage: hypercube(6).ehrhart_polynomial() # optional - latte_int t^6 + 6*t^5 + 15*t^4 + 20*t^3 + 15*t^2 + 6*t + 1 An empty polyhedron:: sage: P = Polyhedron(ambient_dim=3, vertices=[]) sage: P.ehrhart_polynomial() # optional - latte_int 0 sage: parent(_) # optional - latte_int Univariate Polynomial Ring in t over Rational Field TESTS: Test options:: sage: P = Polyhedron(ieqs=[[1,-1,1,0], [-1,2,-1,0], [1,1,-2,0]], eqns=[[-1,2,-1,-3]], base_ring=ZZ) sage: p = P.ehrhart_polynomial(maxdet=5, verbose=True) # optional - latte_int This is LattE integrale ... ... Invocation: count --ehrhart-polynomial '--redundancy-check=none' '--maxdet=5' --cdd ... ... sage: p # optional - latte_int 1/2*t^2 + 3/2*t + 1 sage: p = P.ehrhart_polynomial(dual=True, verbose=True) # optional - latte_int This is LattE integrale ... ... Invocation: count --ehrhart-polynomial '--redundancy-check=none' --dual --cdd ... ... sage: p # optional - latte_int 1/2*t^2 + 3/2*t + 1 sage: p = P.ehrhart_polynomial(irrational_primal=True, verbose=True) # optional - latte_int This is LattE integrale ... ... Invocation: count --ehrhart-polynomial '--redundancy-check=none' --irrational-primal --cdd ... ... sage: p # optional - latte_int 1/2*t^2 + 3/2*t + 1 sage: p = P.ehrhart_polynomial(irrational_all_primal=True, verbose=True) # optional - latte_int This is LattE integrale ... ... Invocation: count --ehrhart-polynomial '--redundancy-check=none' --irrational-all-primal --cdd ... sage: p # optional - latte_int 1/2*t^2 + 3/2*t + 1 Test bad options:: sage: P.ehrhart_polynomial(bim_bam_boum=19) # optional - latte_int Traceback (most recent call last): ... RuntimeError: Latte returned 1 when running: count --ehrhart-polynomial --redundancy-check=none --bim-bam-boum=19 --cdd ... (see output above) """ if not self.is_lattice_polytope(): raise ValueError("this must be a lattice polytope") from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing R = PolynomialRing(QQ, 't') if self.is_empty(): return R.zero() from sage.misc.temporary_file import tmp_filename from sage.misc.misc import SAGE_TMP from subprocess import Popen, PIPE in_str = self.cdd_Hrepresentation() in_filename = tmp_filename() + '.ine' in_file = open(in_filename, 'w') in_file.write(self.cdd_Hrepresentation()) in_file.close() args = ['count', '--ehrhart-polynomial'] if 'redundancy_check' not in kwds: args.append('--redundancy-check=none') # note: the options below are explicitely written in the function # declaration in order to keep tab completion (see #18211). kwds.update({ 'dual' : dual, 'irrational_primal' : irrational_primal, 'irrational_all_primal' : irrational_all_primal, 'maxdet' : maxdet, 'no_decomposition' : no_decomposition, 'compute_vertex_cones' : compute_vertex_cones, 'smith_form' : smith_form, 'dualization' : dualization, 'triangulation' : triangulation, 'triangulation_max_height': triangulation_max_height}) for key,value in kwds.items(): if value is None or value is False: continue key = key.replace('_','-') if value is True: args.append('--{}'.format(key)) else: args.append('--{}={}'.format(key, value)) args.append('--cdd') args.append(in_filename) try: latte_proc = Popen(args, stdin=PIPE, stdout=PIPE, stderr=(None if verbose else PIPE), cwd=str(SAGE_TMP)) except OSError: raise ValueError("The package latte_int must be installed (type " "'sage -i latte_int') in a console or " "'install_package('latte_int') at a Sage prompt)!\n") ans, err = latte_proc.communicate() ret_code = latte_proc.poll() if ret_code: if not verbose: print err raise RuntimeError("Latte returned {} when running:\n{}\n(see output above)".format(ret_code, ' '.join(args))) p = ans.splitlines()[-2] return R(p)
def graphics_from_save(self, save_function, save_kwds, file_extension, output_container, figsize=None, dpi=None): r""" Helper to construct graphics. This method can be used to simplify the implementation of a ``_rich_repr_`` method of a graphics object if there is already a function to save graphics to a file. INPUT: - ``save_function`` -- callable that can save graphics to a file and accepts options like :meth:`sage.plot.graphics.Graphics.save`. - ``save_kwds`` -- dictionary. Keyword arguments that are passed to the save function. - ``file_extension`` -- string starting with ``'.'``. The file extension of the graphics file. - ``output_container`` -- subclass of :class:`sage.repl.rich_output.output_basic.OutputBase`. The output container to use. Must be one of the types in :meth:`supported_output`. - ``figsize`` -- pair of integers (optional). The desired graphics size in pixels. Suggested, but need not be respected by the output. - ``dpi`` -- integer (optional). The desired resolution in dots per inch. Suggested, but need not be respected by the output. OUTPUT: Return an instance of ``output_container``. EXAMPLES:: sage: from sage.repl.rich_output import get_display_manager sage: dm = get_display_manager() sage: plt = plot(sin) sage: out = dm.graphics_from_save(plt.save, dict(), '.png', dm.types.OutputImagePng) sage: out OutputImagePng container sage: out.png.get().startswith('\x89PNG') True sage: out.png.filename() # random '/home/user/.sage/temp/localhost.localdomain/23903/tmp_pu5woK.png' """ import os if not file_extension.startswith(os.path.extsep): raise ValueError('file_extension must start with a period') if output_container not in self.supported_output(): raise OutputTypeException('output_container is not supported by backend') from sage.misc.temporary_file import tmp_filename filename = tmp_filename(ext=file_extension) # Call the save_function with the right arguments kwds = dict(save_kwds) if figsize is not None: kwds['figsize'] = figsize if dpi is not None: kwds['dpi'] = dpi save_function(filename, **kwds) from sage.repl.rich_output.buffer import OutputBuffer buf = OutputBuffer.from_file(filename) return output_container(buf)
def gif(self, delay=20, savefile=None, iterations=0, show_path=False, use_ffmpeg=False): r""" Returns an animated gif composed from rendering the graphics objects in self. This method will only work if either (a) the ImageMagick software suite is installed, i.e., you have the ``convert`` command or (b) ``ffmpeg`` is installed. See [IM] for more about ImageMagick, and see [FF] for more about ``ffmpeg``. By default, this produces the gif using ``convert`` if it is present. If this can't find ``convert`` or if ``use_ffmpeg`` is True, then it uses ``ffmpeg`` instead. INPUT: - ``delay`` - (default: 20) delay in hundredths of a second between frames - ``savefile`` - file that the animated gif gets saved to - ``iterations`` - integer (default: 0); number of iterations of animation. If 0, loop forever. - ``show_path`` - boolean (default: False); if True, print the path to the saved file - ``use_ffmpeg`` - boolean (default: False); if True, use 'ffmpeg' by default instead of 'convert'. If ``savefile`` is not specified: in notebook mode, display the animation; otherwise, save it to a default file name. EXAMPLES:: sage: a = animate([sin(x + float(k)) for k in srange(0,2*pi,0.7)], ....: xmin=0, xmax=2*pi, figsize=[2,1]) sage: dir = tmp_dir() sage: a.gif() # not tested sage: a.gif(savefile=dir + 'my_animation.gif', delay=35, iterations=3) # optional -- ImageMagick sage: a.gif(savefile=dir + 'my_animation.gif', show_path=True) # optional -- ImageMagick Animation saved to .../my_animation.gif. sage: a.gif(savefile=dir + 'my_animation_2.gif', show_path=True, use_ffmpeg=True) # optional -- ffmpeg Animation saved to .../my_animation_2.gif. .. note:: If neither ffmpeg nor ImageMagick is installed, you will get an error message like this:: Error: Neither ImageMagick nor ffmpeg appears to be installed. Saving an animation to a GIF file or displaying an animation requires one of these packages, so please install one of them and try again. See www.imagemagick.org and www.ffmpeg.org for more information. """ from sage.misc.sage_ostools import have_program have_convert = have_program('convert') have_ffmpeg = self._have_ffmpeg() if use_ffmpeg or not have_convert: if have_ffmpeg: self.ffmpeg(savefile=savefile, show_path=show_path, output_format='.gif', delay=delay, iterations=iterations) else: if not have_convert: msg = """ Error: Neither ImageMagick nor ffmpeg appears to be installed. Saving an animation to a GIF file or displaying an animation requires one of these packages, so please install one of them and try again. See www.imagemagick.org and www.ffmpeg.org for more information.""" else: msg = """ Error: ffmpeg does not appear to be installed. Download it from www.ffmpeg.org, or use 'convert' to produce gifs instead.""" raise OSError(msg) else: if not savefile: savefile = tmp_filename(ext='.gif') if not savefile.endswith('.gif'): savefile += '.gif' savefile = os.path.abspath(savefile) d = self.png() cmd = ( 'cd "%s"; sage-native-execute convert -dispose Background ' '-delay %s -loop %s *.png "%s"' ) % ( d, int(delay), int(iterations), savefile ) from subprocess import check_call, CalledProcessError try: check_call(cmd, shell=True) if show_path: print "Animation saved to file %s." % savefile except (CalledProcessError, OSError): msg = """ Error: Cannot generate GIF animation. Verify that convert (ImageMagick) or ffmpeg is installed, and that the objects passed to the animate command can be saved in PNG image format. See www.imagemagick.org and www.ffmpeg.org for more information.""" raise OSError(msg)
def integrate(arg, polynomial=None, algorithm='triangulate', raw_output=False, verbose=False, **kwds): r""" Call to the function integrate from LattE integrale. INPUT: - ``arg`` -- a cdd or LattE description string. - ``polynomial`` -- multivariate polynomial or valid LattE polynomial description string. If given, the valuation parameter of LattE is set to integrate, and is set to volume otherwise. - ``algorithm`` -- (default: 'triangulate') the integration method. Use 'triangulate' for polytope triangulation or 'cone-decompose' for tangent cone decomposition method. - ``raw_output`` -- if ``True`` then return directly the output string from LattE. - ``verbose`` -- if ``True`` then return directly verbose output from LattE. - For all other options of the integrate program, consult the LattE manual. OUTPUT: Either a string (if ``raw_output`` if set to ``True``) or a rational. EXAMPLES:: sage: from sage.interfaces.latte import integrate # optional - latte_int sage: P = 2 * polytopes.cube() sage: x, y, z = polygen(QQ, 'x, y, z') Integrating over a polynomial over a polytope in either the H or V representation:: sage: integrate(P.cdd_Hrepresentation(), x^2*y^2*z^2, cdd=True) # optional - latte_int 4096/27 sage: integrate(P.cdd_Vrepresentation(), x^2*y^2*z^2, cdd=True) # optional - latte_int 4096/27 Computing the volume of a polytope in either the H or V representation:: sage: integrate(P.cdd_Hrepresentation(), cdd=True) # optional - latte_int 64 sage: integrate(P.cdd_Vrepresentation(), cdd=True) # optional - latte_int 64 Polynomials given as a string in LattE description are also accepted:: sage: integrate(P.cdd_Hrepresentation(), '[[1,[2,2,2]]]', cdd=True) # optional - latte_int 4096/27 TESTS: Testing raw output:: sage: from sage.interfaces.latte import integrate # optional - latte_int sage: P = polytopes.cuboctahedron() sage: cddin = P.cdd_Vrepresentation() sage: x, y, z = polygen(QQ, 'x, y, z') sage: f = 3*x^2*y^4*z^6 + 7*y^3*z^5 sage: integrate(cddin, f, cdd=True, raw_output=True) # optional - latte_int '629/47775' Testing the ``verbose`` option to integrate over a polytope:: sage: ans = integrate(cddin, f, cdd=True, verbose=True, raw_output=True) # optional - latte_int This is LattE integrale ... ... Invocation: integrate --valuation=integrate --triangulate --redundancy-check=none --cdd --monomials=... /dev/stdin ... Testing triangulate algorithm:: sage: from sage.interfaces.latte import integrate # optional - latte_int sage: P = polytopes.cuboctahedron() sage: cddin = P.cdd_Vrepresentation() sage: integrate(cddin, algorithm='triangulate', cdd=True) # optional - latte_int 20/3 Testing convex decomposition algorithm:: sage: from sage.interfaces.latte import integrate # optional - latte_int sage: P = polytopes.cuboctahedron() sage: cddin = P.cdd_Vrepresentation() sage: integrate(cddin, algorithm='cone-decompose', cdd=True) # optional - latte_int 20/3 Testing raw output:: sage: from sage.interfaces.latte import integrate # optional - latte_int sage: P = polytopes.cuboctahedron() sage: cddin = P.cdd_Vrepresentation() sage: integrate(cddin, cdd=True, raw_output=True) # optional - latte_int '20/3' Testing polynomial given as a string in LattE description:: sage: from sage.interfaces.latte import integrate # optional - latte_int sage: P = polytopes.cuboctahedron() sage: integrate(P.cdd_Hrepresentation(), '[[3,[2,4,6]],[7,[0, 3, 5]]]', cdd=True) # optional - latte_int 629/47775 Testing the ``verbose`` option to compute the volume of a polytope:: sage: from sage.interfaces.latte import integrate # optional - latte_int sage: P = polytopes.cuboctahedron() sage: cddin = P.cdd_Vrepresentation() sage: ans = integrate(cddin, cdd=True, raw_output=True, verbose=True) # optional - latte_int This is LattE integrale ... ... Invocation: integrate --valuation=volume --triangulate --redundancy-check=none --cdd /dev/stdin ... Testing the runtime error:: sage: P = Polyhedron(rays=[[1,0],[0,1]]) sage: P._volume_latte() # optional - latte_int Traceback (most recent call last): ... RuntimeError: LattE integrale program failed (exit code -6): This is LattE integrale ... ... determinant: nonsquare matrix """ # Check that LattE is present Latte().require() arg = str_to_bytes(arg) from sage.rings.rational import Rational args = ['integrate'] got_polynomial = True if polynomial is not None else False if got_polynomial: args.append('--valuation=integrate') else: args.append('--valuation=volume') if algorithm == 'triangulate': args.append('--triangulate') elif algorithm == 'cone-decompose': args.append('--cone-decompose') if 'redundancy_check' not in kwds: args.append('--redundancy-check=none') for key, value in kwds.items(): if value is None or value is False: continue key = key.replace('_', '-') if value is True: args.append('--{}'.format(key)) else: args.append('--{}={}'.format(key, value)) if got_polynomial: if not isinstance(polynomial, str): # transform polynomial to LattE description monomials_list = to_latte_polynomial(polynomial) else: monomials_list = str(polynomial) from sage.misc.temporary_file import tmp_filename filename_polynomial = tmp_filename() with open(filename_polynomial, 'w') as f: f.write(monomials_list) args += ['--monomials=' + filename_polynomial] args += ['/dev/stdin'] # The cwd argument is needed because latte # always produces diagnostic output files. latte_proc = Popen(args, stdin=PIPE, stdout=PIPE, stderr=(None if verbose else PIPE), cwd=str(SAGE_TMP)) ans, err = latte_proc.communicate(arg) if err: err = bytes_to_str(err) ret_code = latte_proc.poll() if ret_code: if err is None: err = ", see error message above" else: err = ":\n" + err raise RuntimeError( "LattE integrale program failed (exit code {})".format(ret_code) + err.strip()) ans = bytes_to_str(ans) ans = ans.splitlines() ans = ans[-5].split() assert (ans[0] == 'Answer:') ans = ans[1] if raw_output: return ans else: return Rational(ans)
def __call__(self, program, complex, subcomplex=None, **kwds): """ Call a CHomP program to compute the homology of a chain complex, simplicial complex, or cubical complex. See :class:`CHomP` for full documentation. EXAMPLES:: sage: from sage.interfaces.chomp import CHomP sage: T = cubical_complexes.Torus() sage: CHomP()('homcubes', T) # indirect doctest, optional - CHomP {0: 0, 1: Z x Z, 2: Z} """ from sage.misc.temporary_file import tmp_filename from sage.homology.all import CubicalComplex, cubical_complexes from sage.homology.all import SimplicialComplex, Simplex from sage.homology.chain_complex import HomologyGroup from subprocess import Popen, PIPE from sage.rings.all import QQ, ZZ from sage.modules.all import VectorSpace, vector from sage.combinat.free_module import CombinatorialFreeModule if not have_chomp(program): raise OSError("Program %s not found" % program) verbose = kwds.get('verbose', False) generators = kwds.get('generators', False) extra_opts = kwds.get('extra_opts', '') base_ring = kwds.get('base_ring', ZZ) if extra_opts: extra_opts = extra_opts.split() else: extra_opts = [] # type of complex: cubical = False simplicial = False chain = False # CHomP seems to have problems with cubical complexes if the # first interval in the first cube defining the complex is # degenerate. So replace the complex X with [0,1] x X. if isinstance(complex, CubicalComplex): cubical = True edge = cubical_complexes.Cube(1) original_complex = complex complex = edge.product(complex) if verbose: print("Cubical complex") elif isinstance(complex, SimplicialComplex): simplicial = True if verbose: print("Simplicial complex") else: chain = True base_ring = kwds.get('base_ring', complex.base_ring()) if verbose: print("Chain complex over %s" % base_ring) if base_ring == QQ: raise ValueError("CHomP doesn't compute over the rationals, only over Z or F_p.") if base_ring.is_prime_field(): p = base_ring.characteristic() extra_opts.append('-p%s' % p) mod_p = True else: mod_p = False # # complex # try: data = complex._chomp_repr_() except AttributeError: raise AttributeError("Complex can not be converted to use with CHomP.") datafile = tmp_filename() f = open(datafile, 'w') f.write(data) f.close() # # subcomplex # if subcomplex is None: if cubical: subcomplex = CubicalComplex([complex.n_cells(0)[0]]) elif simplicial: m = re.search(r'\(([^,]*),', data) v = int(m.group(1)) subcomplex = SimplicialComplex([[v]]) else: # replace subcomplex with [0,1] x subcomplex. if cubical: subcomplex = edge.product(subcomplex) # # generators # if generators: genfile = tmp_filename() extra_opts.append('-g%s' % genfile) # # call program # if subcomplex is not None: try: sub = subcomplex._chomp_repr_() except AttributeError: raise AttributeError("Subcomplex can not be converted to use with CHomP.") subfile = tmp_filename() f = open(subfile, 'w') f.write(sub) f.close() else: subfile = '' if verbose: print("Popen called with arguments", end="") print([program, datafile, subfile] + extra_opts) print("") print("CHomP output:") print("") # output = Popen([program, datafile, subfile, extra_opts], cmd = [program, datafile] if subfile: cmd.append(subfile) if extra_opts: cmd.extend(extra_opts) output = Popen(cmd, stdout=PIPE).communicate()[0] if verbose: print(output) print("End of CHomP output") print("") if generators: gens = open(genfile, 'r').read() if verbose: print("Generators:") print(gens) # # process output # if output.find('ERROR') != -1: raise RuntimeError('error inside CHomP') # output contains substrings of one of the forms # "H_1 = Z", "H_1 = Z_2 + Z", "H_1 = Z_2 + Z^2", # "H_1 = Z + Z_2 + Z" if output.find('trivial') != -1: if mod_p: return {0: VectorSpace(base_ring, 0)} else: return {0: HomologyGroup(0, ZZ)} d = {} h = re.compile("^H_([0-9]*) = (.*)$", re.M) tors = re.compile("Z_([0-9]*)") # # homology groups # for m in h.finditer(output): if verbose: print(m.groups()) # dim is the dimension of the homology group dim = int(m.group(1)) # hom_str is the right side of the equation "H_n = Z^r + Z_k + ..." hom_str = m.group(2) # need to read off number of summands and their invariants if hom_str.find("0") == 0: if mod_p: hom = VectorSpace(base_ring, 0) else: hom = HomologyGroup(0, ZZ) else: rk = 0 if hom_str.find("^") != -1: rk_srch = re.search(r'\^([0-9]*)\s?', hom_str) rk = int(rk_srch.group(1)) rk += len(re.findall("(Z$)|(Z\s)", hom_str)) if mod_p: rk = rk if rk != 0 else 1 if verbose: print("dimension = %s, rank of homology = %s" % (dim, rk)) hom = VectorSpace(base_ring, rk) else: n = rk invts = [] for t in tors.finditer(hom_str): n += 1 invts.append(int(t.group(1))) for i in range(rk): invts.append(0) if verbose: print("dimension = %s, number of factors = %s, invariants = %s" % (dim, n, invts)) hom = HomologyGroup(n, ZZ, invts) # # generators # if generators: if cubical: g = process_generators_cubical(gens, dim) if verbose: print("raw generators: %s" % g) if g: module = CombinatorialFreeModule(base_ring, original_complex.n_cells(dim), prefix="", bracket=True) basis = module.basis() output = [] for x in g: v = module(0) for term in x: v += term[0] * basis[term[1]] output.append(v) g = output elif simplicial: g = process_generators_simplicial(gens, dim, complex) if verbose: print("raw generators: %s" % gens) if g: module = CombinatorialFreeModule(base_ring, complex.n_cells(dim), prefix="", bracket=False) basis = module.basis() output = [] for x in g: v = module(0) for term in x: if complex._is_numeric(): v += term[0] * basis[term[1]] else: translate = complex._translation_from_numeric() simplex = Simplex([translate[a] for a in term[1]]) v += term[0] * basis[simplex] output.append(v) g = output elif chain: g = process_generators_chain(gens, dim, base_ring) if verbose: print("raw generators: %s" % gens) if g: if not mod_p: # sort generators to match up with corresponding invariant g = [_[1] for _ in sorted(zip(invts, g), key=lambda x: x[0])] d[dim] = (hom, g) else: d[dim] = hom else: d[dim] = hom if chain: new_d = {} diff = complex.differential() if len(diff) == 0: return {} bottom = min(diff) top = max(diff) for dim in d: if complex._degree_of_differential == -1: # chain complex new_dim = bottom + dim else: # cochain complex new_dim = top - dim if isinstance(d[dim], tuple): # generators included. group = d[dim][0] gens = d[dim][1] new_gens = [] dimension = complex.differential(new_dim).ncols() # make sure that each vector is embedded in the # correct ambient space: pad with a zero if # necessary. for v in gens: v_dict = v.dict() if dimension - 1 not in v.dict(): v_dict[dimension - 1] = 0 new_gens.append(vector(base_ring, v_dict)) else: new_gens.append(v) new_d[new_dim] = (group, new_gens) else: new_d[new_dim] = d[dim] d = new_d return d
def count(arg, ehrhart_polynomial=False, multivariate_generating_function=False, raw_output=False, verbose=False, **kwds): r""" Call to the program count from LattE integrale INPUT: - ``arg`` -- a cdd or LattE description string - ``ehrhart_polynomial``, ``multivariate_generating_function`` -- to compute Ehrhart polynomial or multivariate generating function instead of just counting points - ``raw_output`` -- if ``True`` then return directly the output string from LattE - For all other options of the count program, consult the LattE manual OUTPUT: Either a string (if ``raw_output`` if set to ``True``) or an integer (when counting points), or a polynomial (if ``ehrhart_polynomial`` is set to ``True``) or a multivariate THING (if ``multivariate_generating_function`` is set to ``True``) EXAMPLES:: sage: from sage.interfaces.latte import count # optional - latte_int sage: P = 2 * polytopes.cube() Counting integer points from either the H or V representation:: sage: count(P.cdd_Hrepresentation(), cdd=True) # optional - latte_int 125 sage: count(P.cdd_Vrepresentation(), cdd=True) # optional - latte_int 125 Ehrhart polynomial:: sage: count(P.cdd_Hrepresentation(), cdd=True, ehrhart_polynomial=True) # optional - latte_int 64*t^3 + 48*t^2 + 12*t + 1 Multivariate generating function currently only work with ``raw_output=True``:: sage: opts = {'cdd': True, ....: 'multivariate_generating_function': True, ....: 'raw_output': True} sage: cddin = P.cdd_Hrepresentation() sage: print(count(cddin, **opts)) # optional - latte_int x[0]^2*x[1]^(-2)*x[2]^(-2)/((1-x[1])*(1-x[2])*(1-x[0]^(-1))) + x[0]^(-2)*x[1]^(-2)*x[2]^(-2)/((1-x[1])*(1-x[2])*(1-x[0])) + x[0]^2*x[1]^(-2)*x[2]^2/((1-x[1])*(1-x[2]^(-1))*(1-x[0]^(-1))) + x[0]^(-2)*x[1]^(-2)*x[2]^2/((1-x[1])*(1-x[0])*(1-x[2]^(-1))) + x[0]^2*x[1]^2*x[2]^(-2)/((1-x[2])*(1-x[1]^(-1))*(1-x[0]^(-1))) + x[0]^(-2)*x[1]^2*x[2]^(-2)/((1-x[2])*(1-x[0])*(1-x[1]^(-1))) + x[0]^2*x[1]^2*x[2]^2/((1-x[2]^(-1))*(1-x[1]^(-1))*(1-x[0]^(-1))) + x[0]^(-2)*x[1]^2*x[2]^2/((1-x[0])*(1-x[2]^(-1))*(1-x[1]^(-1))) TESTS: Testing raw output:: sage: from sage.interfaces.latte import count # optional - latte_int sage: P = polytopes.cuboctahedron() sage: cddin = P.cdd_Vrepresentation() sage: count(cddin, cdd=True, raw_output=True) # optional - latte_int '19' sage: count(cddin, cdd=True, raw_output=True, ehrhart_polynomial=True) # optional - latte_int ' + 1 * t^0 + 10/3 * t^1 + 8 * t^2 + 20/3 * t^3' sage: count(cddin, cdd=True, raw_output=True, multivariate_generating_function=True) # optional - latte_int 'x[0]^(-1)*x[1]^(-1)/((1-x[0]*x[2])*(1-x[0]^(-1)*x[1])*...x[0]^(-1)*x[2]^(-1)))\n' Testing the ``verbose`` option:: sage: n = count(cddin, cdd=True, verbose=True, raw_output=True) # optional - latte_int This is LattE integrale ... ... Invocation: count '--redundancy-check=none' --cdd /dev/stdin ... Total Unimodular Cones: ... Maximum number of simplicial cones in memory at once: ... <BLANKLINE> **** The number of lattice points is: **** Total time: ... sec Trivial input for which LattE's preprocessor does all the work:: sage: P = Polyhedron(vertices=[[0,0,0]]) sage: cddin = P.cdd_Hrepresentation() sage: count(cddin, cdd=True, raw_output=False) # optional - latte_int 1 Testing the runtime error:: sage: P = Polyhedron(rays=[[0,1], [1,0]]) sage: cddin = P.cdd_Hrepresentation() sage: count(cddin, cdd=True, raw_output=False) # optional - latte_int Traceback (most recent call last): ... RuntimeError: LattE integrale program failed (exit code 1): This is LattE integrale ... ... The polyhedron is unbounded. """ # Check that LattE is present Latte().require() arg = str_to_bytes(arg) args = ['count'] if ehrhart_polynomial and multivariate_generating_function: raise ValueError if ehrhart_polynomial: args.append('--ehrhart-polynomial') elif multivariate_generating_function: args.append('--multivariate-generating-function') if 'redundancy_check' not in kwds: args.append('--redundancy-check=none') for key, value in kwds.items(): if value is None or value is False: continue key = key.replace('_', '-') if value is True: args.append('--{}'.format(key)) else: args.append('--{}={}'.format(key, value)) if multivariate_generating_function: from sage.misc.temporary_file import tmp_filename filename = tmp_filename() with open(filename, 'w') as f: f.write(bytes_to_str(arg)) args += [filename] else: args += ['/dev/stdin'] # The cwd argument is needed because latte # always produces diagnostic output files. latte_proc = Popen(args, stdin=PIPE, stdout=PIPE, stderr=(None if verbose else PIPE), cwd=str(SAGE_TMP)) ans, err = latte_proc.communicate(arg) if err: err = bytes_to_str(err) ret_code = latte_proc.poll() if ret_code: if err is None: err = ", see error message above" else: err = ":\n" + err raise RuntimeError( "LattE integrale program failed (exit code {})".format(ret_code) + err.strip()) ans = bytes_to_str(ans) if ehrhart_polynomial: ans = ans.splitlines()[-2] if raw_output: return ans else: from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.rings.rational_field import QQ R = PolynomialRing(QQ, 't') return R(ans) elif multivariate_generating_function: with open(filename + '.rat') as f: ans = f.read() if raw_output: return ans else: raise NotImplementedError( "there is no Sage object to handle multivariate series from LattE, use raw_output=True" ) else: if ans: # Sometimes (when LattE's preproc does the work), no output appears on stdout. ans = ans.splitlines()[-1] if not ans: # opening a file is slow (30e-6s), so we read the file # numOfLatticePoints only in case of a IndexError above with open(SAGE_TMP + '/numOfLatticePoints', 'r') as f: ans = f.read() if raw_output: return ans else: return Integer(ans)
def export_image(self, targetfile, datafile, #name (path) of data file Jmol can read or script file telling it what to read or load datafile_cmd='script', #"script" or "load" image_type ='PNG', #PNG, JPG, GIF figsize=5, **kwds): r""" This executes JmolData.jar to make an image file. INPUT: - targetfile -- the full path to the file where the image should be written. - datafile -- full path to the data file Jmol can read or text of a script telling Jmol what to read or load. - datafile_cmd -- (default ``'script'``) ``'load'`` or ``'script'`` should be ``"load"`` for a data file. - image_type -- (default ``"PNG"``) ``'PNG'`` ``'JPG'`` or ``'GIF'`` - figsize -- number (default 5) equal to (pixels/side)/100 OUTPUT: Image file, .png, .gif or .jpg (default .png) .. note:: Examples will generate an error message if a functional Java Virtual Machine (JVM) is not installed on the machine the Sage instance is running on. .. warning:: Programmers using this module should check that the JVM is available before making calls to avoid the user getting error messages. Check for the JVM using the function :meth:`is_jvm_available`, which returns True if a JVM is available. EXAMPLES: Use Jmol to load a pdb file containing some DNA from a web data base and make an image of the DNA. If you execute this in the notebook, the image will appear in the output cell:: sage: from sage.interfaces.jmoldata import JmolData sage: JData = JmolData() sage: script = "load =1lcd;display DNA;moveto 0.0 { -473 -713 -518 59.94} 100.0 0.0 0.0 {21.17 26.72 27.295} 27.544636 {0.0 0.0 0.0} -25.287832 64.8414 0.0;" sage: testfile = tmp_filename(ext="DNA.png") sage: JData.export_image(targetfile=testfile,datafile=script,image_type="PNG") # optional -- java internet sage: print os.path.exists(testfile) # optional -- java internet True Use Jmol to save an image of a 3-D object created in Sage. This method is used internally by plot3d to generate static images. This example doesn't have correct scaling:: sage: from sage.interfaces.jmoldata import JmolData sage: JData = JmolData() sage: D=dodecahedron() sage: from sage.misc.misc import SAGE_TMP sage: archive_name=os.path.join(SAGE_TMP, "archive.jmol.zip") sage: D.export_jmol(archive_name) #not scaled properly...need some more steps. sage: testfile = os.path.join(SAGE_TMP, "testimage.png") sage: script = 'set defaultdirectory "%s"\n script SCRIPT\n'%archive_name sage: JData.export_image(targetfile =testfile,datafile = script, image_type="PNG") # optional -- java sage: print os.path.exists(testfile) # optional -- java True """ # Set up paths, file names and scripts jmolpath = os.path.join(SAGE_LOCAL, "share", "jmol", "JmolData.jar") launchscript = "" if (datafile_cmd!='script'): launchscript = "load " launchscript = launchscript + datafile imagescript = "write "+ image_type +" "+targetfile+"\n" sizeStr = "%sx%s" %(figsize*100,figsize*100) # Scratch file for Jmol errors scratchout = tmp_filename(ext=".txt") with open(scratchout, 'w') as jout: # Now call the java application and write the file. subprocess.call(["java", "-Xmx512m", "-Djava.awt.headless=true", "-jar", jmolpath, "-iox", "-g", sizeStr, "-J", launchscript, "-j", imagescript], stdout=jout, stderr=jout) if not os.path.isfile(targetfile): raise RuntimeError("Jmol failed to create file %s, see %s for details"%(repr(targetfile), repr(scratchout))) os.unlink(scratchout)
def gen_html_code(G, vertex_labels=True, edge_labels=False, vertex_partition=[], vertex_colors=None, edge_partition=[], force_spring_layout=False, charge=-120, link_distance=30, link_strength=2, gravity=.04, vertex_size=7, edge_thickness=4): r""" Creates a .html file showing the graph using `d3.js <http://d3js.org/>`_. This function returns the name of the .html file. If you want to visualize the actual graph use :meth:`~sage.graphs.generic_graph.GenericGraph.show`. INPUT: - ``G`` -- the graph - ``vertex_labels`` -- boolean (default: ``False``); whether to display vertex labels - ``edge_labels`` -- boolean (default: ``False``); whether to display edge labels - ``vertex_partition`` -- list (default: ``[]``); a list of lists representing a partition of the vertex set. Vertices are then colored in the graph according to the partition - ``vertex_colors`` -- dict (default: ``None``); a dictionary representing a partition of the vertex set. Keys are colors (ignored) and values are lists of vertices. Vertices are then colored in the graph according to the partition - ``edge_partition`` -- list (default: ``[]``); same as ``vertex_partition``, with edges instead - ``force_spring_layout`` -- boolean (default: ``False``); whether to take previously computed position of nodes into account if there is one, or to compute a spring layout - ``vertex_size`` -- integer (default: ``7``); the size of a vertex' circle - ``edge_thickness`` -- integer (default: ``4``); thickness of an edge - ``charge`` -- integer (default: ``-120``); the vertices' charge. Defines how they repulse each other. See `<https://github.com/mbostock/d3/wiki/Force-Layout>`_ for more information - ``link_distance`` -- integer (default: ``30``); see `<https://github.com/mbostock/d3/wiki/Force-Layout>`_ for more information - ``link_strength`` -- integer (default: ``2``); see `<https://github.com/mbostock/d3/wiki/Force-Layout>`_ for more information - ``gravity`` -- float (default: ``0.04``); see `<https://github.com/mbostock/d3/wiki/Force-Layout>`_ for more information .. WARNING:: Since the d3js package is not standard yet, the javascript is fetched from d3js.org website by the browser. If you want to avoid that (e.g. to protect your privacy or by lack of internet connection), you can install the d3js package for offline use by running ``sage -i d3js`` from the command line. EXAMPLES:: sage: graphs.RandomTree(50).show(method="js") # optional -- internet sage: g = graphs.PetersenGraph() sage: g.show(method="js", vertex_partition=g.coloring()) # optional -- internet sage: graphs.DodecahedralGraph().show(method="js", force_spring_layout=True) # optional -- internet sage: graphs.DodecahedralGraph().show(method="js") # optional -- internet sage: g = digraphs.DeBruijn(2, 2) sage: g.allow_multiple_edges(True) sage: g.add_edge("10", "10", "a") sage: g.add_edge("10", "10", "b") sage: g.add_edge("10", "10", "c") sage: g.add_edge("10", "10", "d") sage: g.add_edge("01", "11", "1") sage: g.show(method="js", vertex_labels=True,edge_labels=True, ....: link_distance=200, gravity=.05, charge=-500, ....: edge_partition=[[("11", "12", "2"), ("21", "21", "a")]], ....: edge_thickness=4) # optional -- internet TESTS:: sage: from sage.graphs.graph_plot_js import gen_html_code sage: filename = gen_html_code(graphs.PetersenGraph()) :trac:`17370`:: sage: filename = gen_html_code(graphs.CompleteBipartiteGraph(4, 5)) In the generated html code, the source (resp. target) of a link is the index of the node in the list defining the names of the nodes. We check that the order is correct (:trac:`27460`):: sage: filename = gen_html_code(DiGraph({1: [10]})) sage: with open(filename, 'r') as f: ....: data = f.read() sage: nodes = data.partition('"nodes":')[2]; nodes ...[{..."name": "10"...}, {..."name": "1"...}]... sage: links = data.partition('"links":')[2] sage: '"source": 1' in links and '"target": 0' in links True """ directed = G.is_directed() multiple_edges = G.has_multiple_edges() # Associated an integer to each vertex v_to_id = {v: i for i, v in enumerate(G)} # Vertex colors if vertex_colors is not None: vertex_partition = list(vertex_colors.values()) len_vertex_partition = len(vertex_partition) color = {i: len_vertex_partition for i in range(G.order())} for i, l in enumerate(vertex_partition): for v in l: color[v_to_id[v]] = i # Vertex list # Data for vertex v must be at position v_to_id[v] in list nodes nodes = [{"name": str(v), "group": str(color[v_to_id[v]])} for v in G] # Edge colors. edge_color_default = "#aaa" color_list = rainbow(len(edge_partition)) edge_color = {} for i, l in enumerate(edge_partition): for e in l: u, v, label = e if len(e) == 3 else e + (None, ) edge_color[u, v, label] = color_list[i] if not directed: edge_color[v, u, label] = color_list[i] # Edge list edges = [] seen = {} # How many times has this edge been seen ? for u, v, l in G.edge_iterator(): # Edge color color = edge_color.get((u, v, l), edge_color_default) # Computes the curve of the edge curve = 0 # Loop ? if u == v: seen[u, v] = seen.get((u, v), 0) + 1 curve = seen[u, v] * 10 + 10 # For directed graphs, one also has to take into accounts # edges in the opposite direction elif directed: if G.has_edge(v, u): seen[u, v] = seen.get((u, v), 0) + 1 curve = seen[u, v] * 15 else: if multiple_edges and len(G.edge_label(u, v)) != 1: # Multiple edges. The first one has curve 15, then # -15, then 30, then -30, ... seen[u, v] = seen.get((u, v), 0) + 1 curve = (1 if seen[u, v] % 2 else -1) * (seen[u, v] // 2) * 15 elif not directed and multiple_edges: # Same formula as above for multiple edges if len(G.edge_label(u, v)) != 1: seen[u, v] = seen.get((u, v), 0) + 1 curve = (1 if seen[u, v] % 2 else -1) * (seen[u, v] // 2) * 15 # Adding the edge to the list # The source (resp. target) is the index of u (resp. v) in list nodes edges.append({ "source": v_to_id[u], "target": v_to_id[v], "strength": 0, "color": color, "curve": curve, "name": str(l) if edge_labels else "" }) loops = [e for e in edges if e["source"] == e["target"]] edges = [e for e in edges if e["source"] != e["target"]] # Defines the vertices' layout if possible Gpos = G.get_pos() pos = [] if Gpos is not None and force_spring_layout is False: charge = 0 link_strength = 0 gravity = 0 for v in G: x, y = Gpos[v] pos.append([float(x), float(-y)]) # Encodes the data as a JSON string from json import JSONEncoder string = JSONEncoder().encode({ "nodes": nodes, "links": edges, "loops": loops, "pos": pos, "directed": G.is_directed(), "charge": int(charge), "link_distance": int(link_distance), "link_strength": int(link_strength), "gravity": float(gravity), "vertex_labels": bool(vertex_labels), "edge_labels": bool(edge_labels), "vertex_size": int(vertex_size), "edge_thickness": int(edge_thickness) }) from sage.env import SAGE_EXTCODE, SAGE_SHARE js_code_file = open(SAGE_EXTCODE + "/graphs/graph_plot_js.html", 'r') js_code = js_code_file.read().replace("// GRAPH_DATA_HEREEEEEEEEEEE", string) js_code_file.close() # Add d3.js script depending on whether d3js package is installed. d3js_filepath = os.path.join(SAGE_SHARE, 'd3js', 'd3.min.js') if os.path.exists(d3js_filepath): with open(d3js_filepath, 'r') as d3js_code_file: d3js_script = '<script>' + d3js_code_file.read() + '</script>' else: d3js_script = '<script src="http://d3js.org/d3.v3.min.js"></script>' js_code = js_code.replace('// D3JS_SCRIPT_HEREEEEEEEEEEE', d3js_script) # Writes the temporary .html file filename = tmp_filename(ext='.html') f = open(filename, 'w') f.write(js_code) f.close() return filename