pyvpmr 241217__cp310-cp310-musllinux_1_2_x86_64.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.
Binary file
pyvpmr/__init__.py ADDED
@@ -0,0 +1,148 @@
1
+ # Copyright (C) 2024 Theodore Chang
2
+ #
3
+ # This program is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
+
16
+ from __future__ import annotations
17
+
18
+ import re
19
+ from typing import Callable
20
+
21
+ import numpy as np
22
+ from _pyvpmr import vpmr as vpmr_impl
23
+ from matplotlib import pyplot as plt
24
+
25
+ vpmr = vpmr_impl
26
+
27
+
28
+ def split(result: str) -> tuple | None:
29
+ """
30
+ Split the output of the vpmr program into two arrays of complex numbers.
31
+
32
+ :param result: The raw output of the vpmr program.
33
+ :return: A tuple of two arrays of complex numbers.
34
+ """
35
+ split_r = result.strip().split('\n')
36
+ regex = re.compile(r'([+\-]\d+\.\d+e[+\-]\d+){2}j')
37
+ items = [i for i in split_r if regex.match(i)]
38
+ if len(items) % 2 != 0:
39
+ print('something wrong with the output')
40
+ return None
41
+
42
+ m_complex = [complex(i) for i in items[:len(items) // 2]]
43
+ s_complex = [complex(i) for i in items[len(items) // 2:]]
44
+ return np.array(m_complex), np.array(s_complex)
45
+
46
+
47
+ def plot(
48
+ m: list | np.ndarray, s: list | np.ndarray, kernel: Callable, *,
49
+ size: tuple[float, float] = (6, 4),
50
+ xlim: tuple[float, float] = (0, 10),
51
+ show: bool = True,
52
+ save_to: str = None
53
+ ):
54
+ """
55
+ Plot the kernel function and the approximation.
56
+
57
+ :param m: The list of m values.
58
+ :param s: The list of s values.
59
+ :param kernel: The kernel function.
60
+ :param size: The size of the figure.
61
+ :param xlim: The x-axis limits.
62
+ :param show: If to show the figure.
63
+ :param save_to: Where to save the figure.
64
+ """
65
+ x = np.linspace(*xlim, 401)
66
+
67
+ y_ref = np.zeros(len(x))
68
+ for i in range(len(x)):
69
+ y_ref[i] = kernel(x[i])
70
+
71
+ y = np.zeros(len(x), dtype=complex)
72
+ for ml, sl in zip(m, s):
73
+ y += ml * np.exp(-sl * x)
74
+
75
+ fig = plt.figure(figsize=size)
76
+
77
+ ax1 = plt.gca()
78
+ ax1.plot(x, y_ref, 'b-', label='kernel', linewidth=2)
79
+ ax1.plot(x, y.real, 'r', linestyle='dashdot', label='approximation', linewidth=3)
80
+ ax1.set_xlabel('time $t$ [s]')
81
+ ax1.set_ylabel('kernel function $g(t)$')
82
+ ax1.legend(loc='upper right', handlelength=4)
83
+
84
+ ax2 = plt.twinx()
85
+ ax2.plot(x, np.abs(y_ref - y), 'g--', label='absolute error', linewidth=1)
86
+ ax2.set_yscale('log')
87
+ ax2.set_ylabel('absolute error')
88
+ ax2.legend(loc='center right')
89
+
90
+ plt.xlim(np.min(x), np.max(x))
91
+ plt.tight_layout(pad=.05)
92
+ if show:
93
+ plt.show()
94
+ if save_to:
95
+ fig.savefig(save_to)
96
+
97
+
98
+ def _process_args(*args):
99
+ if len(args) == 1:
100
+ assert 2 == len(args[0])
101
+ m, s = args[0]
102
+ elif len(args) == 2:
103
+ m, s = args
104
+ else:
105
+ raise ValueError('Wrong number of arguments.')
106
+
107
+ if len(m) == len(s):
108
+ return np.array(m), np.array(s)
109
+
110
+ raise ValueError('The length of m and s must be the same.')
111
+
112
+
113
+ def to_global_damping(*args):
114
+ """
115
+ Generate a command to use the kernel as a global nonviscous damping model in suanPan.
116
+ :param args: The m and s values.
117
+ :return: The command.
118
+ """
119
+ command = '# The following can be used as a global nonviscous damping with the Newmark time integration.\n'
120
+ command += '# You may need to modify the first line to change tag and integration parameters.\n'
121
+ command += 'integrator NonviscousNewmark 1 .25 .5'
122
+
123
+ for m, s in zip(*_process_args(*args)):
124
+ command += f' \\\n{m.real:+.15e} {m.imag:+.15e} {s.real:+.15e} {s.imag:+.15e}'
125
+
126
+ command += '\n'
127
+
128
+ return command
129
+
130
+
131
+ def to_elemental_damping(*args):
132
+ """
133
+ Generate a command to use the kernel as a per-element nonviscous damping model in suanPan.
134
+ :param args: The m and s values.
135
+ :return: The command.
136
+ """
137
+ command = '# The following can be used as a per-element based nonviscous damping.\n'
138
+ command += '# You may need to modify the first line to change tags.\n'
139
+ command += '# Use the alternative form to apply to multiplier elements.\n'
140
+ command += '# modifier ElementalNonviscousGroup {unique_modifier_tag} {associated_element_group_tag}'
141
+ command += 'modifier ElementalNonviscous {unique_modifier_tag} {associated_element_tag}'
142
+
143
+ for m, s in zip(*_process_args(*args)):
144
+ command += f' \\\n{m.real:+.15e} {m.imag:+.15e} {s.real:+.15e} {s.imag:+.15e}'
145
+
146
+ command += '\n'
147
+
148
+ return command