from math import sin, exp

def func1(x):
    return sin(x / 5.) * exp(x / 10.) + 5. * exp(-x/ 2.)

import numpy as np

xArr1 = np.arange(1., 31.)
print xArr1
print "Shape x:", xArr1.shape
yArr1 = np.array([func1(x) for x in xArr1])
print yArr1
print "Shape y:", yArr1.shape

%matplotlib inline
import matplotlib.pyplot as plt

plt.plot(xArr1, yArr1)
plt.grid(True)
plt.axis([0, 30, -15, 5])
plt.show()

"""
Explanation: Задача 1. Минимизация гладкой функции
1. Рассмотрим все ту же функцию из задания по линейной алгебре: f(x) = sin(x / 5) * exp(x / 10) + 5 * exp(-x / 2), но теперь уже на промежутке [1, 30]
End of explanation
"""


from scipy.optimize import minimize

minFunc1Value1 = minimize(func1, 5)
print "Minimized f(x) (standard method): ", round(minFunc1Value1.fun, 3), "for x = ", round(minFunc1Value1.x, 3)
print "Number of iterations: ", minFunc1Value1.nit

"""
Explanation: 2. В первом задании будем искать минимум этой функции на заданном промежутке с помощью scipy.optimize. Разумеется, в дальнейшем вы будете использовать методы оптимизации для более сложных функций, а f(x) мы рассмотрим как удобный учебный пример.
3. Напишите на Питоне функцию, вычисляющую значение f(x) по известному x. Будьте внимательны: не забывайте про то, что по умолчанию в питоне целые числа делятся нацело, и о том, что функции sin и exp нужно импортировать из модуля math.
4. Изучите примеры использования scipy.optimize.minimize в документации Scipy (см. "Материалы")
5. Попробуйте найти минимум, используя стандартные параметры в функции scipy.optimize.minimize (т.е. задав только функцию и начальное приближение). Попробуйте менять начальное приближение и изучить, меняется ли результат.
End of explanation
"""


minFunc1Value2 = minimize(func1, 2, method = 'BFGS')
print "Minimized f(x) (BFGS method): ", round(minFunc1Value2.fun, 3), "for x = ", round(minFunc1Value2.x, 3)
print "Number of iterations: ", minFunc1Value2.nit

minValuesAnswer1 = np.zeros( (2) )
minValuesAnswer1[0] = round(minFunc1Value2.fun, 2)
print minValuesAnswer1

"""
Explanation: 6. Укажите в scipy.optimize.minimize в качестве метода BFGS (один из самых точных в большинстве случаев градиентных методов оптимизации), запустите из начального приближения x=2. Градиент функции при этом указывать не нужно – он будет оценен численно. Полученное значение функции в точке минимума - ваш первый ответ по заданию 1, его надо записать с точностью до 2 знака после запятой.
End of explanation
"""


minFunc1Value3 = minimize(func1, 30, method = 'BFGS')
print "Minimized f(x) (BFGS method): ", round(minFunc1Value3.fun, 3), "for x = ", round(minFunc1Value3.x, 3)
print "Number of iterations: ", minFunc1Value3.nit

minValuesAnswer1[1] = round(minFunc1Value3.fun, 2)
print minValuesAnswer1

"""
Explanation: 7. Теперь измените начальное приближение на x=30. Значение функции в точке минимума - ваш второй ответ по заданию 1, его надо записать через пробел после первого, с точностью до 2 знака после запятой.
End of explanation
"""


print minValuesAnswer1

with open("AnswerTask1.txt", "w") as fAnswer:
    for item in minValuesAnswer1:
        fAnswer.write(str(item) + ' ')

"""
Explanation: 8. Стоит обдумать полученный результат. Почему ответ отличается в зависимости от начального приближения? Если нарисовать график функции (например, как это делалось в видео, где мы знакомились с Numpy, Scipy и Matplotlib), можно увидеть, в какие именно минимумы мы попали. В самом деле, градиентные методы обычно не решают задачу глобальной оптимизации, поэтому результаты работы ожидаемые и вполне корректные.
End of explanation
"""


from scipy.optimize import differential_evolution

bounds = [(1, 30)]
minFunc1Value4 = differential_evolution(func1, bounds)
print "Minimized f(x) (differential evolution): ", round(minFunc1Value4.fun, 3), "for x = ", round(minFunc1Value4.x, 3)
print "Number of iterations: ", minFunc1Value4.nit

with open("AnswerTask2.txt", "w") as fAnswer:
    fAnswer.write(str(round(minFunc1Value4.fun, 2)))

"""
Explanation: Задача 2. Глобальная оптимизация
1.Теперь попробуем применить к той же функции f(x) метод глобальной оптимизации — дифференциальную эволюцию.
2. Изучите документацию и примеры использования функции scipy.optimize.differential_evolution.
3. Обратите внимание, что границы значений аргументов функции представляют собой список кортежей (list, в который помещены объекты типа tuple). Даже если у вас функция одного аргумента, возьмите границы его значений в квадратные скобки, чтобы передавать в этом параметре список из одного кортежа, т.к. в реализации scipy.optimize.differential_evolution длина этого списка используется чтобы определить количество аргументов функции.
4. Запустите поиск минимума функции f(x) с помощью дифференциальной эволюции на промежутке [1, 30]. Полученное значение функции в точке минимума - ответ в задаче 2. Запишите его с точностью до второго знака после запятой. В этой задаче ответ - только одно число.
5. Заметьте, дифференциальная эволюция справилась с задачей поиска глобального минимума на отрезке, т.к. по своему устройству она предполагает борьбу с попаданием в локальные минимумы.
6. Сравните количество итераций, потребовавшихся BFGS для нахождения минимума при хорошем начальном приближении, с количеством итераций, потребовавшихся дифференциальной эволюции. При повторных запусках дифференциальной эволюции количество итераций будет меняться, но в этом примере, скорее всего, оно всегда будет сравнимым с количеством итераций BFGS. Однако в дифференциальной эволюции за одну итерацию требуется выполнить гораздо больше действий, чем в BFGS. Например, можно обратить внимание на количество вычислений значения функции (nfev) и увидеть, что у BFGS оно значительно меньше. Кроме того, время работы дифференциальной эволюции очень быстро растет с увеличением числа аргументов функции.
End of explanation
"""


def func2(x): return int(func1(x))

xArr2 = np.arange(1., 31., 0.01)
print xArr2
print "Shape x:", xArr2.shape
yArr2 = np.array([func2(x) for x in xArr2])
print yArr2
print "Shape y:", yArr2.shape

plt.plot(xArr2, yArr2)
plt.grid(True)
plt.axis([0, 30, -15, 5])
plt.show()

"""
Explanation: Задача 3. Минимизация негладкой функции
1. Теперь рассмотрим функцию h(x) = int(f(x)) на том же отрезке [1, 30], т.е. теперь каждое значение f(x) приводится к типу int и функция принимает только целые значения.
2. Такая функция будет негладкой и даже разрывной, а ее график будет иметь ступенчатый вид. Убедитесь в этом, построив график h(x) с помощью matplotlib.
End of explanation
"""


minFunc2Value1 = minimize(func2, 30, method = 'BFGS')
print "Minimized f(x) (BFGS method): ", round(minFunc2Value1.fun, 3), "for x = ", round(minFunc2Value1.x, 3)
print "Number of iterations: ", minFunc2Value1.nit

minValuesAnswer2 = np.zeros( (2) )
minValuesAnswer2[0] = round(minFunc2Value1.fun, 2)
print minValuesAnswer2

"""
Explanation: 3. Попробуйте найти минимум функции h(x) с помощью BFGS, взяв в качестве начального приближения x=30. Получившееся значение функции – ваш первый ответ в этой задаче.
End of explanation
"""


minFunc2Value2 = differential_evolution(func2, bounds)
print "Minimized f(x) (BFGS method): ", round(minFunc2Value2.fun, 3), "for x = ", round(minFunc2Value2.x, 3)
print "Number of iterations: ", minFunc2Value2.nit

minValuesAnswer2[1] = round(minFunc2Value2.fun, 2)
print minValuesAnswer2

with open("AnswerTask3.txt", "w") as fAnswer:
    for item in minValuesAnswer2:
        fAnswer.write(str(item) + ' ')

"""
Explanation: 4. Теперь попробуйте найти минимум h(x) на отрезке [1, 30] с помощью дифференциальной эволюции. Значение функции h(x) в точке минимума – это ваш второй ответ в этом задании. Запишите его через пробел после предыдущего.
5. Обратите внимание на то, что полученные ответы различаются. Это ожидаемый результат, ведь BFGS использует градиент (в одномерном случае – производную) и явно не пригоден для минимизации рассмотренной нами разрывной функции. Попробуйте понять, почему минимум, найденный BFGS, именно такой (возможно в этом вам поможет выбор разных начальных приближений).
6. Выполнив это задание, вы увидели на практике, чем поиск минимума функции отличается от глобальной оптимизации, и когда может быть полезно применить вместо градиентного метода оптимизации метод, не использующий градиент. Кроме того, вы попрактиковались в использовании библиотеки SciPy для решения оптимизационных задач, и теперь знаете, насколько это просто и удобно.
End of explanation
"""


