Skip to content

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

Open
gabe-kafka wants to merge 2 commits 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 2 commits into
fib-international:devfrom
gabe-kafka:feature/aci318-material-properties

Conversation

@gabe-kafka

@gabe-kafka gabe-kafka commented Apr 8, 2026

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.

  • Design code module: structuralcodes.codes.aci318_19, registered as aci318_19.
  • Concrete functions: Ec, fr, lambda_factor, eps_cu, eps_c0, alpha1, and beta1 as standalone functions following existing EC2/MC2010 patterns. fct is intentionally not included because ACI 318-19 does not define it.
  • Reinforcement functions: Es, fy_design, epsyd, and reinforcement_grade_props for the ACI 318-19 Table 20.2.1.3(a) grade lookup.
  • Material classes: ConcreteACI318_19 and ReinforcementACI318_19 with constitutive law support (elastic, parabola-rectangle, and bilinear for concrete; elastic and elastic-plastic for steel).
  • ACI customary inputs: unit conversion helpers plus ConcreteACI318_19.from_psi(...), ReinforcementACI318_19.from_grade(...), and ReinforcementACI318_19.from_ksi(...); values are converted at the API boundary and stored internally in SI units.

All direct code/material APIs use SI internally (MPa, kg/m3). ConcreteACI318_19 accepts ACI fc directly; fck remains available only for compatibility with the shared concrete factory/base API.

ACI vs Eurocode safety philosophy

ACI 318 uses LRFD. Strength reduction factors (phi) are applied at member capacity level, not material level. The ACI material classes keep gamma_c and gamma_s for compatibility with the shared material APIs, but values other than None or 1.0 now raise ValueError.

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

  • ./venv/bin/python -m ruff check structuralcodes/codes/aci318_19 structuralcodes/materials/concrete/_concreteACI318_19.py structuralcodes/materials/reinforcement/_reinforcementACI318_19.py tests/test_aci318_19 - passes
  • ./venv/bin/python -m ruff format --check structuralcodes/codes/aci318_19 structuralcodes/materials/concrete/_concreteACI318_19.py structuralcodes/materials/reinforcement/_reinforcementACI318_19.py tests/test_aci318_19 - passes, 11 files already formatted
  • ./venv/bin/python -m pytest tests/test_aci318_19 - 84 passed
  • ./venv/bin/python -m pytest - ran locally; 10,240 passed and 1 EC2 2023 expectation mismatch reproduced only in this workspace so far, not in CI: tests/test_ec2_2023/test_ec2_2023_section5_materials.py::test_eps_cs_50y[25-dry-800-CN-0.46333] returned 0.45333333333333337 vs expected 0.46333
  • Round-trip: set_design_code('aci318_19') -> create_concrete(fck=28) -> .Ec returns the ACI normalweight value
  • Factory dispatch works for both concrete and reinforcement
  • Concrete and reinforcement 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 2 times, most recently 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
Comment thread structuralcodes/codes/__init__.py Outdated
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

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

this convention makes sense and you are right about the -19 -25 signaling release year. I will focus on ACI318-19 for now.

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.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

understood and fixed

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.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

done

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.

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 a9987bf to abb8aa5 Compare June 13, 2026 21:05
@gabe-kafka

Copy link
Copy Markdown
Author

Pushed a rebased update against current dev addressing the ACI 318-19 review comments.

Summary of the changes:

  • Versioned the code module and classes as aci318_19, ConcreteACI318_19, and ReinforcementACI318_19.
  • Removed fct, reordered the ACI concrete functions by code sequence, used the requested exact beta1 expression, and enforced the 17 MPa lower limit.
  • Separated base material density from ACI equilibrium density wc; Ec and lambda_factor now use the ACI density path where applicable.
  • Kept gamma_c and gamma_s only for shared API compatibility; non-1.0 values now raise ValueError.
  • Added the reinforcement grade table reference, and did not add grade-level ultimate strain because ACI 318-19 Table 20.2.1.3(a) does not provide a single value independent of reinforcement specification and bar size.
  • Added customary-unit helpers plus ConcreteACI318_19.from_psi(...), ReinforcementACI318_19.from_grade(...), and ReinforcementACI318_19.from_ksi(...), with SI storage internally.

Verification after rebasing:

  • ./venv/bin/python -m ruff check structuralcodes/codes/aci318_19 structuralcodes/materials/concrete/_concreteACI318_19.py structuralcodes/materials/reinforcement/_reinforcementACI318_19.py tests/test_aci318_19 - passed
  • ./venv/bin/python -m ruff format --check structuralcodes/codes/aci318_19 structuralcodes/materials/concrete/_concreteACI318_19.py structuralcodes/materials/reinforcement/_reinforcementACI318_19.py tests/test_aci318_19 - passed
  • ./venv/bin/python -m pytest tests/test_aci318_19 - 84 passed
  • ./venv/bin/python -m pytest - 10,240 passed, 1 local EC2 2023 mismatch

For the EC2 2023 point: I should not have described this as a confirmed upstream/pipeline failure. It is reproducible locally in this workspace, while CI does not appear to show it. The exact local failure is tests/test_ec2_2023/test_ec2_2023_section5_materials.py::test_eps_cs_50y[25-dry-800-CN-0.46333], where the actual value is 0.45333333333333337 and the expected value is 0.46333. I am treating it as local/environment-specific unless you would like it triaged separately.

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