structuralcodes 0.0.2__py3-none-any.whl → 0.1.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.

@@ -3,7 +3,7 @@
3
3
  from . import codes, core, geometry, materials, sections
4
4
  from .codes import get_design_codes, set_design_code, set_national_annex
5
5
 
6
- __version__ = '0.0.2'
6
+ __version__ = '0.1.0'
7
7
 
8
8
  __all__ = [
9
9
  'set_design_code',
@@ -3,10 +3,11 @@
3
3
  import types
4
4
  import typing as t
5
5
 
6
- from . import ec2_2004, ec2_2023, mc2010
6
+ from . import ec2_2004, ec2_2023, mc2010, mc2020
7
7
 
8
8
  __all__ = [
9
9
  'mc2010',
10
+ 'mc2020',
10
11
  'ec2_2023',
11
12
  'ec2_2004',
12
13
  'set_design_code',
@@ -23,25 +24,43 @@ _NATIONAL_ANNEX: t.Optional[str] = None
23
24
  # Design code registry
24
25
  _DESIGN_CODES = {
25
26
  'mc2010': mc2010,
27
+ 'mc2020': mc2020,
26
28
  'ec2_2004': ec2_2004,
27
29
  'ec2_2023': ec2_2023,
28
30
  }
29
31
 
30
32
 
31
- def set_design_code(design_code: t.Optional[str] = None) -> None:
33
+ def set_design_code(
34
+ design_code: t.Optional[t.Union[str, types.ModuleType]] = None,
35
+ ) -> None:
32
36
  """Set the current design code globally.
33
37
 
34
38
  Args:
35
- design_code (str): The abbreviation of the code.
39
+ design_code (Union[str, Moduletype]): The abbreviation of the code
40
+ (str), or a module that represents the code (ModuleType).
36
41
 
37
42
  Note:
38
43
  Call get_design_codes() to get a list of the available codes.
39
44
  """
40
45
  global _CODE # pylint: disable=W0603
41
- if design_code is not None:
46
+ if design_code is None:
47
+ # Reset to None
48
+ _CODE = None
49
+ elif isinstance(design_code, str) and design_code.lower() in _DESIGN_CODES:
50
+ # The design code abbreviation is valid
42
51
  _CODE = _DESIGN_CODES.get(design_code.lower())
52
+ elif isinstance(design_code, types.ModuleType) and all(
53
+ name in dir(design_code)
54
+ for name in ('__title__', '__year__', '__materials__')
55
+ ):
56
+ # The module is a valid design code
57
+ _CODE = design_code
43
58
  else:
44
- _CODE = None
59
+ raise ValueError(
60
+ f'{design_code} is not a valid abbreviation for a design code, or'
61
+ ' a valid module representing a design code.\nType '
62
+ 'get_design_codes() to list the available design codes.'
63
+ )
45
64
 
46
65
 
47
66
  def get_design_codes() -> t.List[str]:
@@ -75,5 +94,9 @@ def _use_design_code(
75
94
  Call get_design_codes() to get a list of the available codes.
76
95
  """
77
96
  if design_code is None:
97
+ # Returned the globally set design code
78
98
  return _CODE
79
- return _DESIGN_CODES.get(design_code.lower())
99
+
100
+ # Set design code before returning
101
+ set_design_code(design_code)
102
+ return _CODE
@@ -0,0 +1,7 @@
1
+ """The fib Model Code 2020."""
2
+
3
+ import typing as t
4
+
5
+ __title__: str = 'fib Model Code 2020'
6
+ __year__: str = '2024'
7
+ __materials__: t.Tuple[str] = ('concrete', 'reinforcement')
@@ -175,37 +175,87 @@ class UltimateBendingMomentResults:
175
175
 
176
176
 
177
177
  @dataclass
178
- class NMMInteractionDomain:
178
+ class InteractionDomain:
179
+ """Class for storing common data on all interaction domain results.
180
+
181
+ Attributes:
182
+ strains (numpy.Array): A numpy array with shape (n, 3) containing ea,
183
+ ky and kz.
184
+ forces (numpy.Array): A numpy array with shape (n, 3) containing n, my
185
+ and mz.
186
+ field_num (numpy.Array): a numpy array with shape (n,) containing a
187
+ number between 1 and 6 indicating the failure field.
188
+ """
189
+
190
+ # array with shape (n,3) containing ea, ky, kz:
191
+ strains: ArrayLike = None
192
+ # array with shape(n,3) containing N, My, Mz
193
+ forces: ArrayLike = None
194
+ # array with shape(n,) containing the field number from 1 to 6
195
+ field_num: ArrayLike = None
196
+
197
+ @property
198
+ def n(self):
199
+ """Return axial force."""
200
+ if self.forces is None:
201
+ return None
202
+ return self.forces[:, 0]
203
+
204
+ @property
205
+ def m_y(self):
206
+ """Return my."""
207
+ if self.forces is None:
208
+ return None
209
+ return self.forces[:, 1]
210
+
211
+ @property
212
+ def m_z(self):
213
+ """Return mz."""
214
+ if self.forces is None:
215
+ return None
216
+ return self.forces[:, 2]
217
+
218
+ @property
219
+ def e_a(self):
220
+ """Return ea."""
221
+ if self.strains is None:
222
+ return None
223
+ return self.strains[:, 0]
224
+
225
+ @property
226
+ def k_y(self):
227
+ """Return ky."""
228
+ if self.strains is None:
229
+ return None
230
+ return self.strains[:, 1]
231
+
232
+ @property
233
+ def k_z(self):
234
+ """Return kz."""
235
+ if self.strains is None:
236
+ return None
237
+ return self.strains[:, 2]
238
+
239
+
240
+ @dataclass
241
+ class NMMInteractionDomain(InteractionDomain):
179
242
  """Class for storing the NMM interaction domain results."""
180
243
 
181
244
  num_theta: int = 0 # number of discretizations along the angle
182
245
  num_axial: int = 0 # number of discretizations along axial load axis
183
246
 
184
- strains: ArrayLike = None # array with shape (n,3) containing strains
185
- forces: ArrayLike = None # array with shape(n,3) containing N, My, Mz
186
-
187
247
 
188
248
  @dataclass
189
- class NMInteractionDomain:
249
+ class NMInteractionDomain(InteractionDomain):
190
250
  """Class for storing the NM interaction domain results."""
191
251
 
192
252
  theta: float = 0 # the inclination of n.a.
193
253
  num_axial: float = 0 # number of discretizations along axial load axis
194
254
 
195
- n: ArrayLike = None # Axial loads
196
- m_y: ArrayLike = None # Moments My
197
- m_z: ArrayLike = None # Moments Mz
198
-
199
- strains: ArrayLike = None
200
-
201
255
 
202
256
  @dataclass
203
- class MMInteractionDomain:
257
+ class MMInteractionDomain(InteractionDomain):
204
258
  """Class for storing the MM interaction domain results."""
205
259
 
206
260
  num_theta: float = 0 # number of discretizations along the angle
207
- n: float = 0 # axial load
208
-
209
- theta: ArrayLike = None # Angle theta respect axis Y
210
- m_y: ArrayLike = None # Moments My
211
- m_z: ArrayLike = None # Moments Mz
261
+ theta: ArrayLike = None # Array with shape (n,) containing the angle of NA
@@ -101,7 +101,7 @@ class ConstitutiveLaw(abc.ABC):
101
101
  @abc.abstractmethod
102
102
  def get_ultimate_strain(self) -> t.Tuple[float, float]:
103
103
  """Each constitutive law should provide a method to return the
104
- ultimate strain (positive and negative).
104
+ ultimate strain (negative and positive).
105
105
  """
106
106
 
107
107
  def preprocess_strains_with_limits(self, eps: ArrayLike) -> ArrayLike:
@@ -109,7 +109,7 @@ class ConstitutiveLaw(abc.ABC):
109
109
  near to ultimate strain limits to exactly ultimate strain limit.
110
110
  """
111
111
  eps = np.atleast_1d(np.asarray(eps))
112
- eps_max, eps_min = self.get_ultimate_strain()
112
+ eps_min, eps_max = self.get_ultimate_strain()
113
113
 
114
114
  idxs = np.isclose(eps, np.zeros_like(eps) + eps_max, atol=1e-6)
115
115
  eps[idxs] = eps_max
@@ -138,7 +138,7 @@ class ConstitutiveLaw(abc.ABC):
138
138
  # All values are zero for x > 0
139
139
  return None
140
140
 
141
- eps_max, eps_min = self.get_ultimate_strain()
141
+ eps_min, eps_max = self.get_ultimate_strain()
142
142
  eps_max = min(eps_max, 1)
143
143
  # Analise positive branch
144
144
  eps = np.linspace(0, eps_max, 10000)
@@ -7,6 +7,7 @@ from ._geometry import (
7
7
  SurfaceGeometry,
8
8
  create_line_point_angle,
9
9
  )
10
+ from ._reinforcement import add_reinforcement, add_reinforcement_line
10
11
  from ._steel_sections import HE, IPE, IPN, UB, UBP, UC, UPN
11
12
 
12
13
  __all__ = [
@@ -22,4 +23,6 @@ __all__ = [
22
23
  'UBP',
23
24
  'IPN',
24
25
  'UPN',
26
+ 'add_reinforcement',
27
+ 'add_reinforcement_line',
25
28
  ]
@@ -22,17 +22,6 @@ from structuralcodes.core.base import ConstitutiveLaw, Material
22
22
  from structuralcodes.materials.concrete import Concrete
23
23
  from structuralcodes.materials.constitutive_laws import Elastic
24
24
 
25
- # Useful classes and functions: where to put?????? (core?
26
- # utility folder in sections? here in this file?)
27
- # Polygons, LineStrings, Points, MultiLyneStrings, MultiPolygons etc.
28
-
29
- # to think: dataclass or class?
30
- # Note that some things are already computed (like area) by shapely
31
- # like: polygon.area, polygon.centroid, etc.
32
-
33
- # For now dataclass, if we need convert to regular class,
34
- # init commented for now
35
-
36
25
 
37
26
  class Geometry:
38
27
  """Base class for a geometry object."""
@@ -154,11 +143,12 @@ class PointGeometry(Geometry):
154
143
  self._density = density
155
144
  if isinstance(material, Material):
156
145
  self._density = material.density
157
- material = material.constitutive_law
146
+ self._material = material.constitutive_law
147
+ elif isinstance(material, ConstitutiveLaw):
148
+ self._material = material
158
149
 
159
150
  self._point = point
160
151
  self._diameter = diameter
161
- self._material = material
162
152
  self._area = np.pi * diameter**2 / 4.0
163
153
 
164
154
  @property
@@ -172,7 +162,7 @@ class PointGeometry(Geometry):
172
162
  return self._area
173
163
 
174
164
  @property
175
- def material(self) -> Material:
165
+ def material(self) -> ConstitutiveLaw:
176
166
  """Returns the point material."""
177
167
  return self._material
178
168
 
@@ -341,10 +331,12 @@ class SurfaceGeometry:
341
331
  holes.
342
332
  """
343
333
 
334
+ _material: ConstitutiveLaw
335
+
344
336
  def __init__(
345
337
  self,
346
338
  poly: Polygon,
347
- mat: t.Union[Material, ConstitutiveLaw],
339
+ material: t.Union[Material, ConstitutiveLaw],
348
340
  density: t.Optional[float] = None,
349
341
  concrete: bool = False,
350
342
  ) -> None:
@@ -352,7 +344,7 @@ class SurfaceGeometry:
352
344
 
353
345
  Arguments:
354
346
  poly (shapely.Polygon): A Shapely polygon.
355
- mat (Union(Material, ConstitutiveLaw)): A Material or
347
+ material (Union(Material, ConstitutiveLaw)): A Material or
356
348
  ConsitutiveLaw class applied to the geometry.
357
349
  density (Optional(float)): When a ConstitutiveLaw is passed as mat,
358
350
  the density can be provided by this argument. When mat is a
@@ -365,25 +357,25 @@ class SurfaceGeometry:
365
357
  f'poly need to be a valid shapely.geometry.Polygon object. \
366
358
  {repr(poly)}'
367
359
  )
368
- if not isinstance(mat, Material) and not isinstance(
369
- mat, ConstitutiveLaw
360
+ if not isinstance(material, Material) and not isinstance(
361
+ material, ConstitutiveLaw
370
362
  ):
371
363
  raise TypeError(
372
364
  f'mat should be a valid structuralcodes.base.Material \
373
365
  or structuralcodes.base.ConstitutiveLaw object. \
374
- {repr(mat)}'
366
+ {repr(material)}'
375
367
  )
376
- self.polygon = poly
368
+ self._polygon = poly
377
369
  # Pass a constitutive law to the SurfaceGeometry
378
370
  self._density = density
379
- if isinstance(mat, Material):
380
- self._density = mat.density
381
- if isinstance(mat, Concrete):
371
+ if isinstance(material, Material):
372
+ self._density = material.density
373
+ if isinstance(material, Concrete):
382
374
  concrete = True
383
- mat = mat.constitutive_law
375
+ material = material.constitutive_law
384
376
 
385
- self.material = mat
386
- self.concrete = concrete
377
+ self._material = material
378
+ self._concrete = concrete
387
379
 
388
380
  @property
389
381
  def area(self) -> float:
@@ -408,6 +400,21 @@ class SurfaceGeometry:
408
400
  """Returns the density."""
409
401
  return self._density
410
402
 
403
+ @property
404
+ def material(self) -> ConstitutiveLaw:
405
+ """Returns the Constitutive law."""
406
+ return self._material
407
+
408
+ @property
409
+ def concrete(self) -> bool:
410
+ """Returns true if the geometry material is Concrete."""
411
+ return self._concrete
412
+
413
+ @property
414
+ def polygon(self) -> Polygon:
415
+ """Returns the Shapely Polygon."""
416
+ return self._polygon
417
+
411
418
  def calculate_extents(self) -> t.Tuple[float, float, float, float]:
412
419
  """Calculate extents of SurfaceGeometry.
413
420
 
@@ -528,7 +535,9 @@ class SurfaceGeometry:
528
535
  for g in other.geometries:
529
536
  sub_polygon = sub_polygon - g.polygon
530
537
 
531
- return SurfaceGeometry(poly=sub_polygon, mat=material, density=density)
538
+ return SurfaceGeometry(
539
+ poly=sub_polygon, material=material, density=density
540
+ )
532
541
 
533
542
  def _repr_svg_(self) -> str:
534
543
  """Returns the svg representation."""
@@ -546,7 +555,7 @@ class SurfaceGeometry:
546
555
  """
547
556
  return SurfaceGeometry(
548
557
  poly=affinity.translate(self.polygon, dx, dy),
549
- mat=self.material,
558
+ material=self.material,
550
559
  density=self._density,
551
560
  )
552
561
 
@@ -573,7 +582,7 @@ class SurfaceGeometry:
573
582
  poly=affinity.rotate(
574
583
  self.polygon, angle, origin=point, use_radians=use_radians
575
584
  ),
576
- mat=self.material,
585
+ material=self.material,
577
586
  density=self._density,
578
587
  )
579
588
 
@@ -616,7 +625,7 @@ class SurfaceGeometry:
616
625
  new_material = Elastic(E=geo.material.get_tangent(eps=0)[0])
617
626
 
618
627
  return SurfaceGeometry(
619
- poly=geo.polygon, mat=new_material, density=geo._density
628
+ poly=geo.polygon, material=new_material, density=geo._density
620
629
  )
621
630
 
622
631
  # here we can also add static methods like:
@@ -635,13 +644,15 @@ def _process_geometries_multipolygon(
635
644
  materials: t.Optional[
636
645
  t.Union[t.List[Material], Material, ConstitutiveLaw]
637
646
  ],
638
- ) -> list:
647
+ ) -> list[Geometry]:
639
648
  """Process geometries for initialization."""
640
649
  checked_geometries = []
641
650
  # a MultiPolygon is provided
642
651
  if isinstance(materials, (ConstitutiveLaw, Material)):
643
652
  for g in geometries.geoms:
644
- checked_geometries.append(SurfaceGeometry(poly=g, mat=materials))
653
+ checked_geometries.append(
654
+ SurfaceGeometry(poly=g, material=materials)
655
+ )
645
656
  elif isinstance(materials, list):
646
657
  # the list of materials is provided, one for each polygon
647
658
  if len(geometries.geoms) != len(materials):
@@ -649,13 +660,13 @@ def _process_geometries_multipolygon(
649
660
  'geometries and materials should have the same length'
650
661
  )
651
662
  for g, m in zip(geometries.geoms, materials):
652
- checked_geometries.append(SurfaceGeometry(poly=g, mat=m))
663
+ checked_geometries.append(SurfaceGeometry(poly=g, material=m))
653
664
  return checked_geometries
654
665
 
655
666
 
656
667
  def _process_geometries_list(
657
668
  geometries: t.List[Geometry],
658
- ) -> t.Tuple[list, list]:
669
+ ) -> t.Tuple[list[SurfaceGeometry], list[PointGeometry]]:
659
670
  """Process geometries for initialization."""
660
671
  # a list of SurfaceGeometry is provided
661
672
  checked_geometries = []
@@ -680,6 +691,8 @@ class CompoundGeometry(Geometry):
680
691
  properties.
681
692
  """
682
693
 
694
+ geometries: t.List[Geometry]
695
+
683
696
  def __init__(
684
697
  self,
685
698
  geometries: t.Union[t.List[Geometry], MultiPolygon],
@@ -689,8 +702,9 @@ class CompoundGeometry(Geometry):
689
702
 
690
703
  Arguments:
691
704
  geometries (Union(List(Geometry), MultiPolygon)): A list of
692
- SurfaceGeometry objects or a shapely MultiPolygon object
693
- (in this case also a list of materials should be given).
705
+ Geometry objects (i.e. PointGeometry or SurfaceGeometry) or a
706
+ shapely MultiPolygon object (in this latter case also a list of
707
+ materials should be given).
694
708
  materials (Optional(List(Material), Material)): A material (applied
695
709
  to all polygons) or a list of materials. In this case the
696
710
  number of polygons should match the number of materials.
@@ -712,7 +726,7 @@ class CompoundGeometry(Geometry):
712
726
  # useful for representation in svg
713
727
  geoms_representation = [g.polygon for g in self.geometries]
714
728
  geoms_representation += [
715
- pg._point.buffer(pg._diameter / 2)
729
+ pg.point.buffer(pg.diameter / 2)
716
730
  for pg in self.point_geometries
717
731
  ]
718
732
  self.geom = MultiPolygon(geoms_representation)
@@ -228,8 +228,8 @@ class BaseProfile:
228
228
  xmax = max(abs(bounds[0]), bounds[2])
229
229
  ymax = max(abs(bounds[1]), bounds[3])
230
230
  # Then compute section modulus
231
- self._Wely = self._Iy / ymax
232
- self._Welz = self._Iz / xmax
231
+ self._Wely = self.Iy / ymax
232
+ self._Welz = self.Iz / xmax
233
233
 
234
234
  @property
235
235
  def Wely(self) -> float:
@@ -64,11 +64,11 @@ class Elastic(ConstitutiveLaw):
64
64
  return strains, coeff
65
65
 
66
66
  def get_ultimate_strain(self, **kwargs) -> t.Tuple[float, float]:
67
- """Return the ultimate strain (positive and negative)."""
67
+ """Return the ultimate strain (negative and positive)."""
68
68
  # There is no real strain limit, so set it to very large values
69
69
  # unlesse specified by the user differently
70
70
  del kwargs
71
- return self._eps_su or (100, -100)
71
+ return self._eps_su or (-100, 100)
72
72
 
73
73
  def set_ultimate_strain(
74
74
  self, eps_su=t.Union[float, t.Tuple[float, float]]
@@ -76,19 +76,20 @@ class Elastic(ConstitutiveLaw):
76
76
  """Set ultimate strains for Elastic Material if needed.
77
77
 
78
78
  Arguments:
79
- eps_su (float or (float, float)): Defining ultimate strain if a
80
- single value is provided the same is adopted for both positive
81
- and negative strains.
79
+ eps_su (float or (float, float)): Defining ultimate strain. If a
80
+ single value is provided the same is adopted for both negative
81
+ and positive strains. If a tuple is provided, it should be
82
+ given as (negative, positive).
82
83
  """
83
84
  if isinstance(eps_su, float):
84
- self._eps_su = (abs(eps_su), -abs(eps_su))
85
+ self._eps_su = (-abs(eps_su), abs(eps_su))
85
86
  elif isinstance(eps_su, tuple):
86
87
  if len(eps_su) < 2:
87
88
  raise ValueError(
88
89
  'Two values need to be provided when setting the tuple'
89
90
  )
90
- eps_su_p = eps_su[0]
91
- eps_su_n = eps_su[1]
91
+ eps_su_n = eps_su[0]
92
+ eps_su_p = eps_su[1]
92
93
  if eps_su_p < eps_su_n:
93
94
  eps_su_p, eps_su_n = eps_su_n, eps_su_p
94
95
  if eps_su_p < 0:
@@ -99,7 +100,7 @@ class Elastic(ConstitutiveLaw):
99
100
  raise ValueError(
100
101
  'Negative utimate strain should be non-positive'
101
102
  )
102
- self._eps_su = (eps_su_p, eps_su_n)
103
+ self._eps_su = (eps_su_n, eps_su_p)
103
104
  else:
104
105
  raise ValueError(
105
106
  'set_ultimate_strain requires a single value or a tuple \
@@ -185,8 +186,8 @@ class ElasticPlastic(ConstitutiveLaw):
185
186
  """
186
187
  strains = []
187
188
  coeff = []
188
- eps_sy_p, eps_sy_n = self.get_ultimate_strain(yielding=True)
189
- eps_su_p, eps_su_n = self.get_ultimate_strain()
189
+ eps_sy_n, eps_sy_p = self.get_ultimate_strain(yielding=True)
190
+ eps_su_n, eps_su_p = self.get_ultimate_strain()
190
191
  if strain[1] == 0:
191
192
  # Uniform strain equal to strain[0]
192
193
  # Understand in which branch are we
@@ -233,13 +234,13 @@ class ElasticPlastic(ConstitutiveLaw):
233
234
  def get_ultimate_strain(
234
235
  self, yielding: bool = False
235
236
  ) -> t.Tuple[float, float]:
236
- """Return the ultimate strain (positive and negative)."""
237
+ """Return the ultimate strain (negative and positive)."""
237
238
  if yielding:
238
- return (self._eps_sy, -self._eps_sy)
239
+ return (-self._eps_sy, self._eps_sy)
239
240
  # If not specified eps
240
241
  if self._eps_su is None:
241
- return (self._eps_sy * 2, -self._eps_sy * 2)
242
- return (self._eps_su, -self._eps_su)
242
+ return (-self._eps_sy * 2, self._eps_sy * 2)
243
+ return (-self._eps_su, self._eps_su)
243
244
 
244
245
 
245
246
  class ParabolaRectangle(ConstitutiveLaw):
@@ -399,10 +400,10 @@ class ParabolaRectangle(ConstitutiveLaw):
399
400
  def get_ultimate_strain(
400
401
  self, yielding: bool = False
401
402
  ) -> t.Tuple[float, float]:
402
- """Return the ultimate strain (positive and negative)."""
403
+ """Return the ultimate strain (negative and positive)."""
403
404
  if yielding:
404
- return (100, self._eps_0)
405
- return (100, self._eps_u)
405
+ return (self._eps_0, 100)
406
+ return (self._eps_u, 100)
406
407
 
407
408
 
408
409
  class BilinearCompression(ConstitutiveLaw):
@@ -509,10 +510,10 @@ class BilinearCompression(ConstitutiveLaw):
509
510
  def get_ultimate_strain(
510
511
  self, yielding: bool = False
511
512
  ) -> t.Tuple[float, float]:
512
- """Return the ultimate strain (positive and negative)."""
513
+ """Return the ultimate strain (negative and positive)."""
513
514
  if yielding:
514
- return (100, self._eps_c)
515
- return (100, self._eps_cu)
515
+ return (self._eps_c, 100)
516
+ return (self._eps_cu, 100)
516
517
 
517
518
 
518
519
  class Sargin(ConstitutiveLaw):
@@ -602,10 +603,10 @@ class Sargin(ConstitutiveLaw):
602
603
  def get_ultimate_strain(
603
604
  self, yielding: bool = False
604
605
  ) -> t.Tuple[float, float]:
605
- """Return the ultimate strain (positive and negative)."""
606
+ """Return the ultimate strain (negative and positive)."""
606
607
  if yielding:
607
- return (100, self._eps_c1)
608
- return (100, self._eps_cu1)
608
+ return (self._eps_c1, 100)
609
+ return (self._eps_cu1, 100)
609
610
 
610
611
 
611
612
  class Popovics(ConstitutiveLaw):
@@ -713,10 +714,10 @@ class Popovics(ConstitutiveLaw):
713
714
  def get_ultimate_strain(
714
715
  self, yielding: bool = False
715
716
  ) -> t.Tuple[float, float]:
716
- """Return the ultimate strain (positive and negative)."""
717
+ """Return the ultimate strain (negative and positive)."""
717
718
  if yielding:
718
- return (100, self._eps_c)
719
- return (100, self._eps_cu)
719
+ return (self._eps_c, 100)
720
+ return (self._eps_cu, 100)
720
721
 
721
722
 
722
723
  class UserDefined(ConstitutiveLaw):
@@ -875,9 +876,9 @@ class UserDefined(ConstitutiveLaw):
875
876
  return strains, coeff
876
877
 
877
878
  def get_ultimate_strain(self, **kwargs) -> t.Tuple[float, float]:
878
- """Return the ultimate strain (positive and negative)."""
879
+ """Return the ultimate strain (negative and positive)."""
879
880
  del kwargs
880
- return (self._ultimate_strain_p, self._ultimate_strain_n)
881
+ return (self._ultimate_strain_n, self._ultimate_strain_p)
881
882
 
882
883
  def set_ultimate_strain(
883
884
  self, eps_su=t.Union[float, t.Tuple[float, float]]
@@ -885,9 +886,10 @@ class UserDefined(ConstitutiveLaw):
885
886
  """Set ultimate strains for Elastic Material if needed.
886
887
 
887
888
  Arguments:
888
- eps_su (float or (float, float)): Defining ultimate strain if a
889
- single value is provided the same is adopted for both positive
890
- and negative strains.
889
+ eps_su (float or (float, float)): Defining ultimate strain. If a
890
+ single value is provided the same is adopted for both negative
891
+ and positive strains. If a tuple is provided, it should be
892
+ given as (negative, positive).
891
893
  """
892
894
  if isinstance(eps_su, float):
893
895
  self._ultimate_strain_p = abs(eps_su)
@@ -897,8 +899,8 @@ class UserDefined(ConstitutiveLaw):
897
899
  raise ValueError(
898
900
  'Two values need to be provided when setting the tuple'
899
901
  )
900
- eps_su_p = eps_su[0]
901
- eps_su_n = eps_su[1]
902
+ eps_su_n = eps_su[0]
903
+ eps_su_p = eps_su[1]
902
904
  if eps_su_p < eps_su_n:
903
905
  eps_su_p, eps_su_n = eps_su_n, eps_su_p
904
906
  if eps_su_p < 0:
@@ -1,7 +1,6 @@
1
1
  """Main entry point for sections."""
2
2
 
3
3
  from ._generic import GenericSection, GenericSectionCalculator
4
- from ._reinforcement import add_reinforcement, add_reinforcement_line
5
4
  from .section_integrators import (
6
5
  FiberIntegrator,
7
6
  MarinIntegrator,
@@ -13,8 +12,6 @@ from .section_integrators import (
13
12
  __all__ = [
14
13
  'GenericSection',
15
14
  'GenericSectionCalculator',
16
- 'add_reinforcement',
17
- 'add_reinforcement_line',
18
15
  'SectionIntegrator',
19
16
  'FiberIntegrator',
20
17
  'MarinIntegrator',
@@ -291,14 +291,14 @@ class GenericSectionCalculator(SectionCalculator):
291
291
  for g in geom.geometries + geom.point_geometries:
292
292
  for other_g in geom.geometries + geom.point_geometries:
293
293
  # if g != other_g:
294
- eps_p = g.material.get_ultimate_strain(yielding=yielding)[0]
294
+ eps_p = g.material.get_ultimate_strain(yielding=yielding)[1]
295
295
  if isinstance(g, SurfaceGeometry):
296
296
  y_p = g.polygon.bounds[1]
297
297
  elif isinstance(g, PointGeometry):
298
298
  y_p = g._point.coords[0][1]
299
299
  eps_n = other_g.material.get_ultimate_strain(
300
300
  yielding=yielding
301
- )[1]
301
+ )[0]
302
302
  if isinstance(other_g, SurfaceGeometry):
303
303
  y_n = other_g.polygon.bounds[3]
304
304
  elif isinstance(other_g, PointGeometry):
@@ -610,8 +610,8 @@ class GenericSectionCalculator(SectionCalculator):
610
610
  axial load.
611
611
 
612
612
  Arguments:
613
- theta (float): Inclination of n.a. respect to section y axis,
614
- default = 0.
613
+ theta (float): Inclination of n.a. respect to section y axis in
614
+ radians, default = 0.
615
615
  n (float): Axial load applied to the section (+: tension, -:
616
616
  compression), default = 0.
617
617
 
@@ -667,7 +667,8 @@ class GenericSectionCalculator(SectionCalculator):
667
667
  n.a. and axial load.
668
668
 
669
669
  Arguments:
670
- theta (float): Inclination of n.a. respect to y axis, default = 0.
670
+ theta (float): Inclination of n.a. respect to y axis in radians,
671
+ default = 0.
671
672
  n (float): Axial load applied to the section (+: tension, -:
672
673
  compression), default = 0.
673
674
  chi_first (float): The first value of the curvature, default =
@@ -866,7 +867,7 @@ class GenericSectionCalculator(SectionCalculator):
866
867
  """Calculate the NM interaction domain.
867
868
 
868
869
  Arguments:
869
- theta (float): Inclination of n.a. respect to y axis
870
+ theta (float): Inclination of n.a. respect to y axis in radians
870
871
  (Optional, default = 0).
871
872
  num_1 (int): Number of strain profiles in field 1
872
873
  (Optional, default = 1).
@@ -913,7 +914,7 @@ class GenericSectionCalculator(SectionCalculator):
913
914
  )
914
915
 
915
916
  # Get ultimate strain profiles for theta angle
916
- strains = self._compute_ultimate_strain_profiles(
917
+ strains, field_num = self._compute_ultimate_strain_profiles(
917
918
  theta=theta,
918
919
  num_1=num_1,
919
920
  num_2=num_2,
@@ -948,9 +949,8 @@ class GenericSectionCalculator(SectionCalculator):
948
949
 
949
950
  # Save to results
950
951
  res.strains = strains
951
- res.m_z = forces[:, 2]
952
- res.m_y = forces[:, 1]
953
- res.n = forces[:, 0]
952
+ res.forces = forces
953
+ res.field_num = field_num
954
954
 
955
955
  return res
956
956
 
@@ -1047,6 +1047,9 @@ class GenericSectionCalculator(SectionCalculator):
1047
1047
  raise ValueError(f'Type of spacing not known: {type}')
1048
1048
 
1049
1049
  # For generation of fields 1 and 2 pivot on positive strain
1050
+ field_num = np.repeat(
1051
+ [1, 2, 3, 4, 5, 6], [num_1, num_2, num_3, num_4, num_5, num_6]
1052
+ )
1050
1053
  # Field 1: pivot on positive strain
1051
1054
  eps_n = _np_space(eps_p_b, 0, num_1, type_1, endpoint=False)
1052
1055
  eps_p = np.zeros_like(eps_n) + eps_p_b
@@ -1098,16 +1101,15 @@ class GenericSectionCalculator(SectionCalculator):
1098
1101
  eps_n = np.append(eps_n, eps_n_6)
1099
1102
  eps_p = np.append(eps_p, eps_p_6)
1100
1103
 
1101
- # rotate them
1104
+ # compute strain components
1102
1105
  kappa_y = (eps_n - eps_p) / (y_n - y_p)
1103
1106
  eps_a = eps_n - kappa_y * y_n
1104
- kappa_z = np.zeros_like(kappa_y)
1105
1107
 
1106
1108
  # rotate back components to work in section CRS
1107
1109
  T = np.array([[cos(theta), -sin(theta)], [sin(theta), cos(theta)]])
1108
- components = np.vstack((kappa_y, kappa_z))
1110
+ components = np.vstack((kappa_y, np.zeros_like(kappa_y)))
1109
1111
  rotated_components = T @ components
1110
- return np.column_stack((eps_a, rotated_components.T))
1112
+ return np.column_stack((eps_a, rotated_components.T)), field_num
1111
1113
 
1112
1114
  def calculate_nmm_interaction_domain(
1113
1115
  self,
@@ -1180,7 +1182,7 @@ class GenericSectionCalculator(SectionCalculator):
1180
1182
  strains = np.empty((0, 3))
1181
1183
  for theta in thetas:
1182
1184
  # Get ultimate strain profiles for theta angle
1183
- strain = self._compute_ultimate_strain_profiles(
1185
+ strain, field_num = self._compute_ultimate_strain_profiles(
1184
1186
  theta=theta,
1185
1187
  num_1=num_1,
1186
1188
  num_2=num_2,
@@ -1216,6 +1218,7 @@ class GenericSectionCalculator(SectionCalculator):
1216
1218
  # Save to results
1217
1219
  res.strains = strains
1218
1220
  res.forces = forces
1221
+ res.field_num = field_num
1219
1222
 
1220
1223
  return res
1221
1224
 
@@ -1234,16 +1237,21 @@ class GenericSectionCalculator(SectionCalculator):
1234
1237
  # Prepare the results
1235
1238
  res = s_res.MMInteractionDomain()
1236
1239
  res.num_theta = num_theta
1237
- res.n = n
1238
1240
  # Create array of thetas
1239
1241
  res.theta = np.linspace(0, np.pi * 2, num_theta)
1240
1242
  # Initialize the result's arrays
1241
- res.m_y = np.zeros_like(res.theta)
1242
- res.m_z = np.zeros_like(res.theta)
1243
+ res.forces = np.zeros((num_theta, 3))
1244
+ res.strains = np.zeros((num_theta, 3))
1243
1245
  # Compute strength for given angle of NA
1244
1246
  for i, th in enumerate(res.theta):
1245
1247
  res_bend_strength = self.calculate_bending_strength(theta=th, n=n)
1246
- res.m_y[i] = res_bend_strength.m_y
1247
- res.m_z[i] = res_bend_strength.m_z
1248
+ # Save forces
1249
+ res.forces[i, 0] = n
1250
+ res.forces[i, 1] = res_bend_strength.m_y
1251
+ res.forces[i, 2] = res_bend_strength.m_z
1252
+ # Save strains
1253
+ res.strains[i, 0] = res_bend_strength.eps_a
1254
+ res.strains[i, 1] = res_bend_strength.chi_y
1255
+ res.strains[i, 2] = res_bend_strength.chi_z
1248
1256
 
1249
1257
  return res
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: structuralcodes
3
- Version: 0.0.2
3
+ Version: 0.1.0
4
4
  Summary: A Python package that contains models from structural design codes.
5
5
  Author-email: fib - International Federation for Structural Concrete <info@fib-international.org>
6
6
  Requires-Python: >=3.8
@@ -17,11 +17,15 @@ Project-URL: source, https://github.com/fib-international/structuralcodes
17
17
 
18
18
  *A Python library for structural engineering calculations.*
19
19
 
20
- [![Build](https://github.com/fib-international/structuralcodes/actions/workflows/build.yaml/badge.svg)](https://github.com/fib-international/structuralcodes/actions/workflows/build.yaml)
20
+ [![PyPI version](https://badge.fury.io/py/structuralcodes.svg)](https://badge.fury.io/py/structuralcodes)
21
21
  [![Code style: Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/format.json)](https://github.com/charliermarsh/ruff)
22
22
  [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json)](https://github.com/charliermarsh/ruff)
23
23
  [![Docs](https://img.shields.io/badge/%F0%9F%93%9A%20docs-fib--international.github.io%2Fstructuralcodes%2F-orange)](https://fib-international.github.io/structuralcodes/)
24
24
 
25
+ [![Tests](https://github.com/fib-international/structuralcodes/actions/workflows/build.yaml/badge.svg)](https://github.com/fib-international/structuralcodes/actions/workflows/build.yaml)
26
+ [![Publish](https://github.com/fib-international/structuralcodes/actions/workflows/create-release.yml/badge.svg)](https://github.com/fib-international/structuralcodes/actions/workflows/create-release.yml)
27
+ [![Docs](https://github.com/fib-international/structuralcodes/actions/workflows/sphinx.yml/badge.svg)](https://github.com/fib-international/structuralcodes/actions/workflows/sphinx.yml)
28
+
25
29
  ## How to install
26
30
 
27
31
  StructuralCodes is available on PyPI and can be installed using `pip`:
@@ -38,3 +42,7 @@ Read the [quickstart guide](https://fib-international.github.io/structuralcodes/
38
42
 
39
43
  Check out our docs for how to get started [contributing](https://fib-international.github.io/structuralcodes/contributing/) to StructuralCodes.
40
44
 
45
+ ## Versioning
46
+
47
+ Check out our docs to see how we handle [versioning](https://fib-international.github.io/structuralcodes/versioning/).
48
+
@@ -1,5 +1,5 @@
1
- structuralcodes/__init__.py,sha256=iRaPZQiBsPlIMdiaTPyWPg9WtJ6izQ200aKbu4WXQpA,390
2
- structuralcodes/codes/__init__.py,sha256=D2lYPJ2IOgughWugFWQaHjz90zIl7gLX2Rrz53TvstA,1917
1
+ structuralcodes/__init__.py,sha256=opNmFZxtV5QKx38kj-eHuoZX-JPLuLojIGDH7au31WE,390
2
+ structuralcodes/codes/__init__.py,sha256=g5xMAJ3jEZHFd0cypvZY6lMCi7XeVEntsO8zHzI2mWc,2803
3
3
  structuralcodes/codes/ec2_2004/__init__.py,sha256=_PdL9kX7qlfn9VBfqUCJQy6V9fWmIyugzIiSZczjNAA,1986
4
4
  structuralcodes/codes/ec2_2004/_concrete_material_properties.py,sha256=Ol51tzcVOHUvc2Vea24WQJ4FABxXc-9cB5RVu2N1pio,5964
5
5
  structuralcodes/codes/ec2_2004/_reinforcement_material_properties.py,sha256=_ZlvdHcOswu1Ge1XjSvt4j5ue-znDceMOlA0s528IqM,2779
@@ -18,14 +18,16 @@ structuralcodes/codes/mc2010/_concrete_punching.py,sha256=ZG2P7WXIRcFh5sGlmHP20B
18
18
  structuralcodes/codes/mc2010/_concrete_shear.py,sha256=HZhhpzQd-09NPrHpSK5fnPA1epVCQH8JfdSi6bpgiFg,21883
19
19
  structuralcodes/codes/mc2010/_concrete_torsion.py,sha256=QBFnCYTOnP6FjN0MgX9iBrXN1yuELxQVP2TeDXDB7PM,4523
20
20
  structuralcodes/codes/mc2010/_reinforcement_material_properties.py,sha256=FELmgMcCrHlajGwQNRRHb8nqKnMCsLso1OyjonsTAdk,2842
21
+ structuralcodes/codes/mc2020/__init__.py,sha256=5hrAfBtAeG69N_lroFpG10_ZKB1SuNlKBnuHug2DI3E,174
21
22
  structuralcodes/core/__init__.py,sha256=spnvZIm_w3jX_lV-v3bloDjgHh8lrH6UHpA1Nv1zeAI,55
22
- structuralcodes/core/_section_results.py,sha256=FCYivFY6e2JFF5Nl9dUZNytgTT_-UeDOEc79czX1Lxg,6978
23
- structuralcodes/core/base.py,sha256=0QcYgYtmU5fK039a8QxrPM4Fa-ZpMUp2ez0XmpaCT_0,8562
24
- structuralcodes/geometry/__init__.py,sha256=B6zKtPQy_OgsKz-fhbvZs7gSnADQPNTQwCMDF1yp-Lo,434
25
- structuralcodes/geometry/_geometry.py,sha256=PcSMaxlacEJ5jxURWaVFtL3AxOpzbykMtDtnEi04iAU,30818
26
- structuralcodes/geometry/_steel_sections.py,sha256=RjecVItWDjKT2rC2U6cvo0NtNHuUygY1pfybLUKJBUM,60227
23
+ structuralcodes/core/_section_results.py,sha256=hHXoS71TpSmWqw276pBeLiLrEEiMEWclOd28ArRW_Kk,8280
24
+ structuralcodes/core/base.py,sha256=TqgsXzIXITQmER3tKNjzjPrbSN5uT_PqCpG3PVMd3Yw,8562
25
+ structuralcodes/geometry/__init__.py,sha256=FwzywfyGKOk6v96ZyOfyBo5iVeuK_W0TQVz5llAkYW4,559
26
+ structuralcodes/geometry/_geometry.py,sha256=kmY7TER5yS-TVw0XY4EuwNviLuEhExtLoon8oO2TJbA,31175
27
+ structuralcodes/geometry/_reinforcement.py,sha256=8Xes3N8zm85bJVu_bgVNzMs4zo2zPv17xadw2I9CnE0,3739
28
+ structuralcodes/geometry/_steel_sections.py,sha256=UdJmhhnK8r5gEfBzvWsMFHGs5gmuoOhFoduBanlRMQg,60225
27
29
  structuralcodes/materials/__init__.py,sha256=r5E5vsXVKB-BGZXTnEbsrYJbH6rr6Xlc_b4LlcUPIbc,173
28
- structuralcodes/materials/constitutive_laws.py,sha256=JSCbBvFbDQqDkqiOtmJykHgtKqxRZu8Fh0GMpQP4gSU,34285
30
+ structuralcodes/materials/constitutive_laws.py,sha256=DX_yuC4OpgDCl_g3sl0Hi2ee7lFD2fy3yO4PTINKBzE,34455
29
31
  structuralcodes/materials/concrete/__init__.py,sha256=GRD5WcbYrnE4iN-L7qVkhVTi7w_PUP7pnbGueOaVeFs,2576
30
32
  structuralcodes/materials/concrete/_concrete.py,sha256=3zMTFtaqFeUl7ne7-pe9wF4LryzqvGpUwThnHTidCZU,3774
31
33
  structuralcodes/materials/concrete/_concreteEC2_2004.py,sha256=gWG3O9yeGw3UeftCjYTajZco_XYDmBxPGqhAEpH3nY0,14666
@@ -36,15 +38,14 @@ structuralcodes/materials/reinforcement/_reinforcement.py,sha256=zrSdBvHKTYqOHpk
36
38
  structuralcodes/materials/reinforcement/_reinforcementEC2_2004.py,sha256=svLpubjaTH_DepwY68TQIA8fRwadoAE3Y3KsyViGQHk,3265
37
39
  structuralcodes/materials/reinforcement/_reinforcementEC2_2023.py,sha256=3tKpFcMNYK52s5K2K_PctRcuSgwZTe-QXX3xziHPUno,2887
38
40
  structuralcodes/materials/reinforcement/_reinforcementMC2010.py,sha256=az_IAQJNKSF6Vv9KMoXjWTdYkWI6xcEm7s8i8GETn3A,2939
39
- structuralcodes/sections/__init__.py,sha256=401eBeFFzG-Xvj26HUIT_SwuSsSz_nERrl9pj129oME,566
40
- structuralcodes/sections/_generic.py,sha256=f-F9yMIxByY18s6RnHExv2Pvhe_dsCInXiaS6Phb74E,49033
41
- structuralcodes/sections/_reinforcement.py,sha256=8Xes3N8zm85bJVu_bgVNzMs4zo2zPv17xadw2I9CnE0,3739
41
+ structuralcodes/sections/__init__.py,sha256=qPoD5eS31at-uveYtxtVkXGLNHPrIMRrxGYY3wOLQ4s,441
42
+ structuralcodes/sections/_generic.py,sha256=UHbj7r2fsBvT4DVDWkxpaQck82cQiV0XFkY8N-g_eYI,49479
42
43
  structuralcodes/sections/section_integrators/__init__.py,sha256=PK4ixV0XrfHXN-itIrB1r90npoWo3aIJqMcenqcaees,399
43
44
  structuralcodes/sections/section_integrators/_factory.py,sha256=MHp14hfWU-oXTiIutCKLJEC47LirYsHgEAAmHVtnFMY,1242
44
45
  structuralcodes/sections/section_integrators/_fiber_integrator.py,sha256=5sxcfbmxMJfy45Pfd84I4sAh6HpPc5B4r6ab0WpjSNI,9305
45
46
  structuralcodes/sections/section_integrators/_marin_integration.py,sha256=SZgya6d_Tequ3jez7UEBlYioZepW2IDKaAxn_6WrMbU,1563
46
47
  structuralcodes/sections/section_integrators/_marin_integrator.py,sha256=rtEtd1X0QYAWNcaH7Pjd_b6LEzVjHqD_fbIrX64p7fg,9121
47
48
  structuralcodes/sections/section_integrators/_section_integrator.py,sha256=O-jsG1Pu_doovgRJsFG1Sf0KlkN2wNfQdmgkJiSHNN0,1590
48
- structuralcodes-0.0.2.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
49
- structuralcodes-0.0.2.dist-info/METADATA,sha256=PCsKsfFf9xBbl9Ab2W0xumVRjJvNyFOHmhU96mSnS-E,1811
50
- structuralcodes-0.0.2.dist-info/RECORD,,
49
+ structuralcodes-0.1.0.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
50
+ structuralcodes-0.1.0.dist-info/METADATA,sha256=z10AmhI6UDYF_wu-kJTNvLYcmSu_nhn1WBNiodbrm8M,2444
51
+ structuralcodes-0.1.0.dist-info/RECORD,,