import Oscillators as osc

"""
Explanation: Tutorial
This is an example of how an interactive IPython/Jupyter notebook works with this optics project.
It'll be split into different sections explaining individual components and building up towards a full use case.
Oscillators
We have implemented a few commonly used oscillators/lineshapes. These are located in the Oscillators module.
End of explanation
"""


l = osc.Lorentz(1,0.2,1.5)
print(l)

"""
Explanation: Let's create a Lorentz oscillator of amplitude 1, center position 1.5 eV, and width 0.2 eV.
End of explanation
"""


import numpy as np
l.dielectricFunction(np.arange(0.01,3,0.1))

"""
Explanation: We can directly ask some properties to this oscillator, like its dielectric function in a particular energy/frequency window of interest.
First we define the window of interest though a set of points where the dielectric function will be evaluated. In our case we want the range from 0.01 to 3 eV in steps of 0.1. For this we will use numpy, but a standard Python list works too!
End of explanation
"""


import numpy as np
%pylab inline
window = np.arange(0.01,3,0.01)
plot(window, l.dielectricFunction(window))

"""
Explanation: As it can be seen, a numpy array of complex values containing the dielectric function evaluated in the points given is returned. But just a list of numbers do not tell much, thus we want to be able to see it.
We will use the matplotlib package to manually plot the dielectric function of this oscillator, but later we will see an alternative that is provided allowing for a shortcut.
Again, we will define a window of interest but with a smaller step.
End of explanation
"""


plot(window, np.imag(l.dielectricFunction(window)))

"""
Explanation: We can see that we get a warning because the dielectric function is a complex value and only the real part is considered ($\varepsilon_1$). Let's plot the imaginary part of the dielectric function ($\varepsilon_2$).
End of explanation
"""


plot(window, np.real(l.dielectricFunction(window)))
plot(window, np.imag(l.dielectricFunction(window)))

"""
Explanation: And of course we could show both parts of the dielectric function in one single plot.
End of explanation
"""


from OpticalModel import *
om = OpticalModel()
om.add(l)

"""
Explanation: Another interesting propertie of oscillators are their optical conductivity and spectral weight. These two can also be accessed at an oscillator level.
Optical Models
To combine more than one oscillator to model the optical response of a material, an optical model can be created. This model contains the collection of oscillators describing a system.
We will create an empty model and add to it the already created oscillator in the previous section.
End of explanation
"""


om.name = "Demo Model"
om.plot(window)

"""
Explanation: Here we can see a text representation of the model indicating its name, which by default is Optical Model 1, and a list of the oscillators included. If you have run the previous cell more than once, then the number in the name of the model will change representing how many models have been created in a session. This can be changed by providing a name when creating the model of afterwards.
Let us change the name and then make use of a shortcut to plot the dielectric function of the model reusing the previously defined window of interest.
End of explanation
"""


om.add(osc.Gauss(1,0.4,3))

"""
Explanation: This shortcut provides a way to visualize the dielectric function in a quick and convenient way, providing automatic labeling of axes and a title. Note that the title coincides with the name of the model!
For those with some Python experience, an Optical Model instance supports all operations of a list!
Now we would like to make something slightly more complex, and add a second oscillator to the model. In this case, we will add a Gaussian. The oscillator is added directly to the model and it is in its own cell to avoid adding it more than once in case we want to modify the plot creation that follows.
End of explanation
"""


om.show()
om.plot(window)

"""
Explanation: Now we can show the model and then plot it again.
End of explanation
"""


extended_window = np.arange(0.01,4,0.01)
om.plot(extended_window)

"""
Explanation: We do not see the whole of the Gaussian! In this case, since the Gaussian is added at 3 eV, we have to extend the window of interest.
End of explanation
"""


om.plot(extended_window, flag = "e2")

"""
Explanation: Now, we can see that the plot is becoming a bit saturated. Optionally, we could ask for alternative plot. For example we want to see $\varepsilon_2$ alone.
End of explanation
"""


om.plot(extended_window, flag = "R")

"""
Explanation: Alternatively, we can get other optical properties of the model. For instance the reflectivity by using the "R" flag.
End of explanation
"""


om.dump("Demo Model")

"""
Explanation: Other available options are e1, e2, s1, s2, n, k, nk.
A model can be saved to disk as a json file to be reloaded in later sessions. When saved without a filename, it uses the name of the model as a filename.
End of explanation
"""


from Datasets import *
data = Dataset()
data.loadRaw("Examples/Reflectivity-Simple-wavenumber.dat", unit = "cm-1")
data.plot()

"""
Explanation: Datasets
Datasets of experimental data can be also managed with the intention of using them to fit a model to it.
Let us import a simple reflectivity dataset using the loadRaw function. Since the unit of the data in inverse centimeters ($cm^{-1}$), as encountered in datasets from Bruker FT equipement for example, we use the optional argument unit= "cm-1" to indicate that. Internally we use an eV representatin of energies. In this case we again have a shortcut to provide a simple graph of the dataset.
End of explanation
"""


data.scale(1.1)
data.plot()

"""
Explanation: A dataset can be transformed in different ways. For example we could scale the values for reflectivity. Let us try it up $10%$.
End of explanation
"""


scaled_data = data.clone(scale=0.9, name = "Scaled reflectivity")
scaled_data.plot()

"""
Explanation: The previous operation is an in place one, modifying the data. Alternatively, to preserve the original dataset one could clone and apply the scaling.
End of explanation
"""


