Skip to content

feat: add ACI 318-19 concrete and reinforcement material properties#343

Open
gabe-kafka wants to merge 1 commit into
fib-international:devfrom
gabe-kafka:feature/aci318-material-properties
Open

feat: add ACI 318-19 concrete and reinforcement material properties#343
gabe-kafka wants to merge 1 commit into
fib-international:devfrom
gabe-kafka:feature/aci318-material-properties

Conversation

@gabe-kafka

Copy link
Copy Markdown

Summary

Adds the first American design code to the library — ACI 318-19 material properties from Chapters 19, 20, and 22.

  • Concrete: Ec, fr, beta1, eps_cu, alpha1, fct, lambda_factor — all as standalone functions following the existing EC2/MC2010 patterns
  • Reinforcement: Es, fy_design, epsyd, reinforcement_grade_props (ASTM grade lookup)
  • Material classes: ConcreteACI318 and ReinforcementACI318 with constitutive law support (elastic, parabola-rectangle, bilinear for concrete; elastic, elastic-plastic for steel)
  • 73 new tests, all passing. Zero regressions on the existing 10,113 tests.

All units are SI (MPa, kg/m³) to match library conventions.

ACI vs Eurocode safety philosophy

ACI 318 uses LRFD — strength reduction factors (φ) are applied at member capacity level, not material level. The material classes default gamma_c=1.0 and gamma_s=1.0. This is documented in class docstrings. See discussion in #187 about long-term architectural implications.

Scope

Material properties only. No member design (shear, flexure, etc.) in this PR — that can follow once the architecture question in #187 is resolved.

Closes #187 (partially — first code added, more can follow).

Test plan

  • make form — ruff formatting passes
  • make lint — ruff linting passes
  • make test — 10,186 passed (73 new + 10,113 existing), 1 pre-existing failure in EC2 2023
  • Round-trip: set_design_code('aci318')create_concrete(fck=28).Ec returns correct value
  • Factory dispatch works for both concrete and reinforcement
  • All three constitutive laws instantiate correctly

@mortenengen mortenengen moved this to New ✨ in PR tracker Apr 9, 2026
@mortenengen mortenengen added the enhancement New feature or request label Apr 9, 2026
@gabe-kafka gabe-kafka force-pushed the feature/aci318-material-properties branch from b91e308 to f4d0636 Compare April 15, 2026 15:30
Add the first American design code to the library. Implements
material properties from ACI 318-19 Chapters 19, 20, and 22:

Concrete (codes/aci318):
- Ec: modulus of elasticity (Table 19.2.2.1)
- fr: modulus of rupture (Eq. 19.2.3.1)
- beta1: Whitney stress block depth factor (Table 22.2.2.4.3)
- eps_cu: ultimate concrete strain (Section 22.2.2.1)
- alpha1: stress block intensity (Section 22.2.2.4.1)
- fct: splitting tensile strength (Section 19.2.4.3)
- lambda_factor: lightweight modification factor (Table 19.2.4.2)

Reinforcement (codes/aci318):
- Es: modulus of elasticity (Section 20.2.2.2)
- fy_design: design yield strength
- epsyd: yield strain
- reinforcement_grade_props: ASTM grade lookup (Table 20.2.2.4a)

Material classes:
- ConcreteACI318 with elastic, parabola-rectangle, and bilinear
  constitutive laws
- ReinforcementACI318 with elastic and elastic-plastic laws

ACI 318 uses LRFD (strength reduction factors on member capacity)
rather than partial safety factors on material strength. Material
classes default gamma_c=1.0 and gamma_s=1.0 accordingly.

Ref: fib-international#187
@gabe-kafka gabe-kafka force-pushed the feature/aci318-material-properties branch from f4d0636 to a9987bf Compare April 15, 2026 19:37
@mortenengen mortenengen moved this from New ✨ to Under review 👀 in PR tracker Apr 17, 2026
import typing as t

from . import ec2_2004, ec2_2023, mc2010, mc2020
from . import aci318, ec2_2004, ec2_2023, mc2010, mc2020

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor comment about the naming.

For Eurocode 2 we decided to implement different generations in different modules, e.g. ec2_2004 for the first generation that was released in 2004, and ec2_2023 for the second generation that was released in 2023. The second generation is expected to become accepted as national governing design code around Europe some time in the period 2027-2028.

Please correct me if I am wrong here, but my impression is that the 19 in ACI318-19 indicates that it was released in 2019. Similarly, a slightly newer version was released in 2025 and named ACI318-25. If this is right, please consider indicating this in the name of the module, e.g. aci318_19 instead of just aci318.

@aperezcaldentey aperezcaldentey Jun 1, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Morten, I agree with your comment. We need to keep different versions separate. When there is no change we can use the method of the previous version, but we may need to update the references and son on. So, yes it should be aci318-19

from ._concrete import Concrete


class ConcreteACI318(Concrete): # noqa: N801

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same minor comment as above, please consider renaming the class to ConcreteACI318_19.

from ._reinforcement import Reinforcement


class ReinforcementACI318(Reinforcement): # noqa: N801

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same minor comment as above, please consider renaming the class to ReinforcementACI318_19.

self._fct = abs(fct) if fct is not None else None
self._wc = wc
self._lambda_s = lambda_s

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the other concrete classes we have implemented a .__post_init__ method which is responsible for validating the parameters that are set. See for example here. Please consider implementing a similar method here.

"""
return {
'fc': self.fcd(),
'eps_0': 0.002,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a clause in the code that sets the value eps_0 = 0.002? If it is, please consider implementing a function in the code module similar as for eps_cu. Furthermore, the value of eps_0 should preferably be taken from a property on this concrete class such that it is more transparent for the user what is routed to the constitutive laws. Please consider implementing this property, possibly calling the function for eps_0 in the code module.

"""
return {
'fc': self.fcd(),
'eps_c': 0.002,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as for the comment related to eps_0 above.

return fy / _Es


def reinforcement_grade_props(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does ACI 318-19 also provide values for the strain at ultimate strength? If so, please consider including it in the return dictionary.

@mortenengen mortenengen left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this very nice contribution @gabe-kafka. I have left a couple of comments for you to consider while finalizing the PR. Please note that @talledodiego and @aperezcaldentey have also agreed to provide their reviews.

@mortenengen

Copy link
Copy Markdown
Member

@gabe-kafka, in the above message you provided the following comment:

* [x]  `make test` — 10,186 passed (73 new + 10,113 existing), 1 pre-existing failure in EC2 2023

However, I have not experienced any pre-existing failues in the pipeline. Please elaborate so that we can triage this.


def fct(fc: float, lambda_s: float = 1.0) -> float:
"""The approximate splitting tensile strength of concrete.

@aperezcaldentey aperezcaldentey Jun 1, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The formula used in this method does not correspond to ACI 318-19, Section 19.2.4.3, as stated. We should possibly delete this method as fct is not defined in ACI 318-19.

@@ -0,0 +1,164 @@
"""Concrete material properties according to ACI 318-19."""

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest we order the methods as they appear in the code. Let's not jump from Chaper 19 to 22 and then back to 19.

if fc >= 55:
return 0.65
return 0.85 - 0.05 * (fc - 28) / 7

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace expression: 0.85 - 0.05 * (fc - 28) / 7 by 0.85-0.20/27*(fc-28) - why simplify and lose accuracy? I could drive some people crazy...


Raises:
ValueError: If fc is not positive.
"""

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to Table 22.2.2.4.3. f'c must be greater that 17. We should set this as a lower limit. Not 0.


def Ec(fc: float, wc: float = 2320.0) -> float:
"""The modulus of elasticity of concrete.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Default value of concrete. The typical specific weight of concrete used in North American practice is 150 lbs/cf, which I roughly equivalent to a density of 150/(2.2*0.3048^3)≈2400 kg/m3. I understand this my have to do with going from specific weight to density using g=9.81 m/s2, but it is not clear for me how you derive it, as the units are not consistent. On the other hand, Eq. 19.2.2.1b provides a factor of 4700 which would lead to a value of w=2286 kg/m3.

'sand-lightweight': 0.85,
'all-lightweight': 0.75,
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lambda factor in ACI-318-19 is a function of the weight and lies between 1.00 and 0.75. You provide 'sand-lightweight as an intermediate value. Consider programing the equation as a function of the weight (as done for the modulus of elasticity) or adding the values from Table 19.2.4.1 'lightweight, fine blend' and 'sand-lightweight, corase blend'

alpha1,
beta1,
eps_cu,
fct,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fct not defined in ACI 318-19

'80': {'fy': 550.0, 'fu': 690.0},
'100': {'fy': 690.0, 'fu': 860.0},
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Values for Reinforcement grades based on table 20.2.1.3(a). Please add this reference to comments.


def fy_design(fy: float, phi: float = 1.0) -> float:
"""The design yield strength of reinforcement.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As you say, ACI does not consider a partial factor on steel. Why do we need to provide one? The user should not be able to introduce a value different that 1.00. If we need this method for compatibility reasons we shoudl delete phi as a KWARG.

characteristic strength (fck). The parameter is named fck for
compatibility with the base class, but represents fc' in ACI
notation. An fc property is provided as an alias.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that fck is a 95% fractile, while f'c is a 90% fractile

gamma_c defaults to 1.0 and should be left at 1.0 for
standard ACI 318 design. Reducing fc' via gamma_c is not
ACI 318 compliant.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, so don't give the user the possibility of doing otherwise.

(default: 2320).
gamma_c (float, optional): Partial factor for concrete.
Default is 1.0 (ACI does not use material partial
factors).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we should not give the user this possibility. Delete parameter or do we need it for compatibility reasons? If so we should somehow force the user to input a value of 1.0.

Ec (float, optional): The modulus of elasticity in MPa.
fr (float, optional): The modulus of rupture in MPa.
fct (float, optional): The splitting tensile strength
in MPa.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As commented above fct is not defined in ACI 318-19. We should not use it.

fct (float, optional): The splitting tensile strength
in MPa.
wc (float): Unit weight of concrete in kg/m3
(default: 2320).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Justify default value.

Default is 1.0 for ACI 318 (no material partial
factor).
"""
return self._gamma_s or 1.0

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As commented on before for ACI we should set all material partial factors to 1.00 and not allow the user to change them. I don't think any good will come from this freedom.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

Status: Under review 👀

Development

Successfully merging this pull request may close these issues.

3 participants