From 5e15ad9faf0ae75a0fb171f902e566b1107f1d4d Mon Sep 17 00:00:00 2001 From: harryswift01 Date: Fri, 14 Aug 2026 14:45:20 +0100 Subject: [PATCH 1/7] feat: remove the bundled `irf308.linux.exe` executable from repository --- irf308.linux.exe | Bin 98072 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100755 irf308.linux.exe diff --git a/irf308.linux.exe b/irf308.linux.exe deleted file mode 100755 index ed62440d0756c651ecae1d97046a5e02fdf38d17..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 98072 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 }lI9<-0K!|7kx)p3Tn$WsUS z`@mOro!Fa;l(;oWn@jvg;uo}r!%af3T*<>CM;;`82l1PU4@$hG_p0857bAW`C_`e(Y=3Bzy?+HArUn<0I_Za=Mf&$0R z`^9#bOVYnjdTTxX-Hc1I`bpUnqMx3K(@#peb^0QFZ3}J-4`N{Vvk)8?-EPXK-8CV|kE{`L>@*-t*pOg`6(U&zcOqw|`?D+X#kLDuPW z@KqPw@xM=BL;4odUmyg}6?-VIzf;fekok73w4Lz!F?jvBVU%ir(yt@^k0pJSuS)I_ zAmm4^JGiU7D2X$9)_tGnsi*;asuK&(A@M%+_CfFFh&(Cz988|dGgc-ft{+rD85Mpp zXhiuiH}FFztg8P=d-(C`TbvPjKY`Te`o!rGzhy;ao|%Xe888e@kuG{X|96vqCFye{ zz3QKrJ7T5$U{4djj`%n^)OzB)C@&)^FqtRqo^THa-k#Kr{$$Fz%sd9VU+@cKukb9Fz?T7E3*2n6RB(~^n}BZy?v(ow{Rx-N zyjJw*@8CNvL{CP7kz~zx?Os#z@(-c6DI6XnIiUCFqOHwzL;Js3`{^crv&7R1YovZM zo)$1N$e=Q9Abt_}u3v`3*9bnj8T(d`lU2GUotBsOs{wxl_%7j}qE9oa@me_;O~aR} zhDSXh1k;BJe{Lh)reB4_EJ1NVMut+i!k@c=Zw4;w3X=Z~GQYZB=C8&N?TPq}%QF@v z{_8OEDpZwzOeTZgU$e#}fT(=YmTQvLd<{QBV&!11tL+9i>9c+l4*%4ozb^4y(fgVo zsAs9*EaOs;{_6*S9r&mr(|?JTdoGdNn@qe<0MYtI{Vl3~%5N|u!__e8U?=>02|D+Y zk4%rhy1wGTR~-0?17C6AD-L|cfv-656$ifJz*ijjiUVJ9;42RN-{8Q?6I?2r1txUz zlqtMi@!>cPwuYO~mElr%Ea9oE=p~m5aRun>9IpzGGmp-EnklflQcb+fzvXh`Kln^9 zBdkw!3D|PhU_3+hvn)xZ z2FpBMF1Dno3zwC;Qqx>2bl}}b=x3+g9u;d@Y=KahlmBOCS)|I~ht0E-?|$?A&s3L5 z`F|(KHvEsz?JCo*YfN~v34d6W#Jb=gmkvaG-0xAxJ2|885>;%{13^H|H0 zFLE}!E$hVDmeskC?GRUy_4Sr@<*)cU)nj}!?*q&F1si~V>`kyP+>~IQ{b_v3Tq9Yz5&UJQh1TPsTk!#~E_H)Sxm zGx|lTBygK-y%UZ?^qq_&3Dz-I&QwiDy0%<0J`IL{BJhMk>YE2V(Mq@WAMfHWdgXH~ za64}NFE#LZyWm3LQl9;$UQ%wkWbHa(5dL=p7y6E!A-Hng%722q6cpDk_W>WQUfY2S z9jnL1on@&tXTq2Gm-0-Qs`=xL!=D;>ZX7(D0ta@c6_o&w4`R->=DApBi|9>F*|6OZEiP>W5r;3Qqfe z!=>V!cXM85V3TrsOukP0PB-|jlQq727lo8kKdJA&?`Sx~k-F+kzANMO*tZRS)>RsR zp21%ST=K1otM6?F-}QjTzsTUF>nXt^RYCv)N6yGV;hDZ4Zgs;&-E$_jy#-X;Eo=1_oYI)=XJBe^ay_}23-^{mzoxocB!1sd>kt-;@XrAxup zUTN-bJ`DcvflIxzDs{b_^8d-uvCq?V)SejVJZkW>Gyvg9# z#g%8V!EZUyr7+zFzr(<7Lnqt7e`@Ho#L?LV9Ia>d#*L?U8T=)2_{SN&vC_aDdE4gX z+oa2bsjIHt3T_@^f|m`xW%&O+6ZiIE@Ds45g`fLPz1j?ZmVsAY?NX4t41B2SzlYjO z$0>Y+6cdQ8F7)?3?NZQLMjoaCKN9}DW$X}&r>?mMzk82MJv;a{2EN&h`!dgvYpH_k z``F)3MIZfO__>K^xo$S_x;X>o`8Gudc{Xx&wnn!k-C%l2CNR>!RvJ2v9^Pu`?~S9s z-J${97xt6$4<8F;}B8m{&-6F1YqtF~+SR)fFLq5p`6KVabP3T_@^g0;YfZ~F{? z9DVqG0}ss|*e*94`n^6)pQ)U>etH=C_Zs{Svo*eYCyf+)41Mc-P2cHXFB*7(fjj#4 zZ3CYW2Y=VVU2$-6yhyz~2JXo7P~d~LOSZwUjKjALe1U;G<;*kib#eKgspzL$1zozn zYA-GIooVpb$Kju6;2YxLa}9ixfji~DNYRI2+|{7q>DD@*OT|n!@yiZ_|9#-XpWnpM z{~rV299JHh`itHucvjONW9UC&?22xKZyWfQ!{qz?Vc>5Y`V0P|>8m|!&iMMlp1YF7)iYw>u419ka{EvsB-=pB^R(H29Pq)eYIRkf@ zaZ0^QMci8kzTfCo)R(&6bLiME_57R$TS-hH)p+!%hA%VlVCLZc=<5c*<kPhYrlzCb*(J>i;I>H{8(eSj z?Mqz>ywk|T&A_Ey78v?YfB%hvuQPDirLJy8$2`OYPYZrxrsXm77boh~!{EQK;OW-p z+9Q`mx!LiRxIbG=(l3X0yqWeX4je2&E|kH?1OaRX1?I$`vXHi%g85>>e^`F z_P1Q>dAkN%PZ)T&;X}KDzv946KP@uw_YAz($hl)5e&oQXxfG@|{tg?a`CK(c!_^r8 zV4MV8@fK24g>$Wq0{?km!j}vL+2p_Z+TS1oqoN?z&D(v;f`JO`eEpQZ1B5{eO_ni z4;!xe)?oPK$fvx6Cw$m^uBP8>@aH36S@3ypy_O#Xwh9fMtT_28GVqqTcBwG*TVBxg zR~kaq27gH${tswSDbM!fHNHW&t}t{~-lXA6Ohl`pv(nI+XW&Z>ewG2E82qfKG``wfP26mQ?=tuf-@FRn zB#sHL0xt3pKVSQbt?@n{4QOV(_!(YJ7Ee0&!Uxnx6&JG<<}?mvfec&c--8rx|#+f!hYZ z#K89%xWnfQ4E>g7P2bUDmm9c!g@#{m%73MyleI{6~6J|By{D(%}6N2lm8 zbUcR6=2lHdy}M0WFEa22r)s!53kUeOoqQK*_zsir_YOmUgG1l&S)G{yonHf&a;`Mx zbmo13RPb!hF!O>XzI1*4$>49Otx*66f4ib@kYa*o6&(IN=u*)4Ox#O`&Zas|$C1Og z4gP+Q##d*@kR}y_P|DeD@E!S=w+2LSZ!qKIXhT2G!1r&{bV?0;qJuwQ!*>|?G(|s~ z0b19$*T63__*K(1z9Z+g3T}{Mf?>(JocrVI^&LeYJ!!@@r~KC%c!7aCeE8mB=>Oy} z@ZTFc6XNK|yQG83?JmJjJk$^Nw4vjQqw}1igMZlQe}`{>H}HZznm?-yFTR02AnoOe zqjSK}uQG7hrLM#ybvyPhb*X3d&Odj@0v9@|as7Rifgd#O^S@OABN863pAY@RB6~-Vc@orpUVu~k`KO*&> z(fC5Ey4A0~Q~`AU{OMC?&#$bQGk5-ci|-Hf-3j>|%Y1+1{A#|T;%ft=rg?sC1K)>g zm|x>>Zf%?IZC_%^w+Z-E4@V21Id1&;!I9?6SLWxdGj8W|+-&PoYms~ma(+#FW8+ei z#5~Oh(?0+;=G4&0G2Z93*H>T3G5mgyuYog&At6zfoGm&hdML9w?pBU-9vorHQod<3 zcvR{CQ`*)0$Z{0%;w+>`f{Z}oNf0~krOHj`Tcj{FcN+{LpBqCHRAO!vkLZL9=vKVR8_2qCWOo zkB&vuo79zSfy#3EI_~vO&d&xzchKq5`2F!{jF^k%NO^y@?JC*; z6``a!%Hv%wcp+{*cQ}n4cxALraZYxTVrbK}F6*H48WoFd&QQDzGP43>k_@LM({Z*f zN=3*d9MygSftY|#e$-ms@aNgmMf$Ad3LJxnx>Jbg8qyRUx`f_w0QPdlZdnk+HVd*S z8pUU`U!9)KMq?jow(T6CL%tD3nE(i;`M8p}b^`zr!Bq=lCWQl|43`6t35Q+Bo7-lI zV?cw9gUts$05)g-P!+ zQR=3<732K^aAaB09Y9Jx+NCIFC_2m71yVc?Jy&cXVY(h@jyc0cxmu+0jfxf5{sahCI*j||QpOac zx(C|rE&+9#&lRRQ>`~DYv1**Wycgx>4(CDd+mU1NVB>HCxZpo?qXBhO^In9uHYDMY z0*U&)X@7D)x;UAg3crFQ<1v~`L4a{k*s^dN99Cb76fCyQ(iuT#2`nD)xO`U9gfk9+ zv(2{b?9u_3B+y50PSk~ni%n63D}y%_-Zq6p>Z~EJrHOW$KjD1oK*YFQvzw!Q{w_ur z!uAdePl1EHi~cH^0)l(Jd@ssxR1mkYAvmMDsEQ512&%Li5P;_BNzv^3!Vqhqw)=Ee zn4v2PyaP*`xp5@96Fed(LI#87hN1=sG!yt*;A~W~Vxr!q=ryqGWOjs!DK2p&cDx|4 z39#C2x;&7zx=0MHC*4kVh^ zNFYT76&=swf}%IfkckY3N4Zl=svIN2!M+qK72YtV=}I(9WvVFqj5?n1bkUezSnS|S zK*_mw%#4?L0@Lm7m6k=(0S1um=Y2gB9;xJM%ld`_r>CFOwU`KLoJtF~ONG{(H$iiU zNCX26qX>j|m^3kx@KJ#9m)~Wp6i_)JUg+m3rZ8ynGNl=Y)W&}ZyN1~@>K>xUPnK)U zPuyd;5y;M3NpPJL+|ZXJeQI!YDh;$*QbsBv|D{jk+WwG>Nh;meH$3 zQ`w?d=_O`%Dg+P20UpAzvsLTSEF~#S#$Yc+!{m8dbj9sxXS!y<>BbDC47hKI4k>ya zA}2(ipnL=bR7*ccgH2_-j=AW8>0MH^HtidfHbl}YL6Io{iAxP%CqG4a5#F(?AmJ=uCZ(}j5qahXd zbw7QUwWy*4nm^?Uw8?+xX5*n>j7P8g#tDRaOAydJGkX|gL4ldp@(ngFgIBqad5p1UuneRDEYwXP zhq8PK`dL+Pi(Mc*F|N@aPO(kFIODH5G%y88GbNniwW0Rr;ky0KL4<_if`~P&UUuTe zLADJRH1JZeYHL>GEwNZ3wm|$cq3TWTt5K)8U6#=`LO4?rF6>TB>4rLhMM^vU!P(;y zsFfOfvf6d7cUhi1#;d#z$E2B^WCB35yz*) zZ^-a$EoejD*7OKa7zLrANywM%8#n!yg@~MN7miL>(+0W!-3R z1plw_TcG|D{_%4gx!&CC`A`Bme@gH4lkVHN#suZI}U5cCB;l34v=-v?OA zK5jz#a<3Bfa#OjYW{ShAh6MG`9{h#jjB=e^};Z=#%`$Fw|1fNV7xWOyxs{Oy& z>mkPbMJ1r;zC!%`Q21o@5A`v9AK^>*xB6{A7UK=^W&2Dure)YYhd;nS@9{Kx+qd;M zU(pAg3D=i3sOqu&H%R9_qZYUQU6ZdU!N${Z`#F!b-zU=#al>6SdfNxazjxy6RD-I1 zqqqM52+x^*;Pcsju?POey6uzY_J0%qZTg=gjb)14$CvFZ>wQ(@_Wg^->hCEn{$t#P z^tS)(15f{%b<7js>nXV`{25{)eeY{MdEUeKAAJ4&w;I}~(OLZGxC!ZPKilD-G>2!S zWv{+xwS-^bhUu-Dw(qUy>HpBRoEjScEcKVT3F&Qr+{2eS6=zy{`IQJhT2cBH(n5OM zN4NNf(!at6BDmW1{~FJj)vN<@y&d^b>D&1&ZsG5c8(#@m^tSKm!P_rJ|3}brPOIPc z@4fs#=s_aapYh4EXY{tu@1dvvOq)^3r?uPU;4h#J>$m-YpMUSg+W*%UeYJ0}r}WQ# z{m3Sl)o$lk-)~{v@HUB5E(!ca|joYWyYjIZd Q`{;l2vF3cxB4|bFKa5k+Hvj+t From ae39292afdd190eea16fd8cc203b767dd6607ddb Mon Sep 17 00:00:00 2001 From: harryswift01 Date: Fri, 14 Aug 2026 15:44:01 +0100 Subject: [PATCH 2/7] feat: add Conda packaging and external IRF support --- IR_finder.pl | 7 ++--- conda-recipe/build.sh | 41 ++++++++++++++++++++++++++ conda-recipe/meta.yaml | 42 +++++++++++++++++++++++++++ conda-recipe/run_test.sh | 62 ++++++++++++++++++++++++++++++++++++++++ master.pl | 7 +++-- trans_compete/Makefile | 6 ++-- trans_three/Makefile | 7 +++-- 7 files changed, 159 insertions(+), 13 deletions(-) create mode 100755 conda-recipe/build.sh create mode 100644 conda-recipe/meta.yaml create mode 100755 conda-recipe/run_test.sh diff --git a/IR_finder.pl b/IR_finder.pl index 91bf088..adbf282 100755 --- a/IR_finder.pl +++ b/IR_finder.pl @@ -21,8 +21,7 @@ die $usage; } -#my $code = "irf305.macos.exe"; -my $code = "irf308.linux.exe"; +my $code = "irf"; my $temp = $ARGV[0]; #temperature my $shape = $ARGV[1]; #linear or circular @@ -110,7 +109,7 @@ sub get_result { my ($in_file,$shape) = @_; my $skip = 0; #PARAMETERS: Match: $match Mismatch: $mismatch Delta: $delta pm: $pm pi: $pi minscore:$minscore maxlength: $maxlength maxloop: $maxloop \n"; - system("./$code $in_file $match $mismatch $delta $pm $pi $minscore $maxlength $maxloop > /dev/null"); + system("$code $in_file $match $mismatch $delta $pm $pi $minscore $maxlength $maxloop > /dev/null"); my $num_files = `ls -l $in_file.$match.$mismatch.$delta.$pm.$pi.$minscore.$maxlength.$maxloop.*.txt.html | wc -l`; for(my $i = 1; $i <= $num_files; $i++) { my $out_file = "$in_file.$match.$mismatch.$delta.$pm.$pi.$minscore.$maxlength.$maxloop.$i.txt.html"; @@ -275,7 +274,7 @@ sub get_IR { my @la = split("--",$rrl[0]); my @ra = split("--",$rrl[1]); my $start = $la[0]; #start position of IR - my $end = $ra[1]; #end position of IR + my $end = $ra[1]; #end of IR my $s_loop = $la[1]+1; #start position of loop my $l_IR = $end-$start+1; #length of IR return ($l_loop,$s_loop,$start,$end,$l_IR); diff --git a/conda-recipe/build.sh b/conda-recipe/build.sh new file mode 100755 index 0000000..01991f8 --- /dev/null +++ b/conda-recipe/build.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +set -euxo pipefail + +make -C trans_three clean +make -C trans_three + +make -C trans_compete clean +make -C trans_compete + +install -d "${PREFIX}/bin" +install -d "${PREFIX}/libexec/sist" +install -d "${PREFIX}/libexec/sist/trans_three" +install -d "${PREFIX}/libexec/sist/trans_compete" + +install -m 755 \ + master.pl \ + "${PREFIX}/libexec/sist/master.pl" + +install -m 755 \ + IR_finder.pl \ + "${PREFIX}/libexec/sist/IR_finder.pl" + +install -m 755 \ + trans_three/qsidd \ + "${PREFIX}/libexec/sist/trans_three/qsidd" + +install -m 755 \ + trans_compete/qsidd \ + "${PREFIX}/libexec/sist/trans_compete/qsidd" + +cat > "${PREFIX}/bin/sist" << 'EOF' +#!/usr/bin/env bash + +PREFIX="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +exec "${PREFIX}/bin/perl" \ + "${PREFIX}/libexec/sist/master.pl" \ + "$@" +EOF + +chmod 755 "${PREFIX}/bin/sist" \ No newline at end of file diff --git a/conda-recipe/meta.yaml b/conda-recipe/meta.yaml new file mode 100644 index 0000000..ffad036 --- /dev/null +++ b/conda-recipe/meta.yaml @@ -0,0 +1,42 @@ +{% set name = "sist" %} +{% set version = "0.0.0" %} + +package: + name: {{ name }} + version: {{ version }} + +source: + path: .. + +build: + number: 0 + +requirements: + build: + - {{ compiler('cxx') }} + - make + + run: + - perl + - irf >=3.08,<3.09 + +test: + source_files: + - tests/data/pbr322.toy.fa + - tests/reference/v1.0.0/melting.txt + - tests/reference/v1.0.0/z-dna.txt + - tests/reference/v1.0.0/cruciform.txt + - tests/reference/v1.0.0/competition.rebuilt.txt + +about: + home: https://github.com/CCPBioSim/SIST + license: MIT + license_family: MIT + license_file: LICENSE + license_url: https://github.com/CCPBioSim/SIST/blob/main/LICENSE + summary: Stress-Induced Structural Transitions in superhelical DNA. + description: > + SIST calculates stress-induced structural transition probabilities in + superhelical DNA, including strand separation, Z-DNA formation, + cruciform extrusion, and competition between these transitions. + dev_url: https://github.com/CCPBioSim/SIST \ No newline at end of file diff --git a/conda-recipe/run_test.sh b/conda-recipe/run_test.sh new file mode 100755 index 0000000..032c1d8 --- /dev/null +++ b/conda-recipe/run_test.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +set -euo pipefail + +command -v sist +command -v irf +command -v perl + +TEST_ROOT="$(pwd)" + +run_reference_test() { + mode="$1" + name="$2" + reference="$3" + + mkdir -p "${name}-test" + + cp \ + "${TEST_ROOT}/tests/data/pbr322.toy.fa" \ + "${name}-test/pbr322.toy.fa" + + ( + cd "${name}-test" + + sist \ + -f pbr322.toy.fa \ + -a "${mode}" \ + -o "${name}.txt" \ + -b \ + -p \ + -r + + grep -v '^Run time =' \ + "${name}.txt" \ + > actual.txt + + grep -v '^Run time =' \ + "${TEST_ROOT}/${reference}" \ + > expected.txt + + diff -u expected.txt actual.txt + ) +} + +run_reference_test \ + M \ + melting \ + tests/reference/v1.0.0/melting.txt + +run_reference_test \ + Z \ + z-dna \ + tests/reference/v1.0.0/z-dna.txt + +run_reference_test \ + C \ + cruciform \ + tests/reference/v1.0.0/cruciform.txt + +run_reference_test \ + A \ + competition \ + tests/reference/v1.0.0/competition.rebuilt.txt \ No newline at end of file diff --git a/master.pl b/master.pl index dda6dfa..61d852b 100755 --- a/master.pl +++ b/master.pl @@ -5,6 +5,7 @@ use strict; use warnings; use Getopt::Long; +use FindBin qw($RealBin); my ($file,$trans, $out_file); my $temp = 310; my $sig = 0.06; @@ -72,8 +73,8 @@ my @name = split("/",$file); -my $single_exe = "trans_three/qsidd"; -my $compete_exe = "trans_compete/qsidd"; +my $single_exe = "$RealBin/trans_three/qsidd"; +my $compete_exe = "$RealBin/trans_compete/qsidd"; my $output_IR; if ($trans eq "M") { @@ -85,7 +86,7 @@ $output_IR = `$single_exe $b $p $r $c -T $temp -s $sig -i $salt -t $theta -Z -f $file`; } if ($trans eq "C" or $trans eq "A") { - my $code_IR = "IR_finder.pl"; + my $code_IR = "$RealBin/IR_finder.pl"; my $IR_results = `perl $code_IR $temp $shape $file`; #run IR_finder if($trans eq "C") { #run single transition algorithm for cruciforms diff --git a/trans_compete/Makefile b/trans_compete/Makefile index c991d8b..703b15c 100644 --- a/trans_compete/Makefile +++ b/trans_compete/Makefile @@ -27,13 +27,13 @@ default: make gcc $(APP): $(OBJ) $(OBJECTS) - $(CC) -o $(APP) $(CFLAGS) $(OBJ) $(OBJECTS) -lm + $(CC) $(LDFLAGS) -o $(APP) $(CFLAGS) $(OBJ) $(OBJECTS) -lm clean: rm -f *.o $(APP) depend: $(OBJECTS:.o=.cpp) - gcc -MM $^ > $@ + $(CXX) -MM $^ > $@ test: ./qsidd example.fasta @@ -52,7 +52,7 @@ tar: ################# gcc: - make $(APP) CC="g++" CPPFLAGS="-O2 -Wall" + make $(APP) CC="$(CXX)" CPPFLAGS="-O2 -Wall" ################### diff --git a/trans_three/Makefile b/trans_three/Makefile index dc336c5..06b0afe 100644 --- a/trans_three/Makefile +++ b/trans_three/Makefile @@ -16,6 +16,7 @@ APP = qsidd SRC = qsidd.c OBJ = qsidd.o + DATE = $(shell date +\%Y-\%m-\%d) ########### @@ -26,13 +27,13 @@ default: make gcc $(APP): $(OBJ) $(OBJECTS) - $(CC) -o $(APP) $(CFLAGS) $(OBJ) $(OBJECTS) -lm + $(CC) $(LDFLAGS) -o $(APP) $(CFLAGS) $(OBJ) $(OBJECTS) -lm clean: rm -f *.o $(APP) depend: $(OBJECTS:.o=.cpp) - gcc -MM $^ > $@ + $(CXX) -MM $^ > $@ test: ./qsidd example.fasta @@ -51,7 +52,7 @@ tar: ################# gcc: - make $(APP) CC="g++" CPPFLAGS="-O2 -Wall " + make $(APP) CC="$(CXX)" CPPFLAGS="-O2 -Wall " ################### From 7120b2f62ffb7e81f814b96214425c1bd2ed4e74 Mon Sep 17 00:00:00 2001 From: harryswift01 Date: Fri, 14 Aug 2026 15:55:04 +0100 Subject: [PATCH 3/7] test: add pytest dependencies to Conda recipe --- conda-recipe/meta.yaml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/conda-recipe/meta.yaml b/conda-recipe/meta.yaml index ffad036..8b610ca 100644 --- a/conda-recipe/meta.yaml +++ b/conda-recipe/meta.yaml @@ -21,12 +21,13 @@ requirements: - irf >=3.08,<3.09 test: + requires: + - python >=3.14 + - pytest >=9,<10 + source_files: - - tests/data/pbr322.toy.fa - - tests/reference/v1.0.0/melting.txt - - tests/reference/v1.0.0/z-dna.txt - - tests/reference/v1.0.0/cruciform.txt - - tests/reference/v1.0.0/competition.rebuilt.txt + - tests + - pyproject.toml about: home: https://github.com/CCPBioSim/SIST From c6330a3cc654eb3243283d5efdde8d02bfae6d19 Mon Sep 17 00:00:00 2001 From: harryswift01 Date: Fri, 14 Aug 2026 16:30:10 +0100 Subject: [PATCH 4/7] test: run regression suite against installed Conda package --- conda-recipe/run_test.sh | 58 ++-------------------------- tests/conftest.py | 82 +++++++++++++++++++++++++++++----------- 2 files changed, 62 insertions(+), 78 deletions(-) diff --git a/conda-recipe/run_test.sh b/conda-recipe/run_test.sh index 032c1d8..f6bace2 100755 --- a/conda-recipe/run_test.sh +++ b/conda-recipe/run_test.sh @@ -4,59 +4,7 @@ set -euo pipefail command -v sist command -v irf command -v perl +command -v python +command -v pytest -TEST_ROOT="$(pwd)" - -run_reference_test() { - mode="$1" - name="$2" - reference="$3" - - mkdir -p "${name}-test" - - cp \ - "${TEST_ROOT}/tests/data/pbr322.toy.fa" \ - "${name}-test/pbr322.toy.fa" - - ( - cd "${name}-test" - - sist \ - -f pbr322.toy.fa \ - -a "${mode}" \ - -o "${name}.txt" \ - -b \ - -p \ - -r - - grep -v '^Run time =' \ - "${name}.txt" \ - > actual.txt - - grep -v '^Run time =' \ - "${TEST_ROOT}/${reference}" \ - > expected.txt - - diff -u expected.txt actual.txt - ) -} - -run_reference_test \ - M \ - melting \ - tests/reference/v1.0.0/melting.txt - -run_reference_test \ - Z \ - z-dna \ - tests/reference/v1.0.0/z-dna.txt - -run_reference_test \ - C \ - cruciform \ - tests/reference/v1.0.0/cruciform.txt - -run_reference_test \ - A \ - competition \ - tests/reference/v1.0.0/competition.rebuilt.txt \ No newline at end of file +python -m pytest tests -vv \ No newline at end of file diff --git a/tests/conftest.py b/tests/conftest.py index ad3c98f..f5eea65 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -66,14 +66,10 @@ def require_success( ) -@pytest.fixture(scope="session") -def built_sist(tmp_path_factory: pytest.TempPathFactory) -> Path: - """ - Copy and build SIST in a temporary directory. - - This ensures the tests use executables built from the maintained source - without modifying the repository working tree. - """ +def build_sist( + tmp_path_factory: pytest.TempPathFactory, +) -> Path: + """Copy and build SIST in a temporary directory.""" build_root = tmp_path_factory.mktemp("sist-build") working_copy = build_root / "SIST" @@ -121,39 +117,73 @@ def built_sist(tmp_path_factory: pytest.TempPathFactory) -> Path: return working_copy +@pytest.fixture(scope="session") +def sist_command( + tmp_path_factory: pytest.TempPathFactory, +) -> list[str]: + """ + Return the SIST command under test. + + During conda-build testing, use the installed package. Otherwise build + and test the maintained source tree. + """ + + if os.environ.get("CONDA_BUILD_STATE") == "TEST": + executable = shutil.which("sist") + + if executable is None: + pytest.fail( + "The installed sist command was not found in PATH", + pytrace=False, + ) + + return [executable] + + built_sist = build_sist(tmp_path_factory) + + return [ + "perl", + str(built_sist / "master.pl"), + ] + + def run_sist_calculation( - built_sist: Path, + sist_command: list[str], + tmp_path_factory: pytest.TempPathFactory, *, name: str, algorithm: str, ) -> SistRun: """Run one SIST calculation using the regression test sequence.""" - source_input = built_sist / "tests" / "data" / "pbr322.toy.fa" - runtime_input = built_sist / "pbr322.toy.fa" + runtime_directory = tmp_path_factory.mktemp(f"sist-{name}") - shutil.copy2(source_input, runtime_input) + source_input = ( + REPOSITORY_ROOT + / "tests" + / "data" + / "pbr322.toy.fa" + ) + runtime_input = runtime_directory / "pbr322.toy.fa" - output_directory = built_sist / "test-results" - output_directory.mkdir(exist_ok=True) + shutil.copy2(source_input, runtime_input) - output_path = output_directory / f"{name}.txt" + output_path = runtime_directory / f"{name}.txt" result = run_command( [ - "perl", - "master.pl", + *sist_command, "-f", runtime_input.name, "-a", algorithm, "-o", - str(output_path.relative_to(built_sist)), + output_path.name, "-b", "-p", "-r", ], - cwd=built_sist, + cwd=runtime_directory, ) return SistRun( @@ -165,11 +195,15 @@ def run_sist_calculation( @pytest.fixture(scope="session") -def competition_run(built_sist: Path) -> SistRun: +def competition_run( + sist_command: list[str], + tmp_path_factory: pytest.TempPathFactory, +) -> SistRun: """Run the SIST competition calculation once.""" return run_sist_calculation( - built_sist, + sist_command, + tmp_path_factory, name="competition", algorithm="A", ) @@ -181,14 +215,16 @@ def competition_run(built_sist: Path) -> SistRun: ) def transition_run( request: pytest.FixtureRequest, - built_sist: Path, + sist_command: list[str], + tmp_path_factory: pytest.TempPathFactory, ) -> SistRun: """Run each supported individual SIST transition calculation once.""" name, algorithm = request.param return run_sist_calculation( - built_sist, + sist_command, + tmp_path_factory, name=name, algorithm=algorithm, ) From e5eed0755c992c271cea79619284553056908ee6 Mon Sep 17 00:00:00 2001 From: harryswift01 Date: Fri, 14 Aug 2026 16:51:11 +0100 Subject: [PATCH 5/7] ci: add release workflow and citation metadata --- .github/workflows/release.yaml | 229 +++++++++++++++++++++++++++++++++ CITATION.cff | 36 ++++++ 2 files changed, 265 insertions(+) create mode 100644 .github/workflows/release.yaml create mode 100644 CITATION.cff diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..6aec1ba --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,229 @@ +name: Release SIST + +on: + workflow_dispatch: + inputs: + version: + required: true + default: 'x.y.z' + +permissions: + contents: write + pull-requests: write + +jobs: + checks: + name: Version check + runs-on: ubuntu-24.04 + steps: + - name: Checkout repository + id: repo + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: 3.14.6 + + - name: Get latest release + id: latestreleased + run: | + PREVIOUS_VERSION=$(git tag --sort=-v:refname | head -n 1) + echo "release_tag=$PREVIOUS_VERSION" >> "$GITHUB_OUTPUT" + echo "$PREVIOUS_VERSION" + + - name: version comparison + id: compare + run: | + python -m pip install semver + + PREVIOUS_VERSION="${{ steps.latestreleased.outputs.release_tag }}" + NEW_VERSION="${{ github.event.inputs.version }}" + + if [ -z "$PREVIOUS_VERSION" ]; then + echo "No previous release found" + exit 0 + fi + + output=$(pysemver compare "$PREVIOUS_VERSION" "$NEW_VERSION") + + if [ "$output" -ge 0 ]; then + exit 1 + fi + + version: + name: prepare ${{ github.event.inputs.version }} + needs: checks + runs-on: ubuntu-24.04 + steps: + + - name: checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + + - name: Change version in repo and CITATION.cff + run: | + # Update Conda package version + sed -i \ + "s/{% set version = \".*\" %}/{% set version = \"${{ github.event.inputs.version }}\" %}/" \ + conda-recipe/meta.yaml + + # Update CITATION.cff version and date-released + if [ -f CITATION.cff ]; then + sed -i -E \ + "s/^(version:\s*).*/\1${{ github.event.inputs.version }}/" \ + CITATION.cff + + sed -i -E \ + "s/^(date-released:\s*).*/\1'$(date -u +%F)'/" \ + CITATION.cff + fi + + - name: send PR + id: pr_id + uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 + with: + commit-message: Update version to ${{ github.event.inputs.version }} + branch: version-update + title: "Update to version ${{ github.event.inputs.version }}" + body: | + Update version + - Update the Conda recipe with new release + - Update CITATION.cff version & date-released + - Auto-generated by [CI] + committer: version-updater + author: version-updater + base: main + signoff: false + draft: false + + - name: auto approve review + uses: hmarr/auto-approve-action@f0939ea97e9205ef24d872e76833fa908a770363 # v4.0.0 + with: + pull-request-number: ${{ steps.pr_id.outputs.pull-request-number }} + review-message: "Auto approved version bump PR" + github-token: ${{ secrets.AUTO_PR_MERGE }} + + - name: merge PR + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ steps.pr_id.outputs.pull-request-number }} + run: | + gh pr merge \ + --merge \ + --delete-branch \ + --auto \ + "$PR_NUMBER" + + while true; do + STATE=$(gh pr view "$PR_NUMBER" --json state --jq '.state') + + if [ "$STATE" = "MERGED" ]; then + echo "Version bump PR merged" + break + fi + + if [ "$STATE" = "CLOSED" ]; then + echo "Version bump PR was closed without merging" + exit 1 + fi + + sleep 10 + done + + tag: + name: tag release + needs: version + runs-on: ubuntu-24.04 + steps: + - name: Checkout repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + ref: main + + - name: tag v${{ github.event.inputs.version }} + run: | + git config user.name github-actions + git config user.email github-actions@github.com + git tag ${{ github.event.inputs.version }} + git push origin tag ${{ github.event.inputs.version }} + + release: + name: make github release + needs: tag + runs-on: ubuntu-24.04 + steps: + + - name: create release + uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2 + with: + name: v${{ github.event.inputs.version }} + generate_release_notes: true + tag_name: ${{ github.event.inputs.version }} + + conda: + name: publish conda to anaconda.org + needs: [tag, release] + runs-on: ubuntu-24.04 + + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + ref: ${{ github.event.inputs.version }} + + - name: Setup Conda + uses: conda-incubator/setup-miniconda@8ee1f361103df19b6f8c8655fd3967a8ecb162d5 # v4 + with: + miniforge-variant: Miniforge3 + python-version: "3.14" + auto-activate: true + activate-environment: base + conda-remove-defaults: true + channels: conda-forge,bioconda + channel-priority: strict + + - name: Install build tools + shell: bash -l {0} + run: | + conda install -y \ + --override-channels \ + -c conda-forge \ + conda-build \ + anaconda-client + + conda config --set anaconda_upload no + conda list conda-build + conda build --help + + - name: Build package + shell: bash -l {0} + run: | + PKG_PATH=$(conda build conda-recipe \ + --output \ + --override-channels \ + -c conda-forge \ + -c bioconda) + + echo "PKG_PATH=$PKG_PATH" >> "$GITHUB_ENV" + + conda build conda-recipe \ + --override-channels \ + -c conda-forge \ + -c bioconda \ + --no-anaconda-upload + + test -f "$PKG_PATH" + echo "Built: $PKG_PATH" + + - name: Upload to Anaconda + shell: bash -l {0} + env: + ANACONDA_API_TOKEN: ${{ secrets.ANACONDA_TOKEN }} + run: | + anaconda \ + -t "$ANACONDA_API_TOKEN" \ + upload "$PKG_PATH" \ + --user CCPBioSim \ + --force \ No newline at end of file diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 0000000..5ed8a20 --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,36 @@ +# This CITATION.cff file was generated with cffinit. +# Visit https://bit.ly/cffinit to generate yours today! + +cff-version: 1.2.0 +title: SIST +message: >- + If you use this software, please cite it using the + metadata from this file. +type: software + +authors: + +repository-code: 'https://github.com/CCPBioSim/SIST' +url: 'https://sist.readthedocs.io/en/latest/' + +abstract: >- + SIST (Stress-Induced Structural Transitions) calculates + stress-induced structural transition probabilities in + superhelical DNA, including strand separation, Z-DNA + formation, cruciform extrusion, and competition between + these transitions. + +keywords: + - DNA + - superhelical DNA + - DNA supercoiling + - strand separation + - Z-DNA + - cruciform extrusion + - structural transitions + - statistical mechanics + - biomolecular simulations + +license: MIT +version: 0.0.0 +date-released: '2026-08-14' \ No newline at end of file From e0ed3880da3c2c60f723f957b67acbfcef5c944e Mon Sep 17 00:00:00 2001 From: harryswift01 Date: Fri, 14 Aug 2026 17:04:42 +0100 Subject: [PATCH 6/7] ci: run regression tests through Conda build --- .github/workflows/pr.yaml | 47 ++++++++++++------------- .github/workflows/regression-tests.yaml | 47 ++++++++++++------------- 2 files changed, 46 insertions(+), 48 deletions(-) diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 9c17fd6..9a4fdb6 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -18,35 +18,34 @@ jobs: steps: - name: Checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 + uses: actions/checkout@8e8c483db84bbee98b60c0593521ed34d9990e8 - - name: Set up Python 3.14 - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 + - name: Setup Conda + uses: conda-incubator/setup-miniconda@8ee1f361103df19b6f8c8655fd3967a8ecb162d5 # v4 with: - python-version: "3.14" + miniforge-variant: Miniforge3 + auto-activate: true + activate-environment: base + conda-remove-defaults: true + channels: conda-forge,bioconda + channel-priority: strict - - name: Install testing dependencies + - name: Install conda-build + shell: bash -l {0} run: | - python -m pip install --upgrade pip - python -m pip install -e '.[testing]' + conda install -y \ + --override-channels \ + -c conda-forge \ + conda-build - - name: Run regression tests + - name: Build and run regression tests + shell: bash -l {0} run: | - python -m pytest tests \ - -m regression \ - -vv \ - --durations=20 \ - --basetemp=.testdata - - - name: Upload regression artefacts on failure - if: failure() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - name: pr-regression-failure - path: .testdata/ - if-no-files-found: ignore - include-hidden-files: true - retention-days: 7 + conda build conda-recipe \ + --override-channels \ + -c conda-forge \ + -c bioconda \ + --no-anaconda-upload docs: name: Docs @@ -55,7 +54,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 + uses: actions/checkout@8e8c483db84bbee98b60c0593521ed34d9990e8 - name: Set up Python 3.14 uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 diff --git a/.github/workflows/regression-tests.yaml b/.github/workflows/regression-tests.yaml index 394132e..d2c31ab 100644 --- a/.github/workflows/regression-tests.yaml +++ b/.github/workflows/regression-tests.yaml @@ -23,32 +23,31 @@ jobs: steps: - name: Checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 + uses: actions/checkout@8e8c483db84bbee98b60c0593521ed34d9990e8 - - name: Set up Python 3.14 - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 + - name: Setup Conda + uses: conda-incubator/setup-miniconda@8ee1f361103df19b6f8c8655fd3967a8ecb162d5 # v4 with: - python-version: "3.14" - - - name: Install testing dependencies + miniforge-variant: Miniforge3 + auto-activate: true + activate-environment: base + conda-remove-defaults: true + channels: conda-forge,bioconda + channel-priority: strict + + - name: Install conda-build + shell: bash -l {0} run: | - python -m pip install --upgrade pip - python -m pip install -e '.[testing]' + conda install -y \ + --override-channels \ + -c conda-forge \ + conda-build - - name: Run regression tests + - name: Build and run regression tests + shell: bash -l {0} run: | - python -m pytest tests \ - -m regression \ - -vv \ - --durations=20 \ - --basetemp=.testdata - - - name: Upload regression artefacts on failure - if: failure() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - name: sist-regression-failure - path: .testdata/ - if-no-files-found: ignore - include-hidden-files: true - retention-days: 7 \ No newline at end of file + conda build conda-recipe \ + --override-channels \ + -c conda-forge \ + -c bioconda \ + --no-anaconda-upload \ No newline at end of file From 844e88baff80c4a117daa93379d7aa1cbfe0290d Mon Sep 17 00:00:00 2001 From: harryswift01 Date: Fri, 14 Aug 2026 17:08:15 +0100 Subject: [PATCH 7/7] ci: update checkout action to correct SHA --- .github/workflows/pr.yaml | 4 ++-- .github/workflows/regression-tests.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 9a4fdb6..106b2f6 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -18,7 +18,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@8e8c483db84bbee98b60c0593521ed34d9990e8 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 - name: Setup Conda uses: conda-incubator/setup-miniconda@8ee1f361103df19b6f8c8655fd3967a8ecb162d5 # v4 @@ -54,7 +54,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@8e8c483db84bbee98b60c0593521ed34d9990e8 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 - name: Set up Python 3.14 uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 diff --git a/.github/workflows/regression-tests.yaml b/.github/workflows/regression-tests.yaml index d2c31ab..d046ca2 100644 --- a/.github/workflows/regression-tests.yaml +++ b/.github/workflows/regression-tests.yaml @@ -23,7 +23,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@8e8c483db84bbee98b60c0593521ed34d9990e8 + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 - name: Setup Conda uses: conda-incubator/setup-miniconda@8ee1f361103df19b6f8c8655fd3967a8ecb162d5 # v4