bilinear.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. #############################################################################
  2. # Copyright (c) 2013 by Panagiotis Mavrogiorgos
  3. # All rights reserved.
  4. #
  5. # Redistribution and use in source and binary forms, with or without
  6. # modification, are permitted provided that the following conditions are met:
  7. #
  8. # * Redistributions of source code must retain the above copyright notice,
  9. # this list of conditions and the following disclaimer.
  10. # * Redistributions in binary form must reproduce the above copyright notice,
  11. # this list of conditions and the following disclaimer in the documentation
  12. # and/or other materials provided with the distribution.
  13. # * Neither the name(s) of the copyright holders nor the names of its
  14. # contributors may be used to endorse or promote products derived from this
  15. # software without specific prior written permission.
  16. #
  17. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AS IS AND ANY EXPRESS OR
  18. # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  19. # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
  20. # EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,
  21. # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  22. # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
  23. # OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  24. # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  25. # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
  26. # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  27. #############################################################################
  28. #
  29. # @license: http://opensource.org/licenses/BSD-3-Clause
  30. from bisect import bisect_left
  31. import logging
  32. log = logging.getLogger('base')
  33. class BilinearInterpolation(object):
  34. """
  35. Bilinear interpolation with optional extrapolation.
  36. Usage:
  37. table = BilinearInterpolation(
  38. x_index=(1, 2, 3),
  39. y_index=(1, 2, 3),
  40. values=((110, 120, 130),
  41. (210, 220, 230),
  42. (310, 320, 330)),
  43. extrapolate=True)
  44. assert table(1, 1) == 110
  45. assert table(2.5, 2.5) == 275
  46. """
  47. def __init__(self, x_index, y_index, values):
  48. # sanity check
  49. x_length = len(x_index)
  50. y_length = len(y_index)
  51. if x_length < 2 or y_length < 2:
  52. raise ValueError("Table must be at least 2x2.")
  53. if y_length != len(values):
  54. raise ValueError("Table must have equal number of rows to y_index.")
  55. if any(x2 - x1 <= 0 for x1, x2 in zip(x_index, x_index[1:])):
  56. raise ValueError("x_index must be in strictly ascending order!")
  57. if any(y2 - y1 <= 0 for y1, y2 in zip(y_index, y_index[1:])):
  58. raise ValueError("y_index must be in strictly ascending order!")
  59. self.x_index = x_index
  60. self.y_index = y_index
  61. self.values = values
  62. self.x_length = x_length
  63. self.y_length = y_length
  64. self.extrapolate = True
  65. #slopes = self.slopes = []
  66. #for j in range(y_length):
  67. #intervals = zip(x_index, x_index[1:], values[j], values[j][1:])
  68. #slopes.append([(y2 - y1) / (x2 - x1) for x1, x2, y1, y2 in intervals])
  69. def __call__(self, x, y):
  70. # local lookups
  71. x_index, y_index, values = self.x_index, self.y_index, self.values
  72. i = bisect_left(x_index, x) - 1
  73. j = bisect_left(y_index, y) - 1
  74. if self.extrapolate:
  75. # fix x index
  76. if i == -1:
  77. x_slice = slice(None, 2)
  78. elif i == self.x_length - 1:
  79. x_slice = slice(-2, None)
  80. else:
  81. x_slice = slice(i, i + 2)
  82. # fix y index
  83. if j == -1:
  84. j = 0
  85. y_slice = slice(None, 2)
  86. elif j == self.y_length - 1:
  87. j = -2
  88. y_slice = slice(-2, None)
  89. else:
  90. y_slice = slice(j, j + 2)
  91. else:
  92. if i == -1 or i == self.x_length - 1:
  93. raise ValueError("Extrapolation not allowed!")
  94. if j == -1 or j == self.y_length - 1:
  95. raise ValueError("Extrapolation not allowed!")
  96. # if the extrapolations is False this will fail
  97. x1, x2 = x_index[x_slice]
  98. y1, y2 = y_index[y_slice]
  99. z11, z12 = values[j][x_slice]
  100. z21, z22 = values[j + 1][x_slice]
  101. return (z11 * (x2 - x) * (y2 - y) +
  102. z21 * (x - x1) * (y2 - y) +
  103. z12 * (x2 - x) * (y - y1) +
  104. z22 * (x - x1) * (y - y1)) / ((x2 - x1) * (y2 - y1))