restriction-analysis/restriction-sites/SKILL.md
Find restriction enzyme cut sites in DNA sequences using Biopython Bio.Restriction. Searches single enzymes, batches, or commercial enzyme sets and returns cut positions for linear or circular DNA. Use when locating where one or more restriction enzymes cut a sequence, screening a sequence for the presence or absence of a site, or counting how often an enzyme cuts.
npx skillsauth add GPTomics/bioSkills bio-restriction-sitesInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Reference examples tested with: BioPython 1.83+ (API verified on 1.86)
Before using code patterns, verify installed versions match. If versions differ:
pip show biopython then help(Bio.Restriction.Analysis) to check method namesIf code throws ImportError, AttributeError, or TypeError, introspect the installed package and adapt the example to match the actual API rather than retrying. The Analysis "cutters" methods in particular were renamed across versions (see Common Errors).
"Find where this enzyme cuts my DNA" -> Return the cut positions for one or more restriction enzymes along a linear or circular sequence.
enzyme.search(seq, linear=...) for one enzyme; Bio.Restriction.Analysis(batch, seq, linear=...).full() for many.The one fact that governs every result: search() returns a 1-based position equal to the first base of the downstream fragment (the base immediately 3' of the cut on the top strand), not the start of the recognition site. For EcoRI G^AATTC whose site starts at position 4, search reports 5 (the base after the cut). Confusing the cut position with the recognition-site start is the single most common bug in restriction code, and it propagates silently into fragment sizes and map coordinates.
| Decision | Options | When to pick which |
|----------|---------|--------------------|
| Enzyme scope | one enzyme / a curated RestrictionBatch / CommOnly / AllEnzymes | A named enzyme when the assay dictates it; a small batch for a cloning panel; CommOnly (623 buyable enzymes) when the answer must be an enzyme one can purchase; AllEnzymes (1088, includes non-commercial) only for exhaustive in-silico surveys |
| Topology | linear=True (default) / linear=False | linear=False for any plasmid, viral circle, or BAC. A circular molecule lets a site span the origin and changes fragment counts (see fragment-analysis) |
| Question | does it cut? / how often? / where? | search() for positions; Analysis.with_N_sites(n) for exact cut counts; bool(search()) for a yes/no screen |
Use CommOnly not AllEnzymes by default: proposing an enzyme nobody sells wastes a wet-lab cycle. The README's legacy "800+" figure is stale; the installed database holds 1088 enzymes total, 623 commercially available.
from Bio import SeqIO
from Bio.Restriction import EcoRI
record = SeqIO.read('sequence.fasta', 'fasta')
seq = record.seq
sites = EcoRI.search(seq) # list of 1-based cut positions, e.g. [5, 14]
print(f'EcoRI cuts {len(sites)} time(s) at {sites}')
if not sites:
print('EcoRI does not cut this sequence')
Goal: Screen a sequence against several enzymes at once and keep only those that cut.
Approach: Build a RestrictionBatch, run Analysis.full() to get every enzyme's positions, then filter to cutters with with_sites().
from Bio.Restriction import RestrictionBatch, Analysis, EcoRI, BamHI, HindIII, XhoI
batch = RestrictionBatch([EcoRI, BamHI, HindIII, XhoI])
analysis = Analysis(batch, seq, linear=True)
cutters = analysis.with_sites() # {enzyme: [positions]} only enzymes that cut
for enzyme, positions in cutters.items():
print(f'{enzyme}: {positions}')
Goal: Separate single-cutters (linearize a plasmid), double-cutters (excise an insert), and non-cutters (safe to carry through a digest).
Approach: Analysis exposes with_N_sites(n) for an exact count and without_site() for enzymes with no site. (The older once_cutters()/twice_cutters()/only_dont_cut() names do not exist in current BioPython.)
from Bio.Restriction import Analysis, CommOnly
analysis = Analysis(CommOnly, seq, linear=False) # circular plasmid
single_cutters = analysis.with_N_sites(1) # {enzyme: [pos]} good for linearization
double_cutters = analysis.with_N_sites(2) # {enzyme: [pos, pos]} good for excision
non_cutters = analysis.without_site() # {enzyme: []} safe in a multi-step digest
all_cutters = analysis.with_sites() # any number of sites
print(f'{len(single_cutters)} single-cutters, {len(non_cutters)} non-cutters')
# Pretty-print a chosen subset
analysis.print_as('map')
analysis.print_that(single_cutters) # formats the dict you pass it
from Bio.Restriction import AllEnzymes, CommOnly, Analysis
print(f'{len(AllEnzymes)} known enzymes, {len(CommOnly)} commercially available')
analysis = Analysis(CommOnly, seq) # default: only buyable enzymes
for enzyme, positions in analysis.with_sites().items():
print(f'{enzyme}: {positions}')
from Bio.Restriction import EcoRI
sites_linear = EcoRI.search(seq, linear=True) # ends are free; no wrap-around
sites_circular = EcoRI.search(seq, linear=False) # a site may span the origin
A circular search can find a site that straddles position 1, which a linear search misses. Always pass linear=False for plasmids; the fragment count and map differ (see restriction-analysis/fragment-analysis).
Goal: Know what ends an enzyme leaves before designing a ligation.
Approach: elucidate() draws the cut unambiguously; the boolean predicates and the signed ovhg summarize it. The sign convention is the trap: negative ovhg is a 5' overhang, positive is a 3' overhang, zero is blunt.
from Bio.Restriction import EcoRI, KpnI, EcoRV
for enz in (EcoRI, KpnI, EcoRV):
print(enz, enz.elucidate()) # EcoRI G^AATT_C ; KpnI G_GTAC^C ; EcoRV GAT^_ATC
print(f' site={enz.site} ovhg={enz.ovhg} ovhgseq={enz.ovhgseq!r}'
f' 5prime={enz.is_5overhang()} 3prime={enz.is_3overhang()} blunt={enz.is_blunt()}')
# EcoRI.ovhg == -4 -> a 5' overhang (NOT +4). In elucidate, ^ = top-strand cut, _ = bottom-strand cut.
from Bio.Restriction import AllEnzymes
if 'EcoRI' in AllEnzymes:
ecori = AllEnzymes.get('EcoRI')
sites = ecori.search(seq)
Not every enzyme has a fixed 6-bp palindrome. Degenerate sites use IUPAC codes (HincII GTYRAC), and interrupted palindromes carry an unspecified N spacer (BstXI CCANNNNNNTGG, DraIII CACNNNGTG). Note what is_ambiguous() actually means in BioPython: it is True when the site or cut is ambiguous -- N-spacer / interrupted sites (BstXI, DraIII) and enzymes that cut outside their site -- but it is False for a fully IUPAC-degenerate site whose cut is fixed, such as HincII GTYRAC (BioPython reports that as is_defined()). So is_ambiguous() does not detect IUPAC degeneracy; read enzyme.site for the actual letters. Either way, the expected cut frequency for a degenerate or N-containing site is not a clean 1/4^n, so do not estimate cutter rarity from site length alone for these enzymes.
from Bio.Restriction import HincII, BstXI
for enz in (HincII, BstXI):
print(enz, enz.site, 'ambiguous=', enz.is_ambiguous())
from Bio import SeqIO
from Bio.Restriction import RestrictionBatch, Analysis, EcoRI, BamHI
batch = RestrictionBatch([EcoRI, BamHI])
for record in SeqIO.parse('sequences.fasta', 'fasta'):
cutters = Analysis(batch, record.seq).with_sites()
print(record.id, {str(e): p for e, p in cutters.items()})
| Symptom | Cause | Fix |
|---------|-------|-----|
| AttributeError: 'Analysis' object has no attribute 'once_cutters' | Method renamed across BioPython versions | Use with_N_sites(1) / with_N_sites(2); without_site() for non-cutters; with_sites() for any cutter |
| AttributeError: ... 'print_that_cut' / 'esite' | These names do not exist | Use print_as(...) + print_that(dct); read the cut with elucidate() |
| Fragment sizes or map coordinates off by a few bases | Treated search() output as the recognition-site start | The integer is the cut position = first base of the downstream fragment (1-based) |
| Site near the origin missed on a plasmid | Searched with linear=True | Pass linear=False for circular DNA |
| Reported a 5' overhang as 3' (or vice versa) | Misread the ovhg sign | Negative ovhg = 5' overhang, positive = 3' overhang, zero = blunt; confirm with elucidate() |
| Proposed enzyme cannot be purchased | Searched AllEnzymes | Search CommOnly when the answer must be a buyable enzyme |
tools
End-to-end CLIP-seq pipeline from FASTQ to ENCODE-compliant binding sites, single-nucleotide crosslink maps, annotation, motifs, and (optionally) differential binding. Use when running the full Yeo lab eCLIP / iCLIP / iCLIP2 / iCLIP3 / irCLIP / PAR-CLIP analysis with SMInput control, protocol-specific UMI extraction, ENCODE STAR parameters, CLIPper or Skipper peak calling with stringent log2 FC and -log10 p thresholds, IDR rescue and self-consistency QC, and downstream motif registration with mCross or PEKA.
development
Detect, date, and contextualize whole-genome duplication (WGD / paleopolyploidy) events using wgd v2 (Chen et al 2024), KsRates (Sensalari 2022 substitution-rate-corrected Ks dating), DupGen_finder (Qiao 2019), MAPS (Li 2018 phylogenomic), POInT (Conant 2008 ordered-block), SLEDGe (2024 ML-based), Whale.jl (Bayesian DL+WGD), and synteny-anchored paranome construction. Use when identifying ancient polyploidy from Ks distributions and synteny block analysis, positioning WGD events relative to speciation, distinguishing tandem from segmental from WGD duplications, dating the 2R/3R vertebrate / fish / salmonid WGDs, building paranome and Ks-age mixture models, applying KsRates substitution-rate correction across lineages, or testing alternative biased-fractionation / dosage-balance models post-WGD.
tools
Build whole-genome alignments using Progressive Cactus (Armstrong 2020 reference-free clade-level WGA), Minigraph-Cactus (Hickey 2024 pangenome-aware), LASTZ chain/net (UCSC pipeline), MUMmer4 (Marçais 2018 pairwise), minimap2 -x asm5/10/20 (Li 2018 fast pairwise), AnchorWave (Song 2022 WGD-aware), and Mauve / progressiveMauve (bacterial). Operates the HAL toolkit (Hickey 2013) for downstream extraction including halSynteny, halLiftover, halBranchMutations, and hal2maf. Use when constructing multi-species alignments for comparative-annotation projection (TOGA), synteny detection, conservation analyses (phyloP / PhastCons), or pangenome graph construction; selecting between reference-free (Cactus) and reference-anchored (LASTZ chains/nets) approaches; tuning sensitivity for closely vs distantly related genomes; or producing HAL files for genome-wide downstream tools.
development
Detect syntenic blocks and structural rearrangements between genomes using MCScanX (Wang 2012), JCVI/MCScan (Tang 2008 Python), GENESPACE (Lovell 2022) for orthology-anchored riparian visualization, SyRI for structural variation, AnchorWave for sequence-level synteny, i-ADHoRe 3.0 for highly diverged species, SynNet for synteny networks, and ntSynt for multi-genome macrosynteny. Use when identifying collinear gene blocks across species, distinguishing macrosynteny from microsynteny, detecting inversions/translocations/duplications, anchoring orthology in WGD lineages, producing publication riparian plots, computing synteny block age via Ks (cross-references whole-genome-duplication), or running synteny-aware ortholog inference in polyploids.