Pendulum Example¶
In this example we go from generating raw simulation data through extracting the generators of the underlying potential using SymDet.
[1]:
import symdet
from symdet.utils.data_clustering import range_binning
import hephaestus as hp
import numpy as np
import tensorflow as tf
import copy
import matplotlib.pyplot as plt
Generating the Data¶
First things first, we need to generate some data to use in the symmetry extraction. We have a pendulum swinging ideally with an angle for 4.0 degrees.
[2]:
pendulum = hp.SinglePendulum(time_step=0.001, steps=50000, mass=1.0, gravity=10.0, theta_start=5.0)
integrator = hp.VelocityVerlet(time_step=0.001, model=pendulum)
pendulum.integrator = integrator # update the integrator
[3]:
pendulum.run_simulation() # seperated to avoid unnecesary running of simulations.
100%|█████████████████████████| 49999/49999 [00:30<00:00, 1629.48it/s]
Before rushing into clustering and generator studies we should spend some time looking at the data that has been generated.
[4]:
plt.plot(pendulum.theta, pendulum.pe, ',')
plt.grid()
plt.xlabel(r'$\theta$')
plt.ylabel('PE')
plt.show()
Clustering the Data¶
[5]:
x_data = pendulum.length * np.sin(pendulum.theta)
y_data = pendulum.length * np.cos(pendulum.theta)
domain = tf.convert_to_tensor(list(zip(x_data, y_data)))
image = tf.convert_to_tensor(pendulum.pe / max(pendulum.pe))
[6]:
clustered_data = range_binning(image=image,
domain=domain,
value_range=[0, 3],
bin_operation=[1 / 5, 0.008],
representatives=500)
WARNING: Not enough data! Some classes will be under-represented.
[7]:
plt.plot(pendulum.theta, pendulum.pe / max(pendulum.pe), ',')
plt.grid()
plt.xlabel(r'$\theta$')
plt.ylabel('PE')
for i, item in enumerate(clustered_data):
theta = np.arcsin(clustered_data[item]['domain'][:, 0]) / pendulum.length
v = clustered_data[item]['image']
plt.plot(theta, v, '.', label=f"Class {i}", markersize=6)
plt.legend(loc='center left', bbox_to_anchor=(1, 0.5))
plt.show()
Detecting Symmetry Groups¶
[8]:
model = symdet.DenseModel(n_layers=7,
units=80,
epochs=10,
batch_size=64,
lr=0.00025)
[9]:
sym_detector = symdet.GroupDetection(model, clustered_data)
[11]:
point_cloud = sym_detector.run_symmetry_detection()
WARNING:tensorflow:Please add `keras.layers.InputLayer` instead of `keras.Input` to Sequential model. `keras.Input` is intended to be used by Functional model.
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
dense (Dense) (None, 80) 240
_________________________________________________________________
dense_1 (Dense) (None, 80) 6480
_________________________________________________________________
dense_2 (Dense) (None, 80) 6480
_________________________________________________________________
dense_3 (Dense) (None, 80) 6480
_________________________________________________________________
dense_4 (Dense) (None, 80) 6480
_________________________________________________________________
second_last_layer (Dense) (None, 80) 6480
_________________________________________________________________
embedding_layer (Dense) (None, 80) 6480
_________________________________________________________________
softmax_layer (Dense) (None, 4) 324
=================================================================
Total params: 39,444
Trainable params: 39,444
Non-trainable params: 0
_________________________________________________________________
None
Epoch 1/10
16/16 [==============================] - 1s 18ms/step - loss: 1.4639 - acc: 0.2200 - val_loss: 1.4610 - val_acc: 0.2500
Epoch 2/10
16/16 [==============================] - 0s 4ms/step - loss: 1.4591 - acc: 0.2920 - val_loss: 1.4566 - val_acc: 0.3767
Epoch 3/10
16/16 [==============================] - 0s 5ms/step - loss: 1.4549 - acc: 0.2930 - val_loss: 1.4524 - val_acc: 0.3767
Epoch 4/10
16/16 [==============================] - 0s 3ms/step - loss: 1.4508 - acc: 0.3450 - val_loss: 1.4484 - val_acc: 0.5000
Epoch 5/10
16/16 [==============================] - 0s 4ms/step - loss: 1.4467 - acc: 0.3560 - val_loss: 1.4443 - val_acc: 0.2500
Epoch 6/10
16/16 [==============================] - 0s 4ms/step - loss: 1.4430 - acc: 0.3150 - val_loss: 1.4402 - val_acc: 0.6267
Epoch 7/10
16/16 [==============================] - 0s 5ms/step - loss: 1.4385 - acc: 0.4200 - val_loss: 1.4359 - val_acc: 0.5000
Epoch 8/10
16/16 [==============================] - 0s 3ms/step - loss: 1.4338 - acc: 0.4390 - val_loss: 1.4305 - val_acc: 0.3767
Epoch 9/10
16/16 [==============================] - 0s 3ms/step - loss: 1.4281 - acc: 0.3760 - val_loss: 1.4241 - val_acc: 0.3767
Epoch 10/10
16/16 [==============================] - 0s 3ms/step - loss: 1.4202 - acc: 0.3730 - val_loss: 1.4159 - val_acc: 0.3767
Epoch 1/10
16/16 [==============================] - 0s 7ms/step - loss: 1.4105 - acc: 0.3820 - val_loss: 1.4018 - val_acc: 0.3767
Epoch 2/10
16/16 [==============================] - 0s 3ms/step - loss: 1.3934 - acc: 0.3730 - val_loss: 1.3787 - val_acc: 0.3767
Epoch 3/10
16/16 [==============================] - 0s 4ms/step - loss: 1.3651 - acc: 0.3730 - val_loss: 1.3427 - val_acc: 0.3767
Epoch 4/10
16/16 [==============================] - 0s 4ms/step - loss: 1.3206 - acc: 0.3880 - val_loss: 1.2794 - val_acc: 0.3767
Epoch 5/10
16/16 [==============================] - 0s 6ms/step - loss: 1.2449 - acc: 0.4910 - val_loss: 1.1860 - val_acc: 0.5000
Epoch 6/10
16/16 [==============================] - 0s 4ms/step - loss: 1.1350 - acc: 0.5820 - val_loss: 1.0477 - val_acc: 0.5000
Epoch 7/10
16/16 [==============================] - 0s 3ms/step - loss: 0.9870 - acc: 0.5930 - val_loss: 0.8977 - val_acc: 0.7500
Epoch 8/10
16/16 [==============================] - 0s 4ms/step - loss: 0.8335 - acc: 0.7400 - val_loss: 0.7501 - val_acc: 0.7500
Epoch 9/10
16/16 [==============================] - 0s 5ms/step - loss: 0.6981 - acc: 0.7810 - val_loss: 0.6329 - val_acc: 0.8767
Epoch 10/10
16/16 [==============================] - 0s 3ms/step - loss: 0.5987 - acc: 0.8960 - val_loss: 0.5449 - val_acc: 0.8733
Epoch 1/10
16/16 [==============================] - 0s 5ms/step - loss: 0.5146 - acc: 0.9200 - val_loss: 0.4703 - val_acc: 1.0000
Epoch 2/10
16/16 [==============================] - 0s 4ms/step - loss: 0.4341 - acc: 0.9860 - val_loss: 0.3856 - val_acc: 1.0000
Epoch 3/10
16/16 [==============================] - 0s 4ms/step - loss: 0.3685 - acc: 1.0000 - val_loss: 0.3293 - val_acc: 1.0000
Epoch 4/10
16/16 [==============================] - 0s 3ms/step - loss: 0.3072 - acc: 1.0000 - val_loss: 0.3197 - val_acc: 1.0000
Epoch 5/10
16/16 [==============================] - 0s 4ms/step - loss: 0.2685 - acc: 1.0000 - val_loss: 0.2382 - val_acc: 1.0000
Epoch 6/10
16/16 [==============================] - 0s 3ms/step - loss: 0.2215 - acc: 1.0000 - val_loss: 0.2045 - val_acc: 1.0000
Epoch 7/10
16/16 [==============================] - 0s 6ms/step - loss: 0.1865 - acc: 1.0000 - val_loss: 0.1659 - val_acc: 1.0000
Epoch 8/10
16/16 [==============================] - 0s 3ms/step - loss: 0.1560 - acc: 1.0000 - val_loss: 0.1417 - val_acc: 1.0000
Epoch 9/10
16/16 [==============================] - 0s 3ms/step - loss: 0.1356 - acc: 1.0000 - val_loss: 0.1216 - val_acc: 1.0000
Epoch 10/10
16/16 [==============================] - 0s 4ms/step - loss: 0.1186 - acc: 1.0000 - val_loss: 0.1058 - val_acc: 1.0000
Epoch 1/10
16/16 [==============================] - 0s 5ms/step - loss: 0.1015 - acc: 1.0000 - val_loss: 0.0908 - val_acc: 1.0000
Epoch 2/10
16/16 [==============================] - 0s 4ms/step - loss: 0.0874 - acc: 1.0000 - val_loss: 0.0810 - val_acc: 1.0000
Epoch 3/10
16/16 [==============================] - 0s 4ms/step - loss: 0.0811 - acc: 1.0000 - val_loss: 0.0763 - val_acc: 1.0000
Epoch 4/10
16/16 [==============================] - 0s 3ms/step - loss: 0.0711 - acc: 1.0000 - val_loss: 0.0657 - val_acc: 1.0000
Epoch 5/10
16/16 [==============================] - 0s 3ms/step - loss: 0.0636 - acc: 1.0000 - val_loss: 0.0609 - val_acc: 1.0000
Epoch 6/10
16/16 [==============================] - 0s 3ms/step - loss: 0.0595 - acc: 1.0000 - val_loss: 0.0565 - val_acc: 1.0000
Epoch 7/10
16/16 [==============================] - 0s 4ms/step - loss: 0.0557 - acc: 1.0000 - val_loss: 0.0527 - val_acc: 1.0000
Epoch 8/10
16/16 [==============================] - 0s 4ms/step - loss: 0.0516 - acc: 1.0000 - val_loss: 0.0505 - val_acc: 1.0000
Epoch 9/10
16/16 [==============================] - 0s 3ms/step - loss: 0.0493 - acc: 1.0000 - val_loss: 0.0471 - val_acc: 1.0000
Epoch 10/10
16/16 [==============================] - 0s 3ms/step - loss: 0.0467 - acc: 1.0000 - val_loss: 0.0451 - val_acc: 1.0000
Epoch 1/10
16/16 [==============================] - 0s 7ms/step - loss: 0.0444 - acc: 1.0000 - val_loss: 0.0430 - val_acc: 1.0000
Epoch 2/10
16/16 [==============================] - 0s 3ms/step - loss: 0.0428 - acc: 1.0000 - val_loss: 0.0415 - val_acc: 1.0000
Epoch 3/10
16/16 [==============================] - 0s 3ms/step - loss: 0.0414 - acc: 1.0000 - val_loss: 0.0403 - val_acc: 1.0000
Epoch 4/10
16/16 [==============================] - 0s 3ms/step - loss: 0.0399 - acc: 1.0000 - val_loss: 0.0392 - val_acc: 1.0000
Epoch 5/10
16/16 [==============================] - 0s 4ms/step - loss: 0.0391 - acc: 1.0000 - val_loss: 0.0383 - val_acc: 1.0000
Epoch 6/10
16/16 [==============================] - 0s 5ms/step - loss: 0.0376 - acc: 1.0000 - val_loss: 0.0370 - val_acc: 1.0000
Epoch 7/10
16/16 [==============================] - 0s 4ms/step - loss: 0.0368 - acc: 1.0000 - val_loss: 0.0364 - val_acc: 1.0000
Epoch 8/10
16/16 [==============================] - 0s 4ms/step - loss: 0.0358 - acc: 1.0000 - val_loss: 0.0351 - val_acc: 1.0000
Epoch 9/10
16/16 [==============================] - 0s 3ms/step - loss: 0.0348 - acc: 1.0000 - val_loss: 0.0344 - val_acc: 1.0000
Epoch 10/10
16/16 [==============================] - 0s 5ms/step - loss: 0.0341 - acc: 1.0000 - val_loss: 0.0338 - val_acc: 1.0000
WARNING:tensorflow:Please add `keras.layers.InputLayer` instead of `keras.Input` to Sequential model. `keras.Input` is intended to be used by Functional model.
A quick sanity check¶
At this stage we need to stop and check that we have collected some group data on which to extract generators. We do this by looking at the keys of the point_cloud and seeing if they look good when compared to the tSNE representation.
[12]:
point_cloud.keys()
[12]:
dict_keys([3.0])
In this case, the points selected are from groups 1.0, 2.0, and 3.0, which, by looking at both the tSNE representation and the cluster image above, looks correct. However, it is also very possible that we are now missing data that is also clearly clustered. This is due to the pair detection algorithm being used as is currently being re-evaluated and improved. For now, if you want to make the code more sensitive to pairs in the tSNE representation you can do so in the source code.
[14]:
generator_list = {}
for item in point_cloud:
try:
generator_list[item] = {}
generator_extractor = symdet.GeneratorExtraction(
point_cloud[item], # clustered data
delta=0.5, # distance of points to hyperplane
epsilon=0.3, # distance between points connected by a generator
candidate_runs=5 # Number of times to run the extraction loop
)
generators, variance_list = generator_extractor.perform_generator_extraction(pca_components=4,
plot=True,
factor=False,
gs_precision=3)
generator_list[item]['generators'] = generators
generator_list[item]['variance'] = variance_list
except ValueError:
print(f"No generator found for {item}")
continue
Producing generator candidates: 100%|█████████████████████████████████| 5/5 [07:05<00:00, 85.17s/it]
Principle Component 1: Explained Variance: 1.0
[[-4.09885574e-04 4.62072724e-04]
[-9.99999679e-01 -5.10757461e-04]]
Principle Component 2: Explained Variance: 1.9849783433354677e-38
[[-4.62828467e-01 -5.80943111e-01]
[-4.20708106e-04 6.69548157e-01]]
Principle Component 3: Explained Variance: 5.572204974647974e-40
[[-4.17203676e-01 -5.23675465e-01]
[ 3.08404258e-04 -7.42768473e-01]]
Principle Component 4: Explained Variance: 0.0
[[-7.82132172e-01 6.23112265e-01]
[ 6.08508071e-04 1.24628931e-13]]
There are a few interesting points to go through here. The first, and most important, is the final plot. We can see that the code has produced a single relevant generator matrix suggesting that the space was correctly defined. The second point is the actual generator that was found. We can see that is appears to only cotain one dominant points, i.e. the -1 in the in the lower left corner of the matrix. This is not exactly what one would expect to see in tru SO(2) symmetry but considering that the energy is only indirectly influence by the x-position it is not unlikely that the generator is reduced to only a single number.