Functioning of SymPy's Column module - sympy/sympy GitHub Wiki

The Column class implemented in PR #17122 enables the continuum mechanics module of SymPy to deal with column buckling related calculations. The Column module is currently able to calculate:

  • The moment equation
  • The deflection equation
  • The slope equation
  • The critical load

..for a column defined by a user.

Below is an example usage of the Column Class:

# Column Class
>>> from sympy.physics.continuum_mechanics.column import Column
>>> from sympy import Symbol, symbols
>>> E, I, P = symbols('E, I, P', positive=True)

# defining an object of the Column class
>>> c = Column(3, E, I, 78000, top="pinned", bottom="pinned")

# checking the end-conditions
>>> c.end_conditions
{'bottom': 'pinned', 'top': 'pinned'}

# Specifying the boundary conditions of the column
>>> c.boundary_conditions
{'deflection': [(0, 0), (3, 0)], 'slope': [(0, 0)]}

# getting the moment equation
>>> c.moment()
78000*y(x)

# Calculating slope and deflection
>>> c.solve_slope_deflection()
>>> c.deflection()
C1*sin(20*sqrt(195)*x/(sqrt(E)*sqrt(I)))
>>> c.slope()
20*sqrt(195)*C1*cos(20*sqrt(195)*x/(sqrt(E)*sqrt(I)))/(sqrt(E)*sqrt(I))

# Calculating the critical load
>>> c.critical_load()
pi**2*E*I/9

The Column class

The Column class is non-mutable, which means unlike the Beam class, a user cannot change the attributes of the class once they are defined along with the object definition. Therefore to change the attribute values one will have to define a new object.

Reasons for creating a non-mutable class

  • From a backward-compatibility perspective, it is always possible to adopt a different plan and add mutability later but not the other way around.
  • Most things are immutable in SymPy which is useful for caching etc. Also, considering the Matrix module where allowing mutability has lead to many problems that are now impossible to fix without breaking backward compatibility.

Working of the column class:

The governing equation for column buckling is:

E, I: Elastic modulus, Second moment of the column respectively

y: deflection

M: moment at a distance x from the bottom

If we determine the moment (or moment equation) of the column and place it in the above equation, we might be able to get the deflection by further solving the differential equation for y.

Step-1: To determine the internal moment.

This is simply done by assuming deflection at any arbitrary cross-section at a distance x from the bottom to be y and then multiplying it by the load P. For eccentric load, another moment of magnitude P*e is added to the moment.

Simple load is given by:

Eccentric load is given by:

Step-2:

This moment can then be substituted in the governing equation and the resulting differential equation can be solved using SymPy’s dsolve() for the deflection y.

Applying different end-conditions

The above steps considers a simple example of a column pinned at both of its ends. But the end-condition of the column can vary, which will cause the moment equation to vary.

Currently, four basic end-conditions are implemented:

  • Pinned-pinned
  • Fixed-fixed
  • Fixed-pinned
  • One pinned-other free.

Depending on the supports the moment due to applied load would change as:

  • Pinned-Pinned: no change in the moment

  • Fixed-fixed: reaction moment M is included

  • Fixed-pinned:

Here M is the restraint moment at B (which is fixed). To counter this, another moment is considered by applying a horizontal force F at point A.

  • One pinned- other free:

Solving for slope and critical load

Once we get the deflection equation we can solve for the slope by differentiating the deflection equation with respect to x. This is done by SymPy’s diff() function.

self._slope = self._deflection.diff(x)

Critical load

Critical load for single bow buckling condition can be easily determined by substituting the boundary conditions in the deflection equation and solving it for P i.e the load.

Note: Even if the user provides the applied load, during the entire calculation, we consider the load to be P. Whenever the moment(), slope(), deflection(), etc. methods are called the variable P is replaced with the user's value. This is done so that it is easier for us to calculate the critical load in the end.

defl_eqs = []
# taking last two bounndary conditions which are actually
# the initial boundary conditions.
for point, value in self._boundary_conditions['deflection'][-2:]:
    defl_eqs.append(self._deflection.subs(x, point) - value)
 
# C1, C2 already solved, solve for P
self._critical_load = solve(defl_eqs, P, dict=True)[0][P]

The case of the pinned-pinned end condition is a bit tricky. On solving the differential equation via dsolve(), the deflection comes out to be zero. This problem has been described in this blog. Its calculation is handled a bit differently in the code. Instead of directly solving it via dsolve(), it is solved in steps, and the trivial solutions are removed. This technique not only solves for the deflection of the column but simultaneously also calculates the critical load for it.

Although this may be considered as a hack to the problem. I think in future it would be better if dsolve() gets the ability to remove the trivial solutions. But this seems to be better as of now.

A problem that still persists is the calculation of critical load for pinned-fixed end condition. Currently, it has been made as an XFAIL, since to resolve that either solve() or solveset() has to return the solution in the required form. An issue has been raised on GitHub, regarding the same.

I hope that it gives a crisp idea about the functioning of SymPy’s Column module.