morphopt 3.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.
Potentially problematic release.
This version of morphopt might be problematic. Click here for more details.
- morphopt/__init__.py +32 -0
- morphopt/opt_runner.py +130 -0
- morphopt/optcore/baseobject.py +48 -0
- morphopt/optcore/controller.py +404 -0
- morphopt/optcore/history.py +440 -0
- morphopt/optcore/modelparams/__init__.py +4 -0
- morphopt/optcore/modelparams/base_params.py +97 -0
- morphopt/optcore/modelparams/feamodel/__init__.py +0 -0
- morphopt/optcore/modelparams/feamodel/feainterface/__init__.py +10 -0
- morphopt/optcore/modelparams/feamodel/feainterface/basefeainterface.py +50 -0
- morphopt/optcore/modelparams/feamodel/feainterface/bodyforceinterface.py +71 -0
- morphopt/optcore/modelparams/feamodel/feainterface/boundaryconditioninterface.py +59 -0
- morphopt/optcore/modelparams/feamodel/feainterface/contactinterface.py +113 -0
- morphopt/optcore/modelparams/feamodel/feainterface/coupleinterface.py +31 -0
- morphopt/optcore/modelparams/feamodel/feainterface/pointinterface.py +79 -0
- morphopt/optcore/modelparams/feamodel/feainterface/pressureinterface.py +68 -0
- morphopt/optcore/modelparams/feamodel/feainterface/referencepointinterface.py +29 -0
- morphopt/optcore/modelparams/feamodel/feainterface/springinterface.py +104 -0
- morphopt/optcore/modelparams/feamodel/feaparams.py +264 -0
- morphopt/optcore/modelparams/geometry/__init__.py +3 -0
- morphopt/optcore/modelparams/geometry/geometryinterfaces/__init__.py +1 -0
- morphopt/optcore/modelparams/geometry/geometryinterfaces/basesurfaceinterface.py +763 -0
- morphopt/optcore/modelparams/geometry/geometryinterfaces/bspsurfaceinterface.py +723 -0
- morphopt/optcore/modelparams/geometry/geometryinterfaces/cpgeosurfaceinterface.py +374 -0
- morphopt/optcore/modelparams/geometry/geometryparams.py +715 -0
- morphopt/optcore/modelparams/materials/__init__.py +1 -0
- morphopt/optcore/modelparams/materials/materialparams.py +110 -0
- morphopt/optcore/modelparams/params.py +88 -0
- morphopt/optcore/objfunc.py +248 -0
- morphopt/optcore/solver.py +159 -0
- morphopt/optcore/updaters/__init__.py +2 -0
- morphopt/optcore/updaters/base_updater.py +110 -0
- morphopt/optcore/updaters/geometry/__init__.py +2 -0
- morphopt/optcore/updaters/geometry/objectivefuncs/__init__.py +5 -0
- morphopt/optcore/updaters/geometry/objectivefuncs/basefuncs.py +117 -0
- morphopt/optcore/updaters/geometry/objectivefuncs/boundarys.py +91 -0
- morphopt/optcore/updaters/geometry/objectivefuncs/distancesurface.py +123 -0
- morphopt/optcore/updaters/geometry/objectivefuncs/shapederivative.py +179 -0
- morphopt/optcore/updaters/geometry/objectivefuncs/surfacefairness.py +40 -0
- morphopt/optcore/updaters/geometry/update_geometry.py +347 -0
- morphopt/optcore/updaters/optimizer.py +177 -0
- morphopt/optcore/updaters/updaters.py +82 -0
- morphopt/optcore/utils/plot_history_surface.py +239 -0
- morphopt/taskoptmization.py +75 -0
- morphopt/taskui.py +458 -0
- morphopt-3.1.1.dist-info/METADATA +242 -0
- morphopt-3.1.1.dist-info/RECORD +49 -0
- morphopt-3.1.1.dist-info/WHEEL +5 -0
- morphopt-3.1.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
import csv
|
|
2
|
+
import numpy as np
|
|
3
|
+
import torch
|
|
4
|
+
from .baseobject import BaseObject
|
|
5
|
+
|
|
6
|
+
class History(BaseObject):
|
|
7
|
+
"""
|
|
8
|
+
A class to record the information of the optimization process.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
def __init__(self):
|
|
12
|
+
self.data: dict[str, np.ndarray] = {}
|
|
13
|
+
self.iteration: int = 0
|
|
14
|
+
|
|
15
|
+
def initialize(self) -> None:
|
|
16
|
+
self.iteration = 0
|
|
17
|
+
self.data = {}
|
|
18
|
+
|
|
19
|
+
def append(self, name: str, value: any) -> None:
|
|
20
|
+
"""
|
|
21
|
+
Append a value to the history data.
|
|
22
|
+
"""
|
|
23
|
+
if value is None:
|
|
24
|
+
return
|
|
25
|
+
|
|
26
|
+
# Helper to safely convert torch tensors to numpy
|
|
27
|
+
def to_numpy(val):
|
|
28
|
+
if isinstance(val, torch.Tensor):
|
|
29
|
+
return val.detach().cpu().numpy()
|
|
30
|
+
return val
|
|
31
|
+
|
|
32
|
+
if isinstance(value, list):
|
|
33
|
+
value = [to_numpy(v) for v in value]
|
|
34
|
+
else:
|
|
35
|
+
value = to_numpy(value)
|
|
36
|
+
|
|
37
|
+
new_val = np.array(value)
|
|
38
|
+
if name not in self.data:
|
|
39
|
+
if new_val.ndim == 0:
|
|
40
|
+
# Scalar -> 1D array
|
|
41
|
+
self.data[name] = new_val.reshape(1)
|
|
42
|
+
else:
|
|
43
|
+
# Array -> (1, ...) array
|
|
44
|
+
self.data[name] = new_val.reshape(1, *new_val.shape)
|
|
45
|
+
else:
|
|
46
|
+
# stack on the first axis (0)
|
|
47
|
+
target_shape = (1, *new_val.shape)
|
|
48
|
+
self.data[name] = np.concatenate((self.data[name], new_val.reshape(target_shape)), axis=0)
|
|
49
|
+
|
|
50
|
+
if name == 'objective':
|
|
51
|
+
self.iteration = len(self.data[name])
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def history_objective(self):
|
|
55
|
+
return self.data.get('objective', np.array([]))
|
|
56
|
+
|
|
57
|
+
@history_objective.setter
|
|
58
|
+
def history_objective(self, val):
|
|
59
|
+
self.data['objective'] = np.array(val)
|
|
60
|
+
self.iteration = len(val)
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def history_time(self):
|
|
64
|
+
return self.data.get('time', np.array([]))
|
|
65
|
+
|
|
66
|
+
@history_time.setter
|
|
67
|
+
def history_time(self, val):
|
|
68
|
+
self.data['time'] = np.array(val)
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def history_num_elements(self):
|
|
72
|
+
return self.data.get('num_elements', np.array([]))
|
|
73
|
+
|
|
74
|
+
@history_num_elements.setter
|
|
75
|
+
def history_num_elements(self, val):
|
|
76
|
+
self.data['num_elements'] = np.array(val)
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def history_num_nodes(self):
|
|
80
|
+
return self.data.get('num_nodes', np.array([]))
|
|
81
|
+
|
|
82
|
+
@history_num_nodes.setter
|
|
83
|
+
def history_num_nodes(self, val):
|
|
84
|
+
self.data['num_nodes'] = np.array(val)
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def history_deformation(self):
|
|
88
|
+
return self.data.get('deformation', np.array([]))
|
|
89
|
+
|
|
90
|
+
@history_deformation.setter
|
|
91
|
+
def history_deformation(self, val):
|
|
92
|
+
self.data['deformation'] = np.array(val)
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
def history_metrics(self):
|
|
96
|
+
return self.data.get('metrics', np.array([]))
|
|
97
|
+
|
|
98
|
+
@history_metrics.setter
|
|
99
|
+
def history_metrics(self, val):
|
|
100
|
+
self.data['metrics'] = np.array(val)
|
|
101
|
+
|
|
102
|
+
def save(self, foldpath: str, *args, **kwargs) -> None:
|
|
103
|
+
"""
|
|
104
|
+
Save the history to a CSV file.
|
|
105
|
+
CSV format:
|
|
106
|
+
- Row 1: Column headers (iteration, objective, T0, T1, ..., U0-0, U0-1, ..., UdF0-0-0, UdF0-0-1, ...)
|
|
107
|
+
- Row 2+: Data records
|
|
108
|
+
"""
|
|
109
|
+
filepath = foldpath + '/history_record.csv'
|
|
110
|
+
|
|
111
|
+
# Determine keys and order
|
|
112
|
+
# Fixed order for known keys, then others
|
|
113
|
+
standard_keys = ['objective', 'metrics', 'time', 'num_elements', 'num_nodes', 'deformation']
|
|
114
|
+
other_keys = [k for k in self.data.keys() if k not in standard_keys]
|
|
115
|
+
all_keys = standard_keys + sorted(other_keys)
|
|
116
|
+
|
|
117
|
+
# Filter keys that actually exist
|
|
118
|
+
target_keys = [k for k in all_keys if k in self.data and len(self.data[k]) > 0]
|
|
119
|
+
|
|
120
|
+
# Prepare headers
|
|
121
|
+
headers = ['iteration']
|
|
122
|
+
key_dims = {} # Store dimensions for each key to handle flattening
|
|
123
|
+
|
|
124
|
+
for k in target_keys:
|
|
125
|
+
arr = self.data[k]
|
|
126
|
+
# check shape of the content (excluding iteration dim)
|
|
127
|
+
item_shape = arr.shape[1:]
|
|
128
|
+
|
|
129
|
+
if len(item_shape) == 0:
|
|
130
|
+
# Scalar per iteration
|
|
131
|
+
headers.append(k)
|
|
132
|
+
key_dims[k] = 0
|
|
133
|
+
elif len(item_shape) == 1:
|
|
134
|
+
# 1D array per iteration
|
|
135
|
+
for i in range(item_shape[0]):
|
|
136
|
+
if k == 'time':
|
|
137
|
+
headers.append(f'T{i}')
|
|
138
|
+
else:
|
|
139
|
+
headers.append(f'{k}_{i}')
|
|
140
|
+
key_dims[k] = 1
|
|
141
|
+
else:
|
|
142
|
+
# Multi-dim array per iteration - flatten
|
|
143
|
+
flat_size = np.prod(item_shape)
|
|
144
|
+
if k == 'deformation':
|
|
145
|
+
if len(item_shape) == 2:
|
|
146
|
+
for r in range(item_shape[0]):
|
|
147
|
+
for c in range(item_shape[1]):
|
|
148
|
+
headers.append(f'U{r}-{c}')
|
|
149
|
+
else:
|
|
150
|
+
for i in range(flat_size):
|
|
151
|
+
headers.append(f'{k}_{i}')
|
|
152
|
+
else:
|
|
153
|
+
for i in range(flat_size):
|
|
154
|
+
headers.append(f'{k}_{i}')
|
|
155
|
+
key_dims[k] = item_shape
|
|
156
|
+
|
|
157
|
+
with open(filepath, 'w', newline='', encoding='utf-8') as csvfile:
|
|
158
|
+
writer = csv.writer(csvfile)
|
|
159
|
+
writer.writerow(headers)
|
|
160
|
+
|
|
161
|
+
# Write rows
|
|
162
|
+
max_len = 0
|
|
163
|
+
for k in target_keys:
|
|
164
|
+
max_len = max(max_len, len(self.data[k]))
|
|
165
|
+
|
|
166
|
+
for i in range(max_len):
|
|
167
|
+
row = [i+1]
|
|
168
|
+
for k in target_keys:
|
|
169
|
+
arr = self.data[k]
|
|
170
|
+
if i < len(arr):
|
|
171
|
+
val = arr[i]
|
|
172
|
+
if key_dims[k] == 0:
|
|
173
|
+
row.append(val)
|
|
174
|
+
else:
|
|
175
|
+
# Flatten
|
|
176
|
+
row.extend(val.flatten().tolist())
|
|
177
|
+
else:
|
|
178
|
+
# Pad with empty
|
|
179
|
+
shape = key_dims[k]
|
|
180
|
+
if shape == 0:
|
|
181
|
+
size = 1
|
|
182
|
+
elif isinstance(shape, int):
|
|
183
|
+
size = shape
|
|
184
|
+
else:
|
|
185
|
+
size = np.prod(shape)
|
|
186
|
+
row.extend([''] * size)
|
|
187
|
+
|
|
188
|
+
# Formatting
|
|
189
|
+
formatted_row = []
|
|
190
|
+
for idx, val in enumerate(row):
|
|
191
|
+
if idx == 0:
|
|
192
|
+
formatted_row.append(val)
|
|
193
|
+
elif isinstance(val, (float, np.floating)):
|
|
194
|
+
formatted_row.append(f"{val:.4e}")
|
|
195
|
+
else:
|
|
196
|
+
formatted_row.append(val)
|
|
197
|
+
writer.writerow(formatted_row)
|
|
198
|
+
|
|
199
|
+
def load(self, foldpath: str, iteration: int = None) -> None:
|
|
200
|
+
"""
|
|
201
|
+
Load the history from a CSV file.
|
|
202
|
+
"""
|
|
203
|
+
filepath = foldpath + '/history_record.csv'
|
|
204
|
+
self.initialize()
|
|
205
|
+
|
|
206
|
+
try:
|
|
207
|
+
with open(filepath, 'r', encoding='utf-8') as csvfile:
|
|
208
|
+
reader = csv.reader(csvfile)
|
|
209
|
+
try:
|
|
210
|
+
headers = next(reader)
|
|
211
|
+
except StopIteration:
|
|
212
|
+
return
|
|
213
|
+
|
|
214
|
+
col_map = []
|
|
215
|
+
|
|
216
|
+
for h in headers:
|
|
217
|
+
if h == 'iteration':
|
|
218
|
+
col_map.append(None)
|
|
219
|
+
elif h == 'objective':
|
|
220
|
+
col_map.append(('objective', None))
|
|
221
|
+
elif h == 'num_elements':
|
|
222
|
+
col_map.append(('num_elements', None))
|
|
223
|
+
elif h == 'num_nodes':
|
|
224
|
+
col_map.append(('num_nodes', None))
|
|
225
|
+
elif h.startswith('T') and h[1:].isdigit():
|
|
226
|
+
col_map.append(('time', int(h[1:])))
|
|
227
|
+
elif h.startswith('U') and '-' in h:
|
|
228
|
+
col_map.append(('deformation', h))
|
|
229
|
+
else:
|
|
230
|
+
if '_' in h:
|
|
231
|
+
parts = h.rsplit('_', 1)
|
|
232
|
+
if parts[1].isdigit():
|
|
233
|
+
col_map.append((parts[0], int(parts[1])))
|
|
234
|
+
else:
|
|
235
|
+
col_map.append((h, None))
|
|
236
|
+
else:
|
|
237
|
+
col_map.append((h, None))
|
|
238
|
+
|
|
239
|
+
rows = list(reader)
|
|
240
|
+
except FileNotFoundError:
|
|
241
|
+
return
|
|
242
|
+
|
|
243
|
+
if not rows:
|
|
244
|
+
return
|
|
245
|
+
|
|
246
|
+
# Temporary lists
|
|
247
|
+
data_lists = {
|
|
248
|
+
'objective': [],
|
|
249
|
+
'time': [],
|
|
250
|
+
'num_elements': [],
|
|
251
|
+
'num_nodes': [],
|
|
252
|
+
'deformation': [],
|
|
253
|
+
'metrics': []
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
# Helper to classify scalars vs arrays
|
|
257
|
+
# Fixed classifications
|
|
258
|
+
scalar_keys = {'objective', 'num_elements', 'num_nodes'}
|
|
259
|
+
|
|
260
|
+
# Analyze col_map to classify other keys
|
|
261
|
+
# Collect subs for each key to determine if it's scalar or array
|
|
262
|
+
key_subs = {}
|
|
263
|
+
for item in col_map:
|
|
264
|
+
if item is None: continue
|
|
265
|
+
k, sub = item
|
|
266
|
+
if k not in key_subs: key_subs[k] = set()
|
|
267
|
+
key_subs[k].add(sub)
|
|
268
|
+
|
|
269
|
+
# --- Correction Logic Start ---
|
|
270
|
+
# If a key seems to be an array (has subs), but does not have index 0,
|
|
271
|
+
# it is likely a scalar variable that happens to end with "_N" (e.g. "param_1").
|
|
272
|
+
keys_to_revert = set()
|
|
273
|
+
for k, subs in key_subs.items():
|
|
274
|
+
if k in ['time', 'deformation']: continue # special arrays
|
|
275
|
+
if k in scalar_keys: continue
|
|
276
|
+
|
|
277
|
+
# If it contains None, it's already treated as scalar/mixed.
|
|
278
|
+
if None in subs: continue
|
|
279
|
+
|
|
280
|
+
# Check if 0 is present in the subs
|
|
281
|
+
# Note: subs contains integers for generic arrays
|
|
282
|
+
if 0 not in subs:
|
|
283
|
+
keys_to_revert.add(k)
|
|
284
|
+
|
|
285
|
+
if keys_to_revert:
|
|
286
|
+
new_col_map = []
|
|
287
|
+
for i, item in enumerate(col_map):
|
|
288
|
+
if item is None:
|
|
289
|
+
new_col_map.append(None)
|
|
290
|
+
else:
|
|
291
|
+
k, sub = item
|
|
292
|
+
if k in keys_to_revert:
|
|
293
|
+
# Revert to using the original header as the key
|
|
294
|
+
full_header_name = headers[i]
|
|
295
|
+
new_col_map.append((full_header_name, None))
|
|
296
|
+
else:
|
|
297
|
+
new_col_map.append(item)
|
|
298
|
+
col_map = new_col_map
|
|
299
|
+
|
|
300
|
+
# Re-generate key_subs after correction
|
|
301
|
+
key_subs = {}
|
|
302
|
+
for item in col_map:
|
|
303
|
+
if item is None: continue
|
|
304
|
+
k, sub = item
|
|
305
|
+
if k not in key_subs: key_subs[k] = set()
|
|
306
|
+
key_subs[k].add(sub)
|
|
307
|
+
# --- Correction Logic End ---
|
|
308
|
+
|
|
309
|
+
for k in key_subs:
|
|
310
|
+
if k not in data_lists:
|
|
311
|
+
data_lists[k] = []
|
|
312
|
+
|
|
313
|
+
# Determine which are scalars (all subs are None)
|
|
314
|
+
# Exception: deformation is always array (sub like U0-0)
|
|
315
|
+
# Exception: time is always array (sub 0, 1...)
|
|
316
|
+
|
|
317
|
+
for k, subs in key_subs.items():
|
|
318
|
+
if k in scalar_keys: continue
|
|
319
|
+
if k == 'deformation' or k == 'time': continue
|
|
320
|
+
|
|
321
|
+
if len(subs) == 1 and list(subs)[0] is None:
|
|
322
|
+
scalar_keys.add(k)
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
# Determine shape for deformation
|
|
326
|
+
def_indices = [h for h in headers if h.startswith('U') and '-' in h]
|
|
327
|
+
max_r, max_c = 0, 0
|
|
328
|
+
if def_indices:
|
|
329
|
+
for h in def_indices:
|
|
330
|
+
parts = h.replace('U','').split('-')
|
|
331
|
+
if len(parts) == 2:
|
|
332
|
+
try:
|
|
333
|
+
r, c = int(parts[0]), int(parts[1])
|
|
334
|
+
max_r = max(max_r, r)
|
|
335
|
+
max_c = max(max_c, c)
|
|
336
|
+
except: pass
|
|
337
|
+
def_shape = (max_r+1, max_c+1)
|
|
338
|
+
else:
|
|
339
|
+
def_shape = None
|
|
340
|
+
|
|
341
|
+
# Determine shape for generic arrays (to handle missing rows)
|
|
342
|
+
array_widths = {}
|
|
343
|
+
for k, subs in key_subs.items():
|
|
344
|
+
if k in scalar_keys or k == 'deformation': continue
|
|
345
|
+
max_idx = -1
|
|
346
|
+
for s in subs:
|
|
347
|
+
try:
|
|
348
|
+
idx = int(s)
|
|
349
|
+
max_idx = max(max_idx, idx)
|
|
350
|
+
except (ValueError, TypeError):
|
|
351
|
+
pass
|
|
352
|
+
if max_idx >= 0:
|
|
353
|
+
array_widths[k] = max_idx + 1
|
|
354
|
+
else:
|
|
355
|
+
array_widths[k] = 0
|
|
356
|
+
|
|
357
|
+
for row in rows:
|
|
358
|
+
if not row: continue
|
|
359
|
+
|
|
360
|
+
# Init row data container
|
|
361
|
+
row_data = {}
|
|
362
|
+
for k in data_lists:
|
|
363
|
+
if k in scalar_keys:
|
|
364
|
+
row_data[k] = None
|
|
365
|
+
else:
|
|
366
|
+
row_data[k] = {}
|
|
367
|
+
|
|
368
|
+
for i, val_str in enumerate(row):
|
|
369
|
+
if i >= len(col_map) or col_map[i] is None: continue
|
|
370
|
+
if val_str == '': continue
|
|
371
|
+
|
|
372
|
+
try: val = float(val_str)
|
|
373
|
+
except: val = val_str # Fallback strings
|
|
374
|
+
|
|
375
|
+
key, sub = col_map[i]
|
|
376
|
+
|
|
377
|
+
if key in scalar_keys:
|
|
378
|
+
if key in ['num_elements', 'num_nodes']:
|
|
379
|
+
try: row_data[key] = int(val)
|
|
380
|
+
except: row_data[key] = val
|
|
381
|
+
else:
|
|
382
|
+
row_data[key] = val
|
|
383
|
+
else:
|
|
384
|
+
# Array type
|
|
385
|
+
if key == 'deformation':
|
|
386
|
+
parts = sub.replace('U','').split('-')
|
|
387
|
+
r, c = int(parts[0]), int(parts[1])
|
|
388
|
+
row_data[key][(r,c)] = val
|
|
389
|
+
else:
|
|
390
|
+
idx = sub if sub is not None else 0
|
|
391
|
+
row_data[key][idx] = val
|
|
392
|
+
|
|
393
|
+
# Convert row_data to list elements
|
|
394
|
+
for k, v in row_data.items():
|
|
395
|
+
if k in scalar_keys:
|
|
396
|
+
if v is not None:
|
|
397
|
+
data_lists[k].append(v)
|
|
398
|
+
else:
|
|
399
|
+
# Append NaN for missing scalar to maintain alignment
|
|
400
|
+
data_lists[k].append(np.nan)
|
|
401
|
+
|
|
402
|
+
elif k == 'deformation':
|
|
403
|
+
if def_shape and v:
|
|
404
|
+
d_mat = np.zeros(def_shape)
|
|
405
|
+
for (r,c), val in v.items():
|
|
406
|
+
d_mat[r, c] = val
|
|
407
|
+
data_lists[k].append(d_mat)
|
|
408
|
+
elif def_shape:
|
|
409
|
+
# Append matrix of NaNs
|
|
410
|
+
data_lists[k].append(np.full(def_shape, np.nan))
|
|
411
|
+
elif v:
|
|
412
|
+
pass
|
|
413
|
+
else:
|
|
414
|
+
# Generic Array
|
|
415
|
+
if v:
|
|
416
|
+
max_idx = max(v.keys()) if all(isinstance(x, int) for x in v.keys()) else len(v)-1
|
|
417
|
+
vec = [v.get(x, 0.0) for x in range(max_idx+1)]
|
|
418
|
+
data_lists[k].append(vec)
|
|
419
|
+
elif k in array_widths and array_widths[k] > 0:
|
|
420
|
+
# Append list of NaNs
|
|
421
|
+
data_lists[k].append([np.nan] * array_widths[k])
|
|
422
|
+
else:
|
|
423
|
+
# If unknown width or not in headers, append empty?
|
|
424
|
+
# Or better, if it's a default key like 'time' but not in CSV,
|
|
425
|
+
# we can append empty list (which leads to 0-width array).
|
|
426
|
+
data_lists[k].append([])
|
|
427
|
+
|
|
428
|
+
# Convert to arrays
|
|
429
|
+
|
|
430
|
+
for k, v in data_lists.items():
|
|
431
|
+
if v:
|
|
432
|
+
self.data[k] = np.array(v)
|
|
433
|
+
|
|
434
|
+
# Iteration slice
|
|
435
|
+
if iteration is not None:
|
|
436
|
+
for k in self.data:
|
|
437
|
+
self.data[k] = self.data[k][:iteration]
|
|
438
|
+
|
|
439
|
+
if 'objective' in self.data:
|
|
440
|
+
self.iteration = len(self.data['objective'])
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
import torch
|
|
4
|
+
from ..baseobject import BaseObject
|
|
5
|
+
|
|
6
|
+
class BaseParams(BaseObject):
|
|
7
|
+
"""
|
|
8
|
+
Base class for all parameter classes.
|
|
9
|
+
"""
|
|
10
|
+
def __init__(self, **kwargs):
|
|
11
|
+
"""
|
|
12
|
+
Initialize the parameters with the given keyword arguments.
|
|
13
|
+
"""
|
|
14
|
+
self.__dict__.update(kwargs)
|
|
15
|
+
|
|
16
|
+
def __repr__(self):
|
|
17
|
+
"""
|
|
18
|
+
Return a string representation of the parameters.
|
|
19
|
+
"""
|
|
20
|
+
return f"{self.__class__.__name__}({self.__dict__})"
|
|
21
|
+
|
|
22
|
+
def __str__(self):
|
|
23
|
+
"""
|
|
24
|
+
Return a string representation of the parameters.
|
|
25
|
+
"""
|
|
26
|
+
return self.__repr__()
|
|
27
|
+
|
|
28
|
+
def reinitialize(self, iteration: int, *args, **kwargs) -> None:
|
|
29
|
+
"""
|
|
30
|
+
reInitialize the parameters.
|
|
31
|
+
|
|
32
|
+
This method should be implemented in subclasses to initialize specific parameters.
|
|
33
|
+
"""
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
def initialize(self, *args, **kwargs) -> None:
|
|
37
|
+
"""
|
|
38
|
+
Initialize the parameters.
|
|
39
|
+
|
|
40
|
+
This method should be implemented in subclasses to initialize specific parameters.
|
|
41
|
+
"""
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
def get_variables(self) -> torch.Tensor:
|
|
45
|
+
"""
|
|
46
|
+
Get the zeros of the parameters.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
torch.Tensor: The variables of the parameters.
|
|
50
|
+
"""
|
|
51
|
+
raise NotImplementedError("This method should be implemented in subclasses.")
|
|
52
|
+
|
|
53
|
+
def update_variables(self, x_change: torch.Tensor) -> None:
|
|
54
|
+
"""
|
|
55
|
+
Update the variables of the class.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
x_change (torch.Tensor): The change in variables.
|
|
59
|
+
"""
|
|
60
|
+
raise NotImplementedError("This method should be implemented in subclasses.")
|
|
61
|
+
|
|
62
|
+
def set_parameters(self, xlist: list[torch.Tensor]) -> None:
|
|
63
|
+
"""
|
|
64
|
+
Set the parameters of the class.
|
|
65
|
+
the new parameters are expected to be in a flattened format with clone and detach.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
xlist (list[torch.Tensor]): The new parameters for the class.
|
|
69
|
+
"""
|
|
70
|
+
raise NotImplementedError("This method should be implemented in subclasses.")
|
|
71
|
+
|
|
72
|
+
def get_parameters(self) -> list[torch.Tensor]:
|
|
73
|
+
"""
|
|
74
|
+
Get the parameters of the class.
|
|
75
|
+
the parameters are expected to be in a flattened format with clone and detach.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
list[torch.Tensor]: The parameters of the class.
|
|
79
|
+
"""
|
|
80
|
+
raise NotImplementedError("This method should be implemented in subclasses.")
|
|
81
|
+
|
|
82
|
+
def plot(self) -> None:
|
|
83
|
+
"""
|
|
84
|
+
Plot the parameters.
|
|
85
|
+
|
|
86
|
+
This method should be implemented in subclasses to plot specific parameters.
|
|
87
|
+
"""
|
|
88
|
+
raise NotImplementedError("This method should be implemented in subclasses.")
|
|
89
|
+
|
|
90
|
+
def _export_data(self, foldpath: str):
|
|
91
|
+
"""
|
|
92
|
+
Export the data of parameters to file(s).
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
foldpath (str): The path to export the data.
|
|
96
|
+
"""
|
|
97
|
+
pass
|
|
File without changes
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from .basefeainterface import BaseFEAInterface
|
|
2
|
+
from .pressureinterface import PressureInterface
|
|
3
|
+
from .contactinterface import ContactInterface, ContactSelfInterface
|
|
4
|
+
from .pointinterface import ConcentratedForceInterface, ConcentratedMomentInterface
|
|
5
|
+
from .springinterface import SpringToGroundInterface, SpringBetweenRPsInterface
|
|
6
|
+
from .coupleinterface import CoupleInterface
|
|
7
|
+
from .boundaryconditioninterface import BoundaryConditionInterface, BoundaryConditionRPInterface
|
|
8
|
+
from .referencepointinterface import ReferencePointInterface
|
|
9
|
+
|
|
10
|
+
from .bodyforceinterface import BodyforceInterface
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import torch
|
|
5
|
+
from torchfea import FEAController
|
|
6
|
+
|
|
7
|
+
class BaseFEAInterface:
|
|
8
|
+
"""
|
|
9
|
+
Base class for fea interfaces.
|
|
10
|
+
This class is not meant to be instantiated directly.
|
|
11
|
+
It provides a common interface for all fea interfaces.
|
|
12
|
+
|
|
13
|
+
all data cannot be cuda tensors
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self):
|
|
17
|
+
"""
|
|
18
|
+
Initialize the base interface and optional parameters.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
self._values: list[float] = np.zeros(self.num_values).tolist()
|
|
22
|
+
"""List of fea parameter values."""
|
|
23
|
+
|
|
24
|
+
self._name: str = ""
|
|
25
|
+
"""Name of the interface."""
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def num_values(self) -> int:
|
|
29
|
+
"""
|
|
30
|
+
Get the number of fea variables.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
int: The number of fea variables.
|
|
34
|
+
"""
|
|
35
|
+
raise NotImplementedError("This method should be implemented in subclasses.")
|
|
36
|
+
|
|
37
|
+
def modify_fea(self, fe: FEAController, name: str) -> None:
|
|
38
|
+
"""
|
|
39
|
+
create the object in FEA assembly
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
fe (FEAController): The FEA controller instance.
|
|
43
|
+
"""
|
|
44
|
+
raise NotImplementedError("This method should be implemented in subclasses.")
|
|
45
|
+
|
|
46
|
+
def apply_fea_value(self, fe: FEAController, name: str) -> None:
|
|
47
|
+
"""
|
|
48
|
+
Apply the values to the FEA object.
|
|
49
|
+
"""
|
|
50
|
+
pass
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
from torchfea import FEAController
|
|
2
|
+
import torch
|
|
3
|
+
from .basefeainterface import BaseFEAInterface
|
|
4
|
+
|
|
5
|
+
from torchfea.model.loads.body_force import BodyForce
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class BodyforceInterface(BaseFEAInterface):
|
|
9
|
+
"""
|
|
10
|
+
Body force (Gravity) load interface.
|
|
11
|
+
|
|
12
|
+
Values (list[float], length=3):
|
|
13
|
+
- [0] fx
|
|
14
|
+
- [1] fy
|
|
15
|
+
- [2] fz
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, element_name: str, instance_name: str = 'final_model'):
|
|
19
|
+
"""
|
|
20
|
+
Initialize the BodyforceInterface class.
|
|
21
|
+
"""
|
|
22
|
+
super().__init__()
|
|
23
|
+
self.instance_name = instance_name
|
|
24
|
+
self.element_name = element_name
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def force_density(self) -> list[float]:
|
|
28
|
+
"""
|
|
29
|
+
Get the force density values.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
list[float]: The force density values [fx, fy, fz].
|
|
33
|
+
"""
|
|
34
|
+
return self._values
|
|
35
|
+
|
|
36
|
+
@force_density.setter
|
|
37
|
+
def force_density(self, value: list[float]) -> None:
|
|
38
|
+
"""
|
|
39
|
+
Set the force density values.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
value (list[float]): The new force density values [fx, fy, fz].
|
|
43
|
+
"""
|
|
44
|
+
if len(value) != 3:
|
|
45
|
+
raise ValueError("Force density must be a list of 3 floats.")
|
|
46
|
+
self._values = [float(v) for v in value]
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def num_values(self) -> int:
|
|
50
|
+
"""
|
|
51
|
+
Get the number of variables.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
int: The number of variables (3 for fx, fy, fz).
|
|
55
|
+
"""
|
|
56
|
+
return 3
|
|
57
|
+
|
|
58
|
+
def modify_fea(self, fe: FEAController, name: str) -> None:
|
|
59
|
+
body_force = BodyForce(instance_name=self.instance_name, element_name=self.element_name, force_density=self.force_density)
|
|
60
|
+
fe.assembly.add_load(body_force, name)
|
|
61
|
+
|
|
62
|
+
def apply_fea_value(self, fe: FEAController, name: str) -> None:
|
|
63
|
+
body_force: BodyForce = fe.assembly.get_load(name)
|
|
64
|
+
device = body_force.force_density.device
|
|
65
|
+
body_force.force_density = torch.tensor(self.force_density, dtype=torch.float64, device=device)
|
|
66
|
+
|
|
67
|
+
# Update cached values if initialized
|
|
68
|
+
if hasattr(body_force, '_element'):
|
|
69
|
+
body_force._pdU_values = torch.einsum('i, ge, gea->eai', body_force.force_density, body_force._element.gaussian_weight, body_force._element.shape_function_d0_gaussian).flatten()
|
|
70
|
+
|
|
71
|
+
|