"""
This is a module for providing common types of frameworks.
"""
import sympy as sp
import pyrigi._input_check as _input_check
import pyrigi.graphDB as graphs
from pyrigi.framework import Framework
[docs]
def Cycle(n: int, dim: int = 2) -> Framework:
"""
Return ``dim``-dimensional framework of the ``n``-cycle.
The resulting realization depends on the dimension ``dim``
and on the number ``n``:
- If ``n-1<=dim``, then the cycle is realized as the vertices of the
`dim`-dimensional simplex.
- For ``dim=1``, the cycle is injectively realized on the 1-dimensional
linear space generated by the vector ``[1,0,...,0]``.
- If ``dim=2``, then the cycle is realized as the vertices of a regular polygon.
"""
_input_check.integrality_and_range(n, "number of vertices n", 3)
_input_check.integrality_and_range(dim, "dimension d", 1)
if n - 1 <= dim:
return Framework.Simplicial(graphs.Cycle(n), dim)
elif dim == 1:
return Framework.Collinear(graphs.Cycle(n), 1)
elif dim == 2:
return Framework.Circular(graphs.Cycle(n))
raise ValueError(
"The number of vertices n has to be at most d+1, or d must be 1 or 2 "
f"(now (d, n) = ({dim}, {n})."
)
[docs]
def Square() -> Framework:
"""Return the 4-cycle with square realization in the plane."""
return Framework(graphs.Cycle(4), {0: [0, 0], 1: [1, 0], 2: [1, 1], 3: [0, 1]})
[docs]
def Diamond() -> Framework:
"""Return the diamond with square realization in the plane."""
return Framework(graphs.Diamond(), {0: [0, 0], 1: [1, 0], 2: [1, 1], 3: [0, 1]})
[docs]
def Cube() -> Framework:
r"""Return the regular cube in $\RR^3$."""
F = Framework(
graphs.CubeWithDiagonal(),
{
0: [0, 0, 0],
1: [1, 0, 0],
2: [1, 1, 0],
3: [0, 1, 0],
4: [0, 0, 1],
5: [1, 0, 1],
6: [1, 1, 1],
7: [0, 1, 1],
},
)
F.delete_edge([0, 6])
return F
[docs]
def Octahedron(realization: str = "regular") -> Framework:
r"""
Return a framework of the octahedron in $\RR^3$.
Parameters
----------
realization:
If ``"regular"``, a realization of the regular octahedron in $\RR^3$
is returned.
If ``"Bricard_line"``, a flexible Bricard's octahedron
line-symmetric w.r.t. z-axis is returned.
If ``"Bricard_plane"``, a flexible Bricard's octahedron
plane-symmetric w.r.t. yz-plane is returned.
"""
if realization == "regular":
return Framework(
graphs.Octahedral(),
{
0: [0, 0, "-sqrt(2)"],
1: [0, 0, "sqrt(2)"],
2: [-1, -1, 0],
3: [1, 1, 0],
4: [1, -1, 0],
5: [-1, 1, 0],
},
)
elif realization == "Bricard_line":
a = 1
b, c, d = 1, 2, 1
e, f, g = 2, -1, -1
# We define a realization symmetric w.r.t. z-axis.
return Framework(
graphs.Octahedral(),
{
0: [a, 0, 0],
1: [-a, 0, 0],
2: [b, c, d],
3: [-b, -c, d],
4: [e, f, g],
5: [-e, -f, g],
},
)
elif realization == "Bricard_plane":
a, b = 1, 1
c, d = 1, -2
e = 1
f, g = 1, 1
# We define a realization symmetric w.r.t. yz-plane, which contains 0 and 1.
return Framework(
graphs.Octahedral(),
{
0: [0, a, b],
1: [0, c, d],
2: [e, 0, 0],
3: [-e, 0, 0],
4: [f, g, 0],
5: [-f, g, 0],
},
)
raise ValueError(f"The parameter `realization` cannot be {realization}.")
[docs]
def Icosahedron() -> Framework:
r"""Return the regular icosahedron in $\RR^3$."""
phi = sp.sympify("(1+sqrt(5))/2")
F = Framework(
graphs.Icosahedral(),
{
0: (phi, 1, 0),
1: (1, 0, phi),
2: (-1, 0, phi),
9: (-phi, 1, 0),
4: (0, -phi, -1),
5: (phi, -1, 0),
6: (0, -phi, 1),
7: (0, phi, -1),
8: (0, phi, 1),
3: (-phi, -1, 0),
10: (-1, 0, -phi),
11: (1, 0, -phi),
},
)
return F
[docs]
def Dodecahedron() -> Framework:
r"""Return the regular dodecahedron in $\RR^3$."""
phi = sp.sympify("(1+sqrt(5))/2")
F = Framework(
graphs.Dodecahedral(),
{
0: (1, 1, 1),
1: (-1, 1, 1),
2: (1, -1, 1),
3: (1, 1, -1),
4: (-1, -1, 1),
5: (-1, 1, -1),
6: (1, -1, -1),
7: (-1, -1, -1),
8: (0, phi, 1 / phi),
9: (0, phi, -1 / phi),
10: (0, -phi, 1 / phi),
11: (0, -phi, -1 / phi),
12: (1 / phi, 0, phi),
13: (-1 / phi, 0, phi),
14: (1 / phi, 0, -phi),
15: (-1 / phi, 0, -phi),
16: (phi, 1 / phi, 0),
17: (phi, -1 / phi, 0),
18: (-phi, 1 / phi, 0),
19: (-phi, -1 / phi, 0),
},
)
return F
[docs]
def K33plusEdge() -> Framework:
r"""
Return the complete bipartite graph on 3+3 vertices plus an edge realized in $\RR^2$.
"""
return Framework(
graphs.K33plusEdge(),
{0: [0, 0], 1: [0, 1], 2: [0, 2], 3: [1, 0], 4: [1, 1], 5: [1, 2]},
)
[docs]
def Complete(n: int, dim: int = 2) -> Framework:
"""
Return ``dim``-dimensional framework of the complete graph on ``n`` vertices.
The resulting realization depends on the dimension ``dim``
and on the number ``n``:
- If ``n-1<=dim``, then the complete graph is realized on the vertices of the
``dim``-dimensional simplex. The complete graph is either given by the
1-skeleton of the simplex itself when ``dim+1==n`` or else as the 1-skeleton
of a facet.
- For ``dim=1``, the complete graph is injectively realized on the
1-dimensional linear space generated by the vector ``[1,0,...,0]``.
- If ``dim=2``, then the complete graph is realized as the vertices
of a regular polygon.
"""
_input_check.integrality_and_range(n, "number of vertices n", 1)
_input_check.integrality_and_range(dim, "dimension d", 1)
if n - 1 <= dim:
return Framework.Simplicial(graphs.Complete(n), dim)
elif dim == 1:
return Framework.Collinear(graphs.Complete(n), 1)
elif dim == 2:
return Framework.Circular(graphs.Complete(n))
raise ValueError(
"The number of vertices n has to be at most d+1, or d must be 1 or 2 "
f"(now (d, n) = ({dim}, {n})."
)
[docs]
def Path(n: int, dim: int = 2) -> Framework:
"""
Return ``dim``-dimensional framework of the path graph on ``n`` vertices.
The resulting realization depends on the dimension ``dim`` and ``n``:
- If ``n-1<=dim``, then the path is realized as the vertices of the
`dim`-dimensional simplex.
- For ``dim=1``, the path is injectively realized on the 1-dimensional linear space
generated by the vector ``[1,0,...,0]``.
- If ``dim=2``, then the path is realized as the vertices of a regular polygon.
"""
_input_check.integrality_and_range(n, "number of vertices n", 2)
_input_check.integrality_and_range(dim, "dimension d", 1)
if n - 1 <= dim:
return Framework.Simplicial(graphs.Path(n), dim)
elif dim == 1:
return Framework.Collinear(graphs.Path(n), 1)
elif dim == 2:
return Framework.Circular(graphs.Path(n))
raise ValueError(
"The number of vertices n has to be at most d+1, or d must be 1 or 2 "
f"(now (d, n) = ({dim}, {n})."
)
[docs]
def ThreePrism(realization: str = None) -> Framework:
"""
Return a framework of the 3-prism graph in the plane.
Parameters
----------
realization:
If ``"parallel"``, a realization with the three edges that are not
in any 3-cycle being parallel is returned.
If ``"flexible"``, a continuously flexible realization is returned.
Otherwise (default), a general realization is returned.
"""
if realization == "parallel":
return Framework(
graphs.ThreePrism(),
{0: (0, 0), 1: (2, 0), 2: (1, 2), 3: (0, 6), 4: (2, 6), 5: (1, 4)},
)
if realization == "flexible":
return Framework(
graphs.ThreePrism(),
{0: (0, 0), 1: (2, 0), 2: (1, 2), 3: (0, 4), 4: (2, 4), 5: (1, 6)},
)
return Framework(
graphs.ThreePrism(),
{0: (0, 0), 1: (3, 0), 2: (2, 1), 3: (0, 4), 4: (2, 4), 5: (1, 3)},
)
[docs]
def ThreePrismPlusEdge() -> Framework:
"""Return a framework of the 3-prism graph with one extra edge in the plane."""
G = ThreePrism()
G.add_edge([0, 5])
return G
[docs]
def CompleteBipartite(n1: int, n2: int, realization: str = None) -> Framework:
"""
Return a complete bipartite framework on ``n1+n2`` vertices in the plane.
Parameters
----------
n1,n2:
The sizes of the two parts.
realization:
If ``"dixonI"``, a realization with one part on the x-axis and
the other on the y-axis is returned.
Otherwise (default), a "general" realization is returned.
Suggested Improvements
----------------------
Implement realization in higher dimensions.
"""
_input_check.integrality_and_range(n1, "size n1", 1)
_input_check.integrality_and_range(n2, "size n2", 1)
if realization == "dixonI":
return Framework(
graphs.CompleteBipartite(n1, n2),
{i: [0, (i + 1) * (-1) ** i] for i in range(n1)}
| {i: [(i - n1 + 1) * (-1) ** i, 0] for i in range(n1, n1 + n2)},
)
elif realization == "collinear":
return Framework(
graphs.CompleteBipartite(n1, n2), {i: [i, 0] for i in range(n1 + n2)}
)
return Framework(
graphs.CompleteBipartite(n1, n2),
{
i: [
sp.cos(i * sp.pi / max([1, n1 - 1])),
sp.sin(i * sp.pi / max([1, n1 - 1])),
]
for i in range(n1)
}
| {
i: [
1 + 2 * sp.cos((i - n1) * sp.pi / max([1, n2 - 1])),
3 + 2 * sp.sin((i - n1) * sp.pi / max([1, n2 - 1])),
]
for i in range(n1, n1 + n2)
},
)
[docs]
def Frustum(n: int) -> Framework:
"""
Return the $n$-Frustum with ``2n`` vertices in dimension 2.
Definitions
-----------
* :prf:ref:`n-Frustum <def-n-frustum>`
"""
if n <= 2 or not isinstance(n, int):
raise ValueError("`n` needs to be at least 3 but is {n}")
realization = {
j: (sp.cos(2 * j * sp.pi / n), sp.sin(2 * j * sp.pi / n)) for j in range(0, n)
}
realization.update(
{
(j + n): (2 * sp.cos(2 * j * sp.pi / n), 2 * sp.sin(2 * j * sp.pi / n))
for j in range(0, n)
}
)
F = Framework(graphs.Frustum(n), realization)
return F
[docs]
def CnSymmetricFourRegular(n: int = 8) -> Framework:
"""
Return a $C_n$-symmetric framework in the plane.
Definitions
-----------
* :prf:ref:`Example with a free group action <def-Cn-symmetric>`
"""
if not n % 2 == 0 or n < 8:
raise ValueError(
"To generate this framework, the cyclic group "
+ "must have an even order of at least 8!"
)
return Framework(
graphs.CnSymmetricFourRegular(n),
{
i: [
sp.cos(2 * i * sp.pi / n),
sp.sin(2 * i * sp.pi / n),
]
for i in range(n)
},
)
[docs]
def CnSymmetricWithFixedVertex(n: int = 8) -> Framework:
"""
Return a $C_n$-symmetric framework with a fixed vertex in the plane.
The value ``n`` must be even and at least 8.
The underlying graph of the returned framework satisfies the
expected symmetry-adapted Laman count for rotation but the
framework is infinitesimally flexible.
Definitions
-----------
* :prf:ref:`Example with joint at origin <def-Cn-symmetric-joint-at-origin>`
"""
if not n % 2 == 0 or n < 8:
raise ValueError(
"To generate this framework, the cyclic group "
+ "must have an even order of at least 8!"
)
return Framework(
graphs.CnSymmetricWithFixedVertex(n),
{
i: [
sp.cos(2 * i * sp.pi / n),
sp.sin(2 * i * sp.pi / n),
]
for i in range(n)
}
| {
i
+ n: [
sp.Rational(9, 5) * sp.cos((2 * i) * sp.pi / n)
- sp.sin((2 * i) * sp.pi / n),
sp.Rational(9, 5) * sp.sin((2 * i) * sp.pi / n)
+ sp.cos((2 * i) * sp.pi / n),
]
for i in range(n)
}
| {2 * n: (0, 0)},
)
[docs]
def SecondOrderRigid() -> Framework:
"""
Return an example by Connelly.
This is an example of a (non-injective) 3-dimensional framework with a
two-dimensional space of flexes and stresses. It appears in
:cite:p:`ConnellyWhiteley1996`. This framework is
second-order rigid but not prestress stable.
"""
F = Framework.from_points(
[(0, 0, 0), (0, 1, 0), (0, 0, 1), (1, 0, 0), (1, 1, 0), (0, 1, 0), (1, 0, 0)]
)
F.add_edges(
[
(0, 1),
(0, 2),
(0, 3),
(0, 5),
(0, 6),
(1, 2),
(1, 4),
(1, 6),
(2, 3),
(2, 4),
(3, 4),
(3, 5),
(4, 5),
(4, 6),
(5, 6),
]
)
return F
[docs]
def Wheel(n: int) -> Framework:
"""
Create the wheel framework on ``n+1`` vertices.
"""
_input_check.integrality_and_range(n + 1, "number of vertices n+1", min_val=4)
G = graphs.Wheel(n)
return Framework(G, Cycle(n).realization(as_points=True) | {n: [0, 0]})