Source code for symdet.data.double_well_potential

"""
This program and the accompanying materials are made available under the terms of the
Eclipse Public License v2.0 which accompanies this distribution, and is available at
https://www.eclipse.org/legal/epl-v20.html
SPDX-License-Identifier: EPL-2.0

Copyright Contributors to the Zincware Project.

Description: Example data generator for the double well potential.
"""
from symdet.data.data_generator import DataGenerator
from symdet.utils.data_clustering import range_binning
from typing import Union
import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt
from tqdm import tqdm


[docs]class DoubleWellPotential(DataGenerator): """ Class for the double well potential implementation. Attributes ---------- a : float Scaling factor in the equation (see below) See Also -------- symdet.data.data_generator.DataGenerator Examples -------- >>> from symdet import DoubleWellPotential >>> generator = DoubleWellPotential() >>> generator.load_data() >>> generator.plot_data() Notes ----- The double well potential is written mathematically: .. math:: V = -a \cdot (x^{2} + y^{2}) + (x^{2} + y^{2})^{2} We will require the data to be stored as x,y coordinates in order to facilitate the generator extraction in this part of the process. """ def __init__(self, a: float = 2.3): """ Constructor for the double well potential. Parameters ---------- a : float Scaling factor of the double well potential. """ super().__init__() self.a = a def _double_well(self): """ Generate point along a double well potential range. Returns ------- """ square_radii = tf.reduce_sum(tf.math.square(self.domain), 1) self.image = -self.a * square_radii + tf.square(square_radii) def _pick_points(self, n_points: int, min_val: float = 0, max_val: float = 1.6): """ Generate random coordinates in the double well domain. Parameters ---------- n_points : int Number of points to pick. min_val : float Minimum value to consider. max_val : float Maximum value to consider. Returns ------- """ self.domain = tf.random.uniform(shape=(n_points, 2), minval=min_val, maxval=max_val)
[docs] def load_data(self, points: Union[int, np.ndarray], save: bool = False): """ Load / generate the data. Parameters ---------- points : Union[int, np.ndarray] Points to generate, either an np.ndarray or an integer. If an integer, N points will be generated, if an array, it will either be treated as input to a function to generate values or those indices will be drawn from a pool. save : bool If true, save the data after generating it. Returns ------- Updates the class state. """ # generate domain and image data. if type(points) is int: self._pick_points(points) self._double_well() # set domain and generate image data. else: self.domain = tf.convert_to_tensor(points) self._double_well()
[docs] def plot_clusters(self, save: bool = False): """ Plot the clusters generated. Parameters ---------- save Returns ------- """ self.plot_data(show=False) for i, item in tqdm(enumerate(self.clustered_data), ncols=70, total=len(self.clustered_data)): r = tf.norm(self.clustered_data[item]['domain'], axis=1) v = self.clustered_data[item]['image'] plt.plot(r, v, '.', label=f"Class {i}", markersize=15) plt.legend(loc='center left', bbox_to_anchor=(1, 0.5)) plt.show()
[docs] def plot_data(self, save: bool = False, show: bool = True): """ Plot the data. Parameters ---------- save : bool If true, save the plot. show : bool (default=True) If true, show the result Returns ------- Plots the data. """ if self.domain is None: self._pick_points(1000, min_val=0, max_val=1.2) self._double_well() plt.plot(tf.norm(self.domain, axis=1), self.image, '.') plt.xlabel('r') plt.ylabel('V') plt.xlim(-0.03, 1.7) plt.ylim(-1.5, 1.3) plt.grid() if save: plt.savefig(f'Double_Well_{len(self.domain)}.svg', dpi=600, format='dpi') if show: plt.show()
[docs] def build_clusters(self, value_range: list = None, bin_operation: list = None, representatives=1000): """ Split the raw function data into classes. Parameters ---------- representatives : int Number of class representatives to have for each bin. value_range : list The parameters within which to bin e.g. k in [-5, 5] bin_operation : list Operation to apply to the bins e.g [1/5, 1e-3] will lead to bins of the form [k/5 - 1e-3, k/5 + 1e-3] Returns ------- Updates the class state. Notes ----- In the double well potential we can simply use the range_binning clustering algorithm. """ # Replace None type parameters. if bin_operation is None: bin_operation = [1 / 5, 1e-3] if value_range is None: value_range = [-5, 5] n_classes = (value_range[1] - value_range[0]) + 1 # Generate data if it does not exist if self.domain is None: self.load_data(n_classes * representatives * 1000) if len(self.domain) < n_classes * representatives * 1000: print("Loading additional data.") self.load_data(n_classes * representatives * 1000) self.clustered_data = range_binning(image=self.image, domain=self.domain, value_range=value_range, bin_operation=bin_operation, representatives=representatives)