Skip to content

Commit e2998bc

Browse files
authored
Adding latest changes from mapdl.math (#109)
1 parent 4b9f310 commit e2998bc

File tree

5 files changed

+226
-15
lines changed

5 files changed

+226
-15
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,4 @@ repos:
5454
- repo: https://github.com/python-jsonschema/check-jsonschema
5555
rev: 0.22.0
5656
hooks:
57-
- id: check-github-workflows
57+
- id: check-github-workflows

doc/styles/Vocab/ANSYS/accept.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ Eigenshape
1717
eigenshapes
1818
eigensolvers
1919
MAPDL
20-
Mapdl
2120
mapdl
2221
my_mat
2322
nax

src/ansys/math/core/math.py

Lines changed: 101 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -636,7 +636,9 @@ def mass(
636636
637637
Examples
638638
--------
639-
>>> mass = mapdl.math.mass()
639+
>>> import ansys.math.core.math as pymath
640+
>>> mm = pymath.AnsMath()
641+
>>> mass = mm.mass()
640642
>>> mass
641643
AnsMath matrix 60 x 60
642644
@@ -678,7 +680,9 @@ def damp(
678680
679681
Examples
680682
--------
681-
>>> ans_mat = mapdl.math.damp()
683+
>>> import ansys.math.core.math as pymath
684+
>>> mm = pymath.AnsMath()
685+
>>> ans_mat = mm.damp()
682686
>>> ans_mat
683687
AnsMath Matrix 60 x 60
684688
@@ -906,7 +910,18 @@ def sparse(self, mat, thresh="", **kwargs):
906910
kwargs.setdefault("mute", True)
907911
self._mapdl.run(f"*COMP,{mat.id},SPARSE,{thresh}", **kwargs)
908912

909-
def eigs(self, nev, k, m=None, c=None, phi=None, algo=None, fmin=None, fmax=None):
913+
def eigs(
914+
self,
915+
nev,
916+
k,
917+
m=None,
918+
c=None,
919+
phi=None,
920+
algo=None,
921+
fmin=None,
922+
fmax=None,
923+
cpxmod=None,
924+
):
910925
"""Solve an eigenproblem.
911926
912927
Parameters
@@ -937,6 +952,8 @@ def eigs(self, nev, k, m=None, c=None, phi=None, algo=None, fmin=None, fmax=None
937952
fmin = ""
938953
if not fmax:
939954
fmax = ""
955+
if not cpxmod:
956+
cpxmod = ""
940957

941958
cid = ""
942959
if not c:
@@ -951,7 +968,7 @@ def eigs(self, nev, k, m=None, c=None, phi=None, algo=None, fmin=None, fmax=None
951968

952969
self._mapdl.run("/SOLU", mute=True)
953970
self._mapdl.run("antype,modal", mute=True)
954-
self._mapdl.run(f"modopt,{algo},{nev},{fmin},{fmax}", mute=True)
971+
self._mapdl.run(f"modopt,{algo},{nev},{fmin},{fmax},{cpxmod}", mute=True)
955972
ev = self.vec()
956973

957974
phistr = "" if not phi else phi.id
@@ -1355,6 +1372,53 @@ def axpy(self, obj, val1, val2):
13551372
self._mapdl.run(f"*AXPY,{val1},0,{obj.id},{val2},0,{self.id}", mute=True)
13561373
return self
13571374

1375+
def kron(self, obj):
1376+
"""Calculates the Kronecker product of two matrices/vectors
1377+
1378+
Parameters
1379+
----------
1380+
obj : ``AnsVec`` or ``AnsMat``
1381+
AnsMath object.
1382+
1383+
Returns
1384+
-------
1385+
``AnsMat`` or ``AnsVec``
1386+
Kronecker product between the two matrices/vectors.
1387+
1388+
.. note::
1389+
Requires at least MAPDL version 2023R2.
1390+
1391+
Examples
1392+
--------
1393+
>>> import ansys.math.core.math as pymath
1394+
>>> mm = pymath.AnsMath()
1395+
>>> m1 = mm.rand(3, 3)
1396+
>>> m2 = mm.rand(4,2)
1397+
>>> res = m1.kron(m2)
1398+
"""
1399+
1400+
mapdl_version = self._mapdl.version
1401+
if mapdl_version < 23.2: # pragma: no cover
1402+
raise VersionError("``kron`` requires MAPDL version 2023R2")
1403+
1404+
if not isinstance(obj, AnsMath):
1405+
raise TypeError("Must be an AnsMath object.")
1406+
1407+
if not isinstance(self, (AnsMat, AnsVec)):
1408+
raise TypeError(f"Kron product aborted: Unknown obj type ({self.type})")
1409+
if not isinstance(obj, (AnsMat, AnsVec)):
1410+
raise TypeError(f"Kron product aborted: Unknown obj type ({obj.type})")
1411+
1412+
name = id_generator() # internal name of the new vector/matrix
1413+
# perform the Kronecker product
1414+
self._mapdl.run(f"*KRON,{self.id},{obj.id},{name}")
1415+
1416+
if isinstance(self, AnsVec) and isinstance(obj, AnsVec):
1417+
objout = AnsVec(name, self._mapdl)
1418+
else:
1419+
objout = AnsMat(name, self._mapdl)
1420+
return objout
1421+
13581422
def __add__(self, op2):
13591423
if not hasattr(op2, "id"):
13601424
raise TypeError("The object to be added must be an AnsMath object.")
@@ -1383,8 +1447,20 @@ def __isub__(self, op):
13831447
return self.axpy(op, -1, 1)
13841448

13851449
def __imul__(self, val):
1386-
self._mapdl._log.info("Call MAPDL to scale the object.")
1387-
self._mapdl.run(f"*SCAL,{self.id},{val}", mute=True)
1450+
mapdl_version = self._mapdl.version
1451+
self._mapdl._log.info("Call MAPDL to scale the object")
1452+
1453+
if isinstance(val, AnsVec):
1454+
if mapdl_version < 23.2: # pragma: no cover
1455+
raise VersionError("Scaling by a vector requires MAPDL version 2023R2 or superior.")
1456+
else:
1457+
self._mapdl._log.info(f"Scaling ({self.type}) by a vector")
1458+
self._mapdl.run(f"*SCAL,{self.id},{val.id}", mute=False)
1459+
elif isinstance(val, (int, float)):
1460+
self._mapdl.run(f"*SCAL,{self.id},{val}", mute=True)
1461+
else:
1462+
raise TypeError(f"The provided type {type(val)} is not supported.")
1463+
13881464
return self
13891465

13901466
def __itruediv__(self, val):
@@ -1433,10 +1509,24 @@ def __repr__(self):
14331509
return f"AnsMath vector size {self.size}"
14341510

14351511
def __getitem__(self, num):
1512+
info = self._mapdl._data_info(self.id)
1513+
dtype = ANSYS_VALUE_TYPE[info.stype]
14361514
if num < 0:
1437-
raise ValueError("Negative indices are not permitted.")
1438-
self._mapdl.run(f"pyval={self.id}({num+1})", mute=True)
1439-
return self._mapdl.scalar_param("pyval")
1515+
raise ValueError("Negative indices not permitted")
1516+
1517+
self._mapdl.run(f"pyval_={self.id}({num+1})", mute=True)
1518+
item_val = self._mapdl.scalar_param("pyval_")
1519+
1520+
if MYCTYPE[dtype].upper() in ["C", "Z"]:
1521+
self._mapdl.run(f"pyval_img_={self.id}({num+1},2)", mute=True)
1522+
img_val = self._mapdl.scalar_param("pyval_img_")
1523+
item_val = item_val + img_val * 1j
1524+
1525+
# Clean parameters
1526+
self._mapdl.run("item_val =")
1527+
self._mapdl.run("pyval_img_=")
1528+
1529+
return item_val
14401530

14411531
def __mul__(self, vec):
14421532
"""Return the element-wise product with another AnsMath vector.
@@ -1507,8 +1597,8 @@ def asarray(self, dtype=None) -> np.ndarray:
15071597
----------
15081598
dtype : numpy.dtype, optional
15091599
NumPy data type to upload the array as. The options are `np.double <numpy.double>`_,
1510-
`np.int32 <numpy.int32>`_, and `np.int64 <numpy.int64>`_. The default is the current array
1511-
type.
1600+
`np.int32 <numpy.int32>`_, and `np.int64 <numpy.int64>`_. The default is the current
1601+
array type.
15121602
15131603
Returns
15141604
-------

tests/test_files/model_damping.db

2.13 MB
Binary file not shown.

tests/test_math.py

Lines changed: 124 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,39 @@
2525
)
2626

2727

28+
PATH = os.path.dirname(os.path.abspath(__file__))
29+
lib_path = os.path.join(PATH, "test_files")
30+
31+
2832
@pytest.fixture(scope="module")
2933
def mm(mapdl):
3034
mm = pymath.AnsMath(mapdl)
3135
return mm
3236

3337

38+
@pytest.fixture()
39+
def cube_with_damping(mm):
40+
mm._mapdl.prep7()
41+
db = os.path.join(lib_path, "model_damping.db")
42+
mm._mapdl.upload(db)
43+
mm._mapdl.resume("model_damping.db")
44+
mm._mapdl.mp("dens", 1, 7800 / 0.5)
45+
46+
mm._mapdl.slashsolu()
47+
mm._mapdl.antype("modal")
48+
mm._mapdl.modopt("damp", 5)
49+
mm._mapdl.mxpand(5)
50+
mm._mapdl.mascale(0.15)
51+
52+
mm._mapdl.alphad(10)
53+
mm._mapdl.solve()
54+
mm._mapdl.save()
55+
if mm._mapdl._distributed:
56+
mm._mapdl.aux2()
57+
mm._mapdl.combine("full")
58+
mm._mapdl.slashsolu()
59+
60+
3461
def test_ones(mm):
3562
v = mm.ones(10)
3663
assert v.size == 10
@@ -73,6 +100,19 @@ def test_inplace_mult(mm):
73100
assert v[0] == 2
74101

75102

103+
def test_inplace_mult_with_vec(mm):
104+
mapdl_version = mm._mapdl.version
105+
if mapdl_version < 23.2:
106+
pytest.skip("Requires MAPDL 2023 R2 or later.")
107+
108+
m1 = mm.rand(3, 3)
109+
m2 = m1.copy()
110+
v1 = mm.ones(3)
111+
v1.const(2)
112+
m2 *= v1
113+
assert np.allclose(m2, np.multiply(m1, v1) * v1)
114+
115+
76116
def test_set_vec_large(mm):
77117
# send a vector larger than the gRPC size limit of 4 MB
78118
sz = 1000000
@@ -111,7 +151,10 @@ def test_vec(mm):
111151
def test_vec_from_name(mm, vecval):
112152
vec0 = mm.set_vec(vecval)
113153
vec1 = mm.vec(name=vec0.id)
114-
assert np.allclose(vecval, vec1.asarray())
154+
assert np.allclose(vec0, vec1)
155+
156+
vec1 = mm.vec(name=vec0.id, asarray=True)
157+
assert isinstance(vec1, np.ndarray)
115158

116159

117160
def test_vec__mul__(mm):
@@ -208,7 +251,7 @@ def test_matrix_matmult(mm):
208251
assert np.allclose(m1.asarray() @ m2.asarray(), m3.asarray())
209252

210253

211-
def test_getitem(mm):
254+
def test_getitem_AnsMat(mm):
212255
size_i, size_j = (3, 3)
213256
mat = mm.rand(size_i, size_j)
214257
np_mat = mat.asarray()
@@ -220,6 +263,49 @@ def test_getitem(mm):
220263
assert vec[j] == np_mat[j, i]
221264

222265

266+
@pytest.mark.parametrize("dtype_", [np.int64, np.double, np.complex128])
267+
def test_getitem_AnsVec(mm, dtype_):
268+
size_i = 3
269+
vec = mm.rand(size_i, dtype=dtype_)
270+
np_vec = vec.asarray()
271+
for i in range(size_i):
272+
assert vec[i] == np_vec[i]
273+
274+
275+
@pytest.mark.parametrize("dtype_", [np.double, np.complex128])
276+
def test_kron_product(mm, dtype_):
277+
mapdl_version = mm._mapdl.version
278+
if mapdl_version < 23.2:
279+
pytest.skip("Requires MAPDL 2023 R2 or later.")
280+
281+
m1 = mm.rand(3, 3, dtype=dtype_)
282+
m2 = mm.rand(2, 2, dtype=dtype_)
283+
v1 = mm.rand(2, dtype=dtype_)
284+
v2 = mm.rand(4, dtype=dtype_)
285+
# *kron product between matrix and another matrix
286+
res1 = m1.kron(m2)
287+
288+
# *kron product between Vector and a matrix
289+
res2 = v1.kron(m2)
290+
291+
# *kron product between Vector and another Vector
292+
res3 = v1.kron(v2)
293+
294+
assert np.allclose(res1.asarray(), np.kron(m1, m2))
295+
assert np.allclose(res2.asarray(), np.kron(v1.asarray().reshape(2, 1), m2))
296+
assert np.allclose(res3.asarray(), np.kron(v1, v2))
297+
298+
299+
def test_kron_product_unsupported_dtype(mm):
300+
mapdl_version = mm._mapdl.version
301+
if mapdl_version < 23.2:
302+
pytest.skip("Requires MAPDL 2023 R2 or later.")
303+
304+
with pytest.raises(TypeError, match=r"Must be an ApdlMathObj"):
305+
m1 = mm.rand(3, 3)
306+
m1.kron(2)
307+
308+
223309
def test_load_stiff_mass(mm, cube_solve, tmpdir):
224310
k = mm.stiff()
225311
m = mm.mass()
@@ -791,6 +877,42 @@ def test_vec2(mm):
791877
assert parameter_["dimensions"] == vec_.size
792878

793879

880+
def test_damp_matrix(mm, cube_with_damping):
881+
d = mm.damp()
882+
m = mm.mass()
883+
884+
assert d.shape == m.shape
885+
assert isinstance(d, pymath.AnsMat)
886+
887+
888+
def test_damp_matrix_as_array(mm, cube_with_damping):
889+
d = mm.damp()
890+
d = d.asarray()
891+
892+
assert sparse.issparse(d)
893+
assert all([each > 0 for each in d.shape])
894+
895+
d = mm.damp(asarray=True)
896+
assert sparse.issparse(d)
897+
assert all([each > 0 for each in d.shape])
898+
899+
900+
@pytest.mark.parametrize("dtype_", [np.int64, np.double])
901+
def test_damp_matrix_dtype(mm, cube_with_damping, dtype_):
902+
d = mm.stiff(asarray=True, dtype=dtype_)
903+
904+
assert sparse.issparse(d)
905+
assert all([each > 0 for each in d.shape])
906+
assert d.dtype == dtype_
907+
908+
d = mm.stiff(dtype=dtype_)
909+
d = d.asarray(dtype=dtype_)
910+
911+
assert sparse.issparse(d)
912+
assert all([each > 0 for each in d.shape])
913+
assert d.dtype == dtype_
914+
915+
794916
@pytest.fixture(scope="module")
795917
def exit(mm):
796918
return mm._mapdl.exit()

0 commit comments

Comments
 (0)