Синтез ДНК кажется чем-то сугубо биологическим — с пробирками, центрифугами и белыми халатами. Но что, если попробовать собрать ДНК в коде? Не просто сгенерировать последовательность, а симулировать реальные процессы: лигирование, гибридизацию, ПЦР, ошибочные вставки, ферментативные сдвиги и многое другое. В этой статье — практическая попытка воссоздать молекулярную биологию средствами Python, без библиотек типа Biopython, с нуля. Много кода, немного шуток и один вопрос — можно ли построить in silico ДНК-лабораторию?

Слова “биоинформатика” и “программирование” обычно встречаются в одном предложении, когда речь идёт о парсинге геномов, анализе экспрессии генов или машинном обучении для диагностики. Но однажды захотелось большего. Хотелось не просто читать гены, а играть с ними. Моделировать их, собирать руками. Вернее, клавиатурой.

Идея: построить в коде лабораторную скамью, где можно будет “сшивать” фрагменты ДНК, копировать их, проверять на ошибки. Причём без привычных библиотек вроде Biopython — просто Python, NumPy и желание воссоздать реальный молекулярный процесс в виртуальной среде. Можно ли собрать виртуальную ДНК-плазмиду и отдать её синтетической биологии?

Да. Но сначала немного теории.


Минимум молекулярной биологии

Если вы не биолог — не страшно. Вот краткий набор знаний, который понадобится:

  • ДНК состоит из 4 оснований: A, T, C, G.

  • A соединяется с T, C с G. Это называется комплементарностью.

  • ДНК может быть одноцепочечной или двуцепочечной.

  • Ферменты могут копировать, соединять, резать или модифицировать ДНК.

  • Сборка ДНК — это как LEGO, только меньше и с более странными инструкциями.

Соберём свой набор инструментов.


1. Структура: последовательности и цепи

Начнём с основного объекта — молекулы ДНК. Представим её как последовательность символов, но добавим немного структуры.

Язык: Python 3.11+

from typing import List

class Strand:
    def __init__(self, sequence: str):
        assert all(base in "ATCG" for base in sequence), "Invalid DNA sequence"
        self.sequence = sequence

    def complement(self) -> str:
        return self.sequence.translate(str.maketrans("ATCG", "TAGC"))

    def reverse_complement(self) -> str:
        return self.complement()[::-1]

    def __repr__(self):
        return f"5'-{self.sequence}-3'"

Пример использования:

s = Strand("ATGCGT")
print(s.reverse_complement())  # ACGCAT

Класс Strand — это строительный кирпич. Теперь можно моделировать гибридизацию, сшивку и другие операции.


2. Гибридизация: связываем цепи

Когда две цепи встречаются и могут быть комплементарными, они связываются — образуется “дуплекс”.

def can_hybridize(s1: Strand, s2: Strand, min_overlap: int = 5) -> bool:
    rc = s2.reverse_complement()
    for i in range(len(s1.sequence) - min_overlap + 1):
        if s1.sequence[i:].startswith(rc[:len(s1.sequence)-i]):
            return True
    return False

Пример:

a = Strand("ATGCGT")
b = Strand("ACGCAT")

print(can_hybridize(a, b))  # True

Здесь мы проверяем: может ли вторая цепь при перевороте и комплементации хотя бы частично приклеиться к первой.


3. ПЦР: полимеразная цепная реакция

ПЦР — это способ быстро наращивать миллионы копий ДНК с помощью фермента и температурных циклов.

from random import randint

def pcr_amplify(template: Strand, primers: List[Strand], cycles: int = 30) -> List[Strand]:
    amplified = []
    for _ in range(cycles):
        for primer in primers:
            if primer.sequence in template.sequence:
                start = template.sequence.find(primer.sequence)
                amplified.append(Strand(template.sequence[start:]))
    return amplified

template = Strand("ATGCGTACCGTTAAGT")
primer1 = Strand("ATGCG")
primer2 = Strand("AAGT")
products = pcr_amplify(template, [primer1, primer2])
print(f"Products: {len(products)}")

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


4. Лигирование: склеивание фрагментов

Фермент лигаза соединяет два фрагмента ДНК, если они совместимы (например, если один заканчивается на последовательность, с которой начинается другой).

def ligate(s1: Strand, s2: Strand) -> Strand:
    # простое сшивание, без проверки комплементарности
    return Strand(s1.sequence + s2.sequence)

frag1 = Strand("ATGCGT")
frag2 = Strand("ACCGTA")

ligated = ligate(frag1, frag2)
print(ligated)  # 5'-ATGCGTACCGTA-3'

В реальности нужна проверка на “липкие концы” (sticky ends). Но даже простая конкатенация уже позволяет моделировать базовое склеивание.


5. Ошибки и мутации

Биология не бывает идеальной. Мутации — часть игры.

from random import choice, random

def mutate(s: Strand, rate: float = 0.01) -> Strand:
    new_seq = ""
    for base in s.sequence:
        if random() < rate:
            new_seq += choice("ATCG".replace(base, ""))
        else:
            new_seq += base
    return Strand(new_seq)

original = Strand("ATGCGTACCGTTAAGT")
mutated = mutate(original, rate=0.1)
print(f"Original: {original}")
print(f"Mutated : {mutated}")

С мутациями можно играть: вставки, делеции, инверсии, перестройки. Особенно забавно смотреть, как они влияют на “виртуальный геном”.


6. Построим плазмиду

Соберём кольцевую ДНК, как это делают в лабораториях. В простом случае представим кольцо как фрагмент, где 3'-конец соединяется с 5'-концом.

class Plasmid(Strand):
    def __repr__(self):
        return f"Circular 5'-{self.sequence}-3'"

    def cut(self, site: str) -> List[Strand]:
        if site not in self.sequence:
            return [self]
        idx = self.sequence.find(site)
        return [Strand(self.sequence[:idx]), Strand(self.sequence[idx:])]

plasmid = Plasmid("ATGCGTACCGTTAAGTCCGATC")
cut_fragments = plasmid.cut("CCGTTA")
for f in cut_fragments:
    print(f)

Теперь мы можем разрезать плазмиды, вставлять гены и даже симулировать клонирование.


Заключение

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

  • Моделирование температурных профилей (анализ работы ПЦР при разных температурах).

  • Ферментативную кинетику (микроскопично эмулировать скорость работы ДНК-полимеразы).

  • Химию буферов (учёт pH, концентрации ионов взаимодействия).

  • Вероятность отжига праймеров (точное моделирование Tm).

И это будет только интереснее.

Главный вывод: биология прекрасно ложится на программирование. Более того, если вы программист — вы уже почти синтетический биолог. Вам просто нужно немного других библиотек… и чуть больше пипеток.

А пока можно собрать собственный геном, сшить его из фрагментов и “запустить” in silico.

P.S. Кода здесь много, но вы можете легко собрать из него полноценную симуляцию. Попробуйте, например, реализовать CRISPR. Или хотя бы фермент EcoRI.

Если нужен Jupyter Notebook с полным примером — напишите в комментариях, подготовим.

Комментарии (0)