#python #plot #function-composition
#python #построение графика #функция-композиция
Вопрос:
Я изучаю динамические системы, в частности логистическое семейство g (x) = cx (1-x), и мне нужно повторить эту функцию произвольное количество раз, чтобы понять ее поведение. У меня нет проблем с итерацией функции с учетом определенной точки x_0, но опять же, я хотел бы отобразить всю функцию и ее итерации, а не только одну точку. Для построения графика одной функции у меня есть этот код:
import numpy as np
import scipy as sp
import matplotlib.pyplot as plt
def logplot(c, n = 10):
dt = .001
x = np.arange(0,1.001,dt)
y = c*x*(1-x)
plt.plot(x,y)
plt.axis([0, 1, 0, c*.25 (1/10)*c*.25])
plt.show()
Я полагаю, я мог бы решить эту проблему с помощью длинного / сложного метода явного создания списка диапазона каждой итерации, используя что-то вроде следующего:
def log(c,x0):
return c*x0*(1-x)
def logiter(c,x0,n):
i = 0
y = []
while i <= n:
val = log(c,x0)
y.append(val)
x0 = val
i = 1
return y
Но это кажется действительно громоздким, и мне было интересно, есть ли лучший способ. Спасибо
Комментарии:
1. Какую версию Python вы используете? Вы могли бы использовать
itertools.accumulate
в Python> = 3.3.
Ответ №1:
Несколько разных вариантов
Это действительно вопрос стиля. Ваше решение работает и не очень сложно понять. Если вы хотите продолжить эти строки, я бы просто немного изменил их:
def logiter(c, x0, n):
y = []
x = x0
for i in range(n):
x = c*x*(1-x)
y.append(x)
return np.array(y)
Изменения:
- цикл for легче читать, чем цикл while
x0
не используется в итерации (это добавляет еще одну переменную, но ее математически легче понять; x0 — константа)- функция выписывается, так как она является очень простой однострочной (если бы это было не так, ее имя следовало бы изменить на что-то другое
log
, что очень легко спутать с логарифмом) - результат преобразуется в
numpy
массив. (Именно то, что я обычно делаю, если мне нужно что-то построить)
На мой взгляд, функция теперь достаточно разборчива.
Вы также можете использовать объектно-ориентированный подход и создать объект логистической функции:
class Logistics():
def __init__(self, c, x0):
self.x = x0
self.c = c
def next_iter(self):
self.x = self.c * self.x * (1 - self.x)
return self.x
Затем вы можете использовать это:
def logiter(c, x0, n):
l = Logistics(c, x0)
return np.array([ l.next_iter() for i in range(n) ])
Или, если вы можете сделать это генератором:
def log_generator(c, x0):
x = x0
while True:
x = c * x * (1-x)
yield x
def logiter(c, x0, n):
l = log_generator(c, x0)
return np.array([ l.next() for i in range(n) ])
Если вам нужна производительность и у вас большие таблицы, я предлагаю:
def logiter(c, x0, n):
res = np.empty((n, len(x0)))
res[0] = c * x0 * (1 - x0)
for i in range(1,n):
res[i] = c * res[i-1] * (1 - res[i-1])
return res
Это позволяет избежать медленного преобразования np.array
и некоторого копирования материала. Память выделяется только один раз, и это позволяет избежать дорогостоящего преобразования из списка в массив.
(Кстати, если бы вы вернули массив с инициалом x0
в качестве первой строки, последняя версия выглядела бы чище. Теперь первый должен вычисляться отдельно, если желательно избежать копирования вектора вокруг.)
Какой из них лучше? Я не знаю. ИМО, все они читаемы и обоснованы, это вопрос стиля. Однако я говорю только на очень разбитом и плохом Pythonic, поэтому могут быть веские причины, по которым что-то еще лучше или почему что-то из вышеперечисленного нехорошо!
Производительность
О производительности: на моей машине я попробовал следующее:
logiter(3.2, linspace(0,1,1000), 10000)
Для первых трех подходов время по существу одинаковое, примерно 1,5 с. Для последнего подхода (предварительно выделенный массив) время выполнения составляет 0,2 с. Однако, если преобразование из списка в массив удалено, первое выполняется за 0,16 с, поэтому время действительно тратится на процедуру преобразования.
Визуализация
Я могу придумать два полезных, но совершенно разных способа визуализации функции. Вы упоминаете, что для начала у вас будет, скажем, 100 или 1000 разных x0. Вы не указываете, сколько итераций вы хотите иметь, но, возможно, мы начнем только со 100. Итак, давайте создадим массив со 100 различными x0 и 100 итерациями при c = 3.2.
data = logiter(3.6, np.linspace(0,1,100), 100)
В некотором смысле стандартным методом визуализации функции является рисование 100 строк, каждая из которых представляет одно начальное значение. Это просто:
import matplotlib.pyplot as plt
plt.plot(data)
plt.show()
Это дает:
Ну, кажется, что все значения в конечном итоге где-то колеблются, но кроме этого у нас есть только беспорядок цвета. Этот подход может быть более полезным, если вы используете более узкий диапазон значений для x0:
data = logiter(3.6, np.linspace(0.8,0.81,100), 100)
вы можете закодировать начальные значения цветом, например:
color1 = np.array([1,0,0])
color2 = np.array([0,0,1])
for i,k in enumerate(np.linspace(0, 1, data.shape[1])):
plt.plot(data[:,i], '.', color=(1-k)*color1 k*color2)
При этом первые столбцы (соответствующие x0 = 0.80) отображаются красным цветом, а последние столбцы — синим, и между ними используется постепенное изменение цвета. (Обратите внимание, что чем синее точка, тем позже она нарисована, и, следовательно, синие перекрывают красные.)
Однако можно использовать совершенно другой подход.
data = logiter(3.6, np.linspace(0,1,1000), 50)
plt.imshow(data.T, cmap=plt.cm.bwr, interpolation='nearest', origin='lower',extent=[1,21,0,1], vmin=0, vmax=1)
plt.axis('tight')
plt.colorbar()
дает:
Это мой личный фаворит. Я не буду портить никому радость, объясняя это слишком подробно, но IMO это очень легко показывает многие особенности поведения.
Комментарии:
1. Спасибо, я плохо разбираюсь в python, так что это довольно проницательно. Как бы вы предложили построить график всей итерированной функции? т.е. как бы вы сгенерировали и построили график для 100-1000 разных x0?
2. Это интересный вопрос. См. Редактирование в конце ответа.
3. Хорошо, итак, я все еще не совсем понял более глубокие аспекты функций, которые вы написали (и под этим, я полагаю, я имею в виду, что я не понял ядро библиотеки, которую мы используем), но мне удалось сделать то, что я изначально надеялся сделать, что я добавлю в»ответ на мой собственный вопрос», но спасибо, DrV, за то, что вы внесли, это действительно красиво. И на самом деле, то, что вы написали, было тем, что мой «коллега» действительно хотел, чтобы я написал, и я думаю, что это более проницательно, чем то, что я только что написал, так что еще раз спасибо.
Ответ №2:
Вот к чему я стремился; косвенный подход к пониманию (путем визуализации) поведения начальных условий функции g(c, x) = c x (1-x):
def jam(c, n):
x = np.linspace(0,1,100)
y = c*x*(1-x)
for i in range(n):
plt.plot(x, y)
y = c*y*(1-y)
plt.show()