pyMOTO 1.5.0__py3-none-any.whl → 1.5.1b1__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.
- pymoto/__init__.py +1 -1
- pymoto/common/domain.py +3 -3
- pymoto/common/dyadcarrier.py +1 -1
- pymoto/common/mma.py +11 -3
- pymoto/core_objects.py +143 -66
- pymoto/modules/assembly.py +17 -13
- pymoto/modules/io.py +19 -11
- pymoto/modules/linalg.py +14 -14
- pymoto/routines.py +2 -2
- pymoto/solvers/iterative.py +20 -16
- pymoto/solvers/solvers.py +44 -13
- pymoto/solvers/sparse.py +10 -0
- {pyMOTO-1.5.0.dist-info → pymoto-1.5.1b1.dist-info}/METADATA +26 -18
- pymoto-1.5.1b1.dist-info/RECORD +28 -0
- {pyMOTO-1.5.0.dist-info → pymoto-1.5.1b1.dist-info}/WHEEL +1 -1
- pyMOTO-1.5.0.dist-info/RECORD +0 -29
- pyMOTO-1.5.0.dist-info/zip-safe +0 -1
- {pyMOTO-1.5.0.dist-info → pymoto-1.5.1b1.dist-info/licenses}/LICENSE +0 -0
- {pyMOTO-1.5.0.dist-info → pymoto-1.5.1b1.dist-info}/top_level.txt +0 -0
pymoto/__init__.py
CHANGED
pymoto/common/domain.py
CHANGED
@@ -91,6 +91,7 @@ class DomainDefinition:
|
|
91
91
|
if self.nelz is None:
|
92
92
|
self.nelz = 0
|
93
93
|
self.unitx, self.unity, self.unitz = unitx, unity, unitz
|
94
|
+
self.origin = np.array([0.0, 0.0, 0.0])
|
94
95
|
|
95
96
|
self.dim = 1 if (self.nelz == 0 and self.nely == 0) else (2 if self.nelz == 0 else 3)
|
96
97
|
|
@@ -287,7 +288,7 @@ class DomainDefinition:
|
|
287
288
|
patch.set_path(self.get_path(x + u, y + v))
|
288
289
|
|
289
290
|
# flake8: noqa: C901
|
290
|
-
def write_to_vti(self, vectors: dict, filename="out.vti", scale=1.0
|
291
|
+
def write_to_vti(self, vectors: dict, filename="out.vti", scale=1.0):
|
291
292
|
""" Write all given vectors to a Paraview (VTI) file
|
292
293
|
|
293
294
|
The size of the vectors should be a multiple of ``nel`` or ``nnodes``. Based on their size they are marked as
|
@@ -299,7 +300,6 @@ class DomainDefinition:
|
|
299
300
|
vectors: A dictionary of vectors to write. Keys are used as vector names.
|
300
301
|
filename (str): The file loction
|
301
302
|
scale: Uniform scaling of the gridpoints
|
302
|
-
origin: Origin of the domain
|
303
303
|
"""
|
304
304
|
ext = '.vti'
|
305
305
|
if ext not in os.path.splitext(filename)[-1].lower():
|
@@ -336,7 +336,7 @@ class DomainDefinition:
|
|
336
336
|
file.write(f"<ImageData WholeExtent=\"0 {self.nelx} 0 {self.nely} 0 {self.nelz}\"".encode())
|
337
337
|
|
338
338
|
# Origin of domain
|
339
|
-
file.write(f" Origin=\"{origin[0]*scale} {origin[1]*scale} {origin[2]*scale}\"".encode())
|
339
|
+
file.write(f" Origin=\"{self.origin[0]*scale} {self.origin[1]*scale} {self.origin[2]*scale}\"".encode())
|
340
340
|
|
341
341
|
# Spacing of points (dx, dy, dz)
|
342
342
|
dx, dy, dz = self.element_size[0:3]*scale
|
pymoto/common/dyadcarrier.py
CHANGED
@@ -393,7 +393,7 @@ class DyadCarrier(object):
|
|
393
393
|
exprvars = (rowvar, colvar) if mat is None else (rowvar, matvar, colvar)
|
394
394
|
expr = ','.join(exprvars) + '->' + batchvar
|
395
395
|
|
396
|
-
val = 0.0 if batchsize is None else np.zeros(batchsize)
|
396
|
+
val = 0.0 if batchsize is None else np.zeros(batchsize, dtype=np.result_type(mat, self.dtype))
|
397
397
|
for ui, vi in zip(self.u, self.v):
|
398
398
|
uarg = ui if rows is None else ui[rows]
|
399
399
|
varg = vi if cols is None else vi[cols]
|
pymoto/common/mma.py
CHANGED
@@ -305,8 +305,12 @@ class MMA:
|
|
305
305
|
|
306
306
|
# Setting up for constriants
|
307
307
|
self.m = len(self.responses) - 1
|
308
|
-
self.a = np.zeros(self.m)
|
309
|
-
|
308
|
+
self.a = kwargs.get("a", np.zeros(self.m))
|
309
|
+
if len(self.a) != self.m:
|
310
|
+
raise RuntimeError(f"Length of the a vector ({len(self.a)}) should be equal to # constraints ({self.m}).")
|
311
|
+
self.c = kwargs.get("c", np.full(self.m, self.cCoef, dtype=float))
|
312
|
+
if len(self.c) != self.m:
|
313
|
+
raise RuntimeError(f"Length of the c vector ({len(self.c)}) should be equal to # constraints ({self.m}).")
|
310
314
|
self.d = np.ones(self.m)
|
311
315
|
self.gold1 = np.zeros(self.m + 1)
|
312
316
|
self.gold2 = self.gold1.copy()
|
@@ -371,11 +375,15 @@ class MMA:
|
|
371
375
|
# Calculate response
|
372
376
|
self.funbl.response()
|
373
377
|
|
378
|
+
xval, _ = _concatenate_to_array([s.state for s in self.variables])
|
379
|
+
|
374
380
|
# Save response
|
375
381
|
f = ()
|
376
382
|
for s in self.responses:
|
377
383
|
if np.size(s.state) != 1:
|
378
384
|
raise TypeError("State of responses must be scalar.")
|
385
|
+
if np.iscomplexobj(s.state):
|
386
|
+
raise TypeError("Responses must be real-valued.")
|
379
387
|
f += (s.state, )
|
380
388
|
|
381
389
|
# Check function change convergence criterion
|
@@ -564,4 +572,4 @@ class MMA:
|
|
564
572
|
print(f" | Changes: {', '.join(change_msgs)}")
|
565
573
|
|
566
574
|
return xmma, change
|
567
|
-
|
575
|
+
|
pymoto/core_objects.py
CHANGED
@@ -5,6 +5,7 @@ import time
|
|
5
5
|
import copy
|
6
6
|
from typing import Union, List, Any
|
7
7
|
from abc import ABC, abstractmethod
|
8
|
+
from collections.abc import Callable
|
8
9
|
from .utils import _parse_to_list, _concatenate_to_array, _split_from_array
|
9
10
|
|
10
11
|
|
@@ -186,14 +187,14 @@ class SignalSlice(Signal):
|
|
186
187
|
The sliced values are referenced to their original source Signal, such that they can be used and updated in modules.
|
187
188
|
This means that updating the values in this SignalSlice changes the data in its source Signal.
|
188
189
|
"""
|
189
|
-
def __init__(self,
|
190
|
-
self.
|
190
|
+
def __init__(self, base, sl, tag=None):
|
191
|
+
self.base = base
|
191
192
|
self.slice = sl
|
192
193
|
self.keep_alloc = False # Allocation must be False because sensitivity cannot be assigned with [] operator
|
193
194
|
|
194
195
|
# for s in slice:
|
195
196
|
if tag is None:
|
196
|
-
self.tag = f"{self.
|
197
|
+
self.tag = f"{self.base.tag}[{fmt_slice(self.slice)}]"
|
197
198
|
else:
|
198
199
|
self.tag = tag
|
199
200
|
|
@@ -203,7 +204,7 @@ class SignalSlice(Signal):
|
|
203
204
|
@property
|
204
205
|
def state(self):
|
205
206
|
try:
|
206
|
-
return None if self.
|
207
|
+
return None if self.base.state is None else self.base.state[self.slice]
|
207
208
|
except Exception as e:
|
208
209
|
# Possibilities: Unslicable object (TypeError) or Wrong dimensions or out of range (IndexError)
|
209
210
|
raise type(e)(str(e) + "\n\t| Above error was raised in SignalSlice.state (getter). Signal details:" +
|
@@ -212,7 +213,7 @@ class SignalSlice(Signal):
|
|
212
213
|
@state.setter
|
213
214
|
def state(self, new_state):
|
214
215
|
try:
|
215
|
-
self.
|
216
|
+
self.base.state[self.slice] = new_state
|
216
217
|
except Exception as e:
|
217
218
|
# Possibilities: Unslicable object (TypeError) or Wrong dimensions or out of range (IndexError)
|
218
219
|
raise type(e)(str(e) + "\n\t| Above error was raised in SignalSlice.state (setter). Signal details:" +
|
@@ -221,7 +222,7 @@ class SignalSlice(Signal):
|
|
221
222
|
@property
|
222
223
|
def sensitivity(self):
|
223
224
|
try:
|
224
|
-
return None if self.
|
225
|
+
return None if self.base.sensitivity is None else self.base.sensitivity[self.slice]
|
225
226
|
except Exception as e:
|
226
227
|
# Possibilities: Unslicable object (TypeError) or Wrong dimensions or out of range (IndexError)
|
227
228
|
raise type(e)(str(e) + "\n\t| Above error was raised in SignalSlice.sensitivity (getter). Signal details:" +
|
@@ -230,26 +231,55 @@ class SignalSlice(Signal):
|
|
230
231
|
@sensitivity.setter
|
231
232
|
def sensitivity(self, new_sens):
|
232
233
|
try:
|
233
|
-
if self.
|
234
|
+
if self.base.sensitivity is None:
|
235
|
+
# Initialize sensitivity of base-signal
|
234
236
|
if new_sens is None:
|
235
237
|
return # Sensitivity doesn't need to be initialized when it is set to None
|
236
238
|
try:
|
237
|
-
self.
|
239
|
+
self.base.sensitivity = self.base.state * 0 # Make a new copy with 0 values
|
238
240
|
except TypeError:
|
239
|
-
if self.
|
241
|
+
if self.base.state is None:
|
240
242
|
raise TypeError("Could not initialize sensitivity because state is not set" + self._err_str())
|
241
243
|
else:
|
242
|
-
raise TypeError(f"Could not initialize sensitivity for type \'{type(self.
|
244
|
+
raise TypeError(f"Could not initialize sensitivity for type \'{type(self.base.state).__name__}\'")
|
243
245
|
|
244
246
|
if new_sens is None:
|
245
247
|
new_sens = 0 # reset() uses this
|
246
248
|
|
247
|
-
self.
|
249
|
+
self.base.sensitivity[self.slice] = new_sens
|
248
250
|
except Exception as e:
|
249
251
|
# Possibilities: Unslicable object (TypeError) or Wrong dimensions or out of range (IndexError)
|
250
252
|
raise type(e)(str(e) + "\n\t| Above error was raised in SignalSlice.state (setter). Signal details:" +
|
251
253
|
self._err_str()).with_traceback(sys.exc_info()[2])
|
252
254
|
|
255
|
+
def add_sensitivity(self, ds: Any):
|
256
|
+
""" Add a new term to internal sensitivity """
|
257
|
+
try:
|
258
|
+
if ds is None:
|
259
|
+
return
|
260
|
+
if self.base.sensitivity is None:
|
261
|
+
self.base.sensitivity = self.base.state * 0
|
262
|
+
# self.sensitivity = copy.deepcopy(ds)
|
263
|
+
|
264
|
+
if hasattr(self.sensitivity, "add_sensitivity"):
|
265
|
+
# Allow user to implement a custom add_sensitivity function instead of __iadd__
|
266
|
+
self.sensitivity.add_sensitivity(ds)
|
267
|
+
else:
|
268
|
+
self.sensitivity += ds
|
269
|
+
return self
|
270
|
+
except TypeError:
|
271
|
+
if isinstance(ds, type(self.sensitivity)):
|
272
|
+
raise TypeError(
|
273
|
+
f"Cannot add to the sensitivity with type '{type(self.sensitivity).__name__}'" + self._err_str())
|
274
|
+
else:
|
275
|
+
raise TypeError(
|
276
|
+
f"Adding wrong type '{type(ds).__name__}' to the sensitivity '{type(self.sensitivity).__name__}'" + self._err_str())
|
277
|
+
except ValueError:
|
278
|
+
sens_shape = self.sensitivity.shape if hasattr(self.sensitivity, 'shape') else ()
|
279
|
+
ds_shape = ds.shape if hasattr(ds, 'shape') else ()
|
280
|
+
raise ValueError(
|
281
|
+
f"Cannot add argument of shape {ds_shape} to the sensitivity of shape {sens_shape}" + self._err_str()) from None
|
282
|
+
|
253
283
|
def reset(self, keep_alloc: bool = None):
|
254
284
|
""" Reset the sensitivities to zero or None
|
255
285
|
This must be called to clear internal memory of subsequent sensitivity calculations.
|
@@ -296,30 +326,54 @@ def _is_valid_module(mod: Any):
|
|
296
326
|
return False
|
297
327
|
|
298
328
|
|
299
|
-
|
329
|
+
# Type definition for bound method
|
330
|
+
class BoundMethod:
|
331
|
+
__self__: object
|
332
|
+
|
333
|
+
|
334
|
+
BoundMethodT = Union[Callable, BoundMethod]
|
335
|
+
|
336
|
+
|
337
|
+
def _check_function_signature(fn: BoundMethodT, signals: list = None) -> (int, int):
|
300
338
|
""" Checks the function signature against given signal list
|
301
|
-
|
302
|
-
|
339
|
+
|
340
|
+
- Only positional-only or positional-or-keyword arguments are allowed
|
341
|
+
(https://peps.python.org/pep-0362/#parameter-object)
|
342
|
+
- If `signals` is provided, the number of them is compared with the allowed min/max number of arguments
|
343
|
+
|
344
|
+
Args:
|
345
|
+
fn: response_ or sensitivity_ function of a Module
|
346
|
+
signals (optional): The signals involved, which are checked with the function signature
|
347
|
+
|
348
|
+
Returns:
|
349
|
+
(min_args, max_args): Minimum and maximum number of accepted arguments to the function;
|
350
|
+
`None` denotes an unlimited number of maximum arguments (caused by `*args`)
|
303
351
|
"""
|
304
352
|
min_args, max_args = 0, 0
|
305
353
|
callstr = f"{type(fn.__self__).__name__}.{fn.__name__}{inspect.signature(fn)}"
|
354
|
+
# Loop over all the parameters defined for the function
|
306
355
|
for s, p in inspect.signature(fn).parameters.items():
|
307
356
|
if p.kind == inspect.Parameter.POSITIONAL_ONLY or p.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD:
|
308
|
-
if p.default
|
309
|
-
|
310
|
-
|
311
|
-
if max_args
|
357
|
+
if p.default == inspect.Parameter.empty:
|
358
|
+
# No default argument is provided, a value MUST be provided
|
359
|
+
min_args += 1
|
360
|
+
if max_args is not None:
|
312
361
|
max_args += 1
|
313
362
|
elif p.kind == inspect.Parameter.VAR_POSITIONAL:
|
314
|
-
|
363
|
+
# If *args is passed, the maximum number of arguments is unknown. Does not affect minimum number
|
364
|
+
max_args = None
|
315
365
|
elif p.kind == inspect.Parameter.KEYWORD_ONLY:
|
316
|
-
raise SyntaxError(f"{callstr} may not contain keyword arguments \"{p}\"")
|
366
|
+
raise SyntaxError(f"{callstr} may not contain keyword-only arguments \"{p}\"")
|
317
367
|
elif p.kind == inspect.Parameter.VAR_KEYWORD:
|
318
|
-
raise SyntaxError(f"{callstr} may not contain \"{p}\"")
|
319
|
-
|
320
|
-
|
321
|
-
if
|
322
|
-
|
368
|
+
raise SyntaxError(f"{callstr} may not contain dict of keyword-only arguments \"{p}\"")
|
369
|
+
|
370
|
+
# Check with signal list
|
371
|
+
if signals is not None:
|
372
|
+
if len(signals) < min_args:
|
373
|
+
raise TypeError(f"Not enough arguments ({len(signals)}) for {callstr}; expected {min_args}")
|
374
|
+
if max_args is not None and len(signals) > max_args:
|
375
|
+
raise TypeError(f"Too many arguments ({len(signals)}) for {callstr}; expected {max_args}")
|
376
|
+
return min_args, max_args
|
323
377
|
|
324
378
|
|
325
379
|
class RegisteredClass(object):
|
@@ -408,7 +462,7 @@ class Module(ABC, RegisteredClass):
|
|
408
462
|
>> Module([inputs])
|
409
463
|
|
410
464
|
Using keywords:
|
411
|
-
>> Module(sig_in=[inputs], sig_out=[outputs]
|
465
|
+
>> Module(sig_in=[inputs], sig_out=[outputs])
|
412
466
|
"""
|
413
467
|
|
414
468
|
def _err_str(self, module_signature: bool = True, init: bool = True, fn=None):
|
@@ -493,8 +547,12 @@ class Module(ABC, RegisteredClass):
|
|
493
547
|
raise type(e)(str(e) + "\n\t| Above error was raised when calling response(). Module details:" +
|
494
548
|
self._err_str(fn=self._response)).with_traceback(sys.exc_info()[2])
|
495
549
|
|
496
|
-
def __call__(self):
|
497
|
-
|
550
|
+
def __call__(self, *args):
|
551
|
+
if len(args) == 0 and len(self.sig_in) > 0:
|
552
|
+
inp = [s.state for s in self.sig_in]
|
553
|
+
return self._response(*inp)
|
554
|
+
else:
|
555
|
+
return self._response(*args)
|
498
556
|
|
499
557
|
def sensitivity(self):
|
500
558
|
""" Calculate sensitivities using backpropagation
|
@@ -556,67 +614,74 @@ class Module(ABC, RegisteredClass):
|
|
556
614
|
class Network(Module):
|
557
615
|
""" Binds multiple Modules together as one Module
|
558
616
|
|
617
|
+
Initialize a network with a number of modules that should be executed consecutively
|
559
618
|
>> Network(module1, module2, ...)
|
560
619
|
|
561
620
|
>> Network([module1, module2, ...])
|
562
621
|
|
563
622
|
>> Network((module1, module2, ...))
|
564
623
|
|
624
|
+
Modules can also be constructed using a dictionary based on strings
|
565
625
|
>> Network([ {type="module1", sig_in=[sig1, sig2], sig_out=[sig3]},
|
566
626
|
{type="module2", sig_in=[sig3], sig_out=[sig4]} ])
|
567
627
|
|
628
|
+
Appending modules to a network will output the signals automatically
|
629
|
+
>> fn = Network()
|
630
|
+
>> s_out = fn.append(module1)
|
631
|
+
|
632
|
+
Args:
|
633
|
+
print_timing: Print timing of each module inside this Network
|
568
634
|
"""
|
569
635
|
def __init__(self, *args, print_timing=False):
|
636
|
+
super().__init__()
|
570
637
|
self._init_loc = get_init_str()
|
571
|
-
|
572
|
-
#
|
573
|
-
self.mods = _parse_to_list(*args)
|
574
|
-
|
575
|
-
# Check if the blocks are initialized, else create them
|
576
|
-
for i, b in enumerate(self.mods):
|
577
|
-
if isinstance(b, dict):
|
578
|
-
exclude_keys = ['type']
|
579
|
-
b_ex = {k: b[k] for k in set(list(b.keys())) - set(exclude_keys)}
|
580
|
-
self.mods[i] = Module.create(b['type'], **b_ex)
|
581
|
-
|
582
|
-
# Check validity of modules
|
583
|
-
for m in self.mods:
|
584
|
-
if not _is_valid_module(m):
|
585
|
-
raise TypeError(f"Argument is not a valid Module, type=\'{type(mod).__name__}\'.")
|
586
|
-
|
587
|
-
# Gather all the input and output signals of the internal blocks
|
588
|
-
all_in = set()
|
589
|
-
all_out = set()
|
590
|
-
[all_in.update(b.sig_in) for b in self.mods]
|
591
|
-
[all_out.update(b.sig_out) for b in self.mods]
|
592
|
-
in_unique = all_in - all_out
|
593
|
-
|
594
|
-
# Initialize the parent module, with correct inputs and outputs
|
595
|
-
super().__init__(list(in_unique), list(all_out))
|
596
|
-
|
638
|
+
self.mods = [] # Empty module list
|
639
|
+
self.append(*args) # Append to module list
|
597
640
|
self.print_timing = print_timing
|
598
641
|
|
599
|
-
def timefn(self, fn,
|
642
|
+
def timefn(self, fn, name=None):
|
600
643
|
start_t = time.time()
|
601
644
|
fn()
|
602
645
|
duration = time.time() - start_t
|
603
|
-
if
|
604
|
-
|
646
|
+
if name is None:
|
647
|
+
name = f"{fn}"
|
648
|
+
if isinstance(self.print_timing, bool):
|
649
|
+
tmin = 0.0
|
650
|
+
else:
|
651
|
+
tmin = self.print_timing
|
652
|
+
if duration > tmin:
|
653
|
+
print(f"{name} took {time.time() - start_t} s")
|
605
654
|
|
606
655
|
def response(self):
|
607
|
-
if self.print_timing:
|
608
|
-
|
656
|
+
if self.print_timing is not False:
|
657
|
+
start_t = time.time()
|
658
|
+
[self.timefn(m.response, name=f"-- Response of \"{type(m).__name__}\"") for m in self.mods]
|
659
|
+
duration = time.time() - start_t
|
660
|
+
if isinstance(self.print_timing, bool):
|
661
|
+
tmin = 0.0
|
662
|
+
else:
|
663
|
+
tmin = self.print_timing
|
664
|
+
if duration > tmin:
|
665
|
+
print(f"-- TOTAL Response took {time.time() - start_t} s")
|
609
666
|
else:
|
610
|
-
[
|
667
|
+
[m.response() for m in self.mods]
|
611
668
|
|
612
669
|
def sensitivity(self):
|
613
|
-
if self.print_timing:
|
614
|
-
|
670
|
+
if self.print_timing is not False:
|
671
|
+
start_t = time.time()
|
672
|
+
[self.timefn(m.sensitivity, name=f"-- Sensitivity of \"{type(m).__name__}\"") for m in reversed(self.mods)]
|
673
|
+
duration = time.time() - start_t
|
674
|
+
if isinstance(self.print_timing, bool):
|
675
|
+
tmin = 0.0
|
676
|
+
else:
|
677
|
+
tmin = self.print_timing
|
678
|
+
if duration > tmin:
|
679
|
+
print(f"-- TOTAL Sensitivity took {time.time() - start_t} s")
|
615
680
|
else:
|
616
|
-
[
|
681
|
+
[m.sensitivity() for m in reversed(self.mods)]
|
617
682
|
|
618
683
|
def reset(self):
|
619
|
-
[
|
684
|
+
[m.reset() for m in reversed(self.mods)]
|
620
685
|
|
621
686
|
def _response(self, *args):
|
622
687
|
pass # Unused
|
@@ -636,13 +701,25 @@ class Network(Module):
|
|
636
701
|
def __iter__(self):
|
637
702
|
return iter(self.mods)
|
638
703
|
|
704
|
+
def __call__(self, *args):
|
705
|
+
return self.append(*args)
|
706
|
+
|
639
707
|
def append(self, *newmods):
|
640
708
|
modlist = _parse_to_list(*newmods)
|
709
|
+
if len(modlist) == 0:
|
710
|
+
return
|
641
711
|
|
642
712
|
# Check if the blocks are initialized, else create them
|
713
|
+
for i, m in enumerate(modlist):
|
714
|
+
if isinstance(m, dict):
|
715
|
+
exclude_keys = ['type']
|
716
|
+
b_ex = {k: m[k] for k in set(list(m.keys())) - set(exclude_keys)}
|
717
|
+
modlist[i] = Module.create(m['type'], **b_ex)
|
718
|
+
|
719
|
+
# Check validity of modules
|
643
720
|
for i, m in enumerate(modlist):
|
644
721
|
if not _is_valid_module(m):
|
645
|
-
raise TypeError(f"Argument #{i} is not a valid module, type=\'{type(
|
722
|
+
raise TypeError(f"Argument #{i} is not a valid module, type=\'{type(m).__name__}\'.")
|
646
723
|
|
647
724
|
# Obtain the internal blocks
|
648
725
|
self.mods.extend(modlist)
|
@@ -657,4 +734,4 @@ class Network(Module):
|
|
657
734
|
self.sig_in = _parse_to_list(in_unique)
|
658
735
|
self.sig_out = _parse_to_list(all_out)
|
659
736
|
|
660
|
-
return modlist[-1].sig_out[0] if len(modlist[-1].sig_out) == 1 else modlist[-1].sig_out
|
737
|
+
return modlist[-1].sig_out[0] if len(modlist[-1].sig_out) == 1 else modlist[-1].sig_out
|
pymoto/modules/assembly.py
CHANGED
@@ -57,20 +57,21 @@ class AssembleGeneral(Module):
|
|
57
57
|
self.bc = bc
|
58
58
|
self.bcdiagval = np.max(element_matrix) if bcdiagval is None else bcdiagval
|
59
59
|
if bc is not None:
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
self.
|
64
|
-
self.cols = np.concatenate((self.cols[self.bcselect], self.bc))
|
60
|
+
bc_inds = np.bitwise_or(np.isin(self.rows, self.bc), np.isin(self.cols, self.bc))
|
61
|
+
self.bcselect = np.argwhere(np.bitwise_not(bc_inds)).flatten()
|
62
|
+
self.bcrows = np.concatenate((self.rows[self.bcselect], self.bc))
|
63
|
+
self.bccols = np.concatenate((self.cols[self.bcselect], self.bc))
|
65
64
|
else:
|
66
65
|
self.bcselect = None
|
66
|
+
self.bcrows = self.rows
|
67
|
+
self.bccols = self.cols
|
67
68
|
|
68
69
|
self.add_constant = add_constant
|
69
70
|
|
70
71
|
def _response(self, xscale: np.ndarray):
|
71
72
|
nel = self.dofconn.shape[0]
|
72
73
|
assert xscale.size == nel, f"Input vector wrong size ({xscale.size}), must be of size #nel ({nel})"
|
73
|
-
scaled_el = (
|
74
|
+
scaled_el = (self.elmat.flatten() * xscale[..., np.newaxis]).flatten()
|
74
75
|
|
75
76
|
# Set boundary conditions
|
76
77
|
if self.bc is not None:
|
@@ -80,7 +81,7 @@ class AssembleGeneral(Module):
|
|
80
81
|
mat_values = scaled_el
|
81
82
|
|
82
83
|
try:
|
83
|
-
mat = self.matrix_type((mat_values, (self.
|
84
|
+
mat = self.matrix_type((mat_values, (self.bcrows, self.bccols)), shape=(self.n, self.n))
|
84
85
|
except TypeError as e:
|
85
86
|
raise type(e)(str(e) + "\n\tInvalid matrix_type={}. Either scipy.sparse.cscmatrix or "
|
86
87
|
"scipy.sparse.csrmatrix are supported"
|
@@ -96,14 +97,16 @@ class AssembleGeneral(Module):
|
|
96
97
|
if self.bc is not None:
|
97
98
|
dgdmat[self.bc, :] = 0.0
|
98
99
|
dgdmat[:, self.bc] = 0.0
|
100
|
+
dx = np.zeros_like(self.sig_in[0].state)
|
99
101
|
if isinstance(dgdmat, np.ndarray):
|
100
|
-
dx = np.zeros_like(self.sig_in[0].state)
|
101
102
|
for i in range(len(dx)):
|
102
103
|
indu, indv = np.meshgrid(self.dofconn[i], self.dofconn[i], indexing='ij')
|
103
|
-
|
104
|
-
|
104
|
+
dxi = einsum("ij,ij->", self.elmat, dgdmat[indu, indv])
|
105
|
+
dx[i] = np.real(dxi) if np.isrealobj(dx) else dxi
|
105
106
|
elif isinstance(dgdmat, DyadCarrier):
|
106
|
-
|
107
|
+
dxi = dgdmat.contract(self.elmat, self.dofconn, self.dofconn)
|
108
|
+
dx[:] = np.real(dxi) if np.isrealobj(dx) else dxi
|
109
|
+
return dx
|
107
110
|
|
108
111
|
|
109
112
|
def get_B(dN_dx, voigt=True):
|
@@ -123,7 +126,7 @@ def get_B(dN_dx, voigt=True):
|
|
123
126
|
"""
|
124
127
|
n_dim, n_shapefn = dN_dx.shape
|
125
128
|
n_strains = int((n_dim * (n_dim+1))/2) # Triangular number: ndim=3 -> nstrains = 3+2+1
|
126
|
-
B = np.zeros((n_strains, n_shapefn*n_dim))
|
129
|
+
B = np.zeros((n_strains, n_shapefn*n_dim), dtype=dN_dx.dtype)
|
127
130
|
if n_dim == 1:
|
128
131
|
for i in range(n_shapefn):
|
129
132
|
B[i, 0] = dN_dx[i, 0]
|
@@ -222,7 +225,8 @@ class AssembleStiffness(AssembleGeneral):
|
|
222
225
|
ndof = nnode*domain.dim
|
223
226
|
|
224
227
|
# Element stiffness matrix
|
225
|
-
|
228
|
+
dtype = np.result_type(D, domain.element_size.dtype)
|
229
|
+
self.stiffness_element = np.zeros((ndof, ndof), dtype=dtype)
|
226
230
|
|
227
231
|
# Numerical integration
|
228
232
|
siz = domain.element_size
|
pymoto/modules/io.py
CHANGED
@@ -1,13 +1,9 @@
|
|
1
1
|
import os
|
2
|
-
import platform
|
3
2
|
import numbers
|
4
|
-
import sys
|
5
3
|
from pathlib import Path
|
6
4
|
import numpy as np
|
7
|
-
|
8
|
-
|
9
|
-
import matplotlib
|
10
|
-
matplotlib.use('TkAgg')
|
5
|
+
import matplotlib
|
6
|
+
matplotlib.use('TkAgg') # Change default backend -- TkAgg does not freeze during calculations
|
11
7
|
import matplotlib.pyplot as plt
|
12
8
|
|
13
9
|
from pymoto import Module
|
@@ -202,15 +198,16 @@ class PlotIter(FigModule):
|
|
202
198
|
show (bool): Show the figure on the screen
|
203
199
|
ylim: Provide y-axis limits for the plot
|
204
200
|
"""
|
205
|
-
def _prepare(self, ylim=None):
|
201
|
+
def _prepare(self, ylim=None, log_scale=False):
|
206
202
|
self.minlim = 1e+200
|
207
203
|
self.maxlim = -1e+200
|
208
204
|
self.ylim = ylim
|
205
|
+
self.log_scale = log_scale
|
209
206
|
|
210
207
|
def _response(self, *args):
|
211
208
|
if not hasattr(self, 'ax'):
|
212
209
|
self.ax = self.fig.add_subplot(111)
|
213
|
-
self.ax.set_yscale('linear')
|
210
|
+
self.ax.set_yscale('linear' if not self.log_scale else 'log')
|
214
211
|
self.ax.set_xlabel("Iteration")
|
215
212
|
|
216
213
|
if not hasattr(self, 'line'):
|
@@ -233,13 +230,24 @@ class PlotIter(FigModule):
|
|
233
230
|
self.minlim = min(self.minlim, np.min(xadd))
|
234
231
|
self.maxlim = max(self.maxlim, np.max(xadd))
|
235
232
|
|
236
|
-
dy = max((self.maxlim - self.minlim)*0.05, sys.float_info.min)
|
237
|
-
|
238
233
|
self.ax.set_xlim([-0.5, self.iter+0.5])
|
239
234
|
if self.ylim is not None:
|
240
235
|
self.ax.set_ylim(self.ylim)
|
241
236
|
elif np.isfinite(self.minlim) and np.isfinite(self.maxlim):
|
242
|
-
|
237
|
+
if self.log_scale:
|
238
|
+
dy = (np.log10(self.maxlim) - np.log10(self.minlim))*0.05
|
239
|
+
ll = 10**(np.log10(self.minlim) - dy)
|
240
|
+
ul = 10**(np.log10(self.maxlim) + dy)
|
241
|
+
else:
|
242
|
+
dy = (self.maxlim - self.minlim)*0.05
|
243
|
+
ll = self.minlim - dy
|
244
|
+
ul = self.maxlim + dy
|
245
|
+
|
246
|
+
if ll == ul:
|
247
|
+
dy = abs(np.nextafter(abs(ll), 1) - abs(ll))
|
248
|
+
ll = ll - 1e5*dy
|
249
|
+
ul = ul + 1e5*dy
|
250
|
+
self.ax.set_ylim([ll, ul])
|
243
251
|
|
244
252
|
self._update_fig()
|
245
253
|
|
pymoto/modules/linalg.py
CHANGED
@@ -268,7 +268,7 @@ class LinSolve(Module):
|
|
268
268
|
if not isinstance(self.solver, LDAWrapper) and self.use_lda_solver:
|
269
269
|
lda_kwargs = dict(hermitian=self.ishermitian, symmetric=self.issymmetric)
|
270
270
|
if hasattr(self.solver, 'tol'):
|
271
|
-
lda_kwargs['tol'] = self.solver.tol *
|
271
|
+
lda_kwargs['tol'] = self.solver.tol * 5
|
272
272
|
self.solver = LDAWrapper(self.solver, **lda_kwargs)
|
273
273
|
|
274
274
|
# Update solver with new matrix
|
@@ -371,9 +371,11 @@ class EigenSolve(Module):
|
|
371
371
|
Bqi = qi if B is None else B@qi
|
372
372
|
|
373
373
|
normval = np.sqrt(qi @ Bqi)
|
374
|
-
|
375
|
-
|
376
|
-
|
374
|
+
sgn = 1 if np.real(np.average(qi)) >= 0 else -1
|
375
|
+
sf = sgn / normval
|
376
|
+
assert np.isfinite(sf)
|
377
|
+
if sf == 0.0:
|
378
|
+
warnings.warn(f"Scaling factor of mode {i} is zero!")
|
377
379
|
qi *= sf
|
378
380
|
return W, Q
|
379
381
|
|
@@ -423,8 +425,9 @@ class EigenSolve(Module):
|
|
423
425
|
if self.is_hermitian:
|
424
426
|
return spsla.eigsh(A, M=B, k=self.nmodes, OPinv=AinvOp, sigma=self.sigma, mode=self.mode)
|
425
427
|
else:
|
426
|
-
|
427
|
-
|
428
|
+
if self.mode.lower() not in ['normal']:
|
429
|
+
raise NotImplementedError('Only `normal` mode can be selected for non-hermitian matrix')
|
430
|
+
return spsla.eigs(A, M=B, k=self.nmodes, OPinv=AinvOp, sigma=self.sigma)
|
428
431
|
|
429
432
|
def _dense_sens(self, A, B, dW, dQ):
|
430
433
|
""" Calculates all (eigenvector and eigenvalue) sensitivities for dense matrix """
|
@@ -465,17 +468,14 @@ class EigenSolve(Module):
|
|
465
468
|
qi = Q[:, i]
|
466
469
|
qmq = qi@qi if B is None else qi @ (B @ qi)
|
467
470
|
dA_u = (dwi/qmq) * qi
|
468
|
-
|
469
|
-
|
470
|
-
else:
|
471
|
-
dA += DyadCarrier(dA_u, qi)
|
471
|
+
dAi = DyadCarrier(dA_u, qi)
|
472
|
+
dA += np.real(dAi) if np.isrealobj(A) else dAi
|
472
473
|
|
473
474
|
if dB is not None:
|
474
475
|
dB_u = (wi*dwi/qmq) * qi
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
dB -= DyadCarrier(dB_u, qi)
|
476
|
+
dBi = DyadCarrier(dB_u, qi)
|
477
|
+
dB -= np.real(dBi) if np.isrealobj(B) else dBi
|
478
|
+
|
479
479
|
return dA, dB
|
480
480
|
|
481
481
|
def _sparse_eigvec_sens(self, A, B, dW, dQ):
|
pymoto/routines.py
CHANGED
@@ -10,10 +10,10 @@ from scipy.sparse import issparse
|
|
10
10
|
def _has_signal_overlap(sig1: List[Signal], sig2: List[Signal]):
|
11
11
|
for s1 in sig1:
|
12
12
|
while isinstance(s1, SignalSlice):
|
13
|
-
s1 = s1.
|
13
|
+
s1 = s1.base
|
14
14
|
for s2 in sig2:
|
15
15
|
while isinstance(s2, SignalSlice):
|
16
|
-
s2 = s2.
|
16
|
+
s2 = s2.base
|
17
17
|
if s1 == s2:
|
18
18
|
return True
|
19
19
|
return False
|
pymoto/solvers/iterative.py
CHANGED
@@ -132,7 +132,7 @@ class GeometricMultigrid(Preconditioner):
|
|
132
132
|
assert cycle.lower() in self._available_cycles, f"Cycle ({cycle}) is not available. Options are {self._available_cycles}"
|
133
133
|
self.cycle = cycle
|
134
134
|
self.inner_level = None if inner_level is None else inner_level
|
135
|
-
self.smoother = DampedJacobi(w=0.5) if smoother is None else
|
135
|
+
self.smoother = DampedJacobi(w=0.5) if smoother is None else smoother
|
136
136
|
self.smooth_steps = smooth_steps
|
137
137
|
self.R = None
|
138
138
|
self.sub_domain = DomainDefinition(domain.nelx // 2, domain.nely // 2, domain.nelz // 2,
|
@@ -299,7 +299,7 @@ class CG(LinearSolver):
|
|
299
299
|
self.A = A
|
300
300
|
self.preconditioner.update(A)
|
301
301
|
if self.verbosity >= 1:
|
302
|
-
print(f"Preconditioner set up in {np.round(time.perf_counter() - tstart,3)}s")
|
302
|
+
print(f"CG Preconditioner set up in {np.round(time.perf_counter() - tstart, 3)}s")
|
303
303
|
|
304
304
|
def solve(self, rhs, x0=None, trans='N'):
|
305
305
|
if trans == 'N':
|
@@ -321,10 +321,18 @@ class CG(LinearSolver):
|
|
321
321
|
x = x.reshape((x.size, 1))
|
322
322
|
|
323
323
|
r = b - A@x
|
324
|
+
tval = np.linalg.norm(r, axis=0) / np.linalg.norm(b, axis=0)
|
325
|
+
if self.verbosity >= 2:
|
326
|
+
print(f"CG Initial (max) residual = {tval.max()}")
|
327
|
+
|
328
|
+
if tval.max() <= self.tol:
|
329
|
+
if self.verbosity >= 1:
|
330
|
+
print(f"CG Converged in 0 iterations and {np.round(time.perf_counter() - tstart, 3)}s, with final (max) residual {tval.max()}")
|
331
|
+
|
332
|
+
return x.flatten() if rhs.ndim == 1 else x
|
333
|
+
|
324
334
|
z = self.preconditioner.solve(r, trans=trans)
|
325
335
|
p = orth(z, normalize=True)
|
326
|
-
if self.verbosity >= 2:
|
327
|
-
print(f"Initial residual = {np.linalg.norm(r, axis=0) / np.linalg.norm(b, axis=0)}")
|
328
336
|
|
329
337
|
for i in range(self.maxit):
|
330
338
|
q = A @ p
|
@@ -333,16 +341,15 @@ class CG(LinearSolver):
|
|
333
341
|
alpha = pq_inv @ (p.conj().T @ r)
|
334
342
|
|
335
343
|
x += p @ alpha
|
336
|
-
if i %
|
344
|
+
if i % self.restart == 0: # Explicit restart
|
337
345
|
r = b - A@x
|
338
346
|
else:
|
339
347
|
r -= q @ alpha
|
340
348
|
|
349
|
+
tval = np.linalg.norm(r, axis=0)/np.linalg.norm(b, axis=0)
|
341
350
|
if self.verbosity >= 2:
|
342
|
-
print(f"i = {i}, residuals = {
|
343
|
-
|
344
|
-
tval = np.linalg.norm(r)/np.linalg.norm(b)
|
345
|
-
if tval <= self.tol:
|
351
|
+
print(f"CG i = {i}, residuals = {tval}")
|
352
|
+
if tval.max() <= self.tol:
|
346
353
|
break
|
347
354
|
|
348
355
|
z = self.preconditioner.solve(r, trans=trans)
|
@@ -350,12 +357,9 @@ class CG(LinearSolver):
|
|
350
357
|
beta = -pq_inv @ (q.conj().T @ z)
|
351
358
|
p = orth(z + p@beta, normalize=False)
|
352
359
|
|
353
|
-
if tval > self.tol:
|
354
|
-
warnings.warn(f'Maximum iterations ({self.maxit}) reached, with final
|
360
|
+
if tval.max() > self.tol:
|
361
|
+
warnings.warn(f'CG Maximum iterations ({self.maxit}) reached, with final residuals {tval}')
|
355
362
|
elif self.verbosity >= 1:
|
356
|
-
print(f"Converged in {i} iterations and {np.round(time.perf_counter() - tstart, 3)}s, with final
|
363
|
+
print(f"CG Converged in {i} iterations and {np.round(time.perf_counter() - tstart, 3)}s, with final (max) residual {tval.max()}")
|
357
364
|
|
358
|
-
if rhs.ndim == 1
|
359
|
-
return x.flatten()
|
360
|
-
else:
|
361
|
-
return x
|
365
|
+
return x.flatten() if rhs.ndim == 1 else x
|
pymoto/solvers/solvers.py
CHANGED
@@ -75,6 +75,17 @@ class LinearSolver:
|
|
75
75
|
return np.linalg.norm(mat@x - b, axis=0) / np.linalg.norm(b, axis=0)
|
76
76
|
|
77
77
|
|
78
|
+
def get_diagonal_indices(mat):
|
79
|
+
""" Get the row/column indices for entries in the matrix that are diagonal (has all zeros in its row and column) """
|
80
|
+
assert mat.ndim == 2, "Not a matrix"
|
81
|
+
n = min(*mat.shape) # Matrix size
|
82
|
+
bmat = mat != 0
|
83
|
+
has_diag = bmat.diagonal()
|
84
|
+
nnz_rows = np.array(bmat.sum(axis=0)).flatten()[:n]
|
85
|
+
nnz_cols = np.array(bmat.sum(axis=1)).flatten()[:n]
|
86
|
+
return np.logical_and(has_diag, nnz_rows <= 1, nnz_cols <= 1)
|
87
|
+
|
88
|
+
|
78
89
|
class LDAWrapper(LinearSolver):
|
79
90
|
r""" Linear dependency aware solver (LDAS)
|
80
91
|
|
@@ -105,6 +116,8 @@ class LDAWrapper(LinearSolver):
|
|
105
116
|
self.xadj_stored = []
|
106
117
|
self.badj_stored = []
|
107
118
|
self.A = None
|
119
|
+
self.diagonal_idx = []
|
120
|
+
self.nondiagonal_idx = slice(None)
|
108
121
|
self._did_solve = False # For debugging purposes
|
109
122
|
self._last_rtol = 0.
|
110
123
|
self.hermitian = hermitian
|
@@ -123,6 +136,9 @@ class LDAWrapper(LinearSolver):
|
|
123
136
|
self.hermitian = matrix_is_hermitian(A)
|
124
137
|
|
125
138
|
self.A = A
|
139
|
+
diags = get_diagonal_indices(A)
|
140
|
+
self.diagonal_idx = np.argwhere(diags).flatten()
|
141
|
+
self.nondiagonal_idx = np.argwhere(~diags).flatten()
|
126
142
|
self.x_stored.clear()
|
127
143
|
self.b_stored.clear()
|
128
144
|
self.xadj_stored.clear()
|
@@ -130,6 +146,9 @@ class LDAWrapper(LinearSolver):
|
|
130
146
|
self.solver.update(A)
|
131
147
|
|
132
148
|
def _do_solve_1rhs(self, A, rhs, x_data, b_data, solve_fn, x0=None):
|
149
|
+
isel = self.nondiagonal_idx
|
150
|
+
idia = self.diagonal_idx
|
151
|
+
|
133
152
|
dtype = np.result_type(A, rhs)
|
134
153
|
if rhs.ndim == 1:
|
135
154
|
rhs_loc = np.zeros((rhs.size, 1), dtype=dtype)
|
@@ -139,11 +158,16 @@ class LDAWrapper(LinearSolver):
|
|
139
158
|
rhs_loc[:] = rhs
|
140
159
|
sol = np.zeros_like(rhs_loc, dtype=dtype)
|
141
160
|
|
142
|
-
#
|
161
|
+
# Diagonal part of the matrix
|
162
|
+
sol[idia, ...] = rhs_loc[idia, ...] / A.diagonal()[idia, None]
|
163
|
+
rhs_loc[idia, ...] = 0
|
164
|
+
|
165
|
+
# Non-diagonal part of the matrix
|
166
|
+
# Reconstruct using the database using modified Gram-Schmidt
|
143
167
|
for (x, b) in zip(x_data, b_data):
|
144
168
|
assert x.ndim == b.ndim == 1
|
145
|
-
assert x.size == b.size == rhs_loc.shape[0]
|
146
|
-
alpha = rhs_loc.T @ b.conj() / (b.conj() @ b)
|
169
|
+
# assert x.size == b.size == rhs_loc.shape[0]
|
170
|
+
alpha = rhs_loc[isel, ...].T @ b.conj() / (b.conj() @ b)
|
147
171
|
|
148
172
|
rem_rhs = alpha * b[:, None]
|
149
173
|
add_sol = alpha * x[:, None]
|
@@ -162,34 +186,41 @@ class LDAWrapper(LinearSolver):
|
|
162
186
|
warnings.warn('LDAS: Complex vector cannot be added to real solution')
|
163
187
|
continue
|
164
188
|
|
165
|
-
rhs_loc -= rem_rhs
|
166
|
-
sol += add_sol
|
189
|
+
rhs_loc[isel, ...] -= rem_rhs
|
190
|
+
sol[isel, ...] += add_sol
|
191
|
+
|
192
|
+
assert np.all(rhs_loc[idia, ...] == 0)
|
167
193
|
|
168
194
|
# Check tolerance
|
169
|
-
self._last_rtol =
|
195
|
+
self._last_rtol = self.residual(A, sol, rhs if rhs.ndim > 1 else rhs.reshape(-1, 1))
|
170
196
|
self._did_solve = self._last_rtol > self.tol
|
197
|
+
# If tolerance too large, use the solver for new values
|
171
198
|
if np.any(self._did_solve):
|
172
199
|
if x0 is not None:
|
173
200
|
if x0.ndim == 1:
|
174
201
|
x0_loc = x0.reshape(-1, 1).copy()
|
175
202
|
else:
|
176
203
|
x0_loc = x0[..., self._did_solve].copy()
|
204
|
+
x0_loc[idia, ...] = 0
|
177
205
|
for x in x_data:
|
178
|
-
beta = x0_loc.T @ x.conj() / (x.conj() @ x)
|
179
|
-
x0_loc -= beta * x
|
206
|
+
beta = x0_loc[isel, ...].T @ x.conj() / (x.conj() @ x)
|
207
|
+
x0_loc[isel, ...] -= beta * x
|
180
208
|
else:
|
181
209
|
x0_loc = None
|
182
210
|
|
183
|
-
# Calculate a new solution
|
211
|
+
# Calculate a new solution (for the vectors that have too high residual)
|
184
212
|
xnew = solve_fn(rhs_loc[..., self._did_solve], x0_loc)
|
185
|
-
|
186
|
-
|
213
|
+
|
214
|
+
if isinstance(isel, slice):
|
215
|
+
sol[isel, self._did_solve] += xnew[isel, ...]
|
216
|
+
else:
|
217
|
+
sol[np.ix_(isel, self._did_solve)] += xnew[isel, ...]
|
187
218
|
|
188
219
|
# Add to database
|
189
220
|
for i in range(xnew.shape[-1]):
|
190
221
|
# Remove all previous components that are already in the database (orthogonalize)
|
191
|
-
xadd = xnew[
|
192
|
-
badd = A @
|
222
|
+
xadd = xnew[isel, i]
|
223
|
+
badd = (A @ xnew[..., i])[isel, ...]
|
193
224
|
for x, b in zip(x_data, b_data):
|
194
225
|
beta = badd @ b.conj() / (b.conj() @ b)
|
195
226
|
badd -= beta * b
|
pymoto/solvers/sparse.py
CHANGED
@@ -140,6 +140,9 @@ class SolverSparsePardiso(LinearSolver):
|
|
140
140
|
Returns:
|
141
141
|
Solution of the system of linear equations, same shape as input b
|
142
142
|
"""
|
143
|
+
if b.dtype != np.float64: # Only float64 is supported --> this is also done in _check_b, but fails for int8
|
144
|
+
warnings.warn(f"Array b's data type was converted from {b.dtype} to float64")
|
145
|
+
b = b.astype(np.float64)
|
143
146
|
if trans == 'N':
|
144
147
|
return self._pardiso_solver.solve(self.A, b)
|
145
148
|
elif trans == 'T' or trans == 'H':
|
@@ -200,6 +203,13 @@ class SolverSparsePardiso(LinearSolver):
|
|
200
203
|
if v != 0:
|
201
204
|
print(f"{i+1}: {v} ({k})") # i+1 because of 1-based numbering
|
202
205
|
|
206
|
+
def __del__(self):
|
207
|
+
try:
|
208
|
+
self._pardiso_solver.free_memory(everything=True)
|
209
|
+
except ImportError:
|
210
|
+
# To prevent ImportError: sys.meta_path is None, Python is likely shutting down
|
211
|
+
pass
|
212
|
+
|
203
213
|
|
204
214
|
# ------------------------------------ LU Solver -----------------------------------
|
205
215
|
try:
|
@@ -1,29 +1,37 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.4
|
2
2
|
Name: pyMOTO
|
3
|
-
Version: 1.5.
|
4
|
-
Summary: A modular approach
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
3
|
+
Version: 1.5.1b1
|
4
|
+
Summary: A modular approach to topology optimization
|
5
|
+
Author-email: Arnoud Delissen <arnouddelissen+pymoto@gmail.com>
|
6
|
+
License: MIT
|
7
|
+
Project-URL: Homepage, https://pymoto.readthedocs.io/
|
8
|
+
Project-URL: Code, https://github.com/aatmdelissen/pyMOTO
|
9
|
+
Project-URL: Documentation, https://pymoto.readthedocs.io/
|
10
|
+
Project-URL: Issues, https://github.com/aatmdelissen/pyMOTO/issues
|
11
|
+
Project-URL: Release notes, https://github.com/aatmdelissen/pyMOTO/releases/latest
|
12
|
+
Keywords: topology optimization,generative design,sensitivity analysis,gradients,finite element method,structural optimization,mechanics,engineering
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
15
|
+
Classifier: Topic :: Scientific/Engineering
|
16
|
+
Classifier: Natural Language :: English
|
10
17
|
Classifier: Programming Language :: Python :: 3
|
11
18
|
Classifier: Operating System :: OS Independent
|
12
|
-
|
13
|
-
Classifier: Topic :: Scientific/Engineering
|
19
|
+
Requires-Python: >=3.8
|
14
20
|
Description-Content-Type: text/markdown
|
15
21
|
License-File: LICENSE
|
16
22
|
Requires-Dist: numpy
|
17
|
-
Requires-Dist: scipy
|
23
|
+
Requires-Dist: scipy>=1.7
|
18
24
|
Requires-Dist: sympy
|
19
25
|
Requires-Dist: matplotlib
|
20
26
|
Provides-Extra: dev
|
21
|
-
Requires-Dist:
|
22
|
-
Requires-Dist:
|
23
|
-
Requires-Dist: cvxopt
|
24
|
-
Requires-Dist: scikit-sparse
|
25
|
-
Requires-Dist: pypardiso
|
26
|
-
Requires-Dist:
|
27
|
+
Requires-Dist: pytest; extra == "dev"
|
28
|
+
Requires-Dist: flake8; extra == "dev"
|
29
|
+
Requires-Dist: cvxopt; extra == "dev"
|
30
|
+
Requires-Dist: scikit-sparse; extra == "dev"
|
31
|
+
Requires-Dist: pypardiso; extra == "dev"
|
32
|
+
Requires-Dist: opt-einsum; extra == "dev"
|
33
|
+
Requires-Dist: jax[cpu]; extra == "dev"
|
34
|
+
Dynamic: license-file
|
27
35
|
|
28
36
|
[](https://doi.org/10.5281/zenodo.8138859)
|
29
37
|
[](https://anaconda.org/aatmdelissen/pymoto)
|
@@ -51,7 +59,7 @@ automatically calculated.
|
|
51
59
|
|
52
60
|
# Quick start installation
|
53
61
|
1. Make sure you have Python running in some kind of virtual environment (e.g.
|
54
|
-
[conda](https://docs.conda.io/projects/conda/en/stable/), [miniconda](https://docs.conda.io/en/latest/miniconda.html),
|
62
|
+
[uv](https://docs.astral.sh/uv/guides/install-python/), [conda](https://docs.conda.io/projects/conda/en/stable/), [miniconda](https://docs.conda.io/en/latest/miniconda.html),
|
55
63
|
[venv](https://realpython.com/python-virtual-environments-a-primer/))
|
56
64
|
2. Install the pymoto Python package (and its dependencies)
|
57
65
|
- Option A (conda): If you are working with Conda, install by `conda install -c aatmdelissen pymoto`
|
@@ -0,0 +1,28 @@
|
|
1
|
+
pymoto/__init__.py,sha256=csgRTVQ2B0pIbQZYvpmL4RDcKYVoyc-vaio3UzlaQ-s,2064
|
2
|
+
pymoto/core_objects.py,sha256=Amxn-338Igm2BT1O76wr6GgftQ3vgldj-aqHhJodGfA,28579
|
3
|
+
pymoto/routines.py,sha256=inz5GbSnvmbD3WKmaNKATTc_SthKsAMPuRc8Jaub4xs,15568
|
4
|
+
pymoto/utils.py,sha256=YJ-PNLJLc12Yx6TYCrEechS2aaBRx0o4mTM1soeeyz0,1122
|
5
|
+
pymoto/common/domain.py,sha256=lstnJ-ITqtjLMQljoc5gN3XVgdFa7gXlMNWiG7IfcR4,18118
|
6
|
+
pymoto/common/dyadcarrier.py,sha256=Q0MCIP4b-W_RuYxynvY-voCHfeZ4oF12ykAuVUnmLq4,19433
|
7
|
+
pymoto/common/mma.py,sha256=SaFdZhpL5uoQRLWxQrElcjRMzGG4CL3BcPc77z3eMVE,24612
|
8
|
+
pymoto/modules/aggregation.py,sha256=Oi17hIJ6dic4lOPw16zmjbdC72MjB6XK34H80bnbWAI,7580
|
9
|
+
pymoto/modules/assembly.py,sha256=o_HZ4bcyK6cMNWVsrmxvv2z6oDgQcrBmB6dquQCWGC8,23216
|
10
|
+
pymoto/modules/autodiff.py,sha256=WAfoAOHBSozf7jbr9gQz9Vw4a_2G9wGJxLMMqUQP0Co,1684
|
11
|
+
pymoto/modules/complex.py,sha256=B_Obk-ABdV66lEudZ5s8o6qG9NsmYlBsX-PbWvbphhc,4429
|
12
|
+
pymoto/modules/filter.py,sha256=6X9FaQMWYZ_TpHVTFiEibzlmAwmSWbydYM93LFrJ0Wo,25490
|
13
|
+
pymoto/modules/generic.py,sha256=YzsGZ8J0oLCORt78Bf2p0v4GuqpWRI77NLoCk7gqidw,10666
|
14
|
+
pymoto/modules/io.py,sha256=nWhXlDc3l-g-5GEY50yRLbbDAy7pay5Tm92pSgLfUIw,13553
|
15
|
+
pymoto/modules/linalg.py,sha256=GIMqcIfVsMib5nIwrhx4sPT6-auLiHBJjbKCAdulLLE,22005
|
16
|
+
pymoto/modules/scaling.py,sha256=uq88HHW9rP16XLz7UGc3CNBBpY2Z1glo8yjYxZEnXUg,2327
|
17
|
+
pymoto/solvers/__init__.py,sha256=9JUeD2SgZbkYFullA7s7s6SuAVv0onqAqJ8hFvNOs2g,1033
|
18
|
+
pymoto/solvers/auto_determine.py,sha256=X8MEG7h6jLfAV1inpja45_-suG8qQFMfLMDfW2ryQqQ,5134
|
19
|
+
pymoto/solvers/dense.py,sha256=9fKPCwNxRKAEk5k1A7fdLrr9ngeVssGlw-sbjWCm4iU,11235
|
20
|
+
pymoto/solvers/iterative.py,sha256=hfMRw2LChupr4sQf8qUKG6OHjhpeVAp6C9N7M4-845M,13179
|
21
|
+
pymoto/solvers/matrix_checks.py,sha256=bbrfjpTSWWnuQW3xY0_CYE8yrh5gA9K5b1LzHEOFAxI,1663
|
22
|
+
pymoto/solvers/solvers.py,sha256=Srn44oRonZIjJq-XVpJY9KoTWV0R7zBWFaSmXKWsUCw,11870
|
23
|
+
pymoto/solvers/sparse.py,sha256=BPw-2Q4lgbmtjCl8eLEmDdWhjqD7RSLrVYy_kQN_LdU,17144
|
24
|
+
pymoto-1.5.1b1.dist-info/licenses/LICENSE,sha256=ZXMC2Txpzs-dBwz9Me4_1rQCSVl4P1B27MomNi43F30,1072
|
25
|
+
pymoto-1.5.1b1.dist-info/METADATA,sha256=Xt0HY8PX1nI1W1_xiWZNkin3vVXNenx-mvhMhn0F2wY,5510
|
26
|
+
pymoto-1.5.1b1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
27
|
+
pymoto-1.5.1b1.dist-info/top_level.txt,sha256=EdvAUSmFMaiqhuEZW8jxANMiK-LdPtlmDWL6SfmCdUU,7
|
28
|
+
pymoto-1.5.1b1.dist-info/RECORD,,
|
pyMOTO-1.5.0.dist-info/RECORD
DELETED
@@ -1,29 +0,0 @@
|
|
1
|
-
pymoto/__init__.py,sha256=YLMAiO2PZHAC6nYWXVh03rhZnZkc_Rc2z7SGQj1T8I4,2058
|
2
|
-
pymoto/core_objects.py,sha256=88AOo041wrcRSPoLCRwBXUoYANGX-b2SAA0Nuf6sn2Y,25252
|
3
|
-
pymoto/routines.py,sha256=yjvcQDcWU47ZM6ZpZoX8VwrJoN9JDO_25DSa9oVcKec,15582
|
4
|
-
pymoto/utils.py,sha256=YJ-PNLJLc12Yx6TYCrEechS2aaBRx0o4mTM1soeeyz0,1122
|
5
|
-
pymoto/common/domain.py,sha256=-eFuYRLehQ17Ai-cV59f4I9FbEM-DJAj6kjjVfj31X0,18120
|
6
|
-
pymoto/common/dyadcarrier.py,sha256=VwMbqPr0NMDPfpsH0BwvXp8M1dmh8ijFDpF6yoyTmto,19394
|
7
|
-
pymoto/common/mma.py,sha256=Pof3clOHA8PG51TmUjs11dkjSP96kovZjsPv62tI2Ec,24055
|
8
|
-
pymoto/modules/aggregation.py,sha256=Oi17hIJ6dic4lOPw16zmjbdC72MjB6XK34H80bnbWAI,7580
|
9
|
-
pymoto/modules/assembly.py,sha256=quuR8QpB2w-O0zly-xS6PK6wZQMY6S5TWh15Y9wuh14,22974
|
10
|
-
pymoto/modules/autodiff.py,sha256=WAfoAOHBSozf7jbr9gQz9Vw4a_2G9wGJxLMMqUQP0Co,1684
|
11
|
-
pymoto/modules/complex.py,sha256=B_Obk-ABdV66lEudZ5s8o6qG9NsmYlBsX-PbWvbphhc,4429
|
12
|
-
pymoto/modules/filter.py,sha256=6X9FaQMWYZ_TpHVTFiEibzlmAwmSWbydYM93LFrJ0Wo,25490
|
13
|
-
pymoto/modules/generic.py,sha256=YzsGZ8J0oLCORt78Bf2p0v4GuqpWRI77NLoCk7gqidw,10666
|
14
|
-
pymoto/modules/io.py,sha256=LcFvJ-cPgg5ee-aag8kaxHw5RzQ-ggxOM5jk7PeJ1r8,13140
|
15
|
-
pymoto/modules/linalg.py,sha256=BNkih4nvvkYuQpm4bG5U38dAjkfD5EFi3MjANpBEPPI,21927
|
16
|
-
pymoto/modules/scaling.py,sha256=uq88HHW9rP16XLz7UGc3CNBBpY2Z1glo8yjYxZEnXUg,2327
|
17
|
-
pymoto/solvers/__init__.py,sha256=9JUeD2SgZbkYFullA7s7s6SuAVv0onqAqJ8hFvNOs2g,1033
|
18
|
-
pymoto/solvers/auto_determine.py,sha256=X8MEG7h6jLfAV1inpja45_-suG8qQFMfLMDfW2ryQqQ,5134
|
19
|
-
pymoto/solvers/dense.py,sha256=9fKPCwNxRKAEk5k1A7fdLrr9ngeVssGlw-sbjWCm4iU,11235
|
20
|
-
pymoto/solvers/iterative.py,sha256=CIxJHjGnCaIjXbtO2NxV60yeDpcCbSD6Bp0xR-7vOf0,12944
|
21
|
-
pymoto/solvers/matrix_checks.py,sha256=bbrfjpTSWWnuQW3xY0_CYE8yrh5gA9K5b1LzHEOFAxI,1663
|
22
|
-
pymoto/solvers/solvers.py,sha256=RwHjZYYlE3oA0U9k7ukla2gOdmq57rSSJQvHqjaM7JU,10626
|
23
|
-
pymoto/solvers/sparse.py,sha256=w8XBlFBIfOpNnfRdLWhLzzqtD8YVxMnDBuhIabFfQQc,16664
|
24
|
-
pyMOTO-1.5.0.dist-info/LICENSE,sha256=ZXMC2Txpzs-dBwz9Me4_1rQCSVl4P1B27MomNi43F30,1072
|
25
|
-
pyMOTO-1.5.0.dist-info/METADATA,sha256=hC38SdgeKEK5NkNDh-gwc4Gz2JOFSymJB-eAMXl7HX4,5006
|
26
|
-
pyMOTO-1.5.0.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
|
27
|
-
pyMOTO-1.5.0.dist-info/top_level.txt,sha256=EdvAUSmFMaiqhuEZW8jxANMiK-LdPtlmDWL6SfmCdUU,7
|
28
|
-
pyMOTO-1.5.0.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
29
|
-
pyMOTO-1.5.0.dist-info/RECORD,,
|
pyMOTO-1.5.0.dist-info/zip-safe
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
|
File without changes
|
File without changes
|