multidimensional.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. """
  2. Provides functionality for multidimensional usage of scalar-functions.
  3. Read the vectorize docstring for more details.
  4. """
  5. from functools import wraps
  6. def apply_on_element(f, args, kwargs, n):
  7. """
  8. Returns a structure with the same dimension as the specified argument,
  9. where each basic element is replaced by the function f applied on it. All
  10. other arguments stay the same.
  11. """
  12. # Get the specified argument.
  13. if isinstance(n, int):
  14. structure = args[n]
  15. is_arg = True
  16. elif isinstance(n, str):
  17. structure = kwargs[n]
  18. is_arg = False
  19. # Define reduced function that is only dependent on the specified argument.
  20. def f_reduced(x):
  21. if hasattr(x, "__iter__"):
  22. return list(map(f_reduced, x))
  23. else:
  24. if is_arg:
  25. args[n] = x
  26. else:
  27. kwargs[n] = x
  28. return f(*args, **kwargs)
  29. # f_reduced will call itself recursively so that in the end f is applied to
  30. # all basic elements.
  31. return list(map(f_reduced, structure))
  32. def iter_copy(structure):
  33. """
  34. Returns a copy of an iterable object (also copying all embedded iterables).
  35. """
  36. return [iter_copy(i) if hasattr(i, "__iter__") else i for i in structure]
  37. def structure_copy(structure):
  38. """
  39. Returns a copy of the given structure (numpy-array, list, iterable, ..).
  40. """
  41. if hasattr(structure, "copy"):
  42. return structure.copy()
  43. return iter_copy(structure)
  44. class vectorize:
  45. """
  46. Generalizes a function taking scalars to accept multidimensional arguments.
  47. Examples
  48. ========
  49. >>> from sympy import vectorize, diff, sin, symbols, Function
  50. >>> x, y, z = symbols('x y z')
  51. >>> f, g, h = list(map(Function, 'fgh'))
  52. >>> @vectorize(0)
  53. ... def vsin(x):
  54. ... return sin(x)
  55. >>> vsin([1, x, y])
  56. [sin(1), sin(x), sin(y)]
  57. >>> @vectorize(0, 1)
  58. ... def vdiff(f, y):
  59. ... return diff(f, y)
  60. >>> vdiff([f(x, y, z), g(x, y, z), h(x, y, z)], [x, y, z])
  61. [[Derivative(f(x, y, z), x), Derivative(f(x, y, z), y), Derivative(f(x, y, z), z)], [Derivative(g(x, y, z), x), Derivative(g(x, y, z), y), Derivative(g(x, y, z), z)], [Derivative(h(x, y, z), x), Derivative(h(x, y, z), y), Derivative(h(x, y, z), z)]]
  62. """
  63. def __init__(self, *mdargs):
  64. """
  65. The given numbers and strings characterize the arguments that will be
  66. treated as data structures, where the decorated function will be applied
  67. to every single element.
  68. If no argument is given, everything is treated multidimensional.
  69. """
  70. for a in mdargs:
  71. if not isinstance(a, (int, str)):
  72. raise TypeError("a is of invalid type")
  73. self.mdargs = mdargs
  74. def __call__(self, f):
  75. """
  76. Returns a wrapper for the one-dimensional function that can handle
  77. multidimensional arguments.
  78. """
  79. @wraps(f)
  80. def wrapper(*args, **kwargs):
  81. # Get arguments that should be treated multidimensional
  82. if self.mdargs:
  83. mdargs = self.mdargs
  84. else:
  85. mdargs = range(len(args)) + kwargs.keys()
  86. arglength = len(args)
  87. for n in mdargs:
  88. if isinstance(n, int):
  89. if n >= arglength:
  90. continue
  91. entry = args[n]
  92. is_arg = True
  93. elif isinstance(n, str):
  94. try:
  95. entry = kwargs[n]
  96. except KeyError:
  97. continue
  98. is_arg = False
  99. if hasattr(entry, "__iter__"):
  100. # Create now a copy of the given array and manipulate then
  101. # the entries directly.
  102. if is_arg:
  103. args = list(args)
  104. args[n] = structure_copy(entry)
  105. else:
  106. kwargs[n] = structure_copy(entry)
  107. result = apply_on_element(wrapper, args, kwargs, n)
  108. return result
  109. return f(*args, **kwargs)
  110. return wrapper