Зачем я вообще полез в ДНК
Первое — это интерес, как двигатель узнать что-то новое. Это также напрямую связано с развитием ИИ, где я принимаю непосредственное участие.
И второе — если посмотреть, как реальная клетка читает мРНК и собирает белки, это похоже на исполнение байткода:
-
Рибосома движется по мРНК в одном направлении и читает её триплетами — по три нуклеотида за раз. Для программиста это похоже на последовательный разбор потока, где каждый следующий токен имеет фиксированную длину.
-
Старт-кодон в живой клетке рибосома читает мРНК, где стартовый кодон записывается как AUG. Я же дальше буду работать с ДНК-записью и алфавитом A/C/G/T, поэтому в статье старт будет записан как ATG…
-
Стоп-кодоны
TAA / TAG / TGA— три варианта возврата. -
Кодоны между ними — основная программа. В живой клетке каждый такой триплет обычно означает: какую аминокислоту нужно добавить к растущей белковой цепочке.
Подменим один-единственный шаг: пусть рибосома вместо аминокислоты выполняет инструкцию виртуальной машины. Этого уже достаточно, чтобы получить маленький, но полноценный интерпретатор. С тем же четырёхбуквенным алфавитом и 64 возможными триплетами, которые природа уже использует для генетического кода.
Меня соблазнила минимальность. На обычном байткоде типа JVM писать квайн — несложно, но скучно. А вот написать его на ДНК-байткоде, чтобы каждый нуклеотид нёс смысл и при этом всё это выглядело как настоящий ген — это уже интересная задача про экономию (задача уместить максимум смысла в минимум нуклеотидов).
Оговорка для биологов: нет, я не буду упрощать клетку до «интерпретатора байткода и больше ничего». В проекте за этой статьёй есть полноценный клеточный цикл с G1/S/G2/M и чекпоинтами p53/ATR/ATM, метаболика с AMPK/mTOR/HIF, апоптоз, теломеры по Хейфлику, стволовая иерархия с Notch-Delta, внеклеточный матрикс с anoikis, paracrine-сигнализация, репарация ДНК (MMR/BER/NER/NHEJ/HR), транспозоны, горизонтальный перенос генов, сплайсинг интронов, и трёхмерная морфогенетика с диффундирующим морфогеном. Полный список — ближе к концу статьи. В первой части мы аккуратно начнём с базы — рибосомы и квайна — а всю остальную биологию я разверну в следующих статьях серии.
ДНК как четверичный байткод
Начнём с базы. Каждый нуклеотид — это одна цифра в системе с основанием 4:
|
нуклеотид |
значение |
|---|---|
|
A |
0 |
|
C |
1 |
|
G |
2 |
|
T |
3 |
Кодон — это три нуклеотида подряд, то есть число от 0 до 63:
codon_value(a, b, c) = a * 16 + b * 4 + c
Маленький бонус: кодировка A=0, T=3 и C=1, G=2 даёт удобное правило комплементарности. Сумма пары всегда равна 3, то есть комплемент любого нуклеотида — это 3 - n. Очень чистая арифметика для двойной спирали.
В коде это выглядит так:
from enum import IntEnumclass Nucleotide(IntEnum): A = 0 C = 1 G = 2 T = 3 @property def complement(self): return Nucleotide(3 - self.value)
Теперь ДНК-цепь — это просто список этих чисел. А кодон — целое значение от 0 до 63. У нас будет 64 слота под опкоды нашей виртуальной машины. Используем мы из них около 20 — остальные пока пустые, но мы вспомним о них в следующих статьях, когда подключим эволюцию.
Виртуальная рибосома: стек-машина в 64 опкода
Дальше нам нужна машина, которая по этому байткоду умеет ходить и что-то делать. Я выбрал самую простую архитектуру: стек-машина с парой регистров. Состояние такое:
-
Стек целых чисел — для арифметики и аргументов
-
Словарь переменных (адресуются одним кодоном-литералом, итого 64 слота памяти)
-
Указатель PC — какой кодон сейчас читаем
-
Выходной буфер — сюда пишутся новые нуклеотиды (понадобится для квайна)
И собственно набор инструкций. Я сделал так, чтобы биологические старт- и стоп-кодоны работали по своему прямому назначению:
опкод | кодон | действие─────────┼────────┼────────────────────────────────────NOP | AAA | ничегоPUSH | AAC | следующий кодон в стек как литералPOP | AAG | выкинуть верхнийDUP | AAT | продублировать верхнийSWAP | ACA | поменять местами два верхнихADD | ACC | a + bSUB | ACG | a − bMUL | ACT | a · bEQ | AGA | a == b это 0 или 1LT | AGC | a < b это 0 или 1JMP | AGG | pc = next_codonJZ | AGT | если top == 0, то pc = next_codonLOAD | ATA | стек - vars[next]STORE | ATC | vars[next] - popSTART | ATG | вход (работает как NOP)GENLEN | ATT | длина генома (в нт) - стекREADAT | CAA | стек - genome[pop()] WRITE | CAC | output - Nucleotide(pop())PRINT | CAG | напечатать pop() (для отладки)STOP | TAA | остановка (плюс TAG/TGA)
Шаг рибосомы выглядит так: считываем кодон по pc, увеличиваем pc на 1, выполняем соответствующее действие. Если действие требует литерал — считываем ещё один кодон и снова увеличиваем pc.
В Python это получается компактно. Вот скелет — без отделочных деталей:
class Ribosome: def __init__(self, genome): self.genome = genome # список Nucleotide self.stack = [] self.vars = {} self.pc = 0 # индекс текущего кодона self.output = [] # дочерняя цепь self.halted = False def codon_at(self, idx): # склеить три нт в число 0..63 base = idx * 3 s = self.genome return int(s[base]) * 16 + int(s[base + 1]) * 4 + int(s[base + 2]) def fetch(self): c = self.codon_at(self.pc) self.pc += 1 return c def step(self): code = self.fetch() if code in STOP_CODONS: self.halted = True elif code == PUSH: self.stack.append(self.fetch()) elif code == ADD: b = self.stack.pop() self.stack[-1] += b elif code == LT: b = self.stack.pop() self.stack[-1] = 1 if self.stack[-1] < b else 0 elif code == JZ: target = self.fetch() if self.stack.pop() == 0: self.pc = target elif code == LOAD: slot = self.fetch() self.stack.append(self.vars.get(slot, 0)) elif code == STORE: slot = self.fetch() self.vars[slot] = self.stack.pop() elif code == GENLEN: self.stack.append(len(self.genome)) elif code == READAT: i = self.stack.pop() self.stack.append(int(self.genome[i])) elif code == WRITE: v = self.stack.pop() self.output.append(Nucleotide(v % 4)) # ... остальное аналогично
Запускать просто: вызывать step() в цикле, пока halted не станет True.
Удобно ещё и то, что у природы уже есть служебные кодоны — то, что в ассемблере мы пишем как «начало процедуры» и «возврат». Это ATG (старт-кодон, с него любой ген начинает читаться) и тройка TAA / TAG / TGA (стоп-кодоны). Представьте, что природа за вас уже выбрала номера для int 21h — нечестное преимущество.
Если интересно, какие кодоны эти служебные занимают:
ATG = 14,TAA = 48,TAG = 50,TGA = 56. Я их зарезервировал и обыкновенные опкоды разместил в свободных номерах 0…18, чтобы не конфликтовать с биологией.
Вот в принципе и весь интерпретатор. На рабочем варианте получается около 80 строк — даже короче, чем эта секция статьи. Самое интересное начинается дальше.
Квайн: программа знает, как себя копировать
Теперь главное. Квайн в обычном программировании — это программа, которая печатает свой собственный исходный код. Классический пример на C занимает страницу и выглядит как смесь экранирования и магии.
Наш квайн делает то же, только в ДНК-форме. Печатает не символами через printf, а нуклеотидами через WRITE. И не текст исходника, а собственный геном — букву за буквой, прямо из памяти.
В живой клетке аналог этого процесса — репликация ДНК. ДНК-полимераза проходит по матричной цепи и нуклеотид за нуклеотидом строит дочернюю. Главное, что ей нужно — доступ к собственной матрице, к самой себе. Без рефлексии (доступа программы к своему коду) самовоспроизведение в общем виде невозможно — это известный результат теоретической информатики, см. теорему о неподвижной точке Клини.
У нас рефлексия есть — она называется READAT. Эта инструкция берёт со стека индекс и кладёт обратно нуклеотид на этой позиции. То есть программа может читать свой собственный геном. С этим уже всё получается.
Псевдоассемблер
Вот что должна делать наша квайн-программа на псевдоассемблере:
START # точка входаPUSH 0 # положить 0 на стекSTORE 0 # сохранить в vars[0] — это наш счётчик iloop: LOAD 0 # положить i на стек GENLEN # положить длину генома LT # 1 если i < len, иначе 0 JZ end # если 0 — выходим из цикла LOAD 0 # i READAT # genome[i] — это нуклеотид как число 0..3 WRITE # вывести в output LOAD 0 PUSH 1 ADD # i + 1 STORE 0 # сохранить обратно JMP loopend: STOP
Если эту программу скомпилировать в кодоны, получится 25 кодонов, то есть 75 нуклеотидов.
Эта же программа в нуклеотидах
После сборки получается такая ДНК-последовательность:
ATGAACAAAATCAAAATAAAAATTAGCAGTCGAATAAAACAACACATAAAAAACAACACCATCAAAAGGACCTAA

Дизассемблер квайна. 25 кодонов = 75 нуклеотидов = вся программа целиком. ATG в начале — точка входа, TAA в конце — выход. Между ними — обычный цикл со счётчиком и инструкцией READAT, через которую программа читает свою же ДНК.
Это полный исходный код. Двадцать пять кодонов:
Покодонная разбивка с мнемониками — для любителей всё проверять
codon | нуклеотиды | мнемоника─────────┼─────────────┼────────────── 0 | ATG | START 1 | AAC | PUSH 2 | AAA | (литерал 0) 3 | ATC | STORE 4 | AAA | (литерал 0) 5 | ATA | LOAD ← начало loop 6 | AAA | (литерал 0) 7 | ATT | GENLEN 8 | AGC | LT 9 | AGT | JZ 10 | CGA | (литерал 24, адрес end) 11 | ATA | LOAD 12 | AAA | (литерал 0) 13 | CAA | READAT 14 | CAC | WRITE 15 | ATA | LOAD 16 | AAA | (литерал 0) 17 | AAC | PUSH 18 | AAC | (литерал 1) 19 | ACC | ADD 20 | ATC | STORE 21 | AAA | (литерал 0) 22 | AGG | JMP 23 | ACC | (литерал 5, адрес loop) 24 | TAA | STOP
Что важно: этот геном не «перезаписан вручную». В нашем коде есть простой ассемблер, который собирает программу из инструкций и возвращает строку нуклеотидов. То есть это настоящий код, скомпилированный для четверичной VM. VM в контексте нашего проекта это программа, которая исполняет другие программы. Конкретно у нас VM — это Рибосома.
Запуск: смотрим, как указатель (рибосома) шагает по цепи
Самое наглядное в работе с такой VM — это запустить её и увидеть, как точка-указатель ползёт по цепи нуклеотидов, шаг за шагом.

Вот как это выглядит в терминале (тут урезанная трассировка маленькой программы 2 + 3; PRINT):
старт — рибосома села на ATG 5'-ATGAACAAGAACAATACCCAGTAA-3' ^^^ pc=0 стек=[] вывод=∅исполнено: AAC (PUSH) 5'-ATGAACAAGAACAATACCCAGTAA-3' ^^^ pc=3 стек=[2] вывод=∅исполнено: AAC (PUSH) 5'-ATGAACAAGAACAATACCCAGTAA-3' ^^^ pc=5 стек=[2, 3] вывод=∅исполнено: ACC (ADD) 5'-ATGAACAAGAACAATACCCAGTAA-3' ^^^ pc=6 стек=[5] вывод=∅5 ← это PRINT вывелисполнено: CAG (PRINT) 5'-ATGAACAAGAACAATACCCAGTAA-3' ^^^ pc=7 стек=[] вывод=∅стоп-кодон TAA — рибосома отделяется

С квайн таким же образом, только цикл проходит 75 раз и в вывод накапливается копия исходной цепи. После завершения проверяем:
mother = build_quine() # исходный геномribosome = Ribosome(mother)daughter = ribosome.run() # запустить до STOPprint(mother)print(daughter)print(str(mother) == str(daughter))
Вывод:
ATGAACAAAATCAAAATAAAAATTAGCAGTCGAATAAAACAACACATAAAAAACAACACCATCAAAAGGACCTAAATGAACAAAATCAAAATAAAAATTAGCAGTCGAATAAAACAACACATAAAAAACAACACCATCAAAAGGACCTAATrue
Цепи побитово идентичны. И это работает не потому, что мы захардкодили вывод — мы реально читаем нуклеотид за нуклеотидом из собственного генома через READAT и пишем в выходной буфер через WRITE. На каждом тике программа может сделать что-то ещё (мутировать саму себя, например) — мы просто не делаем.
Если запустить ту же дочернюю цепь как новый вход, получится точно такая же копия. Я погонял через три поколения — все три побитово равны:
поколение 1: ATGAACAAAATCAAAATAAAAATTAGCAGTCGAATAAAACAACACATAAAAAACAACACCATCAAAAGGACCTAAпоколение 2: ATGAACAAAATCAAAATAAAAATTAGCAGTCGAATAAAACAACACATAAAAAACAACACCATCAAAAGGACCTAAпоколение 3: ATGAACAAAATCAAAATAAAAATTAGCAGTCGAATAAAACAACACATAAAAAACAACACCATCAAAAGGACCTAA
Наследственность работает. На таком крошечном примере уже видно главное свойство живого: способность производить точную копию себя, опираясь на инструкции, которые сами в себе и записаны.

То есть квайн из этой статьи — это первый кирпичик. На нём всё остальное стоит. Но без него и всё остальное было бы не нужно.
А что если поменять одну букву?
Раз уж мы получили работающую самокопирующуюся программу — самое естественное, что хочется сделать дальше, это её сломать. И посмотреть, насколько она вообще терпима к ошибкам.
В реальной ДНК такие ошибки происходят постоянно. Случайная замена одной буквы (точечная мутация) — это базовое событие, на котором стоит вся эволюция. Каждая клетка тела человека получает примерно 10 000 повреждений ДНК в день — большинство тут же чинится репарационными системами, но не всё.
Я написал маленький эксперимент: берём наш 75-нуклеотидный квайн и пробуем все возможные одиночные замены. Каждый из 75 нуклеотидов можно заменить на 3 другие буквы — итого 225 вариантов. Каждый вариант запускаем и смотрим, что получится.

Вот, например, что происходит при мутации финального стоп-кодона TAA в позиции 73:
кодон #24: TAA (STOP) → TCA (?)мутированный геном: 5'-ATGAACAAAATCAAAATAAAAATTAGCAGTCGAATAAAACAACACATAAAAAACAACACCATCAAAAGGACCTCA-3'✗ результат: loop (превышен лимит шагов)
Рибосома доходит до конца генома, но TCA не определён как стоп-кодон — она просто его пропускает и продолжает крутиться в цикле, пока не упрётся в искусственный лимит шагов. У живой рибосомы такого «лимита шагов» нет — она будет крутиться, пока хватает ресурсов. Гипотетически до тепловой смерти Вселенной.

Запуск всех 225 вариантов даёт довольно драматичную статистику:

Ноль идеально нейтральных — потому что в нашем квайне нет ни одного «лишнего» нуклеотида. Все 75 что-то делают: опкоды, литералы, адреса переходов. Каждая буква работает.
Но самое интересное — посмотреть на «повреждённые» поближе. Из этих 72 случаев 35 имеют сходство с оригиналом 90% и выше. То есть квайн всё-таки скопировался — но скопировался вместе со своей мутацией. Дочь получила ту же мутированную ДНК. Это и есть наследственная мутация — копирование с ошибкой, которая передаётся следующему поколению.
В наших 35 мутантах нет ни одного, который улучшил бы исходную программу — все они либо нейтральны, либо чуть портят картину. Но если запустить эту схему миллион раз и добавить отбор (выживают только те, кто хорошо копируется в текущих условиях) — получится дарвиновская эволюция в чистом виде. Об этом будет третья статья серии.
А пока — короткая мораль из этих 153 «летальных» случаев. Один случайный байт не там — и линия мертва. Если бы реальная ДНК работала так же, любой космический луч или химическая ошибка убивали бы клетку. Но клетка как-то живёт. Дело в том, что у неё есть несколько систем репарации ДНК, которые ловят и исправляют большую часть повреждений ещё до репликации. Пять разных систем для разных типов поломок — MMR, BER, NER, NHEJ, HR. Без них наш квайн не дожил бы до второй итерации цикла. Об этих системах — в следующих частях серии. А прямо сейчас — короткая карта всего, что у меня уже реализовано в проекте.
Карта проекта
Прежде чем перейти к финалу, надо пояснить суть статьи. Иначе создаётся впечатление, что у нас простой квайн плюс пара мутаций — а на самом деле там более 15000 строк Python и 25+ биологических подсистем, каждая со своим научным основанием. Я их буду раскручивать в следующих статьях, но раз вы дочитали до сюда — вот честная карта проекта.
Исходные коды проекта опубликую на публичный гит, после завершения цикла статей.
Приближенная карта проекта
Ниже — таблицы того, что за этой статьёй сейчас работает. Это тизер для двух следующих частей, не выводы из этой. Если что-то из терминов непонятно — большая часть имеет короткое пояснение справа.
Молекулярный уровень
|
что реализовано |
биологический аналог |
|---|---|
|
Стек-машина рибосомы (то, что мы сегодня собрали) |
translation: настоящая рибосома точно так же читает mRNA по 3 нуклеотида за раз и собирает из этого белок |
|
Двойная спираль с антипараллельным комплементом |
реальная ДНК: две цепи закручены в обратных направлениях, A пара T, C пара G |
|
RNA polymerase как отдельный автомат |
transcription у pol II: специальный фермент идёт по ДНК и собирает по ней mRNA-копию |
|
Сплайсинг интронов перед трансляцией |
spliceosome у эукариот: вырезает из mRNA не-кодирующие участки (intron), оставляя только то, что пойдёт в белок (exon) |
|
Ассемблер кодонов с rRNA-регионом |
tRNA + аминоацил-tRNA-синтетазы: набор адаптеров, переводящих 3-буквенный кодон в нужную аминокислоту |
Эволюция и наследственность
|
что реализовано |
что это значит |
|---|---|
|
Точечные мутации, insertions, deletions |
три классических типа повреждения ДНК: замена одной буквы, вставка лишней, удаление существующей. У нас управляется параметром |
|
MMR / BER / NER / NHEJ / HR |
пять реальных систем репарации ДНК. MMR ловит ошибки полимеразы, BER чинит окисленные основания, NER лечит UV-повреждения, NHEJ и HR заделывают двунитевые разрывы по разным стратегиям |
|
Proofreading у polymerase |
у настоящей полимеразы есть |
|
Gene duplication |
при копировании генома изредка целый участок дублируется. Один из главных эволюционных механизмов появления новых генов: копия может мутировать без потери исходной функции |
|
Transposable elements |
«прыгающие гены»: участки ДНК с особым маркером, способные с малой вероятностью самокопироваться в случайное место генома. У человека ~45% всей ДНК — это потомки таких прыжков |
|
Horizontal gene transfer |
клетка может «забрать» фрагмент ДНК у живого соседа. У бактерий — главный путь распространения новых признаков, в том числе устойчивости к антибиотикам |
|
Гомологичная рекомбинация в реальном времени |
две клетки обмениваются гомологичными участками генома. Это аналог |
|
Кроссинговер по границам кодонов |
точка разреза при рекомбинации выровнена по 3 нуклеотидам, чтобы не сдвигалась рамка считывания |
Клеточный цикл и регуляция
|
что реализовано |
биологический смысл |
|---|---|
|
Фазы G1 / S / G2 / M / G0 |
стандартный клеточный цикл: подготовка → синтез ДНК → проверка → митоз → возможный покой |
|
Checkpoint G1/S (Restriction point) |
|
|
Checkpoint Intra-S |
|
|
Checkpoint G2/M |
|
|
SAC (Spindle Assembly Checkpoint) |
|
|
Quiescence (G0) |
состояние покоя: клетка жива, активно метаболизирует, но не делится. Большинство нейронов и кардиомиоцитов так и живут всю жизнь |
|
AMPK (энергетический сенсор) |
замечает падение уровня ATP. При энергетическом кризисе блокирует деление и запускает autophagy — расщепление собственных белков ради энергии |
|
mTOR (рост и синтез белка) |
сенсор питательных веществ. При нехватке аминокислот тормозит трансляцию, экономит ресурс на чёрный день |
|
HIF-1α (гипоксия) |
реагирует на низкий уровень кислорода. Переключает клетку с эффективного |
|
UPR (ER-стресс) |
при накоплении неправильно свёрнутых (misfolded) белков останавливает трансляцию. Если стресс хронический — запускает apoptosis |
|
Apoptosis (запрограммированная смерть) |
контролируемый сценарий смерти клетки. Срабатывает по возрасту, по превышению порога повреждений или по сигналу извне |
|
Hayflick limit + telomere clock |
теломеры — защитные «колпачки» на концах хромосом — укорачиваются при каждой репликации. Когда заканчиваются, клетка уходит в |
Многоклеточность
|
что реализовано |
роль в ткани |
|---|---|
|
Stem cell hierarchy (15 уровней commitment) |
лестница от тотипотентной стволовой клетки до terminally differentiated (окончательно специализированной). Каждый шаг — потеря возможностей в обмен на специализацию |
|
Asymmetric division |
одно деление стволовой клетки даёт одну новую stem (для поддержания резерва) и одну progenitor (которая дальше специализируется) |
|
Niche-зависимость stemness |
стволовая клетка сохраняет статус, только пока чувствует поддерживающие сигналы от ниши. Уйдёт из ниши — теряет stemness и становится обычной |
|
Notch–Delta lateral inhibition |
контактный механизм: stem-клетка через мембранные лиганды «сообщает» соседям что она stem, чтобы они не претендовали на эту роль. Предотвращает кластеры stem-клеток |
|
Эпигенетическая память |
состояние специальных ячеек памяти переживает деление и наследуется потомкам без изменения самой ДНК. Биологический аналог — паттерны метилирования |
|
Дифференциация по морфогену |
один и тот же геном даёт разное поведение в зависимости от концентрации сигнальной молекулы в данной точке пространства. Так формируются ткани и органы у эмбриона |
|
Extracellular matrix (ECM) |
белковое поле вокруг клеток (collagen, laminin, fibronectin) — то, за что клетки физически цепляются и что держит ткань вместе |
|
Integrins + anoikis |
интегрины — белки-якоря на мембране клетки, цепляющиеся за ECM. Если клетка не прикреплена — запускается apoptosis. Защита организма от метастазирования |
|
Paracrine signals (mitogen, morphogen, death) |
растворимые молекулы, диффундирующие от клетки-источника. Концентрация падает по закону 1/d² от расстояния |
|
Mechanotransduction (YAP/TAZ-like) |
клетка чувствует физическое давление соседей. При тесном контакте перестаёт делиться — это |
Ткань и среда
|
что реализовано |
как именно |
|---|---|
|
3D-пространство с непрерывными координатами |
каждая клетка имеет (x, y, z); поиск соседей через |
|
Метаболизм: пул нуклеотидов + ATP |
конечные ресурсы внутри клетки. Клетки конкурируют за них; голод останавливает рибосому |
|
Глобальный пул пищи |
среда раздаёт еду равномерно на всех живых. Чем больше популяция, тем меньше каждой клетке. Естественный пресс на размножение |
|
Гипоксия в плотном ядре |
концентрация O₂ падает экспоненциально с плотностью соседей: O₂ = exp(−density × k). В реальной ткани без сосудов всё работает примерно так |
|
Пульсирующий морфоген + дрейф центра |
сигнальное поле меняется во времени. Заставляет клетки перестраиваться, не давая ткани застыть в одной форме |
|
Apoptosis-волны |
когда большая когорта клеток одного возраста доходит до Hayflick limit, они умирают почти одновременно. После такой волны популяция резко обновляется |
Что мы только что увидели
Если смотреть на наш квайн с расстояния — это просто 75 букв в строке. Если приглядеться, эти 75 букв содержат в себе цикл со счётчиком, проверку условия, обращение к собственной памяти и аккуратный выход. Любой студент-первокурсник напишет такое на питоне за пять минут — здесь интересно не что написано, а где написано. Те же самые буквы (A, T, G, C), которые вы найдёте в учебнике по молекулярной биологии, в нашей системе работают как настоящий байткод. И программа на нём — настоящая, исполняемая, копирующая саму себя.
Главный фокус прячется в одной инструкции — READAT. Программа читает свой собственный код, нуклеотид за нуклеотидом, и сама же его выводит наружу. Это и есть та самая рефлексия, без которой самокопирование в общем виде математически невозможно (см. теорему Клини о неподвижной точке). В живой клетке роль READAT играет ДНК-полимераза, которая шагает по матричной цепи и считывает её букву за буквой. Один и тот же принцип на двух уровнях абстракции: что виртуальная машина в питоне, что белковая машина в клетке — обе делают по сути одно и то же: читают свой шаблон и копируют его наружу.
Что мы получили в итоге — 908 шагов виртуальной рибосомы, 75 скопированных нуклеотидов, побитовое совпадение материнской и дочерней цепи. Через три поколения — то же самое: ровно те же 75 букв в той же последовательности. Это не “жизнь” в биологическом смысле, а минимальная вычислительная игрушка, показывающая один из центральных мотивов живых систем: наследуемое самокопирование через чтение собственного шаблона. До настоящей клетки отсюда огромная дистанция — метаболизм, мембрана, регуляция, ошибки копирования, отбор и среда. Но как первый вычислительный кирпичик это уже интересно.
А вот дальше — самое интересное. Стоит добавить к процессу копирования крошечную ошибку (мутацию), и появится эволюция: одни линии будут вымирать, другие случайно получат преимущество. Дайте клетке метаболизм и пространство вокруг — и она начнёт делиться, образуя трёхмерную ткань с дифференциацией.

Об этом — следующие части серии, где мы дойдем до того, что научим нашу молекулярную колонию выполнять математические операции и может даже отвечать текстом на наши вопросы. Также, расскажу о том как уперся в вычислительные мощности. На Mac mini M4 Pro симуляция бежала отлично, пока я не дошёл до колоний на ±20 000 клеток. CPU+GPU стал узким местом — каждый тик на M4 при такой популяции занимал секунды, а потом минуты, но биология требует тысячи тиков для интересной динамики. На таких масштабах сидеть и смотреть на UI становилось утомительно. Пока намекну на ключевые слова: ROCm + GPU-kernels.
P.S. Я не претендую на открытие новых научных направлений. Сейчас моя основная цель — разобраться, как устроены живые организмы изнутри, и переложить это в виде программируемых моделей. Биология описана в учебниках формулами и схемами — а мне хочется уметь её запускать.
ссылка на оригинал статьи https://habr.com/ru/articles/1028184/