1  Пятка бегуньи

Бегунья на длинные дистанции, 75 килограммов, готовится к марафону. За четыре месяца она поднимает недельный объём с 40 до 90 километров. Потом начинает болеть пятка — сильнее всего при первом шаге утром. Рентгеновский снимок показывает вдобавок небольшой костный вырост на нижней поверхности её пяточной кости, рядом с подошвенными прикреплениями подошвенного апоневроза и коротких мышц стопы: энтезофит, в обиходе — пяточную шпору. Возник ли он за эти четыре месяца и болит ли он сам, снимок не говорит — такие выросты находят и у людей, у которых пятка никогда не болела. Точно известно только одно: где он сидит.

У её тела нет чертежа шпоры. Нигде не записано, что именно в этом месте должна появиться кость. И всё-таки она там — там, а не где-нибудь ещё. Это и есть вопрос, на котором держится вся книга:

Откуда кость знает, где ей расти — и откуда она знает это только сейчас?

Ответ книги умещается в одну фразу, а чтобы его отработать, нужны восемнадцать глав: кость отвечает на нагрузку, которую испытывает, — а нагрузку можно вычислить.

Несколько слов о второй половине вопроса, о «только сейчас». Вычислять ты будешь где: место, в котором нагрузка сильнее всего отклоняется от привычной для кости. Когда эта книга не считает. Она нигде не говорит, через сколько недель появляется шпора — для этого нужно знать, как быстро работают строящие клетки, а это биологическая измеряемая величина, а не следствие механики. И всё-таки остаётся немало: модель говорит, что что-то должно измениться, как только меняется нагрузка, и в каком месте это произойдёт раньше всего. Четыре месяца бегуньи — история, из которой вырос вопрос, а не результат расчёта.

На ближайших страницах ты ещё ничего не считаешь. Сначала нужна картина, к которой ведёт всё дальнейшее, — а для этого мы сперва посмотрим на стопу целиком, потом приблизим одну кость, нагрузим её и в конце возьмёмся за готовую программу.

Цели обучения

После этой главы ты сможешь

  1. назвать сцену: одна пяточная кость, три силы (опора, ахиллово сухожилие, подошвенный апоневроз) и стелька — как вопрос, который эта книга ставит и оставляет открытым,
  2. объяснить, что в общих чертах делает численная модель — разбивает действительность на маленькие кусочки, для каждого кусочка считает просто, а решает всё вместе,
  3. перечислить пять строительных блоков любого расчёта методом конечных элементов (геометрия и сетка, закон материала, граничные условия, система уравнений, решатель),
  4. читать цветную картину поля: цвет, шкала, единица, ориентация,
  5. предсказать на программе-чёрном ящике, что качественно меняют лишние тренировочные километры у шпоры, — и проверить это.

1.1 Стопа целиком

Прежде чем приближать одну-единственную кость, взглянем на целое — ведь стопа не колода, а цепь костей, удерживаемая связками и приводимая в движение сухожилиями. Сзади и снизу лежит пяточная кость (по-латыни calcaneus); над ней, между большеберцовой костью и пяточной, сидит таранная кость; дальше вперёд идут предплюсна, плюсневые кости и пальцы.

Из следующего рисунка запомни две вещи, на них держится вся книга. Первое: мощное ахиллово сухожилие идёт от красной икроножной мышцы сверху и прикрепляется к задней поверхности пяточной кости — сзади к кости, а не сверху на ней. Второе: подошвенный апоневроз натягивается снизу вдоль подошвы от пяточной кости вперёд. И то и другое — места, где сила входит в кость. У второго, у прикрепления подошвенного апоневроза, сидит шпора этой книги. Сидит ли она там потому, что апоневроз тянет, — вопрос, который в конце останется открытым.

Рисунок 1.1: Кости, сухожилия и связки стопы сбоку (боковой вид с мягкими тканями, пятка сзади/слева, пальцы спереди/справа): внизу сзади пяточная кость (calcaneus), над ней таранная кость, позади большеберцовая (tibia) и малоберцовая (fibula) кости. Ахиллово сухожилие идёт от красной икроножной мышцы сверху и прикрепляется к задней поверхности пяточной кости; подошвенный апоневроз проходит снизу вдоль подошвы вперёд. Эти прикрепления сухожилий — те места, в которых наша модель вводит силы.

Как это читать. Пяточная кость — краеугольный камень свода стопы: та кость, на которую при постановке ноги ложится вес первой. Запомни заднюю поверхность как место прикрепления ахиллова сухожилия — именно там сейчас начнётся наша стрелка силы, и там же она стоит на каждом рисунке и в каждой модели этой книги.

1.2 Сцена: одна кость, три силы, один вопрос

Пяточная кость, по-латыни calcaneus, — самая крупная кость предплюсны. Мы смотрим на неё сбоку, в плоскости, которая делит стопу вдоль: это сагиттальный срез. Представь себе правую стопу, вид снаружи — слева пятка, справа дорога к пальцам, вверху верх. На этом одном виде построено всё дальнейшее, и каждый рисунок книги говорит тебе, где сзади и где вверху.

Начало отсчёта тоже закреплено в этом виде и остаётся таким во всей книге: сзади внизу у кости. Оттуда первая координата отсчитывается вперёд, к пальцам, вторая — вверх. В этом виде кость имеет длину 66 миллиметров; запись вроде «24 миллиметра вперёд, 3 миллиметра вверх» обозначает, стало быть, то место на нижней поверхности, где сейчас появится шпора.

При каждом шаге кость встречают три силы. Снизу давит опора — сила реакции опоры, которую принимает на себя жировая подушка пятки. Сзади сверху тянет ахиллово сухожилие, когда напрягается икра. Спереди снизу тянет подошвенный апоневроз, плотная соединительнотканная лента подошвы. Эти три действуют сообща, и их взаимодействие поворачивается на протяжении одного-единственного шага — подробнее об этом в главе 2, где ты нащупаешь каждую из трёх на собственной стопе.

Код
import json
import pathlib

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import FancyArrow

контур = json.loads(
    pathlib.Path("../geometrie/kalkaneus_kontur.json").read_text())
точки = np.array(контур["punkte"]) * 1000.0        # м -> мм
абсциссы, ординаты = точки[:, 0], точки[:, 1]

рис, ось = plt.subplots(figsize=(7.2, 5.4))
ось.fill(абсциссы, ординаты, color="#f0e2d0", ec="#a9825f", lw=1.8, zorder=2)

# Три силы как стрелки (схематично: важно направление, а не величина).
def стрелка(x_нач, y_нач, x_кон, y_кон, цвет, подпись, x_подписи, y_подписи):
    ось.add_patch(FancyArrow(x_нач, y_нач, x_кон - x_нач, y_кон - y_нач,
                             width=0.5, head_width=3.0,
                             length_includes_head=True, color=цвет, zorder=4))
    ось.text(x_подписи, y_подписи, подпись, color=цвет, fontsize=11,
             fontweight="bold", zorder=5)

# Реакция опоры под бугром (проверенная граничная группа опоры: x < 19 мм),
# подошвенный апоневроз на переднем нижнем скате бугристости (энтезис,
# x ≈ 22–26 мм) — точки приложения, спроецированные на контур, как в главе 2.
стрелка(8.594, -12, 8.594, 2.237, "#1f77b4", "реакция опоры", -27, -12)
# Ахиллово сухожилие: прикрепляется к ЗАДНЕЙ (левой) поверхности пяточной
# кости — к задней поверхности пяточного бугра — и тянет оттуда вверх к икре.
# Место прикрепления по анатомическому образцу (@fig-kap01-fuss), а НЕ на
# вершине кости.
стрелка(4, 29, 1, 52, "#d62728", "ахиллово сухожилие", -14, 57)
# Конец выбран так, чтобы стрелка указывала в каноническое направление
# апоневроза (0,985; −0,174) — направление тяги из свода чисел.
стрелка(24.065, 3.353, 50, -1.23, "#2ca02c", "подошвенный апоневроз", 40, -8)

# Место шпоры на переднем нижнем скате (спроецированная точка контура).
ось.scatter([24.07], [3.35], s=90, facecolor="none", edgecolor="#7a2b8a", lw=2,
            zorder=6)
ось.annotate("пяточная шпора\n(энтезофит)", xy=(24.07, 3.35), xytext=(12, -16),
             color="#7a2b8a", fontsize=10,
             arrowprops=dict(arrowstyle="->", color="#7a2b8a"))

# Ориентация и масштаб.
ось.text(-27, 6, "сзади (пятка)", fontsize=10, color="#555")
ось.text(52, 33, "спереди (пальцы)", fontsize=10, color="#555")
ось.add_patch(FancyArrow(-20, 16, 0, 12, width=0.15, head_width=2.2,
                         color="#555"))
ось.text(-20, 31, "вверх", fontsize=10, color="#555", ha="center")
ось.plot([40, 50], [48, 48], color="#333", lw=2)
ось.text(40, 50, "10 мм", fontsize=9, color="#333")

ось.set_xlim(-30, 74)
ось.set_ylim(-28, 62)
ось.set_aspect("equal")
ось.axis("off")
plt.tight_layout()
plt.show()
Рисунок 1.2: Сцена, которая сопровождает нас через всю книгу: пяточная кость в боковом разрезе (сагиттальный срез), сзади пятка, спереди пальцы, вверху верх. Действуют три силы — реакция опоры снизу, ахиллово сухожилие сзади сверху, подошвенный апоневроз спереди снизу. Рядом с подошвенными прикреплениями (энтезисами) сидит пяточная шпора. Контур в масштабе, из геометрии книги (длина 66 мм, из приложения с числами).

Эта одна сцена возвращается в каждой главе. Всё, что книга выстраивает, служит одному: предсказать по этим трём силам и по этой форме, где кость изменится.

На рентгеновском снимке стопы шпору легко разглядеть — но объяснить её этим ещё нельзя. Эта книга такого снимка не показывает; следующее сопоставление — схема из того же контура кости: один раз без костного выроста на нижней поверхности, другой раз с ним.

Код
import json
import pathlib

import numpy as np
import matplotlib.pyplot as plt

контур = json.loads(
    pathlib.Path("../geometrie/kalkaneus_kontur.json").read_text())
точки = np.array(контур["punkte"]) * 1000.0
абсциссы, ординаты = точки[:, 0], точки[:, 1]

# Шпора как костный шип. Она сидит там, где отмечает @fig-kap01-szene
# (подошвенный энтезис, спроецированный на контур: 24,07/3,35 мм), опирается
# основанием на нижнюю сторону контура и растёт в направлении тяги
# подошвенного апоневроза (0,985; -0,174) вперёд к пальцам. Длина схематична.
энтезис = np.array([24.065, 3.353])
бугор_низ = np.array([21.816, 0.0])       # самая нижняя точка контура перед ним
направление_апоневроза = np.array([0.985, -0.174])
вершина = 0.5 * (энтезис + бугор_низ) + 7.0 * направление_апоневроза
шпора_абсциссы = [бугор_низ[0], вершина[0], энтезис[0]]
шпора_ординаты = [бугор_низ[1], вершина[1], энтезис[1]]

рис, оси = plt.subplots(2, 1, figsize=(6.4, 7.6))
for ось, со_шпорой in zip(оси, [False, True]):
    ось.fill(абсциссы, ординаты, color="#f0e2d0", ec="#a9825f", lw=1.6)
    if со_шпорой:
        ось.fill(шпора_абсциссы, шпора_ординаты, color="#e7cfae",
                 ec="#a9825f", lw=1.6)
        ось.scatter([25.8], [1.4], s=1500, facecolor="none",
                    edgecolor="#7a2b8a", lw=2)
        ось.set_title("с пяточной шпорой", fontsize=11)
    else:
        ось.set_title("без пяточной шпоры", fontsize=11)
    ось.text(-2, 44, "сзади", fontsize=9, color="#666")
    ось.text(52, 33, "спереди", fontsize=9, color="#666")
    ось.set_xlim(-12, 70)
    ось.set_ylim(-12, 50)
    ось.set_aspect("equal")
    ось.axis("off")
plt.tight_layout()
plt.show()
Рисунок 1.3: Схема (не рентгеновский снимок): одна и та же пяточная кость без подошвенной пяточной шпоры и с ней, ориентация как на рисунке 1.2. Вырост сидит на нижней поверхности, у подошвенного энтезиса, который отмечен на рисунке 1.2; он растёт узким шипом вперёд к пальцам — в направлении тяги подошвенного апоневроза, вдоль зелёной стрелки предыдущего рисунка. Его длина здесь схематически преувеличена, чтобы форма оставалась различимой. Рисунки стоят друг под другом, чтобы форма на обоих читалась в одном размере.

Вырост маленький, но сидит он во вполне определённом месте. Почему именно там? Первое подозрение звучит так: потому что там нагрузка больше всего. Отвечает ли кость и вправду на максимум или на что-то более тонкое — на отклонение от привычного, — выяснит только часть VI. Уже сейчас ясно одно: нужно знать, как нагрузка распределена внутри кости. А это — вычислительная задача.

1.3 Тело как лаборатория

Две из трёх сил ты можешь почувствовать прямо сейчас. Встань и перенеси вес на одну пятку — это реакция опоры, снизу. Нащупай сзади над пяткой тяж под кожей, сначала расслабленной стопой, а потом приподнимая пятку: под натяжением ахиллово сухожилие становится твёрдым. Третий, подошвенный апоневроз, натягивается под подошвой, когда ты тянешь большой палец вверх. Граничное условие, которое однажды почувствовал, заучивать не нужно.

На протяжении шага эти силы сменяют друг друга — сначала на землю опускается пятка, потом стопа перекатывается вперёд, в конце отталкиваются пальцы. Три силы при этом передают эстафету одна другой. Их величины во всей книге даны кратно массе тела, сокращённо МТ: «2,5 · МТ» означает две с половиной собственные массы. Так считать удобнее, и числа перестают зависеть от человека; для 75 килограммов этой сцены 2,5 · МТ — это около 1840 ньютонов, ведь одна МТ здесь равна 736 ньютонам (приложение с числами). У следующего окна просмотра есть ползунок с пятью положениями: протяни его по фазам одну за другой и сравни, какая сила когда больше всего — слева (касание пяткой) направо (отталкивание). Четвёртая сила в этой игре — сила в суставе, которой таранная кость упирается сверху, — вернётся в главе ?sec-kap12_boden_sehne_faszie, там как реакция опоры-закрепления, которой модель заменяет таранную кость.

Важное уведомлениеПрогноз

Пять фаз от касания пяткой до отталкивания: в какой из них ахиллово сухожилие тянет сильнее всего — сразу при касании или только при отталкивании? Определись, а потом протяни ползунок.

Как это читать. Ни один момент сам по себе шпоры не делает. Кость отвечает на всю историю нагружения — на то, как часто встречается тот или иной момент. Именно поэтому техника бега и объём тренировок — регуляторы, а не просто числа: техника бега смещает вес отдельных моментов, объём тренировок задаёт, как часто они складываются вместе. Начиная с части V ты будешь считать эти пять моментов по отдельности.

1.4 Как читать картину поля

Сейчас ты возьмёшься за первую программу, и с этого момента почти каждый рисунок книги показывает поле: величину, у которой значение есть не в одной точке, а в каждой. Поле нельзя выписать одним числом, поэтому его раскрашивают. Читать в нём нужно четыре вещи, и все четыре стоят у каждого рисунка.

Цвет — это число. Рядом с рисунком стоит цветовая полоса со значениями и единицей — легенда значений. Она не украшение: без неё цвет ничего не значит. Только полоса превращает «здесь светло» в утверждение «здесь 0,9 грамма на кубический сантиметр».

Шкала принадлежит рисунку, а не кости. Её можно выбирать, и от выбора зависит, насколько драматично выглядит поле. Чтобы ты могла сравнивать рисунки из разных глав, книга закрепляет две шкалы: напряжения идут от 0 до 1000 килопаскалей (от тёмно-синего через зелёный к красному), плотности — от 0,2 до 1,0 грамма на кубический сантиметр (от чёрного через фиолетовый к светло-жёлтому). Где рисунку нужен другой диапазон, он говорит об этом в подписи.

Где сзади, где вверху. У каждой картины поля стоит ориентация — сзади (пятка) слева, спереди (пальцы) справа, вверх наверху — и вместе с ней масштаб, либо как подписанная ось в миллиметрах, либо как короткий отрезок. Без того и другого ты не знала бы, лежит ли светлое место у подошвы или у голеностопного сустава и насколько оно велико.

Красный значит «много», а не «опасно». Цвет говорит, где значение велико, — а не о том, тревожно ли оно. Перегружено ли ткань напряжением, решает сравнение с прочностью, а его эта книга нигде не проводит. Кто видит красное поле и думает «опасность перелома», прочёл шкалу и не понял рисунка.

Насколько многое зависит от шкалы, показывает следующий рисунок. Он изображает одно и то же посчитанное поле плотности дважды: один раз на постоянной шкале книги, другой раз на шкале, которая охватывает только диапазон встречающихся значений.

Код
import sys
import json
import pathlib

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.tri import Triangulation

sys.path.insert(0, "programme/gemeinsam")
sys.path.insert(0, "programme/kap01")
from blackbox_kern import neues_blackbox

данные = json.loads(pathlib.Path("netze/netz_grob.json").read_text())
модель = neues_blackbox(данные)
узлы = np.array(данные["knoten"]) * 1000.0
триангуляция = Triangulation(узлы[:, 0], узлы[:, 1],
                             np.array(данные["dreiecke"]))

плотность = np.array(модель.blackbox(75, 90, "fersenlauf", "keine")["dichte"])

рис, оси = plt.subplots(2, 1, figsize=(6.4, 6.4))
диапазоны = [(0.2, 1.0, "постоянная шкала книги: 0,2–1,0 г/см³"),
             (float(плотность.min()), float(плотность.max()),
              "растянутая шкала: только встречающиеся значения")]
for ось, (низ, верх, заголовок) in zip(оси, диапазоны):
    картинка = ось.tripcolor(триангуляция, facecolors=плотность, cmap="magma",
                             vmin=низ, vmax=верх)
    ось.set_title(заголовок, fontsize=11)
    ось.set_aspect("equal")
    ось.set_ylabel("y (мм)")
    рис.colorbar(картинка, ax=ось, fraction=0.046, pad=0.03,
                 label="плотность (г/см³)")
оси[-1].set_xlabel("сзади (пятка) ←→ спереди (пальцы),  x (мм)")
plt.tight_layout()
plt.show()
Рисунок 1.4: Одно поле, две шкалы (рисунки друг под другом): сверху поле плотности пяточной кости на постоянной шкале книги (0,2–1,0 г/см³, magma), снизу то же поле на растянутой шкале, которая охватывает только встречающиеся значения. Цвета различаются сильно, расчёт один и тот же — потому легенда значений и нужна у каждой картины поля. Сцена: 75 кг, 90 км в неделю, бег с пятки, без стельки; сагиттальный срез, сзади слева, спереди справа, вверх ↑.

Как это читать. Оба рисунка показывают одни и те же числа. На нижнем уплотнённое место выглядит светлее и решительнее, потому что там самое светлое достаётся уже наибольшему встречающемуся значению; на верхнем оно остаётся заметно ниже, ведь там самый светлый цвет отведён 1,0 грамму на кубический сантиметр — значению, которого этот расчёт вовсе не достигает. Насколько драматично выглядит поле, зависит от шкалы; что оно говорит, написано в легенде. Отсюда правило: сначала прочитать легенду, потом рисунок.

1.5 Чёрный ящик, который можно потрогать

Три силы ты уже почувствовала и проследила, как они странствуют по шагу. Считать их ты пока не будешь — но цель уже можно взять в руки: перед тобой демонстрационная модель того, что стоит в конце книги, — тот же вопрос, но с намеренно упрощённым устройством внутри. Стоящее за ней численное моделирование считает с однородным материалом и упрощённым закреплением, а где строить, здесь ещё задаёт анатомия (зона, в которой лежит подошвенный энтезис); выросший расчёт в главе ?sec-kap18_wo_der_sporn_waechst определяет это место сам, из поля стимула. Четыре регулятора, один результат: поле плотности кости, в котором место шпоры на подошвенной стороне уплотняется — или не уплотняется. Меняется при этом плотность — то, насколько твёрдой кость становится в каждом месте; внешняя форма кости остаётся такой, какая она есть. Светлый цвет означает более плотную, более твёрдую кость.

Все четыре регулятора управляют одним и тем же рисунком: они стоят вместе над единственным полем плотности, и каждое движение — всё равно, у какого регулятора — заставляет пересчитать именно это одно поле. Так сразу видно, что масса, объём, техника бега и стелька крутят один и тот же результат. Каждый регулятор приходит с вопросом. Приём для учёбы старый и работает: сначала предположи, потом двигай. Кто сначала определился, а потом получил опровержение, тот запомнит.

Важное уведомлениеПрогноз

Определись по каждому из четырёх вопросов, прежде чем двигать:

  1. Масса тела: двойная масса — шпора вдвое больше, или кость отвечает иначе?
  2. Километры в неделю: с какого объёма в модели вообще что-то начинается — сразу, или есть порог, ниже которого не происходит ничего?
  3. Техника бега: при беге с передней части стопы пятка ставится мягче. Станет ли уплотнение от этого меньше, или останется таким же, потому что сухожилие и апоневроз тянут сильнее? (Где оно сидит, в этой демонстрационной модели ещё задаёт анатомия; может ли место перемещаться — вопрос отдельный.)
  4. Стелька: мягкая подушка под пяткой — делает ли она шпору меньше или только перекладывает нагрузку в другое место?
Код
import sys
import json
import pathlib

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.tri import Triangulation

sys.path.insert(0, "programme/gemeinsam")
sys.path.insert(0, "programme/kap01")
from blackbox_kern import neues_blackbox

данные = json.loads(pathlib.Path("netze/netz_grob.json").read_text())
модель = neues_blackbox(данные)
узлы = np.array(данные["knoten"]) * 1000.0
триангуляция = Triangulation(узлы[:, 0], узлы[:, 1],
                             np.array(данные["dreiecke"]))

километры = [0, 40, 90]
рис, оси = plt.subplots(3, 1, figsize=(6.4, 9.0))
for ось, км in zip(оси, километры):
    результат = модель.blackbox(75, км, "fersenlauf", "keine")
    картинка = ось.tripcolor(триангуляция,
                             facecolors=np.array(результат["dichte"]),
                             cmap="magma", vmin=0.2, vmax=1.0)
    ось.set_title(f"{км} км в неделю", fontsize=11)
    ось.set_aspect("equal")
    ось.set_ylabel("y (мм)")
    рис.colorbar(картинка, ax=ось, fraction=0.046, pad=0.03,
                 label="плотность (г/см³)")
оси[-1].set_xlabel("сзади (пятка) ←→ спереди (пальцы),  x (мм)")
plt.tight_layout()
plt.show()
Рисунок 1.5: Ряд рисунков к регулятору километров: поле плотности пяточной кости при 0, 40 и 90 км в неделю, все на постоянной шкале плотности 0,2–1,0 г/см³ (шкала magma). Ниже порога кость остаётся мягкой (тёмной); с ростом объёма место шпоры на подошвенной стороне уплотняется. За регуляторами считает тот же самый чёрный ящик.

Что видно на регуляторах. Масса входит в движущую силу сверхлинейно — двойная сила запасает в ткани больше чем вдвое больше работы деформации, — но ответ насыщается: от 50 к 100 кг уплотнение растёт заметно, однако не вдвое. У километров в модели есть порог: ниже примерно 18 в неделю не происходит ничего, выше — уплотнение растёт на глазах. Запомни этот порог. Он не случайность программирования, а первый след того свойства живой кости, которое получит имя в главе ?sec-kap16_der_mechanostat.

Бег с передней части стопы и пяточная подушка делают уплотнение меньше, потому что в этой демонстрационной модели, где главное — давление, они снижают нагрузку на пятку. Смещают ли они место, эта модель сказать не может: место здесь задано, а не посчитано. И так ли всё это в настоящей стопе, остаётся открытым — бег с передней части стопы одновременно увеличивает тягу ахиллова сухожилия и апоневроза. То, что показывает регулятор, следует из заложенных в него допущений; утверждением о стопе бегуньи это станет лишь тогда, когда кто-нибудь проверит, выдерживают ли эти допущения. Как это проверяют, показывает глава ?sec-kap13_traue_keinem_bunten_bild.

У четвёртого регулятора стоит присмотреться внимательнее. Стелька — тот самый вопрос, который эта книга ставит и оставляет открытым. Мягкая подушка меняет то, как стопа лежит на опоре, — а насколько велика площадь прилегания, зависит от того, насколько сильно давят. Именно этого не считает ни одна модель этой книги: они задают нагрузки, вместо того чтобы самим определять соприкосновение с опорой. Значит, регулятор показывает тебе, что делает смягчённая и шире распределённая нагрузка от опоры, — принятая, а не вычисленная. Что делает определённая стелька на определённой стопе, из этого не следует. В конце книги, в главе ?sec-kap18_wo_der_sporn_waechst, ты сможешь точно назвать, какого блока для этого не хватает, — и это ценнее числа, которому нельзя было бы доверять.

Тут впервые встаёт вопрос, который отделяет эту книгу от справочника: кто здесь, собственно, что говорит? Для этого есть врезка, и она возвращается в двух местах, где это особенно важно, — у стельки в главе ?sec-kap12_boden_sehne_faszie и у шпоры в главе ?sec-kap18_wo_der_sporn_waechst.

УведомлениеМодель говорит · литература говорит · твой врач говорит

Модель говорит: больше километров — сильнее уплотняется место у подошвенного энтезиса, ниже порога не происходит ничего, а смягчённая нагрузка от опоры уплотнение снижает. Это утверждение об этой упрощённой демонстрационной модели — о её допущениях, её геометрии, её силах.

Литература говорит: шпора встречается и у людей без всяких жалоб, и болит ли она сама, не выяснено. Возникает ли она от тяги или от давления, спорно до сих пор — именно поэтому в конце книги ты считаешь обе догадки.

Твой врач говорит: что происходит с конкретной стопой и что ей поможет — покой, упражнения, стелька, — решает осмотр, а не расчёт. Эта книга никаких рекомендаций на этот счёт не даёт и ни одной не заменяет.

Наращивание идёт не одним махом. Следующая анимация показывает переход от исходного состояния модели к конечному.

Постоянная сцена примера: масса тела 75 кг, 90 километров в неделю, бег с пятки, без стельки. Последовательность кадров переходит между двумя состояниями — это не настоящие шаги перестройки, в которых каждая новая плотность снова входит в расчёт, и не временная ось; и то и другое ты будешь считать сама начиная с части VI.

Здесь подстерегает широко распространённое короткое замыкание — и оно достаточно важно для отдельной врезки.

ПредупреждениеКомпьютер знает не больше, чем его модель

Догадка: «Если расчёт показывает шпору, значит доказано, что она так и возникает».

Почему это напрашивается: цветная картинка из компьютера действует как измерение. В ней есть числа, цвета, знаки после запятой — она выглядит как истина.

Что на самом деле: программа считает замещающую задачу из конечного числа чисел. Она говорит, что предсказывает модель, — а не что делает тело. Именно поэтому наука в этом месте до сих пор спорит, что движет шпорой — тяга апоневроза или давление снизу. И именно поэтому в главе 18 ты сама просчитаешь обе догадки, вместо того чтобы поверить одной. Модель хороша не тем, что делает красивые картинки, а тем, что позволяет различать догадки.

1.6 Что, собственно, делает моделирование

Как программа вообще приходит к такой картинке? Основная мысль та же, что и в прогнозе погоды. Никто не считает «погоду» одним махом. На карту кладут сетку, для каждой маленькой ячейки считают простые правила — воздух течёт от высокого давления к низкому, тёплый поднимается — и складывают ячейки в общую картину. При этом ни одна ячейка не решается сама по себе: на общих границах каждая держится за соседей, и только все вместе дают согласованную картину. При достаточно мелком разбиении множество простых вычислений даёт картину, которую никто не сумел бы выписать в один шаг.

У кости эти маленькие кусочки — конечные элементы, конечное число конечно малых частей, — и их метод поступает точно так же: он разбивает область на множество маленьких треугольников — это сетка —, считает для каждого треугольника простое соотношение между перемещением и силой и в конце складывает все треугольники в одну большую систему уравнений, на которую решатель отвечает разом. Насколько мелко разбивать — решение, и результату это видно.

Код
import json
import pathlib

import numpy as np
import matplotlib.pyplot as plt

контур = json.loads(
    pathlib.Path("../geometrie/kalkaneus_kontur.json").read_text())
точки_контура = np.array(контур["punkte"]) * 1000.0

грубая = json.loads(pathlib.Path("netze/netz_grob.json").read_text())
мелкая = json.loads(pathlib.Path("netze/netz_fein.json").read_text())

рис, оси = plt.subplots(3, 1, figsize=(6.2, 9.2))

оси[0].fill(точки_контура[:, 0], точки_контура[:, 1], color="#f0e2d0",
            ec="#a9825f", lw=1.6)
оси[0].set_title("сплошная область", fontsize=11)

for ось, сетка, заголовок in [(оси[1], грубая, "грубая сетка"),
                              (оси[2], мелкая, "мелкая сетка")]:
    узлы = np.array(сетка["knoten"]) * 1000.0
    ось.triplot(узлы[:, 0], узлы[:, 1], np.array(сетка["dreiecke"]),
                color="#8a4a10", lw=0.4)
    ось.set_title(f"{заголовок} ({сетка['n_dreiecke']} треугольников)",
                  fontsize=11)

for ось in оси:
    ось.set_aspect("equal")
    ось.axis("off")
plt.tight_layout()
plt.show()
Рисунок 1.6: Разбиение в три ступени (друг под другом, чтобы треугольники оставались различимыми): один и тот же контур пятки как сплошная область, как грубая сетка и как мелкая сетка. Чем мельче сетка, тем ближе расчёт к оригиналу — чаще всего; когда это удаётся, а когда нет, проверяет глава 13 исследованием сходимости. Сетки из геометрии книги (контур пяточной кости, полученный по изображению).

Пять строительных блоков нужны любому такому расчёту, и со всеми ты познакомишься: геометрия и сетка (форма, разбитая на треугольники), закон материала (насколько жёстко ткань отвечает на растяжение), граничные условия (где давят, тянут, удерживают), система уравнений (удовлетворить все треугольники одновременно) и решатель (который на неё отвечает). Не хватает одного — картинки нет.

1.7 Как считает эта книга

Книга о вычислительном методе может прятаться или сказать прямо. Эта говорит: за тем, что ты здесь видишь, стоят три разных расчёта, и задачи у них разные.

Код, который ты читаешь. В главах стоит программный код, который можно читать строка за строкой — с русскими именами, без готовых библиотек и всегда на крошечной сетке из горсти треугольников. Она так мала, что результат можно проверить карандашом на бумаге, и ровно для этого она существует. Кто хочет понять, как работает метод, узнаёт это здесь — и только здесь.

Счётная машина за регуляторами. Каждый регулятор этой книги решает по-настоящему. Стоит его сдвинуть, и твой браузер считает задачу заново, на сетке из нескольких тысяч треугольников вместо горсти, теми же способами, но в отлаженной, быстрой форме. Её кода в тексте нет — не потому, что он тайный, а потому, что он не объясняет ничего сверх того, что уже показывает читаемый код.

Сравнительный расчёт. Некоторых чисел браузер дать не может: им нужны сетки с десятками тысяч узлов и минуты вместо долей секунды. Такие расчёты идут заранее, вне книги, на контуре пятки, полученном из данных изображения. Где эта книга приводит число как надёжное — напряжения, стимулы, места, — оно взято оттуда, и текст об этом говорит.

Откуда берутся числа. Материал, силы, размеры: каждое число, с которым идёт расчёт, стоит один раз в приложении с числами в конце книги — с единицей, происхождением и источником. Ни одна глава не вводит собственного числа. Если в каком-то расчёте тебе встретится значение, которого там нет, ты нашла ошибку.

А кто это проверяет? При каждой сборке этой книги читаемый код сопоставляется со счётной машиной за регуляторами: если оба считают одну и ту же задачу, они обязаны получить одно и то же вплоть до последнего показанного знака. Если нет — книга содержит ошибку, а вовсе не учебный код «упрощён». Эта проверка и есть причина, по которой маленькому примеру можно доверять, хотя картинка приходит из большого.

1.8 Карта книги

Теперь ты видела цель и знаешь, из каких пяти блоков она построена. Не хватает тебе каждого отдельного блока — и его даёт по одной части книги. В верхнем ряду карты показано, какая глава какой блок поставляет. В нижнем ряду стоят главы, которые собственного блока не несут и всё же нужны: стрелки сил, производная и интеграл, равновесие и его ослабленная форма, первая полностью просчитанная сетка и проверка готовой картинки. Как раз этих глав большинство побаивается, и как раз они превращают цветную картинку в утверждение. Карта возвращается в конце каждой части и отмечает, как далеко ты продвинулась.

Код
import matplotlib.pyplot as plt
from matplotlib.patches import FancyBboxPatch, FancyArrowPatch

блоки = [
    ("геометрия\nи сетка", "глава 10", "#cfe8f3"),
    ("закон\nматериала", "глава 6", "#d7eecd"),
    ("граничные\nусловия", "глава 12", "#f6e0c0"),
    ("система\nуравнений", "главы 4–5", "#e6d5f0"),
    ("решатель", "глава 5", "#f3d0d0"),
]

# Инструменты: главы, которые не несут собственного блока, но без которых ни
# один из пяти блоков посчитать было бы нельзя.
инструменты = [
    ("стрелки сил\nи векторы", "главы 2–3"),
    ("наклон\nи площадь", "глава 7"),
    ("равновесие,\nслабая форма", "главы 8–9"),
    ("счёт\nи проверка", "главы 11, 13"),
    ("шаг\nи пики", "главы 14–15"),
]

рис, ось = plt.subplots(figsize=(8.2, 5.0))
сдвиг = 0.0
for (название, глава, цвет), (имя_инстр, глава_инстр) in zip(блоки, инструменты):
    ось.add_patch(FancyBboxPatch((сдвиг, 1.0), 1.5, 1.0,
                  boxstyle="round,pad=0.03", fc=цвет, ec="#556"))
    ось.text(сдвиг + 0.75, 1.5, название, ha="center", va="center", fontsize=10)
    ось.text(сдвиг + 0.75, 0.78, глава, ha="center", va="top", fontsize=9,
             color="#335")
    if сдвиг > 0:
        ось.add_patch(FancyArrowPatch((сдвиг - 0.35, 1.5), (сдвиг - 0.02, 1.5),
                      arrowstyle="->", mutation_scale=14, color="#556"))
    # Ящик с инструментами под цепочкой, без стрелок: инструменты стоят рядом
    # с цепочкой, а не внутри неё.
    ось.add_patch(FancyBboxPatch((сдвиг, -0.55), 1.5, 0.85,
                  boxstyle="round,pad=0.03", fc="#eceef1", ec="#889"))
    ось.text(сдвиг + 0.75, -0.12, имя_инстр, ha="center", va="center",
             fontsize=9)
    ось.text(сдвиг + 0.75, -0.72, глава_инстр, ha="center", va="top",
             fontsize=8.5, color="#445")
    сдвиг += 1.85

# Ящик с результатом справа.
ось.add_patch(FancyBboxPatch((сдвиг + 0.1, 0.9), 1.9, 1.2,
              boxstyle="round,pad=0.03", fc="#fbeecb", ec="#a86"))
ось.text(сдвиг + 1.05, 1.7, "напряжения", ha="center", fontsize=10)
ось.text(сдвиг + 1.05, 1.35, "↓ ремоделирование", ha="center", fontsize=9,
         color="#663")
ось.text(сдвиг + 1.05, 1.05, "шпора (часть VI)", ha="center", fontsize=9,
         color="#663")
ось.add_patch(FancyArrowPatch((сдвиг - 0.35, 1.5), (сдвиг + 0.05, 1.5),
              arrowstyle="->", mutation_scale=14, color="#556"))

ось.text(-0.2, 2.25, "Пять строительных блоков", fontsize=10, color="#334",
         fontweight="bold")
ось.text(-0.2, 0.42, "Инструменты к ним", fontsize=10, color="#334",
         fontweight="bold")

ось.set_xlim(-0.3, сдвиг + 2.2)
ось.set_ylim(-1.1, 2.5)
ось.axis("off")
plt.tight_layout()
plt.show()
Рисунок 1.7: Карта книги. Верхний ряд: пять строительных блоков модели пятки и глава, которая даёт каждый из них; из них возникает картина напряжений, а из картины напряжений через обратную связь ремоделирования — шпора. Нижний ряд: главы-инструменты, без которых ни один блок не держится, — от стрелок сил до проверки готовой картинки. Эта карта сопровождает книгу и заполняется часть за частью.

Части II и III дают тебе язык и законы материала, часть IV превращает тело в сетку, часть V — нагрузку одного шага. В части VI замыкается обратная связь: из напряжения через механостат получается стимул, из стимула — ремоделирование костной ткани, из перестройки — более плотное место, то есть энтезофит. В конце стоит вопрос из заглавия, причём как расчёт: тяга и давление наращивают кость в разных местах, и одно из двух попадает туда, где шпора сидит на самом деле.

Рядом идёт второй список: таблица из предисловия, которая к каждому наглядному образу называет имя, под которым он живёт в университете, — стрелка силы зовётся там вектором, сетка из треугольников — дискретизацией. Она тоже растёт в конце каждой части на те строки, которые ты только что себе заработала. Если позже лекция подсунет тебе одно из этих имён, картинка к нему у тебя уже будет.

1.9 Открытый спор как обещание

Последний пункт, и он самый красивый. У пятки наука ещё не закончила. Возникает ли шпора потому, что апоневроз тянет кость, или потому, что на энтезис действует повторяющееся давление снизу, — открыто: анатомические работы находили шпору как раз не лежащей в направлении тяги апоневроза. В главе ?sec-kap18_wo_der_sporn_waechst ты просчитаешь обе догадки как положения регулятора и сравнишь вычисленные места с тем местом, где шпора сидит анатомически. Спора ты не решишь — зато испытаешь, на что годится модель: подобраться к настоящему открытому вопросу, а не к давно решённому.

Как будет выглядеть этот ответ, выдадим уже здесь, ведь он и есть цель всего пути. В конце стоят две картинки — одна для стимула тяги, одна для стимула давления, — и на каждой модель наращивает кость в разном месте пяточной кости: тяга спереди снизу, в области шпоры; давление сверху, у голеностопного сустава. Рисунок 1.8 показывает оба места. Они отстоят друг от друга примерно на 17 миллиметров при пороге около 2 миллиметров, начиная с которого два вычисленных места вообще позволительно различать, — значит, спутать их нельзя. То, что литература разделить не может, модель разделяет в пространстве.

Права ли она при этом — совсем другой вопрос. Он неудобный, и глава ?sec-kap18_wo_der_sporn_waechst от него не уходит.

Код
# Глава 1 ничего не считает — она показывает, куда ведёт путь. Контур, оба
# места наращивания и место шпоры читаются из тех же источников данных, из
# которых их берёт и заключительная глава; так они не смогут разойтись.
import json
import pathlib

import numpy as np
import matplotlib.pyplot as plt

очертание = np.array(json.loads(
    pathlib.Path("../geometrie/kalkaneus_kontur.json").read_text())["punkte"]) * 1000.0
пакет = json.loads(
    pathlib.Path("programme/kap18/hypothesen_daten.json").read_text())
тяга = пакет["referenz"]["zug_mm"]
давление = пакет["referenz"]["druck_mm"]
шпора = пакет["parameter"]["sporn_ort_mm"]

рис, ось = plt.subplots(figsize=(7.0, 4.6))
ось.fill(очертание[:, 0], очертание[:, 1], color="#f0e2d0", ec="#a9825f",
         lw=1.6, zorder=1)

# Сначала настоящая шпора, чтобы вычисленные метки лежали поверх неё.
ось.scatter([шпора[0]], [шпора[1]], s=150, facecolor="none",
            edgecolor="#7a2b8a", lw=2.0, zorder=4)
ось.annotate("настоящая шпора", xy=(шпора[0], шпора[1]), xytext=(38, -9),
             color="#7a2b8a", fontsize=10, zorder=5,
             arrowprops=dict(arrowstyle="->", color="#7a2b8a"))

# Обе подписи лежат ВЫШЕ строки ориентации (y = −13), чтобы их выносные линии
# её не перечёркивали.
for (x, y), цвет, название, смещение in (
        (тяга, "#c0392b", "тяга наращивает здесь", (-33, -5)),
        (давление, "#1f4e9c", "давление наращивает здесь", (-34, 34))):
    ось.scatter([x], [y], s=110, color=цвет, zorder=5)
    ось.annotate(название, xy=(x, y), xytext=смещение, color=цвет, fontsize=10,
                 fontweight="bold", zorder=5,
                 arrowprops=dict(arrowstyle="->", color=цвет, lw=1.4))

# Расстояние двойной стрелкой — оно и есть высказывание рисунка.
ось.annotate("", xy=(тяга[0], тяга[1]), xytext=(давление[0], давление[1]),
             arrowprops=dict(arrowstyle="<->", color="#444", lw=1.3,
                             linestyle=(0, (4, 3))), zorder=3)
ось.text(24.8, 14.5, "17 мм", fontsize=9, color="#444",
         ha="left", va="center",
         bbox=dict(facecolor="white", alpha=0.85, edgecolor="none", pad=1.5))

# Строка ориентации под подписями, масштаб на собственной высоте под ней
# (правило оформления для картин поля).
ось.text(-13, -13.0, "сзади (пятка)", fontsize=9, color="#666")
ось.text(70, -13.0, "спереди (пальцы)", fontsize=9, color="#666", ha="right")
ось.annotate("вверх", xy=(-9, 26), xytext=(-9, 12), fontsize=9, color="#444",
             ha="center", arrowprops=dict(arrowstyle="->", color="#444",
                                          lw=1.2))
ось.plot([46, 56], [-17.5, -17.5], color="#333", lw=2.2, solid_capstyle="butt")
ось.text(51, -20.6, "10 мм", ha="center", fontsize=9, color="#333")

ось.set_xlim(-16, 74)
ось.set_ylim(-22.5, 47)
ось.set_aspect("equal")
ось.axis("off")
plt.tight_layout()
plt.show()
Рисунок 1.8: Куда ведёт книга: два места, в которых готовая модель наращивает кость — смотря какую из двух гипотез считать. Красным стимул тяги (центр кластера при 21,8/6,1 мм, в области шпоры, вплотную к настоящей шпоре); синим стимул давления (18,3/23,0 мм, сверху у суставной поверхности к таранной кости). Фиолетовым обведено место подошвенного энтезиса (24,1 мм вперёд, 3,4 мм вверх), в котором шпора сидит анатомически. Два вычисленных места отстоят на 17,33 мм, порог различения составляет 2,06 мм — гипотезы в пространстве не спутать. Здесь ничего не посчитано: значения взяты из более точного сравнительного расчёта вне книги, выводятся они в заключительной главе. Сагиттальный срез, сзади слева, спереди справа, вверх ↑.

Упражнения

Упр. 1.1 (Понять). Сопоставь пять строительных блоков модели пятки с вещами на сцене: что здесь сетка, что закон материала, что граничное условие, что система уравнений, что решатель? Назови для каждого блока кусочек с рисунка 1.2.

Упр. 1.2 (Изменить). Проведи на программе-чёрном ящике два опыта. Заранее запиши, чего ты ждёшь, и только потом двигай: (а) переведи километры с 40 на 90 — растёт ли шпора линейно или как-то иначе? (б) при 90 км включи пяточную подушку — насколько сильно меняется плотность у шпоры? Запиши оба наблюдения и сохрани их. Первое ты узнаешь в конце книги, когда глава ?sec-kap18_wo_der_sporn_waechst посчитает тот же вопрос полной моделью. Второе останется открытым: что на самом деле меняет стелька, не считает и заключительная глава — поэтому допиши, что́ регулятор подушки, по-твоему, предполагает, вместо того чтобы это вычислить.

Упр. 1.3 (Перенести). Опиши собственный пример нагрузки из спорта — болящее колено, раздражённая голень — и разложи его на те же пять блоков. Какие силы приложены, где держат, какую форму ты стала бы разбивать на сетку?

Красная нить

Где это уже встречалось, где встретится снова? Эта глава ничего не посчитала и всё пообещала. Глава 2 делает три силы осязаемыми на собственной стопе и вводит стрелку силы. Каждая следующая часть заполняет один блок карты с рисунка 1.7. А в главе ?sec-kap18_wo_der_sporn_waechst моторный отсек стоит открытым: тот же вопрос, что и здесь, но с настоящей обратной связью перестройки, посчитанный на контуре, полученном из изображения, — и с ответом, который ты сможешь проверить сама.

Чего эта глава не делает

Она не даёт тебе ничего пересчитать: ни формулы, кроме ведущего вопроса словами, ни математики, ни видимого программного кода. Числа, которые здесь встречаются, взяты из сцены и из приложения с числами (масса тела, километры, силы, размеры) — ни одного из них эта глава не выводит. Чёрный ящик — демонстрационная модель: он показывает управление, а не надёжные результаты; они возникают блок за блоком в следующих главах. Он не говорит и о том, когда что-то растёт, и не считает, что делает стелька. И он ничего не решает о причине шпоры: спор между тягой и давлением здесь только объявлен, но не ведётся. Это карта, ещё не путешествие.