Skip to content

Commit 3e1bcb6

Browse files
authored
Add __str__ method to ellipsoid classes (#615)
Show information about the ellipsoids when printing them. Include geometry information (semiaxes lengths, center, rotation angles), with physical properties if they are not `None`. Add tests for the new method.
1 parent b0adc1f commit 3e1bcb6

File tree

2 files changed

+175
-0
lines changed

2 files changed

+175
-0
lines changed

harmonica/_forward/ellipsoids/ellipsoids.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,34 @@ def _check_positive_semiaxis(self, value: float, semiaxis: str):
263263
msg = f"Invalid value of '{semiaxis}' equal to '{value}'. It must be positive."
264264
raise ValueError(msg)
265265

266+
def __str__(self) -> str:
267+
e, n, u = self.center
268+
string = (
269+
f"{type(self).__name__}:\n"
270+
f" • a: {float(self.a)} m\n"
271+
f" • b: {float(self.b)} m\n"
272+
f" • c: {float(self.c)} m\n"
273+
f" • center: ({float(e)}, {float(n)}, {float(u)}) m\n"
274+
f" • yaw: {float(self.yaw)}\n"
275+
f" • pitch: {float(self.pitch)}\n"
276+
f" • roll: {float(self.roll)}"
277+
)
278+
279+
if self.density is not None:
280+
string += f"\n • density: {float(self.density)} kg/m³"
281+
if self.susceptibility is not None:
282+
if isinstance(self.susceptibility, Real):
283+
string += f"\n • susceptibility: {float(self.susceptibility)}"
284+
else:
285+
string += "\n • susceptibility:"
286+
matrix_as_str = str(self.susceptibility)
287+
for line in matrix_as_str.splitlines():
288+
string += f"\n {line}"
289+
if self.remanent_mag is not None:
290+
me, mn, mu = self.remanent_mag
291+
string += f"\n • remanent_mag: ({float(me)}, {float(mn)}, {float(mu)}) A/m"
292+
return string
293+
266294
def to_pyvista(self, **kwargs):
267295
"""
268296
Export ellipsoid to a :class:`pyvista.PolyData` object.

harmonica/tests/ellipsoids/test_ellipsoids.py

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -595,3 +595,150 @@ def test_pyvista_object(self, ellipsoid):
595595
4 / 3 * np.pi * ellipsoid.a * ellipsoid.b * ellipsoid.c,
596596
rtol=1e-3,
597597
)
598+
599+
600+
class TestString:
601+
"""Test string representation of ellipsoids."""
602+
603+
a, b, c = 3, 2, 1
604+
yaw, pitch, roll = 73, 14, -35
605+
center = (43.0, -72.0, 105)
606+
607+
def build_ellipsoid(self, ellipsoid_type):
608+
match ellipsoid_type:
609+
case "oblate":
610+
ellipsoid = OblateEllipsoid(
611+
self.b, self.a, self.yaw, self.pitch, center=self.center
612+
)
613+
case "prolate":
614+
ellipsoid = ProlateEllipsoid(
615+
self.a, self.b, self.yaw, self.pitch, center=self.center
616+
)
617+
case "triaxial":
618+
ellipsoid = TriaxialEllipsoid(
619+
self.a,
620+
self.b,
621+
self.c,
622+
self.yaw,
623+
self.pitch,
624+
self.roll,
625+
center=self.center,
626+
)
627+
case "sphere":
628+
ellipsoid = Sphere(self.a, center=self.center)
629+
case _:
630+
raise ValueError()
631+
return ellipsoid
632+
633+
def test_prolate(self):
634+
ellipsoid = self.build_ellipsoid("prolate")
635+
expected = (
636+
"ProlateEllipsoid:"
637+
"\n • a: 3.0 m"
638+
"\n • b: 2.0 m"
639+
"\n • c: 2.0 m"
640+
"\n • center: (43.0, -72.0, 105.0) m"
641+
"\n • yaw: 73.0"
642+
"\n • pitch: 14.0"
643+
"\n • roll: 0.0"
644+
)
645+
assert expected == str(ellipsoid)
646+
647+
def test_oblate(self):
648+
ellipsoid = self.build_ellipsoid("oblate")
649+
expected = (
650+
"OblateEllipsoid:"
651+
"\n • a: 2.0 m"
652+
"\n • b: 3.0 m"
653+
"\n • c: 3.0 m"
654+
"\n • center: (43.0, -72.0, 105.0) m"
655+
"\n • yaw: 73.0"
656+
"\n • pitch: 14.0"
657+
"\n • roll: 0.0"
658+
)
659+
assert expected == str(ellipsoid)
660+
661+
def test_triaxial(self):
662+
ellipsoid = self.build_ellipsoid("triaxial")
663+
expected = (
664+
"TriaxialEllipsoid:"
665+
"\n • a: 3.0 m"
666+
"\n • b: 2.0 m"
667+
"\n • c: 1.0 m"
668+
"\n • center: (43.0, -72.0, 105.0) m"
669+
"\n • yaw: 73.0"
670+
"\n • pitch: 14.0"
671+
"\n • roll: -35.0"
672+
)
673+
assert expected == str(ellipsoid)
674+
675+
def test_sphere(self):
676+
ellipsoid = self.build_ellipsoid("sphere")
677+
expected = (
678+
"Sphere:"
679+
"\n • a: 3.0 m"
680+
"\n • b: 3.0 m"
681+
"\n • c: 3.0 m"
682+
"\n • center: (43.0, -72.0, 105.0) m"
683+
"\n • yaw: 0.0"
684+
"\n • pitch: 0.0"
685+
"\n • roll: 0.0"
686+
)
687+
assert expected == str(ellipsoid)
688+
689+
@pytest.mark.parametrize(
690+
"ellipsoid_type", ["prolate", "oblate", "sphere", "triaxial"]
691+
)
692+
def test_density(self, ellipsoid_type):
693+
ellipsoid = self.build_ellipsoid(ellipsoid_type)
694+
ellipsoid.density = -400
695+
(density_line,) = [
696+
line for line in str(ellipsoid).split("\n") if "density" in line
697+
]
698+
expected_line = " • density: -400.0 kg/m³"
699+
assert density_line == expected_line
700+
701+
@pytest.mark.parametrize(
702+
"ellipsoid_type", ["prolate", "oblate", "sphere", "triaxial"]
703+
)
704+
def test_susceptibility(self, ellipsoid_type):
705+
ellipsoid = self.build_ellipsoid(ellipsoid_type)
706+
ellipsoid.susceptibility = 0.3
707+
(sus_line,) = [
708+
line for line in str(ellipsoid).splitlines() if "susceptibility" in line
709+
]
710+
expected_line = " • susceptibility: 0.3"
711+
assert sus_line == expected_line
712+
713+
@pytest.mark.parametrize(
714+
"ellipsoid_type", ["prolate", "oblate", "sphere", "triaxial"]
715+
)
716+
def test_remanent_mag(self, ellipsoid_type):
717+
ellipsoid = self.build_ellipsoid(ellipsoid_type)
718+
ellipsoid.remanent_mag = (12, -43, 59)
719+
(rem_line,) = [
720+
line for line in str(ellipsoid).splitlines() if "remanent_mag" in line
721+
]
722+
expected_line = " • remanent_mag: (12.0, -43.0, 59.0) A/m"
723+
assert rem_line == expected_line
724+
725+
@pytest.mark.parametrize(
726+
"ellipsoid_type", ["prolate", "oblate", "sphere", "triaxial"]
727+
)
728+
def test_susceptibility_tensor(self, ellipsoid_type):
729+
ellipsoid = self.build_ellipsoid(ellipsoid_type)
730+
sus = np.array([[1, 0, 0], [0, 2, 0], [0, 0, 3]])
731+
ellipsoid.susceptibility = sus
732+
# Grab the susceptibility tensor lines
733+
matrix_lines, record = [], False
734+
for line in str(ellipsoid).splitlines():
735+
if "susceptibility" in line:
736+
record = True
737+
continue
738+
if record:
739+
matrix_lines.append(line)
740+
if len(matrix_lines) == 3:
741+
break
742+
743+
expected = [8 * " " + line for line in str(sus).splitlines()]
744+
assert matrix_lines == expected

0 commit comments

Comments
 (0)