robbievanleeuwen / concrete-properties

Calculate section properties for reinforced concrete sections.
https://concrete-properties.rtfd.io
MIT License
159 stars 47 forks source link

Add support for ACI 318-19 #76

Open Lomarandil opened 1 year ago

Lomarandil commented 1 year ago

Alright, now that you guys have made all these refinements and process improvements, I'm ready to stand on those shoulders to implement ACI 318-19.

Perhaps more accurately, I've started work on a fork implementing ACI 318-19M. I figure that if we get the bones figured out first, skinning it in imperial units (per issue #48) can be a separate undertaking. After a false start adapting Agents NZS code, I've decided to first start by adapting the AS module (easier to wrap my head around for now).

Current work is in the ACI2 branch here: https://github.com/Lomarandil/concrete-properties-ACI/tree/Lomarandil-ACI2

My apologies, I'm still a programming and Github noob, just learning by doing. Please feel free to suggest corrections or style improvements.

Agent6-6-6 commented 1 year ago

One thing to be aware of, the metric and imperial versions of ACI318 have slight differences in many of the formulas for the constants. They are not a direct conversion to many decimal places, and it will lead to small inconsistencies in the answers when compared against metric or imperial workings. So if implementing both most methods for returning factors would need both metric and imperial equations. Converting the end answer probably will be close but not comparable to other tools you might be checking against.

When you first call the class you might need to have a variable that drives the metric or the imperial version of the code equations being utilised. For example metric (or imperial) by default, and can get the inverse when defining the design code:-

design_code=ACI318(imperial=True)

I'd say as a design code NZS3101 is much closer to ACI318 than AS3600, NZS3101 is based on ACI318 after all, whereas AS3600 kind of does its own thing in many areas.

So I'd expect many of the functions you could simply copy across from the NZS3101 class and change the formulas and so forth. Not sure if ACI has similar overstrength and capacity design, so that might be redundant. The main difference I can think of would be in the fact that the strength reduction factors are variable in ACI (but AS3600 Class has a method for this that could be adapted). I started with the AS3600 code and went from there, keeping commonality in the various methods where logical.

One thing I would say about the AS300 class is a lot of the calculations are all under one method for alpha/gamma factors and so forth, and are not refactored out into separate methods like the NZS3101 class. Which I think the way it is in the NZS3101 class more easily leads to easier adaption to other design standards.

Anyway, let me know if there are any specific questions you have on the NZS3101 class and how this might be adapted to an equivalent ACI318 Class as I'm reasonably familiar with ACI318 also. Once you have something up and working submit it as a draft pull request.

robbievanleeuwen commented 1 year ago

Hi @Lomarandil great to hear, looking forward to seeing the progress! Agree that it's probably easiest to build on NZS & AS design codes, happy to answer any questions you have here on in a discussion post. There are also some basic guidelines here.

Lomarandil commented 1 year ago

One thing to be aware of, the metric and imperial versions of ACI318 have slight differences in many of the formulas for the constants. They are not a direct conversion to many decimal places, and it will lead to small inconsistencies in the answers when compared against metric or imperial workings. So if implementing both most methods for returning factors would need both metric and imperial equations. Converting the end answer probably will be close but not comparable to other tools you might be checking against.

When you first call the class you might need to have a variable that drives the metric or the imperial version of the code equations being utilised. For example metric (or imperial) by default, and can get the inverse when defining the design code:-

design_code=ACI318(imperial=True)

Fair point. It's not the kind of precision that bothers me as a practitioner, but I can see the value in getting it spot-on for validation purposes. With that in mind, would we rather have the imperial/metric variable? Or just have two design codes, ACI 318 and 318M?

I'd say as a design code NZS3101 is much closer to ACI318 than AS3600, NZS3101 is based on ACI318 after all, whereas AS3600 kind of does its own thing in many areas.

So I'd expect many of the functions you could simply copy across from the NZS3101 class and change the formulas and so forth. Not sure if ACI has similar overstrength and capacity design, so that might be redundant. The main difference I can think of would be in the fact that the strength reduction factors are variable in ACI (but AS3600 Class has a method for this that could be adapted). I started with the AS3600 code and went from there, keeping commonality in the various methods where logical.

You know, I'll have to check. My experience has always been in low seismic design category applications, so I'm not very familiar with the requirements for SDC C/D/E structures.

One thing I would say about the AS300 class is a lot of the calculations are all under one method for alpha/gamma factors and so forth, and are not refactored out into separate methods like the NZS3101 class. Which I think the way it is in the NZS3101 class more easily leads to easier adaption to other design standards.

Yes, I can definitely implement this in the ACI class.

Agent6-6-6 commented 1 year ago

@Lomarandil, a few thoughts/brain dump below:-

I'd imagine one class with both equations if they differ slightly would probably be the most logical approach, this would prevent repeating larger amounts of identical code and make it easier to maintain if there are future revisions to the equations.

Though could have one parent class with the other over-riding any methods with different constants in equations where appropriate. i.e. ACI318() is the base class based on the imperial version of the ACI code, and define the other as a child class ACI318M(ACI318) and inherits all of the methods using __init__.super() and override parent methods/functions/constants as required. But that feels a bit harder to maintain and since there will be differences in nearly every equation based on concrete strength you might be overriding the majority of the methods apart from those calling the actual analyses methods. Which potentially just makes it harder to maintain consistency between two classes despite some underlying commonality between them.

From my perspective, I think the best way to do it might be to firstly consider listing out where the differences in equations and constants in equations might lay between imperial and metric versions and simply have a single method that sets these constants when the ACI318() class is initiated. That way the rest of the code is effectively static (might depend on the form of the equations ultimately though) and just refers to a given constant and the stored value is either the imperial or metric value that was set when the class was initiated.

If you do this you could still have a ACI318M class that is a child of ACI318 class and inherit all of the methods, with the only difference being there is a metric=True flag run as it is initiated to trigger the metric calculations. So all the python code is in the parent class, but it is imperial by default if using ACI318() and metric if using ACI318M(). This takes out the need for the user to specify metric=True for example. I think that would be a nice neat solution.

Apart from having to consider the output units potentially differently for metric and imperial, that way you're going to have the same code and no need to double up in each method with a metric and an imperial version of the same equation. Though defining the constants at the beginning of an individual method is fine and means all the values that might change in the future are grouped together. Sometimes having something more readily read/understood is better than writing really efficient/streamlined code with lots of inheritance that is hard to interpret.

Until you know how much difference might exist between equations and how many methods might need alternative metric/imperial versions I guess it is hard to say. I suspect you'll be getting either imperial or metric up and running and then looking at the best way to implement the other one once you have it up and working.

Agent6-6-6 commented 1 year ago
class ACI318:
    def __init__(self, metric: bool = False):
        self.metric = metric
        self.metric_test()

    def metric_test(self):
        if self.metric:
            print(f"I'm inside ACI318 and metric = {self.metric}")
        else:
            print(f"I'm inside ACI318 and metric = {self.metric}")

class ACI318M(ACI318):
    def __init__(self):
        super().__init__(metric=True)

design_code = ACI318()

design_code_metric = ACI318M()

returns:-

I'm inside ACI318 and metric = False
I'm inside ACI318 and metric = True

This is what I meant, only thing you need in ACI318M is as shown and you can access all of the code in ACI318 class and metric is set to True if you came from ACI318M to drive the direction of the analysis if all code is located in ACI318 class.

buddyd16 commented 1 year ago

@Lomarandil

To save you a little bit of time I have this class with imperial and metric ACI 318-19 formulas started for the purpose of column design:

class ACIConcrete:
    def __init__(self, fc=4000, density=145, lightweight=False, Ec_method="Density", units="Imperial"):       
        self.fc = fc
        self.density = density
        self.ec_method = Ec_method
        self.lightweight = lightweight
        self.eu = 0.003
        self.units = units

    def elastic_modulus(self):

        # ACI 318-19 - Section 19.2.2
        if self.ec_method == "Density":

            if self.units == "Imperial":
                # ACI 318-19 - Eq. 19.2.2.1.a
                Ec = math.pow(self.density, 1.5)*33*math.sqrt(self.fc)
            else:
                # ACI 318-19 - App. C SI-Metric Equiv. for Eq. 19.2.2.1.a
                Ec = math.pow(self.density, 1.5)*0.043*math.sqrt(self.fc)

        else:

            if self.units == "Imperial":
                # ACI 318-19 - Eq. 19.2.2.1.b
                Ec = 57000*math.sqrt(self.fc)
            else:
                # ACI 318-19 - App. C SI-Metric Equiv. for Eq. 19.2.2.1.b
                Ec = 4700*math.sqrt(self.fc)

        self.Ec = Ec
        return Ec

    def lambdac(self):

        # ACI 318-19 - Section 19.2.4
        if self.lightweight:
            # ACI 318-19 - 19.2.4.2
            lam = 0.75 
        else:
            # ACI 318-19 - 19.2.4.3
            lam = 1.0 

        self.lam = lam
        return lam

    def modulus_of_rupture(self):

        # ACI 318-19 - Section 19.2.3
        lam = self.lambdac()

        if self.units == "Imperial":
            # ACI 318-19 eq. 19.2.3.1
            fr = lam*7.5*math.sqrt(self.fc) 
        else:
            # ACI 318-19 - App. C SI-Metric Equiv. for Eq. 19.2.3.1
            fr = lam*0.62*math.sqrt(self.fc)

        self.fr = fr
        return fr

    def beta1(self):

        # ACI 318-19 - Table 22.2.2.4.3
        if self.units == "Imperial":
            if self.fc <=  2500:
                beta1 = 0

            elif self.fc >= 8000:
                beta1 = 0.65

            elif 2500 <= self.fc and self.fc <= 4000:
                beta1 = 0.85

            else:
                beta1 = 0.85 - ((0.05*(self.fc-4000))/1000)

        else:
            # ACI 318-19 - App. C SI-Metric Equiv. for 22.2.2.4.3(b)
            # 1000 psi = 7 Mpa as defined by ACI, correct this for actual
            # unit conversion
            if self.fc <=  17.5:
                beta1 = 0

            elif self.fc >= 56:
                beta1 = 0.65 

            elif 17.5 <= self.fc and self.fc <= 28:
                beta1 = 0.85

            else:
                beta1 = 0.85 - ((0.05*(self.fc-28))/7)

        self.beta1 = beta1
        return beta1

    def phi_nm(self, et, ety, ties="Other"):

        eu = self.eu

        if ties == "Spiral":
            if et > ety:
                phi = 0.75
                classification = "Compression-controlled"
            elif et < (ety-eu):
                phi = 0.9
                classification = "Tension-controlled"
            else:
                phi = 0.75 + ((0.15*(et-ety))/(-1*eu))
                classification = "Transition"
        else:
            if et > ety:
                phi = 0.65
                classification = "Compression-controlled"
            elif et < (ety-eu):
                phi = 0.9
                classification = "Tension-controlled"
            else:
                phi = 0.65 + ((0.25*(et-ety))/(-1*eu))
                classification = "Transition"

        return {"Phi": phi,"Classification":classification}

    def pn_max(self, Ag, Ast, fy, ties="Other"):

        #ACI 318-19 - 22.4.2.1
        if fy > 80000:
            fy = 80000

        Po = (0.85*self.fc*(Ag-Ast))+(Ast*fy)

        if ties == "Spiral":
            pn_max = 0.85*Po
            phi = 0.75
        else:
            pn_max = 0.80*Po
            phi = 0.65

        pu_max = phi*pn_max

        return {"Pn_max":pn_max,"Phi":phi,"Pu_max":pu_max}

    def pnt_max(self, Ast, fy):
        #ACI 318-19 - 22.4.3
        pnt_max = Ast*fy
        phi = 0.9
        put_max = phi*pnt_max

        return {"Pnt_max":pnt_max,"Phi":phi,"Put_max":put_max}
dankicode commented 3 months ago

Hi @Lomarandil @robbievanleeuwen,

I see that this has somewhat stalled, but I am willing to help get this going. I've written something already to modify the axial-moment interaction diagrams according to the ductility requirements in ACI318.

Would be happy to contribute this along with other implementations.

Where should I start?

Thanks! Dan

Lomarandil commented 3 months ago

Thanks Dan -- I stalled out quickly with my rudimentary programming knowledge -- couldn't sort out how to pull strain values at the time.

Supportive of this being picked up again!

On Wed, Aug 28, 2024 at 9:32 AM Dank @.***> wrote:

Hi @Lomarandil https://github.com/Lomarandil @robbievanleeuwen https://github.com/robbievanleeuwen,

I see that this has somewhat stalled, but I am willing to help get this going. I've written something already to modify the axial-moment interaction diagrams according to the ductility requirements in ACI318.

Would be happy to contribute this along with other implementations.

Where should I start?

Thanks! Dan

— Reply to this email directly, view it on GitHub https://github.com/robbievanleeuwen/concrete-properties/issues/76#issuecomment-2315683074, or unsubscribe https://github.com/notifications/unsubscribe-auth/AK2P4M37YTMM4G652NSHDQTZTXUPJAVCNFSM6AAAAABNIRV3J6VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDGMJVGY4DGMBXGQ . You are receiving this because you were mentioned.Message ID: @.***>

dankicode commented 3 months ago

Sounds good. I'll fork the main repo and get started.

@robbievanleeuwen feel free to assign this to me! I'll issue pull requests in small chunks so it's easy for you to review.

dankicode commented 3 months ago

Ah - I am having issues with the install. It seems triangle won't install on a M1 Mac.. @robbievanleeuwen I see that sectionproperties uses CyTriangle. How difficult would it be to migrate concreteproperties to CyTriangle?