mphkit 0.1.0__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.
- mphkit/__init__.py +23 -0
- mphkit/_comsol.py +719 -0
- mphkit/_expr.py +37 -0
- mphkit/_measure.py +102 -0
- mphkit/_props.py +62 -0
- mphkit/_sel.py +487 -0
- mphkit/errors.py +5 -0
- mphkit/geometry.py +537 -0
- mphkit/py.typed +0 -0
- mphkit/sel.py +37 -0
- mphkit-0.1.0.dist-info/METADATA +174 -0
- mphkit-0.1.0.dist-info/RECORD +14 -0
- mphkit-0.1.0.dist-info/WHEEL +4 -0
- mphkit-0.1.0.dist-info/licenses/LICENSE +21 -0
mphkit/_expr.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Conversion of Python values to COMSOL expression strings."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from collections.abc import Sequence
|
|
5
|
+
from numbers import Real
|
|
6
|
+
|
|
7
|
+
Expr = str | Real
|
|
8
|
+
Vector = Sequence[Expr]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def expr(value: Expr) -> str:
|
|
12
|
+
"""Return `value` as a COMSOL expression string.
|
|
13
|
+
|
|
14
|
+
Numbers are written in plain form (interpreted in the geometry's length
|
|
15
|
+
unit); strings are passed through, so `'5[mm]'` or `'r0/2'` work as-is.
|
|
16
|
+
"""
|
|
17
|
+
if isinstance(value, bool):
|
|
18
|
+
raise TypeError(f'Expected a number or expression, got {value!r}.')
|
|
19
|
+
if isinstance(value, str):
|
|
20
|
+
return value
|
|
21
|
+
if isinstance(value, Real):
|
|
22
|
+
return repr(float(value)) if not float(value).is_integer() else str(int(value))
|
|
23
|
+
raise TypeError(f'Expected a number or expression, got {value!r}.')
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def vector(values: Vector, length: int | None = None) -> list[str]:
|
|
27
|
+
"""Return a sequence of numbers/expressions as a list of strings.
|
|
28
|
+
|
|
29
|
+
MPh casts a list based on its first item, so a mixed list such as
|
|
30
|
+
`[0, 'x1', 0]` fails there. Converting every item to a string avoids that.
|
|
31
|
+
"""
|
|
32
|
+
if isinstance(values, str):
|
|
33
|
+
raise TypeError(f'Expected a sequence, got the string {values!r}.')
|
|
34
|
+
items = [expr(v) for v in values]
|
|
35
|
+
if length is not None and len(items) != length:
|
|
36
|
+
raise ValueError(f'Expected {length} components, got {len(items)}.')
|
|
37
|
+
return items
|
mphkit/_measure.py
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Measurements of the finished geometry. Public as `mk.measure` and
|
|
3
|
+
`mk.bounding_box`.
|
|
4
|
+
|
|
5
|
+
Both return plain numbers in the geometry's length unit and leave nothing
|
|
6
|
+
in the model. Curved geometry is measured on a rendering mesh, so volumes,
|
|
7
|
+
areas and lengths of curved entities are approximate (a cylinder about
|
|
8
|
+
0.3 % too small, a sphere 0.3 to 0.6 % depending on the geometry kernel);
|
|
9
|
+
planar geometry is exact.
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import numbers
|
|
14
|
+
|
|
15
|
+
import numpy
|
|
16
|
+
from mph import Node
|
|
17
|
+
|
|
18
|
+
from . import _comsol
|
|
19
|
+
|
|
20
|
+
AXES = 'xyz'
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _final(geom: Node, entity: str, selection):
|
|
24
|
+
"""
|
|
25
|
+
Returns a measurement of the finished geometry, or `None` if empty.
|
|
26
|
+
|
|
27
|
+
`selection` is an entity number, a list of numbers, a selection node at
|
|
28
|
+
the level of `entity`, or `None` for all entities of that level.
|
|
29
|
+
"""
|
|
30
|
+
_comsol.check_built(geom)
|
|
31
|
+
dim = _comsol.entity_dim(geom, entity)
|
|
32
|
+
count = _comsol.entity_count(geom, dim)
|
|
33
|
+
if selection is None:
|
|
34
|
+
found = list(range(1, count + 1))
|
|
35
|
+
elif isinstance(selection, Node):
|
|
36
|
+
java = _comsol.check_selection(geom, selection)
|
|
37
|
+
level = [int(d) for d in java.dimension()]
|
|
38
|
+
if level != [dim]:
|
|
39
|
+
raise ValueError(f'Selection "{selection}" is not a {entity} '
|
|
40
|
+
'selection.')
|
|
41
|
+
found = [int(e) for e in java.entities()]
|
|
42
|
+
else:
|
|
43
|
+
items = selection if isinstance(
|
|
44
|
+
selection, (list, tuple, numpy.ndarray)) else [selection]
|
|
45
|
+
found = []
|
|
46
|
+
for item in items:
|
|
47
|
+
if isinstance(item, bool) or not isinstance(item, numbers.Integral):
|
|
48
|
+
raise TypeError(f'Expected entity numbers, a selection node '
|
|
49
|
+
f'or None, not {selection!r}.')
|
|
50
|
+
found.append(int(item))
|
|
51
|
+
wrong = [n for n in found if not 1 <= n <= count]
|
|
52
|
+
if wrong:
|
|
53
|
+
raise ValueError(f'No {entity} {wrong} in geometry "{geom}"; it '
|
|
54
|
+
f'has {count} {entity} entities.')
|
|
55
|
+
if not found:
|
|
56
|
+
return None
|
|
57
|
+
measurement = geom.java.measureFinal()
|
|
58
|
+
measurement.selection().geom(geom.tag(), dim)
|
|
59
|
+
measurement.selection().set(found)
|
|
60
|
+
return measurement
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def measure(geom: Node, entity: str, /, selection=None) -> float:
|
|
64
|
+
"""
|
|
65
|
+
Returns the size of entities: volume, area or length.
|
|
66
|
+
|
|
67
|
+
What is measured follows the level of `entity`: the volume of domains
|
|
68
|
+
(the area of 2D domains), the area of boundaries, the length of edges.
|
|
69
|
+
`selection` is an entity number, a list of them, a selection node, or
|
|
70
|
+
`None` for all entities of that kind. Several entities give the sum; a
|
|
71
|
+
number given twice counts once. An empty selection gives 0.
|
|
72
|
+
|
|
73
|
+
The geometry must be built. Curved entities are measured on a
|
|
74
|
+
rendering mesh and are approximate (a cylinder about 0.3 % too small);
|
|
75
|
+
planar ones are exact.
|
|
76
|
+
"""
|
|
77
|
+
_comsol.check_not_workplane(geom, 'measure')
|
|
78
|
+
if _comsol.entity_dim(geom, entity) == 0:
|
|
79
|
+
raise ValueError('Points have no size; use bounding_box() for their '
|
|
80
|
+
'coordinates.')
|
|
81
|
+
measurement = _final(geom, entity, selection)
|
|
82
|
+
return 0.0 if measurement is None else float(measurement.getVolume())
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def bounding_box(geom: Node, entity: str, /, selection=None) -> dict | None:
|
|
86
|
+
"""
|
|
87
|
+
Returns the bounding box of entities as `{'x': (min, max), ...}`.
|
|
88
|
+
|
|
89
|
+
Has one pair per space dimension, so it can be passed on as
|
|
90
|
+
`sel.box(geom, entity, **box)`. Allow a margin then: the values may
|
|
91
|
+
carry single-precision noise, which makes `condition='inside'` miss
|
|
92
|
+
the entity itself. For a single point, min equals max: its
|
|
93
|
+
coordinates. `selection` works as in `measure()`. Returns `None` for
|
|
94
|
+
an empty selection.
|
|
95
|
+
"""
|
|
96
|
+
_comsol.check_not_workplane(geom, 'bounding_box')
|
|
97
|
+
measurement = _final(geom, entity, selection)
|
|
98
|
+
if measurement is None:
|
|
99
|
+
return None
|
|
100
|
+
values = [float(v) for v in measurement.getBoundingBox()]
|
|
101
|
+
return {axis: (values[2*i], values[2*i + 1])
|
|
102
|
+
for i, axis in enumerate(AXES[:len(values) // 2])}
|
mphkit/_props.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Setting properties of any COMSOL object. Public as `mk.set`.
|
|
3
|
+
|
|
4
|
+
Calling a Java `set()` directly fails for plain Python ints ("Ambiguous
|
|
5
|
+
overloads"), numpy arrays and lists that mix numbers and expressions;
|
|
6
|
+
`mk.set` converts them the same way the other helpers do.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from mph import Node
|
|
11
|
+
from mph.node import join
|
|
12
|
+
|
|
13
|
+
from . import _comsol
|
|
14
|
+
from ._comsol import WorkPlaneNode
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def set_(target, /, **properties):
|
|
18
|
+
"""
|
|
19
|
+
Sets properties of an MPh node or a Java object, and returns it.
|
|
20
|
+
|
|
21
|
+
For example on a mesh size feature,
|
|
22
|
+
`mk.set(size, custom=True, hmax='L/10', hgrad=1.45)`. The target may be
|
|
23
|
+
any MPh node, or a Java object MPh does not reach, such as a probe, a
|
|
24
|
+
material function or `physics.java.prop('ShapeProperty')`. Keyword
|
|
25
|
+
arguments are COMSOL property names, set in the given order (e.g.
|
|
26
|
+
`custom` before `hmax`); `None` values are skipped.
|
|
27
|
+
|
|
28
|
+
Ints, numpy arrays and lists mixing numbers and expressions are
|
|
29
|
+
converted for COMSOL; lists of numbers become string arrays. Unknown
|
|
30
|
+
names raise `ValueError` with a suggestion, invalid choices list the
|
|
31
|
+
allowed values. If one property fails, the ones before it stay set.
|
|
32
|
+
|
|
33
|
+
A geometry feature node also takes input selections (`input`,
|
|
34
|
+
`input2`) as in `feature()`; build the geometry again afterwards. To
|
|
35
|
+
turn on a result selection use `sel.result()` rather than `selresult`,
|
|
36
|
+
because it checks for label clashes. On variables
|
|
37
|
+
(`component.variable()`) any value is taken as an expression, and
|
|
38
|
+
`True` becomes 1.
|
|
39
|
+
"""
|
|
40
|
+
owner = container = None
|
|
41
|
+
if isinstance(target, Node):
|
|
42
|
+
if (len(target.path) >= 4 and target.path[0] == 'geometries'
|
|
43
|
+
and not isinstance(target, WorkPlaneNode)):
|
|
44
|
+
# A plain node cannot resolve features inside a work plane.
|
|
45
|
+
target = WorkPlaneNode(target.model, join(target.path))
|
|
46
|
+
java = target.java_if_exists()
|
|
47
|
+
if len(target.path) >= 3 and target.path[0] == 'geometries':
|
|
48
|
+
owner = target.parent()
|
|
49
|
+
container = _comsol.feature_container(owner)
|
|
50
|
+
elif hasattr(target, 'set'):
|
|
51
|
+
java = target
|
|
52
|
+
for key, value in properties.items():
|
|
53
|
+
if value is not None and _comsol.is_selection_input(java, key):
|
|
54
|
+
raise TypeError(
|
|
55
|
+
f'"{key}" is an input selection; use '
|
|
56
|
+
f'java.selection("{key}"), or pass a geometry feature '
|
|
57
|
+
'as an MPh node.')
|
|
58
|
+
else:
|
|
59
|
+
raise TypeError(f'Cannot set properties of {target!r}; expected an '
|
|
60
|
+
'MPh node or a COMSOL Java object.')
|
|
61
|
+
_comsol.set_properties(java, properties, owner, container)
|
|
62
|
+
return target
|