game_qu.math.piecewise_function API documentation

# Module `game_qu.math.piecewise_function`

Expand source code

    from copy import deepcopy

    from game_qu.math.derivative import Derivative
    from game_qu.math.function import Function
    from game_qu.base.utility_functions import modified_mod, is_within_bounds, min_value
    from game_qu.math.indefinite_integral import IndefiniteIntegral

    class PiecewiseFunction(Function):
        """ A function that is made up of multiple functions. The function uses different functions depending on what the x
            coordinate is and if it falls within the other function"""

        functions = []
        is_repeating = False
        x_coordinates_can_be_less_than_min = True
        min_x_coordinate = 0
        max_x_coordinate = 0

        def __init__(self, functions):
            """Initializes the object with the functions"""

            self.set_functions(functions)

        def get_functions(self):
            """
                 Returns:
                    BoundedFunction: all the functions of the piecewise function"""

            return self.functions

        def set_functions(self, functions):
            """Sets the functions of the piecewise function

                 Returns:
                    PiecewiseFunction: 'self'"""

            self.functions = functions
            self.update_function_bounds_fully()
            return self

        def get_piecewise_function_with_functions(self, functions):
            """
                 Returns:
                    PiecewiseFunction: a piecewise function with functions attribute being set to 'functions' (does not modify the current piecewise function)"""

            return self.get_copy().set_functions(functions)

        def get_copy(self):
            """
                 Returns:
                    PiecewiseFunction: a copy of the current piecewise_function"""

            # return deepcopy(self)
            new_functions = []
            copy = PiecewiseFunction(new_functions)
            for val in self.get_functions():
                new_functions.append(val)

            copy.set_is_repeating(self.is_repeating)
            copy.set_x_coordinates_can_be_less_than_min(self.x_coordinates_can_be_less_than_min)
            return copy

        def get_x_coordinates_can_be_less_than_min(self):
            """
                 Returns:
                    bool: whether the x coordinates this function accepts can be less than the min x coordinate"""

            return self.x_coordinates_can_be_less_than_min

        def set_x_coordinates_can_be_less_than_min(self, x_coordinates_can_be_less_than_min):
            """ Sets whether the x coordinates this function accepts can be less than the min x coordinate

                 Returns:
                    BoundedFunction: 'self'"""

            self.x_coordinates_can_be_less_than_min = x_coordinates_can_be_less_than_min
            return self

        def get_bounded_function_with_x_coordinates_less_than_min(self, x_coordinates_can_be_less_than_min):
            """
                 Returns:
                    PiecewiseFunction: a piecewise function with the is repeating attribute set to 'x_coordinates_can_be_less_than_min' (does not modify the current piecewise function)"""

            return self.get_copy().set_x_coordinates_can_be_less_than_min(x_coordinates_can_be_less_than_min)

        def get_is_repeating(self):
            """
                 Returns:
                    boolean: whether the piecewise function repeats"""

            return self.is_repeating

        def set_is_repeating(self, is_repeating):
            """Sets the is_repeating attribute to 'is_repeating'

                 Returns:
                    PiecewiseFunction: 'self'"""

            self.is_repeating = is_repeating
            return self

        def get_piecewise_function_with_is_repeating(self, is_repeating):
            """
                 Returns:
                    PiecewiseFunction: a piecewise function with the is repeating attribute set to 'is_repeating' (does not modify the current piecewise function)"""

            return self.get_copy().set_is_repeating(is_repeating)

        def update_function_bounds_fully(self):
            """Updates the function bounds for operations like remove (has to check every function in the list)"""

            if len(self.functions) == 0:
                return

            self.functions = sorted(self.functions, key=lambda x: x.get_min_x_coordinate())
            self.min_x_coordinate = self.functions[0].get_min_x_coordinate()

            max_index = len(self.functions) - 1
            self.max_x_coordinate = self.functions[max_index].get_max_x_coordinate()

        def update_function_bounds_for_added_function(self, function):
            """Updates the function bounds when a function is added (do not have to check every function in list)"""

            self.min_x_coordinate = min(self.min_x_coordinate, function.get_min_x_coordinate())
            self.max_x_coordinate = max(self.max_x_coordinate, function.get_max_x_coordinate())

        def validate_function_bounds_do_not_overlap(self):
            """Raises a ValueError if the function bounds overlap (if there are overlapping bounds the piecewise function is invalidated)"""

            for i in range(len(self.functions)):
                for j in range(len(self.functions)):
                    function1 = self.functions[i]
                    function2 = self.functions[j]

                    a, b = function1.get_bounds()
                    c, d = function2.get_bounds()

                    # If the function1's bounds fall within function2's bounds (case 1) or it crosses the bounds of
                    # function2 (case 2) then this is no longer a valid piecewise function.
                    is_invalid = a <= c <= b
                    distance_between_c_and_a = a - c
                    function1_bound_size = b - a
                    is_invalid |= distance_between_c_and_a >= function1_bound_size

                    if is_invalid:
                            raise ValueError("The bounds of each function within the piecewise function must not overlap")

        def clear_functions(self):
            """ Clears all functions from the piecewise function

                Returns:
                    PiecewiseFunction: 'self'"""

            self.functions.clear()
            self.min_x_coordinate = 0
            self.max_x_coordinate = 0
            return self

        def get_piecewise_function_with_cleared_functions(self):
            """
                 Returns a new PiecewiseFunction with cleared functions (does not modify the current piecewise function)

                 Returns:
                    PiecewiseFunction: new piecewise function with cleared functions"""

            return self.get_copy().clear_functions()

        def add_function(self, new_function):
            """ Adds a new function to the piecewise function

                Args:
                    new_function (Function): function to be added

                Returns:
                    PiecewiseFunction: 'self'"""

            self.functions.append(new_function)
            self.validate_function_bounds_do_not_overlap()
            self.update_function_bounds_fully()
            return self

        def get_piecewise_function_with_added_function(self, new_function):
            """
                 Returns: a new PiecewiseFunction with the added function (does not modify the current piecewise function)

                Args:
                    new_function (Function): function to be added

                 Returns:
                    PiecewiseFunction: new piecewise function with the added function"""

            return self.get_copy().add_function(new_function)

        def remove_function_at_index(self, index):
            """ Removes a function at the specified index from the piecewise function

                Args:
                    index (int): index of the function to be removed

                Returns:
                    PiecewiseFunction: 'self'"""

            if 0 <= index < len(self.functions):
                del self.functions[index]

            self.update_function_bounds_fully()
            return self

        def get_piecewise_function_with_function_removed_at_index(self, index):
            """
                 Returns: a new PiecewiseFunction with a function removed at the specified index (does not modify the current piecewise function)
                Args:
                    index (int): index of the function to be removed

                 Returns:
                   PiecewiseFunction: new piecewise function with the function removed at the specified index"""

            return self.get_copy().remove_function_at_index(index)

        def remove_function(self, function):
            """ Removes the specified function from the piecewise function

                Args:
                    function (Function): function to be removed

                Returns:
                    PiecewiseFunction: 'self'"""

            self.functions.remove(function)
            self.update_function_bounds_fully()
            return self

        def get_piecewise_function_with_function_removed(self, function):
            """
                 Returns: a new PiecewiseFunction with the specified function removed (does not modify the current piecewise function)
                Args:
                    function (Function): function to be removed

                 Returns:
                    PiecewiseFunction: new piecewise function with the specified function removed"""

            return self.get_copy().remove_function(function)

        def get_y_coordinate(self, x_coordinate):
            """
                 Returns:
                    float: the y coordinate associated with that x coordinate"""

            return self.get_function(x_coordinate).get_y_coordinate(x_coordinate)

        def get_function(self, x_coordinate):
            """
                 Returns:
                    BoundedFunction: the function that controls the piecewise function at the x coordinate"""

            if len(self.functions) == 0:
                raise ValueError("There must be a function in functions in order for the piecewise function to operate")

            if self.is_repeating:
                x_coordinate = modified_mod(x_coordinate, self.bounds_size())

            if not is_within_bounds(x_coordinate, self.min_x_coordinate, self.max_x_coordinate):
                if not self.x_coordinates_can_be_less_than_min:
                    raise ValueError("The x coordinate must be greater")

                else:
                    return self.get_before_piecewise_function().get_function(x_coordinate)

            for function in self.functions:
                if is_within_bounds(x_coordinate, *function.get_bounds()):
                    return function

            raise ValueError("You should not get this value error! This is an implementation error in the code")

        def get_before_piecewise_function(self):
            """
                 Returns:
                    PiecewiseFunction: a piecewise function that 'comes before' the original"""

            before_piecewise_function = self.get_copy()
            new_x_start = self.min_x_coordinate - self.bounds_size()
            # The relative positions of functions stay the same, so I am just performing a 'domain shift'
            x_position_in_original_function = lambda x: self.get_x_relative_position(new_x_start, x) + self.min_x_coordinate
            before_piecewise_function.get_y_coordinate = lambda x: self.get_y_coordinate(x_position_in_original_function(x))

            return before_piecewise_function

        def get_x_relative_position(self, start, x_coordinate):
            """
                 Returns:
                    float: the distance x is from start (the relative position within the interval [start, end])"""

            return start - x_coordinate

        def get_min_x_coordinate(self):
            """
                 Returns:
                    float: the min x coordinate of the piecewise function (min x coordinate of min bounded function)"""

            return self.min_x_coordinate

        def get_max_x_coordinate(self):
            """
                 Returns:
                    float: the max x coordinate of the piecewise function (max x coordinate of max bounded function)"""

            return self.max_x_coordinate

        def get_bounds(self):
            """
                 Returns:
                    list[float]: {min_x_coordinate, max_x_coordinate}; the bounds of the peicewise function"""

            return [self.min_x_coordinate, self.max_x_coordinate]

        def bounds_size(self):
            """
                 Returns:
                    float: the size of the bounds (max - min)"""

            return self.max_x_coordinate - self.min_x_coordinate

        def get_indefinite_integral(self):
            """ Finds and returns the indefinite integral. Raises an AttributeError if any of the functions do not have the
                method: 'get_indefinite_integral'

                Returns:
                    PiecewiseFunction: the indefinite integral of the polynomial"""

            new_functions = []
            for function in self.functions:
                new_functions.append(function.get_indefinite_integral())

            indefinite_integral = self.get_copy().get_piecewise_function_with_functions(new_functions)
            return indefinite_integral

        def evaluate_area(self, start, end):
            """
                 Returns:
                    float: the value gotten from using the bounds for the indefinite integral"""

            indefinite_integral = self.get_indefinite_integral()
            area = 0
            current_start = start

            for x in range(len(indefinite_integral.get_functions())):
                bounded_function = indefinite_integral.get_functions()[x]
                min_x, max_x = bounded_function.get_bounds()

                if is_within_bounds(current_start, min_x, max_x):
                    end_x = min_value(max_x, end)
                    new_area = bounded_function.get_y_coordinate(end_x) - bounded_function.get_y_coordinate(current_start)
                    area += new_area
                    current_start = end_x

                if current_start == end:
                    break

            return area

        def get_derivative(self):
            """ Finds and returns the derivative. Raises an AttributeError if any of the functions do not have the
                method: 'get_derivative'

                Returns:
                    PiecewiseFunction: the derivative of the polynomial"""

            new_functions = []
            for function in self.functions:
                new_functions.append(function.get_derivative())

            return self.get_piecewise_function_with_functions(new_functions)

        def evaluate_derivative_at_point(self, x_coordinate):
            """
                 Returns:
                    float: the value of the derivative evaluated at the 'x_coordinate'"""

            return self.get_derivative().get_y_coordinate(x_coordinate)

## Classes

`class PiecewiseFunction (functions)`

  A function that is made up of multiple functions. The function uses different functions depending on what the x coordinate is and if it falls within the other function

  Initializes the object with the functions

  Expand source code

      class PiecewiseFunction(Function):
          """ A function that is made up of multiple functions. The function uses different functions depending on what the x
              coordinate is and if it falls within the other function"""

          functions = []
          is_repeating = False
          x_coordinates_can_be_less_than_min = True
          min_x_coordinate = 0
          max_x_coordinate = 0

          def __init__(self, functions):
              """Initializes the object with the functions"""

              self.set_functions(functions)

          def get_functions(self):
              """
                   Returns:
                      BoundedFunction: all the functions of the piecewise function"""

              return self.functions

          def set_functions(self, functions):
              """Sets the functions of the piecewise function

                   Returns:
                      PiecewiseFunction: 'self'"""

              self.functions = functions
              self.update_function_bounds_fully()
              return self

          def get_piecewise_function_with_functions(self, functions):
              """
                   Returns:
                      PiecewiseFunction: a piecewise function with functions attribute being set to 'functions' (does not modify the current piecewise function)"""

              return self.get_copy().set_functions(functions)

          def get_copy(self):
              """
                   Returns:
                      PiecewiseFunction: a copy of the current piecewise_function"""

              # return deepcopy(self)
              new_functions = []
              copy = PiecewiseFunction(new_functions)
              for val in self.get_functions():
                  new_functions.append(val)

              copy.set_is_repeating(self.is_repeating)
              copy.set_x_coordinates_can_be_less_than_min(self.x_coordinates_can_be_less_than_min)
              return copy

          def get_x_coordinates_can_be_less_than_min(self):
              """
                   Returns:
                      bool: whether the x coordinates this function accepts can be less than the min x coordinate"""

              return self.x_coordinates_can_be_less_than_min

          def set_x_coordinates_can_be_less_than_min(self, x_coordinates_can_be_less_than_min):
              """ Sets whether the x coordinates this function accepts can be less than the min x coordinate

                   Returns:
                      BoundedFunction: 'self'"""

              self.x_coordinates_can_be_less_than_min = x_coordinates_can_be_less_than_min
              return self

          def get_bounded_function_with_x_coordinates_less_than_min(self, x_coordinates_can_be_less_than_min):
              """
                   Returns:
                      PiecewiseFunction: a piecewise function with the is repeating attribute set to 'x_coordinates_can_be_less_than_min' (does not modify the current piecewise function)"""

              return self.get_copy().set_x_coordinates_can_be_less_than_min(x_coordinates_can_be_less_than_min)

          def get_is_repeating(self):
              """
                   Returns:
                      boolean: whether the piecewise function repeats"""

              return self.is_repeating

          def set_is_repeating(self, is_repeating):
              """Sets the is_repeating attribute to 'is_repeating'

                   Returns:
                      PiecewiseFunction: 'self'"""

              self.is_repeating = is_repeating
              return self

          def get_piecewise_function_with_is_repeating(self, is_repeating):
              """
                   Returns:
                      PiecewiseFunction: a piecewise function with the is repeating attribute set to 'is_repeating' (does not modify the current piecewise function)"""

              return self.get_copy().set_is_repeating(is_repeating)

          def update_function_bounds_fully(self):
              """Updates the function bounds for operations like remove (has to check every function in the list)"""

              if len(self.functions) == 0:
                  return

              self.functions = sorted(self.functions, key=lambda x: x.get_min_x_coordinate())
              self.min_x_coordinate = self.functions[0].get_min_x_coordinate()

              max_index = len(self.functions) - 1
              self.max_x_coordinate = self.functions[max_index].get_max_x_coordinate()

          def update_function_bounds_for_added_function(self, function):
              """Updates the function bounds when a function is added (do not have to check every function in list)"""

              self.min_x_coordinate = min(self.min_x_coordinate, function.get_min_x_coordinate())
              self.max_x_coordinate = max(self.max_x_coordinate, function.get_max_x_coordinate())

          def validate_function_bounds_do_not_overlap(self):
              """Raises a ValueError if the function bounds overlap (if there are overlapping bounds the piecewise function is invalidated)"""

              for i in range(len(self.functions)):
                  for j in range(len(self.functions)):
                      function1 = self.functions[i]
                      function2 = self.functions[j]

                      a, b = function1.get_bounds()
                      c, d = function2.get_bounds()

                      # If the function1's bounds fall within function2's bounds (case 1) or it crosses the bounds of
                      # function2 (case 2) then this is no longer a valid piecewise function.
                      is_invalid = a <= c <= b
                      distance_between_c_and_a = a - c
                      function1_bound_size = b - a
                      is_invalid |= distance_between_c_and_a >= function1_bound_size

                      if is_invalid:
                              raise ValueError("The bounds of each function within the piecewise function must not overlap")

          def clear_functions(self):
              """ Clears all functions from the piecewise function

                  Returns:
                      PiecewiseFunction: 'self'"""

              self.functions.clear()
              self.min_x_coordinate = 0
              self.max_x_coordinate = 0
              return self

          def get_piecewise_function_with_cleared_functions(self):
              """
                   Returns a new PiecewiseFunction with cleared functions (does not modify the current piecewise function)

                   Returns:
                      PiecewiseFunction: new piecewise function with cleared functions"""

              return self.get_copy().clear_functions()

          def add_function(self, new_function):
              """ Adds a new function to the piecewise function

                  Args:
                      new_function (Function): function to be added

                  Returns:
                      PiecewiseFunction: 'self'"""

              self.functions.append(new_function)
              self.validate_function_bounds_do_not_overlap()
              self.update_function_bounds_fully()
              return self

          def get_piecewise_function_with_added_function(self, new_function):
              """
                   Returns: a new PiecewiseFunction with the added function (does not modify the current piecewise function)

                  Args:
                      new_function (Function): function to be added

                   Returns:
                      PiecewiseFunction: new piecewise function with the added function"""

              return self.get_copy().add_function(new_function)

          def remove_function_at_index(self, index):
              """ Removes a function at the specified index from the piecewise function

                  Args:
                      index (int): index of the function to be removed

                  Returns:
                      PiecewiseFunction: 'self'"""

              if 0 <= index < len(self.functions):
                  del self.functions[index]

              self.update_function_bounds_fully()
              return self

          def get_piecewise_function_with_function_removed_at_index(self, index):
              """
                   Returns: a new PiecewiseFunction with a function removed at the specified index (does not modify the current piecewise function)
                  Args:
                      index (int): index of the function to be removed

                   Returns:
                     PiecewiseFunction: new piecewise function with the function removed at the specified index"""

              return self.get_copy().remove_function_at_index(index)

          def remove_function(self, function):
              """ Removes the specified function from the piecewise function

                  Args:
                      function (Function): function to be removed

                  Returns:
                      PiecewiseFunction: 'self'"""

              self.functions.remove(function)
              self.update_function_bounds_fully()
              return self

          def get_piecewise_function_with_function_removed(self, function):
              """
                   Returns: a new PiecewiseFunction with the specified function removed (does not modify the current piecewise function)
                  Args:
                      function (Function): function to be removed

                   Returns:
                      PiecewiseFunction: new piecewise function with the specified function removed"""

              return self.get_copy().remove_function(function)

          def get_y_coordinate(self, x_coordinate):
              """
                   Returns:
                      float: the y coordinate associated with that x coordinate"""

              return self.get_function(x_coordinate).get_y_coordinate(x_coordinate)

          def get_function(self, x_coordinate):
              """
                   Returns:
                      BoundedFunction: the function that controls the piecewise function at the x coordinate"""

              if len(self.functions) == 0:
                  raise ValueError("There must be a function in functions in order for the piecewise function to operate")

              if self.is_repeating:
                  x_coordinate = modified_mod(x_coordinate, self.bounds_size())

              if not is_within_bounds(x_coordinate, self.min_x_coordinate, self.max_x_coordinate):
                  if not self.x_coordinates_can_be_less_than_min:
                      raise ValueError("The x coordinate must be greater")

                  else:
                      return self.get_before_piecewise_function().get_function(x_coordinate)

              for function in self.functions:
                  if is_within_bounds(x_coordinate, *function.get_bounds()):
                      return function

              raise ValueError("You should not get this value error! This is an implementation error in the code")

          def get_before_piecewise_function(self):
              """
                   Returns:
                      PiecewiseFunction: a piecewise function that 'comes before' the original"""

              before_piecewise_function = self.get_copy()
              new_x_start = self.min_x_coordinate - self.bounds_size()
              # The relative positions of functions stay the same, so I am just performing a 'domain shift'
              x_position_in_original_function = lambda x: self.get_x_relative_position(new_x_start, x) + self.min_x_coordinate
              before_piecewise_function.get_y_coordinate = lambda x: self.get_y_coordinate(x_position_in_original_function(x))

              return before_piecewise_function

          def get_x_relative_position(self, start, x_coordinate):
              """
                   Returns:
                      float: the distance x is from start (the relative position within the interval [start, end])"""

              return start - x_coordinate

          def get_min_x_coordinate(self):
              """
                   Returns:
                      float: the min x coordinate of the piecewise function (min x coordinate of min bounded function)"""

              return self.min_x_coordinate

          def get_max_x_coordinate(self):
              """
                   Returns:
                      float: the max x coordinate of the piecewise function (max x coordinate of max bounded function)"""

              return self.max_x_coordinate

          def get_bounds(self):
              """
                   Returns:
                      list[float]: {min_x_coordinate, max_x_coordinate}; the bounds of the peicewise function"""

              return [self.min_x_coordinate, self.max_x_coordinate]

          def bounds_size(self):
              """
                   Returns:
                      float: the size of the bounds (max - min)"""

              return self.max_x_coordinate - self.min_x_coordinate

          def get_indefinite_integral(self):
              """ Finds and returns the indefinite integral. Raises an AttributeError if any of the functions do not have the
                  method: 'get_indefinite_integral'

                  Returns:
                      PiecewiseFunction: the indefinite integral of the polynomial"""

              new_functions = []
              for function in self.functions:
                  new_functions.append(function.get_indefinite_integral())

              indefinite_integral = self.get_copy().get_piecewise_function_with_functions(new_functions)
              return indefinite_integral

          def evaluate_area(self, start, end):
              """
                   Returns:
                      float: the value gotten from using the bounds for the indefinite integral"""

              indefinite_integral = self.get_indefinite_integral()
              area = 0
              current_start = start

              for x in range(len(indefinite_integral.get_functions())):
                  bounded_function = indefinite_integral.get_functions()[x]
                  min_x, max_x = bounded_function.get_bounds()

                  if is_within_bounds(current_start, min_x, max_x):
                      end_x = min_value(max_x, end)
                      new_area = bounded_function.get_y_coordinate(end_x) - bounded_function.get_y_coordinate(current_start)
                      area += new_area
                      current_start = end_x

                  if current_start == end:
                      break

              return area

          def get_derivative(self):
              """ Finds and returns the derivative. Raises an AttributeError if any of the functions do not have the
                  method: 'get_derivative'

                  Returns:
                      PiecewiseFunction: the derivative of the polynomial"""

              new_functions = []
              for function in self.functions:
                  new_functions.append(function.get_derivative())

              return self.get_piecewise_function_with_functions(new_functions)

          def evaluate_derivative_at_point(self, x_coordinate):
              """
                   Returns:
                      float: the value of the derivative evaluated at the 'x_coordinate'"""

              return self.get_derivative().get_y_coordinate(x_coordinate)

  ### Ancestors

  - Function
  - abc.ABC

  ### Class variables

  `var functions`
  `var is_repeating`
  `var max_x_coordinate`
  `var min_x_coordinate`
  `var x_coordinates_can_be_less_than_min`

  ### Methods

  `def add_function(self, new_function)`

    Adds a new function to the piecewise function

    ## Args

    `new_function` :`Function`
      function to be added

    ## Returns

    `PiecewiseFunction`
      'self'
    Expand source code

        def add_function(self, new_function):
            """ Adds a new function to the piecewise function

                Args:
                    new_function (Function): function to be added

                Returns:
                    PiecewiseFunction: 'self'"""

            self.functions.append(new_function)
            self.validate_function_bounds_do_not_overlap()
            self.update_function_bounds_fully()
            return self

  `def bounds_size(self)`

    ## Returns

    `float`
      the size of the bounds (max - min)
    Expand source code

        def bounds_size(self):
            """
                 Returns:
                    float: the size of the bounds (max - min)"""

            return self.max_x_coordinate - self.min_x_coordinate

  `def clear_functions(self)`

    Clears all functions from the piecewise function

    ## Returns

    `PiecewiseFunction`
      'self'
    Expand source code

        def clear_functions(self):
            """ Clears all functions from the piecewise function

                Returns:
                    PiecewiseFunction: 'self'"""

            self.functions.clear()
            self.min_x_coordinate = 0
            self.max_x_coordinate = 0
            return self

  `def evaluate_area(self, start, end)`

    ## Returns

    `float`
      the value gotten from using the bounds for the indefinite integral
    Expand source code

        def evaluate_area(self, start, end):
            """
                 Returns:
                    float: the value gotten from using the bounds for the indefinite integral"""

            indefinite_integral = self.get_indefinite_integral()
            area = 0
            current_start = start

            for x in range(len(indefinite_integral.get_functions())):
                bounded_function = indefinite_integral.get_functions()[x]
                min_x, max_x = bounded_function.get_bounds()

                if is_within_bounds(current_start, min_x, max_x):
                    end_x = min_value(max_x, end)
                    new_area = bounded_function.get_y_coordinate(end_x) - bounded_function.get_y_coordinate(current_start)
                    area += new_area
                    current_start = end_x

                if current_start == end:
                    break

            return area

  `def evaluate_derivative_at_point(self, x_coordinate)`

    ## Returns

    `float`
      the value of the derivative evaluated at the 'x_coordinate'
    Expand source code

        def evaluate_derivative_at_point(self, x_coordinate):
            """
                 Returns:
                    float: the value of the derivative evaluated at the 'x_coordinate'"""

            return self.get_derivative().get_y_coordinate(x_coordinate)

  `def get_before_piecewise_function(self)`

    ## Returns

    `PiecewiseFunction`
      a piecewise function that 'comes before' the original
    Expand source code

        def get_before_piecewise_function(self):
            """
                 Returns:
                    PiecewiseFunction: a piecewise function that 'comes before' the original"""

            before_piecewise_function = self.get_copy()
            new_x_start = self.min_x_coordinate - self.bounds_size()
            # The relative positions of functions stay the same, so I am just performing a 'domain shift'
            x_position_in_original_function = lambda x: self.get_x_relative_position(new_x_start, x) + self.min_x_coordinate
            before_piecewise_function.get_y_coordinate = lambda x: self.get_y_coordinate(x_position_in_original_function(x))

            return before_piecewise_function

  `def get_bounded_function_with_x_coordinates_less_than_min(self, x_coordinates_can_be_less_than_min)`

    ## Returns

    `PiecewiseFunction`
      a piecewise function with the is repeating attribute set to 'x_coordinates_can_be_less_than_min' (does not modify the current piecewise function)
    Expand source code

        def get_bounded_function_with_x_coordinates_less_than_min(self, x_coordinates_can_be_less_than_min):
            """
                 Returns:
                    PiecewiseFunction: a piecewise function with the is repeating attribute set to 'x_coordinates_can_be_less_than_min' (does not modify the current piecewise function)"""

            return self.get_copy().set_x_coordinates_can_be_less_than_min(x_coordinates_can_be_less_than_min)

  `def get_bounds(self)`

    ## Returns

    `list[float]`
      {min_x_coordinate, max_x_coordinate}; the bounds of the peicewise function
    Expand source code

        def get_bounds(self):
            """
                 Returns:
                    list[float]: {min_x_coordinate, max_x_coordinate}; the bounds of the peicewise function"""

            return [self.min_x_coordinate, self.max_x_coordinate]

  `def get_copy(self)`

    ## Returns

    `PiecewiseFunction`
      a copy of the current piecewise_function
    Expand source code

        def get_copy(self):
            """
                 Returns:
                    PiecewiseFunction: a copy of the current piecewise_function"""

            # return deepcopy(self)
            new_functions = []
            copy = PiecewiseFunction(new_functions)
            for val in self.get_functions():
                new_functions.append(val)

            copy.set_is_repeating(self.is_repeating)
            copy.set_x_coordinates_can_be_less_than_min(self.x_coordinates_can_be_less_than_min)
            return copy

  `def get_derivative(self)`

    Finds and returns the derivative. Raises an AttributeError if any of the functions do not have the method: 'get_derivative'

    ## Returns

    `PiecewiseFunction`
      the derivative of the polynomial
    Expand source code

        def get_derivative(self):
            """ Finds and returns the derivative. Raises an AttributeError if any of the functions do not have the
                method: 'get_derivative'

                Returns:
                    PiecewiseFunction: the derivative of the polynomial"""

            new_functions = []
            for function in self.functions:
                new_functions.append(function.get_derivative())

            return self.get_piecewise_function_with_functions(new_functions)

  `def get_function(self, x_coordinate)`

    ## Returns

    `BoundedFunction`
      the function that controls the piecewise function at the x coordinate
    Expand source code

        def get_function(self, x_coordinate):
            """
                 Returns:
                    BoundedFunction: the function that controls the piecewise function at the x coordinate"""

            if len(self.functions) == 0:
                raise ValueError("There must be a function in functions in order for the piecewise function to operate")

            if self.is_repeating:
                x_coordinate = modified_mod(x_coordinate, self.bounds_size())

            if not is_within_bounds(x_coordinate, self.min_x_coordinate, self.max_x_coordinate):
                if not self.x_coordinates_can_be_less_than_min:
                    raise ValueError("The x coordinate must be greater")

                else:
                    return self.get_before_piecewise_function().get_function(x_coordinate)

            for function in self.functions:
                if is_within_bounds(x_coordinate, *function.get_bounds()):
                    return function

            raise ValueError("You should not get this value error! This is an implementation error in the code")

  `def get_functions(self)`

    ## Returns

    `BoundedFunction`
      all the functions of the piecewise function
    Expand source code

        def get_functions(self):
            """
                 Returns:
                    BoundedFunction: all the functions of the piecewise function"""

            return self.functions

  `def get_indefinite_integral(self)`

    Finds and returns the indefinite integral. Raises an AttributeError if any of the functions do not have the method: 'get_indefinite_integral'

    ## Returns

    `PiecewiseFunction`
      the indefinite integral of the polynomial
    Expand source code

        def get_indefinite_integral(self):
            """ Finds and returns the indefinite integral. Raises an AttributeError if any of the functions do not have the
                method: 'get_indefinite_integral'

                Returns:
                    PiecewiseFunction: the indefinite integral of the polynomial"""

            new_functions = []
            for function in self.functions:
                new_functions.append(function.get_indefinite_integral())

            indefinite_integral = self.get_copy().get_piecewise_function_with_functions(new_functions)
            return indefinite_integral

  `def get_is_repeating(self)`

    ## Returns

    `boolean`
      whether the piecewise function repeats
    Expand source code

        def get_is_repeating(self):
            """
                 Returns:
                    boolean: whether the piecewise function repeats"""

            return self.is_repeating

  `def get_max_x_coordinate(self)`

    ## Returns

    `float`
      the max x coordinate of the piecewise function (max x coordinate of max bounded function)
    Expand source code

        def get_max_x_coordinate(self):
            """
                 Returns:
                    float: the max x coordinate of the piecewise function (max x coordinate of max bounded function)"""

            return self.max_x_coordinate

  `def get_min_x_coordinate(self)`

    ## Returns

    `float`
      the min x coordinate of the piecewise function (min x coordinate of min bounded function)
    Expand source code

        def get_min_x_coordinate(self):
            """
                 Returns:
                    float: the min x coordinate of the piecewise function (min x coordinate of min bounded function)"""

            return self.min_x_coordinate

  `def get_piecewise_function_with_added_function(self, new_function)`

    Returns: a new PiecewiseFunction with the added function (does not modify the current piecewise function)

    ## Args

    `new_function` :`Function`
      function to be added

    Returns: PiecewiseFunction: new piecewise function with the added function

    Expand source code

        def get_piecewise_function_with_added_function(self, new_function):
            """
                 Returns: a new PiecewiseFunction with the added function (does not modify the current piecewise function)

                Args:
                    new_function (Function): function to be added

                 Returns:
                    PiecewiseFunction: new piecewise function with the added function"""

            return self.get_copy().add_function(new_function)

  `def get_piecewise_function_with_cleared_functions(self)`

    Returns a new PiecewiseFunction with cleared functions (does not modify the current piecewise function)

    ## Returns

    `PiecewiseFunction`
      new piecewise function with cleared functions
    Expand source code

        def get_piecewise_function_with_cleared_functions(self):
            """
                 Returns a new PiecewiseFunction with cleared functions (does not modify the current piecewise function)

                 Returns:
                    PiecewiseFunction: new piecewise function with cleared functions"""

            return self.get_copy().clear_functions()

  `def get_piecewise_function_with_function_removed(self, function)`

    Returns: a new PiecewiseFunction with the specified function removed (does not modify the current piecewise function)

    ## Args

    `function` :`Function`
      function to be removed

    Returns: PiecewiseFunction: new piecewise function with the specified function removed

    Expand source code

        def get_piecewise_function_with_function_removed(self, function):
            """
                 Returns: a new PiecewiseFunction with the specified function removed (does not modify the current piecewise function)
                Args:
                    function (Function): function to be removed

                 Returns:
                    PiecewiseFunction: new piecewise function with the specified function removed"""

            return self.get_copy().remove_function(function)

  `def get_piecewise_function_with_function_removed_at_index(self, index)`

    Returns: a new PiecewiseFunction with a function removed at the specified index (does not modify the current piecewise function)

    ## Args

    `index` :`int`
      index of the function to be removed

    Returns: PiecewiseFunction: new piecewise function with the function removed at the specified index

    Expand source code

        def get_piecewise_function_with_function_removed_at_index(self, index):
            """
                 Returns: a new PiecewiseFunction with a function removed at the specified index (does not modify the current piecewise function)
                Args:
                    index (int): index of the function to be removed

                 Returns:
                   PiecewiseFunction: new piecewise function with the function removed at the specified index"""

            return self.get_copy().remove_function_at_index(index)

  `def get_piecewise_function_with_functions(self, functions)`

    ## Returns

    `PiecewiseFunction`
      a piecewise function with functions attribute being set to 'functions' (does not modify the current piecewise function)
    Expand source code

        def get_piecewise_function_with_functions(self, functions):
            """
                 Returns:
                    PiecewiseFunction: a piecewise function with functions attribute being set to 'functions' (does not modify the current piecewise function)"""

            return self.get_copy().set_functions(functions)

  `def get_piecewise_function_with_is_repeating(self, is_repeating)`

    ## Returns

    `PiecewiseFunction`
      a piecewise function with the is repeating attribute set to 'is_repeating' (does not modify the current piecewise function)
    Expand source code

        def get_piecewise_function_with_is_repeating(self, is_repeating):
            """
                 Returns:
                    PiecewiseFunction: a piecewise function with the is repeating attribute set to 'is_repeating' (does not modify the current piecewise function)"""

            return self.get_copy().set_is_repeating(is_repeating)

  `def get_x_coordinates_can_be_less_than_min(self)`

    ## Returns

    `bool`
      whether the x coordinates this function accepts can be less than the min x coordinate
    Expand source code

        def get_x_coordinates_can_be_less_than_min(self):
            """
                 Returns:
                    bool: whether the x coordinates this function accepts can be less than the min x coordinate"""

            return self.x_coordinates_can_be_less_than_min

  `def get_x_relative_position(self, start, x_coordinate)`

    ## Returns

    `float`
      the distance x is from start (the relative position within the interval [start, end])
    Expand source code

        def get_x_relative_position(self, start, x_coordinate):
            """
                 Returns:
                    float: the distance x is from start (the relative position within the interval [start, end])"""

            return start - x_coordinate

  `def remove_function(self, function)`

    Removes the specified function from the piecewise function

    ## Args

    `function` :`Function`
      function to be removed

    ## Returns

    `PiecewiseFunction`
      'self'
    Expand source code

        def remove_function(self, function):
            """ Removes the specified function from the piecewise function

                Args:
                    function (Function): function to be removed

                Returns:
                    PiecewiseFunction: 'self'"""

            self.functions.remove(function)
            self.update_function_bounds_fully()
            return self

  `def remove_function_at_index(self, index)`

    Removes a function at the specified index from the piecewise function

    ## Args

    `index` :`int`
      index of the function to be removed

    ## Returns

    `PiecewiseFunction`
      'self'
    Expand source code

        def remove_function_at_index(self, index):
            """ Removes a function at the specified index from the piecewise function

                Args:
                    index (int): index of the function to be removed

                Returns:
                    PiecewiseFunction: 'self'"""

            if 0 <= index < len(self.functions):
                del self.functions[index]

            self.update_function_bounds_fully()
            return self

  `def set_functions(self, functions)`

    Sets the functions of the piecewise function

    ## Returns

    `PiecewiseFunction`
      'self'
    Expand source code

        def set_functions(self, functions):
            """Sets the functions of the piecewise function

                 Returns:
                    PiecewiseFunction: 'self'"""

            self.functions = functions
            self.update_function_bounds_fully()
            return self

  `def set_is_repeating(self, is_repeating)`

    Sets the is_repeating attribute to 'is_repeating'

    ## Returns

    `PiecewiseFunction`
      'self'
    Expand source code

        def set_is_repeating(self, is_repeating):
            """Sets the is_repeating attribute to 'is_repeating'

                 Returns:
                    PiecewiseFunction: 'self'"""

            self.is_repeating = is_repeating
            return self

  `def set_x_coordinates_can_be_less_than_min(self, x_coordinates_can_be_less_than_min)`

    Sets whether the x coordinates this function accepts can be less than the min x coordinate

    ## Returns

    `BoundedFunction`
      'self'
    Expand source code

        def set_x_coordinates_can_be_less_than_min(self, x_coordinates_can_be_less_than_min):
            """ Sets whether the x coordinates this function accepts can be less than the min x coordinate

                 Returns:
                    BoundedFunction: 'self'"""

            self.x_coordinates_can_be_less_than_min = x_coordinates_can_be_less_than_min
            return self

  `def update_function_bounds_for_added_function(self, function)`

    Updates the function bounds when a function is added (do not have to check every function in list)

    Expand source code

        def update_function_bounds_for_added_function(self, function):
            """Updates the function bounds when a function is added (do not have to check every function in list)"""

            self.min_x_coordinate = min(self.min_x_coordinate, function.get_min_x_coordinate())
            self.max_x_coordinate = max(self.max_x_coordinate, function.get_max_x_coordinate())

  `def update_function_bounds_fully(self)`

    Updates the function bounds for operations like remove (has to check every function in the list)

    Expand source code

        def update_function_bounds_fully(self):
            """Updates the function bounds for operations like remove (has to check every function in the list)"""

            if len(self.functions) == 0:
                return

            self.functions = sorted(self.functions, key=lambda x: x.get_min_x_coordinate())
            self.min_x_coordinate = self.functions[0].get_min_x_coordinate()

            max_index = len(self.functions) - 1
            self.max_x_coordinate = self.functions[max_index].get_max_x_coordinate()

  `def validate_function_bounds_do_not_overlap(self)`

    Raises a ValueError if the function bounds overlap (if there are overlapping bounds the piecewise function is invalidated)

    Expand source code

        def validate_function_bounds_do_not_overlap(self):
            """Raises a ValueError if the function bounds overlap (if there are overlapping bounds the piecewise function is invalidated)"""

            for i in range(len(self.functions)):
                for j in range(len(self.functions)):
                    function1 = self.functions[i]
                    function2 = self.functions[j]

                    a, b = function1.get_bounds()
                    c, d = function2.get_bounds()

                    # If the function1's bounds fall within function2's bounds (case 1) or it crosses the bounds of
                    # function2 (case 2) then this is no longer a valid piecewise function.
                    is_invalid = a <= c <= b
                    distance_between_c_and_a = a - c
                    function1_bound_size = b - a
                    is_invalid |= distance_between_c_and_a >= function1_bound_size

                    if is_invalid:
                            raise ValueError("The bounds of each function within the piecewise function must not overlap")

  ### Inherited members

  - `Function`:
    - `get_inverted_function`
    - `get_new_function`
    - `get_x_coordinate`
    - `get_y_coordinate`