Predict the DNA fragments a restriction digest produces -- sizes and sequences, for single or double digests on linear or circular DNA -- and interpret them against an agarose gel. Useful for planning a diagnostic digest to verify a clone and for matching observed bands to an expected pattern.
pip install biopython
A DNA sequence in FASTA or GenBank. For plasmids, prefer GenBank so topology is known.
Tell your AI agent what you want to do: - "Predict the fragment sizes from an EcoRI digest of my plasmid" - "What bands will I see on a gel after cutting with BamHI?" - "Calculate fragments for an EcoRI + BamHI double digest" - "My gel shows 3000, 2000, 1000 bp -- does this match an EcoRI digest?"
"What fragment sizes will I get from an EcoRI digest of my plasmid?"
"Predict the gel pattern for HindIII digestion of sequence.fasta"
"Calculate fragment sizes for an EcoRI + BamHI double digest"
"What bands will I see from digesting with both PstI and SalI?"
"Compare my predicted fragments to a 1 kb ladder and tell me which bands co-migrate"
"My gel shows bands at 3000, 2000, and 1000 bp - does this match EcoRI digestion?"
"Verify my digest worked by comparing observed vs expected fragments"
from Bio import SeqIO
from Bio.Restriction import EcoRI
record = SeqIO.read('plasmid.fasta', 'fasta')
fragments = EcoRI.catalyze(record.seq, linear=False) # tuple of fragment Seqs; no [0]
sizes = sorted([len(f) for f in fragments], reverse=True)
print(f'Fragment sizes: {sizes}')
# catalyze() returns the tuple of fragments DIRECTLY:
# EcoRI.catalyze(seq) -> (Seq(...), Seq(...), Seq(...))
# Do NOT write catalyze(seq)[0] expecting "the fragments" -- that is only the FIRST fragment,
# and iterating it then counts its bases and gives nonsense sizes.
fragments = EcoRI.catalyze(seq, linear=True)
sizes = [len(f) for f in fragments]
| DNA Type | n cuts | Fragments |
|---|---|---|
| Linear | n | n + 1 |
| Circular | n | n |
fragments = EcoRI.catalyze(seq, linear=False) # plasmid -> n fragments from n cuts
from Bio.Restriction import EcoRI, BamHI, RestrictionBatch, Analysis
def calc_fragments(seq_len, positions, linear=True):
cuts = sorted(set(positions))
if not cuts:
return [seq_len]
spans = [cuts[i + 1] - cuts[i] for i in range(len(cuts) - 1)]
if linear:
return [cuts[0]] + spans + [seq_len - cuts[-1]]
return spans + [(seq_len - cuts[-1]) + cuts[0]]
positions = [p for s in Analysis(RestrictionBatch([EcoRI, BamHI]), seq).with_sites().values() for p in s]
sizes = calc_fragments(len(seq), positions, linear=True)
def gel_pattern(sizes, ladder=(10000, 5000, 3000, 2000, 1500, 1000, 500)):
for band in sorted(set(sizes) | set(ladder), reverse=True):
marker = 'L' if band in ladder else ' '
sample = '=' * (sizes.count(band) * 4) # doubled width = co-migrating doublet
print(f'{band:>6} {marker} | {sample}')
predicted = [3000, 2000, 1000]
observed = [3050, 1980, 1020] # measured off the gel
tolerance = 100 # bp; ~5-10% of band size is realistic
for pred in predicted:
match = next((o for o in observed if abs(pred - o) <= tolerance), None)
print(f'{pred} bp -> {"matches " + str(match) if match else "NOT seen (co-migration? partial digest?)"}')
linear=False for plasmids or the fragment count is off by one.