Coverage for python/src/dolfinx_mpc/container.py: 74%

61 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-07 09:58 +0000

1import warnings 

2from typing import Callable, Optional, Union 

3 

4import dolfinx 

5import numpy 

6import numpy.typing as npt 

7 

8import dolfinx_mpc.cpp.mpc 

9 

10_mpc_data_classes = Union[ 

11 dolfinx_mpc.cpp.mpc.mpc_data_double, 

12 dolfinx_mpc.cpp.mpc.mpc_data_float, 

13 dolfinx_mpc.cpp.mpc.mpc_data_complex_double, 

14 dolfinx_mpc.cpp.mpc.mpc_data_complex_float, 

15] 

16_float_array_types = Union[ 

17 npt.NDArray[numpy.float32], 

18 npt.NDArray[numpy.float64], 

19 npt.NDArray[numpy.complex64], 

20 npt.NDArray[numpy.complex128], 

21] 

22 

23_mpc_classes = Union[ 

24 dolfinx_mpc.cpp.mpc.MultiPointConstraint_double, 

25 dolfinx_mpc.cpp.mpc.MultiPointConstraint_float, 

26 dolfinx_mpc.cpp.mpc.MultiPointConstraint_complex_double, 

27 dolfinx_mpc.cpp.mpc.MultiPointConstraint_complex_float, 

28] 

29_float_classes = Union[numpy.float32, numpy.float64, numpy.complex128, numpy.complex64] 

30 

31_type_names = { 

32 numpy.float32: "float", 

33 numpy.float64: "double", 

34 numpy.complex64: "complex_float", 

35 numpy.complex128: "complex_double", 

36} 

37 

38 

39class _Unset: 

40 """The default of a deprecated argument, to tell whether it was passed.""" 

41 

42 def __repr__(self) -> str: 

43 return "<unset>" 

44 

45 

46_UNSET = _Unset() 

47 

48 

49def _deprecated(old: str, new: str, stacklevel: int = 3): 

50 """Warn that the argument `old` is deprecated in favour of `new`, at the caller of the public 

51 function `stacklevel - 2` frames up.""" 

52 warnings.warn(f"`{old}` is deprecated, use {new} instead.", DeprecationWarning, stacklevel=stacklevel) 

53 

54 

55def _default_tolerance(dtype: npt.DTypeLike) -> float: 

56 """The default distance and coefficient tolerance: 500 machine epsilon of the real type of `dtype`. 

57 

58 Mirrors `dolfinx_mpc::default_tolerance` in C++. 

59 """ 

60 return float(500 * numpy.finfo(dtype).eps) 

61 

62 

63def _tolerance(value: Optional[float], dtype: npt.DTypeLike) -> float: 

64 """`value` as a Python float, by default :func:`_default_tolerance` of `dtype`.""" 

65 return _default_tolerance(dtype) if value is None else float(value) 

66 

67 

68def _cpp_function(name: str, dtype: npt.DTypeLike) -> Callable: 

69 """The C++ function `name` for constraints of scalar type `dtype`, bound as `name_<type>`.""" 

70 return getattr(dolfinx_mpc.cpp.mpc, f"{name}_{_type_names[numpy.dtype(dtype).type]}") 

71 

72 

73def _scalar_type(real_type: npt.DTypeLike, dtype: npt.DTypeLike | None = None) -> type: 

74 """The scalar type of a constraint on a mesh with coordinates of `real_type`. 

75 

76 Args: 

77 real_type: The type of the mesh coordinates 

78 dtype: The scalar type asked for. Defaults to the default scalar type of DOLFINx, real or 

79 complex, at the precision of the mesh. 

80 

81 Raises: 

82 ValueError: If `dtype` is not one of float32, float64, complex64 and complex128, or its 

83 precision differs from the mesh's. 

84 """ 

85 real = numpy.dtype(real_type) 

86 if dtype is None: 

87 is_complex = numpy.issubdtype(dolfinx.default_scalar_type, numpy.complexfloating) 

88 dtype = numpy.promote_types(real, numpy.complex64) if is_complex else real 

89 scalar = numpy.dtype(dtype) 

90 if scalar.type not in (numpy.float32, numpy.float64, numpy.complex64, numpy.complex128): 

91 raise ValueError(f"Unsupported scalar type {scalar} for a constraint") 

92 if numpy.finfo(scalar).dtype != real: 

93 raise ValueError( 

94 f"A constraint of scalar type {scalar} needs a mesh of {numpy.finfo(scalar).dtype}, not {real}" 

95 ) 

96 return scalar.type 

97 

98 

99class MPCData: 

100 _cpp_object: _mpc_data_classes 

101 

102 def __init__( 

103 self, 

104 slaves: npt.NDArray[numpy.int32], 

105 masters: npt.NDArray[numpy.int64], 

106 coeffs: _float_array_types, 

107 owners: npt.NDArray[numpy.int32], 

108 offsets: npt.NDArray[numpy.int32], 

109 ): 

110 if coeffs.dtype.type == numpy.float32: 

111 self._cpp_object = dolfinx_mpc.cpp.mpc.mpc_data_float(slaves, masters, coeffs, owners, offsets) 

112 elif coeffs.dtype.type == numpy.float64: 

113 self._cpp_object = dolfinx_mpc.cpp.mpc.mpc_data_double(slaves, masters, coeffs, owners, offsets) 

114 elif coeffs.dtype.type == numpy.complex64: 

115 self._cpp_object = dolfinx_mpc.cpp.mpc.mpc_data_complex_float(slaves, masters, coeffs, owners, offsets) 

116 elif coeffs.dtype.type == numpy.complex128: 

117 self._cpp_object = dolfinx_mpc.cpp.mpc.mpc_data_complex_double(slaves, masters, coeffs, owners, offsets) 

118 else: 

119 raise ValueError("Unsupported dtype {coeffs.dtype.type} for coefficients") 

120 

121 @property 

122 def slaves(self): 

123 return self._cpp_object.slaves 

124 

125 @property 

126 def masters(self): 

127 return self._cpp_object.masters 

128 

129 @property 

130 def coeffs(self): 

131 return self._cpp_object.coeffs 

132 

133 @property 

134 def owners(self): 

135 return self._cpp_object.owners 

136 

137 @property 

138 def offsets(self): 

139 return self._cpp_object.offsets