Closed AkashVardhan7 closed 10 months ago
Hi @AkashVardhan7 ,
In elastica we have an __init__.py
file which is used for importing different functions/classes, if you take a look at it you can see that we are importing all the classes in external_forces.py
like this:
from elastica.external_forces import ( NoForces, EndpointForces, GravityForces, UniformForces, UniformTorques, MuscleTorques, EndpointForcesSinusoidal, )
if you want use the class you just created the way you are doing it now, you have to add it there too. Alternatively, you can put your class in the simulation file with all the necessary imports and use it without the ea.
. One last thing, in order for this class to work it must also have the method apply_torques
so it can be called in the backend, right now you have it called apply__lifting_torques
which would not work.
Let me know if you need more help!
I modified the init file to have the LiftingTorques in from elastica.external_forces import(),. and modified the def apply_torques(self, rod: RodType, time: np.float64 = 0.0): as you suggested in the external_forces.py... the error I get now has to di with the forcing.py... Here are the modified code snippets and the error message...
from elastica.external_forces import ( NoForces, EndpointForces, GravityForces, UniformForces, UniformTorques, MuscleTorques, LiftingTorques, EndpointForcesSinusoidal, )
def apply_torques(self, rod: RodType, time: np.float64 = 0.0): self.compute_lifting_torques( time, self.my_spline, self.s, self.angular_frequency, self.wave_number, self.Amplitude, self.lamBDA, self.phase_shift, self.ramp_up_time, self.direction, rod.director_collection, rod.external_torques, ) def apply_torques(self, rod: RodType, time: np.float64 = 0.0): self.compute_lifting_torques( time, self.my_spline, self.s, self.angular_frequency, self.wave_number, self.Amplitude, self.lamBDA, self.phase_shift, self.ramp_up_time, self.direction, rod.director_collection, rod.external_torques, )
/Users/avardhan7/Documents/OPEN_AI/pyElastica/snake_diffraction/snake_diffraction.py Traceback (most recent call last): File "C:\Users\avardhan7.conda\envs\pyELastica\lib\site-packages\elastica\modules\forcing.py", line 171, in call return self._forcing_cls(*self._args, **self._kwargs) TypeError: NoForces.init() got an unexpected keyword argument 'base_length'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "c:\Users\avardhan7\Documents\OPEN_AI\pyElastica\snake_diffraction\snake_diffraction.py", line 379, in
snake_diffraction_sim.finalize()
File "C:\Users\avardhan7.conda\envs\pyELastica\lib\site-packages\elastica\modules\base_system.py", line 163, in finalize
finalize()
File "C:\Users\avardhan7.conda\envs\pyELastica\lib\site-packages\elastica\modules\forcing.py", line 59, in _finalize_forcing self._ext_forces_torques[:] = [
File "C:\Users\avardhan7.conda\envs\pyELastica\lib\site-packages\elastica\modules\forcing.py", line 60, in
(ext_force_torque.id(), ext_force_torque())
File "C:\Users\avardhan7.conda\envs\pyELastica\lib\site-packages\elastica\modules\forcing.py", line 173, in call
raise TypeError(
TypeError: Unable to construct forcing class.\nDid you provide all necessary force properties?
PS C:\Users\avardhan7\Documents\OPEN_AI\pyElastica\snake_diffraction>
It looks like the error is that you are passing base_length to a forcing class that does not take base_length, did you make any other changes to your code other than I told you about?
I didn't. The only changes I made are the following.
I am pasting this class again., which I modified from the class MuscleTorques...base_length is passed as an input to definit and then in the main simulation it is called by the LiftingTorques class by passing the same base_length value that is passed to the class MuscleTorques. class LiftingTorques(NoForces): """ This class applies Lifting torques along the body. The applied muscle torques are treated as applied external forces. This class can apply muscle torques as a traveling wave with a beta spline or only as a traveling wave. For implementation details refer to Gazzola et. al. RSoS. (2018).
Attributes
----------
direction: numpy.ndarray
2D (dim, 1) array containing data with 'float' type. Muscle torque direction.
angular_frequency: float
Angular frequency of traveling wave.
wave_number: float
Wave number of traveling wave.
phase_shift: float
Phase shift of traveling wave.
ramp_up_time: float
Applied muscle torques are ramped up until ramp up time.
my_spline: numpy.ndarray
1D (blocksize) array containing data with 'float' type. Generated spline.
"""
def init( self, base_length, b_coeff, period, wave_number, Amplitude, lamBDA, phase_shift, direction, rest_lengths, ramp_up_time, with_spline=False, ): """
Parameters
----------
base_length: float
Rest length of the rod-like object.
b_coeff: nump.ndarray
1D array containing data with 'float' type.
Beta coefficients for beta-spline.
period: float
Period of traveling wave.
wave_number: float
Wave number of traveling wave.
Amplitude : flot
lamBDA : float
phase_shift: float
Phase shift of traveling wave.
direction: numpy.ndarray
1D (dim) array containing data with 'float' type. Muscle torque direction.
ramp_up_time: float
Applied muscle torques are ramped up until ramp up time.
with_spline: boolean
Option to use beta-spline.
"""
super(LiftingTorques, self).__init__()
self.direction = direction # Direction torque applied
self.angular_frequency = 2.0 * np.pi / period
self.wave_number = wave_number
self.phase_shift = phase_shift
assert ramp_up_time > 0.0
self.ramp_up_time = ramp_up_time
# s is the position of nodes on the rod, we go from node=1 to node=nelem-1, because there is no
# torques applied by first and last node on elements. Reason is that we cannot apply torque in an
# infinitesimal segment at the beginning and end of rod, because there is no additional element
# (at element=-1 or element=n_elem+1) to provide internal torques to cancel out an external
# torque. This coupled with the requirement that the sum of all muscle torques has
# to be zero results in this condition.
self.s = np.cumsum(rest_lengths)
self.s /= self.s[-1]
if with_spline:
assert b_coeff.size != 0, "Beta spline coefficient array (t_coeff) is empty"
my_spline, ctr_pts, ctr_coeffs = _bspline(b_coeff)
self.my_spline = my_spline(self.s)
else:
def constant_function(input):
"""
Return array of ones same as the size of the input array. This
function is called when Beta spline function is not used.
Parameters
----------
input
Returns
-------
"""
return np.ones(input.shape)
self.my_spline = constant_function(self.s)
def apply_torques(self, rod: RodType, time: np.float64 = 0.0): self.compute_lifting_torques( time, self.my_spline, self.s, self.angular_frequency, self.wave_number, self.Amplitude, self.lamBDA, self.phase_shift, self.ramp_up_time, self.direction, rod.director_collection, rod.external_torques, )
def compute_lifting_torques( time, my_spline, s, angular_frequency, wave_number, # k pf muscle torque Amplitude, lamBDA, phase_shift, ramp_up_time, direction, director_collection, external_torques, ):
factor = min(1.0, time / ramp_up_time)
# From the node 1 to node nelem-1
# Magnitude of the torque. Am = beta(s) * sin(2pi*t/T + 2pi*s/lambda + phi)
# There is an inconsistency with paper and Elastica cpp implementation. In paper sign in
# front of wave number is positive, in Elastica cpp it is negative.
# torque_mag = (
# factor
# * my_spline
# * np.sin(angular_frequency * time - wave_number * s + phase_shift)
# )
kt = wave_number * lamBDA
torque_mag = max(0, (
factor
* my_spline
* Amplitude * np.cos(angular_frequency * time + kt * s + phase_shift) + 1
))
# Head and tail of the snake is opposite compared to elastica cpp. We need to iterate torque_mag
# from last to first element.
torque = _batch_product_i_k_to_ik(direction, torque_mag[::-1])
inplace_addition(
external_torques[..., 1:],
_batch_matvec(director_collection, torque)[..., 1:],
)
inplace_substraction(
external_torques[..., :-1],
_batch_matvec(director_collection[..., :-1], torque[..., 1:]),
)
Here's how I call the class in the main simulation...
snake_diffraction_sim.add_forcing_to(snake_body).using(
ea.LiftingTorques,
base_length=base_length,
b_coeff=b_coeff[:-1],
period=period,
wave_number=2.0 * np.pi / (wave_length),
Amplitude=0.7,
lamBDA=1,
phase_shift=0.25,
rest_lengths=snake_body.rest_lengths,
ramp_up_time=period,
direction=direction,
with_spline=True,
)
and here' the addition of the class to the __init__.py file..
from elastica.external_forces import (
NoForces,
EndpointForces,
GravityForces,
UniformForces,
UniformTorques,
MuscleTorques,
LiftingTorques,
EndpointForcesSinusoidal,
)
Besides, these I made no changes to any of the files..
I think the problem is with the indentation of what is inside of your class, make sure that you indent everything in the class correctly. Let me know how it goes.
There was an indentation error, which I addressed, but now I am getting the following message,
Traceback (most recent call last):
File "c:\Users\avardhan7\Documents\OPEN_AI\pyElastica\snake_diffraction\snake_diffraction.py", line 379, in
This is my current class for applying LiftingTorques
class LiftingTorques(NoForces): """ This class applies Lifting torques along the body. The applied muscle torques are treated as applied external forces. This class can apply muscle torques as a traveling wave with a beta spline or only as a traveling wave. For implementation details refer to Gazzola et. al. RSoS. (2018). """
def __init__(
self,
base_length,
b_coeff,
period,
wave_number,
Amplitude,
lamBDA,
phase_shift,
direction,
rest_lengths,
ramp_up_time,
with_spline=False
):
super(LiftingTorques, self).__init__()
self.direction = direction # Direction torque applied
self.Amplitude = Amplitude
self.lamBDA = lamBDA
self.angular_frequency = 2.0 * np.pi / period
self.wave_number = wave_number
self.phase_shift = phase_shift
assert ramp_up_time > 0.0
self.ramp_up_time = ramp_up_time
# s is the position of nodes on the rod, we go from node=1 to node=nelem-1, because there is no
# torques applied by first and last node on elements. Reason is that we cannot apply torque in an
# infinitesimal segment at the beginning and end of rod, because there is no additional element
# (at element=-1 or element=n_elem+1) to provide internal torques to cancel out an external
# torque. This coupled with the requirement that the sum of all muscle torques has
# to be zero results in this condition.
self.s = np.cumsum(rest_lengths)
self.s /= self.s[-1]
if with_spline:
assert b_coeff.size != 0, "Beta spline coefficient array (t_coeff) is empty"
my_spline, ctr_pts, ctr_coeffs = _bspline(b_coeff)
self.my_spline = my_spline(self.s)
else:
def constant_function(input):
"""
Return array of ones same as the size of the input array. This
function is called when Beta spline function is not used.
Parameters
----------
input
Returns
-------
"""
return np.ones(input.shape)
self.my_spline = constant_function(self.s)
def apply_torques(self, rod: RodType, time: np.float64 = 0.0):
self.compute_lifting_torques(
time,
self.my_spline,
self.s,
self.angular_frequency,
self.wave_number,
self.Amplitude,
self.lamBDA,
self.phase_shift,
self.ramp_up_time,
self.direction,
rod.director_collection,
rod.external_torques,
)
def compute_lifting_torques(
time,
my_spline,
s,
angular_frequency,
wave_number, # k pf muscle torque
Amplitude,
lamBDA,
phase_shift,
ramp_up_time,
direction,
director_collection,
external_torques,
):
# Ramp up the muscle torque
factor = min(1.0, time / ramp_up_time)
# From the node 1 to node nelem-1
# Magnitude of the torque. Am = beta(s) * sin(2pi*t/T + 2pi*s/lambda + phi)
# There is an inconsistency with paper and Elastica cpp implementation. In paper sign in
# front of wave number is positive, in Elastica cpp it is negative.
# torque_mag = (
# factor
# * my_spline
# * np.sin(angular_frequency * time - wave_number * s + phase_shift)
# )
kt = wave_number * lamBDA
torque_mag = max(0, (
factor
* my_spline
* Amplitude * np.cos(angular_frequency * time + kt * s + phase_shift) + 1
))
# Head and tail of the snake is opposite compared to elastica cpp. We need to iterate torque_mag
# from last to first element.
torque = _batch_product_i_k_to_ik(direction, torque_mag[::-1])
inplace_addition(
external_torques[..., 1:],
_batch_matvec(director_collection, torque)[..., 1:],
)
inplace_substraction(
external_torques[..., :-1],
_batch_matvec(director_collection[..., :-1], torque[..., 1:]),
)
I have retained everything which was present in the class MuscleTorques with the addition of variables lamBDA and Amplitude... which represent those two parameters from the paper
Okay, so I got the simulation to run with the liftingtorques class after fiddling around with the indentation some more and using np.maximum to apply the liftingTorques rather than just max., but there is no snake in the final simulation and all I see is a blank.. when I comment out the (liftingTorques) part in the simulation and run it only with the muscle torques, I see the snake moving along a straight line as before.. clearly I am doing something wrong...
I put my code in a repo and shared it with you, it'd be helpful if you could try running it on your end to see what I am saying.
@AkashVardhan7 I think the problem is in torque_mag = np.maximum(0,(factor* my_spline* Amplitude* np.cos(angular_frequency * time + kt * s + phase_shift)+ 1 ),)
, I think it should be torque_mag = np.maximum(0,(factor* my_spline* (Amplitude* np.cos(angular_frequency * time + kt * s + phase_shift)+ 1) ),)
instead.
Ali I changed it to what you suggested, yet the problem persists. :(
@xzhan139 can you also take a look at this issue.
I shared the repo with @xzhan139
Any further pointers on what I should be doing to make this work? I'd want to capture all the behaviors you reported in the manuscript before I can reliably start using this sim for my project...
Hi can you share your repo again? I didn't get any invitation yet
https://github.com/AkashVardhan7/snake_diffraction_Elastica.git
Here's the link to the repo (I made it public so you should be able to access the code... I made modifications to externalforces.py and init.py as you can follow from the thread.. here's a snapshot of the invitation I sent you
Thanks for sharing! Sorry I missed your invitation on Saturday. A couple of things -
If I didn't misunderstand, "direction = direction" is twist direction, not lifting direction. Adding twist in general is very tricky as you have to be very careful with the torque amplitude and timestep, otherwise your simulation diverge very quickly. I guess that is why you don't see the snake, it exploded. Lifting is instead in binormal direction.
Thanks for your reply, I changed the direction of actuation to the binormal_direction = np.array([1.0,0.0,0.0]) and lowered the amplitude of the lifting wave to 0.07, I think there is still a force unbalance as you can see from the slipping snake in the video
These are the lifting torque parameters with which I am running my simulation... lateral_direction is the same binormal direction vector pointing along x axis with unit vector [1.0,0.0,0.0]. The video shows that the snake behaves chaotically after slipping, which is indicating that there is an unbalance in the net forces somewhere., while I am running it with a time step of 1e-4.
https://github.com/GazzolaLab/PyElastica/assets/48886152/24f33303-cd8b-468c-8e0e-9c61ec7cc7e8
Hi, this looks to me like a numerical unstability due to your parameter settings. As I can see from your code there are few parameters that are quite different from what we usually run with, for example you have a period = 15, which makes the snake run very slow. We will push a case in PyElastica soon, which will include functions for lifting wave etc. The parameters of the snake will be slightly different, but you can use it as a reference for your implementation. Thank you.
Thanks please let me know when you have done so, and I can modify the code accordingly..When I changed the period to 2 second which is what is used in the paper SI and the time step to 8e-6 again the value used from the period, I still find that the numerical issue persists,
couple of other things to try -
The damping and number of rods helped and made the simulation more stable, I am observing sidewinding like behavior but the trajectory still seems a little funky in terms of the dynamics. I will try turning off the lateral wave and plotting just the side view with snake initialized in air.
https://github.com/GazzolaLab/PyElastica/assets/48886152/7529ec8a-2e46-42b9-b1d1-806a4f823b2d
I think it's the phase offset and the wavelength of the lifting wave which is giving the snake this waveform, I am using the same wavelength as the lateral wave and a phase shift of 0.25, which is chosen from the region in the parameter space supposed to generate straight side winding for lambda = 1.
Here's the lifting wave without the lateral wave and gravity.
This is the best side-winding I got When are you going to push your version of the code with sidewinding on github?
Hi @AkashVardhan7,
Sorry for the delay I had to resolve some issues, I just opened the pull request with the new continuum snake with lifting wave example, it is in review now.
Hi @AkashVardhan7,
The continuum snake with lifting wave case has been add, you can check it out in the update-0.3.2 branch
Since we have merged PR, I am closing this issue. If you have questions, please reopen again.
Hello again., I have been trying to replicate some of the side-winding results from your paper, and added a separate class to compute the torques from the lifting wave in the external_forces.py file like the MuscleTorques class, and then in the main simulation file, I add the forcing from the lifting wave using the liftingTorques class.. like it's done for the lateral wave with the MuscleTorques class..however it seems I will have to re-build/compile elastica again as I get the following error message AttributeError: module 'elastica' has no attribute 'LiftingTorques'. I am pasting the codes for these files...kindly take a peek and let me know if I am on the right track before I re-compile elastica to reflect the modification with the LiftingTorques.
here's the modified external_forces.py file with the modified Lifting wave doc = """ Numba implementation module for boundary condition implementations that apply external forces to the system."""
import numpy as np from elastica._linalg import _batch_matvec from elastica.typing import SystemType, RodType from elastica.utils import _bspline
from numba import njit from elastica._linalg import _batch_product_i_k_to_ik
class NoForces: """ This is the base class for external forcing boundary conditions applied to rod-like objects.
class GravityForces(NoForces): """ This class applies a constant gravitational force to the entire rod.
class EndpointForces(NoForces): """ This class applies constant forces on the endpoint nodes.
class UniformTorques(NoForces): """ This class applies a uniform torque to the entire rod.
class UniformForces(NoForces): """ This class applies a uniform force to the entire rod.
class MuscleTorques(NoForces): """ This class applies muscle torques along the body. The applied muscle torques are treated as applied external forces. This class can apply muscle torques as a traveling wave with a beta spline or only as a traveling wave. For implementation details refer to Gazzola et. al. RSoS. (2018).
class LiftingTorques(NoForces): """ This class applies Lifting torques along the body. The applied muscle torques are treated as applied external forces. This class can apply muscle torques as a traveling wave with a beta spline or only as a traveling wave. For implementation details refer to Gazzola et. al. RSoS. (2018).
@njit(cache=True) def inplace_addition(external_force_or_torque, force_or_torque): """ This function does inplace addition. First argument
external_force_or_torque
is the system.external_forces or system.external_torques. Second argument force or torque vector to be added.@njit(cache=True) def inplace_substraction(external_force_or_torque, force_or_torque): """ This function does inplace substraction. First argument
external_force_or_torque
is the system.external_forces or system.external_torques. Second argument force or torque vector to be substracted. Parametersclass EndpointForcesSinusoidal(NoForces): """ This class applies sinusoidally varying forces to the ends of a rod. Forces are applied in a plane, which is defined by the tangent_direction and normal_direction.
here's how I call it in my simulation file
import os import numpy as np import elastica as ea
Apply Muscle force related parameters
Helper Function to check if a point is inside a patch
def is_point_inside_patch(point, patch_vertices): """ Check if a 3D point is inside a patch defined by four vertices.
""" from snake_diffraction_postprocessing_3D import ( plot_snake_velocity, plot_video_3d, compute_projected_velocity, plot_curvature, ) """
class SnakeDiffractionSimulator( ea.BaseSystemCollection, ea.Constraints, ea.Forcing, ea.Damping, ea.CallBacks ): pass
def run_snake( b_coeff, PLOT_FIGURE=False, SAVE_FIGURE=False, SAVE_VIDEO=False, SAVE_RESULTS=False ):
Initialize the simulation class
if name == "main":