Вараны острова Комодо, также называемые в литературе драконами, — самая крупная из живущих на земле ящериц. Длина его тела может достигать 3 метров, а масса 140 кг [1]. Это доминирующий хищник своего региона, который может добывать животных (свиньи, буйволы, олени), порой 10-ти кратно превосходящих его весу.
Важнейшим инструментом такой охотничьей эффективности являются зубы. У комодского варана их 60 штук [2], изогнутых как сабли и острых как бритва (край зуба усилен металлизированным слоем, образующим микро пилу [3]).
Этот комплект еще и регулярно, раз в 40 дней обновляется [4]. Не нужно ни стоматологов ни заточников — просто мечта. Однако фантастическая скорость роста зубов должна требовать и фантастических затрат «стройматериалов». Сколько, например, кальция и железа нужно варану в день для поддержания такого темпа?
Ниже мы оценим эти показатели, опираясь на «ангем», «матан» и python. Кто не испугался, welcome.
Содержание
Введение
Оценивать в миллиграммах содержание в ткани того или иного микроэлемента будем по формуле , где — плотность, V — объем.
Чтобы рассчитать объем, нужно знать форму объекта. А лучше — уравнение поверхности, его ограничивающей. С этого и начнем. Вообще задача моделирования поверхности интересна сама по себе, поэтому решим ее в нескольких вариантах, переходя от более простого к более точному.
Геометрия зуба
В качестве экспериментальных данных возьмем фотографии и описание из недавнего исследования, опубликованного в журнале Nature Ecology & Evolution [3]:
На изображении видно, что зубы варана имеют загнутую и слегка сплющенную с боков форму. Длина зубов 2.5–3 см (что также указано в [1]). Зафиксируем для дальнейших расчетов высоту зуба равной 25 мм, глубину 10 мм, ширину 4 мм.
Зуб варана v.1 (конус)
Объем конического зуба
В качестве первого приближения возьмем конус с эллиптическим основанием.
Объем конуса, как известно со школы,
где S — площадь основания, h — высота.
В нашем случае h = 25 мм
В случае эллипса ,
где a и b — большая и малая полуоси эллипса, лежащего в основании.
В нашем случае a = 5 мм, b = 2 мм.
Таким образом,
Подставим численные значения:
Итак объем зуба V = 261.7 мм3. Это наша первая грубая оценка.
Уравнение поверхности и визуализация
Способ 1 (быстрый)
Изобразим конус в логике аналитической геометрии, т.е. опираясь на уравнение его поверхности.
Уравнение конуса запишется как
Или, после подстановки значений a, b и h:
Чтобы задать конечную высоту зубу (сам по себе конус фигура бесконечная), наложим ограничение на диапазон изменения z:
Для быстрой визуализации можем воспользоваться, например, сервисом desmos.com. Вводим полученное уравнение как есть и наблюдаем изображение конуса.
Способ 2 (с полным контролем)
Изобразим конус при помощи Python и Matplotlib.
В данном случае будет удобнее взять уравнение конуса в параметрическом виде:
где параметры изменяются в следующих диапазонах: ,
Код:
import numpy as np import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D # Параметры конуса a, b, h = 2, 5, 25 # Координаты точек на поверхности конуса phi = np.linspace(0, 2 * np.pi, 30) tz = np.linspace(-h, 0, 20) phi, tz = np.meshgrid(phi, tz) # Параметрическое уравнение конуса z = tz x = (a / h) * tz * np.cos(phi) y = (b / h) * tz * np.sin(phi) # Создание графика ax = plt.figure().add_subplot(111, projection='3d') ax.view_init(elev=20, azim=-40, roll=0) ax.plot_surface(x, y, z, color='w', edgecolor='royalblue', rstride=2, cstride=2, alpha=0.5) ax.set_aspect('equal') # Диапазоны и настройки графика x_lims = [-2a, 2a] y_lims = [-1.5b, 1.5b] z_lims = [-h, 0] ax.set(xlim=x_lims, ylim=y_lims, zlim=z_lims, xlabel='X', ylabel='Y', zlabel='Z', xticks=np.round(np.linspace(*x_lims, 5))) plt.show()
В результате получим следующий график:
Однако поверхность зуба все же изогнутая, а не линейная как у конуса, поэтому стоит добавить кривизны. Понизим степень в третьем слагаемом и получим уравнение параболоида.
Зуб варана v.2 (параболоид)
На сей раз начнем с уравнения поверхности.
Уравнение поверхности и визуализация
Уравнение параболоида имеет вид
При подстановке заданных параметров получаем следующее уравнение боковой поверхности зуба:
Как и прежде, ограничим диапазон изменения z:
Вводим полученное уравнение в сервисе desmos.com или аналогичном и наблюдаем соответствующий график параболоида.
Объем параболического зуба
Найдем объем зуба параболической формы как тройной интеграл по области пространства, ограниченной поверхностью параболоида и плоскостью .
В общем случае в выражении через повторные интегралы это выглядит так
Чтобы удобно расставить пределы интегрирования выполним с помощью Matplotlib дополнительный чертеж.
Код для дополнительного чертежа
import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import axes3d import numpy as np a, b, h = 2, 5, 25 # Создание поля для графика fig = plt.figure(constrained_layout=True, figsize=(7,5)) spec = fig.add_gridspec(ncols=2, nrows=1) spec_right = spec[1].subgridspec(ncols=1, nrows=5) ax1 = fig.add_subplot(spec[0, 0], projection='3d') ax2 = fig.add_subplot(spec_right[1:4]) # ---------- График-1: Параболоид в 3D ----------- ax1.view_init(elev=20, azim=-40, roll=0) # диапазоны по осям и сетка x_lims = [-1.5a, 1.5a] y_lims = [-1.5b, 1.5b] z_lims = [-1.0*h, 0] X, Y = np.linspace(*x_lims, 50), np.linspace(*y_lims, 50) X, Y = np.meshgrid(X, Y) def z_func(X, Y): return np.where((X2 / a2 + Y2 / b2) <= 1, -h * (X2 / a2 + Y2 / b2) , -h) Z = z_func(X,Y) # Рисуем 3D поверхность ax1.plot_surface(X, Y, Z, edgecolor='royalblue', lw=0.5, rstride=3, cstride=3, alpha=0.3) ax1.set_aspect('equal') # Проекции на координатные плоскости ax1.contour(X, Y, Z, levels=[-h], zdir='z', offset=z_lims[0], colors=['black']) # Подписи к графику ax1.text(x_lims[0]-1, y_lims[0], -1, r'', (0, 1, 0)) ax1.text(x_lims[0]-1, y_lims[0], -h, r'', (0, 1, 0)) ax1.set(xlim=x_lims, ylim=y_lims, zlim=z_lims, xlabel='X', ylabel='Y', zlabel='Z', xticks=np.linspace(*x_lims, 4)) # ------ График-2: Проекция на плоскость основания ------ # Эллипс phi = np.linspace(0, 2*np.pi, 50) X = a * np.cos(phi) Y = b * np.sin(phi) ax2.plot(X, Y, '--') # Четвертинка X2 = np.linspace(0, a, 50) Y2 = b * np.sqrt(1 — X22 / a2) ax2.plot(X2, Y2, color='orange') ax2.plot(X2, Y2*0, color='orange') ax2.fill_between(X2, Y2, 0, color='blue', alpha=.1) # Аннотации ax2.text(0,-0.6, r'', rotation='vertical') ax2.text(a-0.2,-0.6, r'', rotation='vertical') ax2.text(a + 0.2, 0, r'') ax2.text(a + 0.2, b, r'') # Настройки отображения ax2.grid() ax2.set(xlabel=r'X', ylabel=r'Y') plt.show()
Поскольку фигура симметричная, достаточно получить объем одной четверти, а потом умножить на 4. Это позволит выставить часть приделов равными 0, упростив тем самым выражение.
Таким образом для вычисления одной четверти объема:
-
интеграл по dz берем в пределах от поверхности (плоскости) основания
до поверхности параболоида , -
в интеграле по dy смотрим на проекцию параболоида на плоскость основания (получается эллипс) и берем в пределах
от прямой
до эллиптической границы основания , -
в интеграле по dx смотрим на проекцию основания на ось координат Ox берем в пределах от точки до самой правой точки проекции
Объем всей фигуры тогда запишется как
Вычислим его с помощью функции tplquad() библиотеки SciPy:
from scipy import integrate a, b, h = 2, 5, 25 # Подынтегральная фунция и пределы интегрирования f = lambda x,y,z: 1 x_lims = [0, a] y_lims = [0, lambda x: b * (1 — x*2 / a**2)0.5] z_lims = [-h, lambda x, y: -h * (x2 / a2 + y2 / b*2)] # Тройной интеграл V, err = integrate.tplquad(f, *x_lims, *ylims, *zlims) print(f'Объем = {V * 4:.2f}')
Объем зуба с параболическими обводами равен V=392.7 мм3 .
Продолжим совершенствование формы зуба и сделаем его более острым и изогнутым
Зуб варана v.3 (логарифм)
Уравнение поверхности и визуализация
Нам нужно «загнуть» центральную ось нашего параболического зуба. То есть, если посмотреть на рисунок ниже, — перейти от ситуации с левой верхней схемы к левой нижней.
Код рисунка
import sympy as sp import numpy as np from sympy.abc import x, y, z from spb import * from matplotlib.gridspec import GridSpec from sympy.printing.latex import LatexPrinter a, b, h = 2, 5, 25 la_print = LatexPrinter() # рендеровщик LaTex формул # Уравнение параболоида с логарифмическим сдвигом по "y" left_part_eq = x**2/a**2 + (y + 2*sp.log(1-z))**2/b**2 + z/h equations = [left_part_eq] # Выразим "y" из уравнения sol1, sol2 = sp.solve(equations, y, dict=True) # Достанем программно логарифм из уравнения "загнутого" зуба log_line = left_part_eq.args[2].args[1].args[0].args[1] log_line # Кривая смещения eq_log = sp.Eq(y, -log_line) # Уравнение параболической поверхности зуба parab_left = x**2 / a**2 + y**2 / b**2 + z / h z_sol_parab = sp.solve([parab_left], z) # Проекция параболоида eq_parab = sp.Eq(z, z_sol_parab[z].subs({x:0})) # Параметры отрисовки params = {'aspect':(1,1), 'legend':False, 'show':False} params_proj = {'aspect':(1,1), 'legend':False, 'show':False} ranges_par = [(y, -4*b, 2*b), (z, -h, 0)] ranges_log = [(y, -4*b, 2*b), (z, -h, 0)] x_range = (x, -4*a, 4*a) log_style = {"linestyles": "dashed", "linewidths": 1} bacolor = 'dodgerblue' # -------- График-1: Параболический зуб в профиль -------- # -- Аннотации shift_arrows = [] for z_val in np.linspace(-0.2*h, -0.8*h, 3): y_val = -log_line.subs({z:z_val}).n() arrow = {'text': '', 'xy': (y_val, z_val), 'xytext': (0, z_val), 'arrowprops': dict(arrowstyle="->")} shift_arrows.append(arrow) # -- Парабола, вертикальное сечение зуба p1 = plot_implicit(z < z_sol_parab[z].subs({x:0}), *ranges_par, **params, xlabel='y', ylabel='z') # -- Кривая куда смещаем, искривляя, ось p1_logos = plot_implicit(eq_log, *ranges_par, color='red', **params, annotations=shift_arrows, rendering_kw=log_style,) # -- Кастомная легенда legend_xy = (ranges_par[0][1], -2) pretty_log_leg = la_print.doprint(sp.Eq(y,-log_line)).replace('log', 'ln') text_legend = {'text': f'\n${pretty_log_leg}$', 'xy': (0, 0), 'xytext': legend_xy, 'fontsize': 8, 'bbox': dict(boxstyle="square", fc="w")} # -- Текущая прямая ось зуба-парабалода p1_os = plot_implicit(sp.Eq(y, 0), *ranges_par, **params, color="white", rendering_kw={"linestyles": "-.", "linewidths": 4}) # -- График-2: Проекции на основание параболического зуба -- # -- Эллипс, проекция на основание p1_base = plot_implicit(parab_left.subs({z:0}) <= 1, (y, ranges_par[0][1], ranges_par[0][2]), x_range, markers=[{'args': [0, 0, 'x'], 'color':'white'}], #annotations=[text_para], **params_proj, color=bacolor) # -- Стрелка направление смещения основания в new_cent = -log_line.subs({z:-h}).n() # центр нового основания shift_arrow_base = {'text': '', 'xy': (new_cent, 0), 'xytext': (0, 0), 'arrowprops': dict(arrowstyle="->")} size_arrow_base = {'text': '', 'xy': (new_cent, 3), 'xytext': (0, 3), 'fontsize': 7, 'arrowprops': dict(arrowstyle="|-|")} pretty_log = la_print.doprint(log_line).replace('log', 'ln') text_log = {'text': f'${pretty_log}$', 'xy': (0, 0), 'xytext': (new_cent, 5)} # -- Эллипс, смещенная в новое положение проекция p1_base_sh = plot_implicit(sp.Eq(left_part_eq.subs({z:-h}),0), (y, ranges_par[0][1], ranges_par[0][2]), x_range, annotations=[shift_arrow_base, size_arrow_base, text_log], rendering_kw={"linestyles": "--", "linewidths": 1}, markers=[{'args': [new_cent, 0, 'x'], 'color':'red'}], color='gray', **params_proj, xlabel='y', ylabel='x') # ------- График-3: Логарифмический зуб в профиль -------- # -- Вертикальное сечение изогнутого зуба p2 = plot_implicit(sp.And(y >= sol1[y].subs({x:0}), y <= sol2[y].subs({x:0})), *ranges_log, **params) # -- Центральная ось изогнутого зуба p2_os = plot_implicit(eq_log, *ranges_par, **params, color="white", rendering_kw={"linestyles": "-.", "linewidths": 4}) # -- Кривая смещения (куда таки сместили, изогнув, параболоид) p2_logos = plot_implicit(eq_log, *ranges_log, color='red', **params, rendering_kw=log_style) # -- График-4: Проекции на основание логарифмического зуба -- # -- Эллипс, проекция на основание p2_base = plot_implicit(left_part_eq.subs({z:-h}) <= 0, (y, ranges_log[0][1], ranges_log[0][2]), x_range, markers=[{'args': [0, 0, 'x'], 'color':'gray'}], #annotations=[text_loga], **params_proj, color=bacolor) # -- Эллипс, старая проекция, откуда смещались p2_base_sh = plot_implicit(sp.Eq(parab_left.subs({z:-h}),0), (y, ranges_log[0][1], ranges_log[0][2]), x_range, annotations=[shift_arrow_base], rendering_kw={"linestyles": "--", "linewidths": 1}, markers=[{'args': [new_cent, 0, 'x'], 'color':'red'}], color='gray', **params_proj, xlabel='y', ylabel='x') # Собираем изображения в сетку общего рисунка gs = GridSpec(2, 2) mapping = { gs[0, 0]: p1 + p1_logos + p1_os, # + p1_log_legend, gs[0, 1]: p1_base_sh + p1_base, gs[1, 0]: p2 + p2_os + p2_logos, gs[1, 1]: p2_base_sh + p2_base, } plotgrid(gs=mapping, size=(7,7), legend=False);
Это можно сделать, например, задав переменное смещение для y-координаты.
Способ-1 (канонический)
Уравнение параболоида имеет вид .
Такое мы использовали выше для задания формы зуба в разделе Зуб варана v.2 (параболоид).
Уравнение параболоида с смещением s по y имеет вид
И если s — это просто некоторое положительное число, то весь параболоид сдвинется вдоль оси Oy влево. А если величина s зависит от z, то есть принимает какое-то свое значение для каждого горизонтального сечения параболоида, то мы имеем возможность сместить каждое сечение персонально на нужную нам величину. Схемы такого смещения показаны в правой части вышеприведенного рисунка.
Опираясь на знание, как ведет себя логарифм, и чувство прекрасного, зададим s как функцию от z следующего вида:
Тогда уравнение примет вид
Подставим значения a, b и h и получим искомое уравнение боковой поверхности искривленного драконьего зуба:
Снизу, как обычно, объем зуба ограничен плоскостью .
Вводим полученное уравнение в сервисе desmos.com или аналогичном и наблюдаем изображение.
Способ 2 (параметрический)
Уравнение параболоида можно записать и в параметрическом виде, где аналогично способу-1 задать смещение s для y-координаты:
после подстановки найденной выше
где параметры изменяются в следующих диапазонах: ,
Изобразим драконий зуб программно:
import numpy as np import sympy as sp from mpl_toolkits.mplot3d import Axes3D import matplotlib.pyplot as plt from sympy.abc import x, y, z a, b, h = 2, 5, 25 # функция нелинейного сдвига log_line = 2*sp.log(1-z) # векторизуем функцию логарифмического смещения для расчета сетки ниже v_log_line = np.vectorize(lambda tz: float(log_line.subs({z:tz}).n())) # сетка для переменных-параметров tz = np.linspace(0, -np.sqrt(h), 30) phi = np.linspace(0, 2 * np.pi, 30) # одна сторона зуба tz, phi = np.meshgrid(tz, phi) # подставим сетку в параметрическое уравнение параболоида со смещением Z = -tz**2 X = (a / np.sqrt(h)) * tz * np.cos(phi) Y = (b / np.sqrt(h)) * tz * np.sin(phi) - v_log_line(-tz**2) # График ax = plt.figure().add_subplot(projection='3d') ax.view_init(elev=30, azim=10, roll=0) ax.plot_surface(X, Y, Z, color='w', edgecolor='royalblue', rstride=2, cstride=2, alpha=0.3) # Диапазоны и настройки графика x_lims = [-2*a, 2*a] y_lims = [-3*b, 0] z_lims = [-h, 0] ax.set(xlim=x_lims, ylim=y_lims, zlim=z_lims, xlabel='X', ylabel='Y', zlabel='Z', xticks=np.round(np.linspace(*x_lims, 5),1), yticks=np.round(np.linspace(*y_lims, 6),1) ) ax.set_aspect('equal') plt.show()
Объем искривленного зуба варана
Разрежем мысленно фигуру на элементарные, предельно тонкие, слои — с высотой dh и площадью основания S. Объем каждого такого слоя будет равен :
Из параметрических уравнений поверхности . Найдем дифференциал
Площадь основания слоя S будет изменяться в зависимости от высоты, на которой вырезаем слой. По форме это в любом случае будет эллипс, но с различными полуосями. Обозначим их за a’ и b’ (штрихи просто как штрихи, не производные) и вспомним формулу площади эллипса:
Опять же из параметрических уравнений поверхности явно видны выражения для полуосей (множители перед синусом и косинусом):
,
Подставим их в формулу площади эллипса:
А ее, в свою очередь, подставим в выражение для элементарного объема
Берем интеграл от левой и правой частей
Интегрируем мы в данной статье все численно, не будем делать исключение и для этого интеграла. Подставим значения a,b и h и найдем объем с помощью функции quad() библиотеки SciPy
from scipy import integrate import numpy as np a, b, h = 2, 5, 25 # Подынтегральная функция def f(t): return - 2 * np.pi * a * b * t**3 / h # Интеграл V, err = integrate.quad(f, -np.sqrt(h), 0) print(f'V = {V}')
V = 392.7 мм3.
Объем получился такой же, как для зуба в форме обычного параболоида. Что логично, поскольку мы лишь параллельно сдвинули слои в сторону, не изменив ни их форму, ни толщину. Аналогично тому, как если положить на стол колоду карт и сдвинуть верхние слои относительно нижних, объем колоды останется прежним (привет принципу Кавальери).
Объем искривленного зуба варана с полостью
В заключение учтем тот факт, что зуб варана имеет полость внутри [4]. Ориентируясь на продольный срез зуба, приведенный на Fig.3 из [4], опишем форму полости как аналогично параболическую с высотой 80% от высоты зуба и размерами основания в 30% от соответствующих размеров основания зуба. То есть с h = 20 мм, a = 0.6 мм, b = 1.5 мм. Подставив эти значения в формулу/код из предыдущего раздела получим Vполости = 28.3 мм3
И значит объем костной ткани зуба V = Vобщий — Vполости = 392.7 — 28.3 = 364.4 мм3.
Кальций. Сколько вешать в граммах
Зная объем и плотность вещества можно найти его массу. Объем в наличии, осталось определится с веществом и плотностью.
Зуб варана состоит из дентина и покрывающей его эмали. Ввиду того, что слой эмали у комодского дракона чрезвычайно тонкий — 20 микрометров [3] — в рамках данной модели им пренебрегаем и считаем, что весь найденный выше объем занят дентином.
Каково экспериментальное значение плотности дентина у комодских варанов, мне, к сожалению, найти не удалось (если кто-то из читателей владеет такой информацией, буду признателен). Возьмем в качестве приближения параметры зубных тканей крокодила — рептилии схожей с вараном по размеру и хищному способу добывания пищи.
Минералом, придающим прочность зубам крокодила, является гидроксиапатит [5].
Найдем, каков процент его массы составляет чистый кальций.
Химическая формула гидроксиапатита [6], плотность =3.14 г/см3 [7] .
Массы атомов, входящих в формулу: , , , (где u — атомная единица массы).
Откуда общая масса рассматриваемой молекулы
= 40.078 ∙ 5 + (30.974 + 15.999 ∙ 4) ∙ 3 + (15.999 + 1.008) = 502.307 u
И значит на долю кальция по массе приходится = (40.078 ∙ 5) / 502.307 = 0.3989 = 39.89 %
Содержание самого гидроксиапатита в дентине составляет = 0.713 = 71.3 % . Остальное — органические компоненты [5].
Таким образом, чистого кальция в дентине содержится = 0.713 ∙ 0.3989 = 0.2844 = 28.44 % .
Оставшиеся 42.9% — это суммарная доля фосфора, кислорода и водорода.
Массовые доли рассматриваемых компонентов вещества зуба показаны на диаграмме ниже. Минеральная часть выделена сплошной заливкой, органическая — заполнением точками.
Найдем массу дентина одного драконьего зуба.
= 364.4 мм3 ∙ 3.14 ∙ 10-3 г/мм3 = 1.144 г
Значит масса чистого кальция в одном зубе комодского варана составит
= 0.325 г
Поскольку всего зубов у дракона с острова Комодо 60 штук, для их полной замены необходимо 60 ∙ = 19.5 г кальция.
Если поделить это на 40 дней (период обновления зуба согласо [4]), то получим 0.488 гр./сутки = 488 мг/сутки.
Именно столько кальция требуется варану для обеспечения динамики регулярного обновления комплекса зубов.
Норма потребления по кальцию в целом будет выше, т.к. кроме зубов он содержится в костной ткани, необходим для сокращения скелетных мышц, передачи нервных импульсов и ряда других процессов в организме. К примеру, норма потребления кальция для человека в возрасте от 16 до 50 лет, когда роста зубов уже не происходит, составляет 800-1000 мг/сутки [8].
Режущая кромка
Режущая кромкаКромка v.1 (ровная)
Зуб комодского дракона замечателен не только своей формой, но и наличием укрепленной железом кромки [3]. На рисунке ниже она выделяется оранжевым цветом и волнистой формой.
Для начала опишем геометрию ровной кромки, без учета идущей по ней «волны».
Уравнение кривой и визуализация
Поскольку кромка зуба это линия, принадлежащая его поверхности, воспользуемся полученным ранее параметрическим представлением поверхности.
Кромку будет образовывать множество точек со значениями углового параметра и (соответствующие противоположным краям), при том, что параметр t, отвечающий за положение по высоте, пробегает прежний диапазон значений .
Такое представление пригодится нам для трехмерной визуализации.
Однако в данном случае (и для будущего расчета длины кромки) можно обойтись и двумерным графиком в центральной вертикальной плоскости (), содержащей оба противоположных режущих края.
При фиксированном x = 0 в системе остается два уравнения:
Которые можно свести в одно, выразив t через z из первого уравнения и подставив во второе. Итого
Подставим известные значения b и h.
Для переднего края (обозначен на рисунке ниже L1) с приходим к следующему уравнению в декартовых координатах:
Для заднего края (обозначен на рисунке ниже L2) с — к уравнению:
Построим графики:
Код для построения графиков
import numpy as np import sympy as sp from mpl_toolkits.mplot3d import Axes3D import matplotlib.pyplot as plt from sympy.abc import x, y, z a, b, h = 2, 5, 25 # Кривая смещения log_line = 2*sp.log(1-z) # Поле рисунка для построения графиков fig = plt.figure(constrained_layout=True, figsize=(8,5)) spec = fig.add_gridspec(ncols=2, nrows=1) spec_right = spec[1].subgridspec(ncols=1, nrows=5) ax1 = fig.add_subplot(spec[0, 0], projection='3d') ax2 = fig.add_subplot(spec_right[1:4]) # ------- График-1: зуб с выделенной режущей кромкой -------- # векторизуем функцию логарифмического смещения для расчета сетки ниже v_log_line = np.vectorize(lambda tz: float(log_line.subs({z:tz}).n())) # сетка для переменных-параметров tz = np.linspace(0, np.sqrt(h), 30) phi_0 = np.linspace(0, 2 * np.pi, 30) # поверхность зуба phi_1 = np.linspace((3/2)*np.pi, (3/2)*np.pi, 30) # режущая кромка зуба phi_2 = np.linspace(np.pi / 2, np.pi/ 2, 2) # режущая кромка зуба tz_0, phi_0 = np.meshgrid(tz, phi_0) tz_1, phi_1 = np.meshgrid(tz, phi_1) tz_2, phi_2 = np.meshgrid(tz, phi_2) # График ax1.view_init(elev=30, azim=-35, roll=0) for tz, phi, col, lw in ([tz_0, phi_0, 'royalblue', 1], [tz_1, phi_1, 'r', 2], [tz_2, phi_2, 'r', 2]): # подставим сетку в параметрическое уравнение параболоида со смещением Z = -tz**2 X = (a / np.sqrt(h)) * tz * np.cos(phi) Y = (b / np.sqrt(h)) * tz * np.sin(phi) - v_log_line(-tz**2) ax1.plot_surface(X, Y, Z, color='w', edgecolor=col, lw=lw, rstride=5, cstride=5, alpha=0.3) # Диапазоны и настройки графика x_lims = [-2*a, 2*a] y_lims = [-3*b, 0] z_lims = [-h, 0] ax1.set_aspect('equal') ax1.set(xlim=x_lims, ylim=y_lims, zlim=z_lims, xlabel='X', ylabel='Y', zlabel='Z', xticks=np.round(np.linspace(*x_lims, 5),1), yticks=np.round(np.linspace(*y_lims, 6),1) ) # -------- График-2: режущая кромка на плоскости --------- # Уравнение боковой поверхности изогнутого зуба left_part_eq = x**2/a**2 + (y + log_line)**2/b**2 + z/h equations = [left_part_eq] sol1, sol2 = sp.solve(equations, y, dict=True) print(sol1, sol2) # Проекция вдоль Ox на плоскость Oyz zy_section1 = sol1[y].subs({x:0}) zy_section2 = sol2[y].subs({x:0}) print(zy_section1, zy_section2) # Диапазоны zs = np.linspace(*z_lims, 30) ys1 = [zy_section1.subs({z:val}).n() for val in zs] ys2 = [zy_section2.subs({z:val}).n() for val in zs] # График ax2.plot(ys1, zs, label='$L_1$') ax2.plot(ys2, zs, label='$L_2$') # Настройки графика ax2.set(xlim=y_lims, ylim=z_lims, xlabel='Y', ylabel='Z') ax2.set_aspect('equal') ax2.grid() ax2.legend() plt.show()
Длина ровной режущей кромки
Длину кромки найдем при помощи криволинейного интеграла 1го рода.
Длина передней кромки:
Длина задней кромки вычисляется по той же схеме, отличаясь лишь знаками в выражении для :
И полная длина
Возьмем данные интегралы численно и определим итоговую длину.
import sympy as sp from sympy.abc import x, y, z from scipy import integrate a, b, h = 2, 5, 25 # Кривая смещения оси log_line = 2*sp.log(1-z) # Уравнение боковой поверхности изогнутого зуба left_part_eq = x**2/a**2 + (y + log_line)**2/b**2 + z/h equations = [left_part_eq] # разрешим относительно y sol1, sol2 = sp.solve(equations, y, dict=True) print('Решения отностительно y: \n', sol1, sol2) # Проекция вдоль Ox на плоскость Oyz zy_section1 = sol1[y].subs({x:0}) zy_section2 = sol2[y].subs({x:0}) print('Проекции: \n', zy_section1, zy_section2) # Берем производные derivative_y1 = sp.diff(zy_section1, z) derivative_y2 = sp.diff(zy_section2, z) print('Их производные: \n', derivative_y1, derivative_y2) # Подынтегральная функция для криволинейного интеграла def f(z_val, curve_name): derivative = {'L1': derivative_y1, 'L2': derivative_y2} return (1 + derivative[curve_name].subs({z:z_val}).n()**2) ** 0.5 # Интеграл length = integrate.quad(f, -h, 0, 'L1')[0] + integrate.quad(f, -h, 0, 'L2')[0] print(f'Длина кромки {length} мм')
Таким образом длина ровного режущего края l=54.18 мм
Кромка v.2 (волнистая)
Циклоида путешественница
Для начала вспомним, как выглядит параметрическое уравнение циклоиды:
Если производящая окружность будет катиться не по горизонтальной прямой , а по кривой , то уравнение примет следующий вид:
Как можно заметить, здесь использован тот же принцип с заданием функции смещения по y, что и при искривлении оси параболоида в разделе Зуб варана v.3 (логарифм).
Такая кривая является частным случаем квазитрохоиды (которая в общем случае описывает сочетание произвольного числа разновидностей поступательного и вращательного движений).
Демонстрационный пример
Рассмотрим демонстрационный пример — с опорной кривой вида , по которой отправим в путешествие производящую окружность радиусом R=0.5.
или
Что на графике будет выглядеть так:
Код для построения графика:
import matplotlib.pyplot as plt import numpy as np R = 0.5 # радиус производящей окружности # Уравнение циклоиды, опирающейся на наклонную прямую x = lambda t: R * (t — np.sin(t)) line = lambda t: 0.1 * x(t) # опорная прямая y = lambda t: R * (1 — np.cos(t)) + line(t) # Подготовка данных для графика ts = np.linspace(0, 6*np.pi, 100) xs = [x(t) for t in ts] ys = [y(t) for t in ts] framework = [line(t) for t in ts] # График fig, ax = plt.subplots() ax.plot(xs, ys, label='L') ax.plot(xs, framework) # Оформление графика ax.grid() ax.legend() plt.show()
Уравнение волнистой режущей кромки и визуализация
Теперь направим производящую окружность квазитрохоиды вдоль режущей кромки зуба, уравнения для которой мы получили ранее в разделе Кромка v.1 (ровная).
Передняя кромка зуба описывается уравнением
Значит параметрическое уравнение квазитрохоиды, опирающейся на эту кромку будет иметь вид
Подставим выражение для y1
Заменим z во втором уравнении на ее выражение через параметр t из первого уравнения
Получили параметрическое уравнение волнистой передней кромки зуба при радиусе производящей окружности R=0.5.
Такое значение радиуса обеспечит нам наглядность чертежа. Далее при расчете длины возьмем значение по экспериментальным данным (меньше на порядок).
Аналогично и для задней кромки. Уравнение опорной кривой имеет вид
И, значит, параметрическое уравнение для задней волнистой кромки:
Параметр t, пробегает значения от до 0, где — решение уравнения .
Воспроизведем это в программном коде и графической форме.
Первым этапом находим уравнения для ровных передней и задней кромок.
import matplotlib.pyplot as plt import numpy as np import sympy as sp from sympy.abc import x, y, z, u a, b, h = 2, 5, 25 R = 0.5 # радиус производящей окружности # Кривая смещения оси log_line = 2*sp.log(1-z) # Уравнение боковой поверхности изогнутого зуба left_part_eq = x2/a2 + (y + log_line)2/b2 + z/h equations = [left_part_eq] # Разрешим относительно y sol1, sol2 = sp.solve(equations, y, dict=True) print('Решения относительно y: \n', sol1, sol2) # Проекция вдоль Ox на плоскость Oyz zy_section1 = sol1[y].subs({x:0}) zy_section2 = sol2[y].subs({x:0}) print('Проекции: \n', zy_section1, zy_section2)
Получили выражения для передней и задней ровных кромок (в коде выше zy_section1 и zy_section2). Применим их в качестве направляющих для развертывания волнообразного режущего края.
# Параметрическое уравнение квазитрохоиды на направляющих кромках Z = lambda t: R * (t - np.sin(t)) # Направляющие line_1 = lambda t: float(zy_section1.subs({z:Z(t)}).n()) line_2 = lambda t: float(zy_section2.subs({z:Z(t)}).n()) Y_1 = lambda t: -R * (1 - np.cos(t)) + line_1(t) Y_2 = lambda t: R * (1 - np.cos(t)) + line_2(t)
Определим пределы изменения переменных
# Границы и диапазоны y_lims = [-3*b, 1] z_lims = [-h, 1] # при каком значении параметра z = -h (т.е. на нижнем крае) equation = sp.Eq(R * (u - sp.sin(u)), -h) bottom_u = float(sp.nsolve(equation, u, -1)) # z = 0 очевидно при u = 0 top_u = 0 print(f'Пределы изменения параметра u: от {bottom_u} до {top_u}') # генерируем данные для построения графика ts = np.linspace(bottom_u, top_u, 100) ys_1, ys_2 = [Y_1(t) for t in ts], [Y_2(t) for t in ts] zs_1, zs_2 = [Z(t) for t in ts], [Z(t) for t in ts] framework_y1 = [float(zy_section1.subs({z:z_val}).n()) for z_val in zs_1] framework_y2 = [float(zy_section2.subs({z:z_val}).n()) for z_val in zs_2]
Построим график
# График fig, ax = plt.subplots() ax.plot(ys_1, zs_1, label='$C_1$') ax.plot(framework_y1, zs_1, '--', label='$L_1$') ax.plot(ys_2, zs_2, label='$C_2$') ax.plot(framework_y2, zs_2, '--', label='$L_2$') # параметры отображения графика ax.set(xlim=y_lims, ylim=z_lims, xlabel='Y', ylabel='Z') ax.set_aspect('equal') ax.grid() ax.legend() plt.show()
Длина волнистой режущей кромки
Длина кривой L, заданной параметрически в зависимостями и , где , будет вычисляться по формуле
Займемся ее вычислением.
Зададимся уравнением боковой поверхности зуба и найдем уравнения для передней и задней кромок. Эту часть кода мы уже использовали в предыдущих разделах. Единственным отличием здесь будет значение радиуса порождающей окружности. Установим его равным R=0.01 мм в соответствии с экспериментальными данными [3]
Уравнения волнистых режущих кромок (Sympy)
import sympy as sp from sympy.abc import x, y, z, u from scipy import integrate a, b, h, R = 2, 5, 25, 0.01 # Кривая смещения оси log_line = 2*sp.log(1-z) # Уравнение боковой поверхности изогнутого зуба left_part_eq = x**2/a**2 + (y + log_line)**2/b**2 + z/h equations = [left_part_eq] # Разрешим относительно y sol1, sol2 = sp.solve(equations, y, dict=True) print('Решения отностительно y: \n', sol1, sol2) # Проекция вдоль Ox на плоскость Oyz zy_section1 = sol1[y].subs({x:0}) zy_section2 = sol2[y].subs({x:0}) print('Проекции: \n', zy_section1, zy_section2
Используем полученные как проекции выражения для передней и задней ровных кромок в качестве направляющих для развертывания волнообразного режущего края
# Параметрические уравнения передней и задней кромки zu = R * (u — sp.sin(u)) # Опорные кривые line_1 = zy_section1.subs({z:zu}) line_2 = zy_section2.subs({z:zu}) Y_1 = -R * (1 — sp.cos(u)) + line_1 Y_2 = R * (1 — sp.cos(u)) + line_2
После чего посчитаем необходимые производные и возьмем интеграл
# Их производные по параметру derivative_zu = sp.diff(zu, u) derivative_yu1 = sp.diff(Y_1, u) derivative_yu2 = sp.diff(Y_2, u) print('Их производные: \n', derivative_zu, derivative_yu1, derivative_yu2) # Подынтегральная функция для криволинейного интеграла def f(u_val, curve_name): derivative = {'L1': derivative_yu1, 'L2': derivative_yu2} return (derivative_zu.subs({u:u_val}).n()**2 + derivative[curve_name].subs({u:u_val}).n()**2) ** 0.5 # Пределы # при каком значении параметра z = -h (т.е. на нижнем крае) equation = sp.Eq(R * (u — sp.sin(u)), -h) bottom_u = float(sp.nsolve(equation, u, -1)) # z = 0 очевидно при u = 0 top_u = 0 # Интеграл length = integrate.quad(f, bottom_u, top_u, 'L1')[0] + integrate.quad(f, bottom_u, top_u, 'L2')[0] print(f'Длина кромки {length} мм')
Итого, длина волнообразной режущей кромки составляет 66.47 мм. Циклоидные дуги увеличили длину кромки на 23%.
Объем волнистой режущей кромки
Из [3] можно заключить, что характерный размер идущего по кромке оранжевого канта в поперечном сечении составляет 20 мкм. Представим его как цилиндр диаметром D = 20 мкм, уложенный по найденной выше линии волнистой кромки и рассчитаем объем.
Радиус сечения r = D / 2 = 10 мкм = 0.01 мм.
Площадь сечения
Объем цилиндра
Итак, объем железосодержащего «шнура», образующего режущую кромку, равен V = 0.021 мм3.
Железо оранжевой кромки
Согласно исследованию [3] оранжевая режущая кромка зуба варана состоит из ферригедрита. Формула ферригидрита: [9] Плотность =3.96 г/см3 = 3.96∙10-3 г/мм3 [10]
Массы атомов, входящих в формулу: , , (где u — атомная единица массы).
Откуда общая масса рассматриваемой молекулы = 5 (55.845 ∙ 2 + 15.999 ∙ 3) + 9 * (1.008 ∙ 2 + 15.999) = 960.57 u
Массовая доля Fe составляет
= (10 ∙ 55.845) / 960.57 = 0.581 = 58.1 %
Масса чистого железа в покрытии режущей кромки зуба комодского варана составит
= 0.581 ∙ 3.96∙10-3 ∙ 0.021 = 0.048 ∙ 10-3 г = 0.048 мг
Масса железа на кромках полного комплекта из 60 зубов = 60 ∙ 0.048 мг = 2.88 мг
Зная, что период замены зуба у варана составляет 40 суток [4], найдем, сколько железа нужно в день при условии его равномерного расходования на покрытие формирующихся режущих кромок. / 40 = 2.88 мг/ 40 суток = 0.072 мг/сутки.
Не так уж много. Если сравнить с человеком, то средняя суточная доза потребления железа для взрослых мужчин и пожилых людей обоих полов составляет 8 мг (для женщин в 2-3 раза больше) [11]. При этом оно расходуется в основном на не связанные напрямую с зубами нужды организма.
Варану на покрытие острых кромок своих зубов нужно порядка 1% от такого объема.
Заключение
В рамках рассмотренных моделей мы получили, что варану для обеспечения динамики обновления зубов необходимо порядка 488 мг кальция и 72 мкг железа в сутки. Для сравнения: это примерно половина суточной потребности человека в кальции и менее одного процента суточной потребности человека в железе соответственно. Цифры не астрономические. И для комодского дракона, с его охотничьими способностями и положением на вершине пищевой цепи своего региона, вполне достижимые.
Что касается зубов человека, биоинженерные технологии понемногу идут в сторону создания (включения) механизма выращивания новых зубов взамен поврежденных или отсутствующих ([12] или подробнее в [13]). Но, думаю, еще долгое время варану конкуренция в этом вопросе с нашей стороны не грозит.
Источники
-
https://journals.plos.org/plosone/article?id=10.1371/journal.pone.0295002
-
https://www.handbookofmineralogy.org/pdfs/hydroxylapatite.pdf
-
https://www.consultant.ru/document/cons_doc_LAW_383904/def867cf1af3e9abc3ff8fe0463d65565a4b6264/
-
https://hepd.pnpi.spb.ru/hepd/events/abstract/2024/Getalov_16_04_24.pdf
-
https://www.sciencedirect.com/science/article/pii/S2352320423000044
ссылка на оригинал статьи https://habr.com/ru/articles/855660/
Добавить комментарий