Examples
========

Minimal Snelson 3-Prism
-----------------------

The default constructor of :class:`tnsgrt.prism.Prism`

.. code:: python

   from tnsgrt.prism import Prism
   s = Prism()

produces a *minimal* Snelson 3-Prism, with 3 bars, 3 top and 3 bottom
strings, 3 vertical strings, and a *twist angle* of 30 degrees, as in:

.. image:: /images/snelson2.png
  :scale: 50%

which is generated by the code:

.. code:: python

   import numpy as np
   from matplotlib import pyplot as plt
   from tnsgrt.plotter.matplotlib import MatplotlibPlotter
   %matplotlib widget

   plotter = MatplotlibPlotter()
   plotter.plot(s)
   fig, ax = plotter.get_handles()
   ax.view_init(elev=20, azim=45)
   ax.axis('off')
   plt.show()

The *twist angle* is the angle measured between the rotations of the top
and bottom triangles, which can be better visualized from a different
view point

.. image:: /images/snelson3.png
  :scale: 50%

as produced by the following code:

.. code:: python

   ax.view_init(elev=90, azim=-90)
   ax.axis('off')
   ax.plot([0, 1.1], [0, 0], 'r--')
   ax.plot([0, 1.1*np.cos(np.pi/6)], [0, 1.1*np.sin(np.pi/6)], [1, 1], 'r--')
   ax.text(1.1*np.cos(np.pi/12), 1.1*np.sin(np.pi/12), 0, 'alpha')
   plt.show()

For a symmetric prism, the 30 degrees twist angle is the only possible
equilibrium:

.. code:: python

   s.equilibrium()

which imparts bars and vertical strings the same magnitude of force
coefficient:

.. code:: python

   s.member_properties[['lambda_']]

.. csv-table::
   :file: tab6.csv
   :header-rows: 1

Minimal Snelson Prisms have at least one soft mode, which can be
confirmed by calculating the model stiffness with rigid body constraints

.. code:: python

   s.update_member_properties(['stiffness'])
   stiffness, _, _ = s.stiffness(apply_rigid_body_constraint=True)

and evaluating its eigenvalues

.. code:: python

   d, v = stiffness.eigs()
   d

.. code::

   2.77128123e+00
   4.68096753e+06
   4.68096753e+06
   1.23281719e+07
   1.23281719e+07
   2.45882799e+07
   2.72069922e+07
   2.89745460e+07
   2.89745460e+07
   6.68906843e+07
   6.68906843e+07
   8.82860836e+07

The corresponding eigenvector is plotted below:

.. image:: /images/snelson4.png
  :scale: 50%

as produced by the following code:

.. code:: python

   V = v[:,0].reshape((3, 6), order='F')

   plotter = MatplotlibPlotter()
   plotter.plot(s)
   fig, ax = plotter.get_handles()
   ax.quiver(s.nodes[0,:], s.nodes[1,:], s.nodes[2,:], V[0,:], V[1,:], V[2,:], arrow_length_ratio=.2, color='g')
   ax.view_init(10,20)
   ax.axis('off')
   plt.show()

The plot suggests that the soft mode is associated with a “corkscrew” like
rotational motion of the structure.

The presence of this soft mode means that one should expect large
displacements in response to compressive type forces such as:

.. code:: python

   f = 0.25*np.array([[0,0,1],[0,0,1],[0,0,1],[0,0,-1],[0,0,-1],[0,0,-1]]).transpose()

The corresponding approximate displacement can be obtained as:

.. code:: python

   x = stiffness.displacements(f)
   x

which are

.. code::

   +3.70368807e-09  3.12499981e-02 -3.12500018e-02 -1.80421927e-02 -1.80421991e-02  3.60843918e-02
   -3.60843918e-02  1.80421991e-02  1.80421927e-02  3.12500018e-02 -3.12499981e-02 -3.70368809e-09
   +1.80422060e-02  1.80422060e-02  1.80422060e-02 -1.80422060e-02 -1.80422060e-02 -1.80422060e-02

Comparing the magnitude of the force with the magnitude of the
displacement in the direction of the force

.. code:: python

   np.sum(f * x, axis=0)/np.linalg.norm(x, axis=0)**2

one obtains

.. code::

   2.77128222  2.77128222  2.77128222  2.77128222  2.77128222  2.77128222

from which we can observe the impact of the soft mode on the structure response.
The displacements, scaled 10 times, are visualized along with the
applied forces in the figure:

.. image:: /images/snelson5.png
  :scale: 50%

as produced by the following code:

.. code:: python

   X = f
   Y = 1e1*x

   plotter = MatplotlibPlotter()
   plotter.plot(s)
   fig, ax = plotter.get_handles()
   ax.quiver(s.nodes[0,:], s.nodes[1,:], s.nodes[2,:], X[0,:], X[1,:], X[2,:], arrow_length_ratio=.2, color='g')
   ax.quiver(s.nodes[0,:], s.nodes[1,:], s.nodes[2,:], Y[0,:], Y[1,:], Y[2,:], arrow_length_ratio=.2, color='y')
   ax.view_init(elev=10, azim=20)
   ax.axis('off')
   plt.show()

Non-minimal Snelson 3-Prism
---------------------------

With the addition of *diagonal* strings, Snelson 3-prisms can be
constructed that are in equilibrium at twist angles other than 30 degrees. The
following syntax

.. code:: python

   s = Prism(alpha=np.pi/5, diagonal=True)

produces one such prism. The indices of the additional diagonal strings
can be obtained by searching for the tag ``‘diagonal’``:

.. code:: python

   diagonals = s.get_members_by_tags('diagonal')

We can use these indices to set a different color for the diagonal strings

.. code:: python

   from tnsgrt import utils
   s.set_member_properties(diagonals, 'facecolor', utils.Colors.GREEN.value, wrap=True)

The resulting prism is visualized below:

.. image:: /images/snelson6.png
  :scale: 50%

as produced by the code:

.. code:: python

   plotter = MatplotlibPlotter()
   plotter.plot(s)
   fig, ax = plotter.get_handles()
   ax.view_init(elev=20, azim=45)
   ax.axis('off')
   plt.show()

Note the presence of the additional diagonal strings in green.

Equilibrium of the prism and the member stiffness can be calculated as
before:

.. code:: python

   s.equilibrium()
   s.update_member_properties(['stiffness'])

Next we calculate the model stiffness with rigid body constraints and
its eigenvalues

.. code:: python

   stiffness, _, _ = s.stiffness(apply_rigid_body_constraint=True)
   d, v = stiffness.eigs()
   d

.. code::

   8155119.28425745
   8155119.32734769
   10724386.65730408
   22597331.51554979
   22597331.53389546
   23044828.36153938
   27206992.10546769
   31904308.56966601
   31904308.58628615
   67275457.78066988
   67275457.82203464
   96162998.90710124

Note that there are no soft modes and the associated displacement in
response to a compressive force is

.. code:: python

   x = stiffness.displacements(f)
   x

.. code::

   +4.67852301e-09  2.69664424e-09 -7.37516722e-09  3.67055126e-10 -6.63920203e-09  6.27214688e-09
   -5.81496304e-09  6.95920134e-09 -1.14423828e-09  7.45437076e-09 -3.40930636e-09 -4.04506442e-09
   +1.20433838e-08  1.20433839e-08  1.20433839e-08 -1.20433839e-08 -1.20433839e-08 -1.20433838e-08

The corresponding stiffness in the direction of the applied force is

.. code:: python

   np.sum(f * x, axis=0)/np.linalg.norm(x, axis=0)**2

.. code::

   14998326.01216395  14998325.9409232  14998325.99099865  14998325.98357238  14998325.94372185  14998326.01679158

which is orders of magnitude higher than the displacement of the same
minimal version of the prism, which was soft.

The displacements, scaled :math:`10^7` times, are visualized along with
the applied forces in the figure:

.. image:: /images/snelson7.png
  :scale: 50%

generated by the code:

.. code:: python

   X = f
   Y = 2e7*x

   plotter = MatplotlibPlotter()
   plotter.plot(s)
   fig, ax = plotter.get_handles()
   ax.quiver(s.nodes[0,:], s.nodes[1,:], s.nodes[2,:], X[0,:], X[1,:], X[2,:], arrow_length_ratio=.2, color='g')
   ax.quiver(s.nodes[0,:], s.nodes[1,:], s.nodes[2,:], Y[0,:], Y[1,:], Y[2,:], arrow_length_ratio=.2, color='y')
   ax.view_init(elev=20, azim=45)
   ax.axis('off')
   plt.show()
