| # Copyright 2019 Google LLC | |
| # | |
| # Licensed under the Apache License, Version 2.0 (the "License"); | |
| # you may not use this file except in compliance with the License. | |
| # You may obtain a copy of the License at | |
| # | |
| # https://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| from absl import app | |
| from iree.tf.support import tf_test_utils | |
| import tensorflow.compat.v2 as tf | |
| def complex_add(a_re, a_im, b_re, b_im): | |
| return a_re + b_re, a_im + b_im | |
| def complex_mul(a_re, a_im, b_re, b_im): | |
| c_re = a_re * b_re - a_im * b_im | |
| c_im = a_re * b_im + a_im * b_re | |
| return c_re, c_im | |
| # This is a fun but quite interesting example because the return value and most | |
| # of the interior computations are dynamically shaped. | |
| class MandelbrotModule(tf.Module): | |
| @tf.function(input_signature=[ | |
| tf.TensorSpec([], tf.float32), | |
| tf.TensorSpec([], tf.float32), | |
| tf.TensorSpec([], tf.float32), | |
| tf.TensorSpec([], tf.int32), | |
| tf.TensorSpec([], tf.int32) | |
| ]) | |
| def calculate(self, center_re, center_im, view_size, view_pixels, | |
| num_iterations): | |
| """Calculates an image which represents the Mandelbrot set. | |
| Args: | |
| center_re: The center point of the view (real part). | |
| center_im: The center point of the view (imaginary part). | |
| view_size: The view will display a square with this size. | |
| view_pixels: The returned image will be a square with this many pixels on | |
| a side. | |
| num_iterations: The number of iterations to use for determining escape. | |
| Returns: | |
| A tensor of pixels with shape [view_size, view_size] which represents | |
| the mandelbrot set. | |
| """ | |
| re_min = center_re - view_size / 2. | |
| re_max = center_re + view_size / 2. | |
| im_min = center_im - view_size / 2. | |
| im_max = center_im + view_size / 2. | |
| re_coords = tf.linspace(re_min, re_max, view_pixels) | |
| im_coords = tf.linspace(im_min, im_max, view_pixels) | |
| # Generate flat list of real and imaginary parts of the points to test. | |
| # This requires taking all pairs of re_coords and im_coords, which we | |
| # do by broadcasting into a 2d matrix (real part is broadcasted "vertically" | |
| # and imaginary part is broadcasted "horizontally"). | |
| # We use a Nx1 * 1xN -> NxN matmul to do the broadcast. | |
| c_re = tf.reshape( | |
| tf.matmul(tf.ones([view_pixels, 1]), | |
| tf.reshape(re_coords, [1, view_pixels])), [-1]) | |
| c_im = tf.reshape( | |
| tf.matmul(tf.reshape(im_coords, [view_pixels, 1]), | |
| tf.ones([1, view_pixels])), [-1]) | |
| z_re = tf.zeros_like(c_re) | |
| z_im = tf.zeros_like(c_im) | |
| for _ in range(num_iterations): | |
| square_re, square_im = complex_mul(z_re, z_im, z_re, z_im) | |
| z_re, z_im = complex_add(square_re, square_im, c_re, c_im) | |
| # Calculate if the points are in the set (that is, if their orbit under the | |
| # recurrence relationship has diverged). | |
| z_abs = tf.sqrt(z_re**2 + z_im**2) | |
| z_abs = tf.where(tf.math.is_nan(z_abs), 100. * tf.ones_like(z_abs), z_abs) | |
| in_the_set = tf.where(z_abs > 50., tf.ones_like(z_abs), | |
| tf.zeros_like(z_abs)) | |
| # Return an image | |
| return tf.reshape(in_the_set, shape=[view_pixels, view_pixels]) | |
| class MandelbrotTest(tf_test_utils.TracedModuleTestCase): | |
| def __init__(self, *args, **kwargs): | |
| super().__init__(*args, **kwargs) | |
| self._modules = tf_test_utils.compile_tf_module(MandelbrotModule) | |
| def test_mandelbrot(self): | |
| def mandelbrot(module): | |
| # Basic view of the entire set. | |
| module.calculate(-0.7, 0.0, 3.0, 400, 100) | |
| # This is a much more detailed view, so more iterations are needed. | |
| module.calculate(-0.7436447860, 0.1318252536, 0.0000029336, 400, 3000) | |
| self.compare_backends(mandelbrot, self._modules) | |
| def main(argv): | |
| del argv # Unused | |
| if hasattr(tf, 'enable_v2_behavior'): | |
| tf.enable_v2_behavior() | |
| tf.test.main() | |
| if __name__ == '__main__': | |
| app.run(main) |