Automatic Unit Conversion#
As we all know, the units should be unified in the finite element analysis.
Common basic units include length
, force
, and time
.
The units of the base system can be combined in any combination, but other units
including pressure
, stress
, mass
, etc.
should be unified with base system.
In order to facilitate unit processing in the model, opstool
has developed a class that
can automatically perform unit conversion based on the basic units you set.
For details of the parameters see opstool.preprocessing.UnitSystem()
.
import numpy as np
import matplotlib.pyplot as plt
import openseespy.opensees as ops
import opstool as opst
length_unit = "m"
force_unit = "kN"
UNIT = opst.UnitSystem(length=length_unit, force=force_unit)
print("Length:", UNIT.mm, UNIT.mm2, UNIT.cm, UNIT.m, UNIT.inch, UNIT.ft)
print("Force", UNIT.N, UNIT.kN, UNIT.lbf, UNIT.kip)
print("Stress", UNIT.MPa, UNIT.kPa, UNIT.Pa, UNIT.psi, UNIT.ksi)
print("Mass", UNIT.g, UNIT.kg, UNIT.ton, UNIT.slug)
# print(UNIT)
Length: 0.001 1e-06 0.01 1 0.0254 0.3048
Force 0.001 1 0.0044482216282509 4.4482216
Stress 1000.0 1.0 0.001 6.8947572932000005 6894.7572932
Mass 1e-06 0.001 1.0 0.014593902936999999
Truss example#
Let’s look at a truss example.
You can set the practical values of structural parameters in the model,
and UnitSystem()
will help you automatically convert to the base unit system you specify.
def model(UNIT):
ops.wipe()
ops.model('basic', '-ndm', 2, '-ndf', 2)
# create nodes
ops.node(1, 0, 0)
ops.node(2, 144.0 * UNIT.cm, 0)
ops.node(3, 2.0 * UNIT.m, 0)
ops.node(4, 80. * UNIT.cm, 96.0 * UNIT.cm)
ops.mass(4, 100 * UNIT.kg, 100 * UNIT.kg)
# set boundary condition
ops.fix(1, 1, 1)
ops.fix(2, 1, 1)
ops.fix(3, 1, 1)
# define materials
ops.uniaxialMaterial("Elastic", 1, 3000.0 * UNIT.N / UNIT.cm2)
# define elements
ops.element("Truss", 1, 1, 4, 100.0 * UNIT.cm2, 1)
ops.element("Truss", 2, 2, 4, 50.0 * UNIT.cm2, 1)
ops.element("Truss", 3, 3, 4, 50.0 * UNIT.cm2, 1)
# eigen
omega = np.sqrt(ops.eigen('-fullGenLapack', 2))
f = omega / (2 * np.pi)
# create TimeSeries
ops.timeSeries("Linear", 1)
ops.pattern("Plain", 1, 1)
ops.load(4, 10.0 * UNIT.kN, -5.0 * UNIT.kN)
# ------------------------------
# Start of analysis generation
# ------------------------------
ops.system("BandSPD")
ops.numberer("RCM")
ops.constraints("Plain")
ops.integrator("LoadControl", 1.0 / 10)
ops.algorithm("Linear")
ops.analysis("Static")
u = []
forces = []
for _ in range(10):
ops.analyze(1)
u.append(ops.nodeDisp(4))
ops.reactions()
forces.append(ops.nodeReaction(2))
u = np.array(u)
forces = np.array(forces)
return u, forces, f
Results#
Three Cases:
length_unit1 = "m"
force_unit1 = "kN"
UNIT1 = opst.UnitSystem(length=length_unit1, force=force_unit1)
u1, forces1, f1 = model(UNIT=UNIT1)
length_unit2 = "cm"
force_unit2 = "N"
UNIT2 = opst.UnitSystem(length=length_unit2, force=force_unit2)
u2, forces2, f2 = model(UNIT=UNIT2)
length_unit3 = "ft"
force_unit3 = "lbf"
UNIT3 = opst.UnitSystem(length=length_unit3, force=force_unit3)
u3, forces3, f3 = model(UNIT=UNIT3)
Let’s verify it!
Structure Frequency#
print("structure frequency 1:", f1)
print("structure frequency 2:", f2)
print("structure frequency 3:", f3)
structure frequency 1: [7.05364256 8.28928679]
structure frequency 2: [7.05364256 8.28928679]
structure frequency 3: [7.05364256 8.28928679]
The structural frequencies are consistent, it really has nothing to do with the unit system!
Node 4 Displacement#
print("Dispalcement at node4 case 1:", u1, length_unit1)
print("Dispalcement at node4 case 2:", u2, length_unit2)
print("Dispalcement at node4 case 3:", u3, length_unit3)
Dispalcement at node4 case 1: [[ 0.00516124 -0.00205329]
[ 0.01032248 -0.00410659]
[ 0.01548372 -0.00615988]
[ 0.02064496 -0.00821317]
[ 0.02580621 -0.01026647]
[ 0.03096745 -0.01231976]
[ 0.03612869 -0.01437306]
[ 0.04128993 -0.01642635]
[ 0.04645117 -0.01847964]
[ 0.05161241 -0.02053294]] m
Dispalcement at node4 case 2: [[ 0.51612411 -0.20532937]
[ 1.03224822 -0.41065875]
[ 1.54837233 -0.61598812]
[ 2.06449644 -0.8213175 ]
[ 2.58062055 -1.02664687]
[ 3.09674466 -1.23197624]
[ 3.61286877 -1.43730562]
[ 4.12899288 -1.64263499]
[ 4.64511699 -1.84796437]
[ 5.16124111 -2.05329374]] cm
Dispalcement at node4 case 3: [[ 0.01693321 -0.00673653]
[ 0.03386641 -0.01347306]
[ 0.05079962 -0.02020958]
[ 0.06773282 -0.02694611]
[ 0.08466603 -0.03368264]
[ 0.10159923 -0.04041917]
[ 0.11853244 -0.0471557 ]
[ 0.13546565 -0.05389222]
[ 0.15239885 -0.06062875]
[ 0.16933206 -0.06736528]] ft
Node 2 Reactions#
print("Reaction at node2 case 1:", forces1, force_unit1)
print("Reaction at node2 case 2:", forces2, force_unit2)
print("Reaction at node2 case 3:", forces3, force_unit3)
Reaction at node2 case 1: [[-0.3296672 0.49450079]
[-0.65933439 0.98900159]
[-0.98900159 1.48350238]
[-1.31866878 1.97800317]
[-1.64833598 2.47250397]
[-1.97800317 2.96700476]
[-2.30767037 3.46150556]
[-2.63733757 3.95600635]
[-2.96700476 4.45050714]
[-3.29667196 4.94500794]] kN
Reaction at node2 case 2: [[ -329.6671958 494.50079369]
[ -659.33439159 989.00158739]
[ -989.00158739 1483.50238108]
[-1318.66878318 1978.00317478]
[-1648.33597898 2472.50396847]
[-1978.00317478 2967.00476217]
[-2307.67037057 3461.50555586]
[-2637.33756637 3956.00634955]
[-2967.00476217 4450.50714325]
[-3296.67195796 4945.00793694]] N
Reaction at node2 case 3: [[ -74.11213365 111.16820047]
[-148.22426729 222.33640094]
[-222.33640094 333.5046014 ]
[-296.44853458 444.67280187]
[-370.56066823 555.84100234]
[-444.67280187 667.00920281]
[-518.78493552 778.17740327]
[-592.89706916 889.34560374]
[-667.00920281 1000.51380421]
[-741.12133645 1111.68200468]] lbf
The displacement and force values depend on the base unit system you set up, but they are proportional to each other. Well, the rest is left to you to verify.
Remember that you are free to change the base unit system without rewriting the model code.