structuralcodes 0.1.1__py3-none-any.whl → 0.3.0__py3-none-any.whl

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.

Potentially problematic release.


This version of structuralcodes might be problematic. Click here for more details.

Files changed (30) hide show
  1. structuralcodes/__init__.py +1 -1
  2. structuralcodes/codes/ec2_2004/__init__.py +43 -11
  3. structuralcodes/codes/ec2_2004/_concrete_creep_and_shrinkage.py +529 -0
  4. structuralcodes/codes/mc2010/_concrete_creep_and_shrinkage.py +105 -73
  5. structuralcodes/core/_section_results.py +5 -19
  6. structuralcodes/core/base.py +42 -15
  7. structuralcodes/geometry/__init__.py +10 -1
  8. structuralcodes/geometry/_circular.py +81 -0
  9. structuralcodes/geometry/_geometry.py +4 -2
  10. structuralcodes/geometry/_rectangular.py +83 -0
  11. structuralcodes/geometry/_reinforcement.py +132 -5
  12. structuralcodes/materials/constitutive_laws/__init__.py +84 -0
  13. structuralcodes/materials/constitutive_laws/_bilinearcompression.py +183 -0
  14. structuralcodes/materials/constitutive_laws/_elastic.py +133 -0
  15. structuralcodes/materials/constitutive_laws/_elasticplastic.py +227 -0
  16. structuralcodes/materials/constitutive_laws/_parabolarectangle.py +255 -0
  17. structuralcodes/materials/constitutive_laws/_popovics.py +133 -0
  18. structuralcodes/materials/constitutive_laws/_sargin.py +115 -0
  19. structuralcodes/materials/constitutive_laws/_userdefined.py +262 -0
  20. structuralcodes/sections/__init__.py +2 -0
  21. structuralcodes/sections/_generic.py +174 -27
  22. structuralcodes/sections/_rc_utils.py +114 -0
  23. structuralcodes/sections/section_integrators/_fiber_integrator.py +204 -110
  24. structuralcodes/sections/section_integrators/_marin_integrator.py +273 -102
  25. structuralcodes/sections/section_integrators/_section_integrator.py +28 -4
  26. {structuralcodes-0.1.1.dist-info → structuralcodes-0.3.0.dist-info}/METADATA +2 -2
  27. {structuralcodes-0.1.1.dist-info → structuralcodes-0.3.0.dist-info}/RECORD +28 -18
  28. {structuralcodes-0.1.1.dist-info → structuralcodes-0.3.0.dist-info}/WHEEL +1 -1
  29. structuralcodes/codes/ec2_2004/annex_b_shrink_and_creep.py +0 -257
  30. structuralcodes/materials/constitutive_laws.py +0 -981
@@ -7,7 +7,8 @@ import typing as t
7
7
  import warnings
8
8
 
9
9
  import numpy as np
10
- from numpy.typing import ArrayLike
10
+ from numpy.typing import ArrayLike, NDArray
11
+ from scipy.linalg import lu_factor, lu_solve
11
12
  from shapely import MultiPolygon
12
13
  from shapely.ops import unary_union
13
14
 
@@ -20,7 +21,7 @@ from structuralcodes.geometry import (
20
21
  )
21
22
  from structuralcodes.materials.constitutive_laws import Elastic
22
23
 
23
- from .section_integrators import integrator_factory
24
+ from .section_integrators import SectionIntegrator, integrator_factory
24
25
 
25
26
 
26
27
  class GenericSection(Section):
@@ -55,6 +56,16 @@ class GenericSection(Section):
55
56
  of the section.
56
57
  name (str): The name of the section.
57
58
  integrator (str): The name of the SectionIntegrator to use.
59
+ kwargs (dict): A collection of keyword arguments to pass on to the
60
+ section calculator.
61
+
62
+ Note:
63
+ The GenericSection uses a GenericSectionCalculator for all
64
+ calculations. The GenericSectionCalculator uses a SectionIntegrator
65
+ for integrating over the section. Any additional keyword arguments
66
+ used when creating the GenericSection are passed on to the
67
+ SectionCalculator to customize the behaviour. See
68
+ GenericSectionCalculator for available keyword arguments.
58
69
  """
59
70
  if name is None:
60
71
  name = 'GenericSection'
@@ -72,7 +83,7 @@ class GenericSection(Section):
72
83
  self._gross_properties = None
73
84
 
74
85
  @property
75
- def gross_properties(self) -> s_res.GrossProperties:
86
+ def gross_properties(self) -> s_res.SectionProperties:
76
87
  """Return the gross properties of the section."""
77
88
  if self._gross_properties is None:
78
89
  self._gross_properties = (
@@ -84,6 +95,8 @@ class GenericSection(Section):
84
95
  class GenericSectionCalculator(SectionCalculator):
85
96
  """Calculator class implementing analysis algorithms for code checks."""
86
97
 
98
+ integrator: SectionIntegrator
99
+
87
100
  def __init__(
88
101
  self,
89
102
  sec: GenericSection,
@@ -98,7 +111,7 @@ class GenericSectionCalculator(SectionCalculator):
98
111
  (default = 'marin').
99
112
 
100
113
  Note:
101
- When using 'fiber' integrator the kwarg 'mesh_size' can be used to
114
+ When using `fiber` integrator the kwarg `mesh_size` can be used to
102
115
  specify a dimensionless number (between 0 and 1) specifying the
103
116
  size of the resulting mesh.
104
117
  """
@@ -113,17 +126,17 @@ class GenericSectionCalculator(SectionCalculator):
113
126
  self._n_max = None
114
127
  self._n_min = None
115
128
 
116
- def _calculate_gross_section_properties(self) -> s_res.GrossProperties:
129
+ def _calculate_gross_section_properties(self) -> s_res.SectionProperties:
117
130
  """Calculates the gross section properties of the GenericSection.
118
131
 
119
132
  This function is private and called when the section is created.
120
133
  It stores the result into the result object.
121
134
 
122
135
  Returns:
123
- GrossProperties: The gross properties of the section.
136
+ SectionProperties: The gross properties of the section.
124
137
  """
125
138
  # It will use the algorithms for generic sections
126
- gp = s_res.GrossProperties()
139
+ gp = s_res.SectionProperties()
127
140
 
128
141
  # Computation of perimeter using shapely
129
142
  polygon = unary_union(
@@ -142,13 +155,13 @@ class GenericSectionCalculator(SectionCalculator):
142
155
  # Computation of surface area, reinforcement area, EA (axial rigidity)
143
156
  # and mass: Morten -> problem with units! how do we deal with it?
144
157
  for geo in self.section.geometry.geometries:
145
- gp.ea += geo.area * geo.material.get_tangent(eps=0)[0]
158
+ gp.ea += geo.area * geo.material.get_tangent(eps=0)
146
159
  if geo.density is not None:
147
160
  # this assumes area in mm2 and density in kg/m3
148
161
  gp.mass += geo.area * geo.density * 1e-9
149
162
 
150
163
  for geo in self.section.geometry.point_geometries:
151
- gp.ea += geo.area * geo.material.get_tangent(eps=0)[0]
164
+ gp.ea += geo.area * geo.material.get_tangent(eps=0)
152
165
  gp.area_reinforcement += geo.area
153
166
  if geo.density is not None:
154
167
  # this assumes area in mm2 and density in kg/m3
@@ -325,7 +338,7 @@ class GenericSectionCalculator(SectionCalculator):
325
338
 
326
339
  def find_equilibrium_fixed_pivot(
327
340
  self, geom: CompoundGeometry, n: float, yielding: bool = False
328
- ) -> t.Tuple[float, float, float]:
341
+ ) -> t.List[float]:
329
342
  """Find the equilibrium changing curvature fixed a pivot.
330
343
  The algorithm uses bisection algorithm between curvature
331
344
  of balanced failure and 0. Selected the pivot point as
@@ -339,8 +352,8 @@ class GenericSectionCalculator(SectionCalculator):
339
352
  yielding (bool): ...
340
353
 
341
354
  Returns:
342
- Tuple(float, float, float): 3 floats: Axial strain at (0,0), and
343
- curvatures of y* and z* axes. Note that being uniaxial bending,
355
+ List(float): 3 floats: Axial strain at (0,0), and curvatures of y*
356
+ and z* axes. Note that being uniaxial bending,
344
357
  curvature along z* is 0.0.
345
358
  """
346
359
  # Number of maximum iteration for the bisection algorithm
@@ -359,6 +372,10 @@ class GenericSectionCalculator(SectionCalculator):
359
372
  ) = self.integrator.integrate_strain_response_on_geometry(
360
373
  geom, strain, tri=self.triangulated_data, mesh_size=self.mesh_size
361
374
  )
375
+ # save the triangulation data
376
+ if self.triangulated_data is None and tri is not None:
377
+ self.triangulated_data = tri
378
+
362
379
  # Check if there is equilibrium with this strain distribution
363
380
  chi_a = strain[1]
364
381
  dn_a = n_int - n
@@ -404,9 +421,6 @@ class GenericSectionCalculator(SectionCalculator):
404
421
  s = f'Last iteration reached a unbalance of {dn_c}'
405
422
  raise ValueError(f'Maximum number of iterations reached.\n{s}')
406
423
  # Found equilibrium
407
- # save the triangulation data
408
- if self.triangulated_data is None:
409
- self.triangulated_data = tri
410
424
  # Return the strain distribution
411
425
  return [eps_0, chi_c, 0]
412
426
 
@@ -486,7 +500,7 @@ class GenericSectionCalculator(SectionCalculator):
486
500
  ) = self.integrator.integrate_strain_response_on_geometry(
487
501
  geom, [eps_0, curv, 0], tri=self.triangulated_data
488
502
  )
489
- if self.triangulated_data is None:
503
+ if self.triangulated_data is None and tri is not None:
490
504
  self.triangulated_data = tri
491
505
  dn_a = n_int - n
492
506
  # It may occur that dn_a is already almost zero (in eqiulibrium)
@@ -547,7 +561,7 @@ class GenericSectionCalculator(SectionCalculator):
547
561
  self.section.geometry, [eps_p, 0, 0], tri=tri
548
562
  )
549
563
 
550
- if self.triangulated_data is None:
564
+ if self.triangulated_data is None and tri is not None:
551
565
  self.triangulated_data = tri
552
566
  return n_min, n_max
553
567
 
@@ -594,25 +608,60 @@ class GenericSectionCalculator(SectionCalculator):
594
608
  self.triangulated_data = rotated_triangulated_data
595
609
 
596
610
  def integrate_strain_profile(
597
- self, strain: ArrayLike
598
- ) -> t.Tuple[float, float, float]:
599
- """Integrate a strain profile returning internal forces.
611
+ self,
612
+ strain: ArrayLike,
613
+ integrate: t.Literal['stress', 'modulus'] = 'stress',
614
+ ) -> t.Union[t.Tuple[float, float, float], NDArray]:
615
+ """Integrate a strain profile returning stress resultants or tangent
616
+ section stiffness matrix.
600
617
 
601
618
  Arguments:
602
619
  strain (ArrayLike): Represents the deformation plane. The strain
603
620
  should have three entries representing respectively: axial
604
621
  strain (At 0,0 coordinates), curv_y, curv_z.
622
+ integrate (str): a string indicating the quantity to integrate over
623
+ the section. It can be 'stress' or 'modulus'. When 'stress'
624
+ is selected, the return value will be the stress resultants N,
625
+ My, Mz, while if 'modulus' is selected, the return will be the
626
+ tangent section stiffness matrix (default is 'stress').
605
627
 
606
628
  Returns:
607
- Tuple(float, float, float): N, My and Mz.
629
+ Union(Tuple(float, float, float),NDArray): N, My and Mz when
630
+ `integrate='stress'`, or a numpy array representing the stiffness
631
+ matrix then `integrate='modulus'`.
632
+
633
+ Examples:
634
+ result = self.integrate_strain_profile(strain,integrate='tangent')
635
+ # `result` will be the tangent stiffness matrix (a 3x3 numpy array)
636
+
637
+ result = self.integrate_strain_profile(strain)
638
+ # `result` will be a tuple containing section forces (N, My, Mz)
639
+
640
+ Raises:
641
+ ValueError: If a unkown value is passed to the `integrate`
642
+ parameter.
608
643
  """
609
- N, My, Mz, _ = self.integrator.integrate_strain_response_on_geometry(
644
+ result = self.integrator.integrate_strain_response_on_geometry(
610
645
  geo=self.section.geometry,
611
646
  strain=strain,
647
+ integrate=integrate,
612
648
  tri=self.triangulated_data,
613
649
  mesh_size=self.mesh_size,
614
650
  )
615
- return N, My, Mz
651
+
652
+ # Save triangulation data for future use
653
+ if self.triangulated_data is None and result[-1] is not None:
654
+ self.triangulated_data = result[-1]
655
+
656
+ # manage the returning from integrate_strain_response_on_geometry:
657
+ # this function returns one of the two:
658
+ # a. float, float, float, tri (i.e. N, My, Mz, tri)
659
+ # b. (NDArray, tri) (i.e. section stiff matrix, tri)
660
+ # We need to return only forces or stifness
661
+ # (without triangultion data)
662
+ if len(result) == 2:
663
+ return result[0]
664
+ return result[:-1]
616
665
 
617
666
  def calculate_bending_strength(
618
667
  self, theta=0, n=0
@@ -654,13 +703,15 @@ class GenericSectionCalculator(SectionCalculator):
654
703
  if self.triangulated_data is not None:
655
704
  # Rotate back also triangulated data!
656
705
  self._rotate_triangulated_data(theta)
706
+ # Rotate also curvature!
707
+ strain_rotated = T @ np.array([[strain[1]], [strain[2]]])
657
708
 
658
709
  # Create result object
659
710
  res = s_res.UltimateBendingMomentResults()
660
711
  res.theta = theta
661
712
  res.n = N
662
- res.chi_y = strain[1]
663
- res.chi_z = strain[2]
713
+ res.chi_y = strain_rotated[0, 0]
714
+ res.chi_z = strain_rotated[1, 0]
664
715
  res.eps_a = strain[0]
665
716
  res.m_y = M[0, 0]
666
717
  res.m_z = M[1, 0]
@@ -959,7 +1010,7 @@ class GenericSectionCalculator(SectionCalculator):
959
1010
  mesh_size=self.mesh_size,
960
1011
  )
961
1012
  )
962
- if self.triangulated_data is None:
1013
+ if self.triangulated_data is None and tri is not None:
963
1014
  self.triangulated_data = tri
964
1015
  forces[i, 0] = N
965
1016
  forces[i, 1] = My
@@ -1229,7 +1280,7 @@ class GenericSectionCalculator(SectionCalculator):
1229
1280
  tri=self.triangulated_data,
1230
1281
  )
1231
1282
  )
1232
- if self.triangulated_data is None:
1283
+ if self.triangulated_data is None and tri is not None:
1233
1284
  self.triangulated_data = tri
1234
1285
  forces[i, 0] = N
1235
1286
  forces[i, 1] = My
@@ -1275,3 +1326,99 @@ class GenericSectionCalculator(SectionCalculator):
1275
1326
  res.strains[i, 2] = res_bend_strength.chi_z
1276
1327
 
1277
1328
  return res
1329
+
1330
+ def calculate_strain_profile(
1331
+ self,
1332
+ n,
1333
+ my,
1334
+ mz,
1335
+ initial: bool = False,
1336
+ max_iter: int = 10,
1337
+ tol: float = 1e-6,
1338
+ ) -> t.List[float]:
1339
+ """Get the strain plane for a given axial force and biaxial bending.
1340
+
1341
+ Args:
1342
+ n (float): Axial load.
1343
+ my (float): Bending moment around y-axis.
1344
+ mz (float): Bending moment around z-axis.
1345
+ initial (bool): If True the modified newton with initial tanget is
1346
+ used (default = False).
1347
+ max_iter (int): the maximum number of iterations in the iterative
1348
+ process (default = 10).
1349
+ tol (float): the tolerance for convergence test in terms of strain
1350
+ increment.
1351
+
1352
+ Returns:
1353
+ List(float): 3 floats: Axial strain at (0,0), and curvatures of the
1354
+ section around y and z axes.
1355
+ """
1356
+ # Get the gometry
1357
+ geom = self.section.geometry
1358
+
1359
+ # Collect loads in a numpy array
1360
+ loads = np.array([n, my, mz])
1361
+
1362
+ # Compute initial tangent stiffness matrix
1363
+ stiffness_tangent, tri = (
1364
+ self.integrator.integrate_strain_response_on_geometry(
1365
+ geom,
1366
+ [0, 0, 0],
1367
+ integrate='modulus',
1368
+ tri=self.triangulated_data,
1369
+ )
1370
+ )
1371
+ # eventually save the triangulation data
1372
+ if self.triangulated_data is None and tri is not None:
1373
+ self.triangulated_data = tri
1374
+
1375
+ # Calculate strain plane with Newton Rhapson Iterative method
1376
+ num_iter = 0
1377
+ strain = np.zeros(3)
1378
+
1379
+ # Factorize once the stiffness matrix if using initial
1380
+ if initial:
1381
+ # LU factorization
1382
+ # (maybe also Choelesky could work if matrix always SPD?)
1383
+ lu, piv = lu_factor(stiffness_tangent)
1384
+
1385
+ # Do Newton loops
1386
+ while True:
1387
+ # Check if number of iterations exceeds the maximum
1388
+ if num_iter > max_iter:
1389
+ break
1390
+
1391
+ # Calculate response and residuals
1392
+ response = np.array(self.integrate_strain_profile(strain=strain))
1393
+ residual = loads - response
1394
+
1395
+ if initial:
1396
+ # Solve using the decomposed matrix
1397
+ delta_strain = lu_solve((lu, piv), residual)
1398
+ else:
1399
+ # Calculate the current tangent stiffness
1400
+ stiffness_tangent, _ = (
1401
+ self.integrator.integrate_strain_response_on_geometry(
1402
+ geom,
1403
+ strain,
1404
+ integrate='modulus',
1405
+ tri=self.triangulated_data,
1406
+ )
1407
+ )
1408
+
1409
+ # Solve using the current tangent stiffness
1410
+ delta_strain = np.linalg.solve(stiffness_tangent, residual)
1411
+
1412
+ # Update the strain
1413
+ strain += delta_strain
1414
+
1415
+ num_iter += 1
1416
+
1417
+ # Check for convergence:
1418
+ if np.linalg.norm(delta_strain) < tol:
1419
+ break
1420
+
1421
+ if num_iter >= max_iter:
1422
+ raise StopIteration('Maximum number of iterations reached.')
1423
+
1424
+ return strain.tolist()
@@ -0,0 +1,114 @@
1
+ """A collection of functions related to reinforced concrete sections."""
2
+
3
+ import typing as t
4
+
5
+ import structuralcodes.core._section_results as s_res
6
+ from structuralcodes.geometry import CompoundGeometry, SurfaceGeometry
7
+ from structuralcodes.materials.constitutive_laws import Elastic, UserDefined
8
+ from structuralcodes.sections import GenericSection
9
+ from structuralcodes.sections.section_integrators import FiberIntegrator
10
+
11
+
12
+ def calculate_elastic_cracked_properties(
13
+ section: GenericSection,
14
+ theta: float = 0,
15
+ return_cracked_section: bool = False,
16
+ ) -> t.Union[
17
+ s_res.SectionProperties, t.Tuple[s_res.SectionProperties, CompoundGeometry]
18
+ ]:
19
+ """Calculates the cracked section properties of a reinforced concrete
20
+ section. (GenericSection). Materials in surface geometries and point
21
+ geometries are elastic-linear in order to make the cracking properties
22
+ independent of the stress state. Tension in all surface geometries is
23
+ neglected.
24
+
25
+ Args:
26
+ section (GenericSection): The section to use as basis for the
27
+ calculation.
28
+ theta (float): Angle of the neutral axis to the horizontal in radians.
29
+ theta=0 implies upper compression block.
30
+ return_cracked_section (bool): If true, returns also the cracked
31
+ section.
32
+
33
+ Returns:
34
+ t.Union[SectionProperties, t.Tuple[SectionProperties, GenericSection]]:
35
+ SectionProperties data of a cracked section (i.e cracked section
36
+ properties) and (if return_cracked_section is True) the cracked
37
+ section.
38
+ """
39
+
40
+ def create_surface_geometries(polygons_list, material):
41
+ """Process shapely polygons to SurfaceGeometries."""
42
+ # Create an empty list to store SurfaceGeometry objects
43
+ surface_geometries = []
44
+
45
+ # Iterate over the list of polygons and create SurfaceGeometry for each
46
+ for polygon in polygons_list:
47
+ # Create a new SurfaceGeometry for the current polygon
48
+ surface_geometry = SurfaceGeometry(polygon, material)
49
+ # Add the new SurfaceGeometry to the list
50
+ surface_geometries.append(surface_geometry)
51
+
52
+ return CompoundGeometry(surface_geometries)
53
+
54
+ if not section.geometry.reinforced_concrete:
55
+ return None
56
+
57
+ rotated_geometry = section.geometry.rotate(-theta)
58
+
59
+ for geo in rotated_geometry.geometries:
60
+ Ec = geo.material.get_tangent(eps=0)
61
+ elastic_concrete = UserDefined([-100, 0], [-100 * Ec, 0])
62
+ geo._material = elastic_concrete
63
+
64
+ for pg in rotated_geometry.point_geometries:
65
+ Es = pg.material.get_tangent(eps=0)
66
+ elastic_steel = Elastic(Es, 'elastic steel')
67
+ pg._material = elastic_steel
68
+
69
+ curv = -1e-5 # Any curvature should return the same mechanical properties.
70
+ # Find the equilibrium with fixed curvature
71
+ eps = section.section_calculator.find_equilibrium_fixed_curvature(
72
+ rotated_geometry, 0, curv, 0
73
+ )[0]
74
+ z_na = -eps / curv # distance to neutral fibre
75
+
76
+ # Cutting concrete geometries and retaining the compressed block
77
+ cut_geom = None
78
+ for part in rotated_geometry.geometries:
79
+ upper_div, lower_div = part.split(((0, z_na), 0))
80
+ # Convert to SurfaceGeometry
81
+ subpart = create_surface_geometries(upper_div, part.material)
82
+ if cut_geom is None:
83
+ cut_geom = subpart
84
+ else:
85
+ cut_geom += subpart
86
+
87
+ # Add reinforcement geometries
88
+ for reinf in rotated_geometry.point_geometries:
89
+ cut_geom += reinf
90
+
91
+ # return geoemtry to original rotation
92
+ cracked_geom = cut_geom.rotate(theta)
93
+
94
+ # Define the cracked section
95
+ if isinstance(section.section_calculator.integrator, FiberIntegrator):
96
+ mesh_size = getattr(
97
+ section.section_calculator.integrator, 'mesh_size', 0.01
98
+ )
99
+ cracked_sec = GenericSection(
100
+ cracked_geom,
101
+ integrator='fiber',
102
+ mesh_size=mesh_size,
103
+ )
104
+ else:
105
+ cracked_sec = GenericSection(cracked_geom, integrator='marin')
106
+
107
+ cracked_prop = cracked_sec.gross_properties
108
+
109
+ if return_cracked_section:
110
+ result = (cracked_prop, cracked_geom)
111
+ else:
112
+ result = cracked_prop
113
+
114
+ return result