def sallow(self, element, loadcase, forces): """Allowable stress for sustained, occasional and expansion loadcases. Liberal stress can be excluded for the expansion case by user defined option otherwise enabled by default depending on the code. """ with units.Units(user_units="code_english"): material = element.material tload = self.toper(element, loadcase) try: temp = tload.temp tref = tload.tref except AttributeError: temp = 70 # fahrenheit tref = 70 sh = material.sh[temp] sc = material.sh[tref] if loadcase.stype == "sus": return sh elif loadcase.stype == "occ": # k factor is 1.15 for events lasting 8hrs or less and 1.20 # for 1hr or less per code para. 102.3.3, use 1.15 to be # conservative return self.k * sh elif loadcase.stype == "exp": liberal_stress = 0 # default per code if self.app.models.active_object.settings.liberal_stress: liberal_stress = sh - self.sl(element, loadcase, forces) return self.f * (1.25 * sc + 0.25 * sh + liberal_stress) else: return 0
def __init__(self, name, opercase, point, dx=None, dy=None, dz=None, rx=None, ry=None, rz=None): """Create a displacement support instance.""" super(Displacement, self).__init__(name, opercase) self.opercase = opercase self.point = point self.dx = dx self.dy = dy self.dz = dz self.rx = rx self.ry = ry self.rz = rz model = self.app.models.active_object with units.Units(user_units=DEFAULT_UNITS): self.translation_stiffness = model.settings.translation_stiffness self.rotation_stiffness = model.settings.rotation_stiffness
def is_small_bore(self): """Pipe sizes 2" and below are considered small bore. Imperial units are used here for the relational test. """ with units.Units(user_units="english"): return self.od <= 2
def __init__(self, name, point): """Create a support instance at a node point. Parameters ---------- name : str Unique name for pipe object. point : Point Point instance where support is located. translation_stiffnesss : float Stiffness in the translational directions. .. note:: The default value is based on english units. rotation_stiffness : float Stiffness in the rotational directions. .. note:: The default value is based on english units. """ super(Support, self).__init__(name) self.point = point self.elememt = None model = self.app.models.active_object with units.Units(user_units=DEFAULT_UNITS): self.translation_stiffness = model.settings.translation_stiffness self.rotation_stiffness = model.settings.rotation_stiffness
def kfac(self, element): """Code flexibility factor for fittings.""" model = self.app.models.active_object section = element.section material = element.material with units.Units(user_units="code_english"): if isinstance(element, Bend): R = section.radius # bend radius tn = section.thk # nominal thickness r = (section.od - tn) / 2 # mean radius Ec = material.ymod[model.settings.tref] pmax = self.pmax(element) h = self.h(element) kc = 1 # corrected for pressure - note 5 if section.is_large_bore and section.is_thin_wall: kc = 1 + 6 * (pmax/Ec) * (r/tn)**(7/3) * (R/r)**(1/3) k = (1.65 / h) / kc return 1.0 if k < 1 else k elif isinstance(element, Reducer): return 1.0 else: return 1.0
def sts(self, element, forces): """Transverse shear stress""" with units.Units(user_units="code_english"): fy, fz = forces[1:3] F = math.sqrt(fy**2 + fz**2) section = element.section area = section.area return F / area
def stor(self, element, forces): """Shear stress due to torsion""" with units.Units(user_units="code_english"): mx = forces[3] section = element.section do = section.od Ip = section.ixx # polar moment return mx*do / (2*Ip)
def slb(self, element, point, forces): """Longitudinal stress due to bending moments.""" with units.Units(user_units="code_english"): my, mz = forces[-2:] M = math.sqrt(my**2 + mz**2) # note for B31.1 sifi equals sifo i = self.sifi(element, point) section = element.section Z = section.z # section modulus return M*i / Z
def shoop(self, element, loadcase): """Hoop stress due to pressure. Conservatively equal to P*D/2t. """ with units.Units(user_units="code_english"): sec = element.section do = sec.od thke = sec.thke pload = self.poper(element, loadcase) try: return (pload.pres*do) / (2*thke) except AttributeError: return 0
def from_file(cls, name, opercase, fluid="water", gfac=1.0, fname=None, default_units="english"): if fname is None: fname = psi.FLUID_DATA_FILE with open(fname, "r") as csvfile: reader = csv.DictReader(csvfile) for row in reader: if row["fluid"] == fluid: rho = float(row["rho"]) with units.Units(user_units=default_units): return cls(name, opercase, rho, gfac) else: return None
def __init__(self, default_units=DEFAULT_UNITS): with units.Units(user_units=default_units): self._units = default_units self.vertical = "y" self.stress_case_corroded = True self.bourdon_effect = False self.pressure_thrust = False self.liberal_stress = False self.weak_springs = False self.nonlinear_iteration = 1000 self.translation_stiffness = 10**10 self.rotation_stiffness = 10**12 self.axial_force = False self.tref = 70.0 self.timoshenko = True self.version = VERSION
def sl(self, element, loadcase, point, forces): """Total longitudinal stress due to pressure and bending+torsion. This combination of stresses is also known as the code stress for most codes. """ with units.Units(user_units="code_english"): slp = self.slp(element, loadcase) slb = self.slb(element, point, forces) sax = self.sax(element, forces) stor = self.stor(element, forces) if loadcase.stype == "sus" or loadcase.stype == "occ": return sax + slp + math.sqrt(slb**2 + 4*stor**2) elif loadcase.stype == "exp": return math.sqrt(slb**2 + 4*stor**2) else: return 0
def pmax(self, element): """Convenience function to get the largest element pressure from all operating cases. """ with units.Units(user_units="code_english"): pressures = [] for loadcase in element.loadcases: for loadtype, opercase in zip(loadcase.loadtypes, loadcase.opercases): for load in element.loads: if (isinstance(load, Pressure) and load.opercase == opercase): pressures.append(load) try: return max(pressures, key=lambda p: p.pres) except ValueError: return None
def sax(self, element, forces): """Axial stress due to mechanical loading. Axial stress can be included by user defined option. Force is zero by default. """ sx = 0 # code default if self.app.models.active_object.settings.axial_force: with units.Units(user_units="code_english"): fx = forces[0] section = element.section area = section.area sx = fx/area return sx
def __init__(self, name, point, direction=None, is_rotational=False): """Create a support instance at a node point. Parameters ---------- name : str Unique name for pipe object. point : Point Point instance where support is located. direction : str Support direction. Default is None. "+" and "-" is used to specify a directional support (non-linear). mu : float Support friction (non-linear). is_rotational : bool True if the support is a rotational restraint. is_snubber : bool True if the support is snubber. translation_stiffnesss : float Stiffness in the translational direction. rotation_stiffness : float Stiffness in the rotational direction. gap : float Support gap (non-linear). """ super(AbstractSupport, self).__init__(name, point) self.direction = direction self.is_rotational = is_rotational self._mu = 0 self._gap = 0 self._is_snubber = False model = self.app.models.active_object with units.Units(user_units=DEFAULT_UNITS): self.translation_stiffness = model.settings.translation_stiffness self.rotation_stiffness = model.settings.rotation_stiffness
def slp(self, element, loadcase): """Longitudinal stress due to pressure. Exact formulation for the pressure stress per code section 102.3.2. The pressure stress is a primary stress and by definition can result in large gross deformations and overall failure, i.e., pipe rupture. """ with units.Units(user_units="code_english"): section = element.section do = section.od di = section.id pload = self.poper(element, loadcase) try: # exact formulation return pload.pres*di**2 / (do**2-di**2) except AttributeError: return 0
def from_file(cls, name, size="W4x13", fname=None, default_units="english"): if fname is None: fname = psi.BEAM_DATA_FILE with open(fname, "r") as csvfile: reader = csv.DictReader(csvfile) for row in reader: if row["size"] == size: d = float(row["d"]) tw = float(row["tw"]) bf = float(row["bf"]) tf = float(row["tf"]) with units.Units(user_units=default_units): return cls(name, d, tw, bf, tf)
def toper(self, element, loadcase): """Convenience function to get the largest element temperature load for a particular operating case. """ with units.Units(user_units="code_english"): thermals = [] for loadtype, opercase in zip(loadcase.loadtypes, loadcase.opercases): for load in element.loads: if (isinstance(load, Thermal) and load.opercase == opercase): thermals.append(load) # if length of temperature is greater than one, print warning # message saying multiple thermal loads exist for the same # operating case for the element, then proceed to take the max # worst pressure try: return max(thermals, key=lambda t: t.temp) except ValueError: # empty list return None
def poper(self, element, loadcase): """Convenience function to get the largest element pressure load for a particular operating case. """ with units.Units(user_units="code_english"): # pressure load specified for loadcase and opercase sorted by # maximum pressures = [] for loadtype, opercase in zip(loadcase.loadtypes, loadcase.opercases): for load in element.loads: if (isinstance(load, Pressure) and load.opercase == opercase): pressures.append(load) # if length of pressures is greater than one, print warning message # saying multiple pressure loads exist for the same operating case # for the element, then proceed to take the max worst pressure try: return max(pressures, key=lambda p: p.pres) except ValueError: return None
def h(self, entity): """Flexibility characterisitic for fittings per the code.""" with units.Units(user_units="code_english"): if isinstance(entity, Bend): # Per Appendix D of 1967 code R = entity.radius # bend radius tn = entity.section.thk # nominal thk r = (entity.section.od - T) / 2 # mean radius h = (tn*R) / r**2 # flexibility characteristic # stiffening effect due to flanged ends, note 3 if entity.flange == 0: c = 1 elif entity.flange == 1: c = h**(1/6) elif entity.flange == 2: c = h**(1/3) h = c*h # corrected h return h elif isinstance(entity, Reducer): return 1.0 elif isinstance(entity, Welding): do = entity.do # header pipe dia dob = entity.dob # branch pipe dia tn = entity.tn # nominal header thk rx = entity.rx # crotch radius tc = entity.tc # crotch thk r = (do-tn) / 2 if rx >= dob/8 and tc >= 1.5*tn: h = 4.4*tn / r else: h = 3.1*tn / r return h elif isinstance(entity, Unreinforced): do = entity.do # header pipe dia tn = entity.tn # nominal header thk r = (do-tn) / 2 h = tn / r return h elif isinstance(entity, Reinforced): do = entity.do # header pipe dia tn = entity.tn # nominal header thk tr = entity.tr # pad thk r = (do-tn) / 2 if tr > 1.5*tn: h = 4.05*tn / r else: h = (tn+tr/2)**(5/2) / (r*tn**(3/2)) return h elif isinstance(entity, Weldolet): do = entity.do # header pipe dia tn = entity.tn # nominal header thk r = (do-tn) / 2 h = 3.3*tn / r return h elif isinstance(entity, Sockolet): do = entity.do # header pipe dia tn = entity.tn # nominal header thk r = (do-tn) / 2 h = 3.3*tn / r return h elif isinstance(entity, Sweepolet): do = entity.do # header pipe dia dob = entity.dob # branch pipe dia tn = entity.tn # nominal header thk rx = entity.rx # crotch radius tc = entity.tc # crotch thk r = (do-tn) / 2 if rx >= dob/8 and tc >= 1.5*tn: h = 4.4*tn / r else: h = 3.1*tn / r return h else: return 1.0
def from_file(cls, name, nps, sch, corra=None, milltol=None, fname=None, default_units="english"): """Create a pipe object from a csv data file. Parameters ---------- name : str Unique name for pipe object. nps : str Nominal pipe size. sch : str Pipe schedule designation. corro : float Corrosion allowance. milltol : float Mill tolerance. Enter the percent value such as 12.5. fname : str Full path to the csv data file used to do the lookup. default_units : str The units used for the data. Must be one of the units defined in the psi.UNITS_DIRECTORY path. Example ------- Create a 10" schedule 40 pipe and activate the section. .. code-block:: python >>> p1 = Pipe.from_file("p1", "10", "40") """ if fname is None: fname = psi.PIPE_DATA_FILE with open(fname, "r") as csvfile: reader = csv.DictReader(csvfile) # allows a schedule to have multiple names sch_map = {} schedules = reader.fieldnames[2:] for schedule in schedules: schs = schedule.split("/") for s in schs: sch_map[s] = schedule for row in reader: if float(row["nps"]) == float(nps): try: nps = float(row["nps"]) od = float(row["od"]) thk = float(row[sch_map[sch]]) # using data file units to initialize with units.Units(user_units=default_units): return cls(name, od, thk, nps=nps, sch=sch, corra=corra, milltol=milltol) except ValueError: # calling float on empty string return None else: return None
def from_file(cls, name, material, code, fname=None, default_units="english"): """Create a material from a csv data file. Parameters ---------- name : str Unique name for pipe object. material : str Name of material in database. code : str The piping code the material data comes from, 'B31.1' for example. fname : str Pull path to the csv data file used to do the lookup. .. note:: The default path is set to the variable psi.MATERIAL_DATA_FILE. default_units : str The units used for the data. Must be one of the units defined in the psi.UNITS_DIRECTORY path. .. note:: The values are converted to base units upon loading. Conversion to and from base to user units occurs on the fly. Example ------- Create a A53A material instance and activate it. .. code-block:: python >>> mat1 = Material.from_file("A53A", "A53A", "B31.1") """ if fname is None: fname = psi.MATERIAL_DATA_FILE def num(st): """Convert a string to an int or float""" try: return int(st) except ValueError: try: return float(st) except ValueError: return None # value missing def convert(prop): vals = prop.split(",") if len(vals) == 1: return num(*vals) else: return [num(val) for val in vals] with open(fname) as csvfile: reader = csv.DictReader(csvfile) (NAME, CODE, RHO, NU, TEMP, ALP, YMOD, SH) = \ ("name", "code", "rho", "nu", "temp", "alp", "ymod", "sh") for row in reader: if row[NAME] == material and row[CODE] == code: rho = row[RHO] nu = row[NU] temp = row[TEMP] alp = row[ALP] ymod = row[YMOD] sh = row[SH] rho = convert(rho) nu = convert(nu) tempc = convert(temp) alpc = convert(alp) ymodc = convert(ymod) shc = convert(sh) with units.Units(user_units=default_units): mat = cls(name) # rho and nu not func of temp mat.rho.value = rho mat.nu.value = nu # remove temps with no values given mat.alp.table = [(t, v) for t, v in zip(tempc, alpc) if v is not None] mat.sh.table = [(t, v) for t, v in zip(tempc, shc) if v is not None] mat.ymod.table = [(t, v) for t, v in zip(tempc, ymodc) if v is not None] return mat else: return None
def element_codecheck(points, loadcase, element, S): """Perform element code checking based on the assigned code for primary load cases and load combinations. """ ndof = 6 idxi = points.index(element.from_point) idxj = points.index(element.to_point) # node and corresponding dof (start, finish) niqi, niqj = idxi * ndof, idxi * ndof + ndof njqi, njqj = idxj * ndof, idxj * ndof + ndof with units.Units(user_units="code_english"): # Note: units are changed to code_english for the moments # to have units of inch*lbf per code requirement # code equations are units specific, i.e. imperial or si if isinstance(loadcase, LoadCase): fori = loadcase.forces.results[niqi:niqj, 0] forj = loadcase.forces.results[njqi:njqj, 0] # code stresses per element node i and j for each loadcase shoop = element.code.shoop(element, loadcase) # pressure stress is same at both nodes slp = element.code.slp(element, loadcase) saxi = element.code.sax(element, fori) saxj = element.code.sax(element, forj) stori = element.code.stor(element, fori) storj = element.code.stor(element, forj) slbi = element.code.slb(element, element.from_point, fori) slbj = element.code.slb(element, element.to_point, forj) # total code stress sli = element.code.sl(element, loadcase, element.from_point, fori) slj = element.code.sl(element, loadcase, element.to_point, forj) # fitting and nodal sifs, sum together, take max or average? sifi = element.code.sifi(element, element.from_point) sifo = element.code.sifo(element, element.to_point) sallowi = element.code.sallow(element, loadcase, fori) sallowj = element.code.sallow(element, loadcase, forj) try: sratioi = sli / sallowi # code ratio at node i sratioj = slj / sallowj # code ratio at node j except ZeroDivisionError: sratioi = 0 sratioj = 0 elif isinstance(loadcase, LoadComb): # fitting and nodal sifs, sum together, take max or average? sifi = element.code.sifi(element) sifo = element.code.sifo(element) loadcomb = loadcase shoop_list, slp_list = [], [] saxi_list, stori_list, slbi_list, sli_list = [], [], [], [] saxj_list, storj_list, slbj_list, slj_list = [], [], [], [] for factor, loadcase in zip_longest(loadcomb.factors, loadcomb.loadcases, fillvalue=1): fori = loadcase.forces.results[niqi:niqj, 0] forj = loadcase.forces.results[njqi:njqj, 0] shoop_list.append(factor * element.code.shoop(element, loadcase)) slp_list.append(factor * element.code.slp(element, loadcase)) saxi_list.append(factor * element.code.sax(element, fori)) saxj_list.append(factor * element.code.sax(element, forj)) stori_list.append(factor * element.code.stor(element, fori)) storj_list.append(factor * element.code.stor(element, forj)) slbi_list.append( factor * element.code.slb(element, element.from_point, fori)) slbj_list.append( factor * element.code.slb(element, element.to_point, forj)) # total code stress sli_list.append( element.code.sl(element, loadcase, element.from_point, fori)) slj_list.append( element.code.sl(element, loadcase, element.to_point, forj)) if loadcomb.method == "scaler": shoop = sum(shoop_list) slp = sum(slp_list) saxi = sum(saxi_list) saxj = sum(saxj_list) stori = sum(stori_list) storj = sum(storj_list) slbi = sum(slbi_list) slbj = sum(slbj_list) sli = sum(sli_list) slj = sum(slj_list) elif loadcomb.method == "algebraic": # stress per algebraic combination of forces fori = loadcomb.forces.results[niqi:niqj, 0] forj = loadcomb.forces.results[njqi:njqj, 0] shoop = sum(shoop_list) slp = sum(slp_list) saxi = element.code.sax(element, fori) saxj = element.code.sax(element, forj) stori = element.code.stor(element, fori) storj = element.code.stor(element, forj) slbi = element.code.slb(element, element.from_point, fori) slbj = element.code.slb(element, element.to_point, forj) # total code stress sli = element.code.sl(element, loadcase, element.from_point, fori) slj = element.code.sl(element, loadcase, element.to_point, forj) elif loadcomb.method == "srss": # note: sign of factor has no effect, always positive shoop = sum([s**2 for s in shoop_list]) slp = sum([s**2 for s in slp_list]) saxi = sum([s**2 for s in saxi_list]) saxj = sum([s**2 for s in saxj_list]) stori = sum([s**2 for s in stori_list]) storj = sum([s**2 for s in storj_list]) slbi = sum([s**2 for s in slbi_list]) slbj = sum([s**2 for s in slbj_list]) sli = sum([s**2 for s in sli_list]) slj = sum([s**2 for s in slj_list]) elif loadcomb.method == "abs": shoop = sum([abs(s) for s in shoop_list]) slp = sum([abs(s) for s in slp_list]) saxi = sum([abs(s) for s in saxi_list]) saxj = sum([abs(s) for s in saxj_list]) stori = sum([abs(s) for s in stori_list]) storj = sum([abs(s) for s in storj_list]) slbi = sum([abs(s) for s in slbi_list]) slbj = sum([abs(s) for s in slbj_list]) sli = sum([abs(s) for s in sli_list]) slj = sum([abs(s) for s in slj_list]) elif loadcomb.method == "signmax": shoop = max(shoop_list) slp = max(slp_list) saxi = max(saxi_list) saxj = max(saxj_list) stori = max(stori_list) storj = max(storj_list) slbi = max(slbi_list) slbj = max(slbj_list) sli = max(sli_list) slj = max(slj_list) elif loadcomb.method == "signmin": shoop = min(shoop_list) slp = min(slp_list) saxi = min(saxi_list) saxj = min(saxj_list) stori = min(stori_list) storj = min(storj_list) slbi = min(slbi_list) slbj = min(slbj_list) sli = min(sli_list) slj = min(slj_list) # take the sqrt last if loadcomb.method == "srss": shoop = math.sqrt(shoop) slp = math.sqrt(slp) saxi = math.sqrt(saxi) saxj = math.sqrt(saxj) stori = math.sqrt(stori) storj = math.sqrt(storj) slbi = math.sqrt(slbi) slbj = math.sqrt(slbj) sli = math.sqrt(sli) slj = math.sqrt(slj) # allowable loadcomb stress sallowi_list = [] sallowj_list = [] # determine the loadcomb code stress allowable for loadcase in loadcomb.loadcases: stype = loadcase.stype # save type loadcase.stype = loadcomb.stype # change to loadcomb type fori = loadcase.forces.results[niqi:niqj, 0] forj = loadcase.forces.results[njqi:njqj, 0] # calculate loadcomb allowable sallowi = element.code.sallow(element, loadcase, fori) sallowi_list.append(sallowi) sallowj = element.code.sallow(element, loadcase, forj) sallowj_list.append(sallowj) # revert to loadcase stype loadcase.stype = stype sallowi = min(sallowi_list) sallowj = min(sallowj_list) try: sratioi = sli / sallowi # code ratio at node i sratioj = slj / sallowj # code ratio at node j except ZeroDivisionError: sratioi = 0 sratioj = 0 # hoop, sax, stor, slp, slb, sl, sifi, sifj, sallow, ir # take the worst code stress at node if sratioi > S[idxi, -1]: S[idxi, :10] = (shoop, saxi, stori, slp, slbi, sli, sifi, sifo, sallowi, sratioi) if sratioj > S[idxj, -1]: S[idxj, :10] = (shoop, saxj, storj, slp, slbj, slj, sifi, sifo, sallowj, sratioj)
def sifi(self, element, point, combfunc="max"): """In plane stress intensification factor for fittings. The sif must be 1 or greater. .. todo:: The maximum sif at a point is taken. It should eventually be a user defined option to take the sum, max or average. """ model = self.app.models.active_object section = element.section material = element.material sifs = [] with units.Units(user_units="code_english"): if isinstance(element, (Run, Rigid, Valve, Flange)): sifs.append(1.0) elif isinstance(element, Bend): R = section.radius # bend radius tn = section.thk # nominal thickness r = (section.od - tn) / 2 # mean radius Ec = material.ymod[model.settings.tref] pmax = self.pmax(element) h = self.h(element) ic = 1 # corrected for pressure - note 5 if section.is_large_bore and section.is_thin_wall: ic = 1 + 3.25 * (pmax/Ec) * (r/tn)**(5/2) * (R/r)**(2/3) sif = (0.9 / h**(2/3)) / ic sifs.append(1.0 if sif < 1 else sif) elif isinstance(element, Reducer): D1 = section.od t1 = section.thk D2 = section2.od t2 = section2.thk L = element.length alp = element.alpha alpv = True if alp <= 60 else False conc = True if element.is_concentric else False d2t1 = True if section.d2t <= 100 else False d2t2 = True if section2.d2t <= 100 else False if all([alpv, conc, d2t1, d2t2]): sif = 0.5 + 0.01*alp*(D2/t2)**(1/2) sifs.append(1.0 if sif < 1 else sif) else: sifs.append(2.0) for entity in element.sifs: if self.app.points(entity.point) is point: if isinstance(entity, Welding): # per mandatory appendix D h = self.h(entity) # in-plane and out-of-plane sifs are the same # for B31.1 the higher is used sif = 0.9 / h**(2/3) # must be larger than or equal to 1.0 sifs.append(1.0 if sif < 1 else sif) elif isinstance(entity, Unreinforced): h = self.h(entity) # in-plane and out-of-plane sifs are the same # for B31.1 the higher is used sif = 0.9 / h**(2/3) sifs.append(1.0 if sif < 1 else sif) elif isinstance(entity, Reinforced): h = self.h(entity) # in-plane and out-of-plane sifs are the same # for B31.1 the higher is used sif = 0.9 / h**(2/3) sifs.append(1.0 if sif < 1 else sif) elif isinstance(entity, Weldolet): h = self.h(entity) # in-plane and out-of-plane sifs are the same # for B31.1 the higher is used sif = 0.9 / h**(2/3) sifs.append(1.0 if sif < 1 else sif) elif isinstance(entity, Sockolet): h = self.h(entity) # in-plane and out-of-plane sifs are the same # for B31.1 the higher is used sif = 0.9 / h**(2/3) sifs.append(1.0 if sif < 1 else sif) elif isinstance(entity, Sweepolet): h = self.h(entity) # in-plane and out-of-plane sifs are the same # for B31.1 the higher is used sif = 0.9 / h**(2/3) sifs.append(1.0 if sif < 1 else sif) elif isinstance(entity, ButtWeld): sifs.append(1.0) else: # must be 1 at a minimum sifs.append(1.0) if combfunc == "max": return max(sifs) elif combfunc == "sum": return sum(sifs) elif combfunc == "avg": return sum(sifs) / len(sifs)