Skip to content

Commit

Permalink
Further refactoring of FEM Step data and move solver specific options…
Browse files Browse the repository at this point in the history
… to a new subclass of "SolverOptions" per solver.

Start to remove all use of metadata as a data carrier in a FEM export process.
  • Loading branch information
Krande committed Oct 4, 2021
1 parent 94e1a58 commit 18481d6
Show file tree
Hide file tree
Showing 21 changed files with 769 additions and 634 deletions.
12 changes: 11 additions & 1 deletion src/ada/concepts/levels.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@
_step_types = Union[StepSteadyState, StepEigen, StepImplicit, StepExplicit]


@dataclass
class _ConvertOptions:
ecc_to_mpc: bool = True
hinges_to_coupling: bool = True


class Part(BackendGeom):
"""A Part superclass design to host all relevant information for cad and FEM modelling."""

Expand Down Expand Up @@ -665,7 +671,7 @@ def __init__(
self._user = user

self._ifc_file = assembly_to_ifc_file(self)

self._convert_options = _ConvertOptions()
self._ifc_sections = None
self._ifc_materials = None
self._source_ifc_files = dict()
Expand Down Expand Up @@ -1142,6 +1148,10 @@ def presentation_layers(self):
def user(self) -> User:
return self._user

@property
def convert_options(self) -> _ConvertOptions:
return self._convert_options

def __repr__(self):
nbms = len([bm for p in self.get_all_subparts() for bm in p.beams]) + len(self.beams)
npls = len([pl for p in self.get_all_subparts() for pl in p.plates]) + len(self.plates)
Expand Down
8 changes: 8 additions & 0 deletions src/ada/core/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -887,6 +887,14 @@ def __init__(self, start=1, prefix=None):
def set_i(self, i):
self.i = i

@property
def prefix(self):
return self._prefix

@prefix.setter
def prefix(self, value):
self._prefix = value

def __iter__(self):
return self

Expand Down
4 changes: 2 additions & 2 deletions src/ada/fem/interactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,9 @@ def parent(self):
@parent.setter
def parent(self, value):
from ada import FEM
from ada.fem import Step
from ada.fem.steps import Step

if type(value) not in (FEM, Step) and value is not None:
if type(value) is not FEM and value is not None and issubclass(type(value), Step) is False:
raise ValueError(f'Parent type "{type(value)}" is not supported')
self._parent = value

Expand Down
52 changes: 52 additions & 0 deletions src/ada/fem/io/abaqus/solver.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from dataclasses import dataclass

from ada.fem.io import FEATypes
from ada.fem.steps import SolverOptions


class StabilizeTypes:
ENERGY = "energy"
DAMPING = "damping"
CONTINUE = "continue"


@dataclass
class Stabilize:
factor: float
allsdtol: float
stabilize_type: StabilizeTypes = StabilizeTypes.ENERGY
energy: float = None
damping: float = None
stabilize_continue: bool = True

def to_input_str(self):
st = StabilizeTypes
stable_map = {
st.ENERGY: f"stabilize={self.factor}, allsdtol={self.allsdtol}",
st.DAMPING: f"stabilize, factor={self.factor}, allsdtol={self.allsdtol}",
st.CONTINUE: "stabilize, continue=ON",
}
stabilize_str = stable_map.get(self.stabilize_type, None)
if stabilize_str is None:
raise ValueError(f'Unrecognized stabilization type "{self.stabilize_type}"')

return stabilize_str


@dataclass
class SolverOptionsAbaqus(SolverOptions):

init_accel_calc: bool = True
restart_int: int = None
unsymm: bool = False
stabilize: Stabilize = None

"""
:param init_accel_calc: Calculate Initial acceleration in the beginning of the step
:param restart_int: Restart interval
:param unsymm: Unsymmetric Matrix storage (default=False)
:param stabilize: Default=None.
"""

def __post_init__(self):
self.solver = FEATypes.ABAQUS
52 changes: 52 additions & 0 deletions src/ada/fem/io/abaqus/write_loads.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from ada.fem import Load


def load_str(load: Load) -> str:
load_map = {Load.TYPES.GRAVITY: gravity_load_str, Load.TYPES.FORCE: force_load_str}
load_str_func = load_map.get(load.type, None)

if load_str_func is None:
raise ValueError("Unsupported load type", load.type)

return load_str_func(load)


def gravity_load_str(load: Load) -> str:
dof = [0, 0, 1] if load.dof is None else load.dof
dof_str = ", ".join([str(x) for x in dof[:3]])
return f"""** Name: gravity Type: Gravity\n*Dload\n, GRAV, {load.magnitude}, {dof_str}"""


def force_load_str(load: Load) -> str:
from .writer import get_instance_name

lstr = ""
bc_text_f = ""
bc_text_m = ""

fo = 0
instance_name = get_instance_name(load.fem_set, Load)
for i, f in enumerate(load.dof[:3]):
if f == 0.0 or f is None:
continue
total_force = f * load.magnitude
bc_text_f += f" {instance_name}, {i + 1}, {total_force}\n"
fo += 1

mom = 0
for i, m in enumerate(load.dof[3:]):
if m == 0.0 or m is None:
continue
mom += 1
bc_text_m += f" {instance_name}, {i + 4}, {m}\n"

lstr += "\n" if "\n" not in lstr[-2:] != "" else ""
follower_str = "" if load.follower_force is False else ", follower"
follower_str += f", amplitude={load.amplitude}" if load.amplitude is not None else ""
if fo != 0:
forc_name = load.name + "_F"
lstr += f"** Name: {forc_name} Type: Concentrated force\n*Cload{follower_str}\n{bc_text_f}"
if mom != 0:
mom_name = load.name + "_M"
lstr += f"** Name: {mom_name} Type: Moment\n*Cload{follower_str}\n{bc_text_m}"
return lstr.strip()
101 changes: 37 additions & 64 deletions src/ada/fem/io/abaqus/write_steps.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,29 @@
import logging
from typing import Union

import numpy as np

from ada.core.utils import bool2text
from ada.fem.steps import Step, StepEigen, StepExplicit, StepImplicit, StepSteadyState
from ada.fem.steps import (
Step,
StepEigen,
StepEigenComplex,
StepExplicit,
StepImplicit,
StepSteadyState,
)

from .templates import step_inp_str

_step_types = Union[StepEigen, StepExplicit, StepImplicit, StepSteadyState, StepEigenComplex]


class AbaStep:
def __init__(self, step: Union[Step.TYPES]):
def __init__(self, step: _step_types):
self.step = step
if step.solver_options is None:
from .solver import SolverOptionsAbaqus

step.solver_options = SolverOptionsAbaqus()

@property
def _hist_output_str(self):
Expand Down Expand Up @@ -57,7 +69,7 @@ def bc_str(self):

@property
def load_str(self):
from .writer import load_str
from .write_loads import load_str

if len(self.step.loads) == 0:
return "** No Loads"
Expand All @@ -66,9 +78,9 @@ def load_str(self):

@property
def restart_request_str(self):
if self.step.restart_int is None:
if self.step.solver_options.restart_int is None:
return "** No Restart Requests"
return f"*Restart, write, frequency={self.step.restart_int}"
return f"*Restart, write, frequency={self.step.solver_options.restart_int}"

@property
def str(self):
Expand Down Expand Up @@ -106,7 +118,7 @@ def str(self):

def dynamic_implicit_str(step: StepImplicit):
return f"""*Step, name={step.name}, nlgeom={bool2text(step.nl_geom)}, inc={step.total_incr}
*Dynamic,application={step.dyn_type}, INITIAL={bool2text(step.init_accel_calc)}
*Dynamic,application={step.dyn_type}, INITIAL={bool2text(step.solver_options.init_accel_calc)}
{step.init_incr},{step.total_time},{step.min_incr}, {step.max_incr}"""


Expand All @@ -119,42 +131,19 @@ def explicit_str(step: StepExplicit):


def static_step_str(step: StepImplicit):
static_str = ""
stabilize = step.stabilize
if stabilize is None:
pass
elif type(stabilize) is dict:
stabkeys = list(stabilize.keys())
if "energy" in stabkeys:
static_str += ", stabilize={}, allsdtol={}".format(stabilize["energy"], stabilize["allsdtol"])
elif "damping" in stabkeys:
static_str += ", stabilize, factor={}, allsdtol={}".format(stabilize["damping"], stabilize["allsdtol"])
elif "continue" in stabkeys:
if stabilize["continue"] == "YES":
static_str += ", stabilize, continue={}".format(stabilize["continue"])
else:
static_str += ", stabilize=0.0002, allsdtol=0.05"
print(
'Unrecognized stabilization type "{}". Will revert to energy stabilization "{}"'.format(
stabkeys[0], static_str
)
)
elif stabilize is True:
static_str += ", stabilize=0.0002, allsdtol=0.05"
elif stabilize is False:
pass
else:
static_str += ", stabilize=0.0002, allsdtol=0.05"
logging.error(
"Unrecognized stabilize input. Can be bool, dict or None. " 'Reverting to default stabilizing type "energy"'
)
stabilize_str = ""
stabilize = step.solver_options.stabilize

if stabilize is not None:
stabilize_str = ", " + stabilize.to_input_str()

line1 = (
f"*Step, name={step.name}, nlgeom={bool2text(step.nl_geom)}, "
f"unsymm={bool2text(step.unsymm)}, inc={step.total_incr}"
f"unsymm={bool2text(step.solver_options.unsymm)}, inc={step.total_incr}"
)

return f"""{line1}
*Static{static_str}
*Static{stabilize_str}
{step.init_incr}, {step.total_time}, {step.min_incr}, {step.max_incr}"""


Expand All @@ -169,12 +158,13 @@ def eigenfrequency_str(step: StepEigen):
"""


def complex_eig_str(step: StepEigen):
def complex_eig_str(step: StepEigenComplex):
unsymm = bool2text(step.solver_options.unsymm)
return f"""** ----------------------------------------------------------------
**
** STEP: complex_eig
**
*Step, name=complex_eig, nlgeom=NO, perturbation, unsymm=YES
*Step, name={step.name}, nlgeom=NO, perturbation, unsymm={unsymm}
*Complex Frequency, friction damping=NO
{step.num_eigen_modes}, , ,
"""
Expand All @@ -186,43 +176,26 @@ def steady_state_response_str(step: StepSteadyState) -> str:
load = step.unit_load
directions = [dof for dof in load.dof if dof is not None]
if len(directions) != 1:
raise ValueError("Unit load in a steady state analysis can only work in a single direction")
raise ValueError("Steady state analysis supports only a Unit load in a single degree of freedom")

direction = directions[0]
magnitude = load.magnitude
node_ref = get_instance_name(load.fem_set.members[0], step)

return f"""** ----------------------------------------------------------------
*STEP,NAME={step.name}_{step.fmin}_{step.fmax}Hz
*STEADY STATE DYNAMICS, DIRECT, INTERVAL=RANGE
{add_freq_range(step.fmin, step.fmax)}
{add_freq_range(step.fmin, step.fmax)}
*GLOBAL DAMPING, ALPHA={step.alpha} , BETA={step.beta}
**
*LOAD CASE, NAME=LC1
*CLOAD, OP=NEW
{node_ref}, {direction}, {magnitude}
*END LOAD CASE
"""


# **
# *OUTPUT, FIELD, FREQUENCY=1
# *NODE OUTPUT
# U
# **
# ** *OUTPUT, HISTORY, FREQUENCY=1
# ** *NODE OUTPUT, NSET=accel_data_set
# ** UT, AT, TU, TA

{node_ref}, {direction}, {magnitude}
*END LOAD CASE"""

def add_freq_range(fmin, fmax, intervals=100):
"""
Return a multiline string of frequency range given by <fmin> and <fmax> at a specific interval.

:param intervals: Number of intervals for frequency range. Default is 100.
:param fmin: Minimum frequency
:param fmax: Maximum frequency
:return:
"""
def add_freq_range(fmin: float, fmax: float, intervals: int = 100):
"""Return a multiline string of frequency range given by <fmin> and <fmax> at a specific interval."""
freq_list = np.linspace(fmin, fmax, intervals)
freq_str = ""
for eig in freq_list:
Expand Down
Loading

0 comments on commit 18481d6

Please sign in to comment.