Function bodies 335 total
summary method · python · L212-L218 (7 LOC)src/nlb/tools/bearings.py
def summary(self) -> str:
return (
f"BearingModel ({self.bearing_type}): "
f"{len(self.nodes)} nodes, {len(self.elements)} elements, "
f"{len(self.materials)} materials | "
f"top={self.top_nodes}, bot={self.bottom_nodes}"
)TagAllocator class · python · L225-L242 (18 LOC)src/nlb/tools/bearings.py
class TagAllocator:
"""Sequential tag allocator for OpenSees objects."""
def __init__(self, start: int = 1):
self._next = start
def next(self, count: int = 1) -> int | list[int]:
if count == 1:
tag = self._next
self._next += 1
return tag
tags = list(range(self._next, self._next + count))
self._next += count
return tags
@property
def current(self) -> int:
return self._nextnext method · python · L231-L238 (8 LOC)src/nlb/tools/bearings.py
def next(self, count: int = 1) -> int | list[int]:
if count == 1:
tag = self._next
self._next += 1
return tag
tags = list(range(self._next, self._next + count))
self._next += count
return tags_elastomeric_stiffness function · python · L249-L290 (42 LOC)src/nlb/tools/bearings.py
def _elastomeric_stiffness(cfg: ElastomericConfig) -> dict:
"""Compute elastomeric bearing stiffness properties.
Per AASHTO LRFD 14.7.5 and 14.7.6.
Returns dict with Kh, Kv, shape_factor, rotation_capacity.
"""
A = cfg.length_in * cfg.width_in # plan area (in²)
h_rt = cfg.total_rubber_thickness_in # total rubber thickness
G = cfg.shear_modulus_ksi
# Shape factor S = loaded plan area / (perimeter × layer thickness)
# Per AASHTO 14.7.5.1-1
perimeter = 2 * (cfg.length_in + cfg.width_in)
t_layer = cfg.layer_thickness_in
S = A / (perimeter * t_layer) if (perimeter * t_layer) > 0 else 1.0
# Horizontal stiffness: Kh = G × A / h_rt
Kh = G * A / h_rt if h_rt > 0 else 1.0e6
# Effective compressive modulus (shape factor dependent)
# Ec ≈ 3G(1 + 2κS²) for steel-reinforced bearings
# κ = material constant ≈ 0.93 for plain pads, ≈ 1.0 for reinforced
kappa = 1.0
Ec = 3.0 * G * (1.0 + 2.0 * kappa * S ** 2)
# Verticbuild_elastomeric function · python · L293-L363 (71 LOC)src/nlb/tools/bearings.py
def build_elastomeric(
cfg: ElastomericConfig,
x: float = 0.0,
y: float = 0.0,
z: float = 0.0,
tag_start: int = 1,
) -> BearingModel:
"""Build elastomeric bearing model.
Creates zeroLength element with Elastic material for shear
and ENT (compression-only) for vertical.
"""
tags = TagAllocator(tag_start)
model = BearingModel(bearing_type="elastomeric", compression_only=True)
props = _elastomeric_stiffness(cfg)
model.properties = props
# --- Nodes ---
bot_node = tags.next()
model.nodes.append({"tag": bot_node, "x": x, "y": y, "z": z})
model.bottom_nodes.append(bot_node)
top_node = tags.next()
model.nodes.append({"tag": top_node, "x": x, "y": y, "z": z})
model.top_nodes.append(top_node)
# --- Materials ---
# Horizontal shear (elastic)
mat_h = tags.next()
model.materials.append({
"tag": mat_h, "type": "Elastic",
"name": "elastomeric_shear",
"k_kip_per_in": props["Kh_build_pot_fixed function · python · L370-L422 (53 LOC)src/nlb/tools/bearings.py
def build_pot_fixed(
cfg: PotBearingConfig,
x: float = 0.0,
y: float = 0.0,
z: float = 0.0,
tag_start: int = 1,
) -> BearingModel:
"""Build fixed pot bearing — rigid in both horizontal directions."""
tags = TagAllocator(tag_start)
model = BearingModel(bearing_type="pot_fixed", compression_only=True)
bot_node = tags.next()
model.nodes.append({"tag": bot_node, "x": x, "y": y, "z": z})
model.bottom_nodes.append(bot_node)
top_node = tags.next()
model.nodes.append({"tag": top_node, "x": x, "y": y, "z": z})
model.top_nodes.append(top_node)
# Fixed horizontal: very high stiffness
mat_h = tags.next()
model.materials.append({
"tag": mat_h, "type": "Elastic",
"name": "pot_fixed_horizontal",
"k_kip_per_in": cfg.stiffness_kip_per_in,
})
# Vertical: compression only
mat_v = tags.next()
model.materials.append({
"tag": mat_v, "type": "ENT",
"name": "pot_vertical",
"kbuild_pot_guided function · python · L425-L493 (69 LOC)src/nlb/tools/bearings.py
def build_pot_guided(
cfg: PotBearingConfig,
x: float = 0.0,
y: float = 0.0,
z: float = 0.0,
tag_start: int = 1,
) -> BearingModel:
"""Build guided pot bearing — free in guided direction, fixed in other."""
tags = TagAllocator(tag_start)
model = BearingModel(bearing_type="pot_guided", compression_only=True)
guide_dir = cfg.guide_direction or 1 # default: free longitudinal
bot_node = tags.next()
model.nodes.append({"tag": bot_node, "x": x, "y": y, "z": z})
model.bottom_nodes.append(bot_node)
top_node = tags.next()
model.nodes.append({"tag": top_node, "x": x, "y": y, "z": z})
model.top_nodes.append(top_node)
# Fixed direction: very stiff
mat_fixed = tags.next()
model.materials.append({
"tag": mat_fixed, "type": "Elastic",
"name": "pot_guided_fixed",
"k_kip_per_in": cfg.stiffness_kip_per_in,
})
# Guided direction: very low stiffness (essentially free)
mat_free = tags.next()
Open data scored by Repobility · https://repobility.com
_ptfe_friction function · python · L500-L530 (31 LOC)src/nlb/tools/bearings.py
def _ptfe_friction(ptfe_type: str, temperature: str = "reference") -> dict:
"""Look up PTFE friction coefficients.
Args:
ptfe_type: Key into PTFE_FRICTION table.
temperature: "reference" (~68°F), "cold" (~20°F), "hot" (~100°F).
Returns:
Dict with mu_slow, mu_fast.
"""
if ptfe_type not in PTFE_FRICTION:
ptfe_type = "glass_filled" # default
table = PTFE_FRICTION[ptfe_type]
if temperature == "cold":
return {
"mu_slow": table["mu_slow_cold"],
"mu_fast": table["mu_fast_cold"],
}
elif temperature == "hot":
# Hot = lower friction (interpolate 20% reduction from reference)
return {
"mu_slow": table["mu_slow"] * 0.80,
"mu_fast": table["mu_fast"] * 0.80,
}
else:
return {
"mu_slow": table["mu_slow"],
"mu_fast": table["mu_fast"],
}build_ptfe_sliding function · python · L533-L613 (81 LOC)src/nlb/tools/bearings.py
def build_ptfe_sliding(
cfg: PTFEConfig,
x: float = 0.0,
y: float = 0.0,
z: float = 0.0,
tag_start: int = 1,
) -> BearingModel:
"""Build PTFE sliding bearing with velocity-dependent friction.
Generates upper and lower bound cases for temperature effects.
Uses flatSliderBearing element concept modeled with zeroLength
and friction material.
"""
tags = TagAllocator(tag_start)
model = BearingModel(bearing_type="ptfe_sliding", compression_only=True)
# Reference friction
mu_ref = _ptfe_friction(cfg.ptfe_type, "reference")
mu_cold = _ptfe_friction(cfg.ptfe_type, "cold")
mu_hot = _ptfe_friction(cfg.ptfe_type, "hot")
bot_node = tags.next()
model.nodes.append({"tag": bot_node, "x": x, "y": y, "z": z})
model.bottom_nodes.append(bot_node)
top_node = tags.next()
model.nodes.append({"tag": top_node, "x": x, "y": y, "z": z})
model.top_nodes.append(top_node)
# Friction material (reference temperature)
build_fp_single function · python · L620-L693 (74 LOC)src/nlb/tools/bearings.py
def build_fp_single(
cfg: FPSingleConfig,
x: float = 0.0,
y: float = 0.0,
z: float = 0.0,
tag_start: int = 1,
) -> BearingModel:
"""Build single friction pendulum bearing.
Self-centering period T = 2π√(R/g).
Uses SingleFPBearing element concept.
"""
tags = TagAllocator(tag_start)
model = BearingModel(bearing_type="fp_single", compression_only=True)
# Self-centering period
T = 2.0 * math.pi * math.sqrt(cfg.radius_in / G_ACCEL)
bot_node = tags.next()
model.nodes.append({"tag": bot_node, "x": x, "y": y, "z": z})
model.bottom_nodes.append(bot_node)
top_node = tags.next()
model.nodes.append({"tag": top_node, "x": x, "y": y, "z": z})
model.top_nodes.append(top_node)
# Friction material
mat_fric = tags.next()
model.materials.append({
"tag": mat_fric, "type": "frictionModel",
"name": "fp_friction",
"mu": cfg.mu,
})
# Vertical: compression only
mat_v = tags.next()
build_fp_triple function · python · L700-L788 (89 LOC)src/nlb/tools/bearings.py
def build_fp_triple(
cfg: FPTripleConfig,
x: float = 0.0,
y: float = 0.0,
z: float = 0.0,
tag_start: int = 1,
) -> BearingModel:
"""Build triple friction pendulum bearing.
Multi-stage behavior with 4 friction surfaces and 4 radii.
Uses TripleFrictionPendulum element concept.
"""
tags = TagAllocator(tag_start)
model = BearingModel(bearing_type="fp_triple", compression_only=True)
bot_node = tags.next()
model.nodes.append({"tag": bot_node, "x": x, "y": y, "z": z})
model.bottom_nodes.append(bot_node)
top_node = tags.next()
model.nodes.append({"tag": top_node, "x": x, "y": y, "z": z})
model.top_nodes.append(top_node)
# Friction materials per surface
fric_tags = []
for i, (mu, R) in enumerate(zip(
[cfg.mu1, cfg.mu2, cfg.mu3, cfg.mu4],
[cfg.R1, cfg.R2, cfg.R3, cfg.R4],
)):
mat_tag = tags.next()
model.materials.append({
"tag": mat_tag, "type": "frictionModel",
build_integral function · python · L795-L833 (39 LOC)src/nlb/tools/bearings.py
def build_integral(
x: float = 0.0,
y: float = 0.0,
z: float = 0.0,
tag_start: int = 1,
) -> BearingModel:
"""Build integral (monolithic) connection.
Uses equalDOF constraint — no relative displacement.
No bearing element, just a constraint between super and sub nodes.
"""
tags = TagAllocator(tag_start)
model = BearingModel(bearing_type="integral", compression_only=False)
bot_node = tags.next()
model.nodes.append({"tag": bot_node, "x": x, "y": y, "z": z})
model.bottom_nodes.append(bot_node)
top_node = tags.next()
model.nodes.append({"tag": top_node, "x": x, "y": y, "z": z})
model.top_nodes.append(top_node)
# equalDOF constraint: all DOFs tied
model.constraints.append({
"type": "equalDOF",
"master": bot_node,
"slave": top_node,
"dofs": [1, 2, 3, 4, 5, 6],
})
model.properties = {
"connection": "monolithic",
"relative_displacement": 0.0,
}
# No tempbuild_rocker_roller function · python · L840-L918 (79 LOC)src/nlb/tools/bearings.py
def build_rocker_roller(
cfg: RockerRollerConfig,
x: float = 0.0,
y: float = 0.0,
z: float = 0.0,
tag_start: int = 1,
) -> BearingModel:
"""Build rocker/roller bearing (vintage steel).
Uses ENT (compression-only) vertical + near-free horizontal.
Flags uplift vulnerability.
"""
tags = TagAllocator(tag_start)
model = BearingModel(bearing_type="rocker_roller", compression_only=True)
bot_node = tags.next()
model.nodes.append({"tag": bot_node, "x": x, "y": y, "z": z})
model.bottom_nodes.append(bot_node)
top_node = tags.next()
model.nodes.append({"tag": top_node, "x": x, "y": y, "z": z})
model.top_nodes.append(top_node)
# Horizontal: near-free (roller) or with some resistance (rocker)
if cfg.roller_diameter_in > 0:
# Roller: very free
mat_h = tags.next()
model.materials.append({
"tag": mat_h, "type": "Elastic",
"name": "roller_horizontal",
"k_kip_per_in"layout_bearings function · python · L925-L983 (59 LOC)src/nlb/tools/bearings.py
def layout_bearings(
num_girders: int,
girder_spacing_ft: float,
bearing_type: str,
support_type: str = "fixed",
x: float = 0.0,
y: float = 0.0,
z_center: float = 0.0,
tag_start: int = 1,
**kwargs: Any,
) -> list[BearingModel]:
"""Generate bearings for a multi-girder bridge at one support line.
Places one bearing per girder line at regular transverse spacing.
Args:
num_girders: Number of girders.
girder_spacing_ft: Girder spacing (ft).
bearing_type: BearingType value string.
support_type: "fixed" or "expansion" (affects pot bearing choice).
x: Longitudinal coordinate (inches).
y: Vertical coordinate (inches).
z_center: Transverse center coordinate (inches).
tag_start: Starting tag.
**kwargs: Forwarded to bearing config constructors.
Returns:
List of BearingModel, one per girder line.
"""
spacing_in = girder_spacing_ft * FT_TO_IN
total_width create_bearing function · python · L990-L1074 (85 LOC)src/nlb/tools/bearings.py
def create_bearing(
bearing_type: str,
x: float = 0.0,
y: float = 0.0,
z: float = 0.0,
tag_start: int = 1,
**kwargs: Any,
) -> BearingModel:
"""Create a bearing model from type string and parameters.
Main entry point for the NLB pipeline.
Args:
bearing_type: One of BearingType values.
x, y, z: Bearing location (inches).
tag_start: Starting tag.
**kwargs: Parameters forwarded to specific builder.
Returns:
BearingModel.
Raises:
ValueError: If bearing_type is not recognized.
"""
bt = bearing_type.lower().replace(" ", "_").replace("-", "_")
if bt == "elastomeric":
cfg = kwargs.get("config", ElastomericConfig(**{
k: v for k, v in kwargs.items()
if k in ElastomericConfig.__dataclass_fields__
}))
return build_elastomeric(cfg, x, y, z, tag_start)
elif bt == "pot_fixed":
cfg = kwargs.get("config", PotBearingConfig(**{
If a scraper extracted this row, it came from Repobility (https://repobility.com)
DistributionFactors class · python · L36-L61 (26 LOC)src/nlb/tools/distribution_factors.py
class DistributionFactors:
"""Live-load distribution factors for a single girder line.
Governing values (``gM_int``, etc.) are the maximum of the 1-lane and
2+-lane cases. The AASHTO formulas already embed the multiple-presence
factor (m=1.20 for 1 lane, m=1.00 for 2 lanes); the governing factor is
therefore simply ``max(g_1lane, g_2plus)``.
The lever-rule values (exterior, 1 lane) explicitly include m=1.20.
"""
# --- Governing (max of 1-lane and 2+-lane) ----------------------------
gM_int: float # Interior girder moment DF
gM_ext: float # Exterior girder moment DF
gV_int: float # Interior girder shear DF
gV_ext: float # Exterior girder shear DF
# --- Individual cases -------------------------------------------------
gM_int_1: float # Interior moment, 1 lane loaded
gM_int_2: float # Interior moment, 2+ lanes loaded
gM_ext_1: float # Exterior moment, 1 lane (lever rule × m=1.20)
gM_ext_2: float # Ecompute_kg function · python · L68-L80 (13 LOC)src/nlb/tools/distribution_factors.py
def compute_kg(n: float, I_girder: float, A_girder: float, eg: float) -> float:
"""Longitudinal stiffness parameter per AASHTO 4.6.2.2.1-1.
Args:
n: Modular ratio, Es / Ec (dimensionless).
I_girder: Moment of inertia of the steel girder alone (in⁴).
A_girder: Cross-sectional area of the steel girder alone (in²).
eg: Distance from girder centroid to centroid of deck (in).
Returns:
Kg in in⁴.
"""
return n * (I_girder + A_girder * eg ** 2)_lever_rule_exterior function · python · L87-L126 (40 LOC)src/nlb/tools/distribution_factors.py
def _lever_rule_exterior(S: float, de: float, m: float = 1.20) -> float:
"""Compute the exterior-girder DF using the lever rule.
AASHTO 4.6.2.2.2d-1 note: "Lever Rule" for one design lane loaded.
Lane placement (AASHTO 3.6.1.3.1):
- First wheel at 2 ft from the interior face of the traffic barrier.
- Second wheel 6 ft further toward interior (standard 6-ft wheel spacing).
Geometry (all distances measured from exterior girder, positive toward
interior of bridge):
- ``de`` = distance from interior face of barrier to exterior web of
exterior girder (AASHTO sign convention: positive when girder is
inboard of barrier).
- Wheel 1 at (de - 2) ft from exterior girder.
- Wheel 2 at (de + 4) ft from exterior girder.
The lever rule treats the deck as a simply-supported beam spanning *S* ft
between the exterior and first interior girder.
Args:
S: Girder spacing (ft).
de: Distance from exterior gircompute_distribution_factors function · python · L133-L266 (134 LOC)src/nlb/tools/distribution_factors.py
def compute_distribution_factors(
S: float,
L: float,
ts: float,
Kg: float,
de: float = 2.0,
Nb: int = 5,
num_lanes: int = 0,
roadway_width: float = 0.0,
strict_roa: bool = False,
) -> DistributionFactors:
"""Compute AASHTO LRFD live-load distribution factors.
Valid for Type (a) cross-sections: steel I-girder with composite concrete
deck (AASHTO Tables 4.6.2.2.2b-1, 4.6.2.2.2d-1, 4.6.2.2.3a-1, 4.6.2.2.3c-1).
The multiple-presence factor is **embedded** in both the 1-lane and 2+-lane
tabulated formulas (m = 1.20 and 1.00 respectively), so the governing factor
is simply ``max(g_1lane, g_2plus)`` with no additional scaling.
For exterior girders with one lane, the lever rule is used directly and
m = 1.20 is applied explicitly (it is not embedded in the formula).
Args:
S: Girder spacing (ft), range 3.5–16.0.
L: Span length (ft), range 20–240.
ts: Structural SoilType class · python · L52-L56 (5 LOC)src/nlb/tools/foundation.py
class SoilType(str, Enum):
SOFT_CLAY = "soft_clay"
STIFF_CLAY = "stiff_clay"
SAND = "sand"
ROCK = "rock"FoundationType class · python · L59-L63 (5 LOC)src/nlb/tools/foundation.py
class FoundationType(str, Enum):
DRILLED_SHAFT = "drilled_shaft"
DRIVEN_PILE_GROUP = "driven_pile_group"
SPREAD_FOOTING = "spread_footing"
PILE_BENT = "pile_bent"SoilLayer class · python · L71-L156 (86 LOC)src/nlb/tools/foundation.py
class SoilLayer:
"""Single soil layer in a profile.
Attributes:
soil_type: Classification (soft_clay, stiff_clay, sand, rock).
top_depth_ft: Depth to top of layer from ground surface (ft).
thickness_ft: Layer thickness (ft).
su_ksf: Undrained shear strength (ksf) — clays.
phi_deg: Friction angle (degrees) — sands.
gamma_pcf: Total unit weight (pcf).
N_spt: SPT blow count (blows/ft).
eps50: Strain at 50% strength — clays (default from su).
k_py_pci: Initial modulus of subgrade reaction for p-y (pci) — sand.
qu_ksf: Unconfined compressive strength (ksf) — rock.
"""
soil_type: str
top_depth_ft: float
thickness_ft: float
su_ksf: float = 0.0
phi_deg: float = 0.0
gamma_pcf: float = 120.0
N_spt: int = 0
eps50: float | None = None
k_py_pci: float | None = None
qu_ksf: float = 0.0
@property
def bot_depth_ft(self) -> float:
return self.top_depth_ftget_eps50 method · python · L121-L136 (16 LOC)src/nlb/tools/foundation.py
def get_eps50(self) -> float:
"""Return eps50; estimate from su if not provided (FHWA Table)."""
if self.eps50 is not None:
return self.eps50
# Matlock recommended values based on su
su = self.su_ksf
if su <= 0.5:
return 0.02
elif su <= 1.0:
return 0.01
elif su <= 2.0:
return 0.007
elif su <= 4.0:
return 0.005
else:
return 0.004Repobility (the analyzer behind this table) · https://repobility.com
get_k_py method · python · L138-L156 (19 LOC)src/nlb/tools/foundation.py
def get_k_py(self) -> float:
"""Initial modulus of subgrade reaction (pci) for sand p-y.
Per API RP 2GEO Table for submerged sand.
"""
if self.k_py_pci is not None:
return self.k_py_pci
# Approximate from friction angle (pci)
phi = self.phi_deg
if phi <= 25:
return 20.0
elif phi <= 30:
return 35.0
elif phi <= 35:
return 55.0
elif phi <= 40:
return 90.0
else:
return 115.0SiteProfile class · python · L160-L201 (42 LOC)src/nlb/tools/foundation.py
class SiteProfile:
"""Complete site geotechnical profile."""
layers: list[SoilLayer]
gwt_depth_ft: float = 10.0 # groundwater table depth from surface
scour: dict | None = None # {water_crossing: bool, depth_ft: float}
@property
def gwt_depth_in(self) -> float:
return self.gwt_depth_ft * FT_TO_IN
def layer_at_depth(self, depth_ft: float) -> SoilLayer:
"""Return the soil layer at a given depth."""
for layer in self.layers:
if layer.top_depth_ft <= depth_ft < layer.bot_depth_ft:
return layer
# Below last layer — return last
return self.layers[-1]
def effective_vertical_stress(self, depth_ft: float) -> float:
"""Compute effective vertical stress at depth (ksi).
Integrates gamma through layers, subtracts pore pressure below GWT.
"""
sigma_v = 0.0
prev_depth = 0.0
for layer in sorted(self.layers, key=lambda l: l.top_depth_ft):
layer_at_depth method · python · L170-L176 (7 LOC)src/nlb/tools/foundation.py
def layer_at_depth(self, depth_ft: float) -> SoilLayer:
"""Return the soil layer at a given depth."""
for layer in self.layers:
if layer.top_depth_ft <= depth_ft < layer.bot_depth_ft:
return layer
# Below last layer — return last
return self.layers[-1]effective_vertical_stress method · python · L178-L201 (24 LOC)src/nlb/tools/foundation.py
def effective_vertical_stress(self, depth_ft: float) -> float:
"""Compute effective vertical stress at depth (ksi).
Integrates gamma through layers, subtracts pore pressure below GWT.
"""
sigma_v = 0.0
prev_depth = 0.0
for layer in sorted(self.layers, key=lambda l: l.top_depth_ft):
top = max(layer.top_depth_ft, prev_depth)
bot = min(layer.bot_depth_ft, depth_ft)
if top >= bot:
continue
dz = (bot - top) * FT_TO_IN
sigma_v += layer.gamma_pci * dz
prev_depth = bot
if bot >= depth_ft:
break
# Pore pressure below GWT
if depth_ft > self.gwt_depth_ft:
u = GAMMA_WATER_PCI * (depth_ft - self.gwt_depth_ft) * FT_TO_IN
sigma_v -= u
return max(sigma_v, 0.0)FoundationModel class · python · L205-L228 (24 LOC)src/nlb/tools/foundation.py
class FoundationModel:
"""Complete foundation model output.
All tags are integers suitable for direct use in OpenSees commands.
All coordinates and forces are in kip-inch-second units.
"""
nodes: list[dict] = field(default_factory=list)
elements: list[dict] = field(default_factory=list)
springs: list[dict] = field(default_factory=list)
materials: list[dict] = field(default_factory=list)
boundary_conditions: list[dict] = field(default_factory=list)
top_node: int = 0
base_node: int = 0
capacity: dict = field(default_factory=dict)
cases: dict = field(default_factory=dict)
opensees_commands: list[str] = field(default_factory=list)
def summary(self) -> str:
return (
f"FoundationModel: {len(self.nodes)} nodes, "
f"{len(self.elements)} elements, {len(self.springs)} springs, "
f"{len(self.materials)} materials | "
f"top_node={self.top_node}, base_node={self.base_node}"
summary method · python · L222-L228 (7 LOC)src/nlb/tools/foundation.py
def summary(self) -> str:
return (
f"FoundationModel: {len(self.nodes)} nodes, "
f"{len(self.elements)} elements, {len(self.springs)} springs, "
f"{len(self.materials)} materials | "
f"top_node={self.top_node}, base_node={self.base_node}"
)PYCurve class · python · L235-L451 (217 LOC)src/nlb/tools/foundation.py
class PYCurve:
"""p-y curve computation for lateral soil resistance.
References:
- Matlock (1970): Soft clay
- Reese et al. (1975): Stiff clay
- API RP 2GEO: Sand
- FHWA-NHI-10-016 Ch. 9: Drilled shafts
"""
@staticmethod
def soft_clay_matlock(
depth_in: float,
su_ksi: float,
eps50: float,
diameter_in: float,
gamma_pci: float,
sigma_v_ksi: float | None = None,
) -> dict:
"""Matlock (1970) soft clay p-y curve.
pu increases from 3*su*D at surface to 9*su*D at depth.
Transition depth: J*su*D / (gamma*D + J*su) where J ≈ 0.5 (soft) or 0.25.
Args:
depth_in: Depth below ground (in).
su_ksi: Undrained shear strength (ksi).
eps50: Strain at 50% strength.
diameter_in: Shaft/pile diameter (in).
gamma_pci: Effective unit weight (pci).
sigma_v_ksi: Effective overburden (ksi), computed if Nosoft_clay_matlock method · python · L246-L295 (50 LOC)src/nlb/tools/foundation.py
def soft_clay_matlock(
depth_in: float,
su_ksi: float,
eps50: float,
diameter_in: float,
gamma_pci: float,
sigma_v_ksi: float | None = None,
) -> dict:
"""Matlock (1970) soft clay p-y curve.
pu increases from 3*su*D at surface to 9*su*D at depth.
Transition depth: J*su*D / (gamma*D + J*su) where J ≈ 0.5 (soft) or 0.25.
Args:
depth_in: Depth below ground (in).
su_ksi: Undrained shear strength (ksi).
eps50: Strain at 50% strength.
diameter_in: Shaft/pile diameter (in).
gamma_pci: Effective unit weight (pci).
sigma_v_ksi: Effective overburden (ksi), computed if None.
Returns:
dict with pu (kip/in), y50 (in), curve points [(y, p), ...].
"""
D = diameter_in
J = 0.5 # Matlock recommended for soft clay
if sigma_v_ksi is None:
sigma_v_ksi = gamma_pci * depth_in
Provenance: Repobility (https://repobility.com) — every score reproducible from /scan/
stiff_clay_reese method · python · L298-L351 (54 LOC)src/nlb/tools/foundation.py
def stiff_clay_reese(
depth_in: float,
su_ksi: float,
eps50: float,
diameter_in: float,
gamma_pci: float,
sigma_v_ksi: float | None = None,
) -> dict:
"""Reese et al. (1975) stiff clay p-y curve (no free water).
Args:
depth_in: Depth below ground (in).
su_ksi: Undrained shear strength (ksi).
eps50: Strain at 50% strength.
diameter_in: Shaft/pile diameter (in).
gamma_pci: Effective unit weight (pci).
Returns:
dict with pu, y50, curve points.
"""
D = diameter_in
if sigma_v_ksi is None:
sigma_v_ksi = gamma_pci * depth_in
# Ultimate resistance — Reese formulation
# Shallow wedge failure
ca = su_ksi # adhesion ≈ su for stiff clay
pu_wedge = (2.0 * ca * D
+ sigma_v_ksi * D
+ 2.83 * su_ksi * depth_in)
# Deep flow-around failsand_api method · python · L354-L415 (62 LOC)src/nlb/tools/foundation.py
def sand_api(
depth_in: float,
phi_deg: float,
diameter_in: float,
gamma_pci: float,
k_py_pci: float,
) -> dict:
"""API RP 2GEO sand p-y curve.
pu from passive pressure coefficients; initial modulus k*z.
Args:
depth_in: Depth below ground (in).
phi_deg: Friction angle (degrees).
diameter_in: Shaft/pile diameter (in).
gamma_pci: Effective unit weight (pci).
k_py_pci: Initial modulus of subgrade reaction (pci).
Returns:
dict with pu, k_initial, curve points.
"""
D = diameter_in
phi = math.radians(phi_deg)
# Coefficients per API
beta_angle = math.pi / 4.0 + phi / 2.0
Kp = math.tan(beta_angle) ** 2
Ka = math.tan(math.pi / 4.0 - phi / 2.0) ** 2
K0 = 1.0 - math.sin(phi)
alpha_a = phi / 2.0
# Shallow failure (wedge)
C1 = (Kp - Ka) * math.tan(phi) + K0rock method · python · L418-L451 (34 LOC)src/nlb/tools/foundation.py
def rock(
depth_in: float,
qu_ksi: float,
diameter_in: float,
) -> dict:
"""Simplified rock p-y curve.
pu = qu * D (unconfined compressive strength × diameter).
Linear-elastic up to yield, then constant.
Args:
depth_in: Depth below ground (in).
qu_ksi: Unconfined compressive strength (ksi).
diameter_in: Shaft/pile diameter (in).
Returns:
dict with pu, curve points.
"""
D = diameter_in
pu = qu_ksi * D
# Initial stiffness: 100*qu*D / D = 100*qu per unit length
k_rock = 100.0 * qu_ksi
y_yield = pu / k_rock if k_rock > 0 else 0.01
points = [
(0.0, 0.0),
(y_yield * 0.5, pu * 0.5),
(y_yield, pu),
(y_yield * 5.0, pu),
(y_yield * 20.0, pu),
]
return {"pu": pu, "y_yield": y_yield, "points": points}TZCurve class · python · L454-L576 (123 LOC)src/nlb/tools/foundation.py
class TZCurve:
"""t-z curve computation for skin friction resistance.
References:
- API RP 2GEO
- AASHTO 10th Ed. Section 10.8
- FHWA-NHI-10-016 Ch. 13
"""
@staticmethod
def clay_alpha(
su_ksi: float,
diameter_in: float,
spacing_in: float,
) -> dict:
"""Alpha method for clay skin friction.
tult = alpha * su (per unit length = alpha * su * pi * D * dz)
alpha from API: alpha = 0.5*(su/sigma_v')^-0.5 for su/sigma_v' <= 1
alpha = 0.5*(su/sigma_v')^-0.25 for su/sigma_v' > 1
Simplified: alpha ≈ min(1.0, 0.55 - 0.1*(su_ksf - 1.5)) per FHWA
Args:
su_ksi: Undrained shear strength (ksi).
diameter_in: Shaft diameter (in).
spacing_in: Spring spacing (in).
Returns:
dict with tult (kip/in), z_peak (in), curve points [(z, t)].
"""
su_ksf = su_ksi / KSF_TO_KSI
# Alpha per AASHTO/FHWAclay_alpha method · python · L464-L512 (49 LOC)src/nlb/tools/foundation.py
def clay_alpha(
su_ksi: float,
diameter_in: float,
spacing_in: float,
) -> dict:
"""Alpha method for clay skin friction.
tult = alpha * su (per unit length = alpha * su * pi * D * dz)
alpha from API: alpha = 0.5*(su/sigma_v')^-0.5 for su/sigma_v' <= 1
alpha = 0.5*(su/sigma_v')^-0.25 for su/sigma_v' > 1
Simplified: alpha ≈ min(1.0, 0.55 - 0.1*(su_ksf - 1.5)) per FHWA
Args:
su_ksi: Undrained shear strength (ksi).
diameter_in: Shaft diameter (in).
spacing_in: Spring spacing (in).
Returns:
dict with tult (kip/in), z_peak (in), curve points [(z, t)].
"""
su_ksf = su_ksi / KSF_TO_KSI
# Alpha per AASHTO/FHWA simplified
if su_ksf <= 1.5:
alpha = 0.55
else:
alpha = max(0.25, 0.55 - 0.1 * (su_ksf - 1.5))
alpha = min(1.0, alpha)
# Unit skin friction
fs_ksisand_beta method · python · L515-L576 (62 LOC)src/nlb/tools/foundation.py
def sand_beta(
sigma_v_ksi: float,
phi_deg: float,
diameter_in: float,
spacing_in: float,
depth_in: float,
) -> dict:
"""Beta method for sand skin friction.
tult = beta * sigma_v' (per unit area)
beta from AASHTO Table 10.8.3.5.2b-1 (simplified).
Args:
sigma_v_ksi: Effective vertical stress (ksi).
phi_deg: Friction angle (degrees).
diameter_in: Shaft diameter (in).
spacing_in: Spring spacing (in).
depth_in: Depth below ground (in).
Returns:
dict with tult (kip/in), z_peak (in), curve points.
"""
# Beta from AASHTO — increases with friction angle, decreases with depth
depth_ft = depth_in / FT_TO_IN
N_corr = depth_ft # simplified depth correction
if phi_deg <= 25:
beta_base = 0.25
elif phi_deg <= 30:
beta_base = 0.35
elif phi_deg <= 35:
QZCurve class · python · L579-L694 (116 LOC)src/nlb/tools/foundation.py
class QZCurve:
"""Q-z curve computation for end bearing resistance.
References:
- Reese & O'Neill (1988): Drilled shafts
- AASHTO 10th Ed. Section 10.8.3.5
"""
@staticmethod
def clay(su_ksi: float, diameter_in: float) -> dict:
"""Clay end bearing Q-z curve.
qult = Nc * su, Nc ≈ 9 per AASHTO/Reese & O'Neill.
Args:
su_ksi: Undrained shear strength at tip (ksi).
diameter_in: Shaft diameter (in).
Returns:
dict with Qult (kip), z_peak (in), curve points [(z, Q)].
"""
Nc = 9.0
area = math.pi * diameter_in ** 2 / 4.0
qult = Nc * su_ksi
Qult = qult * area # Total end bearing (kip)
z_peak = 0.05 * diameter_in # 5% of diameter per Reese & O'Neill
points = [
(0.0, 0.0),
(0.002 * diameter_in, 0.15 * Qult),
(0.013 * diameter_in, 0.50 * Qult),
(0.042 * diameter_in, 0.85 * Qult),
clay method · python · L588-L616 (29 LOC)src/nlb/tools/foundation.py
def clay(su_ksi: float, diameter_in: float) -> dict:
"""Clay end bearing Q-z curve.
qult = Nc * su, Nc ≈ 9 per AASHTO/Reese & O'Neill.
Args:
su_ksi: Undrained shear strength at tip (ksi).
diameter_in: Shaft diameter (in).
Returns:
dict with Qult (kip), z_peak (in), curve points [(z, Q)].
"""
Nc = 9.0
area = math.pi * diameter_in ** 2 / 4.0
qult = Nc * su_ksi
Qult = qult * area # Total end bearing (kip)
z_peak = 0.05 * diameter_in # 5% of diameter per Reese & O'Neill
points = [
(0.0, 0.0),
(0.002 * diameter_in, 0.15 * Qult),
(0.013 * diameter_in, 0.50 * Qult),
(0.042 * diameter_in, 0.85 * Qult),
(z_peak, Qult),
(0.10 * diameter_in, Qult),
]
return {"Qult": Qult, "z_peak": z_peak, "Nc": Nc, "points": points}Open data scored by Repobility · https://repobility.com
sand method · python · L619-L665 (47 LOC)src/nlb/tools/foundation.py
def sand(sigma_v_ksi: float, phi_deg: float, diameter_in: float) -> dict:
"""Sand end bearing Q-z curve.
qult = Nq * sigma_v', Nq from AASHTO tables.
Args:
sigma_v_ksi: Effective vertical stress at tip (ksi).
phi_deg: Friction angle at tip (degrees).
diameter_in: Shaft diameter (in).
Returns:
dict with Qult (kip), z_peak (in), Nq, curve points.
"""
# Nq from AASHTO (simplified Meyerhof/Berezantzev)
if phi_deg <= 25:
Nq = 8.0
elif phi_deg <= 28:
Nq = 12.0
elif phi_deg <= 30:
Nq = 20.0
elif phi_deg <= 33:
Nq = 30.0
elif phi_deg <= 36:
Nq = 50.0
elif phi_deg <= 40:
Nq = 80.0
else:
Nq = 120.0
area = math.pi * diameter_in ** 2 / 4.0
# Limit qult to 100 ksf ≈ 0.694 ksi per AASHTO
qult = min(Nq * sigma_v_ksi, 0.694)
Qultrock method · python · L668-L694 (27 LOC)src/nlb/tools/foundation.py
def rock(qu_ksi: float, diameter_in: float) -> dict:
"""Rock end bearing Q-z curve.
qult = 2.5 * qu per FHWA-NHI-10-016.
Args:
qu_ksi: Unconfined compressive strength (ksi).
diameter_in: Shaft diameter (in).
Returns:
dict with Qult (kip), z_peak (in), curve points.
"""
area = math.pi * diameter_in ** 2 / 4.0
qult = 2.5 * qu_ksi
Qult = qult * area
z_peak = 0.01 * diameter_in # Rock — very stiff
points = [
(0.0, 0.0),
(z_peak * 0.25, 0.40 * Qult),
(z_peak * 0.50, 0.70 * Qult),
(z_peak, Qult),
(z_peak * 5.0, Qult),
]
return {"Qult": Qult, "z_peak": z_peak, "points": points}TagAllocator class · python · L701-L718 (18 LOC)src/nlb/tools/foundation.py
class TagAllocator:
"""Simple sequential tag allocator for OpenSees objects."""
def __init__(self, start: int = 1):
self._next = start
def next(self, count: int = 1) -> int | list[int]:
if count == 1:
tag = self._next
self._next += 1
return tag
tags = list(range(self._next, self._next + count))
self._next += count
return tags
@property
def current(self) -> int:
return self._nextnext method · python · L707-L714 (8 LOC)src/nlb/tools/foundation.py
def next(self, count: int = 1) -> int | list[int]:
if count == 1:
tag = self._next
self._next += 1
return tag
tags = list(range(self._next, self._next + count))
self._next += count
return tagsbuild_circular_section function · python · L725-L801 (77 LOC)src/nlb/tools/foundation.py
def build_circular_section(
tag_alloc: TagAllocator,
diameter_in: float,
fc_ksi: float,
fy_ksi: float = 60.0,
n_bars: int = 12,
bar_size: str = "#10",
cover_in: float = 3.0,
) -> tuple[list[dict], int]:
"""Build OpenSees fiber section for circular RC section.
Uses Concrete01 (confined/unconfined) and Steel02 materials.
Returns:
(materials_list, section_tag)
"""
materials = []
# Concrete — Mander confined model (simplified)
# Unconfined concrete
fc = fc_ksi
ec0 = 0.002
fcu = 0.0 # spalling
ecu = 0.005
unconf_tag = tag_alloc.next()
materials.append({
"tag": unconf_tag,
"type": "Concrete01",
"params": [-fc, -ec0, -fcu, -ecu],
"description": f"Unconfined concrete f'c={fc} ksi",
})
# Confined concrete (simplified Mander — ~1.3x f'c for typical confinement)
fcc = 1.3 * fc
ecc0 = 0.004
fccu = 0.2 * fcc
eccu = 0.015
conf_tag = tag_alloc.next_spring_depths function · python · L808-L830 (23 LOC)src/nlb/tools/foundation.py
def _spring_depths(
length_in: float,
spacing_in: float = 12.0,
scour_depth_in: float = 0.0,
) -> list[float]:
"""Generate spring depths along shaft, skipping scour zone.
Args:
length_in: Total embedded length (in).
spacing_in: Spring spacing (in), default 12 (1 ft).
scour_depth_in: Depth of scour below ground (in). Springs above
this depth are omitted.
Returns:
List of depths (in) from ground surface where springs are placed.
"""
depths = []
d = spacing_in / 2.0 # First spring at half-spacing
while d < length_in:
if d >= scour_depth_in:
depths.append(d)
d += spacing_in
return depths_build_py_spring function · python · L833-L890 (58 LOC)src/nlb/tools/foundation.py
def _build_py_spring(
layer: SoilLayer,
depth_in: float,
diameter_in: float,
spacing_in: float,
site: SiteProfile,
multiplier: float = 1.0,
) -> dict:
"""Build a single p-y spring at given depth.
Selects formulation based on soil type. Returns curve data with
multiplier applied for upper/lower bound analysis.
"""
sigma_v = site.effective_vertical_stress(depth_in / FT_TO_IN)
if layer.soil_type in (SoilType.SOFT_CLAY, "soft_clay"):
curve = PYCurve.soft_clay_matlock(
depth_in=depth_in,
su_ksi=layer.su_ksi,
eps50=layer.get_eps50(),
diameter_in=diameter_in,
gamma_pci=layer.gamma_pci,
sigma_v_ksi=sigma_v,
)
elif layer.soil_type in (SoilType.STIFF_CLAY, "stiff_clay"):
curve = PYCurve.stiff_clay_reese(
depth_in=depth_in,
su_ksi=layer.su_ksi,
eps50=layer.get_eps50(),
diameter_in=diameter_in,
_build_tz_spring function · python · L893-L934 (42 LOC)src/nlb/tools/foundation.py
def _build_tz_spring(
layer: SoilLayer,
depth_in: float,
diameter_in: float,
spacing_in: float,
site: SiteProfile,
multiplier: float = 1.0,
) -> dict:
"""Build a single t-z spring at given depth."""
if layer.soil_type in (SoilType.SOFT_CLAY, SoilType.STIFF_CLAY, "soft_clay", "stiff_clay"):
curve = TZCurve.clay_alpha(
su_ksi=layer.su_ksi,
diameter_in=diameter_in,
spacing_in=spacing_in,
)
elif layer.soil_type in (SoilType.SAND, "sand"):
sigma_v = site.effective_vertical_stress(depth_in / FT_TO_IN)
curve = TZCurve.sand_beta(
sigma_v_ksi=sigma_v,
phi_deg=layer.phi_deg,
diameter_in=diameter_in,
spacing_in=spacing_in,
depth_in=depth_in,
)
elif layer.soil_type in (SoilType.ROCK, "rock"):
# Rock skin friction — use alpha method with high su
su_equiv = layer.qu_ksi / 2.0 # su ≈ qu/2
curve = TZCurvIf a scraper extracted this row, it came from Repobility (https://repobility.com)
build_drilled_shaft function · python · L937-L1211 (275 LOC)src/nlb/tools/foundation.py
def build_drilled_shaft(
params: dict,
site: SiteProfile,
tag_alloc: TagAllocator | None = None,
spring_spacing_ft: float = 1.0,
) -> FoundationModel:
"""Build complete drilled shaft foundation model.
Args:
params: {
diameter_ft: float, # Shaft diameter (ft)
length_ft: float, # Embedded length (ft)
above_grade_ft: float, # Extension above ground (ft), default 0
fc_ksi: float, # Concrete strength (ksi), default 4.0
fy_ksi: float, # Rebar yield strength (ksi), default 60
n_bars: int, # Number of longitudinal bars
bar_size: str, # e.g. "#10"
cover_in: float, # Clear cover (in), default 3
}
site: SiteProfile with soil layers and GWT.
tag_alloc: Tag allocator (created if None).
spring_spacing_ft: Spring spacing in ft (default 1.0).
Returns:
build_spread_footing function · python · L1218-L1354 (137 LOC)src/nlb/tools/foundation.py
def build_spread_footing(
params: dict,
site: SiteProfile,
tag_alloc: TagAllocator | None = None,
) -> FoundationModel:
"""Build spread footing with Winkler spring model.
Uses compression-only (ENT material) zeroLength springs on a grid.
Args:
params: {
length_ft: float, # Footing length in plan (ft)
width_ft: float, # Footing width in plan (ft)
depth_ft: float, # Embedment depth (ft)
thickness_ft: float, # Footing thickness (ft), default 3
subgrade_modulus_kcf: float, # kcf (default 100)
n_springs_l: int, # Springs along length (default 5)
n_springs_w: int, # Springs along width (default 5)
}
site: SiteProfile.
tag_alloc: Tag allocator.
Returns:
FoundationModel with translational + rotational stiffness.
"""
if tag_alloc is None:
tag_alloc = TagAllocator(start=2000)_get_p_multipliers function · python · L1372-L1420 (49 LOC)src/nlb/tools/foundation.py
def _get_p_multipliers(spacing_over_D: float, n_rows: int) -> list[float]:
"""Get p-multipliers for pile group per AASHTO 10.7.2.4.
Interpolates between tabulated values.
Args:
spacing_over_D: Center-to-center spacing / pile diameter.
n_rows: Number of rows in loading direction.
Returns:
List of p-multiplier per row [lead, 2nd, 3rd, ... trailing].
"""
# Breakpoints
breakpoints = [3.0, 4.0, 5.0, 6.0, 8.0]
values_table = [
[0.80, 0.40, 0.30, 0.30],
[0.85, 0.55, 0.45, 0.40],
[0.90, 0.65, 0.55, 0.50],
[0.95, 0.75, 0.65, 0.60],
[1.00, 0.90, 0.80, 0.75],
]
s = max(3.0, min(8.0, spacing_over_D))
# Find bracketing indices
for i in range(len(breakpoints) - 1):
if breakpoints[i] <= s <= breakpoints[i + 1]:
frac = (s - breakpoints[i]) / (breakpoints[i + 1] - breakpoints[i])
interp = [
values_table[i][j] + frac * (values_table[i + 1][j