loadpath 0.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- loadpath/README.md +294 -0
- loadpath/SKILL.md +584 -0
- loadpath/__init__.py +17 -0
- loadpath/__main__.py +1202 -0
- loadpath/am.py +316 -0
- loadpath/amgfast.py +326 -0
- loadpath/assembly.py +985 -0
- loadpath/benchmarks/__init__.py +0 -0
- loadpath/benchmarks/beams.py +266 -0
- loadpath/benchmarks/cases/ge-bracket.json +380 -0
- loadpath/benchmarks/evaluate.py +172 -0
- loadpath/benchmarks/mechanisms.py +416 -0
- loadpath/benchmarks/problems.py +140 -0
- loadpath/benchmarks/reference/bridge-3d.top3d.npz +0 -0
- loadpath/benchmarks/reference/cantilever-3d.top3d.npz +0 -0
- loadpath/benchmarks/reference/lbracket-2d.top88-ft2.npz +0 -0
- loadpath/benchmarks/reference/mbb-2d.top88-ft1.npz +0 -0
- loadpath/benchmarks/reference/mbb-2d.top88-ft2.npz +0 -0
- loadpath/benchmarks/reference.py +138 -0
- loadpath/benchmarks/run.py +301 -0
- loadpath/brep.py +1470 -0
- loadpath/bucklingopt.py +300 -0
- loadpath/buildsim.py +1490 -0
- loadpath/cadimport.py +904 -0
- loadpath/calibration.py +885 -0
- loadpath/cavities.py +211 -0
- loadpath/checks/__init__.py +0 -0
- loadpath/checks/dfm.py +354 -0
- loadpath/checks/exchanger.py +7 -0
- loadpath/checks/fea.py +212 -0
- loadpath/checks/fit.py +475 -0
- loadpath/checks/networks.py +260 -0
- loadpath/checks/opening.py +67 -0
- loadpath/checks/sdf_kit.py +7 -0
- loadpath/conjugate.py +267 -0
- loadpath/constrained.py +329 -0
- loadpath/contact.py +691 -0
- loadpath/coopt.py +342 -0
- loadpath/cores.py +163 -0
- loadpath/crease.py +1138 -0
- loadpath/critic.py +212 -0
- loadpath/crosscheck.py +1107 -0
- loadpath/cutcell.py +510 -0
- loadpath/data/lattice_buckling.json +439 -0
- loadpath/data/lattice_tensors.json +27449 -0
- loadpath/data/published_experiments.json +71 -0
- loadpath/decimate.py +177 -0
- loadpath/delivery.py +190 -0
- loadpath/directions.py +262 -0
- loadpath/domain.py +630 -0
- loadpath/drains.py +615 -0
- loadpath/durability.py +413 -0
- loadpath/editor.html +3135 -0
- loadpath/editor.py +610 -0
- loadpath/escape.py +354 -0
- loadpath/evals/__init__.py +0 -0
- loadpath/evals/baselines/coarse.json +1543 -0
- loadpath/evals/baselines/full.json +1497 -0
- loadpath/evals/brief_kinds/__init__.py +1 -0
- loadpath/evals/brief_kinds/heatx.py +387 -0
- loadpath/evals/brief_kinds/lattice.py +571 -0
- loadpath/evals/brief_kinds/multimat.py +593 -0
- loadpath/evals/briefs/assets/bracket.step +1201 -0
- loadpath/evals/briefs/assets/desk_arm.urdf +22 -0
- loadpath/evals/briefs/assets/elbow-gearbox.stl +0 -0
- loadpath/evals/briefs/assets/motor-body.stl +0 -0
- loadpath/evals/briefs/assets/sketch.png +0 -0
- loadpath/evals/briefs/base-adapter-fdm.json +492 -0
- loadpath/evals/briefs/bell-crank-lpbf.json +180 -0
- loadpath/evals/briefs/belt-tensioner-fdm.json +151 -0
- loadpath/evals/briefs/camera-boom-mjf.json +339 -0
- loadpath/evals/briefs/camera-mount-pla.json +203 -0
- loadpath/evals/briefs/coolant-isolator-petg.json +100 -0
- loadpath/evals/briefs/elbow-bearing-bracket-sls.json +300 -0
- loadpath/evals/briefs/elbow-gearbox-forearm-mjf.json +558 -0
- loadpath/evals/briefs/elbow-link-fatigue-fdm.json +251 -0
- loadpath/evals/briefs/elbow-stop-pad-tpu.json +39 -0
- loadpath/evals/briefs/forearm-fdm.json +146 -0
- loadpath/evals/briefs/forearm-servo-link-fdm.json +327 -0
- loadpath/evals/briefs/grip-pad-finger-mm.json +354 -0
- loadpath/evals/briefs/gripper-bracket-creep-fdm.json +340 -0
- loadpath/evals/briefs/gripper-finger-fdm.json +155 -0
- loadpath/evals/briefs/gripper-jaw-fatigue-mjf.json +328 -0
- loadpath/evals/briefs/gripper-palm-fdm.json +715 -0
- loadpath/evals/briefs/hot-stepper-mount-asa-fdm.json +471 -0
- loadpath/evals/briefs/hpu-oil-cooler-lpbf.json +121 -0
- loadpath/evals/briefs/isolated-camera-mount-mm.json +675 -0
- loadpath/evals/briefs/kart-charge-cooler-lpbf.json +93 -0
- loadpath/evals/briefs/lighten-step-bracket.json +220 -0
- loadpath/evals/briefs/nema17-mount-stl.json +429 -0
- loadpath/evals/briefs/pillow-block-sls.json +220 -0
- loadpath/evals/briefs/polyjet-fingertip-mm.json +305 -0
- loadpath/evals/briefs/rod-hook-pla.json +154 -0
- loadpath/evals/briefs/sensor-arm-resin.json +150 -0
- loadpath/evals/briefs/shelf-bracket-pla.json +158 -0
- loadpath/evals/briefs/shoulder-riser-mjf.json +487 -0
- loadpath/evals/briefs/sketch-clevis.json +229 -0
- loadpath/evals/briefs/spring-bracket-pacf.json +294 -0
- loadpath/evals/briefs/tool-flange-ti64.json +280 -0
- loadpath/evals/briefs/tray-arm-lattice-fdm.json +260 -0
- loadpath/evals/briefs/turret-fdm.json +281 -0
- loadpath/evals/briefs/upper-arm-sls.json +179 -0
- loadpath/evals/briefs/urdf-forearm.json +233 -0
- loadpath/evals/briefs/wrist-link-mjf.json +278 -0
- loadpath/evals/briefs/wrist-yoke-mjf.json +245 -0
- loadpath/evals/briefs.py +393 -0
- loadpath/evals/cases/arm-link-cast.json +157 -0
- loadpath/evals/cases/arm-link-driven.json +171 -0
- loadpath/evals/cases/arm-link-sandwich.json +315 -0
- loadpath/evals/cases/arm-link.json +111 -0
- loadpath/evals/cases/bell-crank.json +116 -0
- loadpath/evals/cases/grip-turret.json +479 -0
- loadpath/evals/cases/grip-upper-arm.json +253 -0
- loadpath/evals/cases/grip-wrist-link-mjf.json +929 -0
- loadpath/evals/cases/grip-wrist-yoke.json +831 -0
- loadpath/evals/cases/lpbf-intercooler-core.json +59 -0
- loadpath/evals/cases/mbb-beam.json +61 -0
- loadpath/evals/cases/motor-body.stl +0 -0
- loadpath/evals/cases/motor-mount.json +265 -0
- loadpath/evals/cases/printed-hook.json +77 -0
- loadpath/evals/cases/wall-bracket-fasteners.json +125 -0
- loadpath/evals/cases/wall-bracket.json +126 -0
- loadpath/evals/corpus.py +179 -0
- loadpath/evals/pareto.py +90 -0
- loadpath/evals/run.py +860 -0
- loadpath/examples/arm-link.json +25 -0
- loadpath/examples/bumpered-bracket-compression.json +225 -0
- loadpath/examples/bumpered-bracket-lattice.json +204 -0
- loadpath/examples/bumpered-bracket.json +34 -0
- loadpath/examples/gripper-finger-mm.json +185 -0
- loadpath/examples/heat-exchanger-counterflow.json +21 -0
- loadpath/examples/heat-exchanger-crossflow-tuned.json +48 -0
- loadpath/examples/heat-exchanger-crossflow.json +20 -0
- loadpath/examples/pedal-lattice.json +23 -0
- loadpath/examples/pressure-plenum.json +12 -0
- loadpath/examples/shelf-bracket.json +27 -0
- loadpath/exchanger.py +831 -0
- loadpath/fasteners.py +199 -0
- loadpath/fem.py +668 -0
- loadpath/femops.py +70 -0
- loadpath/fieldjit.py +981 -0
- loadpath/fieldops.py +119 -0
- loadpath/fields.py +3697 -0
- loadpath/fieldtorch.py +464 -0
- loadpath/flow.py +984 -0
- loadpath/flowtopo.py +300 -0
- loadpath/gpu.py +521 -0
- loadpath/heatx.py +1067 -0
- loadpath/heatx_thermal.py +432 -0
- loadpath/homogenized.py +886 -0
- loadpath/ibrep.py +552 -0
- loadpath/impact.py +693 -0
- loadpath/infill.py +376 -0
- loadpath/interface_library.py +340 -0
- loadpath/interlock.py +186 -0
- loadpath/isolator.py +149 -0
- loadpath/knn.py +204 -0
- loadpath/lattice.py +1065 -0
- loadpath/lattice3mf.py +760 -0
- loadpath/lattice_buckling.py +245 -0
- loadpath/lattice_grade.py +325 -0
- loadpath/lattice_orient.py +595 -0
- loadpath/lattice_print.py +159 -0
- loadpath/lattice_tensors.py +558 -0
- loadpath/latticestep.py +1210 -0
- loadpath/manufacture.py +446 -0
- loadpath/material_library.py +489 -0
- loadpath/materials.py +144 -0
- loadpath/mcp_server.py +786 -0
- loadpath/mechanism.py +218 -0
- loadpath/mechanism_nl.py +811 -0
- loadpath/meshops.py +452 -0
- loadpath/mg.py +853 -0
- loadpath/mma.py +214 -0
- loadpath/modal.py +210 -0
- loadpath/multimat.py +1788 -0
- loadpath/narrowband.py +418 -0
- loadpath/nlkernel.py +241 -0
- loadpath/nonlinear.py +1095 -0
- loadpath/orient.py +334 -0
- loadpath/porous.py +994 -0
- loadpath/pressure.py +171 -0
- loadpath/preview.py +335 -0
- loadpath/printcheck.py +263 -0
- loadpath/printsetup.py +207 -0
- loadpath/processcheck.py +376 -0
- loadpath/quick.py +113 -0
- loadpath/recall.py +406 -0
- loadpath/reconstruct.py +797 -0
- loadpath/render.py +436 -0
- loadpath/resolved.py +189 -0
- loadpath/schema.py +1701 -0
- loadpath/sdf_kit.py +843 -0
- loadpath/selfx.py +292 -0
- loadpath/shapes.py +584 -0
- loadpath/sharp.py +717 -0
- loadpath/simp.py +1170 -0
- loadpath/sizing.py +312 -0
- loadpath/slicer.py +321 -0
- loadpath/spec.py +1072 -0
- loadpath/step.py +806 -0
- loadpath/struts.py +639 -0
- loadpath/study.py +1937 -0
- loadpath/tasks.py +822 -0
- loadpath/thermal.py +1691 -0
- loadpath/threemf.py +487 -0
- loadpath/tune.py +437 -0
- loadpath/validation.py +145 -0
- loadpath/verify.py +1215 -0
- loadpath/viewer.html +694 -0
- loadpath/viewer.py +446 -0
- loadpath/viscoelastic.py +106 -0
- loadpath/voxelprint.py +582 -0
- loadpath/voxels.py +402 -0
- loadpath-0.1.1.dist-info/METADATA +319 -0
- loadpath-0.1.1.dist-info/RECORD +220 -0
- loadpath-0.1.1.dist-info/WHEEL +5 -0
- loadpath-0.1.1.dist-info/entry_points.txt +2 -0
- loadpath-0.1.1.dist-info/licenses/LICENSE +21 -0
- loadpath-0.1.1.dist-info/top_level.txt +1 -0
loadpath/bucklingopt.py
ADDED
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Linear buckling as an optimization constraint: the smallest buckling load
|
|
3
|
+
factors of a density design and their sensitivities, for the constrained
|
|
4
|
+
method (constrained.py).
|
|
5
|
+
|
|
6
|
+
The check (verify._buckling) reads the finished part; the optimizer needs
|
|
7
|
+
the same number on the grey design, every iteration, with its gradient.
|
|
8
|
+
For a design x with stiffness K(x) and geometric stiffness Kg(x, u(x)) of
|
|
9
|
+
the linear stresses under the load case,
|
|
10
|
+
|
|
11
|
+
K phi = lambda (-Kg) phi <=> (-Kg) phi = mu K phi, mu = 1 / lambda,
|
|
12
|
+
|
|
13
|
+
and the largest mu are the smallest positive buckling loads. With phi
|
|
14
|
+
normalized to phi^T K phi = 1, the sensitivity of mu to cell e is
|
|
15
|
+
|
|
16
|
+
dmu/dx_e = -E_G'(x_e) phi_e^T kg_e phi_e - mu E'(x_e) phi_e^T ke phi_e
|
|
17
|
+
+ E'(x_e) w_e^T ke u_e, K w = a, a = d(phi^T Kg phi)/du,
|
|
18
|
+
|
|
19
|
+
the last term because Kg depends on the displacements, which depend on the
|
|
20
|
+
design (one more solve per mode, on the same factorization). The modes are
|
|
21
|
+
aggregated with a Kreisselmeier-Steinhauser max, which keeps the constraint
|
|
22
|
+
smooth where two modes cross.
|
|
23
|
+
|
|
24
|
+
Pseudo-modes. Grey cells are soft in K (E ~ x^3) but carry stress in Kg,
|
|
25
|
+
and a soft cell far from the load path can "buckle" at a tiny load (Neves
|
|
26
|
+
et al. 1995; Ferrari and Sigmund 2019). The model is built on the live
|
|
27
|
+
cells only (density above simp.ALIVE and joined to a support, as the
|
|
28
|
+
stiffness solve), and Kg's stresses take a harsher interpolation than K
|
|
29
|
+
below a density of `cutoff`, so a nearly empty cell neither stiffens nor
|
|
30
|
+
loads the mode.
|
|
31
|
+
|
|
32
|
+
Element: incompatible-mode hex8 (fem.hex8_element), the verify check's own,
|
|
33
|
+
so a member 2-3 cells thick reads its Euler load within a few percent.
|
|
34
|
+
The factorization is PARDISO (pypardiso, if installed) or SuperLU.
|
|
35
|
+
"""
|
|
36
|
+
from __future__ import annotations
|
|
37
|
+
|
|
38
|
+
import time
|
|
39
|
+
|
|
40
|
+
import numpy as np
|
|
41
|
+
import scipy.sparse as sp
|
|
42
|
+
from scipy import ndimage
|
|
43
|
+
from scipy.sparse.linalg import LinearOperator, eigsh, lobpcg
|
|
44
|
+
|
|
45
|
+
from .fem import HexMesh, Stiffness, _hex8_parts, hex8_element
|
|
46
|
+
|
|
47
|
+
__all__ = ["BucklingModel", "geometric_parts"]
|
|
48
|
+
|
|
49
|
+
_CONN26 = np.ones((3, 3, 3), bool)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _factor(k: sp.csr_matrix):
|
|
53
|
+
"""A solve with K for one right-hand side or a block of them: PARDISO
|
|
54
|
+
when available (symmetric positive definite, upper triangle; 10-30x
|
|
55
|
+
SuperLU on 3D hex meshes, and a block of eight costs about two single
|
|
56
|
+
solves), else SuperLU."""
|
|
57
|
+
try:
|
|
58
|
+
import pypardiso
|
|
59
|
+
except ImportError:
|
|
60
|
+
from .nonlinear import _splu
|
|
61
|
+
|
|
62
|
+
lu = _splu(k)
|
|
63
|
+
return lu.solve, None
|
|
64
|
+
a = sp.triu(k, format="csr")
|
|
65
|
+
a.sort_indices()
|
|
66
|
+
solver = pypardiso.PyPardisoSolver()
|
|
67
|
+
solver.set_matrix_type(2)
|
|
68
|
+
try:
|
|
69
|
+
solver.factorize(a)
|
|
70
|
+
except Exception: # noqa: BLE001 - a zero pivot (a hinge in the live cells): pivoting LU
|
|
71
|
+
solver.free_memory(everything=True)
|
|
72
|
+
a = k.tocsr()
|
|
73
|
+
a.sort_indices()
|
|
74
|
+
solver = pypardiso.PyPardisoSolver()
|
|
75
|
+
solver.set_matrix_type(11)
|
|
76
|
+
solver.factorize(a)
|
|
77
|
+
solver.set_phase(33)
|
|
78
|
+
|
|
79
|
+
def solve(b):
|
|
80
|
+
b = np.asarray(b, float)
|
|
81
|
+
# straight to the solve phase: pypardiso.solve() hashes the matrix
|
|
82
|
+
# every call to see whether it is factored already
|
|
83
|
+
return solver._call_pardiso(a, np.asfortranarray(b) if b.ndim == 2 else np.ascontiguousarray(b))
|
|
84
|
+
|
|
85
|
+
return solve, lambda: solver.free_memory(everything=True)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _gauss_im():
|
|
89
|
+
from .nonlinear import _GAUSS, _GAUSS_IM
|
|
90
|
+
|
|
91
|
+
return _GAUSS_IM if hex8_element() == "im" else _GAUSS
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def geometric_parts(ue, phis, h, nu):
|
|
95
|
+
"""Per element, for displacements ue (m, 24) and modes phis [(m, 24)]:
|
|
96
|
+
g (m,) = phi_e^T kg_e(u_e) phi_e and a (m, 24) = d g / d u_e, at unit
|
|
97
|
+
stress modulus. kg_e is nonlinear._geometric's element matrix (the
|
|
98
|
+
bubbles' linear amplitudes in both the stresses and the modes)."""
|
|
99
|
+
from .nonlinear import _lame
|
|
100
|
+
|
|
101
|
+
lam, mu = _lame(nu)
|
|
102
|
+
grads = _gauss_im()
|
|
103
|
+
r = _hex8_parts(nu, hex8_element())[1]
|
|
104
|
+
im = r is not None and len(grads[0]) == 11
|
|
105
|
+
t = np.vstack([np.eye(24), r]) if im else np.eye(24)
|
|
106
|
+
n = t.shape[0] // 3
|
|
107
|
+
m = len(ue)
|
|
108
|
+
w = h ** 3 / 8.0
|
|
109
|
+
ux = (ue @ t.T).reshape(m, n, 3)
|
|
110
|
+
out = []
|
|
111
|
+
for ph in phis:
|
|
112
|
+
px = (ph @ t.T).reshape(m, n, 3)
|
|
113
|
+
g = np.zeros(m)
|
|
114
|
+
da = np.zeros((m, n, 3))
|
|
115
|
+
for dn0 in grads:
|
|
116
|
+
dn = dn0 / h
|
|
117
|
+
gp = np.einsum("mak,ai->mki", px, dn) # d phi_k / d x_i
|
|
118
|
+
hh = np.einsum("mki,mkj->mij", gp, gp)
|
|
119
|
+
s = 2 * mu * hh
|
|
120
|
+
s[:, [0, 1, 2], [0, 1, 2]] += lam * np.trace(hh, axis1=1, axis2=2)[:, None]
|
|
121
|
+
gu = np.einsum("mai,aj->mij", ux, dn) # d u_i / d x_j
|
|
122
|
+
g += w * np.einsum("mij,mij->m", gu, s)
|
|
123
|
+
da += w * np.einsum("aj,mij->mai", dn, s)
|
|
124
|
+
out.append((g, da.reshape(m, 3 * n) @ t))
|
|
125
|
+
return out
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class BucklingModel:
|
|
129
|
+
"""Buckling load factors and their gradients for density designs on
|
|
130
|
+
`domain`'s grid. `n_modes` modes per load case are computed and
|
|
131
|
+
aggregated (KS, `rho`)."""
|
|
132
|
+
|
|
133
|
+
def __init__(self, domain, nu: float, n_modes: int = 4, rho: float = 40.0, cutoff: float = 0.3,
|
|
134
|
+
alive: float = 1e-3):
|
|
135
|
+
self.domain = domain
|
|
136
|
+
self.nu = nu
|
|
137
|
+
self.n_modes = n_modes
|
|
138
|
+
self.rho = rho
|
|
139
|
+
self.cutoff = cutoff
|
|
140
|
+
self.alive = alive
|
|
141
|
+
mesh = domain.mesh
|
|
142
|
+
self.support_touch = domain.support_nodes[mesh.enode].any(axis=1)
|
|
143
|
+
self.anchor = self.support_touch | domain.load_nodes[mesh.enode].any(axis=1) | domain.preserve
|
|
144
|
+
self._v0 = {}
|
|
145
|
+
self._mu0 = {}
|
|
146
|
+
self.last = {}
|
|
147
|
+
|
|
148
|
+
def interp(self, xe, p):
|
|
149
|
+
"""(E, dE/dx, E_G, dE_G/dx) per cell at unit modulus: E is SIMP's
|
|
150
|
+
(E_MIN floor), E_G the stress interpolation of Kg, x^p above the
|
|
151
|
+
cutoff and faded to zero below it as (x / cutoff)^3 more."""
|
|
152
|
+
from .simp import E_MIN
|
|
153
|
+
|
|
154
|
+
e = E_MIN + xe ** p * (1 - E_MIN)
|
|
155
|
+
de = p * xe ** (p - 1) * (1 - E_MIN)
|
|
156
|
+
c = self.cutoff
|
|
157
|
+
if c > 0:
|
|
158
|
+
r = np.clip(xe / c, 0.0, 1.0)
|
|
159
|
+
f = np.where(xe < c, r ** 3, 1.0)
|
|
160
|
+
df = np.where(xe < c, 3 * r ** 2 / c, 0.0)
|
|
161
|
+
else:
|
|
162
|
+
f, df = np.ones_like(xe), np.zeros_like(xe)
|
|
163
|
+
eg = xe ** p * f
|
|
164
|
+
deg = p * xe ** (p - 1) * f + xe ** p * df
|
|
165
|
+
return e, de, eg, deg
|
|
166
|
+
|
|
167
|
+
def _modes(self, c, a, k, minv, nm, fd, n_full):
|
|
168
|
+
"""The nm largest mu of a phi = mu k phi, largest first. Warm
|
|
169
|
+
(LOBPCG from the last iteration's modes, preconditioned with the
|
|
170
|
+
factorization: a few block solves) when there are last modes,
|
|
171
|
+
else ARPACK in shift-invert-like mode 2 (tens of solves)."""
|
|
172
|
+
prev = self._v0.get(c)
|
|
173
|
+
if prev is not None:
|
|
174
|
+
p = nm + 2
|
|
175
|
+
x0 = prev[fd]
|
|
176
|
+
if x0.shape[1] < p:
|
|
177
|
+
x0 = np.hstack([x0, np.random.default_rng(c).standard_normal((len(fd), p - x0.shape[1]))])
|
|
178
|
+
try:
|
|
179
|
+
with np.errstate(all="ignore"):
|
|
180
|
+
import warnings
|
|
181
|
+
|
|
182
|
+
with warnings.catch_warnings():
|
|
183
|
+
warnings.simplefilter("ignore")
|
|
184
|
+
# residuals to 0.3% of mu: the eigenvalues come out
|
|
185
|
+
# good to about the square of that
|
|
186
|
+
tol = 3e-3 * self._mu0.get(c, 1.0)
|
|
187
|
+
mu, vec, res = lobpcg(a, x0, B=k, M=minv, largest=True, tol=tol, maxiter=25,
|
|
188
|
+
retResidualNormsHistory=True)
|
|
189
|
+
order = np.argsort(-mu)
|
|
190
|
+
mu, vec = mu[order], vec[:, order]
|
|
191
|
+
# converged on the wanted modes (relative to mu_max)?
|
|
192
|
+
r = np.asarray(res[-1])[order][:nm] if len(res) else np.full(nm, np.inf)
|
|
193
|
+
if mu[0] > 0 and np.all(np.isfinite(mu)) and np.all(r < 1e-2 * abs(mu[0])):
|
|
194
|
+
self._mu0[c] = float(mu[0])
|
|
195
|
+
self._store(c, vec, fd, n_full)
|
|
196
|
+
self.last_method = "lobpcg"
|
|
197
|
+
return mu[:nm], vec[:, :nm]
|
|
198
|
+
except Exception: # noqa: BLE001 - fall back to ARPACK below
|
|
199
|
+
pass
|
|
200
|
+
try:
|
|
201
|
+
mu, vec = eigsh(a, k=nm, M=k, Minv=minv, which="LA", tol=1e-4, maxiter=2000)
|
|
202
|
+
except Exception: # noqa: BLE001 - ARPACK did not converge: once more, looser
|
|
203
|
+
mu, vec = eigsh(a, k=nm, M=k, Minv=minv, which="LA", tol=1e-3, maxiter=5000)
|
|
204
|
+
order = np.argsort(-mu)
|
|
205
|
+
mu, vec = mu[order], vec[:, order]
|
|
206
|
+
self._store(c, vec, fd, n_full)
|
|
207
|
+
self._mu0[c] = float(mu[0])
|
|
208
|
+
self.last_method = "arpack"
|
|
209
|
+
return mu, vec
|
|
210
|
+
|
|
211
|
+
def _store(self, c, vec, fd, n_full):
|
|
212
|
+
full = np.zeros((n_full, vec.shape[1]))
|
|
213
|
+
full[fd] = vec
|
|
214
|
+
self._v0[c] = full
|
|
215
|
+
|
|
216
|
+
def _live(self, xe):
|
|
217
|
+
mesh = self.domain.mesh
|
|
218
|
+
alive = (xe > self.alive) | self.anchor
|
|
219
|
+
g = np.zeros(self.domain.dims, bool)
|
|
220
|
+
ijk = tuple(mesh.elem_ijk.T)
|
|
221
|
+
g[ijk] = alive
|
|
222
|
+
lbl, _ = ndimage.label(g, structure=_CONN26)
|
|
223
|
+
el = lbl[ijk]
|
|
224
|
+
keep_l = np.unique(el[alive & self.support_touch])
|
|
225
|
+
return alive & np.isin(el, keep_l[keep_l > 0])
|
|
226
|
+
|
|
227
|
+
def evaluate(self, xe: np.ndarray, p: float, cases=None):
|
|
228
|
+
"""For each load case (default all): (mu_ks, dmu_ks/dxe (n,), mus),
|
|
229
|
+
where 1 / mu_ks is the aggregated smallest buckling load factor
|
|
230
|
+
(inf when the case does not compress anything)."""
|
|
231
|
+
t0 = time.time()
|
|
232
|
+
d = self.domain
|
|
233
|
+
mesh = d.mesh
|
|
234
|
+
cases = list(range(len(d.loads))) if cases is None else list(cases)
|
|
235
|
+
keep = self._live(xe)
|
|
236
|
+
el = np.flatnonzero(keep)
|
|
237
|
+
sub = HexMesh(mesh.elem_ijk[el], mesh.pitch, mesh.origin)
|
|
238
|
+
fn = mesh.node_id(sub.node_ijk)
|
|
239
|
+
fd = (3 * fn[:, None] + np.arange(3)).ravel()
|
|
240
|
+
fixed = d.fixed[fd]
|
|
241
|
+
stiff = Stiffness(sub, self.nu, fixed)
|
|
242
|
+
e, de, eg, deg = self.interp(xe[el], p)
|
|
243
|
+
k = stiff.assemble(e)
|
|
244
|
+
if d.springs is not None:
|
|
245
|
+
free = sp.diags((~fixed).astype(float))
|
|
246
|
+
k = (k + free @ d.springs[fd][:, fd] @ free).tocsr()
|
|
247
|
+
solve, free_memory = _factor(k)
|
|
248
|
+
try:
|
|
249
|
+
ndof = sub.n_dof
|
|
250
|
+
minv = LinearOperator((ndof, ndof), matvec=solve, matmat=solve, dtype=float)
|
|
251
|
+
ke = stiff.ke
|
|
252
|
+
out = []
|
|
253
|
+
for c in cases:
|
|
254
|
+
f = d.loads[c][fd].copy()
|
|
255
|
+
f[fixed] = 0.0
|
|
256
|
+
if not np.any(f):
|
|
257
|
+
out.append((0.0, np.zeros(mesh.n_elem), np.zeros(0)))
|
|
258
|
+
continue
|
|
259
|
+
u = solve(f)
|
|
260
|
+
from .nonlinear import _geometric
|
|
261
|
+
|
|
262
|
+
kg = _geometric(stiff, u, eg, self.nu)
|
|
263
|
+
nm = min(self.n_modes, ndof - 2)
|
|
264
|
+
mu, vec = self._modes(c, -kg, k, minv, nm, fd, mesh.n_dof)
|
|
265
|
+
if mu[0] <= 0:
|
|
266
|
+
out.append((0.0, np.zeros(mesh.n_elem), mu))
|
|
267
|
+
continue
|
|
268
|
+
# KS over the modes, on mu / mu_max
|
|
269
|
+
mu = np.asarray(mu[:nm], float)
|
|
270
|
+
mmax = float(mu[0])
|
|
271
|
+
wts = np.exp(self.rho * (mu / mmax - 1.0))
|
|
272
|
+
ks = mmax * (1.0 + np.log(wts.sum()) / self.rho)
|
|
273
|
+
wts = wts / wts.sum()
|
|
274
|
+
use = [i for i in range(nm) if wts[i] >= 1e-6]
|
|
275
|
+
# modes normalized phi^T K phi = 1
|
|
276
|
+
phis = []
|
|
277
|
+
for i in use:
|
|
278
|
+
ph = vec[:, i]
|
|
279
|
+
phis.append(ph / np.sqrt(max(float(ph @ (k @ ph)), 1e-300)))
|
|
280
|
+
ue = u[sub.edof]
|
|
281
|
+
parts = geometric_parts(ue, [ph[sub.edof] for ph in phis], mesh.pitch, self.nu)
|
|
282
|
+
# the adjoints of every counted mode in one block solve
|
|
283
|
+
arhs = np.zeros((ndof, len(use)))
|
|
284
|
+
for j, (_, a_e) in enumerate(parts):
|
|
285
|
+
arhs[:, j] = np.bincount(sub.edof.ravel(), weights=(eg[:, None] * a_e).ravel(), minlength=ndof)
|
|
286
|
+
arhs[fixed] = 0.0
|
|
287
|
+
wsol = np.asarray(solve(arhs)).reshape(ndof, len(use))
|
|
288
|
+
grad = np.zeros(mesh.n_elem)
|
|
289
|
+
for j, i in enumerate(use):
|
|
290
|
+
pe = phis[j][sub.edof]
|
|
291
|
+
g_e = parts[j][0]
|
|
292
|
+
kphi = np.einsum("mi,ij,mj->m", pe, ke, pe)
|
|
293
|
+
term = np.einsum("mi,ij,mj->m", wsol[:, j][sub.edof], ke, ue)
|
|
294
|
+
grad[el] += wts[i] * (-deg * g_e - mu[i] * de * kphi + de * term)
|
|
295
|
+
out.append((ks, grad, mu))
|
|
296
|
+
finally:
|
|
297
|
+
if free_memory is not None:
|
|
298
|
+
free_memory()
|
|
299
|
+
self.last = {"cells": int(len(el)), "seconds": time.time() - t0}
|
|
300
|
+
return out
|