Created
November 16, 2024 08:41
-
-
Save likecodingloveproblems/97b437cf0d776f690daf6684402b983e to your computer and use it in GitHub Desktop.
Sample Opensees API wrapper
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import enum | |
from typing import Any, Literal, Optional | |
from openseespy import opensees as ops | |
from pydantic import BaseModel, Field | |
def tag_generator(): | |
count = 0 | |
def counter(): | |
nonlocal count | |
count += 1 | |
return count | |
return counter | |
class TaggedObject(BaseModel): | |
tag: int = Field(default_factory=tag_generator()) | |
class Wipe(BaseModel): | |
def model_post_init(self, __context: Any) -> None: | |
ops.wipe() | |
class Model(BaseModel): | |
type_: str = "basic" | |
ndm: Literal[1, 2, 3] | |
ndf: Optional[int] = Field(None, gt=0, lt=7, init=False) | |
def model_post_init(self, __context: Any) -> None: | |
if self.ndf is None: | |
self.ndf = int(self.ndm * (self.ndm + 1) / 2) | |
ops.model(self.type_, "-ndm", self.ndm, "-ndf", self.ndf) | |
class Node(TaggedObject): | |
crds: list[float] | |
def model_post_init(self, __context: Any) -> None: | |
ops.node(self.tag, *self.crds) | |
class FixEnum(enum.Enum): | |
FREE = 0 | |
FIX = 1 | |
class Fix(BaseModel): | |
node: Node | |
values: list[FixEnum] | |
def model_post_init(self, __context: Any) -> None: | |
fix_values = [constr.value for constr in self.values] | |
ops.fix(self.node.tag, *fix_values) | |
class Material(TaggedObject): | |
pass | |
class Elastic(Material): | |
E: float | |
eta: float = 0.0 | |
Eneg: Optional[float] = None | |
def model_post_init(self, __context: Any) -> None: | |
if self.Eneg is None: | |
self.Eneg = self.E | |
ops.uniaxialMaterial( | |
self.__class__.__name__, | |
self.tag, | |
self.E, | |
self.eta, | |
self.Eneg, | |
) | |
class MassMatrixEnum(enum.Enum): | |
LUMPED = 0 | |
CONSISTENT = 1 | |
class RayleighDampingEnum(enum.Enum): | |
OFF = 0 | |
ON = 1 | |
class Truss(TaggedObject): | |
nodes: list[Node] | |
A: float | |
material: Material | |
rho: float = 0.0 | |
cFlag: MassMatrixEnum = MassMatrixEnum.LUMPED | |
rFlag: RayleighDampingEnum = RayleighDampingEnum.OFF | |
def model_post_init(self, __context: Any) -> None: | |
ops.element( | |
self.__class__.__name__, | |
self.tag, | |
*(node.tag for node in self.nodes), | |
self.A, | |
self.material.tag, | |
"-rho", | |
self.rho, | |
"-cMass", | |
self.cFlag.value, | |
"-doRayleigh", | |
self.rFlag.value, | |
) | |
class TimeSeries(TaggedObject): | |
pass | |
class TimeSeriesLinear(TimeSeries): | |
type_: str = "Linear" | |
factor: Optional[float] = None | |
tStart: Optional[float ]=None | |
def model_post_init(self, __context: Any) -> None: | |
command = [self.type_, self.tag] | |
if self.factor is not None: | |
command.extend(['-factor', self.factor]) | |
if self.tStart is not None: | |
command.extend(['-tStart', self.tStart]) | |
ops.timeSeries( | |
*command | |
) | |
class PatternPlain(TaggedObject): | |
type_: str = "Plain" | |
time_series: TimeSeries | |
fact: Optional[float] = None | |
def model_post_init(self, __context: Any) -> None: | |
command = [ | |
self.type_, | |
self.tag, | |
self.time_series.tag, | |
] | |
if self.fact is not None: | |
command.extend(["-fact", self.fact]) | |
ops.pattern(*command) | |
class Load(BaseModel): | |
node: Node | |
values: list[float] | |
def model_post_init(self, __context: Any) -> None: | |
ops.load(self.node.tag, *self.values) | |
class System(BaseModel): | |
type_: Literal[ | |
"BandGen", | |
"BandSPD", | |
"ProfileSPD", | |
"SuperLU", | |
"UmfPack", | |
"FullGeneral", | |
"SparseSYM", | |
"Mumps", | |
] | |
def model_post_init(self, __context: Any) -> None: | |
ops.system(self.type_) | |
class Numberer(BaseModel): | |
type_: Literal["Plain", "RCM", "AMD", "ParallelPlain", "ParallelRCM"] | |
def model_post_init(self, __context: Any) -> None: | |
ops.numberer(self.type_) | |
class Constraints(BaseModel): | |
type_: Literal["Plain", "Transformation"] | |
alphaS: float = 1.0 | |
alphaM: float = 1.0 | |
@property | |
def __accept_alpha_params(self): | |
return self.type_ in {"Lagrange", "Penalty"} | |
def model_post_init(self, __context: Any) -> None: | |
if self.__accept_alpha_params: | |
ops.constraints(self.type_, self.alphaS, self.alphaM) | |
else: | |
ops.constraints(self.type_) | |
class IntegratorLoadControl(BaseModel): | |
type_: str = "LoadControl" | |
incr: float | |
num_iter: int = 1 | |
min_incr: Optional[float] = None | |
max_incr: Optional[float] = None | |
def model_post_init(self, __context: Any) -> None: | |
if self.min_incr is None: | |
self.min_incr = self.incr | |
if self.max_incr is None: | |
self.max_incr = self.incr | |
ops.integrator( | |
self.type_, self.incr, self.num_iter, self.min_incr, self.max_incr | |
) | |
class AlgorithmLinear(BaseModel): | |
type_: str = "Linear" | |
secant: bool = False | |
initial: bool = False | |
factor_once: bool = False | |
def model_post_init(self, __context: Any) -> None: | |
ops.algorithm(self.type_, self.secant, self.initial, self.factor_once) | |
class Analysis(BaseModel): | |
type_: Literal["Static", "Transient", "VariableTransient", "PFEM"] | |
def model_post_init(self, __context: Any) -> None: | |
ops.analysis(self.type_) | |
def analyze( | |
num_incr: int = 1, | |
dt: float = 0.0, | |
dt_min: float = 0.0, | |
dt_max: float = 0.0, | |
jd: float = 0.0, | |
) -> bool: | |
return ops.analyze(num_incr, dt, dt_min, dt_max, jd) | |
class Analyze(BaseModel): | |
num_incr: int = 1 | |
dt: float = 0.0 | |
dt_min: float = 0.0 | |
dt_max: float = 0.0 | |
Jd: float = 0.0 | |
failed: Optional[bool] = None | |
def model_post_init(self, __context: Any) -> None: | |
self.failed = ops.analyze(self.num_incr, self.dt, self.dt_min, self.dt_max, self.Jd) < 0 | |
def node_disp(node: Node, dof: int = -1) -> float: | |
return ops.nodeDisp(node.tag, dof) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
######################################################################## | |
####### the application part ####### | |
######################################################################## | |
from ops import ( | |
Wipe, | |
Model, | |
Node, | |
Fix, | |
FixEnum, | |
Elastic, | |
Truss, | |
TimeSeriesLinear, | |
Load, | |
PatternPlain, | |
System, | |
Numberer, | |
Constraints, | |
IntegratorLoadControl, | |
AlgorithmLinear, | |
Analysis, | |
Analyze, | |
node_disp, | |
) | |
# ------------------------------ | |
# Start of model generation | |
# ----------------------------- | |
# remove existing model | |
Wipe() | |
# set modelbuilder | |
Model(ndm=2, ndf=2) | |
# create nodes | |
fixed_nodes = [ | |
Node(crds=[0.0, 0.0]), | |
Node(crds=[144.0, 0.0]), | |
Node(crds=[168.0, 0.0]), | |
] | |
top_node = Node(crds=[72.0, 96.0]) | |
# set boundary condition | |
for node in fixed_nodes: | |
Fix(node=node, values=[FixEnum.FIX, FixEnum.FIX]) | |
# define materials | |
material = Elastic(E=3000.0) | |
# define elements | |
areas = [10.0, 5.0, 5.0] | |
for node, area in zip(fixed_nodes, areas): | |
Truss(nodes=[node, top_node], A=area, material=material) | |
# create TimeSeries | |
time_series = TimeSeriesLinear() | |
# create a plain load pattern | |
PatternPlain(time_series=time_series) | |
# Create the nodal load - command: load nodeID xForce yForce | |
Load(node=top_node, values=[100.0, -50.0]) | |
# ------------------------------ | |
# Start of analysis generation | |
# ------------------------------ | |
# create SOE | |
System(type_="BandSPD") | |
# create DOF number | |
Numberer(type_="RCM") | |
# create constraint handler | |
Constraints(type_="Plain") | |
# create integrator | |
IntegratorLoadControl(incr=1.0) | |
# create algorithm | |
AlgorithmLinear() | |
# create analysis object | |
Analysis(type_="Static") | |
# perform the analysis | |
analyze_ = Analyze() | |
if analyze_.failed: | |
print("analyze failed!") | |
ux = node_disp(node=top_node, dof=1) | |
uy = node_disp(node=top_node, dof=2) | |
if ( | |
abs(ux - 0.53009277713228375450) < 1e-12 | |
and abs(uy + 0.17789363846931768864) < 1e-12 | |
): | |
print("Passed!") | |
else: | |
print("Failed!") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment