Skip to content

Adding latest changes from mapdl.math #109

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Merged
merged 11 commits into from
Jun 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,4 @@ repos:
- repo: https://github.com/python-jsonschema/check-jsonschema
rev: 0.22.0
hooks:
- id: check-github-workflows
- id: check-github-workflows
1 change: 0 additions & 1 deletion doc/styles/Vocab/ANSYS/accept.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ Eigenshape
eigenshapes
eigensolvers
MAPDL
Mapdl
mapdl
my_mat
nax
Expand Down
112 changes: 101 additions & 11 deletions src/ansys/math/core/math.py
Original file line number Diff line number Diff line change
Expand Up @@ -636,7 +636,9 @@ def mass(

Examples
--------
>>> mass = mapdl.math.mass()
>>> import ansys.math.core.math as pymath
>>> mm = pymath.AnsMath()
>>> mass = mm.mass()
>>> mass
AnsMath matrix 60 x 60

Expand Down Expand Up @@ -678,7 +680,9 @@ def damp(

Examples
--------
>>> ans_mat = mapdl.math.damp()
>>> import ansys.math.core.math as pymath
>>> mm = pymath.AnsMath()
>>> ans_mat = mm.damp()
>>> ans_mat
AnsMath Matrix 60 x 60

Expand Down Expand Up @@ -906,7 +910,18 @@ def sparse(self, mat, thresh="", **kwargs):
kwargs.setdefault("mute", True)
self._mapdl.run(f"*COMP,{mat.id},SPARSE,{thresh}", **kwargs)

def eigs(self, nev, k, m=None, c=None, phi=None, algo=None, fmin=None, fmax=None):
def eigs(
self,
nev,
k,
m=None,
c=None,
phi=None,
algo=None,
fmin=None,
fmax=None,
cpxmod=None,
):
"""Solve an eigenproblem.

Parameters
Expand Down Expand Up @@ -937,6 +952,8 @@ def eigs(self, nev, k, m=None, c=None, phi=None, algo=None, fmin=None, fmax=None
fmin = ""
if not fmax:
fmax = ""
if not cpxmod:
cpxmod = ""

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

self._mapdl.run("/SOLU", mute=True)
self._mapdl.run("antype,modal", mute=True)
self._mapdl.run(f"modopt,{algo},{nev},{fmin},{fmax}", mute=True)
self._mapdl.run(f"modopt,{algo},{nev},{fmin},{fmax},{cpxmod}", mute=True)
ev = self.vec()

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

def kron(self, obj):
"""Calculates the Kronecker product of two matrices/vectors

Parameters
----------
obj : ``AnsVec`` or ``AnsMat``
AnsMath object.

Returns
-------
``AnsMat`` or ``AnsVec``
Kronecker product between the two matrices/vectors.

.. note::
Requires at least MAPDL version 2023R2.

Examples
--------
>>> import ansys.math.core.math as pymath
>>> mm = pymath.AnsMath()
>>> m1 = mm.rand(3, 3)
>>> m2 = mm.rand(4,2)
>>> res = m1.kron(m2)
"""

mapdl_version = self._mapdl.version
if mapdl_version < 23.2: # pragma: no cover
raise VersionError("``kron`` requires MAPDL version 2023R2")

if not isinstance(obj, AnsMath):
raise TypeError("Must be an AnsMath object.")

if not isinstance(self, (AnsMat, AnsVec)):
raise TypeError(f"Kron product aborted: Unknown obj type ({self.type})")
if not isinstance(obj, (AnsMat, AnsVec)):
raise TypeError(f"Kron product aborted: Unknown obj type ({obj.type})")

name = id_generator() # internal name of the new vector/matrix
# perform the Kronecker product
self._mapdl.run(f"*KRON,{self.id},{obj.id},{name}")

if isinstance(self, AnsVec) and isinstance(obj, AnsVec):
objout = AnsVec(name, self._mapdl)
else:
objout = AnsMat(name, self._mapdl)
return objout

def __add__(self, op2):
if not hasattr(op2, "id"):
raise TypeError("The object to be added must be an AnsMath object.")
Expand Down Expand Up @@ -1383,8 +1447,20 @@ def __isub__(self, op):
return self.axpy(op, -1, 1)

def __imul__(self, val):
self._mapdl._log.info("Call MAPDL to scale the object.")
self._mapdl.run(f"*SCAL,{self.id},{val}", mute=True)
mapdl_version = self._mapdl.version
self._mapdl._log.info("Call MAPDL to scale the object")

if isinstance(val, AnsVec):
if mapdl_version < 23.2: # pragma: no cover
raise VersionError("Scaling by a vector requires MAPDL version 2023R2 or superior.")
else:
self._mapdl._log.info(f"Scaling ({self.type}) by a vector")
self._mapdl.run(f"*SCAL,{self.id},{val.id}", mute=False)
elif isinstance(val, (int, float)):
self._mapdl.run(f"*SCAL,{self.id},{val}", mute=True)
else:
raise TypeError(f"The provided type {type(val)} is not supported.")

return self

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

def __getitem__(self, num):
info = self._mapdl._data_info(self.id)
dtype = ANSYS_VALUE_TYPE[info.stype]
if num < 0:
raise ValueError("Negative indices are not permitted.")
self._mapdl.run(f"pyval={self.id}({num+1})", mute=True)
return self._mapdl.scalar_param("pyval")
raise ValueError("Negative indices not permitted")

self._mapdl.run(f"pyval_={self.id}({num+1})", mute=True)
item_val = self._mapdl.scalar_param("pyval_")

if MYCTYPE[dtype].upper() in ["C", "Z"]:
self._mapdl.run(f"pyval_img_={self.id}({num+1},2)", mute=True)
img_val = self._mapdl.scalar_param("pyval_img_")
item_val = item_val + img_val * 1j

# Clean parameters
self._mapdl.run("item_val =")
self._mapdl.run("pyval_img_=")

return item_val

def __mul__(self, vec):
"""Return the element-wise product with another AnsMath vector.
Expand Down Expand Up @@ -1507,8 +1597,8 @@ def asarray(self, dtype=None) -> np.ndarray:
----------
dtype : numpy.dtype, optional
NumPy data type to upload the array as. The options are `np.double <numpy.double>`_,
`np.int32 <numpy.int32>`_, and `np.int64 <numpy.int64>`_. The default is the current array
type.
`np.int32 <numpy.int32>`_, and `np.int64 <numpy.int64>`_. The default is the current
array type.

Returns
-------
Expand Down
Binary file added tests/test_files/model_damping.db
Binary file not shown.
126 changes: 124 additions & 2 deletions tests/test_math.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,39 @@
)


PATH = os.path.dirname(os.path.abspath(__file__))
lib_path = os.path.join(PATH, "test_files")


@pytest.fixture(scope="module")
def mm(mapdl):
mm = pymath.AnsMath(mapdl)
return mm


@pytest.fixture()
def cube_with_damping(mm):
mm._mapdl.prep7()
db = os.path.join(lib_path, "model_damping.db")
mm._mapdl.upload(db)
mm._mapdl.resume("model_damping.db")
mm._mapdl.mp("dens", 1, 7800 / 0.5)

mm._mapdl.slashsolu()
mm._mapdl.antype("modal")
mm._mapdl.modopt("damp", 5)
mm._mapdl.mxpand(5)
mm._mapdl.mascale(0.15)

mm._mapdl.alphad(10)
mm._mapdl.solve()
mm._mapdl.save()
if mm._mapdl._distributed:
mm._mapdl.aux2()
mm._mapdl.combine("full")
mm._mapdl.slashsolu()


def test_ones(mm):
v = mm.ones(10)
assert v.size == 10
Expand Down Expand Up @@ -73,6 +100,19 @@ def test_inplace_mult(mm):
assert v[0] == 2


def test_inplace_mult_with_vec(mm):
mapdl_version = mm._mapdl.version
if mapdl_version < 23.2:
pytest.skip("Requires MAPDL 2023 R2 or later.")

m1 = mm.rand(3, 3)
m2 = m1.copy()
v1 = mm.ones(3)
v1.const(2)
m2 *= v1
assert np.allclose(m2, np.multiply(m1, v1) * v1)


def test_set_vec_large(mm):
# send a vector larger than the gRPC size limit of 4 MB
sz = 1000000
Expand Down Expand Up @@ -111,7 +151,10 @@ def test_vec(mm):
def test_vec_from_name(mm, vecval):
vec0 = mm.set_vec(vecval)
vec1 = mm.vec(name=vec0.id)
assert np.allclose(vecval, vec1.asarray())
assert np.allclose(vec0, vec1)

vec1 = mm.vec(name=vec0.id, asarray=True)
assert isinstance(vec1, np.ndarray)


def test_vec__mul__(mm):
Expand Down Expand Up @@ -208,7 +251,7 @@ def test_matrix_matmult(mm):
assert np.allclose(m1.asarray() @ m2.asarray(), m3.asarray())


def test_getitem(mm):
def test_getitem_AnsMat(mm):
size_i, size_j = (3, 3)
mat = mm.rand(size_i, size_j)
np_mat = mat.asarray()
Expand All @@ -220,6 +263,49 @@ def test_getitem(mm):
assert vec[j] == np_mat[j, i]


@pytest.mark.parametrize("dtype_", [np.int64, np.double, np.complex128])
def test_getitem_AnsVec(mm, dtype_):
size_i = 3
vec = mm.rand(size_i, dtype=dtype_)
np_vec = vec.asarray()
for i in range(size_i):
assert vec[i] == np_vec[i]


@pytest.mark.parametrize("dtype_", [np.double, np.complex128])
def test_kron_product(mm, dtype_):
mapdl_version = mm._mapdl.version
if mapdl_version < 23.2:
pytest.skip("Requires MAPDL 2023 R2 or later.")

m1 = mm.rand(3, 3, dtype=dtype_)
m2 = mm.rand(2, 2, dtype=dtype_)
v1 = mm.rand(2, dtype=dtype_)
v2 = mm.rand(4, dtype=dtype_)
# *kron product between matrix and another matrix
res1 = m1.kron(m2)

# *kron product between Vector and a matrix
res2 = v1.kron(m2)

# *kron product between Vector and another Vector
res3 = v1.kron(v2)

assert np.allclose(res1.asarray(), np.kron(m1, m2))
assert np.allclose(res2.asarray(), np.kron(v1.asarray().reshape(2, 1), m2))
assert np.allclose(res3.asarray(), np.kron(v1, v2))


def test_kron_product_unsupported_dtype(mm):
mapdl_version = mm._mapdl.version
if mapdl_version < 23.2:
pytest.skip("Requires MAPDL 2023 R2 or later.")

with pytest.raises(TypeError, match=r"Must be an ApdlMathObj"):
m1 = mm.rand(3, 3)
m1.kron(2)


def test_load_stiff_mass(mm, cube_solve, tmpdir):
k = mm.stiff()
m = mm.mass()
Expand Down Expand Up @@ -791,6 +877,42 @@ def test_vec2(mm):
assert parameter_["dimensions"] == vec_.size


def test_damp_matrix(mm, cube_with_damping):
d = mm.damp()
m = mm.mass()

assert d.shape == m.shape
assert isinstance(d, pymath.AnsMat)


def test_damp_matrix_as_array(mm, cube_with_damping):
d = mm.damp()
d = d.asarray()

assert sparse.issparse(d)
assert all([each > 0 for each in d.shape])

d = mm.damp(asarray=True)
assert sparse.issparse(d)
assert all([each > 0 for each in d.shape])


@pytest.mark.parametrize("dtype_", [np.int64, np.double])
def test_damp_matrix_dtype(mm, cube_with_damping, dtype_):
d = mm.stiff(asarray=True, dtype=dtype_)

assert sparse.issparse(d)
assert all([each > 0 for each in d.shape])
assert d.dtype == dtype_

d = mm.stiff(dtype=dtype_)
d = d.asarray(dtype=dtype_)

assert sparse.issparse(d)
assert all([each > 0 for each in d.shape])
assert d.dtype == dtype_


@pytest.fixture(scope="module")
def exit(mm):
return mm._mapdl.exit()