xara-prism 0.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- xara_prism-0.0.0/PKG-INFO +8 -0
- xara_prism-0.0.0/pyproject.toml +26 -0
- xara_prism-0.0.0/setup.cfg +4 -0
- xara_prism-0.0.0/src/xara_prism/__init__.py +576 -0
- xara_prism-0.0.0/src/xara_prism/analysis.py +98 -0
- xara_prism-0.0.0/src/xara_prism/loading.py +233 -0
- xara_prism-0.0.0/src/xara_prism/solutions/__init__.py +2 -0
- xara_prism-0.0.0/src/xara_prism/solutions/aisc25.py +105 -0
- xara_prism-0.0.0/src/xara_prism/solutions/linear.py +314 -0
- xara_prism-0.0.0/src/xara_prism/solutions/pdelta.py +337 -0
- xara_prism-0.0.0/src/xara_prism/solutions/pdelta_euler.py +251 -0
- xara_prism-0.0.0/src/xara_prism/solutions/pdelta_shear.py +93 -0
- xara_prism-0.0.0/src/xara_prism/solutions/plane.py +42 -0
- xara_prism-0.0.0/src/xara_prism/solutions/reissner_terminal.py +386 -0
- xara_prism-0.0.0/src/xara_prism/solutions/reissner_uniform.py +115 -0
- xara_prism-0.0.0/src/xara_prism.egg-info/PKG-INFO +8 -0
- xara_prism-0.0.0/src/xara_prism.egg-info/SOURCES.txt +21 -0
- xara_prism-0.0.0/src/xara_prism.egg-info/dependency_links.txt +1 -0
- xara_prism-0.0.0/src/xara_prism.egg-info/requires.txt +5 -0
- xara_prism-0.0.0/src/xara_prism.egg-info/top_level.txt +1 -0
- xara_prism-0.0.0/tests/test_aisc_360.py +119 -0
- xara_prism-0.0.0/tests/test_pdelta.py +6 -0
- xara_prism-0.0.0/tests/test_reissner_terminal.py +128 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
|
|
2
|
+
[project]
|
|
3
|
+
name = "xara_prism"
|
|
4
|
+
version = "0.0.0"
|
|
5
|
+
dependencies = [
|
|
6
|
+
"xsection",
|
|
7
|
+
"xara",
|
|
8
|
+
"numpy",
|
|
9
|
+
"scipy",
|
|
10
|
+
"matplotlib"
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
[build-system]
|
|
14
|
+
requires = [
|
|
15
|
+
"setuptools >= 52.0.2",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
build-backend = "setuptools.build_meta"
|
|
19
|
+
|
|
20
|
+
[tool.setuptools]
|
|
21
|
+
package-dir = {"" = "src"}
|
|
22
|
+
|
|
23
|
+
[tool.setuptools.packages.find]
|
|
24
|
+
where = ["src"]
|
|
25
|
+
|
|
26
|
+
|
|
@@ -0,0 +1,576 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import xara
|
|
3
|
+
from xara.helpers import find_node
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class BasicShape(dict):
|
|
7
|
+
materials = {}
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Span:
|
|
11
|
+
def __init__(self,
|
|
12
|
+
length,
|
|
13
|
+
shape,
|
|
14
|
+
shear,
|
|
15
|
+
loads,
|
|
16
|
+
smax=1.0,
|
|
17
|
+
rotate=None,
|
|
18
|
+
units=None,
|
|
19
|
+
# Internal
|
|
20
|
+
_span_name=None,
|
|
21
|
+
):
|
|
22
|
+
self.length = length
|
|
23
|
+
self.shape = shape
|
|
24
|
+
self.shear = shear
|
|
25
|
+
self.units = units
|
|
26
|
+
self.smax = float(smax)
|
|
27
|
+
|
|
28
|
+
if not isinstance(loads, list):
|
|
29
|
+
loads = [loads]
|
|
30
|
+
self.loads = loads
|
|
31
|
+
|
|
32
|
+
self._span_name = _span_name
|
|
33
|
+
|
|
34
|
+
if rotate is None:
|
|
35
|
+
rotate = np.eye(3)
|
|
36
|
+
self._rotate = rotate
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
self.e_long = np.array([0, 1, 0]) # Global longitudinal axis
|
|
40
|
+
self.e_tran = np.array([1, 0, 0]) # Global transverse axis
|
|
41
|
+
self.e_bend = np.array([0, 0, 1]) # Global bending axis
|
|
42
|
+
|
|
43
|
+
self.i_long = np.array([1, 0, 0])
|
|
44
|
+
self.i_tran = np.array([0, 0, 1]) # local z
|
|
45
|
+
self.i_bend = np.array([0, 1, 0]) # local y
|
|
46
|
+
|
|
47
|
+
self._elastic_section = xara.FrameSection("Elastic", self.shape)
|
|
48
|
+
|
|
49
|
+
def plane_properties(self, iv, im):
|
|
50
|
+
section = self._elastic_section
|
|
51
|
+
cnn = section.cnn()
|
|
52
|
+
cmm = section.cmm()
|
|
53
|
+
cmn = section.cmn()
|
|
54
|
+
EA = cnn[0][0]
|
|
55
|
+
GJ = cmm[0][0]
|
|
56
|
+
EI = float(im.T@cmm@im)
|
|
57
|
+
GA = float(iv.T@cnn@iv)
|
|
58
|
+
|
|
59
|
+
return EA, GA, EI
|
|
60
|
+
|
|
61
|
+
def solution(self, type, time=None, shear=None, **kwds):
|
|
62
|
+
if shear is None:
|
|
63
|
+
shear = self.shear
|
|
64
|
+
|
|
65
|
+
from .solutions.linear import LinearPlaneSolution
|
|
66
|
+
from .solutions.pdelta import PDelta
|
|
67
|
+
|
|
68
|
+
if type == "linear" and self._span_name in {"span1", "span2", "span3", "span4", "span5"}:
|
|
69
|
+
return LinearPlaneSolution(span=self._span_name, prism=self, shear=shear, **kwds)
|
|
70
|
+
|
|
71
|
+
elif type == "pdelta" and self._span_name in {"span1", "span2", "span3", "span4", "span5"}:
|
|
72
|
+
return PDelta(span=self._span_name, prism=self, shear=shear, **kwds)
|
|
73
|
+
|
|
74
|
+
raise NotImplementedError("Solution not implemented for this span type.")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def create_prism(self, ndm,
|
|
78
|
+
element,
|
|
79
|
+
transform,
|
|
80
|
+
section="Elastic",
|
|
81
|
+
ne=1,
|
|
82
|
+
nen=2,
|
|
83
|
+
echo_file=None,
|
|
84
|
+
options=None):
|
|
85
|
+
model = xara.Model(ndm=ndm, echo_file=echo_file)
|
|
86
|
+
# nen = order + 1
|
|
87
|
+
nn = ne*(nen-1)+1
|
|
88
|
+
shear = self.shear
|
|
89
|
+
if options is None:
|
|
90
|
+
options = {}
|
|
91
|
+
|
|
92
|
+
if section == "Elastic":
|
|
93
|
+
section = self._elastic_section
|
|
94
|
+
else:
|
|
95
|
+
section = xara.FrameSection(section, self.shape)
|
|
96
|
+
model.section(section)
|
|
97
|
+
|
|
98
|
+
for i, x in enumerate(np.linspace(0, self.length, nn)):
|
|
99
|
+
model.node(i+1, 0, x, 0)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
model.geomTransf(transform, 1, (1, 0, 0))
|
|
103
|
+
|
|
104
|
+
geometry = None
|
|
105
|
+
for i in range(ne):
|
|
106
|
+
start = i * (nen - 1) + 1
|
|
107
|
+
nodes = list(range(start, start + nen))
|
|
108
|
+
model.element(element, i+1,
|
|
109
|
+
nodes,
|
|
110
|
+
section=1,
|
|
111
|
+
shear=shear,
|
|
112
|
+
transform=1,
|
|
113
|
+
**options)
|
|
114
|
+
return model
|
|
115
|
+
|
|
116
|
+
def analyze(self,
|
|
117
|
+
ndm,
|
|
118
|
+
element,
|
|
119
|
+
transform,
|
|
120
|
+
section="Elastic",
|
|
121
|
+
ne=1,
|
|
122
|
+
nen=2,
|
|
123
|
+
steps=None,
|
|
124
|
+
echo_file=None,
|
|
125
|
+
analysis_options: dict =None,
|
|
126
|
+
element_options : dict =None,
|
|
127
|
+
loading_options : dict =None):
|
|
128
|
+
|
|
129
|
+
from .analysis import AnalysisOutput
|
|
130
|
+
|
|
131
|
+
model = self._create_model(ndm, element, transform, section=section,
|
|
132
|
+
ne=ne, nen=nen,
|
|
133
|
+
echo_file=echo_file,
|
|
134
|
+
options=element_options)
|
|
135
|
+
|
|
136
|
+
if loading_options is None:
|
|
137
|
+
loading_options = {}
|
|
138
|
+
|
|
139
|
+
#
|
|
140
|
+
# 1. Analyze constant loads if present
|
|
141
|
+
#
|
|
142
|
+
if self.apply_loads(model, scale_type="constant", **loading_options) > 0:
|
|
143
|
+
model.integrator("LoadControl", 1/10)
|
|
144
|
+
model.system("BandGeneral")
|
|
145
|
+
model.test("NormUnbalance", 1e-8, 100, 0)
|
|
146
|
+
model.analysis("Static")
|
|
147
|
+
assert model.analyze(10) == 0
|
|
148
|
+
model.loadConst(time=0)
|
|
149
|
+
|
|
150
|
+
#
|
|
151
|
+
# 2. Analyze linear loads if present
|
|
152
|
+
#
|
|
153
|
+
if analysis_options is None:
|
|
154
|
+
analysis_options = {"hooks": [], "system": "BandGeneral"}
|
|
155
|
+
|
|
156
|
+
if "integrator" not in analysis_options:
|
|
157
|
+
if steps is None:
|
|
158
|
+
steps = 10
|
|
159
|
+
dlam = self.smax/steps if steps != 0 else 1
|
|
160
|
+
analysis_options["integrator"] = ("LoadControl", dlam)
|
|
161
|
+
|
|
162
|
+
if "test" not in analysis_options:
|
|
163
|
+
analysis_options["test"] = ("NormUnbalance", 1e-8, 100, 0)
|
|
164
|
+
|
|
165
|
+
if "hooks" not in analysis_options:
|
|
166
|
+
analysis_options["hooks"] = []
|
|
167
|
+
|
|
168
|
+
if "system" not in analysis_options:
|
|
169
|
+
analysis_options["system"] = "BandGeneral"
|
|
170
|
+
|
|
171
|
+
output = AnalysisOutput(self, model, label=f"{element}/{transform}, ne={ne}, nen={nen}")
|
|
172
|
+
analysis_options["hooks"].append(output.create_hook())
|
|
173
|
+
analysis_options["hooks"][-1](model)
|
|
174
|
+
analysis = xara.StaticAnalysis(model, **analysis_options)
|
|
175
|
+
output.analysis = analysis
|
|
176
|
+
if self.apply_loads(model, scale_type="linear", **loading_options) > 0:
|
|
177
|
+
for _ in range(steps):
|
|
178
|
+
analysis.analyze(1)
|
|
179
|
+
return output
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _create_model(self, ndm, element, transform, section="Elastic", ne=1, nen=2, **kwds):
|
|
183
|
+
model = self.create_prism(ndm, element, transform, section=section, ne=ne, nen=nen, **kwds)
|
|
184
|
+
i_node = find_node(model, y=0)
|
|
185
|
+
j_node = find_node(model, y=self.length)
|
|
186
|
+
self._fix_nodes(model, i_node, j_node)
|
|
187
|
+
return model
|
|
188
|
+
|
|
189
|
+
def _fix_nodes(self, model, i_node: int, j_node: int):
|
|
190
|
+
raise NotImplementedError("Subclasses must implement the _fix_nodes method.")
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def apply_loads(self, model, scale_type, **kwargs):
|
|
194
|
+
loads = []
|
|
195
|
+
n_loads = 0
|
|
196
|
+
for load in self.loads:
|
|
197
|
+
if scale_type == load._scale_type:
|
|
198
|
+
n_loads += 1
|
|
199
|
+
xara_load = load.create(self, model, **kwargs)
|
|
200
|
+
if xara_load is None:
|
|
201
|
+
# When creating legacy opensees-type loads,
|
|
202
|
+
# the load must be created after a pattern is created;
|
|
203
|
+
# This will have been done entirely within load.create()
|
|
204
|
+
continue
|
|
205
|
+
loads.append(xara_load)
|
|
206
|
+
|
|
207
|
+
if len(loads) > 0:
|
|
208
|
+
model.pattern(
|
|
209
|
+
xara.StaticPattern(loads)
|
|
210
|
+
)
|
|
211
|
+
return n_loads
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
class Span1(Span):
|
|
217
|
+
# Cantilever with tip loads
|
|
218
|
+
def __init__(self, length, shape, shear, loads, smax=1.0, **kwds):
|
|
219
|
+
super().__init__(length,
|
|
220
|
+
shape,
|
|
221
|
+
shear,
|
|
222
|
+
loads,
|
|
223
|
+
smax=smax,
|
|
224
|
+
_span_name="span1",
|
|
225
|
+
**kwds
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
def _fix_nodes(self, model, i_node, j_node):
|
|
229
|
+
model.fix(i_node, (1, 1, 1, 1, 1, 1))
|
|
230
|
+
model.fix(j_node, (0, 0, 0, 0, 0, 0))
|
|
231
|
+
|
|
232
|
+
def critical_load(self, iv, im, mode=1):
|
|
233
|
+
"""
|
|
234
|
+
Positive magnitude of the cantilever Euler load for odd mode number:
|
|
235
|
+
|
|
236
|
+
Pcr_j = ((2*j - 1)*pi/2L)^2 EI, j = 1,2,...
|
|
237
|
+
|
|
238
|
+
"""
|
|
239
|
+
mode = int(mode)
|
|
240
|
+
L = self.length
|
|
241
|
+
if mode < 1:
|
|
242
|
+
raise ValueError("mode must be >= 1")
|
|
243
|
+
q = (2 * mode - 1) * np.pi / (2.0 * L)
|
|
244
|
+
_, GA, EI = self.plane_properties(iv, im)
|
|
245
|
+
return EI * q**2
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
class Span2(Span):
|
|
249
|
+
# Encastre beam with transverse deflection at L
|
|
250
|
+
def __init__(self, length, shape, shear, loads, smax=1.0, **kwds):
|
|
251
|
+
super().__init__(length,
|
|
252
|
+
shape,
|
|
253
|
+
shear,
|
|
254
|
+
loads,
|
|
255
|
+
smax=smax,
|
|
256
|
+
_span_name="span2",
|
|
257
|
+
**kwds
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
def _fix_nodes(self, model, i_node, j_node):
|
|
261
|
+
model.fix(i_node, (1, 1, 1, 1, 1, 1))
|
|
262
|
+
model.fix(j_node, (0, 0, 1, 1, 1, 1))
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
class Span3(Span):
|
|
266
|
+
# Simply supported beam
|
|
267
|
+
def __init__(self, length, shape, shear, loads, smax=1.0, **kwds):
|
|
268
|
+
super().__init__(length,
|
|
269
|
+
shape,
|
|
270
|
+
shear,
|
|
271
|
+
loads,
|
|
272
|
+
smax=smax,
|
|
273
|
+
**kwds,
|
|
274
|
+
_span_name="span3"
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
def _fix_nodes(self, model, i_node, j_node):
|
|
278
|
+
model.fix(i_node, (1, 1, 1, 1, 1, 0))
|
|
279
|
+
model.fix(j_node, (1, 0, 1, 1, 1, 0))
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
class Span4(Span):
|
|
283
|
+
# Fixed-Fixed
|
|
284
|
+
def __init__(self, length, shape, shear, loads, **kwds):
|
|
285
|
+
super().__init__(length,
|
|
286
|
+
shape,
|
|
287
|
+
shear,
|
|
288
|
+
loads,
|
|
289
|
+
_span_name="span4",
|
|
290
|
+
**kwds
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
def _fix_nodes(self, model, i_node, j_node):
|
|
294
|
+
model.fix(i_node, (1, 1, 1, 1, 1, 1))
|
|
295
|
+
model.fix(j_node, (1, 0, 1, 1, 1, 1))
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
class Span5(Span):
|
|
299
|
+
# Propped column
|
|
300
|
+
def __init__(self, length, shape, shear, loads, **kwds):
|
|
301
|
+
super().__init__(length,
|
|
302
|
+
shape,
|
|
303
|
+
shear,
|
|
304
|
+
loads,
|
|
305
|
+
_span_name="span5",
|
|
306
|
+
**kwds
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
def _fix_nodes(self, model, i_node, j_node):
|
|
310
|
+
model.fix(i_node, (1, 1, 1, 1, 1, 1))
|
|
311
|
+
model.fix(j_node, (1, 0, 1, 1, 1, 0))
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
from xsection.library import from_aisc
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
class AISC_360_C1(Span3):
|
|
318
|
+
def __init__(self, shear, smax=1.0):
|
|
319
|
+
import xara.units.iks as units
|
|
320
|
+
from .loading import UniformShear, AxialForce, ShearForce
|
|
321
|
+
L = 336*units.inch
|
|
322
|
+
material = xara.MultiaxialMaterial(
|
|
323
|
+
"ElasticIsotropic",
|
|
324
|
+
E=29000*units.ksi,
|
|
325
|
+
G=11200*units.ksi,
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
super().__init__(
|
|
329
|
+
length=L,
|
|
330
|
+
shape=from_aisc("W14x48", material=material),
|
|
331
|
+
shear=shear,
|
|
332
|
+
loads=[
|
|
333
|
+
UniformShear(0.2*units.kip/units.foot, scale="constant"),
|
|
334
|
+
AxialForce(-450*units.kip, x=L, scale="linear"),
|
|
335
|
+
],
|
|
336
|
+
smax=smax,
|
|
337
|
+
units=units
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
def solution(self, type, time=None, shear=None):
|
|
341
|
+
if shear is None:
|
|
342
|
+
shear = self.shear
|
|
343
|
+
if type == "aisc" and not shear:
|
|
344
|
+
from .solutions.aisc25 import AISC_25_S3
|
|
345
|
+
return AISC_25_S3(self, time=time, shear=shear)
|
|
346
|
+
else:
|
|
347
|
+
return super().solution(type, time=time, shear=shear)
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
class AISC_360_C2(Span1):
|
|
351
|
+
def __init__(self, shear, smax=1.0, **kwds):
|
|
352
|
+
import xara.units.iks as units
|
|
353
|
+
from .loading import AxialForce, ShearForce
|
|
354
|
+
L = 336*units.inch
|
|
355
|
+
material = xara.MultiaxialMaterial(
|
|
356
|
+
"ElasticIsotropic",
|
|
357
|
+
E=29000*units.ksi,
|
|
358
|
+
G=11200*units.ksi,
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
super().__init__(
|
|
362
|
+
length=L,
|
|
363
|
+
shape=from_aisc("W14x48", material=material),
|
|
364
|
+
shear=shear,
|
|
365
|
+
units=units,
|
|
366
|
+
loads=[
|
|
367
|
+
ShearForce(1*units.kip, x=L, scale="constant"),
|
|
368
|
+
AxialForce(-200*units.kip, x=L, scale="linear"),
|
|
369
|
+
],
|
|
370
|
+
smax=smax,
|
|
371
|
+
**kwds
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
def solution(self, type, time=None, shear=None):
|
|
375
|
+
if shear is None:
|
|
376
|
+
shear = self.shear
|
|
377
|
+
|
|
378
|
+
if type == "aisc" and not shear:
|
|
379
|
+
from .solutions.aisc25 import AISC_25_S1
|
|
380
|
+
return AISC_25_S1(self, time=time, shear=shear)
|
|
381
|
+
else:
|
|
382
|
+
return super().solution(type, time=time)
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
class AISC_D25_C2(Span2):
|
|
386
|
+
def __init__(self, shear, smax=1.0, **kwds):
|
|
387
|
+
import xara.units.iks as units
|
|
388
|
+
from .loading import AxialForce, ShearForce
|
|
389
|
+
L = 336*units.inch
|
|
390
|
+
material = xara.MultiaxialMaterial(
|
|
391
|
+
"ElasticIsotropic",
|
|
392
|
+
E=29000*units.ksi,
|
|
393
|
+
G=11200*units.ksi,
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
super().__init__(
|
|
397
|
+
length=L,
|
|
398
|
+
shape=from_aisc("W14x48", material=material),
|
|
399
|
+
shear=shear,
|
|
400
|
+
units=units,
|
|
401
|
+
loads=[
|
|
402
|
+
ShearForce(1*units.kip, x=L, scale="constant"),
|
|
403
|
+
AxialForce(-200*units.kip, x=L, scale="linear"),
|
|
404
|
+
],
|
|
405
|
+
smax=smax,
|
|
406
|
+
**kwds
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
def AISC_25(loads, span_type, shear=1, smax=1.0, slenderness=None, **kwds):
|
|
411
|
+
|
|
412
|
+
import xara.units.iks as units
|
|
413
|
+
|
|
414
|
+
material = xara.MultiaxialMaterial(
|
|
415
|
+
"ElasticIsotropic",
|
|
416
|
+
E=29000*units.ksi,
|
|
417
|
+
G=11200*units.ksi,
|
|
418
|
+
)
|
|
419
|
+
shape = from_aisc("W14x48", material=material)
|
|
420
|
+
|
|
421
|
+
if slenderness is None:
|
|
422
|
+
length = 336*units.inch
|
|
423
|
+
else:
|
|
424
|
+
length = slenderness * shape.d
|
|
425
|
+
|
|
426
|
+
if span_type == "span1":
|
|
427
|
+
span = Span1
|
|
428
|
+
elif span_type == "span2":
|
|
429
|
+
span = Span2
|
|
430
|
+
elif span_type == "span3":
|
|
431
|
+
span = Span3
|
|
432
|
+
elif span_type == "span4":
|
|
433
|
+
span = Span4
|
|
434
|
+
elif span_type == "span5":
|
|
435
|
+
span = Span5
|
|
436
|
+
else:
|
|
437
|
+
raise ValueError(f"Unknown span type: {span_type}")
|
|
438
|
+
|
|
439
|
+
return span(
|
|
440
|
+
length=length,
|
|
441
|
+
shape=shape,
|
|
442
|
+
shear=shear,
|
|
443
|
+
units=units,
|
|
444
|
+
loads=loads,
|
|
445
|
+
smax=smax,
|
|
446
|
+
**kwds
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
class PlotPoint:
|
|
450
|
+
def __init__(self,
|
|
451
|
+
prism,
|
|
452
|
+
x,
|
|
453
|
+
y,
|
|
454
|
+
loc,
|
|
455
|
+
ax=None,
|
|
456
|
+
time=None):
|
|
457
|
+
self.prism = prism
|
|
458
|
+
self.x_key = x
|
|
459
|
+
self.y_key = y
|
|
460
|
+
self.loc = loc
|
|
461
|
+
if time is None:
|
|
462
|
+
time = np.linspace(0, prism.smax, 100)
|
|
463
|
+
self.time = time
|
|
464
|
+
if ax is not None:
|
|
465
|
+
self.ax = ax
|
|
466
|
+
self.fig = ax.figure
|
|
467
|
+
else:
|
|
468
|
+
from matplotlib import pyplot as plt
|
|
469
|
+
self.fig, self.ax = plt.subplots()
|
|
470
|
+
self.ax.grid(True)
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
def add(self, obj, **kwds):
|
|
474
|
+
x = self._collect(obj, self.x_key)
|
|
475
|
+
y = self._collect(obj, self.y_key)
|
|
476
|
+
self.ax.plot(x, y, **kwds)
|
|
477
|
+
|
|
478
|
+
def _collect(self, obj, var):
|
|
479
|
+
if var == "time":
|
|
480
|
+
if obj.time is not None:
|
|
481
|
+
return obj.time
|
|
482
|
+
return self.time
|
|
483
|
+
|
|
484
|
+
return [
|
|
485
|
+
getattr(obj, var)(self.loc, time) for time in self._collect(obj, var="time")
|
|
486
|
+
]
|
|
487
|
+
|
|
488
|
+
class PlotSpan:
|
|
489
|
+
def __init__(self, prism, time=1, space=None, scale=1, ax=None):
|
|
490
|
+
self.prism = prism
|
|
491
|
+
self.time = time
|
|
492
|
+
self.scale = scale
|
|
493
|
+
small = 1e-3 * prism.length
|
|
494
|
+
self._x_lim = (-small, small)
|
|
495
|
+
if space is None:
|
|
496
|
+
space = np.linspace(0, prism.length, 100)
|
|
497
|
+
self.space = space
|
|
498
|
+
import matplotlib.pyplot as plt
|
|
499
|
+
if ax is None:
|
|
500
|
+
_, self.ax = plt.subplots()
|
|
501
|
+
# self.ax.axis("equal")
|
|
502
|
+
self.ax.plot([0]*len(space), space, color="k", lw=0.5)
|
|
503
|
+
else:
|
|
504
|
+
self.ax = ax
|
|
505
|
+
|
|
506
|
+
def add(self, obj, **kwds):
|
|
507
|
+
if obj.space is None:
|
|
508
|
+
space = self.space
|
|
509
|
+
else:
|
|
510
|
+
# import pprint
|
|
511
|
+
# pprint.pprint(obj.data)
|
|
512
|
+
space = obj.space
|
|
513
|
+
|
|
514
|
+
x = [xi + self.scale*getattr(obj, "u_long")(xi, self.time) for xi in space]
|
|
515
|
+
y = [self.scale*getattr(obj, "u_tran")(x, self.time) for x in space]
|
|
516
|
+
|
|
517
|
+
e = 1.2
|
|
518
|
+
self._x_lim = (min(self._x_lim[0], min(y)*e), max(self._x_lim[1], max(y)*e))
|
|
519
|
+
self.ax.plot(y, x, **kwds)
|
|
520
|
+
# self.ax.plot(space, y, **kwds)
|
|
521
|
+
|
|
522
|
+
def finish(self):
|
|
523
|
+
self.ax.set_xlim(self._x_lim)
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
def check_span(obj, sol, time=1, space=None, rtol=None, atol=None):
|
|
527
|
+
if time is None:
|
|
528
|
+
time = obj.time
|
|
529
|
+
time = map(float, np.atleast_1d(time))
|
|
530
|
+
|
|
531
|
+
if space is None:
|
|
532
|
+
space = obj.space
|
|
533
|
+
|
|
534
|
+
print(obj, sol)
|
|
535
|
+
print(f"{'time':>6} {'x':>6} │ {'u_long_out':>10} {'u_long_sol':>10} {'diff':>10} │ {'u_tran_out':>10} {'u_tran_sol':>10} {'diff':>10}")
|
|
536
|
+
print(f"{'─'*6:>6} {'─':>6} │ {'─'*10 :>10} {'─'*10 :>10} {'─'*10:>10}─┼ {'─'*10 :>10} {'─'*10 :>10} {'─'*10:>10}")
|
|
537
|
+
for t in time:
|
|
538
|
+
print(f"{t:6.3f}", end="\n" if len(space) > 1 else "")
|
|
539
|
+
pad_time = f"{' '*6:>6}" if len(space) > 1 else ""
|
|
540
|
+
for xi in space:
|
|
541
|
+
print(f"{pad_time} {xi:>6.0f}", end=" │ ")
|
|
542
|
+
for key in ["u_long", "u_tran"]:
|
|
543
|
+
u_out = getattr(obj, key)(xi, t)
|
|
544
|
+
u_sol = getattr(sol, key)(xi, t)
|
|
545
|
+
diff = u_out - u_sol
|
|
546
|
+
if atol is not None:
|
|
547
|
+
assert abs(diff) < atol[key], f"{key} at x={xi} has absolute error {diff} > {atol[key]}"
|
|
548
|
+
if u_sol != 0:
|
|
549
|
+
diff /= u_sol
|
|
550
|
+
if rtol is not None:
|
|
551
|
+
assert abs(diff) < rtol[key], f"{key} at x={xi} has relative error {diff} > {rtol[key]}"
|
|
552
|
+
print(f"{u_out:>10.4f} {u_sol:>10.4f} {diff:>10.3e}", end=" │ ")
|
|
553
|
+
try:
|
|
554
|
+
print(f" {obj.ic(t):>6}", end="")
|
|
555
|
+
except:
|
|
556
|
+
pass
|
|
557
|
+
print()
|
|
558
|
+
print()
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
if __name__ == "__main__":
|
|
562
|
+
|
|
563
|
+
p = AISC_360_C1(shear=1)
|
|
564
|
+
|
|
565
|
+
# plot = PlotPoint(p,
|
|
566
|
+
# x="u_tran",
|
|
567
|
+
# y="time",
|
|
568
|
+
# loc=p.length
|
|
569
|
+
# )
|
|
570
|
+
|
|
571
|
+
plot = PlotSpan(p, var="u_tran", time=1, space=None)
|
|
572
|
+
|
|
573
|
+
# plot.add(p.simulation(3, "PrismFrame", ..., n=...))
|
|
574
|
+
|
|
575
|
+
plot.add(p.solution("linear"))
|
|
576
|
+
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import xara
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
class XaraAnalysis:
|
|
5
|
+
def __init__(self, span,
|
|
6
|
+
element: str,
|
|
7
|
+
transform: str,
|
|
8
|
+
section: str = "ElasticFrame",
|
|
9
|
+
element_options=None,
|
|
10
|
+
loading_options=None,
|
|
11
|
+
analysis_options=None,
|
|
12
|
+
):
|
|
13
|
+
self.span = span
|
|
14
|
+
self._element = element
|
|
15
|
+
self._transform = transform
|
|
16
|
+
self._section = section
|
|
17
|
+
self._element_options = element_options or {}
|
|
18
|
+
self._loading_options = loading_options or {}
|
|
19
|
+
self._analysis_options = analysis_options or {}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class AnalysisOutput:
|
|
24
|
+
def __init__(self, span, model: xara.Model, label=None, analysis=None):
|
|
25
|
+
self.prism = span
|
|
26
|
+
self.model = model
|
|
27
|
+
self.label = label
|
|
28
|
+
self.analysis = analysis
|
|
29
|
+
self.space = np.array([model.nodeCoord(node, 2) for node in model.getNodeTags()])
|
|
30
|
+
|
|
31
|
+
self.data = {
|
|
32
|
+
"u_tran": [],
|
|
33
|
+
"u_long": [],
|
|
34
|
+
"r_plan": [],
|
|
35
|
+
"ic": [],
|
|
36
|
+
"time": [],
|
|
37
|
+
}
|
|
38
|
+
def __repr__(self):
|
|
39
|
+
return f"{self.__class__.__name__}({self.label})"
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def time(self):
|
|
43
|
+
return np.asarray(self.data["time"], dtype=float)
|
|
44
|
+
|
|
45
|
+
def create_hook(self):
|
|
46
|
+
def hook(model):
|
|
47
|
+
self.data["time"].append(model.getTime())
|
|
48
|
+
|
|
49
|
+
self.data["u_tran"].append([
|
|
50
|
+
model.nodeDisp(node, 1)
|
|
51
|
+
for node in model.getNodeTags()
|
|
52
|
+
])
|
|
53
|
+
self.data["u_long"].append([
|
|
54
|
+
model.nodeDisp(node, 2)
|
|
55
|
+
for node in model.getNodeTags()
|
|
56
|
+
])
|
|
57
|
+
self.data["r_plan"].append([
|
|
58
|
+
model.nodeDisp(node, 6)
|
|
59
|
+
for node in model.getNodeTags()
|
|
60
|
+
])
|
|
61
|
+
ic = None
|
|
62
|
+
try:
|
|
63
|
+
ic = model.getIterationCount()
|
|
64
|
+
except:
|
|
65
|
+
pass
|
|
66
|
+
self.data["ic"].append(ic)
|
|
67
|
+
return hook
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def time(self):
|
|
73
|
+
return np.asarray(self.data["time"], dtype=float)
|
|
74
|
+
|
|
75
|
+
def _response(self, key: str, x: float, time: float):
|
|
76
|
+
|
|
77
|
+
i_time = np.argmin(np.abs(self.time - time))
|
|
78
|
+
i_space = np.argmin(np.abs(self.space - x))
|
|
79
|
+
# Check that the requested time and space are in the available data points
|
|
80
|
+
if np.abs(self.time[i_time] - time) > 1e-6:
|
|
81
|
+
raise ValueError(f"Requested time {time} is not available in the output data.")
|
|
82
|
+
if np.abs(self.space[i_space] - x) > 1e-6:
|
|
83
|
+
raise ValueError(f"Requested space {x} is not available in the output data.")
|
|
84
|
+
|
|
85
|
+
return self.data[key][i_time][i_space]
|
|
86
|
+
|
|
87
|
+
def u_tran(self, x, time):
|
|
88
|
+
return self._response("u_tran", x, time)
|
|
89
|
+
def u_long(self, x, time):
|
|
90
|
+
return self._response("u_long", x, time)
|
|
91
|
+
def r_plan(self, x, time):
|
|
92
|
+
return self._response("r_plan", x, time)
|
|
93
|
+
|
|
94
|
+
def ic(self, time):
|
|
95
|
+
i_time = np.argmin(np.abs(self.time - time))
|
|
96
|
+
if np.abs(self.time[i_time] - time) > 1e-8:
|
|
97
|
+
raise ValueError(f"Requested time {time} is not available in the output data.")
|
|
98
|
+
return self.data["ic"][i_time]
|