From 2baf863a42cd79849834f7d8ad4d4f428929e3d1 Mon Sep 17 00:00:00 2001 From: Danielle Bolan Date: Thu, 7 Aug 2014 17:17:18 +0200 Subject: [PATCH 01/16] Update docs --- doc/examples.rst | 60 ++++++++++++++++++++++++++---------------------- doc/index.rst | 4 ++-- 2 files changed, 35 insertions(+), 29 deletions(-) diff --git a/doc/examples.rst b/doc/examples.rst index a4b0f5a..62a7dfd 100644 --- a/doc/examples.rst +++ b/doc/examples.rst @@ -1,9 +1,9 @@ LinPy Examples ============== -Creating a Polyhedron ------------------ - To create any polyhedron, first define the symbols used. Then use the polyhedron functions to define the constraints for the polyhedron. This example creates a square. +Basic Examples +------------- + To create any polyhedron, first define the symbols used. Then use the polyhedron functions to define the constraints. The following is a simple running example illustrating some different operations and properties that can be performed by LinPy with two squares. >>> from linpy import * >>> x, y = symbols('x y') @@ -11,25 +11,37 @@ Creating a Polyhedron >>> square1 = Le(0, x) & Le(x, 2) & Le(0, y) & Le(y, 2) >>> print(square1) And(Ge(x, 0), Ge(-x + 2, 0), Ge(y, 0), Ge(-y + 2, 0)) - -Urnary Operations ------------------ - + + Binary operations and properties examples: + + >>> square2 = Le(2, x) & Le(x, 4) & Le(2, y) & Le(y, 4) + >>> #test equality + >>> square1 == square2 + False + >>> # find the union of two polygons + >>> square1 + square2 + Or(And(Ge(x, 0), Ge(-x + 2, 0), Ge(y, 0), Ge(-y + 2, 0)), And(Ge(x - 2, 0), Ge(-x + 4, 0), Ge(y - 2, 0), Ge(-y + 4, 0))) + >>> # check if square1 and square2 are disjoint + >>> square1.disjoint(square2) + False + >>> # find the intersection of two polygons + >>> square1 & square2 + And(Eq(y - 2, 0), Eq(x - 2, 0)) + >>> # find the convex union of two polygons + >>> Polyhedron(square1 | sqaure2) + And(Ge(x, 0), Ge(-x + 4, 0), Ge(y, 0), Ge(-y + 4, 0), Ge(x - y + 2, 0), Ge(-x + y + 2, 0)) + + Unary operation and properties examples: + >>> square1.isempty() False - >>> square1.isbounded() - True - -Binary Operations ------------------ - - >>> square2 = Le(2, x) & Le(x, 4) & Le(2, y) & Le(y, 4) - >>> square1 + square2 - Or(And(Ge(x, 0), Ge(-x + 2, 0), Ge(y, 0), Ge(-y + 2, 0)), And(Ge(x - 2, 0), Ge(-x + 4, 0), Ge(y - 2, 0), Ge(-y + 4, 0))) - >>> # check if square1 and square2 are disjoint - >>> square1.disjoint(square2) - False - + >>> square1.symbols() + (x, y) + >>> square1.inequalities + (x, -x + 2, y, -y + 2) + >>> square1.project([x]) + And(Ge(-y + 2, 0), Ge(y, 0)) + Plot Examples ------------- @@ -51,7 +63,7 @@ Plot Examples .. figure:: images/cube.jpg :align: center - The user can also inspect a polygon's vertices and the integer points included in the polygon. + LinPy can also inspect a polygon's vertices and the integer points included in the polygon. >>> diamond = Ge(y, x - 1) & Le(y, x + 1) & Ge(y, -x - 1) & Le(y, -x + 1) >>> diamond.vertices() @@ -59,9 +71,3 @@ Plot Examples >>> diamond.points() [Point({x: -1, y: 0}), Point({x: 0, y: -1}), Point({x: 0, y: 0}), Point({x: 0, y: 1}), Point({x: 1, y: 0})] - - - - - - diff --git a/doc/index.rst b/doc/index.rst index c5fe0b4..b3a9271 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -6,12 +6,12 @@ Welcome to LinPy’s documentation! ================================= -LinPy is a Python library for symbolic mathematics. +LinPy is a Python wrapper for the Integer Set Library (isl) by Sven Verdoolaege. Isl ia a C library for manipulating sets and relations of integer points bounded by linear constraints. + If you are new to LinPy, start with the Examples. This is the central page for all of LinPy’s documentation. - Contents: .. toctree:: -- 2.20.1 From 997e3c68590b274c372a6785cda5a69887683891 Mon Sep 17 00:00:00 2001 From: Danielle Bolan Date: Fri, 8 Aug 2014 17:47:55 +0200 Subject: [PATCH 02/16] Update plot examples --- doc/domain.rst | 15 +-------------- doc/examples.rst | 31 +++++++++++++++++++++++++++---- doc/images/union.jpg | Bin 0 -> 24624 bytes doc/linexpr.rst | 6 ++++++ doc/polyhedra.rst | 4 +--- 5 files changed, 35 insertions(+), 21 deletions(-) create mode 100644 doc/images/union.jpg diff --git a/doc/domain.rst b/doc/domain.rst index 4cd79d9..edf8934 100644 --- a/doc/domain.rst +++ b/doc/domain.rst @@ -3,8 +3,6 @@ Domains Module .. py:class :: Domain - The properties of a domain can be are found using the following - .. py:method:: symbols Returns a tuple of the symbols that exsist in a domain. @@ -21,8 +19,6 @@ Domains Module Returns ``True`` if a domain depends on the given dimensions. - The unary properties of a domain can be inspected using the following methods. - .. py:method:: isempty(self) Return ``True`` is a domain is empty. @@ -39,8 +35,6 @@ Domains Module It is not guarenteed that a domain is disjoint. If it is necessary, this method will return a domain as disjoint. - The following methods compare two domains to find the binary properties. - .. py:method:: isdisjoint(self, other) Return ``True`` if the intersection of *self* and *other* results in an empty set. @@ -74,9 +68,6 @@ Domains Module Test whether every element in *other* is in a domain. - - The following methods implement unary operations on a domain. - .. py:method:: complement(self) ¬self @@ -98,8 +89,6 @@ Domains Module Return a single sample subset of a domain. - The following methods implement binary operations on two domains. - .. py:method:: intersection(self, other) self | other @@ -120,8 +109,6 @@ Domains Module Return the sum of two domains. - The following methods use lexicographical ordering to find the maximum or minimum element in a domain. - .. py:method:: lexmin(self) Return a new set containing the lexicographic minimum of the elements in the set. @@ -131,7 +118,7 @@ Domains Module Return a new set containing the lexicographic maximum of the elements in the set. - A 2D or 3D domain can be plotted using the :meth:`plot` function. The points, verticies, and faces of a domain can be inspected using the following functions. +A 2D or 3D domain can be plotted using the :meth:`plot` function. The points, verticies, and faces of a domain can be inspected using the following functions. .. py:method:: points(self) diff --git a/doc/examples.rst b/doc/examples.rst index 62a7dfd..13c59fc 100644 --- a/doc/examples.rst +++ b/doc/examples.rst @@ -18,16 +18,16 @@ Basic Examples >>> #test equality >>> square1 == square2 False - >>> # find the union of two polygons - >>> square1 + square2 + >>> # compute the union of two polygons + >>> square1 | square2 Or(And(Ge(x, 0), Ge(-x + 2, 0), Ge(y, 0), Ge(-y + 2, 0)), And(Ge(x - 2, 0), Ge(-x + 4, 0), Ge(y - 2, 0), Ge(-y + 4, 0))) >>> # check if square1 and square2 are disjoint >>> square1.disjoint(square2) False - >>> # find the intersection of two polygons + >>> # compute the intersection of two polygons >>> square1 & square2 And(Eq(y - 2, 0), Eq(x - 2, 0)) - >>> # find the convex union of two polygons + >>> # compute the convex union of two polygons >>> Polyhedron(square1 | sqaure2) And(Ge(x, 0), Ge(-x + 4, 0), Ge(y, 0), Ge(-y + 4, 0), Ge(x - y + 2, 0), Ge(-x + y + 2, 0)) @@ -39,6 +39,7 @@ Basic Examples (x, y) >>> square1.inequalities (x, -x + 2, y, -y + 2) + >>> # project out the variable x >>> square1.project([x]) And(Ge(-y + 2, 0), Ge(y, 0)) @@ -70,4 +71,26 @@ Plot Examples [Point({x: Fraction(0, 1), y: Fraction(1, 1)}), Point({x: Fraction(-1, 1), y: Fraction(0, 1)}), Point({x: Fraction(1, 1), y: Fraction(0, 1)}), Point({x: Fraction(0, 1), y: Fraction(-1, 1)})] >>> diamond.points() [Point({x: -1, y: 0}), Point({x: 0, y: -1}), Point({x: 0, y: 0}), Point({x: 0, y: 1}), Point({x: 1, y: 0})] + + The user also can pass another plot to the :meth:`plot` method. This can be useful to compare two polyhedrons on the same axis. This example illustrates the union of two squares. + + >>> from linpy import * + >>> import matplotlib.pyplot as plt + >>> from matplotlib import pylab + >>> x, y = symbols('x y') + >>> square1 = Le(0, x) & Le(x, 2) & Le(0, y) & Le(y, 2) + >>> square2 = Le(1, x) & Le(x, 3) & Le(1, y) & Le(y, 3) + >>> fig = plt.figure() + >>> plot = fig.add_subplot(1, 1, 1, aspect='equal') + >>> square1.plot(plot, facecolor='red', alpha=0.3) + >>> square2.plot(plot, facecolor='blue', alpha=0.3) + >>> squares = Polyhedron(square1 + square2) + >>> squares.plot(plot, facecolor='blue', alpha=0.3) + >>> pylab.show() + + .. figure:: images/union.jpg + :align: center + + + diff --git a/doc/images/union.jpg b/doc/images/union.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7d14ae8e22e6f46e425b1d79fc75a275743794e6 GIT binary patch literal 24624 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 b&!Ls0zOv#ZsmTD&OVfJ&i^vuTy8nLxur$`D literal 0 HcmV?d00001 diff --git a/doc/linexpr.rst b/doc/linexpr.rst index 4101252..2aab652 100644 --- a/doc/linexpr.rst +++ b/doc/linexpr.rst @@ -3,6 +3,9 @@ Linear Expression Module This class implements linear expressions. +.. py:class:: Expression + + .. py:method:: coefficient(self, symbol) Return the coefficient value of the given symbol. @@ -38,6 +41,9 @@ This class implements linear expressions. .. py:method:: tosympy(self) Return an expression as a sympy object. + +.. py:class:: Symbol(Expression) + .. py:class:: Dummy(Symbol) diff --git a/doc/polyhedra.rst b/doc/polyhedra.rst index 736cff6..47462a7 100644 --- a/doc/polyhedra.rst +++ b/doc/polyhedra.rst @@ -3,7 +3,7 @@ Polyhedra Module .. py:class:: Polyhedron - Polyhedron class allows users to build and inspect polyherons. The following methods provide the properties of a polyhedron. + Polyhedron class allows users to build and inspect polyherons. .. py:method:: equalities(self) @@ -17,8 +17,6 @@ Polyhedra Module Return ta list of the constraints of a polyhedron. - The following unary operations can be used to inspect a polyhedron. - .. py:method:: disjoint(self) Returns a polyhedron as a disjoint. -- 2.20.1 From 35c965895771a3df79744ae54f1eda91f0b62862 Mon Sep 17 00:00:00 2001 From: Danielle Bolan Date: Mon, 11 Aug 2014 17:56:08 +0200 Subject: [PATCH 03/16] Small changes to docs as recommended --- doc/examples.rst | 39 +++++++++++++++++++++++---------------- doc/images/cham_cube.jpg | Bin 0 -> 32703 bytes doc/images/cube.jpg | Bin 36839 -> 0 bytes doc/install.rst | 29 ++++++++++++++++++++++------- doc/modules.rst | 2 +- doc/polyhedra.rst | 10 +++++----- 6 files changed, 51 insertions(+), 29 deletions(-) create mode 100644 doc/images/cham_cube.jpg delete mode 100644 doc/images/cube.jpg diff --git a/doc/examples.rst b/doc/examples.rst index 13c59fc..1884f49 100644 --- a/doc/examples.rst +++ b/doc/examples.rst @@ -9,27 +9,27 @@ Basic Examples >>> x, y = symbols('x y') >>> # define the constraints of the polyhedron >>> square1 = Le(0, x) & Le(x, 2) & Le(0, y) & Le(y, 2) - >>> print(square1) + >>> square1 And(Ge(x, 0), Ge(-x + 2, 0), Ge(y, 0), Ge(-y + 2, 0)) Binary operations and properties examples: - >>> square2 = Le(2, x) & Le(x, 4) & Le(2, y) & Le(y, 4) + >>> square2 = Le(1, x) & Le(x, 3) & Le(1, y) & Le(y, 3) >>> #test equality >>> square1 == square2 False - >>> # compute the union of two polygons + >>> # compute the union of two polyhedrons >>> square1 | square2 - Or(And(Ge(x, 0), Ge(-x + 2, 0), Ge(y, 0), Ge(-y + 2, 0)), And(Ge(x - 2, 0), Ge(-x + 4, 0), Ge(y - 2, 0), Ge(-y + 4, 0))) + Or(And(Ge(x, 0), Ge(-x + 2, 0), Ge(y, 0), Ge(-y + 2, 0)), And(Ge(x - 1, 0), Ge(-x + 3, 0), Ge(y - 1, 0), Ge(-y + 3, 0))) >>> # check if square1 and square2 are disjoint >>> square1.disjoint(square2) False - >>> # compute the intersection of two polygons + >>> # compute the intersection of two polyhedrons >>> square1 & square2 - And(Eq(y - 2, 0), Eq(x - 2, 0)) - >>> # compute the convex union of two polygons + And(Ge(x - 1, 0), Ge(-x + 2, 0), Ge(y - 1, 0), Ge(-y + 2, 0)) + >>> # compute the convex union of two polyhedrons >>> Polyhedron(square1 | sqaure2) - And(Ge(x, 0), Ge(-x + 4, 0), Ge(y, 0), Ge(-y + 4, 0), Ge(x - y + 2, 0), Ge(-x + y + 2, 0)) + And(Ge(x, 0), Ge(y, 0), Ge(-y + 3, 0), Ge(-x + 3, 0), Ge(x - y + 2, 0), Ge(-x + y + 2, 0)) Unary operation and properties examples: @@ -55,24 +55,31 @@ Plot Examples >>> # define the symbols >>> x, y, z = symbols('x y z') >>> fig = plt.figure() - >>> cham_plot = fig.add_subplot(2, 2, 3, projection='3d') + >>> cham_plot = fig.add_subplot(1, 1, 1, projection='3d', aspect='equal') >>> cham_plot.set_title('Chamfered cube') - >>> cham = Le(0, x) & Le(x, 3) & Le(0, y) & Le(y, 3) & Le(0, z) & Le(z, 3) & Le(z - 2, x) & Le(x, z + 2) & Le(1 - z, x) & Le(x, 5 - z) & Le(z - 2, y) & Le(y, z + 2) & Le(1 - z, y) & Le(y, 5 - z) & Le(y - 2, x) & Le(x, y + 2) & Le(1 - y, x) & Le(x, 5 - y) - >>> cham.plot(cham_plot, facecolors=(1, 0, 0, 0.75)) + >>> cham = Le(0, x) & Le(x, 3) & Le(0, y) & Le(y, 3) & Le(0, z) & \ + Le(z, 3) & Le(z - 2, x) & Le(x, z + 2) & Le(1 - z, x) & \ + Le(x, 5 - z) & Le(z - 2, y) & Le(y, z + 2) & Le(1 - z, y) & \ + Le(y, 5 - z) & Le(y - 2, x) & Le(x, y + 2) & Le(1 - y, x) & Le(x, 5 - y) + >>> cham.plot(cham_plot, facecolor='red', alpha=0.75) >>> pylab.show() - .. figure:: images/cube.jpg + .. figure:: images/cham_cube.jpg :align: center - LinPy can also inspect a polygon's vertices and the integer points included in the polygon. +LinPy can also inspect a polygon's vertices and the integer points included in the polygon. >>> diamond = Ge(y, x - 1) & Le(y, x + 1) & Ge(y, -x - 1) & Le(y, -x + 1) >>> diamond.vertices() - [Point({x: Fraction(0, 1), y: Fraction(1, 1)}), Point({x: Fraction(-1, 1), y: Fraction(0, 1)}), Point({x: Fraction(1, 1), y: Fraction(0, 1)}), Point({x: Fraction(0, 1), y: Fraction(-1, 1)})] + [Point({x: Fraction(0, 1), y: Fraction(1, 1)}), \ + Point({x: Fraction(-1, 1), y: Fraction(0, 1)}), \ + Point({x: Fraction(1, 1), y: Fraction(0, 1)}), \ + Point({x: Fraction(0, 1), y: Fraction(-1, 1)})] >>> diamond.points() - [Point({x: -1, y: 0}), Point({x: 0, y: -1}), Point({x: 0, y: 0}), Point({x: 0, y: 1}), Point({x: 1, y: 0})] + [Point({x: -1, y: 0}), Point({x: 0, y: -1}), Point({x: 0, y: 0}), \ + Point({x: 0, y: 1}), Point({x: 1, y: 0})] - The user also can pass another plot to the :meth:`plot` method. This can be useful to compare two polyhedrons on the same axis. This example illustrates the union of two squares. +The user also can pass another plot to the :meth:`plot` method. This can be useful to compare two polyhedrons on the same axis. This example illustrates the union of two squares. >>> from linpy import * >>> import matplotlib.pyplot as plt diff --git a/doc/images/cham_cube.jpg b/doc/images/cham_cube.jpg new file mode 100644 index 0000000000000000000000000000000000000000..dd7dfcaae08cbd5a7bbf68a9b294e1c99b07cc9a GIT binary patch literal 32703 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 Date: Mon, 11 Aug 2014 18:07:05 +0200 Subject: [PATCH 04/16] Fix warnings in documentation generation --- doc/examples.rst | 31 ++++++++++++++++--------------- doc/polyhedra.rst | 6 +++--- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/doc/examples.rst b/doc/examples.rst index 1884f49..ea044b8 100644 --- a/doc/examples.rst +++ b/doc/examples.rst @@ -2,7 +2,8 @@ LinPy Examples ============== Basic Examples -------------- +-------------- + To create any polyhedron, first define the symbols used. Then use the polyhedron functions to define the constraints. The following is a simple running example illustrating some different operations and properties that can be performed by LinPy with two squares. >>> from linpy import * @@ -11,11 +12,11 @@ Basic Examples >>> square1 = Le(0, x) & Le(x, 2) & Le(0, y) & Le(y, 2) >>> square1 And(Ge(x, 0), Ge(-x + 2, 0), Ge(y, 0), Ge(-y + 2, 0)) - + Binary operations and properties examples: - + >>> square2 = Le(1, x) & Le(x, 3) & Le(1, y) & Le(y, 3) - >>> #test equality + >>> #test equality >>> square1 == square2 False >>> # compute the union of two polyhedrons @@ -30,9 +31,9 @@ Basic Examples >>> # compute the convex union of two polyhedrons >>> Polyhedron(square1 | sqaure2) And(Ge(x, 0), Ge(y, 0), Ge(-y + 3, 0), Ge(-x + 3, 0), Ge(x - y + 2, 0), Ge(-x + y + 2, 0)) - + Unary operation and properties examples: - + >>> square1.isempty() False >>> square1.symbols() @@ -42,7 +43,7 @@ Basic Examples >>> # project out the variable x >>> square1.project([x]) And(Ge(-y + 2, 0), Ge(y, 0)) - + Plot Examples ------------- @@ -78,9 +79,9 @@ LinPy can also inspect a polygon's vertices and the integer points included in t >>> diamond.points() [Point({x: -1, y: 0}), Point({x: 0, y: -1}), Point({x: 0, y: 0}), \ Point({x: 0, y: 1}), Point({x: 1, y: 0})] - + The user also can pass another plot to the :meth:`plot` method. This can be useful to compare two polyhedrons on the same axis. This example illustrates the union of two squares. - + >>> from linpy import * >>> import matplotlib.pyplot as plt >>> from matplotlib import pylab @@ -93,11 +94,11 @@ The user also can pass another plot to the :meth:`plot` method. This can be usef >>> square2.plot(plot, facecolor='blue', alpha=0.3) >>> squares = Polyhedron(square1 + square2) >>> squares.plot(plot, facecolor='blue', alpha=0.3) - >>> pylab.show() - + >>> pylab.show() + .. figure:: images/union.jpg - :align: center - - - + :align: center + + + diff --git a/doc/polyhedra.rst b/doc/polyhedra.rst index 605dded..1cb9396 100644 --- a/doc/polyhedra.rst +++ b/doc/polyhedra.rst @@ -4,12 +4,12 @@ Polyhedra Module Polyhedron class allows users to build and inspect polyherons. .. py:class:: Polyhedron - - .. py:property:: equalities(self) + + .. attribute:: equalities(self) Return a list of the equalities in a polyhedron. - .. py:method:: inequalities(self) + .. attribute:: inequalities(self) Return a list of the inequalities in a polyhedron. -- 2.20.1 From 148dae3a90146e4b1c5a32d1803a0a2ff66f9deb Mon Sep 17 00:00:00 2001 From: Dani Date: Mon, 11 Aug 2014 20:10:14 +0200 Subject: [PATCH 05/16] Update Docs --- doc/domain.rst | 25 ++++++++++--------------- doc/examples.rst | 6 ++++-- doc/geometry.rst | 18 +++++++++--------- doc/linexpr.rst | 12 ++++++------ doc/modules.rst | 4 +++- doc/polyhedra.rst | 28 ++++++++++++++-------------- 6 files changed, 46 insertions(+), 47 deletions(-) diff --git a/doc/domain.rst b/doc/domain.rst index edf8934..b85b2d0 100644 --- a/doc/domain.rst +++ b/doc/domain.rst @@ -3,22 +3,14 @@ Domains Module .. py:class :: Domain - .. py:method:: symbols + .. attribute:: symbols Returns a tuple of the symbols that exsist in a domain. - .. py:method:: dimension + .. attribute:: dimension Returns the number of variables that exist in a domain. - .. py:method:: disjoint - - Returns a domain as disjoint. - - .. py:method:: involves_vars(self, dims) - - Returns ``True`` if a domain depends on the given dimensions. - .. py:method:: isempty(self) Return ``True`` is a domain is empty. @@ -69,7 +61,7 @@ Domains Module Test whether every element in *other* is in a domain. .. py:method:: complement(self) - ¬self + ~self Return the complement of a domain. @@ -77,9 +69,9 @@ Domains Module Return a new domain without any redundant constraints. - .. py:method:: project(self, dims) + .. py:method:: project(self, variables) - Return a new domain with the given dimensions removed. + Return a new domain with the given variables removed. .. py:method:: aspolyhedron(self) @@ -90,16 +82,19 @@ Domains Module Return a single sample subset of a domain. .. py:method:: intersection(self, other) + __or__ self | other Return a new domain with the elements that are common between *self* and *other*. .. py:method:: union(self, other) + __and__ self & other Return a new domain with all the elements from *self* and *other*. .. py:method:: difference(self, other) + __sub__ self - other Return a new domain with the elements in a domain that are not in *other* . @@ -118,7 +113,7 @@ Domains Module Return a new set containing the lexicographic maximum of the elements in the set. -A 2D or 3D domain can be plotted using the :meth:`plot` function. The points, verticies, and faces of a domain can be inspected using the following functions. +A 2D or 3D domain can be plotted using the :meth:`plot` method. The points, vertices, and faces of a domain can be inspected using the following functions. .. py:method:: points(self) @@ -134,4 +129,4 @@ A 2D or 3D domain can be plotted using the :meth:`plot` function. The points, ve .. py:method:: plot(self, plot=None, **kwargs) - Return a plot of the given domain. + Return a plot of the given domain or add a plot to a plot instance. diff --git a/doc/examples.rst b/doc/examples.rst index ea044b8..ee254bc 100644 --- a/doc/examples.rst +++ b/doc/examples.rst @@ -21,7 +21,8 @@ Basic Examples False >>> # compute the union of two polyhedrons >>> square1 | square2 - Or(And(Ge(x, 0), Ge(-x + 2, 0), Ge(y, 0), Ge(-y + 2, 0)), And(Ge(x - 1, 0), Ge(-x + 3, 0), Ge(y - 1, 0), Ge(-y + 3, 0))) + Or(And(Ge(x, 0), Ge(-x + 2, 0), Ge(y, 0), Ge(-y + 2, 0)), \ + And(Ge(x - 1, 0), Ge(-x + 3, 0), Ge(y - 1, 0), Ge(-y + 3, 0))) >>> # check if square1 and square2 are disjoint >>> square1.disjoint(square2) False @@ -30,7 +31,8 @@ Basic Examples And(Ge(x - 1, 0), Ge(-x + 2, 0), Ge(y - 1, 0), Ge(-y + 2, 0)) >>> # compute the convex union of two polyhedrons >>> Polyhedron(square1 | sqaure2) - And(Ge(x, 0), Ge(y, 0), Ge(-y + 3, 0), Ge(-x + 3, 0), Ge(x - y + 2, 0), Ge(-x + y + 2, 0)) + And(Ge(x, 0), Ge(y, 0), Ge(-y + 3, 0), Ge(-x + 3, 0), \ + Ge(x - y + 2, 0), Ge(-x + y + 2, 0)) Unary operation and properties examples: diff --git a/doc/geometry.rst b/doc/geometry.rst index 0058ee9..838ec6e 100644 --- a/doc/geometry.rst +++ b/doc/geometry.rst @@ -13,19 +13,19 @@ This class represents points in space. .. py:method:: __eq__(self, other) - Compares two Points for equality. + Compare two Points for equality. .. py:method:: __add__(self, other) - Adds a Point to a Vector and returns the result as a Point. + Add a Point to a Vector and return the result as a Point. .. py:method:: __sub__(self, other) - Returns the difference between two Points as a Vector. + Return the difference between two Points as a Vector. .. py:method:: aspolyhedon(self) - Returns a Point as a polyhedron. + Return a Point as a polyhedron corresponding to the Point, assuming the Point has integer coordinates. .. py:class:: Vector @@ -34,11 +34,11 @@ This class represents displacements in space. .. py:method:: __eq__(self, other) - Compares two Vectors for equality. + Compare two Vectors for equality. .. py:method:: __add__(self, other) - Adds either a Point or Vector to a Vector. The resulting sum is returned as the same structure *other* is. + Add either a Point or Vector to a Vector. The resulting sum is returned as the same structure *other* is. .. py:method:: __sub__(self, other) @@ -46,11 +46,11 @@ This class represents displacements in space. .. py:method:: __mul__(self, other) - Multiples a Vector by a scalar value and returns the result as a Vector. + Multiply a Vector by a scalar value and returns the result as a Vector. .. py:method:: __neg__(self) - Negates a Vector. + Negate a Vector. .. py:method:: norm(self) @@ -67,7 +67,7 @@ This class represents displacements in space. .. py:method:: cross(self, other) - Calculate the cross product of two Vector3D structures. + Calculate the cross product of two Vector3D structures. If the vectors are not tridimensional, a _____ error is raised. .. py:method:: dot(self, other) diff --git a/doc/linexpr.rst b/doc/linexpr.rst index 2aab652..b5d8069 100644 --- a/doc/linexpr.rst +++ b/doc/linexpr.rst @@ -1,7 +1,7 @@ Linear Expression Module ======================== -This class implements linear expressions. +This class implements linear expressions. A linear expression is…. .. py:class:: Expression @@ -14,21 +14,21 @@ This class implements linear expressions. Return a list of the coefficients of an expression - .. py:method:: constant(self) + .. attribute:: constant Return the constant value of an expression. - .. py:method:: symbols(self) + .. attribute:: symbols Return a list of symbols in an expression. - .. py:method:: dimension(self) + .. attribute:: dimension - Return the number of vriables in an expression. + Return the number of variables in an expression. .. py:method:: __sub__(self, other) - Return the difference between two expressions. + Return the difference between *self* and *other*. .. py:method:: subs(self, symbol, expression=None) diff --git a/doc/modules.rst b/doc/modules.rst index fd8158f..bf383c7 100644 --- a/doc/modules.rst +++ b/doc/modules.rst @@ -3,7 +3,9 @@ LinPy Module Reference ====================== -There are four main LinPy modules: +There are four main LinPy modules, all of them can be inherited at once with the LinPy package: + + >>> from linpy import * .. toctree:: :maxdepth: 2 diff --git a/doc/polyhedra.rst b/doc/polyhedra.rst index 1cb9396..1f2756b 100644 --- a/doc/polyhedra.rst +++ b/doc/polyhedra.rst @@ -5,17 +5,17 @@ Polyhedron class allows users to build and inspect polyherons. .. py:class:: Polyhedron - .. attribute:: equalities(self) + .. py:property:: equalities - Return a list of the equalities in a polyhedron. + Returns a list of the equalities in a polyhedron. - .. attribute:: inequalities(self) + .. py:property:: inequalities - Return a list of the inequalities in a polyhedron. + Returns a list of the inequalities in a polyhedron. - .. py:method:: constraints(self) + .. py:property:: constraints - Return ta list of the constraints of a polyhedron. + Returns a list of the constraints of a polyhedron. .. py:method:: disjoint(self) @@ -27,30 +27,30 @@ Polyhedron class allows users to build and inspect polyherons. .. py:method:: subs(self, symbol, expression=None) - Subsitutes an expression into a polyhedron and returns the result. + Substitutes an expression into a polyhedron and returns the result. -To create a polyhedron, the user can use the following functions to define equalities and inequalities as the contraints. +To create a polyhedron, the user can use the following functions to define equalities and inequalities as the constraints. .. py:function:: Eq(left, right) - Create a constraint by setting *left* equal to *right*. + Returns a Polyhedron instance with a single constraint as *left* equal to *right*. .. py:function:: Ne(left, right) - Create a constraint by setting *left* not equal to *right*. + Returns a Polyhedron instance with a single constraint as *left* not equal to *right*. .. py:function:: Lt(left, right) - Create a constraint by setting *left* less than *right*. + Returns a Polyhedron instance with a single constraint as *left* less than *right*. .. py:function:: Le(left, right) - Create a constraint by setting *left* less than or equal to *right*. + Returns a Polyhedron instance with a single constraint as *left* less than or equal to *right*. .. py:function:: Gt(left, right) - Create a constraint by setting *left* greater than *right*. + Returns a Polyhedron instance with a single constraint as *left* greater than *right*. .. py:function:: Ge(left, right) - Create a constraint by setting *left* greater than or equal to *right*. + Returns a Polyhedron instance with a single constraint as *left* greater than or equal to *right*. -- 2.20.1 From 50f0fde4dca3f10e535314b1c9d325cd9283735b Mon Sep 17 00:00:00 2001 From: Vivien Maisonneuve Date: Mon, 11 Aug 2014 21:37:03 +0200 Subject: [PATCH 06/16] Docstrings and simplifications of domains.py --- linpy/domains.py | 181 ++++++++++++++++++++---------------- linpy/tests/test_domains.py | 9 -- 2 files changed, 100 insertions(+), 90 deletions(-) diff --git a/linpy/domains.py b/linpy/domains.py index 21e78db..70e5e4f 100644 --- a/linpy/domains.py +++ b/linpy/domains.py @@ -15,6 +15,13 @@ # You should have received a copy of the GNU General Public License # along with LinPy. If not, see . +""" +Polyhedral domains + +This module provides classes and functions to deal with polyhedral +domains, i.e. unions of polyhedra. +""" + import ast import functools import re @@ -36,6 +43,9 @@ __all__ = [ @functools.total_ordering class Domain(GeometricObject): + """ + This class represents polyhedral domains, i.e. unions of polyhedra. + """ __slots__ = ( '_polyhedra', @@ -44,6 +54,9 @@ class Domain(GeometricObject): ) def __new__(cls, *polyhedra): + """ + Create and return a new domain from a string or a list of polyhedra. + """ from .polyhedra import Polyhedron if len(polyhedra) == 1: argument = polyhedra[0] @@ -74,19 +87,28 @@ class Domain(GeometricObject): @property def polyhedra(self): + """ + The tuple of polyhedra which constitute the domain. + """ return self._polyhedra @property def symbols(self): + """ + The tuple of symbols present in the domain equations. + """ return self._symbols @property def dimension(self): + """ + The dimension of the domain, i.e. the number of symbols. + """ return self._dimension - def disjoint(self): + def make_disjoint(self): """ - Returns this set as disjoint. + Return an equivalent domain, whose polyhedra are disjoint. """ islset = self._toislset(self.polyhedra, self.symbols) islset = libisl.isl_set_make_disjoint(mainctx, islset) @@ -94,7 +116,7 @@ class Domain(GeometricObject): def isempty(self): """ - Returns true if this set is an Empty set. + Return True if the domain is empty. """ islset = self._toislset(self.polyhedra, self.symbols) empty = bool(libisl.isl_set_is_empty(islset)) @@ -102,11 +124,14 @@ class Domain(GeometricObject): return empty def __bool__(self): + """ + Return True if the domain is non-empty. + """ return not self.isempty() def isuniverse(self): """ - Returns true if this set is the Universe set. + Return True if the domain is universal, i.e. with no constraint. """ islset = self._toislset(self.polyhedra, self.symbols) universe = bool(libisl.isl_set_plain_is_universe(islset)) @@ -115,7 +140,7 @@ class Domain(GeometricObject): def isbounded(self): """ - Returns true if this set is bounded. + Return True if the domain is bounded. """ islset = self._toislset(self.polyhedra, self.symbols) bounded = bool(libisl.isl_set_is_bounded(islset)) @@ -124,7 +149,7 @@ class Domain(GeometricObject): def __eq__(self, other): """ - Returns true if two sets are equal. + Return True if the two domains are equal. """ symbols = self._xsymbols([self, other]) islset1 = self._toislset(self.polyhedra, symbols) @@ -136,7 +161,7 @@ class Domain(GeometricObject): def isdisjoint(self, other): """ - Return True if two sets have a null intersection. + Return True if two domains have a null intersection. """ symbols = self._xsymbols([self, other]) islset1 = self._toislset(self.polyhedra, symbols) @@ -148,7 +173,7 @@ class Domain(GeometricObject): def issubset(self, other): """ - Report whether another set contains this set. + Report whether another domain contains the domain. """ symbols = self._xsymbols([self, other]) islset1 = self._toislset(self.polyhedra, symbols) @@ -159,14 +184,12 @@ class Domain(GeometricObject): return equal def __le__(self, other): - """ - Returns true if this set is less than or equal to another set. - """ return self.issubset(other) + __le__.__doc__ = issubset.__doc__ def __lt__(self, other): """ - Returns true if this set is less than another set. + Report whether another domain is contained within the domain. """ symbols = self._xsymbols([self, other]) islset1 = self._toislset(self.polyhedra, symbols) @@ -178,21 +201,37 @@ class Domain(GeometricObject): def complement(self): """ - Returns the complement of this set. + Return the complementary domain of the domain. """ islset = self._toislset(self.polyhedra, self.symbols) islset = libisl.isl_set_complement(islset) return self._fromislset(islset, self.symbols) def __invert__(self): + return self.complement() + __invert__.__doc__ = complement.__doc__ + + def coalesce(self): """ - Returns the complement of this set. + Simplify the representation of the domain by trying to combine pairs of + polyhedra into a single polyhedron. """ - return self.complement() + islset = self._toislset(self.polyhedra, self.symbols) + islset = libisl.isl_set_coalesce(islset) + return self._fromislset(islset, self.symbols) - def simplify(self): + def detect_equalities(self): """ - Returns a set without redundant constraints. + Simplify the representation of the domain by detecting implicit + equalities. + """ + islset = self._toislset(self.polyhedra, self.symbols) + islset = libisl.isl_set_detect_equalities(islset) + return self._fromislset(islset, self.symbols) + + def remove_redundancies(self): + """ + Remove redundant constraints in the domain. """ islset = self._toislset(self.polyhedra, self.symbols) islset = libisl.isl_set_remove_redundancies(islset) @@ -200,7 +239,7 @@ class Domain(GeometricObject): def aspolyhedron(self): """ - Returns polyhedral hull of set. + Return the polyhedral hull of the domain. """ from .polyhedra import Polyhedron islset = self._toislset(self.polyhedra, self.symbols) @@ -210,26 +249,27 @@ class Domain(GeometricObject): def asdomain(self): return self - def project(self, dims): + def project(self, symbols): """ - Return new set with given dimensions removed. + Project out the symbols given in arguments. """ islset = self._toislset(self.polyhedra, self.symbols) n = 0 for index, symbol in reversed(list(enumerate(self.symbols))): - if symbol in dims: + if symbol in symbols: n += 1 elif n > 0: - islset = libisl.isl_set_project_out(islset, libisl.isl_dim_set, index + 1, n) + islset = libisl.isl_set_project_out(islset, + libisl.isl_dim_set, index + 1, n) n = 0 if n > 0: islset = libisl.isl_set_project_out(islset, libisl.isl_dim_set, 0, n) - dims = [symbol for symbol in self.symbols if symbol not in dims] - return Domain._fromislset(islset, dims) + symbols = [symbol for symbol in self.symbols if symbol not in symbols] + return Domain._fromislset(islset, symbols) def sample(self): """ - Returns a single subset of the input. + Return a sample of the domain. """ islset = self._toislset(self.polyhedra, self.symbols) islpoint = libisl.isl_set_sample_point(islset) @@ -247,7 +287,7 @@ class Domain(GeometricObject): def intersection(self, *others): """ - Return the intersection of two sets as a new set. + Return the intersection of two domains as a new domain. """ if len(others) == 0: return self @@ -259,14 +299,12 @@ class Domain(GeometricObject): return self._fromislset(islset1, symbols) def __and__(self, other): - """ - Return the intersection of two sets as a new set. - """ return self.intersection(other) + __and__.__doc__ = intersection.__doc__ def union(self, *others): """ - Return the union of sets as a new set. + Return the union of two domains as a new domain. """ if len(others) == 0: return self @@ -278,20 +316,16 @@ class Domain(GeometricObject): return self._fromislset(islset1, symbols) def __or__(self, other): - """ - Return a new set with elements from both sets. - """ return self.union(other) + __or__.__doc__ = union.__doc__ def __add__(self, other): - """ - Return new set containing all elements in both sets. - """ return self.union(other) + __add__.__doc__ = union.__doc__ def difference(self, other): """ - Return the difference of two sets as a new set. + Return the difference of two domains as a new domain. """ symbols = self._xsymbols([self, other]) islset1 = self._toislset(self.polyhedra, symbols) @@ -300,14 +334,12 @@ class Domain(GeometricObject): return self._fromislset(islset, symbols) def __sub__(self, other): - """ - Return the difference of two sets as a new set. - """ return self.difference(other) + __sub__.__doc__ = difference.__doc__ def lexmin(self): """ - Return a new set containing the lexicographic minimum of the elements in the set. + Return the lexicographic minimum of the elements in the domain. """ islset = self._toislset(self.polyhedra, self.symbols) islset = libisl.isl_set_lexmin(islset) @@ -315,44 +347,23 @@ class Domain(GeometricObject): def lexmax(self): """ - Return a new set containing the lexicographic maximum of the elements in the set. + Return the lexicographic maximum of the elements in the domain. """ islset = self._toislset(self.polyhedra, self.symbols) islset = libisl.isl_set_lexmax(islset) return self._fromislset(islset, self.symbols) - - def involves_vars(self, vars): - """ - Returns true if a set depends on given dimensions. - """ - islset = self._toislset(self.polyhedra, self.symbols) - dims = sorted(vars) - symbols = sorted(list(self.symbols)) - n = 0 - if len(dims)>0: - for dim in dims: - if dim in symbols: - first = symbols.index(dims[0]) - n +=1 - else: - first = 0 - else: - return False - value = bool(libisl.isl_set_involves_dims(islset, libisl.isl_dim_set, first, n)) - libisl.isl_set_free(islset) - return value - _RE_COORDINATE = re.compile(r'\((?P\-?\d+)\)(/(?P\d+))?') def vertices(self): """ - Return a list of vertices for this Polygon. + Return the vertices of the domain. """ from .polyhedra import Polyhedron if not self.isbounded(): raise ValueError('domain must be bounded') - islbset = self._toislbasicset(self.equalities, self.inequalities, self.symbols) + islbset = self._toislbasicset(self.equalities, self.inequalities, + self.symbols) vertices = libisl.isl_basic_set_compute_vertices(islbset); vertices = islhelper.isl_vertices_vertices(vertices) points = [] @@ -385,7 +396,7 @@ class Domain(GeometricObject): def points(self): """ - Returns the points contained in the set. + Return the points with integer coordinates contained in the domain. """ if not self.isbounded(): raise ValueError('domain must be bounded') @@ -456,7 +467,7 @@ class Domain(GeometricObject): def faces(self): """ - Returns the vertices of the faces of a polyhedra. + Return the vertices of the domain, grouped by face. """ faces = [] for polyhedron in self.polyhedra: @@ -518,10 +529,9 @@ class Domain(GeometricObject): axes.set_zlim(zmin, zmax) return axes - def plot(self, plot=None, **kwargs): """ - Display plot of this set. + Plot the domain using matplotlib. """ if not self.isbounded(): raise ValueError('domain must be bounded') @@ -533,6 +543,9 @@ class Domain(GeometricObject): raise ValueError('polyhedron must be 2 or 3-dimensional') def __contains__(self, point): + """ + Return True if point if contained within the domain. + """ for polyhedron in self.polyhedra: if point in polyhedron: return True @@ -540,8 +553,8 @@ class Domain(GeometricObject): def subs(self, symbol, expression=None): """ - Subsitute the given value into an expression and return the resulting - expression. + Subsitute symbol by expression in equations and return the resulting + domain. """ polyhedra = [polyhedron.subs(symbol, expression) for polyhedron in self.polyhedra] @@ -634,6 +647,9 @@ class Domain(GeometricObject): @classmethod def fromstring(cls, string): + """ + Convert a string into a domain. + """ # remove curly brackets string = cls._RE_BRACES.sub(r'', string) # replace '=' by '==' @@ -655,6 +671,9 @@ class Domain(GeometricObject): return cls._fromast(tree) def __repr__(self): + """ + Return repr(self). + """ assert len(self.polyhedra) >= 2 strings = [repr(polyhedron) for polyhedron in self.polyhedra] return 'Or({})'.format(', '.join(strings)) @@ -667,6 +686,9 @@ class Domain(GeometricObject): @classmethod def fromsympy(cls, expr): + """ + Convert a SymPy expression into a domain. + """ import sympy from .polyhedra import Lt, Le, Eq, Ne, Ge, Gt funcmap = { @@ -683,33 +705,30 @@ class Domain(GeometricObject): raise ValueError('non-domain expression: {!r}'.format(expr)) def tosympy(self): + """ + Convert a domain into a SymPy expression. + """ import sympy polyhedra = [polyhedron.tosympy() for polyhedron in polyhedra] return sympy.Or(*polyhedra) def And(*domains): - """ - Return the intersection of two sets as a new set. - """ if len(domains) == 0: from .polyhedra import Universe return Universe else: return domains[0].intersection(*domains[1:]) +And.__doc__ = Domain.intersection.__doc__ def Or(*domains): - """ - Return the union of sets as a new set. - """ if len(domains) == 0: from .polyhedra import Empty return Empty else: return domains[0].union(*domains[1:]) +Or.__doc__ = Domain.union.__doc__ def Not(domain): - """ - Returns the complement of this set. - """ return ~domain +Not.__doc__ = Domain.complement.__doc__ diff --git a/linpy/tests/test_domains.py b/linpy/tests/test_domains.py index 4b8c5ea..9e9f6fa 100644 --- a/linpy/tests/test_domains.py +++ b/linpy/tests/test_domains.py @@ -115,10 +115,6 @@ class TestDomain(unittest.TestCase): self.assertEqual(self.universe.project([]), self.universe) self.assertEqual(self.empty.project([]), Empty) - def test_simplify(self): - self.assertEqual(self.universe.simplify(), self.universe) - self.assertEqual(self.empty.simplify(), Empty) - def test_sample(self): self.assertEqual(self.square6.sample(), {Symbol('x'): 1, Symbol('y'): 3}) with self.assertRaises(ValueError): @@ -168,8 +164,3 @@ class TestDomain(unittest.TestCase): self.assertEqual(self.square1.lexmax(), self.lexmax) self.assertEqual(self.universe.lexmax(), self.universe) self.assertEqual(self.empty.lexmax(), Empty) - - def test_involves_vars(self): - self.assertTrue(self.square1.involves_vars(symbols('x y'))) - self.assertFalse(self.empty.involves_vars(symbols('x'))) - self.assertFalse(self.universe.involves_vars(symbols('x'))) -- 2.20.1 From 88b274d96fde76a3fb7f41c7c2359c8dc8987cb8 Mon Sep 17 00:00:00 2001 From: Vivien Maisonneuve Date: Mon, 11 Aug 2014 22:29:20 +0200 Subject: [PATCH 07/16] Add url to setup.py --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 05ea91c..4f3fd3b 100755 --- a/setup.py +++ b/setup.py @@ -23,6 +23,7 @@ setup( name='LinPy', description='A polyhedral library based on ISL', author='MINES ParisTech', + url='https://scm.cri.ensmp.fr/git/linpy.git', packages=['linpy'], ext_modules = [ Extension('linpy._islhelper', -- 2.20.1 From 8f61fe65a53ff47b23c8527c47e2bed32c1d2e6f Mon Sep 17 00:00:00 2001 From: Vivien Maisonneuve Date: Tue, 12 Aug 2014 00:21:16 +0200 Subject: [PATCH 08/16] Fix typo in __init__.py docstring --- linpy/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/linpy/__init__.py b/linpy/__init__.py index 7d67f77..0ae2e66 100644 --- a/linpy/__init__.py +++ b/linpy/__init__.py @@ -16,7 +16,7 @@ # along with LinPy. If not, see . """ -A polyhedral library based on ISL. +A polyhedral library based on ISL """ from .geometry import Point, Vector -- 2.20.1 From 7fff25bf40e4db570565586fb49165d1675002c2 Mon Sep 17 00:00:00 2001 From: Danielle Bolan Date: Tue, 12 Aug 2014 15:10:00 +0200 Subject: [PATCH 09/16] Doc updates (not complete) --- doc/domain.rst | 62 +++++++++++++++++++++++++---- doc/linexpr.rst | 98 ++++++++++++++++++++++++++++++++++++++++++++-- doc/modules.rst | 2 +- doc/polyhedra.rst | 32 +++++++++++---- linpy/linexprs.py | 85 ++++++++++++++++++++++++++++++++++++++-- linpy/polyhedra.py | 44 +++++++++++++-------- 6 files changed, 285 insertions(+), 38 deletions(-) diff --git a/doc/domain.rst b/doc/domain.rst index b85b2d0..06eec6e 100644 --- a/doc/domain.rst +++ b/doc/domain.rst @@ -1,8 +1,21 @@ Domains Module ============== +This module provides classes and functions to deal with polyhedral +domains, i.e. unions of polyhedra. + .. py:class :: Domain + This class represents polyhedral domains, i.e. unions of polyhedra. + + .. py:method:: __new__(cls, *polyhedra) + + Create and return a new domain from a string or a list of polyhedra. + + .. attribute:: polyhedra + + The tuple of polyhedra which constitute the domain. + .. attribute:: symbols Returns a tuple of the symbols that exsist in a domain. @@ -14,6 +27,10 @@ Domains Module .. py:method:: isempty(self) Return ``True`` is a domain is empty. + + .. py:method:: __bool__(self) + + Return ``True`` if the domain is non-empty. .. py:method:: isuniverse(self) @@ -23,9 +40,9 @@ Domains Module Return ``True`` if a domain is bounded. - .. py:method:: disjoint(self) + .. py:method:: make_disjoint(self) - It is not guarenteed that a domain is disjoint. If it is necessary, this method will return a domain as disjoint. + It is not guarenteed that a domain is disjoint. If it is necessary, this method will return an equivalent domain, whose polyhedra are disjoint. .. py:method:: isdisjoint(self, other) @@ -63,7 +80,18 @@ Domains Module .. py:method:: complement(self) ~self - Return the complement of a domain. + Return the complementary domain of a domain. + + .. py:method:: coalesce(self) + + Simplify the representation of the domain by trying to combine pairs of + polyhedra into a single polyhedron. + + + .. py:method:: detect_equalities(self) + + Simplify the representation of the domain by detecting implicit + equalities. .. py:method:: simplify(self) @@ -106,18 +134,38 @@ Domains Module .. py:method:: lexmin(self) - Return a new set containing the lexicographic minimum of the elements in the set. + Return a new domain containing the lexicographic minimum of the elements in the domain. .. py:method:: lexmax(self) - Return a new set containing the lexicographic maximum of the elements in the set. + Return a new domain containing the lexicographic maximum of the elements in the domain. + + .. py:method:: subs(self, symbol, expression=None): + + Subsitute symbol by expression in equations and return the resulting + domain. + .. py:method:: fromstring(cls, string) + + Convert a string into a domain. + + .. py:method:: fromsympy(cls, expr) + + Convert a SymPy expression into a domain. + + .. py:method:: tosympy(self) + + Convert a domain into a SymPy expression. A 2D or 3D domain can be plotted using the :meth:`plot` method. The points, vertices, and faces of a domain can be inspected using the following functions. .. py:method:: points(self) - Return a list of the points contained in a domain as :class:`Points` objects. + Return a list of the points with integer coordinates contained in a domain as :class:`Points` objects. + + .. py:method:: __contains__(self, point) + + Return ``True`` if point if contained within the domain. .. py:method:: vertices(self) @@ -129,4 +177,4 @@ A 2D or 3D domain can be plotted using the :meth:`plot` method. The points, vert .. py:method:: plot(self, plot=None, **kwargs) - Return a plot of the given domain or add a plot to a plot instance. + Return a plot of the given domain or add a plot to a plot instance, using matplotlib. diff --git a/doc/linexpr.rst b/doc/linexpr.rst index b5d8069..eb2f704 100644 --- a/doc/linexpr.rst +++ b/doc/linexpr.rst @@ -4,8 +4,11 @@ Linear Expression Module This class implements linear expressions. A linear expression is…. .. py:class:: Expression - - + + .. py:method:: __new__(cls, coefficients=None, constant=0) + + Create and return a new linear expression from a string or a list of coefficients and a constant. + .. py:method:: coefficient(self, symbol) Return the coefficient value of the given symbol. @@ -26,13 +29,67 @@ This class implements linear expressions. A linear expression is…. Return the number of variables in an expression. + .. py:method:: isconstant(self) + + Return ``True`` if an expression is a constant. + + .. py:method:: issymbol(self) + + Return ``True`` if an expression is a symbol. + + + .. py:method:: values(self) + + Return the coefficient and constant values of an expression. + + .. py:method:: __add__(self, other) + + Return the sum of *self* and *other*. + .. py:method:: __sub__(self, other) Return the difference between *self* and *other*. - + + .. py:method:: __mul__(self, other) + + Return the product of *self* and *other* if *other* is a rational number. + + .. py:method:: __eq__(self, other) + + Test whether two expressions are equal. + + .. py:method:: __le__(self, other) + self <= other + + Create a new polyhedra from an expression with a single constraint as *self* less than or equal to *other*. + + .. py:method:: __lt__(self, other) + self < other + + Create a new polyhedra from an expression with a single constraint as *self* less than *other*. + + .. py:method:: __ge__(self, other) + self >= other + + Create a new polyhedra from an expression with a single constraint as *self* greater than or equal to *other*. + + .. py:method:: __gt__(self, other) + self > other + + Create a new polyhedra from an expression with a single constraint as *self* greater than *other*. + + .. py:method:: scaleint(self) + + Multiply an expression by a scalar to make all coefficients integer values. + .. py:method:: subs(self, symbol, expression=None) Subsitute the given value into an expression and return the resulting expression. + + + .. py:method:: fromstring(self) + + Create an expression from a string. .. py:method:: fromsympy(self) @@ -43,8 +100,41 @@ This class implements linear expressions. A linear expression is…. Return an expression as a sympy object. .. py:class:: Symbol(Expression) + + .. py:method:: __new__(cls, name) + + Create and return a symbol from a string. + + .. py:method:: symbols(names) + + This function returns a sequence of symbols with names taken from names argument, which can be a comma or whitespace delimited string, or a sequence of strings. + .. py:method:: asdummy(self) + + Return a symbol as a :class:`Dummy` Symbol. .. py:class:: Dummy(Symbol) -This class returns a dummy symbol to ensure that each no variables are repeated in an expression + This class returns a dummy symbol to ensure that no variables are repeated in an expression. This is useful when the user needs to have a unique symbol, for example as a temporary one in some calculation, which is going to be substituted for something else at the end anyway. + + .. py:method:: __new__(cls, name=None) + + Create and return a new dummy symbol. + + +.. py:class:: Rational(Expression, Fraction) + + This class represents integers and rational numbers of any size. + + .. attribute:: constant + + Return rational as a constant. + + .. py:method:: isconstant(self) + + Test whether a value is a constant. + + .. py:method:: fromsympy(cls, expr) + + Create a rational object from a sympy expression + diff --git a/doc/modules.rst b/doc/modules.rst index bf383c7..4859f2d 100644 --- a/doc/modules.rst +++ b/doc/modules.rst @@ -11,7 +11,7 @@ There are four main LinPy modules, all of them can be inherited at once with the :maxdepth: 2 linexpr.rst - polyhedra.rst domain.rst + polyhedra.rst geometry.rst diff --git a/doc/polyhedra.rst b/doc/polyhedra.rst index 1f2756b..f6f1a30 100644 --- a/doc/polyhedra.rst +++ b/doc/polyhedra.rst @@ -1,33 +1,51 @@ Polyhedra Module ================ -Polyhedron class allows users to build and inspect polyherons. +Polyhedron class allows users to build and inspect polyherons. Polyhedron inherits all methods from the :class:`Domain` class. -.. py:class:: Polyhedron +.. py:class:: Polyhedron(Domain) - .. py:property:: equalities + .. py:method:: __new__(cls, equalities=None, inequalities=None) + + Create and return a new Polyhedron from a string or list of equalities and inequalities. + + .. attribute:: equalities Returns a list of the equalities in a polyhedron. - .. py:property:: inequalities + .. attribute:: inequalities Returns a list of the inequalities in a polyhedron. - .. py:property:: constraints + .. attribute:: constraints Returns a list of the constraints of a polyhedron. - .. py:method:: disjoint(self) + .. py:method:: make_disjoint(self) Returns a polyhedron as a disjoint. .. py:method:: isuniverse(self) Return ``True`` if a polyhedron is the Universe set. + + .. py:method:: aspolyhedron(self) + + Return the polyhedral hull of a polyhedron. + + .. py:method:: __contains__(self, point) + + Report whether a polyhedron constains an integer point .. py:method:: subs(self, symbol, expression=None) - Substitutes an expression into a polyhedron and returns the result. + Subsitute the given value into an expression and return the resulting + expression. + + .. py:method:: fromstring(cls, string) + + Create and return a Polyhedron from a string. + To create a polyhedron, the user can use the following functions to define equalities and inequalities as the constraints. diff --git a/linpy/linexprs.py b/linpy/linexprs.py index aedf170..bc36fda 100644 --- a/linpy/linexprs.py +++ b/linpy/linexprs.py @@ -49,6 +49,9 @@ class Expression: """ def __new__(cls, coefficients=None, constant=0): + """ + Create a new expression. + """ if isinstance(coefficients, str): if constant != 0: raise TypeError('too many arguments') @@ -82,6 +85,9 @@ class Expression: return self def coefficient(self, symbol): + """ + Return the coefficient value of the given symbol. + """ if not isinstance(symbol, Symbol): raise TypeError('symbol must be a Symbol instance') return Rational(self._coefficients.get(symbol, 0)) @@ -89,31 +95,52 @@ class Expression: __getitem__ = coefficient def coefficients(self): + """ + Return a list of the coefficients of an expression + """ for symbol, coefficient in self._coefficients.items(): yield symbol, Rational(coefficient) @property def constant(self): + """ + Return the constant value of an expression. + """ return Rational(self._constant) @property def symbols(self): + """ + Return a list of symbols in an expression. + """ return self._symbols @property def dimension(self): + """ + Create and return a new linear expression from a string or a list of coefficients and a constant. + """ return self._dimension def __hash__(self): return hash((tuple(self._coefficients.items()), self._constant)) def isconstant(self): + """ + Return true if an expression is a constant. + """ return False def issymbol(self): + """ + Return true if an expression is a symbol. + """ return False def values(self): + """ + Return the coefficient and constant values of an expression. + """ for coefficient in self._coefficients.values(): yield Rational(coefficient) yield Rational(self._constant) @@ -129,6 +156,9 @@ class Expression: @_polymorphic def __add__(self, other): + """ + Return the sum of two expressions. + """ coefficients = defaultdict(Fraction, self._coefficients) for symbol, coefficient in other._coefficients.items(): coefficients[symbol] += coefficient @@ -139,6 +169,9 @@ class Expression: @_polymorphic def __sub__(self, other): + """ + Return the difference between two expressions. + """ coefficients = defaultdict(Fraction, self._coefficients) for symbol, coefficient in other._coefficients.items(): coefficients[symbol] -= coefficient @@ -150,6 +183,9 @@ class Expression: return other - self def __mul__(self, other): + """ + Return the product of two expressions if other is a rational number. + """ if isinstance(other, numbers.Rational): coefficients = ((symbol, coefficient * other) for symbol, coefficient in self._coefficients.items()) @@ -169,8 +205,9 @@ class Expression: @_polymorphic def __eq__(self, other): - # returns a boolean, not a constraint - # see http://docs.sympy.org/dev/tutorial/gotchas.html#equals-signs + """ + Test whether two expressions are equal + """ return isinstance(other, Expression) and \ self._coefficients == other._coefficients and \ self._constant == other._constant @@ -192,11 +229,18 @@ class Expression: return Gt(self, other) def scaleint(self): + """ + Multiply an expression by a scalar to make all coefficients integer values. + """ lcm = functools.reduce(lambda a, b: a*b // gcd(a, b), [value.denominator for value in self.values()]) return self * lcm def subs(self, symbol, expression=None): + """ + Subsitute symbol by expression in equations and return the resulting + expression. + """ if expression is None: if isinstance(symbol, Mapping): symbol = symbol.items() @@ -244,6 +288,9 @@ class Expression: @classmethod def fromstring(cls, string): + """ + Create an expression from a string. + """ # add implicit multiplication operators, e.g. '5x' -> '5*x' string = Expression._RE_NUM_VAR.sub(r'\1*\2', string) tree = ast.parse(string, 'eval') @@ -306,6 +353,9 @@ class Expression: @classmethod def fromsympy(cls, expr): + """ + Convert sympy object to an expression. + """ import sympy coefficients = [] constant = 0 @@ -321,6 +371,9 @@ class Expression: return Expression(coefficients, constant) def tosympy(self): + """ + Return an expression as a sympy object. + """ import sympy expr = 0 for symbol, coefficient in self.coefficients(): @@ -333,6 +386,9 @@ class Expression: class Symbol(Expression): def __new__(cls, name): + """ + Create and return a symbol from a string. + """ if not isinstance(name, str): raise TypeError('name must be a string') self = object().__new__(cls) @@ -360,6 +416,9 @@ class Symbol(Expression): return self.sortkey() == other.sortkey() def asdummy(self): + """ + Return a symbol as a Dummy Symbol. + """ return Dummy(self.name) @classmethod @@ -390,10 +449,15 @@ class Symbol(Expression): class Dummy(Symbol): - + """ + This class returns a dummy symbol to ensure that no variables are repeated in an expression + """ _count = 0 def __new__(cls, name=None): + """ + Create and return a new dummy symbol. + """ if name is None: name = 'Dummy_{}'.format(Dummy._count) elif not isinstance(name, str): @@ -422,12 +486,18 @@ class Dummy(Symbol): def symbols(names): + """ + Transform strings into instances of the Symbol class + """ if isinstance(names, str): names = names.replace(',', ' ').split() return tuple(Symbol(name) for name in names) class Rational(Expression, Fraction): + """ + This class represents integers and rational numbers of any size. + """ def __new__(cls, numerator=0, denominator=None): self = object().__new__(cls) @@ -444,9 +514,15 @@ class Rational(Expression, Fraction): @property def constant(self): + """ + Return rational as a constant. + """ return self def isconstant(self): + """ + Test whether a value is a constant. + """ return True def __bool__(self): @@ -470,6 +546,9 @@ class Rational(Expression, Fraction): @classmethod def fromsympy(cls, expr): + """ + Create a rational object from a sympy expression + """ import sympy if isinstance(expr, sympy.Rational): return Rational(expr.p, expr.q) diff --git a/linpy/polyhedra.py b/linpy/polyhedra.py index e9226f2..8eddb2d 100644 --- a/linpy/polyhedra.py +++ b/linpy/polyhedra.py @@ -35,7 +35,9 @@ __all__ = [ class Polyhedron(Domain): - + """ + Polyhedron class allows users to build and inspect polyherons. Polyhedron inherits from Domain. + """ __slots__ = ( '_equalities', '_inequalities', @@ -45,6 +47,10 @@ class Polyhedron(Domain): ) def __new__(cls, equalities=None, inequalities=None): + """ + Create and return a new Polyhedron from a string or list of equalities and inequalities. + """ + if isinstance(equalities, str): if inequalities is not None: raise TypeError('too many arguments') @@ -74,21 +80,21 @@ class Polyhedron(Domain): @property def equalities(self): """ - Return a list of the equalities in a set. + Return a list of the equalities in a polyhedron. """ return self._equalities @property def inequalities(self): """ - Return a list of the inequalities in a set. + Return a list of the inequalities in a polyhedron. """ return self._inequalities @property def constraints(self): """ - Return ta list of the constraints of a set. + Return the list of the constraints of a polyhedron. """ return self._constraints @@ -96,15 +102,15 @@ class Polyhedron(Domain): def polyhedra(self): return self, - def disjoint(self): + def make_disjoint(self): """ - Return a set as disjoint. + Return a polyhedron as disjoint. """ return self def isuniverse(self): """ - Return true if a set is the Universe set. + Return true if a polyhedron is the Universe set. """ islbset = self._toislbasicset(self.equalities, self.inequalities, self.symbols) @@ -114,11 +120,14 @@ class Polyhedron(Domain): def aspolyhedron(self): """ - Return polyhedral hull of a set. + Return the polyhedral hull of a polyhedron. """ return self def __contains__(self, point): + """ + Report whether a polyhedron constains an integer point + """ if not isinstance(point, Point): raise TypeError('point must be a Point instance') if self.symbols != point.symbols: @@ -233,6 +242,9 @@ class Polyhedron(Domain): @classmethod def fromstring(cls, string): + """ + Create and return a Polyhedron from a string. + """ domain = Domain.fromstring(string) if not isinstance(domain, Polyhedron): raise ValueError('non-polyhedral expression: {!r}'.format(string)) @@ -261,7 +273,7 @@ class Polyhedron(Domain): @classmethod def fromsympy(cls, expr): """ - Convert a sympy object to an expression. + Convert a sympy object to a polyhedron. """ domain = Domain.fromsympy(expr) if not isinstance(domain, Polyhedron): @@ -270,7 +282,7 @@ class Polyhedron(Domain): def tosympy(self): """ - Return an expression as a sympy object. + Return a polyhedron as a sympy object. """ import sympy constraints = [] @@ -351,41 +363,41 @@ def _polymorphic(func): @_polymorphic def Lt(left, right): """ - Assert first set is less than the second set. + Returns a Polyhedron instance with a single constraint as left less than right. """ return Polyhedron([], [right - left - 1]) @_polymorphic def Le(left, right): """ - Assert first set is less than or equal to the second set. + Returns a Polyhedron instance with a single constraint as left less than or equal to right. """ return Polyhedron([], [right - left]) @_polymorphic def Eq(left, right): """ - Assert first set is equal to the second set. + Returns a Polyhedron instance with a single constraint as left equal to right. """ return Polyhedron([left - right], []) @_polymorphic def Ne(left, right): """ - Assert first set is not equal to the second set. + Returns a Polyhedron instance with a single constraint as left not equal to right. """ return ~Eq(left, right) @_polymorphic def Gt(left, right): """ - Assert first set is greater than the second set. + Returns a Polyhedron instance with a single constraint as left greater than right. """ return Polyhedron([], [left - right - 1]) @_polymorphic def Ge(left, right): """ - Assert first set is greater than or equal to the second set. + Returns a Polyhedron instance with a single constraint as left greater than or equal to right. """ return Polyhedron([], [left - right]) -- 2.20.1 From 197818714e75c2353ed8b7c9fec653f1212f13ae Mon Sep 17 00:00:00 2001 From: Danielle Bolan Date: Wed, 13 Aug 2014 10:59:08 +0200 Subject: [PATCH 10/16] Add license to examples --- examples/bac2014.py | 17 +++++++++++++++ examples/diamonds.py | 17 +++++++++++++++ examples/menger.py | 17 +++++++++++++++ examples/nsad2010.py | 17 +++++++++++++++ examples/squares.py | 48 +++++++++++++++++++++++++++++++++---------- examples/tesseract.py | 17 +++++++++++++++ 6 files changed, 122 insertions(+), 11 deletions(-) diff --git a/examples/bac2014.py b/examples/bac2014.py index 775be66..3c69100 100755 --- a/examples/bac2014.py +++ b/examples/bac2014.py @@ -1,4 +1,21 @@ #!/usr/bin/env python3 +# +# Copyright 2014 MINES ParisTech +# +# This file is part of LinPy. +# +# LinPy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# LinPy is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with LinPy. If not, see . from linpy import * diff --git a/examples/diamonds.py b/examples/diamonds.py index 0978d4c..fdf1cc7 100755 --- a/examples/diamonds.py +++ b/examples/diamonds.py @@ -1,4 +1,21 @@ #!/usr/bin/env python3 +# +# Copyright 2014 MINES ParisTech +# +# This file is part of LinPy. +# +# LinPy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# LinPy is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with LinPy. If not, see . import matplotlib.pyplot as plt diff --git a/examples/menger.py b/examples/menger.py index d8a74d4..064219e 100755 --- a/examples/menger.py +++ b/examples/menger.py @@ -1,4 +1,21 @@ #!/usr/bin/env python3 +# +# Copyright 2014 MINES ParisTech +# +# This file is part of LinPy. +# +# LinPy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# LinPy is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with LinPy. If not, see . import argparse diff --git a/examples/nsad2010.py b/examples/nsad2010.py index 9359315..91a85b4 100755 --- a/examples/nsad2010.py +++ b/examples/nsad2010.py @@ -1,4 +1,21 @@ #!/usr/bin/env python3 +# +# Copyright 2014 MINES ParisTech +# +# This file is part of LinPy. +# +# LinPy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# LinPy is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with LinPy. If not, see . from linpy import * diff --git a/examples/squares.py b/examples/squares.py index ea48fe4..89be192 100755 --- a/examples/squares.py +++ b/examples/squares.py @@ -1,6 +1,25 @@ #!/usr/bin/env python3 +# +# Copyright 2014 MINES ParisTech +# +# This file is part of LinPy. +# +# LinPy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# LinPy is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with LinPy. If not, see . from linpy import * +import matplotlib.pyplot as plt +from matplotlib import pylab a, x, y, z = symbols('a x y z') @@ -13,7 +32,6 @@ sq6 = Le(1, x) & Le(x, 2) & Le(1, y) & Le(y, 3) sq7 = Le(0, x) & Le(x, 2) & Le(0, y) & Eq(z, 2) & Le(a, 3) p = Le(2*x+1, y) & Le(-2*x-1, y) & Le(y, 1) - universe = Polyhedron([]) q = sq1 - sq2 e = Empty @@ -60,19 +78,27 @@ print('lexographic max of sq2:', sq2.lexmax()) #test lexmax() print() print('Polyhedral hull of sq1 + sq2 is:', q.aspolyhedron()) #test polyhedral hull print() -print('is sq1 bounded?', sq1.isbounded()) #unbounded should return True +print('is sq1 bounded?', sq1.isbounded()) #bounded should return True print('is sq5 bounded?', sq5.isbounded()) #unbounded should return False print() print('sq6:', sq6) -print('sq6 simplified:', sq6.sample()) -print() -print(universe.project([x])) -print('sq7 with out constraints involving y and a', sq7.project([a, z, x, y])) #drops dims that are passed -print() -print('sq1 has {} parameters'.format(sq1.num_parameters())) +print('sample Polyhedron from sq6:', sq6.sample()) print() -print('does sq1 constraints involve x?', sq1.involves_dims([x])) +print('sq7 with out constraints involving y and a', sq7.project([a, z, x, y])) print() print('the verticies for s are:', p.vertices()) -print() -print(p.plot()) + + +# plotting the intersection of two squares +square1 = Le(0, x) & Le(x, 2) & Le(0, y) & Le(y, 2) +square2 = Le(1, x) & Le(x, 3) & Le(1, y) & Le(y, 3) + +fig = plt.figure() +plot = fig.add_subplot(1, 1, 1, aspect='equal') +square1.plot(plot, facecolor='red', alpha=0.3) +square2.plot(plot, facecolor='blue', alpha=0.3) + +squares = Polyhedron(square1 + square2) +squares.plot(plot, facecolor='blue', alpha=0.3) + +pylab.show() diff --git a/examples/tesseract.py b/examples/tesseract.py index 0a57188..bf338f7 100755 --- a/examples/tesseract.py +++ b/examples/tesseract.py @@ -1,4 +1,21 @@ #!/usr/bin/env python3 +# +# Copyright 2014 MINES ParisTech +# +# This file is part of LinPy. +# +# LinPy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# LinPy is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with LinPy. If not, see . from linpy import * -- 2.20.1 From 454a26a54cc7ff563ab278567f3bbad9c6ff42bb Mon Sep 17 00:00:00 2001 From: Danielle Bolan Date: Thu, 14 Aug 2014 15:59:51 +0200 Subject: [PATCH 11/16] Remove license and add to doc examples --- doc/examples.rst | 8 +++++++- examples/bac2014.py | 17 ----------------- examples/diamonds.py | 17 ----------------- examples/menger.py | 17 ----------------- examples/nsad2010.py | 17 ----------------- examples/squares.py | 17 ----------------- examples/tesseract.py | 17 ----------------- 7 files changed, 7 insertions(+), 103 deletions(-) diff --git a/doc/examples.rst b/doc/examples.rst index ee254bc..3d2626d 100644 --- a/doc/examples.rst +++ b/doc/examples.rst @@ -15,7 +15,9 @@ Basic Examples Binary operations and properties examples: - >>> square2 = Le(1, x) & Le(x, 3) & Le(1, y) & Le(y, 3) + >>> # create a polyhedron from a string + >>> square2 = Polyhedron('1 <= x') & Polyhedron('x <= 3') & \ + Polyhedron('1 <= y') & Polyhedron('y <= 3') >>> #test equality >>> square1 == square2 False @@ -38,6 +40,10 @@ Basic Examples >>> square1.isempty() False + >>> # compute the complement of square1 + >>> ~square1 + Or(Ge(-x - 1, 0), Ge(x - 3, 0), And(Ge(x, 0), Ge(-x + 2, 0), \ + Ge(-y - 1, 0)), And(Ge(x, 0), Ge(-x + 2, 0), Ge(y - 3, 0))) >>> square1.symbols() (x, y) >>> square1.inequalities diff --git a/examples/bac2014.py b/examples/bac2014.py index 3c69100..775be66 100755 --- a/examples/bac2014.py +++ b/examples/bac2014.py @@ -1,21 +1,4 @@ #!/usr/bin/env python3 -# -# Copyright 2014 MINES ParisTech -# -# This file is part of LinPy. -# -# LinPy is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# LinPy is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with LinPy. If not, see . from linpy import * diff --git a/examples/diamonds.py b/examples/diamonds.py index fdf1cc7..0978d4c 100755 --- a/examples/diamonds.py +++ b/examples/diamonds.py @@ -1,21 +1,4 @@ #!/usr/bin/env python3 -# -# Copyright 2014 MINES ParisTech -# -# This file is part of LinPy. -# -# LinPy is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# LinPy is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with LinPy. If not, see . import matplotlib.pyplot as plt diff --git a/examples/menger.py b/examples/menger.py index 064219e..d8a74d4 100755 --- a/examples/menger.py +++ b/examples/menger.py @@ -1,21 +1,4 @@ #!/usr/bin/env python3 -# -# Copyright 2014 MINES ParisTech -# -# This file is part of LinPy. -# -# LinPy is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# LinPy is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with LinPy. If not, see . import argparse diff --git a/examples/nsad2010.py b/examples/nsad2010.py index 91a85b4..9359315 100755 --- a/examples/nsad2010.py +++ b/examples/nsad2010.py @@ -1,21 +1,4 @@ #!/usr/bin/env python3 -# -# Copyright 2014 MINES ParisTech -# -# This file is part of LinPy. -# -# LinPy is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# LinPy is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with LinPy. If not, see . from linpy import * diff --git a/examples/squares.py b/examples/squares.py index 89be192..98e2ca8 100755 --- a/examples/squares.py +++ b/examples/squares.py @@ -1,21 +1,4 @@ #!/usr/bin/env python3 -# -# Copyright 2014 MINES ParisTech -# -# This file is part of LinPy. -# -# LinPy is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# LinPy is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with LinPy. If not, see . from linpy import * import matplotlib.pyplot as plt diff --git a/examples/tesseract.py b/examples/tesseract.py index bf338f7..0a57188 100755 --- a/examples/tesseract.py +++ b/examples/tesseract.py @@ -1,21 +1,4 @@ #!/usr/bin/env python3 -# -# Copyright 2014 MINES ParisTech -# -# This file is part of LinPy. -# -# LinPy is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# LinPy is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with LinPy. If not, see . from linpy import * -- 2.20.1 From a08ebc700e22f6aee8147cb5b5323a6c040b12db Mon Sep 17 00:00:00 2001 From: Vivien Maisonneuve Date: Sun, 17 Aug 2014 22:25:22 +0200 Subject: [PATCH 12/16] Reformat documentation. --- doc/conf.py | 10 +- doc/domain.rst | 180 ------------ doc/examples.rst | 198 ++++++------- doc/geometry.rst | 79 ------ doc/index.rst | 29 +- doc/install.rst | 53 ++-- doc/linexpr.rst | 140 ---------- doc/modules.rst | 17 -- doc/polyhedra.rst | 74 ----- doc/reference.rst | 695 ++++++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 849 insertions(+), 626 deletions(-) delete mode 100644 doc/domain.rst delete mode 100644 doc/geometry.rst delete mode 100644 doc/linexpr.rst delete mode 100644 doc/modules.rst delete mode 100644 doc/polyhedra.rst create mode 100644 doc/reference.rst diff --git a/doc/conf.py b/doc/conf.py index 23a1b91..ed93095 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -29,9 +29,7 @@ import os # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = [ - 'sphinx.ext.mathjax', -] +extensions = [] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] @@ -54,9 +52,9 @@ copyright = '2014, MINES ParisTech' # built documents. # # The short X.Y version. -version = '' +version = '1.0' # The full version, including alpha/beta/rc tags. -release = '' +release = '1.0a' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -244,7 +242,7 @@ man_pages = [ # dir menu entry, description, category) texinfo_documents = [ ('index', 'LinPy', 'LinPy Documentation', - 'MINES ParisTech', 'LinPy', 'One line description of project.', + 'MINES ParisTech', 'LinPy', 'A polyheral library based on ISL.', 'Miscellaneous'), ] diff --git a/doc/domain.rst b/doc/domain.rst deleted file mode 100644 index 06eec6e..0000000 --- a/doc/domain.rst +++ /dev/null @@ -1,180 +0,0 @@ -Domains Module -============== - -This module provides classes and functions to deal with polyhedral -domains, i.e. unions of polyhedra. - -.. py:class :: Domain - - This class represents polyhedral domains, i.e. unions of polyhedra. - - .. py:method:: __new__(cls, *polyhedra) - - Create and return a new domain from a string or a list of polyhedra. - - .. attribute:: polyhedra - - The tuple of polyhedra which constitute the domain. - - .. attribute:: symbols - - Returns a tuple of the symbols that exsist in a domain. - - .. attribute:: dimension - - Returns the number of variables that exist in a domain. - - .. py:method:: isempty(self) - - Return ``True`` is a domain is empty. - - .. py:method:: __bool__(self) - - Return ``True`` if the domain is non-empty. - - .. py:method:: isuniverse(self) - - Return ``True`` if a domain is the Universe set. - - .. py:method:: isbounded(self) - - Return ``True`` if a domain is bounded. - - .. py:method:: make_disjoint(self) - - It is not guarenteed that a domain is disjoint. If it is necessary, this method will return an equivalent domain, whose polyhedra are disjoint. - - .. py:method:: isdisjoint(self, other) - - Return ``True`` if the intersection of *self* and *other* results in an empty set. - - .. py:method:: issubset(self, other) - - Test whether every element in a domain is in *other*. - - .. py:method:: __eq__(self, other) - self == other - - Test whether a domain is equal to *other*. - - .. py:method:: __lt__(self, other) - self < other - - Test whether a domain is a strict subset of *other*. - - .. py:method:: __le__(self, other) - self <= other - - Test whether every element in a domain is in *other*. - - .. py:method:: __gt__(self, other) - self > other - - Test whether a domain is a strict superset of *other*. - - .. py:method:: __ge__(self, other) - self >= other - - Test whether every element in *other* is in a domain. - - .. py:method:: complement(self) - ~self - - Return the complementary domain of a domain. - - .. py:method:: coalesce(self) - - Simplify the representation of the domain by trying to combine pairs of - polyhedra into a single polyhedron. - - - .. py:method:: detect_equalities(self) - - Simplify the representation of the domain by detecting implicit - equalities. - - .. py:method:: simplify(self) - - Return a new domain without any redundant constraints. - - .. py:method:: project(self, variables) - - Return a new domain with the given variables removed. - - .. py:method:: aspolyhedron(self) - - Return polyhedral hull of a domain. - - .. py:method:: sample(self) - - Return a single sample subset of a domain. - - .. py:method:: intersection(self, other) - __or__ - self | other - - Return a new domain with the elements that are common between *self* and *other*. - - .. py:method:: union(self, other) - __and__ - self & other - - Return a new domain with all the elements from *self* and *other*. - - .. py:method:: difference(self, other) - __sub__ - self - other - - Return a new domain with the elements in a domain that are not in *other* . - - .. py:method:: __add__(self, other) - self + other - - Return the sum of two domains. - - .. py:method:: lexmin(self) - - Return a new domain containing the lexicographic minimum of the elements in the domain. - - .. py:method:: lexmax(self) - - Return a new domain containing the lexicographic maximum of the elements in the domain. - - .. py:method:: subs(self, symbol, expression=None): - - Subsitute symbol by expression in equations and return the resulting - domain. - - .. py:method:: fromstring(cls, string) - - Convert a string into a domain. - - .. py:method:: fromsympy(cls, expr) - - Convert a SymPy expression into a domain. - - .. py:method:: tosympy(self) - - Convert a domain into a SymPy expression. - -A 2D or 3D domain can be plotted using the :meth:`plot` method. The points, vertices, and faces of a domain can be inspected using the following functions. - - .. py:method:: points(self) - - Return a list of the points with integer coordinates contained in a domain as :class:`Points` objects. - - .. py:method:: __contains__(self, point) - - Return ``True`` if point if contained within the domain. - - .. py:method:: vertices(self) - - Return a list of the verticies of a domain. - - .. py:method:: faces(self) - - Return a list of the vertices for each face of a domain. - - .. py:method:: plot(self, plot=None, **kwargs) - - Return a plot of the given domain or add a plot to a plot instance, using matplotlib. diff --git a/doc/examples.rst b/doc/examples.rst index 3d2626d..b552b7f 100644 --- a/doc/examples.rst +++ b/doc/examples.rst @@ -1,112 +1,118 @@ -LinPy Examples -============== + +.. _examples: + +Examples +======== Basic Examples -------------- - To create any polyhedron, first define the symbols used. Then use the polyhedron functions to define the constraints. The following is a simple running example illustrating some different operations and properties that can be performed by LinPy with two squares. - - >>> from linpy import * - >>> x, y = symbols('x y') - >>> # define the constraints of the polyhedron - >>> square1 = Le(0, x) & Le(x, 2) & Le(0, y) & Le(y, 2) - >>> square1 - And(Ge(x, 0), Ge(-x + 2, 0), Ge(y, 0), Ge(-y + 2, 0)) - - Binary operations and properties examples: - - >>> # create a polyhedron from a string - >>> square2 = Polyhedron('1 <= x') & Polyhedron('x <= 3') & \ - Polyhedron('1 <= y') & Polyhedron('y <= 3') - >>> #test equality - >>> square1 == square2 - False - >>> # compute the union of two polyhedrons - >>> square1 | square2 - Or(And(Ge(x, 0), Ge(-x + 2, 0), Ge(y, 0), Ge(-y + 2, 0)), \ +To create any polyhedron, first define the symbols used. +Then use the polyhedron functions to define the constraints. +The following is a simple running example illustrating some different operations and properties that can be performed by LinPy with two squares. + +>>> from linpy import * +>>> x, y = symbols('x y') +>>> # define the constraints of the polyhedron +>>> square1 = Le(0, x) & Le(x, 2) & Le(0, y) & Le(y, 2) +>>> square1 +And(Ge(x, 0), Ge(-x + 2, 0), Ge(y, 0), Ge(-y + 2, 0)) + +Binary operations and properties examples: + +>>> # create a polyhedron from a string +>>> square2 = Polyhedron('1 <= x') & Polyhedron('x <= 3') & \ + Polyhedron('1 <= y') & Polyhedron('y <= 3') +>>> #test equality +>>> square1 == square2 +False +>>> # compute the union of two polyhedra +>>> square1 | square2 +Or(And(Ge(x, 0), Ge(-x + 2, 0), Ge(y, 0), Ge(-y + 2, 0)), \ And(Ge(x - 1, 0), Ge(-x + 3, 0), Ge(y - 1, 0), Ge(-y + 3, 0))) - >>> # check if square1 and square2 are disjoint - >>> square1.disjoint(square2) - False - >>> # compute the intersection of two polyhedrons - >>> square1 & square2 - And(Ge(x - 1, 0), Ge(-x + 2, 0), Ge(y - 1, 0), Ge(-y + 2, 0)) - >>> # compute the convex union of two polyhedrons - >>> Polyhedron(square1 | sqaure2) - And(Ge(x, 0), Ge(y, 0), Ge(-y + 3, 0), Ge(-x + 3, 0), \ +>>> # check if square1 and square2 are disjoint +>>> square1.disjoint(square2) +False +>>> # compute the intersection of two polyhedra +>>> square1 & square2 +And(Ge(x - 1, 0), Ge(-x + 2, 0), Ge(y - 1, 0), Ge(-y + 2, 0)) +>>> # compute the convex union of two polyhedra +>>> Polyhedron(square1 | sqaure2) +And(Ge(x, 0), Ge(y, 0), Ge(-y + 3, 0), Ge(-x + 3, 0), \ Ge(x - y + 2, 0), Ge(-x + y + 2, 0)) - Unary operation and properties examples: +Unary operation and properties examples: - >>> square1.isempty() - False - >>> # compute the complement of square1 - >>> ~square1 - Or(Ge(-x - 1, 0), Ge(x - 3, 0), And(Ge(x, 0), Ge(-x + 2, 0), \ +>>> square1.isempty() +False +>>> # compute the complement of square1 +>>> ~square1 +Or(Ge(-x - 1, 0), Ge(x - 3, 0), And(Ge(x, 0), Ge(-x + 2, 0), \ Ge(-y - 1, 0)), And(Ge(x, 0), Ge(-x + 2, 0), Ge(y - 3, 0))) - >>> square1.symbols() - (x, y) - >>> square1.inequalities - (x, -x + 2, y, -y + 2) - >>> # project out the variable x - >>> square1.project([x]) - And(Ge(-y + 2, 0), Ge(y, 0)) +>>> square1.symbols() +(x, y) +>>> square1.inequalities +(x, -x + 2, y, -y + 2) +>>> # project out the variable x +>>> square1.project([x]) +And(Ge(-y + 2, 0), Ge(y, 0)) Plot Examples ------------- - LinPy uses matplotlib plotting library to plot 2D and 3D polygons. The user has the option to pass subplots to the :meth:`plot` method. This can be a useful tool to compare polygons. Also, key word arguments can be passed such as color and the degree of transparency of a polygon. - - >>> import matplotlib.pyplot as plt - >>> from matplotlib import pylab - >>> from mpl_toolkits.mplot3d import Axes3D - >>> from linpy import * - >>> # define the symbols - >>> x, y, z = symbols('x y z') - >>> fig = plt.figure() - >>> cham_plot = fig.add_subplot(1, 1, 1, projection='3d', aspect='equal') - >>> cham_plot.set_title('Chamfered cube') - >>> cham = Le(0, x) & Le(x, 3) & Le(0, y) & Le(y, 3) & Le(0, z) & \ - Le(z, 3) & Le(z - 2, x) & Le(x, z + 2) & Le(1 - z, x) & \ - Le(x, 5 - z) & Le(z - 2, y) & Le(y, z + 2) & Le(1 - z, y) & \ - Le(y, 5 - z) & Le(y - 2, x) & Le(x, y + 2) & Le(1 - y, x) & Le(x, 5 - y) - >>> cham.plot(cham_plot, facecolor='red', alpha=0.75) - >>> pylab.show() - - .. figure:: images/cham_cube.jpg - :align: center +LinPy can use the matplotlib plotting library to plot 2D and 3D polygons. +This can be a useful tool to visualize and compare polygons. +The user has the option to pass plot objects to the :meth:`Domain.plot` method, which provides great flexibility. +Also, keyword arguments can be passed such as color and the degree of transparency of a polygon. + +>>> import matplotlib.pyplot as plt +>>> from matplotlib import pylab +>>> from mpl_toolkits.mplot3d import Axes3D +>>> from linpy import * +>>> # define the symbols +>>> x, y, z = symbols('x y z') +>>> fig = plt.figure() +>>> cham_plot = fig.add_subplot(1, 1, 1, projection='3d', aspect='equal') +>>> cham_plot.set_title('Chamfered cube') +>>> cham = Le(0, x) & Le(x, 3) & Le(0, y) & Le(y, 3) & Le(0, z) & \ + Le(z, 3) & Le(z - 2, x) & Le(x, z + 2) & Le(1 - z, x) & \ + Le(x, 5 - z) & Le(z - 2, y) & Le(y, z + 2) & Le(1 - z, y) & \ + Le(y, 5 - z) & Le(y - 2, x) & Le(x, y + 2) & Le(1 - y, x) & Le(x, 5 - y) +>>> cham.plot(cham_plot, facecolor='red', alpha=0.75) +>>> pylab.show() + +.. figure:: images/cham_cube.jpg + :align: center LinPy can also inspect a polygon's vertices and the integer points included in the polygon. - >>> diamond = Ge(y, x - 1) & Le(y, x + 1) & Ge(y, -x - 1) & Le(y, -x + 1) - >>> diamond.vertices() - [Point({x: Fraction(0, 1), y: Fraction(1, 1)}), \ - Point({x: Fraction(-1, 1), y: Fraction(0, 1)}), \ - Point({x: Fraction(1, 1), y: Fraction(0, 1)}), \ - Point({x: Fraction(0, 1), y: Fraction(-1, 1)})] - >>> diamond.points() - [Point({x: -1, y: 0}), Point({x: 0, y: -1}), Point({x: 0, y: 0}), \ - Point({x: 0, y: 1}), Point({x: 1, y: 0})] - -The user also can pass another plot to the :meth:`plot` method. This can be useful to compare two polyhedrons on the same axis. This example illustrates the union of two squares. - - >>> from linpy import * - >>> import matplotlib.pyplot as plt - >>> from matplotlib import pylab - >>> x, y = symbols('x y') - >>> square1 = Le(0, x) & Le(x, 2) & Le(0, y) & Le(y, 2) - >>> square2 = Le(1, x) & Le(x, 3) & Le(1, y) & Le(y, 3) - >>> fig = plt.figure() - >>> plot = fig.add_subplot(1, 1, 1, aspect='equal') - >>> square1.plot(plot, facecolor='red', alpha=0.3) - >>> square2.plot(plot, facecolor='blue', alpha=0.3) - >>> squares = Polyhedron(square1 + square2) - >>> squares.plot(plot, facecolor='blue', alpha=0.3) - >>> pylab.show() - - .. figure:: images/union.jpg - :align: center - - - - +>>> diamond = Ge(y, x - 1) & Le(y, x + 1) & Ge(y, -x - 1) & Le(y, -x + 1) +>>> diamond.vertices() +[Point({x: Fraction(0, 1), y: Fraction(1, 1)}), \ + Point({x: Fraction(-1, 1), y: Fraction(0, 1)}), \ + Point({x: Fraction(1, 1), y: Fraction(0, 1)}), \ + Point({x: Fraction(0, 1), y: Fraction(-1, 1)})] +>>> diamond.points() +[Point({x: -1, y: 0}), Point({x: 0, y: -1}), Point({x: 0, y: 0}), \ + Point({x: 0, y: 1}), Point({x: 1, y: 0})] + +The user also can pass another plot to the :meth:`Domain.plot` method. +This can be useful to compare two polyhedra on the same axis. +This example illustrates the union of two squares. + +>>> from linpy import * +>>> import matplotlib.pyplot as plt +>>> from matplotlib import pylab +>>> x, y = symbols('x y') +>>> square1 = Le(0, x) & Le(x, 2) & Le(0, y) & Le(y, 2) +>>> square2 = Le(1, x) & Le(x, 3) & Le(1, y) & Le(y, 3) +>>> fig = plt.figure() +>>> plot = fig.add_subplot(1, 1, 1, aspect='equal') +>>> square1.plot(plot, facecolor='red', alpha=0.3) +>>> square2.plot(plot, facecolor='blue', alpha=0.3) +>>> squares = Polyhedron(square1 + square2) +>>> squares.plot(plot, facecolor='blue', alpha=0.3) +>>> pylab.show() + +.. figure:: images/union.jpg + :align: center diff --git a/doc/geometry.rst b/doc/geometry.rst deleted file mode 100644 index 838ec6e..0000000 --- a/doc/geometry.rst +++ /dev/null @@ -1,79 +0,0 @@ -Geometry Module -=============== - -The geometry module is used to obtain information about the points and vertices of a ployhedra. - -.. py:class:: Points - -This class represents points in space. - - .. py:method:: isorigin(self) - - Return ``True`` if a point is the origin. - - .. py:method:: __eq__(self, other) - - Compare two Points for equality. - - .. py:method:: __add__(self, other) - - Add a Point to a Vector and return the result as a Point. - - .. py:method:: __sub__(self, other) - - Return the difference between two Points as a Vector. - - .. py:method:: aspolyhedon(self) - - Return a Point as a polyhedron corresponding to the Point, assuming the Point has integer coordinates. - - -.. py:class:: Vector - -This class represents displacements in space. - - .. py:method:: __eq__(self, other) - - Compare two Vectors for equality. - - .. py:method:: __add__(self, other) - - Add either a Point or Vector to a Vector. The resulting sum is returned as the same structure *other* is. - - .. py:method:: __sub__(self, other) - - Subtract a Point or Vector from a Vector. The resulting difference is returned in the same form as *other*. - - .. py:method:: __mul__(self, other) - - Multiply a Vector by a scalar value and returns the result as a Vector. - - .. py:method:: __neg__(self) - - Negate a Vector. - - .. py:method:: norm(self) - - Normalizes a Vector. - - .. py:method:: isnull(self) - - Tests whether a Vector is null. - - .. py:method:: angle(self, other) - - Retrieve the angle required to rotate the vector into the vector passed in argument. The result is an angle in radians, ranging between -pi and - pi. - - .. py:method:: cross(self, other) - - Calculate the cross product of two Vector3D structures. If the vectors are not tridimensional, a _____ error is raised. - - .. py:method:: dot(self, other) - - Calculate the dot product of two vectors. - - .. py:method:: __trudiv__(self, other) - - Divide the vector by the specified scalar and returns the result as a vector. - diff --git a/doc/index.rst b/doc/index.rst index b3a9271..ad985bf 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -1,25 +1,30 @@ -.. LinPy documentation master file, created by - sphinx-quickstart on Wed Jun 25 20:34:21 2014. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. Welcome to LinPy’s documentation! ================================= -LinPy is a Python wrapper for the Integer Set Library (isl) by Sven Verdoolaege. Isl ia a C library for manipulating sets and relations of integer points bounded by linear constraints. +LinPy is a polyhedral library for Python based on `isl `_. +isl (Integer Set Library) is a C library for manipulating sets and relations of integer points bounded by linear constraints. -If you are new to LinPy, start with the Examples. +LinPy is a free software, licensed under the `GPLv3 license `_. +Its source code is available `here `_. -This is the central page for all of LinPy’s documentation. +To have an overview of LinPy's functionalities, you may wish to consult the :ref:`examples` section. -Contents: +.. only:: html + + Contents: .. toctree:: - :maxdepth: 2 + :maxdepth: 2 - install.rst - examples.rst - modules.rst + install.rst + examples.rst + reference.rst +.. only:: html + Indices and tables + ================== + * :ref:`genindex` + * :ref:`search` diff --git a/doc/install.rst b/doc/install.rst index d1b9f1f..040029d 100644 --- a/doc/install.rst +++ b/doc/install.rst @@ -1,38 +1,36 @@ -.. _installation: Installation ------------- -Source -====== +============ -Users can install LinPy by cloning the git repository:: +Dependencies +------------ - git clone https://scm.cri.ensmp.fr/git/linpy.git +LinPy requires Python version 3.4 or above to work. -Then, execute the following to complete installation:: - - python3 setup.py install +LinPy's one mandatory dependency is isl version 0.12 or 0.13 (it may work with other versions of isl, but this has not been tested). +isl can be downloaded `here `_ or preferably, using your favorite distribution's package manager. +For Ubuntu, the command to run is:: -PyPi -==== + sudo apt-get install libisl-dev - sudo pip install linpy +For Arch Linux, run:: -Dependencies -============ + sudo pacman -S isl -LinPy's one mandatory dependency is isl, which can be downloaded from `here`_ or using package manager, e.g for ubuntu:: +Apart from isl, there are two optional dependencies that will maximize the use of LinPy's functions: `SymPy `_ and `matplotlib `_. +Please consult the `SymPy download page `_ and `matplotlib installation instructions `_ to install these libraries. - sudo apt-get install libisl­-dev +pip +--- -There are two optional dependencies that will maximize the use of LinPy's -functions; SymPy and matplotlib. SymPy installation instructions can be found on the SymPy `download page`_. Matplotlib install information can be found at this `link`_. +.. warning:: -License -======= + The project has not been published in PyPI yet, so this section is not relevant. + Instead, see the :ref:`source` section to install LinPy. -LinPy is a free software, licensed under the `GPLv3 license`_ . +LinPy can be installed using pip with the command:: + sudo pip install linpy .. _here: http://freshmeat.net/projects/isl/ @@ -40,5 +38,16 @@ LinPy is a free software, licensed under the `GPLv3 license`_ . .. _link: http://matplotlib.org/faq/installing_faq.html -.. _GPLv3 license: https://www.gnu.org/licenses/gpl-3.0.txt +.. _source: + +Source +------ + +Alternatively, LinPy can be installed from source. +First, clone the public git repository:: + + git clone https://scm.cri.ensmp.fr/git/linpy.git + +and build and install as usual with:: + sudo python3 setup.py install diff --git a/doc/linexpr.rst b/doc/linexpr.rst deleted file mode 100644 index eb2f704..0000000 --- a/doc/linexpr.rst +++ /dev/null @@ -1,140 +0,0 @@ -Linear Expression Module -======================== - -This class implements linear expressions. A linear expression is…. - -.. py:class:: Expression - - .. py:method:: __new__(cls, coefficients=None, constant=0) - - Create and return a new linear expression from a string or a list of coefficients and a constant. - - .. py:method:: coefficient(self, symbol) - - Return the coefficient value of the given symbol. - - .. py:method:: coefficients(self) - - Return a list of the coefficients of an expression - - .. attribute:: constant - - Return the constant value of an expression. - - .. attribute:: symbols - - Return a list of symbols in an expression. - - .. attribute:: dimension - - Return the number of variables in an expression. - - .. py:method:: isconstant(self) - - Return ``True`` if an expression is a constant. - - .. py:method:: issymbol(self) - - Return ``True`` if an expression is a symbol. - - - .. py:method:: values(self) - - Return the coefficient and constant values of an expression. - - .. py:method:: __add__(self, other) - - Return the sum of *self* and *other*. - - .. py:method:: __sub__(self, other) - - Return the difference between *self* and *other*. - - .. py:method:: __mul__(self, other) - - Return the product of *self* and *other* if *other* is a rational number. - - .. py:method:: __eq__(self, other) - - Test whether two expressions are equal. - - .. py:method:: __le__(self, other) - self <= other - - Create a new polyhedra from an expression with a single constraint as *self* less than or equal to *other*. - - .. py:method:: __lt__(self, other) - self < other - - Create a new polyhedra from an expression with a single constraint as *self* less than *other*. - - .. py:method:: __ge__(self, other) - self >= other - - Create a new polyhedra from an expression with a single constraint as *self* greater than or equal to *other*. - - .. py:method:: __gt__(self, other) - self > other - - Create a new polyhedra from an expression with a single constraint as *self* greater than *other*. - - .. py:method:: scaleint(self) - - Multiply an expression by a scalar to make all coefficients integer values. - - .. py:method:: subs(self, symbol, expression=None) - - Subsitute the given value into an expression and return the resulting expression. - - - .. py:method:: fromstring(self) - - Create an expression from a string. - - .. py:method:: fromsympy(self) - - Convert sympy object to an expression. - - .. py:method:: tosympy(self) - - Return an expression as a sympy object. - -.. py:class:: Symbol(Expression) - - .. py:method:: __new__(cls, name) - - Create and return a symbol from a string. - - .. py:method:: symbols(names) - - This function returns a sequence of symbols with names taken from names argument, which can be a comma or whitespace delimited string, or a sequence of strings. - - .. py:method:: asdummy(self) - - Return a symbol as a :class:`Dummy` Symbol. - -.. py:class:: Dummy(Symbol) - - This class returns a dummy symbol to ensure that no variables are repeated in an expression. This is useful when the user needs to have a unique symbol, for example as a temporary one in some calculation, which is going to be substituted for something else at the end anyway. - - .. py:method:: __new__(cls, name=None) - - Create and return a new dummy symbol. - - -.. py:class:: Rational(Expression, Fraction) - - This class represents integers and rational numbers of any size. - - .. attribute:: constant - - Return rational as a constant. - - .. py:method:: isconstant(self) - - Test whether a value is a constant. - - .. py:method:: fromsympy(cls, expr) - - Create a rational object from a sympy expression - diff --git a/doc/modules.rst b/doc/modules.rst deleted file mode 100644 index 4859f2d..0000000 --- a/doc/modules.rst +++ /dev/null @@ -1,17 +0,0 @@ -.. module-docs: - -LinPy Module Reference -====================== - -There are four main LinPy modules, all of them can be inherited at once with the LinPy package: - - >>> from linpy import * - -.. toctree:: - :maxdepth: 2 - - linexpr.rst - domain.rst - polyhedra.rst - geometry.rst - diff --git a/doc/polyhedra.rst b/doc/polyhedra.rst deleted file mode 100644 index f6f1a30..0000000 --- a/doc/polyhedra.rst +++ /dev/null @@ -1,74 +0,0 @@ -Polyhedra Module -================ - -Polyhedron class allows users to build and inspect polyherons. Polyhedron inherits all methods from the :class:`Domain` class. - -.. py:class:: Polyhedron(Domain) - - .. py:method:: __new__(cls, equalities=None, inequalities=None) - - Create and return a new Polyhedron from a string or list of equalities and inequalities. - - .. attribute:: equalities - - Returns a list of the equalities in a polyhedron. - - .. attribute:: inequalities - - Returns a list of the inequalities in a polyhedron. - - .. attribute:: constraints - - Returns a list of the constraints of a polyhedron. - - .. py:method:: make_disjoint(self) - - Returns a polyhedron as a disjoint. - - .. py:method:: isuniverse(self) - - Return ``True`` if a polyhedron is the Universe set. - - .. py:method:: aspolyhedron(self) - - Return the polyhedral hull of a polyhedron. - - .. py:method:: __contains__(self, point) - - Report whether a polyhedron constains an integer point - - .. py:method:: subs(self, symbol, expression=None) - - Subsitute the given value into an expression and return the resulting - expression. - - .. py:method:: fromstring(cls, string) - - Create and return a Polyhedron from a string. - - -To create a polyhedron, the user can use the following functions to define equalities and inequalities as the constraints. - -.. py:function:: Eq(left, right) - - Returns a Polyhedron instance with a single constraint as *left* equal to *right*. - -.. py:function:: Ne(left, right) - - Returns a Polyhedron instance with a single constraint as *left* not equal to *right*. - -.. py:function:: Lt(left, right) - - Returns a Polyhedron instance with a single constraint as *left* less than *right*. - -.. py:function:: Le(left, right) - - Returns a Polyhedron instance with a single constraint as *left* less than or equal to *right*. - -.. py:function:: Gt(left, right) - - Returns a Polyhedron instance with a single constraint as *left* greater than *right*. - -.. py:function:: Ge(left, right) - - Returns a Polyhedron instance with a single constraint as *left* greater than or equal to *right*. diff --git a/doc/reference.rst b/doc/reference.rst new file mode 100644 index 0000000..8184c43 --- /dev/null +++ b/doc/reference.rst @@ -0,0 +1,695 @@ + +Module Reference +================ + +Symbols +------- + +*Symbols* are the basic components to build expressions and constraints. +They correspond to mathematical variables. + +.. class:: Symbol(name) + + Return a symbol with the name string given in argument. + Alternatively, the function :func:`symbols` allows to create several symbols at once. + Symbols are instances of class :class:`LinExpr` and, as such, inherit its functionalities. + + >>> x = Symbol('x') + >>> x + x + + Two instances of :class:`Symbol` are equal if they have the same name. + + .. attribute:: name + + The name of the symbol. + + .. method:: asdummy() + + Return a new :class:`Dummy` symbol instance with the same name. + + .. method:: sortkey() + + Return a sorting key for the symbol. + It is useful to sort a list of symbols in a consistent order, as comparison functions are overridden (see the documentation of class :class:`LinExpr`). + + >>> sort(symbols, key=Symbol.sortkey) + + +.. function:: symbols(names) + + This function returns a tuple of symbols whose names are taken from a comma or whitespace delimited string, or a sequence of strings. + It is useful to define several symbols at once. + + >>> x, y = symbols('x y') + >>> x, y = symbols('x, y') + >>> x, y = symbols(['x', 'y']) + + +Sometimes, you need to have a unique symbol, for example as a temporary one in some calculation, which is going to be substituted for something else at the end anyway. +This is achieved using ``Dummy('x')``. + +.. class:: Dummy(name=None) + + A variation of :class:`Symbol` which are all unique, identified by an internal count index. + If a name is not supplied then a string value of the count index will be used. + This is useful when a unique, temporary variable is needed and the name of the variable used in the expression is not important. + + Unlike :class:`Symbol`, :class:`Dummy` instances with the same name are not equal: + + >>> x = Symbol('x') + >>> x1, x2 = Dummy('x'), Dummy('x') + >>> x == x1 + False + >>> x1 == x2 + False + >>> x1 == x1 + True + + +Linear Expressions +------------------ + +A *linear expression* consists of a list of coefficient-variable pairs that capture the linear terms, plus a constant term. +Linear expressions are used to build constraints. They are temporary objects that typically have short lifespans. + +Linear expressions are generally built using overloaded operators. +For example, if ``x`` is a :class:`Symbol`, then ``x + 1`` is an instance of :class:`LinExpr`. + +.. class:: LinExpr(coefficients=None, constant=0) + LinExpr(string) + + Return a linear expression from a dictionary or a sequence that maps symbols to their coefficients, and a constant term. + The coefficients and the constant must be rational numbers. + + For example, the linear expression ``x + 2y + 1`` can be constructed using one of the following instructions: + + >>> x, y = symbols('x y') + >>> LinExpr({x: 1, y: 2}, 1) + >>> LinExpr([(x, 1), (y, 2)], 1) + + although it may be easier to use overloaded operators: + + >>> x, y = symbols('x y') + >>> x + 2*y + 1 + + Alternatively, linear expressions can be constructed from a string: + + >>> LinExpr('x + 2*y + 1') + + :class:`LinExpr` instances are hashable, and should be treated as immutable. + + A linear expression with a single symbol of coefficient 1 and no constant term is automatically subclassed as a :class:`Symbol` instance. + A linear expression with no symbol, only a constant term, is automatically subclassed as a :class:`Rational` instance. + + .. method:: coefficient(symbol) + __getitem__(symbol) + + Return the coefficient value of the given symbol, or ``0`` if the symbol does not appear in the expression. + + .. method:: coefficients() + + Iterate over the pairs ``(symbol, value)`` of linear terms in the expression. + The constant term is ignored. + + .. attribute:: constant + + The constant term of the expression. + + .. attribute:: symbols + + The tuple of symbols present in the expression, sorted according to :meth:`Symbol.sortkey`. + + .. attribute:: dimension + + The dimension of the expression, i.e. the number of symbols present in it. + + .. method:: isconstant() + + Return ``True`` if the expression only consists of a constant term. + In this case, it is a :class:`Rational` instance. + + .. method:: issymbol() + + Return ``True`` if an expression only consists of a symbol with coefficient ``1``. + In this case, it is a :class:`Symbol` instance. + + .. method:: values() + + Iterate over the coefficient values in the expression, and the constant term. + + .. method:: __add__(expr) + + Return the sum of two linear expressions. + + .. method:: __sub__(expr) + + Return the difference between two linear expressions. + + .. method:: __mul__(value) + + Return the product of the linear expression by a rational. + + .. method:: __truediv__(value) + + Return the quotient of the linear expression by a rational. + + .. method:: __eq__(expr) + + Test whether two linear expressions are equal. + + As explained below, it is possible to create polyhedra from linear expressions using comparison methods. + + .. method:: __lt__(expr) + __le__(expr) + __ge__(expr) + __gt__(expr) + + Create a new :class:`Polyhedron` instance whose unique constraint is the comparison between two linear expressions. + As an alternative, functions :func:`Lt`, :func:`Le`, :func:`Ge` and :func:`Gt` can be used. + + >>> x, y = symbols('x y') + >>> x < y + Le(x - y + 1, 0) + + + .. method:: scaleint() + + Return the expression multiplied by its lowest common denominator to make all values integer. + + .. method:: subs(symbol, expression) + subs(pairs) + + Substitute the given symbol by an expression and return the resulting expression. + Raise :exc:`TypeError` is the resulting expression is not linear. + + >>> x, y = symbols('x y') + >>> e = x + 2*y + 1 + >>> e.subs(y, x - 1) + 3*x - 1 + + To perform multiple substitutions at once, pass a sequence or a dictionary of ``(old, new)`` pairs to ``subs``. + + >>> e.subs({x: y, y: x}) + 2*x + y + 1 + + .. classmethod:: fromstring(string) + + Create an expression from a string. + Raise :exc:`SyntaxError` if the string is not properly formatted. + + There are also methods to convert linear expressions to and from `SymPy `_ expressions: + + .. classmethod:: fromsympy(expr) + + Create a linear expression from a :mod:`sympy` expression. + Raise :exc:`ValueError` is the :mod:`sympy` expression is not linear. + + .. method:: tosympy() + + Convert the linear expression to a sympy expression. + + +Apart from :mod:`Symbol`, a particular case of linear expressions are rational values, i.e. linear expressions consisting only of a constant term, with no symbol. +They are implemented by the :class:`Rational` class, that inherits from both :class:`LinExpr` and :class:`fractions.Fraction` classes. + +.. class:: Rational(numerator, denominator=1) + Rational(string) + + The first version requires that *numerator* and *denominator* are instances of :class:`numbers.Rational` and returns a new :class:`Rational` instance with value ``numerator/denominator``. + If denominator is ``0``, it raises a :exc:`ZeroDivisionError`. + The other version of the constructor expects a string. + The usual form for this instance is:: + + [sign] numerator ['/' denominator] + + where the optional ``sign`` may be either '+' or '-' and ``numerator`` and ``denominator`` (if present) are strings of decimal digits. + + See the documentation of :class:`fractions.Fraction` for more information and examples. + +Polyhedra +--------- + +A *convex polyhedron* (or simply polyhedron) is the space defined by a system of linear equalities and inequalities. +This space can be unbounded. + +.. class:: Polyhedron(equalities, inequalities) + Polyhedron(string) + Polyhedron(geometric object) + + Return a polyhedron from two sequences of linear expressions: *equalities* is a list of expressions equal to ``0``, and *inequalities* is a list of expressions greater or equal to ``0``. + For example, the polyhedron ``0 <= x <= 2, 0 <= y <= 2`` can be constructed with: + + >>> x, y = symbols('x y') + >>> square = Polyhedron([], [x, 2 - x, y, 2 - y]) + + It may be easier to use comparison operators :meth:`LinExpr.__lt__`, :meth:`LinExpr.__le__`, :meth:`LinExpr.__ge__`, :meth:`LinExpr.__gt__`, or functions :func:`Lt`, :func:`Le`, :func:`Eq`, :func:`Ge` and :func:`Gt`, using one of the following instructions: + + >>> x, y = symbols('x y') + >>> square = (0 <= x) & (x <= 2) & (0 <= y) & (y <= 2) + >>> square = Le(0, x, 2) & Le(0, y, 2) + + It is also possible to build a polyhedron from a string. + + >>> square = Polyhedron('0 <= x <= 2, 0 <= y <= 2') + + Finally, a polyhedron can be constructed from a :class:`GeometricObject` instance, calling the :meth:`GeometricObject.aspolyedron` method. + This way, it is possible to compute the polyhedral hull of a :class:`Domain` instance, i.e., the convex hull of two polyhedra: + + >>> square = Polyhedron('0 <= x <= 2, 0 <= y <= 2') + >>> square2 = Polyhedron('2 <= x <= 4, 2 <= y <= 4') + >>> Polyhedron(square | square2) + + A polyhedron is a :class:`Domain` instance, and, as such, inherits the functionalities of this class. + It is also a :class:`GeometricObject` instance. + + .. attribute:: equalities + + The tuple of equalities. + This is a list of :class:`LinExpr` instances that are equal to ``0`` in the polyhedron. + + .. attribute:: inequalities + + The tuple of inequalities. + This is a list of :class:`LinExpr` instances that are greater or equal to ``0`` in the polyhedron. + + .. attribute:: constraints + + The tuple of constraints, i.e., equalities and inequalities. + This is semantically equivalent to: ``equalities + inequalities``. + + .. method:: widen(polyhedron) + + Compute the standard widening of two polyhedra, à la Halbwachs. + + +.. data:: Empty + + The empty polyhedron, whose set of constraints is not satisfiable. + +.. data:: Universe + + The universe polyhedron, whose set of constraints is always satisfiable, i.e. is empty. + +Domains +------- + +A *domain* is a union of polyhedra. +Unlike polyhedra, domains allow exact computation of union and complementary operations. + +.. class:: Domain(*polyhedra) + Domain(string) + Domain(geometric object) + + Return a domain from a sequence of polyhedra. + + >>> square = Polyhedron('0 <= x <= 2, 0 <= y <= 2') + >>> square2 = Polyhedron('2 <= x <= 4, 2 <= y <= 4') + >>> dom = Domain([square, square2]) + + It is also possible to build domains from polyhedra using arithmetic operators :meth:`Domain.__and__`, :meth:`Domain.__or__` or functions :func:`And` and :func:`Or`, using one of the following instructions: + + >>> square = Polyhedron('0 <= x <= 2, 0 <= y <= 2') + >>> square2 = Polyhedron('2 <= x <= 4, 2 <= y <= 4') + >>> dom = square | square2 + >>> dom = Or(square, square2) + + Alternatively, a domain can be built from a string: + + >>> dom = Domain('0 <= x <= 2, 0 <= y <= 2; 2 <= x <= 4, 2 <= y <= 4') + + Finally, a domain can be built from a :class:`GeometricObject` instance, calling the :meth:`GeometricObject.asdomain` method. + + A domain is also a :class:`GeometricObject` instance. + A domain with a unique polyhedron is automatically subclassed as a :class:`Polyhedron` instance. + + .. attribute:: polyhedra + + The tuple of polyhedra present in the domain. + + .. attribute:: symbols + + The tuple of symbols present in the domain expression, sorted according to :meth:`Symbol.sortkey`. + + .. attribute:: dimension + + The dimension of the domain, i.e. the number of symbols present in it. + + .. method:: isempty() + + Return ``True`` if the domain is empty, that is, equal to :data:`Empty`. + + .. method:: __bool__() + + Return ``True`` if the domain is non-empty. + + .. method:: isuniverse() + + Return ``True`` if the domain is universal, that is, equal to :data:`Universe`. + + .. method:: isbounded() + + Return ``True`` is the domain is bounded. + + .. method:: __eq__(domain) + + Return ``True`` if two domains are equal. + + .. method:: isdisjoint(domain) + + Return ``True`` if two domains have a null intersection. + + .. method:: issubset(domain) + __le__(domain) + + Report whether another domain contains the domain. + + .. method:: __lt__(domain) + + Report whether another domain is contained within the domain. + + .. method:: complement() + __invert__() + + Return the complementary domain of the domain. + + .. method:: make_disjoint() + + Return an equivalent domain, whose polyhedra are disjoint. + + .. method:: coalesce() + + Simplify the representation of the domain by trying to combine pairs of polyhedra into a single polyhedron, and return the resulting domain. + + .. method:: detect_equalities() + + Simplify the representation of the domain by detecting implicit equalities, and return the resulting domain. + + .. method:: remove_redundancies() + + Remove redundant constraints in the domain, and return the resulting domain. + + .. method:: project(symbols) + + Project out the sequence of symbols given in arguments, and return the resulting domain. + + .. method:: sample() + + Return a sample of the domain, as an integer instance of :class:`Point`. + If the domain is empty, a :exc:`ValueError` exception is raised. + + .. method:: intersection(domain[, ...]) + __and__(domain) + + Return the intersection of two or more domains as a new domain. + As an alternative, function :func:`And` can be used. + + .. method:: union(domain[, ...]) + __or__(domain) + __add__(domain) + + Return the union of two or more domains as a new domain. + As an alternative, function :func:`Or` can be used. + + .. method:: difference(domain) + __sub__(domain) + + Return the difference between two domains as a new domain. + + .. method:: lexmin() + + Return the lexicographic minimum of the elements in the domain. + + .. method:: lexmax() + + Return the lexicographic maximum of the elements in the domain. + + .. method:: vertices() + + Return the vertices of the domain, as a list of rational instances of :class:`Point`. + + .. method:: points() + + Return the integer points of a bounded domain, as a list of integer instances of :class:`Point`. + If the domain is not bounded, a :exc:`ValueError` exception is raised. + + .. method:: __contains__(point) + + Return ``True`` if the :class:`Point` is contained within the domain. + + .. method:: faces() + + Return the list of faces of a bounded domain. + Each face is represented by a list of vertices, in the form of rational instances of :class:`Point`. + If the domain is not bounded, a :exc:`ValueError` exception is raised. + + .. method:: plot(plot=None, **options) + + Plot a 2D or 3D domain using `matplotlib `_. + Draw it to the current *plot* object if present, otherwise create a new one. + *options* are keyword arguments passed to the matplotlib drawing functions, they can be used to set the drawing color for example. + Raise :exc:`ValueError` is the domain is not 2D or 3D. + + .. method:: subs(symbol, expression) + subs(pairs) + + Substitute the given symbol by an expression in the domain constraints. + To perform multiple substitutions at once, pass a sequence or a dictionary of ``(old, new)`` pairs to ``subs``. + The syntax of this function is similar to :func:`LinExpr.subs`. + + .. classmethod:: fromstring(string) + + Create a domain from a string. + Raise :exc:`SyntaxError` if the string is not properly formatted. + + There are also methods to convert a domain to and from `SymPy `_ expressions: + + .. classmethod:: fromsympy(expr) + + Create a domain from a sympy expression. + + .. method:: tosympy() + + Convert the domain to a sympy expression. + + +Comparison and Logic Operators +------------------------------ + +The following functions allow to create :class:`Polyhedron` or :class:`Domain` instances by comparison of :class:`LinExpr` instances: + +.. function:: Lt(expr1, expr2[, expr3, ...]) + + Create the polyhedron with constraints ``expr1 < expr2 < expr3 ...``. + +.. function:: Le(expr1, expr2[, expr3, ...]) + + Create the polyhedron with constraints ``expr1 <= expr2 <= expr3 ...``. + +.. function:: Eq(expr1, expr2[, expr3, ...]) + + Create the polyhedron with constraints ``expr1 == expr2 == expr3 ...``. + +.. function:: Ne(expr1, expr2[, expr3, ...]) + + Create the domain such that ``expr1 != expr2 != expr3 ...``. + The result is a :class:`Domain`, not a :class:`Polyhedron`. + +.. function:: Ge(expr1, expr2[, expr3, ...]) + + Create the polyhedron with constraints ``expr1 >= expr2 >= expr3 ...``. + +.. function:: Gt(expr1, expr2[, expr3, ...]) + + Create the polyhedron with constraints ``expr1 > expr2 > expr3 ...``. + +The following functions allow to combine :class:`Polyhedron` or :class:`Domain` instances using logic operators: + +.. function:: Or(domain1, domain2[, ...]) + + Create the union domain of domains given in arguments. + +.. function:: And(domain1, domain2[, ...]) + + Create the intersection domain of domains given in arguments. + +.. function:: Not(domain) + + Create the complementary domain of the domain given in argument. + + +Geometric Objects +----------------- + +.. class:: GeometricObject + + :class:`GeometricObject` is an abstract class to represent objects with a geometric representation in space. + Subclasses of :class:`GeometricObject` are :class:`Polyhedron`, :class:`Domain` and :class:`Point`. + The following elements must be provided: + + .. attribute:: symbols + + The tuple of symbols present in the object expression, sorted according to :class:`Symbol.sortkey()`. + + .. attribute:: dimension + + The dimension of the object, i.e. the number of symbols present in it. + + .. method:: aspolyedron() + + Return a :class:`Polyhedron` object that approximates the geometric object. + + .. method:: asdomain() + + Return a :class:`Domain` object that approximates the geometric object. + +.. class:: Point(coordinates) + + Create a point from a dictionnary or a sequence that maps symbols to their coordinates. + Coordinates must be rational numbers. + + For example, the point ``(x: 1, y: 2)`` can be constructed using one of the following instructions: + + >>> x, y = symbols('x y') + >>> p = Point({x: 1, y: 2}) + >>> p = Point([(x, 1), (y, 2)]) + + :class:`Point` instances are hashable, and should be treated as immutable. + + A point is a :class:`GeometricObject` instance. + + .. attribute:: symbols + + The tuple of symbols present in the point, sorted according to :class:`Symbol.sortkey()`. + + .. attribute:: dimension + + The dimension of the point, i.e. the number of symbols present in it. + + .. method:: coordinate(symbol) + __getitem__(symbol) + + Return the coordinate value of the given symbol. + Raise :exc:`KeyError` if the symbol is not involved in the point. + + .. method:: coordinates() + + Iterate over the pairs ``(symbol, value)`` of coordinates in the point. + + .. method:: values() + + Iterate over the coordinate values in the point. + + .. method:: isorigin() + + Return ``True`` if all coordinates are ``0``. + + .. method:: __bool__() + + Return ``True`` if not all coordinates are ``0``. + + .. method:: __add__(vector) + + Translate the point by a :class:`Vector` instance and return the resulting point. + + .. method:: __sub__(point) + __sub__(vector) + + The first version substract a point from another and return the resulting vector. + The second version translates the point by the opposite vector of *vector* and returns the resulting point. + + .. method:: __eq__(point) + + Test whether two points are equal. + + +.. class:: Vector(coordinates) + + Create a point from a dictionnary or a sequence that maps symbols to their coordinates, similarly to :meth:`Point`. + Coordinates must be rational numbers. + + :class:`Vector` instances are hashable, and should be treated as immutable. + + .. attribute:: symbols + + The tuple of symbols present in the point, sorted according to :class:`Symbol.sortkey()`. + + .. attribute:: dimension + + The dimension of the point, i.e. the number of symbols present in it. + + .. method:: coordinate(symbol) + __getitem__(symbol) + + Return the coordinate value of the given symbol. + Raise :exc:`KeyError` if the symbol is not involved in the point. + + .. method:: coordinates() + + Iterate over the pairs ``(symbol, value)`` of coordinates in the point. + + .. method:: values() + + Iterate over the coordinate values in the point. + + .. method:: isnull() + + Return ``True`` if all coordinates are ``0``. + + .. method:: __bool__() + + Return ``True`` if not all coordinates are ``0``. + + .. method:: __add__(point) + __add__(vector) + + The first version translates the point *point* to the vector and returns the resulting point. + The second version adds vector *vector* to the vector and returns the resulting vector. + + .. method:: __sub__(point) + __sub__(vector) + + The first version substracts a point from a vector and returns the resulting point. + The second version returns the difference vector between two vectors. + + .. method:: __neg__() + + Return the opposite vector. + + .. method:: angle(vector) + + Retrieve the angle required to rotate the vector into the vector passed in argument. + The result is an angle in radians, ranging between ``-pi`` and ``pi``. + + .. method:: cross(vector) + + Compute the cross product of two 3D vectors. + If either one of the vectors is not tridimensional, a :exc:`ValueError` exception is raised. + + .. method:: dot(vector) + + Compute the dot product of two vectors. + + .. method:: __eq__(vector) + + Test whether two vectors are equal. + + .. method:: __mul__(value) + + Multiply the vector by a scalar value and return the resulting vector. + + .. method:: __truediv__(value) + + Divide the vector by a scalar value and return the resulting vector. + + .. method:: norm() + + Return the norm of the vector. + + .. method:: norm2() + + Return the square norm of the vector. + + .. method:: asunit() + + Return the normalized vector, i.e. the vector of same direction but with norm 1. -- 2.20.1 From a1ffefe911e2309673c57f4766567314713d59aa Mon Sep 17 00:00:00 2001 From: Vivien Maisonneuve Date: Mon, 18 Aug 2014 08:24:13 +0200 Subject: [PATCH 13/16] Cleanup in doc/install.rst --- doc/install.rst | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/doc/install.rst b/doc/install.rst index 040029d..635e9c0 100644 --- a/doc/install.rst +++ b/doc/install.rst @@ -18,7 +18,7 @@ For Arch Linux, run:: sudo pacman -S isl Apart from isl, there are two optional dependencies that will maximize the use of LinPy's functions: `SymPy `_ and `matplotlib `_. -Please consult the `SymPy download page `_ and `matplotlib installation instructions `_ to install these libraries. +Please consult the `SymPy download page `_ and `matplotlib installation instructions `_ to install these libraries. pip --- @@ -32,12 +32,6 @@ LinPy can be installed using pip with the command:: sudo pip install linpy -.. _here: http://freshmeat.net/projects/isl/ - -.. _download page: http://sympy.org/en/download.html - -.. _link: http://matplotlib.org/faq/installing_faq.html - .. _source: Source -- 2.20.1 From f422e08c7a7758cd1e084a5cf042f5ddb3701ffc Mon Sep 17 00:00:00 2001 From: Danielle Bolan Date: Mon, 18 Aug 2014 10:10:11 +0200 Subject: [PATCH 14/16] Fix small grammar mistakes in doc --- doc/index.rst | 2 +- doc/install.rst | 2 +- doc/reference.rst | 44 ++++++++++++++++++++++---------------------- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/doc/index.rst b/doc/index.rst index ad985bf..a1809d6 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -3,7 +3,7 @@ Welcome to LinPy’s documentation! ================================= LinPy is a polyhedral library for Python based on `isl `_. -isl (Integer Set Library) is a C library for manipulating sets and relations of integer points bounded by linear constraints. +Integer Set Library (isl) is a C library for manipulating sets and relations of integer points bounded by linear constraints. LinPy is a free software, licensed under the `GPLv3 license `_. Its source code is available `here `_. diff --git a/doc/install.rst b/doc/install.rst index 635e9c0..3a56000 100644 --- a/doc/install.rst +++ b/doc/install.rst @@ -37,7 +37,7 @@ LinPy can be installed using pip with the command:: Source ------ -Alternatively, LinPy can be installed from source. +Alternatively, LinPy can be installed from the source. First, clone the public git repository:: git clone https://scm.cri.ensmp.fr/git/linpy.git diff --git a/doc/reference.rst b/doc/reference.rst index 8184c43..af5cd4d 100644 --- a/doc/reference.rst +++ b/doc/reference.rst @@ -12,7 +12,7 @@ They correspond to mathematical variables. Return a symbol with the name string given in argument. Alternatively, the function :func:`symbols` allows to create several symbols at once. - Symbols are instances of class :class:`LinExpr` and, as such, inherit its functionalities. + Symbols are instances of class :class:`LinExpr` and inherit its functionalities. >>> x = Symbol('x') >>> x @@ -46,12 +46,12 @@ They correspond to mathematical variables. >>> x, y = symbols(['x', 'y']) -Sometimes, you need to have a unique symbol, for example as a temporary one in some calculation, which is going to be substituted for something else at the end anyway. +Sometimes you need to have a unique symbol. For example, you might need a temporary one in some calculation, which is going to be substituted for something else at the end anyway. This is achieved using ``Dummy('x')``. .. class:: Dummy(name=None) - A variation of :class:`Symbol` which are all unique, identified by an internal count index. + A variation of :class:`Symbol` in which all symbols are unique and identified by an internal count index. If a name is not supplied then a string value of the count index will be used. This is useful when a unique, temporary variable is needed and the name of the variable used in the expression is not important. @@ -79,8 +79,8 @@ For example, if ``x`` is a :class:`Symbol`, then ``x + 1`` is an instance of :cl .. class:: LinExpr(coefficients=None, constant=0) LinExpr(string) - Return a linear expression from a dictionary or a sequence that maps symbols to their coefficients, and a constant term. - The coefficients and the constant must be rational numbers. + Return a linear expression from a dictionary or a sequence, that maps symbols to their coefficients, and a constant term. + The coefficients and the constant term must be rational numbers. For example, the linear expression ``x + 2y + 1`` can be constructed using one of the following instructions: @@ -88,7 +88,7 @@ For example, if ``x`` is a :class:`Symbol`, then ``x + 1`` is an instance of :cl >>> LinExpr({x: 1, y: 2}, 1) >>> LinExpr([(x, 1), (y, 2)], 1) - although it may be easier to use overloaded operators: + However, it may be easier to use overloaded operators: >>> x, y = symbols('x y') >>> x + 2*y + 1 @@ -148,11 +148,11 @@ For example, if ``x`` is a :class:`Symbol`, then ``x + 1`` is an instance of :cl .. method:: __mul__(value) - Return the product of the linear expression by a rational. + Return the product of the linear expression as a rational. .. method:: __truediv__(value) - Return the quotient of the linear expression by a rational. + Return the quotient of the linear expression as a rational. .. method:: __eq__(expr) @@ -181,7 +181,7 @@ For example, if ``x`` is a :class:`Symbol`, then ``x + 1`` is an instance of :cl subs(pairs) Substitute the given symbol by an expression and return the resulting expression. - Raise :exc:`TypeError` is the resulting expression is not linear. + Raise :exc:`TypeError` if the resulting expression is not linear. >>> x, y = symbols('x y') >>> e = x + 2*y + 1 @@ -216,14 +216,14 @@ They are implemented by the :class:`Rational` class, that inherits from both :cl .. class:: Rational(numerator, denominator=1) Rational(string) - The first version requires that *numerator* and *denominator* are instances of :class:`numbers.Rational` and returns a new :class:`Rational` instance with value ``numerator/denominator``. - If denominator is ``0``, it raises a :exc:`ZeroDivisionError`. + The first version requires that the *numerator* and *denominator* are instances of :class:`numbers.Rational` and returns a new :class:`Rational` instance with the value ``numerator/denominator``. + If the denominator is ``0``, it raises a :exc:`ZeroDivisionError`. The other version of the constructor expects a string. The usual form for this instance is:: [sign] numerator ['/' denominator] - where the optional ``sign`` may be either '+' or '-' and ``numerator`` and ``denominator`` (if present) are strings of decimal digits. + where the optional ``sign`` may be either '+' or '-' and the ``numerator`` and ``denominator`` (if present) are strings of decimal digits. See the documentation of :class:`fractions.Fraction` for more information and examples. @@ -260,7 +260,7 @@ This space can be unbounded. >>> square2 = Polyhedron('2 <= x <= 4, 2 <= y <= 4') >>> Polyhedron(square | square2) - A polyhedron is a :class:`Domain` instance, and, as such, inherits the functionalities of this class. + A polyhedron is a :class:`Domain` instance, and, therefore, inherits the functionalities of this class. It is also a :class:`GeometricObject` instance. .. attribute:: equalities @@ -476,7 +476,7 @@ Unlike polyhedra, domains allow exact computation of union and complementary ope Comparison and Logic Operators ------------------------------ -The following functions allow to create :class:`Polyhedron` or :class:`Domain` instances by comparison of :class:`LinExpr` instances: +The following functions create :class:`Polyhedron` or :class:`Domain` instances by comparison of :class:`LinExpr` instances: .. function:: Lt(expr1, expr2[, expr3, ...]) @@ -503,15 +503,15 @@ The following functions allow to create :class:`Polyhedron` or :class:`Domain` i Create the polyhedron with constraints ``expr1 > expr2 > expr3 ...``. -The following functions allow to combine :class:`Polyhedron` or :class:`Domain` instances using logic operators: +The following functions combine :class:`Polyhedron` or :class:`Domain` instances using logic operators: .. function:: Or(domain1, domain2[, ...]) - Create the union domain of domains given in arguments. + Create the union domain of the domains given in arguments. .. function:: And(domain1, domain2[, ...]) - Create the intersection domain of domains given in arguments. + Create the intersection domain of the domains given in arguments. .. function:: Not(domain) @@ -545,7 +545,7 @@ Geometric Objects .. class:: Point(coordinates) - Create a point from a dictionnary or a sequence that maps symbols to their coordinates. + Create a point from a dictionary or a sequence that maps the symbols to their coordinates. Coordinates must be rational numbers. For example, the point ``(x: 1, y: 2)`` can be constructed using one of the following instructions: @@ -554,7 +554,7 @@ Geometric Objects >>> p = Point({x: 1, y: 2}) >>> p = Point([(x, 1), (y, 2)]) - :class:`Point` instances are hashable, and should be treated as immutable. + :class:`Point` instances are hashable and should be treated as immutable. A point is a :class:`GeometricObject` instance. @@ -595,7 +595,7 @@ Geometric Objects .. method:: __sub__(point) __sub__(vector) - The first version substract a point from another and return the resulting vector. + The first version substracts a point from another and returns the resulting vector. The second version translates the point by the opposite vector of *vector* and returns the resulting point. .. method:: __eq__(point) @@ -605,10 +605,10 @@ Geometric Objects .. class:: Vector(coordinates) - Create a point from a dictionnary or a sequence that maps symbols to their coordinates, similarly to :meth:`Point`. + Create a point from a dictionary or a sequence that maps the symbols to their coordinates, similar to :meth:`Point`. Coordinates must be rational numbers. - :class:`Vector` instances are hashable, and should be treated as immutable. + :class:`Vector` instances are hashable and should be treated as immutable. .. attribute:: symbols -- 2.20.1 From 7f4790ef28ae6335966de93be23a8be9320d8cde Mon Sep 17 00:00:00 2001 From: Vivien Maisonneuve Date: Mon, 18 Aug 2014 10:22:03 +0200 Subject: [PATCH 15/16] Last-minute changes in the documentation --- doc/reference.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/reference.rst b/doc/reference.rst index af5cd4d..4dcfbdc 100644 --- a/doc/reference.rst +++ b/doc/reference.rst @@ -230,7 +230,7 @@ They are implemented by the :class:`Rational` class, that inherits from both :cl Polyhedra --------- -A *convex polyhedron* (or simply polyhedron) is the space defined by a system of linear equalities and inequalities. +A *convex polyhedron* (or simply "polyhedron") is the space defined by a system of linear equalities and inequalities. This space can be unbounded. .. class:: Polyhedron(equalities, inequalities) @@ -476,7 +476,7 @@ Unlike polyhedra, domains allow exact computation of union and complementary ope Comparison and Logic Operators ------------------------------ -The following functions create :class:`Polyhedron` or :class:`Domain` instances by comparison of :class:`LinExpr` instances: +The following functions create :class:`Polyhedron` or :class:`Domain` instances using the comparisons of two or more :class:`LinExpr` instances: .. function:: Lt(expr1, expr2[, expr3, ...]) -- 2.20.1 From 3f3ec5755dc4b96250b8bd09be9bede967d7203d Mon Sep 17 00:00:00 2001 From: Vivien Maisonneuve Date: Mon, 18 Aug 2014 11:10:15 +0200 Subject: [PATCH 16/16] Fix unitary tests --- linpy/tests/test_domains.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/linpy/tests/test_domains.py b/linpy/tests/test_domains.py index 9e9f6fa..0955a09 100644 --- a/linpy/tests/test_domains.py +++ b/linpy/tests/test_domains.py @@ -53,9 +53,9 @@ class TestDomain(unittest.TestCase): Polyhedron(1) def test_disjoint(self): - self.assertEqual(self.square1.disjoint(), self.disjoint) - self.assertEqual(self.empty.disjoint(), Empty) - self.assertEqual(self.universe.disjoint(), self.universe) + self.assertEqual(self.square1.make_disjoint(), self.disjoint) + self.assertEqual(self.empty.make_disjoint(), Empty) + self.assertEqual(self.universe.make_disjoint(), self.universe) def test_isempty(self): self.assertFalse(self.square1.isempty()) -- 2.20.1