Skip to content

Commit

Permalink
Merge branch 'update-0.3.0' into feature/controller-interface
Browse files Browse the repository at this point in the history
  • Loading branch information
mstoelzle authored Jul 31, 2022
2 parents d62f5b3 + d0cb959 commit 4801f03
Show file tree
Hide file tree
Showing 51 changed files with 154 additions and 417 deletions.
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ poetry-remove:
install:
poetry install

.PHONY: install_examples_dependencies
install_examples_dependencies:
poetry install -E examples
# sadly pip ffmpeg doesnt work, hence we use conda for ffmpeg
conda install -c conda-forge ffmpeg

.PHONY: install_with_new_dependency
install_with_new_dependency:
poetry lock
Expand Down
12 changes: 6 additions & 6 deletions docs/api/damping.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,16 @@ Damping is used to numerically stabilize the simulations.
:nosignatures:

DamperBase
ExponentialDamper
AnalyticalLinearDamper
LaplaceDissipationFilter

Compatibility
~~~~~~~~~~~~~

=============================== ==== ===========
=============================== ==== ===========
Damping/Numerical Dissipation Rod Rigid Body
=============================== ==== ===========
ExponentialDamper ✅ ✅
=============================== ==== ===========
AnalyticalLinearDamper ✅ ✅
LaplaceDissipationFilter ✅ ❌
=============================== ==== ===========

Expand All @@ -34,10 +34,10 @@ Built-in Constraints
.. autoclass:: DamperBase
:inherited-members:
:undoc-members:
:exclude-members:
:exclude-members:
:show-inheritance:

.. autoclass:: ExponentialDamper
.. autoclass:: AnalyticalLinearDamper
:special-members: __init__

.. autoclass:: LaplaceDissipationFilter
Expand Down
54 changes: 27 additions & 27 deletions docs/guide/workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
When using PyElastica, users will setup a simulation in which they define a system of rods, define initial and boundary conditions on the rods, run the simulation, and then post-process the results. Here, we outline a typical template of using PyElastica.

:::{important}
**A note on notation:** Like other FEA packages such as Abaqus, PyElastica does not enforce units. This means that you are required to make sure that all units for your input variables are consistent. When in doubt, SI units are always safe, however, if you have a very small length scale ($\sim$ nm), then you may need to rescale your units to avoid needing prohibitively small time steps and/or roundoff errors.
**A note on notation:** Like other FEA packages such as Abaqus, PyElastica does not enforce units. This means that you are required to make sure that all units for your input variables are consistent. When in doubt, SI units are always safe, however, if you have a very small length scale ($\sim$ nm), then you may need to rescale your units to avoid needing prohibitively small time steps and/or roundoff errors.
:::

<h2>1. Setup Simulation</h2>
Expand All @@ -25,8 +25,8 @@ class SystemSimulator(
Connections, # Enabled to use FixedJoint
CallBacks, # Enabled to use callback
Damping, # Enabled to use damping models on systems.
):
pass
):
pass
```
This simply combines all the wrappers previously imported together. If a wrapper is not needed for the simulation, it does not need to be added here.

Expand All @@ -48,7 +48,7 @@ We adopted a composition and mixin design paradigm in building elastica. The det


<h2>2. Create Rods</h2>
Each rod has a number of physical parameters that need to be defined. These values then need to be assigned to the rod to create the object, and the rod needs to be added to the simulator.
Each rod has a number of physical parameters that need to be defined. These values then need to be assigned to the rod to create the object, and the rod needs to be added to the simulator.

```python
from elastica.rod.cosserat_rod import CosseratRod
Expand Down Expand Up @@ -94,11 +94,11 @@ The number of element (`n_elements`) and `base_length` determines the spatial di

<h2>3. Define Boundary Conditions, Forcings, Damping and Connections</h2>

Now that we have added all our rods to `SystemSimulator`, we
need to apply relevant boundary conditions.
Now that we have added all our rods to `SystemSimulator`, we
need to apply relevant boundary conditions.
See [this page](../api/constraints.rst) for in-depth explanations and documentation.

As a simple example, to fix one end of a rod, we use the `OneEndFixedBC` boundary condition (which we imported in step 1 and apply it to the rod. Here we will be fixing the $0^{\text{th}}$ node as well as the $0^{\text{th}}$ element.
As a simple example, to fix one end of a rod, we use the `OneEndFixedBC` boundary condition (which we imported in step 1 and apply it to the rod. Here we will be fixing the $0^{\text{th}}$ node as well as the $0^{\text{th}}$ element.

```python
from elastica.boundary_conditions import OneEndFixedBC
Expand All @@ -117,33 +117,33 @@ from elastica.external_forces import EndpointForces

#Define 1x3 array of the applied forces
origin_force = np.array([0.0, 0.0, 0.0])
end_force = np.array([-15.0, 0.0, 0.0])
end_force = np.array([-15.0, 0.0, 0.0])
SystemSimulator.add_forcing_to(rod1).using(
EndpointForces, # Traction BC being applied
origin_force, # Force vector applied at first node
end_force, # Force vector applied at last node
ramp_up_time=final_time / 2.0 # Ramp up time
ramp_up_time=final_time / 2.0 # Ramp up time
)
```

Next, if required, in order to numerically stabilize the simulation,
we can apply damping to the rods.
Next, if required, in order to numerically stabilize the simulation,
we can apply damping to the rods.
See [this page](../api/damping.rst) for in-depth explanations and documentation.

```python
from elastica.dissipation import ExponentialDamper
from elastica.dissipation import AnalyticalLinearDamper

nu = 1e-3 # Damping constant of the rod
dt = 1e-5 # Time-step of simulation in seconds

SystemSimulator.dampin(rod1).using(
ExponentialDamper,
AnalyticalLinearDamper,
damping_constant = nu,
time_step = dt,
)

SystemSimulator.dampin(rod2).using(
ExponentialDamper,
AnalyticalLinearDamper,
damping_constant = nu,
time_step = dt,
)
Expand All @@ -154,14 +154,14 @@ One last condition we can define is the connections between rods. See [this page
```python
from elastica.connections import FixedJoint

# Connect rod 1 and rod 2. '_connect_idx' specifies the node number that
# the connection should be applied to. You are specifying the index of a
# list so you can use -1 to access the last node.
# Connect rod 1 and rod 2. '_connect_idx' specifies the node number that
# the connection should be applied to. You are specifying the index of a
# list so you can use -1 to access the last node.
SystemSimulator.connect(
first_rod = rod1,
second_rod = rod2,
first_connect_idx = -1, # Connect to the last node of the first rod.
second_connect_idx = 0 # Connect to first node of the second rod.
first_rod = rod1,
second_rod = rod2,
first_connect_idx = -1, # Connect to the last node of the first rod.
second_connect_idx = 0 # Connect to first node of the second rod.
).using(
FixedJoint, # Type of connection between rods
k = 1e5, # Spring constant of force holding rods together (F = k*x)
Expand All @@ -181,15 +181,15 @@ PyElastica __does not automatically saves__ the simulation result. If you do not
```python
from elastica.callback_functions import CallBackBaseClass

# MyCallBack class is derived from the base call back class.
# MyCallBack class is derived from the base call back class.
class MyCallBack(CallBackBaseClass):
def __init__(self, step_skip: int, callback_params):
CallBackBaseClass.__init__(self)
self.every = step_skip
self.callback_params = callback_params

# This function is called every time step
def make_callback(self, system, time, current_step: int):
def make_callback(self, system, time, current_step: int):
if current_step % self.every == 0:
# Save time, step number, position, orientation and velocity
self.callback_params["time"].append(time)
Expand Down Expand Up @@ -223,7 +223,7 @@ This goes through and collects all the rods and applied conditions, preparing th

<h2>6. Set Timestepper</h2>

With our system now ready to be run, we need to define which time stepping algorithm to use. Currently, we suggest using the position Verlet algorithm. We also need to define how much time we want to simulate as well as either the time step (dt) or the number of total time steps we want to take. Once we have defined these things, we can run the simulation by calling `integrate()`, which will start the simulation.
With our system now ready to be run, we need to define which time stepping algorithm to use. Currently, we suggest using the position Verlet algorithm. We also need to define how much time we want to simulate as well as either the time step (dt) or the number of total time steps we want to take. Once we have defined these things, we can run the simulation by calling `integrate()`, which will start the simulation.

>> We are still actively testing different integration and time-stepping techniques, `PositionVerlet` is the best default at this moment.
Expand All @@ -233,12 +233,12 @@ from elastica.timestepper import integrate

timestepper = PositionVerlet()
final_time = 10 # seconds
total_steps = int(final_time / dt)
total_steps = int(final_time / dt)
integrate(timestepper, SystemSimulator, final_time, total_steps)
```

More documentation on timestepper and integrator is included [here](../api/time_steppers.rst)

<h2>7. Post Process</h2>

Once the simulation ends, it is time to analyze the data. If you defined a callback function, the data you outputted in available there (i.e. `callback_data_rod1`), otherwise you can access the final configuration of your system through your rod objects. For example, if you want the final position of one of your rods, you can get it from `rod1.position_collection[:]`.
Once the simulation ends, it is time to analyze the data. If you defined a callback function, the data you outputted in available there (i.e. `callback_data_rod1`), otherwise you can access the final configuration of your system through your rod objects. For example, if you want the final position of one of your rods, you can get it from `rod1.position_collection[:]`.
30 changes: 14 additions & 16 deletions elastica/dissipation.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"""
__all__ = [
"DamperBase",
"ExponentialDamper",
"AnalyticalLinearDamper",
"LaplaceDissipationFilter",
]
from abc import ABC, abstractmethod
Expand Down Expand Up @@ -72,9 +72,9 @@ def dampen_rates(self, rod: SystemType, time: float):
pass


class ExponentialDamper(DamperBase):
class AnalyticalLinearDamper(DamperBase):
"""
Exponential damper class. This class corresponds to the analytical version of
Analytical linear damper class. This class corresponds to the analytical version of
a linear damper, and uses the following equations to damp translational and
rotational velocities:
Expand All @@ -86,10 +86,10 @@ class ExponentialDamper(DamperBase):
Examples
--------
How to set exponential damper for rod or rigid body:
How to set analytical linear damper for rod or rigid body:
>>> simulator.dampen(rod).using(
... ExponentialDamper,
... AnalyticalLinearDamper,
... damping_constant=0.1,
... time_step = 1E-4, # Simulation time-step
... )
Expand All @@ -99,7 +99,7 @@ class ExponentialDamper(DamperBase):
Since this class analytically treats the damping term, it is unconditionally stable
from a timestep perspective, i.e. the presence of damping does not impose any additional
restriction on the simulation timestep size. This implies that when using
Exponential Damper, one can set `damping_constant` as high as possible, without worrying
AnalyticalLinearDamper, one can set `damping_constant` as high as possible, without worrying
about the simulation becoming unstable. This now leads to a streamlined procedure
for tuning the `damping_constant`:
Expand All @@ -109,37 +109,35 @@ class ExponentialDamper(DamperBase):
Attributes
----------
translational_exponential_damping_coefficient: numpy.ndarray
translational_damping_coefficient: numpy.ndarray
1D array containing data with 'float' type.
Damping coefficient acting on translational velocity.
rotational_exponential_damping_coefficient : numpy.ndarray
rotational_damping_coefficient : numpy.ndarray
1D array containing data with 'float' type.
Damping coefficient acting on rotational velocity.
"""

def __init__(self, damping_constant, time_step, **kwargs):
"""
Exponential damper initializer
Analytical linear damper initializer
Parameters
----------
damping_constant : float
Damping constant for the exponential dampers.
Damping constant for the analytical linear damper.
time_step : float
Time-step of simulation
"""
super().__init__(**kwargs)
# Compute the damping coefficient for translational velocity
nodal_mass = self._system.mass
self.translational_exponential_damping_coefficient = np.exp(
-damping_constant * time_step
)
self.translational_damping_coefficient = np.exp(-damping_constant * time_step)

# Compute the damping coefficient for exponential velocity
element_mass = 0.5 * (nodal_mass[1:] + nodal_mass[:-1])
element_mass[0] += 0.5 * nodal_mass[0]
element_mass[-1] += 0.5 * nodal_mass[-1]
self.rotational_exponential_damping_coefficient = np.exp(
self.rotational_damping_coefficient = np.exp(
-damping_constant
* time_step
* element_mass
Expand All @@ -148,11 +146,11 @@ def __init__(self, damping_constant, time_step, **kwargs):

def dampen_rates(self, rod: SystemType, time: float):
rod.velocity_collection[:] = (
rod.velocity_collection * self.translational_exponential_damping_coefficient
rod.velocity_collection * self.translational_damping_coefficient
)

rod.omega_collection[:] = rod.omega_collection * np.power(
self.rotational_exponential_damping_coefficient, rod.dilatation
self.rotational_damping_coefficient, rod.dilatation
)


Expand Down
6 changes: 3 additions & 3 deletions elastica/rod/factory_function.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,9 +195,9 @@ def allocate(
# Remove the option to set internal nu inside, beyond v0.4.0
"The option to set damping coefficient (nu) for the rod during rod "
"initialisation is now deprecated. Instead, for adding damping to rods, "
"please derive your simulation class from the add-on Damping mixin class."
"For reference see the class elastica.dissipation.ExponentialDamper(),"
"and for usage check examples/axial_stretching.py"
"please derive your simulation class from the add-on Damping mixin class. "
"For reference see the class elastica.dissipation.AnalyticalLinearDamper(), "
"and for usage check examples/axial_stretching.py "
"The option to set damping coefficient (nu) during rod construction "
"will be removed in the future (v0.3.1)."
)
Expand Down
9 changes: 1 addition & 8 deletions examples/AxialStretchingCase/axial_stretching.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,6 @@
isort:skip_file
"""
# FIXME without appending sys.path make it more generic
import sys

sys.path.append("../../") # isort:skip

# from collections import defaultdict

import numpy as np
from matplotlib import pyplot as plt

Expand Down Expand Up @@ -96,7 +89,7 @@ class StretchingBeamSimulator(
dt = 0.1 * dl
damping_constant = 0.1
stretch_sim.dampen(stretchable_rod).using(
ExponentialDamper,
AnalyticalLinearDamper,
damping_constant=damping_constant,
time_step=dt,
)
Expand Down
14 changes: 7 additions & 7 deletions examples/Binder/1_Timoshenko_Beam.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
"from elastica.rod.cosserat_rod import CosseratRod\n",
"\n",
"# Import Damping Class\n",
"from elastica.dissipation import ExponentialDamper\n",
"from elastica.dissipation import AnalyticalLinearDamper\n",
"\n",
"# Import Boundary Condition Classes\n",
"from elastica.boundary_conditions import OneEndFixedRod, FreeRod\n",
Expand Down Expand Up @@ -180,7 +180,7 @@
},
"source": [
"## Adding Damping\n",
"With the rod added to the simulator, we can add damping to the rod. We do this using the `.dampen()` option and the `ExponentialDamper`. We are modifying `timoshenko_sim` simulator to `dampen` the `shearable_rod` object using `ExponentialDamper` type of dissipation (damping) model.\n",
"With the rod added to the simulator, we can add damping to the rod. We do this using the `.dampen()` option and the `AnalyticalLinearDamper`. We are modifying `timoshenko_sim` simulator to `dampen` the `shearable_rod` object using `AnalyticalLinearDamper` type of dissipation (damping) model.\n",
"\n",
"We also need to define `damping_constant` and simulation `time_step` and pass in `.using()` method."
]
Expand All @@ -199,7 +199,7 @@
"dl = base_length / n_elem\n",
"dt = 0.01 * dl\n",
"timoshenko_sim.dampen(shearable_rod).using(\n",
" ExponentialDamper,\n",
" AnalyticalLinearDamper,\n",
" damping_constant=nu,\n",
" time_step=dt,\n",
")"
Expand Down Expand Up @@ -319,7 +319,7 @@
"timoshenko_sim.append(unshearable_rod)\n",
"\n",
"timoshenko_sim.dampen(unshearable_rod).using(\n",
" ExponentialDamper,\n",
" AnalyticalLinearDamper,\n",
" damping_constant=nu,\n",
" time_step=dt,\n",
")\n",
Expand Down Expand Up @@ -588,7 +588,7 @@
")\n",
"dynamic_update_sim.append(shearable_rod_new)\n",
"dynamic_update_sim.dampen(shearable_rod_new).using(\n",
" ExponentialDamper,\n",
" AnalyticalLinearDamper,\n",
" damping_constant=nu,\n",
" time_step=dt,\n",
")\n",
Expand All @@ -614,7 +614,7 @@
")\n",
"dynamic_update_sim.append(unshearable_rod_new)\n",
"dynamic_update_sim.dampen(unshearable_rod_new).using(\n",
" ExponentialDamper,\n",
" AnalyticalLinearDamper,\n",
" damping_constant=nu,\n",
" time_step=dt,\n",
")\n",
Expand Down Expand Up @@ -766,4 +766,4 @@
},
"nbformat": 4,
"nbformat_minor": 2
}
}
Loading

0 comments on commit 4801f03

Please sign in to comment.