From d348e525e8ef62c3a8a6a3ad214f423af2c7b8ca Mon Sep 17 00:00:00 2001 From: sjplimp Date: Thu, 25 Jul 2013 22:59:29 +0000 Subject: [PATCH] git-svn-id: svn://svn.icms.temple.edu/lammps-ro/trunk@10378 f3b2605a-c512-4ea7-a41b-209d697bcdaa --- tools/polybond/Manual.pdf | Bin 0 -> 92552 bytes tools/polybond/README | 12 + tools/polybond/lmpsdata.py | 1945 ++++++++++++++++++++++++++++++++++++ 3 files changed, 1957 insertions(+) create mode 100644 tools/polybond/Manual.pdf create mode 100644 tools/polybond/README create mode 100644 tools/polybond/lmpsdata.py diff --git a/tools/polybond/Manual.pdf b/tools/polybond/Manual.pdf new file mode 100644 index 0000000000000000000000000000000000000000..4c5db6025a2007536416d209830ecf4a3c240dd1 GIT binary patch literal 92552 zcmbrFW0Yjwwx-jpv~AnAZQHhO+s>@CDs9`&N@t~QTV3^?^PPKc-`k^akNy*T?}b<^ zR*ZPZe4jakL|#~whJls|ie&KM_Ta4a?)&WEFcdRBJ-(fxB@{O|KAp6Qt(mhqKFjYW zMSMC@3u|W+$KO|L17{Oq6C*og6MSA?C?{t}69XG4cfiFgow)U3l+ddeDpEJ??+Fie zniq%Fu9&?cFzmJQy38U8Wv(Jovte^}j&axi#_Cqnkx{cj z^;FMk5FNv6a3yWpv0Zv0M&;Jx2G?TiaZ|gA#fh(%^Ex zB2jVnc}57v%}*oTuP3X`tFMagEpD~+CkIa&L_K991lH@Jp_1(=F~KDvoyF%sIt~Po zM>R8BAKvxWOaf`k!O9M7aMeRru(<3U{WcN`BwCG{dn@8*(7&vvZ8??S$7G&3f#U1E^?Ww% zX&rQiaZIojusd6eJ^Ur*^j&GY(-*~GHBb#uDIQDbu&dxG1EJQEujx>>ZJ?d ztVW3v`s2Js7J!##iY~1<@lB*xRO3_T`ODqlqbHyO?!s>5Wnz$o%+WVRS(kmwEuyk! z@jRXn0; z6gpk;=_E)VmI5?lW`@+Lp2+rz<0G)46B#YFL8N8Ig6fM*6+QLt!L#}H)*7o(x>;c9 z*ezqqv9JcuMrc;YVt=aS0`Oc>rRQ5v&A`zNrW7W(@!~8(Q3+8uf3j-4qeWbtu zpy#$lS6!S}#DO)(Fk63kBKcF-I8E^DG?s5^-*vFRIeIa#uhN%!Xdb9fEhd-z8W^k< zS8*ayf?StB#(yX@On(!c6*4pk*$#^KA*^}7Ir(WeV-~RH>Q-m*gtyMG3It+J%zv{J zI~+!`Xx(fABo`T~MIKYNUBI$6&d>f3w2yB}=D%B1y8t-azYXFcBlvA08NIQe%)71? z9XI|QK=y77;+H`c0k;Z`%WWMgENZSQwHma`J_YpG*lEkWJ$OQG)7USip@xL~wQ66u z%18g7{=|x1E)hj=e&O$D^Lh%ixTuBXvA+b$c6ijx<`qB!^9vL2z3$Dfo%bWAj~(1G z1R($!dc4U8*T3WM;9liabVE0e0WJ~iQTo}h(3Mpdvm#UIMef5pMMDk%0L+;lYXK1q z7aqrQHAwV26xM}>SJIj^bSG$%hzq z@InQ&<))lMkB%f?z>cb?#BCU>CJSVd(BJbISsdqrWcglUEE_6Y{bjpxOp{F_{{=_8bh=d@v3%G|yh3-DVLu zp9{ojGF`J0oz9$j(9cp!xpxZ{qtvnOQm=Y|K~eJw5Cu%ihoM%PW4jSd$*olgCq-$c z+5%R{0Z{K6){$jQs?yRM?mMQyws4%hO(AVSIMnx*u6z{i6#YwpMSXFV!b|fs94Yvu z1gZ=3md0B;fS{(3YOZwV1={A$XU=AgwrzL!Ap8>+K-4$CrQVQm3k*0jIqr!z%8N*G zYQUIQCvZReU1&=PWepZ%#X&2^5b#E$_=ZH(S^&=cPD^HLeg%l#Fkz}NOflJfhU2|$ z=B}}XIuc`-_b^Nwp}2L)FuaX7#4e<>yG0LAL!8aPoq$vk^X1^1V1K%EbDIa-(WY$- zUZ!igvmXwyy~SIDNfuZA~hhYJP+GHJ&1oga3G-%@2?-yyhPc3xgyRNOVQ zObxgD(A<-#=<(R<>~k&G^5zA;#0uW3R z&1TZ+83(!;p^t~`5By4yf25A~B8zB7M80p&aCBSAi)r^tx>z_p2=2JM&^^0UhLDTG z1!o2FQ>--sAvTpibX`*|2(IHN^TU2Bb>Z61eq&M-i!_y*yR0SMouxwyb3wYukDi+s zg3XbKJ>SSwKRStx#D=2I+n=pVc1W=lmZWmoIe!A5mhd64(017rMR-*1sPcV{-tj71+n%#Qeg|zXSaqsezwX}+6dV!a&+ zK!8^RIJg~tx5!ej;t{_8Qqk4QmcvTqQMbN-IRA80;k-aIHn0FQ^8^7HwT;X2itYmG zTy~p6ES5-Y=+l^H>lZ&^Yf!VIUS$}(^f!MBl48wXI+R_YV zh>xFW0qU*6g0k59hk&5~o$pISA!g`LkE=I=-U1=U)8-P2GLo6j8MYZC2{$>Ve1$n%{uBU(gf$D z;q^QboBP}&1$q+Df$HV`htK#{rHHb!wxephg`DFfE8c&TjU5GH5`9g_`D{JyO20p zhmGySW&uxv3tCn`Mw57u@d6+oJkiiKI!FU47#?s<#TFWC48(g!`~lRc1S1-6nd9K5 ztKKzWqUp`OcFozVHXgPPJ7#1>E@t>6aHMTUIazi+v7pX3Vb-C1zCH^Nd{PMfV^?6#!J?`B7Q#Vv!TYFD&nj`u57DGrgGYFSrNk&zu+jz}N>#9fgoRM|6qRPk zUT=%fX63a2->;hYtjF1U}a#$ zXX9Yhf%23Yi{jHMo47mU>)_J~*;(5;D%l$tnc)9% zT0%|?_-uc@JumNXhxL2KznYbm8EEP8+5dDv(J3o4{(j*2Yt&y44EPLxcA)s%h5a#N z|6HAc;dk5L%m35C{odnWTbIRWz^4=VW8-XpdpLYLVG~yiBNH)41CRf^y$ey1vBpw? z$2D7qt>}*gZd9R4A^X%6v1o)uMx^{+ox^ECxI6_HuZUu^Jae;@wPE6R!KxtF7LTM1 zL{N<%> zcg@`)1wX}=ua`bK-OcSK?Y#A!cD^j^8r^i;M5-8aIQC5Z^TPYmM~Wi#*RP3g&KcH? z;t59jd>7CO8t_AI1xd2;`;1&XL>Ig;?_=j<24Ae_zO{Q}*k|rq)ODbFA%W}XqN`3$GQ_HyJZelUsr<%kfw5n_ea7*CKW$t#Al7|CmADSs8%@lDQLKQ zY)+l0MJuM&l1{7IUA)YQ12WXgpLBqH068X9*jZqg;J(Wo8r+CydR1+E=Vn~{Jy`;m z=+{QVQ!;|e$NW2LBq%s7<%eI4kLS8rP_SP*|mW-q25cc zWN&ri;LEj&bhbOT^)BxE5hud@rDApVJL03-se{NzczW}K7fc*)mA&_@?`h!*HH|~m&(NZ;nWr<^@i&&r|`-)D<`1GU&Lag zRj6lQ$PENZ-a1@w2am$RhALg|@1`?N4bH?GQDvM5F-_uIvRe{0yKkzVG$%wKLY^>r zTFD|^=i#d!#v|C{;b%bZ^zjb<;iqL(Vm&QHBPc5&vvnt8*=-3b{!s5Q6Pkm{^@kC; z$D?UZW_UOlYS~U)+*gBPla=0G?lt04KCdIMbg$sAZm%`36`#1XGdOdNuSJU_3QrVv z6nPYS6luvbDQCpVRO6A38f_KcMcx4$rSa9|Y1<(#T0G0=b*@+b4W?+B4t@-9zv=`A zv8|ZwsBJ{-q0G)Qr1qO{LZ^Lgw~BEVTrJ})JuVC`9Bc&779TCZ8*SF`m;6~PE{4aZ zUAI;oSUj2&d93EF2TdCiu~kssU&H5n&3I^Ym7+}HTSQ8Uzyix)VNJKcevUhZw25~G zZZ<9GoceR3&M2sEgh^mtrI!=rHcpC@=8Ywe-kXLp0cQ+EBltLt3!AdZhTZVYPlszT zByr++j0NL5tr>gy=sxR3ctvzE``erwlh17Dh$K68fo^41h8oH*=OJix5*DL7O@8tk ztTczm0_{xjzrtu-lKFns{J#WE5nt!;F%16=qGI?TGO8g>ExUD5RNteT^nSP! zG}?ik9MJdp3)cYJM3H)iW;h2_d1A^XS`vu=m z?IZcTA*~yY;Cnw(qJ*@B73E#FIz&2O+oE(yriZ+Cda|@5@CNV^KNHd5 z)C#=h&w5aL;9vVXPEtyzt)CYKdv+Vv4<)_fTfX3Xez}^r4n;M(2P+a6QRCcx+zwL9 zA}*XU$9q4Lp?;{*97%qWFr<*mBU0L=$mY&?@trV9Lj&Ce`7ttd?i8L~mL1(qv#l@~ zlf6?Y)m?AF6_?U%gfYg3FuEX%r@w*|Vjn0JeG~G9*YLeQO9_l7R`stf)2e#l>A_cD zhz)H4=pd^~J)#>!SfHIZ^uK;^ViydBe8b3vkN3Ih9l;+*6q+qh?YkqOh>GQr2b1e< z=qraws#dREmCwC#;AKL@(XXS2Nd{aAWr2{(r}%CHq^Cih4X$(!D#8oWFR$KpObG>*!u9p^AsHmJ8=97vNCB3@N$O6I=r0s*@$7qe~xo z!VRW?fxLoZ=PwJ~n?4x}IDqAzG@$-d>=8p8DvEKa&JRZAd0bc@n?7%MPMp?$5U8Jd z(rYh%Sh#Z>u@fyD00|hPl^;f~QA3(F7|nnVXo?&(LhQ%(13T<9RvBB)uJ+H#CcbT& z@MUa)DjB(l)#yk|HGPgcPi6glz0|q%;uv;()SX$h79pQO zw!ulIFTTIksT*B9vj+oE0dVYFXn(3}mCH(#q3;9@Mc&jC@GR=xEj(9_!0b@TQq$@6 z_};~)1WN(%91jm~S3wY@`?MN?6V@`|;Rc?(fKhA$!#@Mk_hKD}gDVHLPkz1matVrz z0)U2I+M(e00`$|Tmh+V$*N^h+SA*`MQNO4|Yy#%EQ<>9@-6UtS$4nWaPa%cFN@mka z!)=SB1xP61H20kPArX6E5*J*!?j&w@Q%UxmqZKAdkgi&f3u>J8p zXlk@#5?jkYyj(p)UCI`JQQ`CjOpEt1^1;~*x)(g*zP}8@-=7M4g4-T-{>jb#Ga$G> zU9;`y63!<_yC4^xs)9MKY6(q9>THO}GVDRdU`*ExSF$~OvdStt2s^U4lh=UawX88H zOc6srLu?$F0~;&E5l%Qje)L2wvQW9co3F!6lMQ(3FaKJbPTI3=c7Tvf=iDRYy*oN* z?!C^jFf8)$=XD(q5A6MZIB7+OZlR!@R-E%-fu)bMpOn%A^$>fBsTQin2q;=3-$lJJ zY~X|{dX+K*k2Aup%?K5+PrcFabT;&;@s@1-yx;U#wC9vaFi|;<&n8lmx|114_n0vp zZ_F9@qdZ?`CrPygdkyb%8l~AQqEqjtGQkv4&ukr0J;_ehQ3Vu36{rgk;BG&|ba-CG zZO!UlU@^yuAtQ_8VKI(9w`)t5+vmrYs1xyNb{l6znq0b{ql6@GCy8z5EXcydwohTa znf5eoez+2Rs>qlc)*-IHNlKrN92l}7RE}AG(63sf{mf*P18;sp{W)9c4!2hjF0E~j zbHsKQX6bUo#SB9{LU^Jro?*tUYj5sNj#*3{$UY^7u-d5-x2tEV5)^jes2H54mV2N$ z?xgeXes5sN8<~ioDl&Iez|zSp)TB#z{TdNkFD*}+h!`N%G-VQZ;cL@ssJT`I;-;Q> z#09aXgV8>4cpXo$)~J3vHPo7WVa#^L=imHvG1eA_L2@JjFlbnOnq%`&} z?^{&96M020abB_WvYUxxEc-5l^yGluS6D1&OlLlbjOnOiqBuk*si&m?O`GPg>p)Jx zhhR*ICj5=XK*bDAd8zNH_YCXV39Z~&Q1QL3qa*$@mOB|2>VO??iX%QE>M!O2!J^9!$4UC=uNJX-vhwT2?(VZ zO5lPrVI3_4WvMNjXIiF{G+@$XJ3a~?YpX39yAW*bnDxGYpHrjV2ycrz$+x$AIov}n zJFp+HQNNaaG6IR!(RS`}G!EcGjwk1#)u2@|uo8hfRQ`E(>cm|JJ>x>7`UG$00H3CC zsQmWIb%J)Z06L+}nV~z4cjozBetE8AoHeY7!dXHc%mcGTcFi?kh=I}9({pe0RqDjD zTX%Zai-t@_$OnC~@W8l3y#}O8S*clvZpigXKH^fjc^M|EPH8g)z@+3{sLnZIHfe`S;Kam#yH0b8N&g!7rfWyY@rzZEO|$k8D+ zdVV&f7OMZ5aoX*MNh^)`NbW%!6hxXM-|*+Jm&c9s*Q=!Va)>|&G$YGhHi7uiI^e(_ z6_=)}_W5N_)YI*>3SBw0brF%F$cpU&<2r<&E-a;_pKT_ag@#^!X>SD95?@+||!)$!2JLjKx|ZP!V>$`B->sw7!OF38Jc2ccp}8(EX!b z79aa&w@Gc|u)0scElLf*rFLo6tj@8nH?6a^c54f?B`2*v^dN9BhcVj4YnhQRkei`% zzEnDD&Zd5xh+H&4&S@xWI+!;)beyMs(z2(^)Rw27(JjsmbSjPXBgAwMa8EZ8U&Ik| zczApk0|poFwIQF71JFCL;(lKta7C_N#jre0VN}K02b~QC^fgFP=}HI zJlQw1div&w*)#Te?=e*D!-vi#u zcmFHi)o`{AB-1EUE)5wfha)-FyiA(KiH=Gp-mfB$_`wCv$Mh3`I;E}h*5bX~ijPMR zQrocXrESx@L)OT8lQc!h=7T-x&`Z7>jctdqDeG9+YuO)I&NOo_Mzbm=$>nb-^3P~4 zB6n4*IdUygSQc47wulndM>hM`J>{d{S`K|b9;&;f2-7z|@%Fx^;{Tg~{coni$j(at zuVDNSHTj!1{7;|?!~afI{tD;+4XZ%?C#>@CVB`ObtNcbRO!y4{nX&wp7zl>H5v{wse` z6chZP;Fm4Cb!BAU2s_0hR6cxlv9QRY8Wkn2HZ07lVK9lL;tNUC;*%9h;(boGh;l6O z^^@ibIj;aVuy;WIRbV)AZUPuEL$B4x-bM+fFv6ErmGIQc>YN$kl%!JBnl&9&l z9h^vriHn5GPuWMJ9?z&>a}k7cy|ySgJ{Zs66ebs}nx&9?z~7!NTEKqYVPF4b1p%rA zO7qD^be|g`A5Gd(vBhd9G)-uzN;Ghs*`bSb zXkqw*qs?-;Ki3ITf6FdzGirs4*ra+??vCJFZo0?5ma5gOX_GMU1AUHro%aTE$5Xg3 zPxNu#JQCB>t3uuc+XVHRvoD2~d9!q3@WMu~n|mNROCKd-Uv@D=Y$!tWzfyf}TroOM zC>vF>*f!Yilk};`(93jCJq+HMXwb}<1R{zv?kSzx_XqdqYpm)l1D%@ZWMe8oy_Hh4 zvaK!`SgX#|P=Nkm%;3_E8>O)6Na;_;n}BV1&BvHPl^mWsA@!>=^)y3RTLJCJMO%x{ z+n7Ih21EB);p^n2I|RdC*I7_BY%c5-I~Eoul0~I4Tr8L5Dw5A-OdjkKW#uMe91RW` zm@r@f`K#1%-07;Y6YQ=|lc-*D2?sjLIo1fBVkwLmsX$y?TZDIu-XBnprDFbx$NW%fPI4 zfrJl^WbVQ?&s%C`#di+NJr36KJblq^W##e~lS7rHZ4gQZN97tIX_Ix`#z zSPt0ou<}rFr8BC=dR|hABJ1ez$~{r3t9JH5YNR`7ZX}s_#8s5s;K(i$P)ly-v+gr# zZ3*zhXA%-UC+D^ewKT~n({2u@gR00qlylbXl#Vah7Hok;rRx&wIEvb8O+^g_r1y9$ zqt2pO7|Ikg%FF>dt6OVVe^eoHcMxIhvB2hAw}|V;H=9c>;jpiMt}|70tuiE^3;2f~ z%(c*KS?@!pf++BZ_O9KdLh*gD1SQVetv8$#sof3dmN|F-CzTm8VJ?efzbpCQy7f=w{eLU{X~LSY z{l$J;U9)Wsn_34+q<96uAswxu50;e!qpuI^pzqwiC}H<%QjRn7_MnxlE+Sd5uezVZ zp^i}Qsa98^?lZz$%Z1we6%kK8MM4Grh_>Fatd^ob5ecbEW1v!XZa&lO3$@bx={V6{ z-{dxo3(Bm_4~Q4HOAc4m{|(|2AkNdf_!EHEuFdGG+CU8+Bv6Mgqt~TRag1@S+U@+| z!DSTq`POsl9qqiv(TQ^9#H#Oc2zZ_DG3>y=ja&Kj!qktmR-~&|GaL16s=4*P`fI?q z1MD+Fg^a{HJCPeqvJ;gA3eZn}@Yi$Nq~Lt*v`D;}KsuGL2dQwT@QE@)IQgzdGNfvu z9rW&Q>pD&SwkqUyDzUwmQq!uPY}U}DA?8);`2h7kBV<3SVa;6c1jeK#MGO4__k=N6 zdS`XD2(*$-iBWq){vl*s5WAy5R~xf_$7A@tgiY}_Wg;YoB@;5YdGK|@OV3BEaSae{ zMo(`T4lg#Hz)@}eq93%j{%eS}uKOpJ_Z@5t_I+yCXUDuBTh5c-eGe8uZroNC!fwx2 z&(}j@2tLH&UI)@;Kbmld#}11&!Nn6M9K$k65jYTqp0=g@b3qjLkTAR?azA7O_081d zeC3}nD(xJp3L^CcCdVY`2U*PwTXtsBNWEfNa_Db|PMp<5(tn0cdf95V= z+f*z>D|{Yra0`~5^n?|z5t^@Z@o)%1E@thumyZZP-U1jR_!qZ-Un9R4Z zv@nIh*$?;e60B=|^r?Z^(ZU)CSl>;()k2s1aC`!UWJE|(X(>V>=vg8xKmK-eCf}+Q zll?WV**te%i#lRZ1Qx0HXn1(&!9%Qi*()KRdqHf8eu!TT#)J*9Tk3g9@b;&72(kV0 zqpcBq&u|Y8(P1X(B2yKf;)JLX!dWiCsb^o7VeG+*c%9F22Jr^z>;{2@H}OHzyAq5f zt;#V9Ji-pWhT`0r5O&AG?ChYXQ4i?q_BWqL%;_|o4a5%f7_vdD1xg^jtx>Z*Gk}yk zqt1XPNUL^9m`Ao74H}JyeE?Ord*2X7$!lZy4h2iYdnN%h`GL+8&lXd14s!MYXvRYG z8zWe7?H6Umavv*MSC}EP_osOp_e=q1n%Jt0aezx94%>c`@Kxgx75a7Po>I zv)eszrGLdjH8kmR=MDXpZUJWRmK|{Mj$gN zATcj;eT63G9F+Zz0OdjfA=$9|;`2<5Q4r8OhqV@juF*o}9Ck~Rk)Rl%=P9(Gf5?-^ zyY4__m{4Il+2lKyQfEP?ERO2Z{Tc&pi2dpo!|l~i*!`ee86Z@|)%Pu{tfMA_r2W7$ zi<~72B5f#@G^3V|W{CBJ15sZ@3Iu_JWE2Ek4HM9lcPza*u?}IyY0WaW8h#aCK)4D) zQ82(bVBKQOG=AAQLyy73h+K^JF( zw_$k9q7$%`;T$jk2}TtxU_ieo4*qPOc(qoQ0xGgV;EJdq<AsEFqv@WX?>@TUx0j z3lBPb7sPIn%TQz(`EE-pX{WGH4RX7pd)MAhF@o0dcJU^2Sjw__s*GWqsnuGA_+fh^ z5^u~G{64k5meQPONk!$&wuj>N#`NM<9CZpt`n#>_7J{c#OQK@Pijc&%mys*PVK~D$ z)8Smg_sp=LhiUyLZurFt9%11+STd6#`*V)z_0jgE02pM`WT5J0e}IGZoo2q)?a0FI6oV{wJ7X4Yrna4oBPA+H^fQK&QJ94)37 zB~m;aa8*hE85$gF4hj4lSZ2hpB@f*?ljChQ8*~1UPP2=R36G%PQ|t%j59(sVAJX&d z-tzUbK|akf_)ba6p~D;;MGD2}iCp0d%mdUcXj;Vuq}VtiP|8R}01rwrS=&fH=a$P3 zBMOzr1aXKA$O)@|6;hFA&!2NpMZ~=xAaKZyK>vQU%WR&H`6zcjo)ORwVVb`^ze3YX z_U`oB26&_>&ci>!2S@U(J%n01Ae#aAru_qsKB|q=`cZG?Xu!Al6L(^uUVS4C6uT%@ zKdcRhs}GrEwfDV%u(0HK*iR5>_Lp-uxzXnDaCVlG14hk9X_1ISZ3POS^93oSV9 z-9UGVgJVXiHgS3X0<_XiMt&h2%QwMXQ%m+?z$Mr0@<;Tipz>QN=I1;`uEC1A2qSQ> z*&on?>2j)0nyG#ZuM)nxmW=xnJS7MEPO)ombv)nl+vS|nHO-ymXQJ~a0-&w?4LosZ z7qRt6*x8l9r;}Kt1#u@JvTH@xrpsxS-5DvSl;w*0Z2dRx;MU_QTrx5WMJZ&-G|lwS zK=`%n1iubJ+NeU`PP?_~wqpbOKR6slD3n}KO*mFuwK6dS9GHZw&}_Zev8pUsNO0vo z`geX!PVYEd^e54SmQLK}Qch1fGOLr#h%Uo|duk4y@(!xG0KvP5qC+kj_8l#=vba74N)!|GUyQ}}UzK7B3W zBeZQZih}?%o(mzyqMPGTH$Qbu`8rgdw5jm*QCzaaGt;PdM18;;iQus{N-E=;N2!cl zzJB8@?4eh`9bOP&eL0S*VBf$~X_0clfVC2RArImVtztcMQK(?XxUW-g^HiEpR#8Z( zoU9ly*q#&D^ZM<0lowqaz8+OwcAhK9V^4R6msjtmiYxgqvaN-3T+9Fx)90E;`=G*( z4JJU5p|T>!+8ZmAqg{tiJzyKrGZQy}FY?vBl|=Sy@rOwmx~5qY%f|Ar)sJH7;&Bm$ z7f^#YSkbF(^U4^FMxUyFG)=#J_vbqAiH<5L4!!fOWk*%0y9aReG&l{(FD2A42ChF2 z9V%U!anP+uF>KmU9w{!(xz}*4B9_<4V_I$}IXYe=Kh#b`CNKHaWeyl`GJ}wT^@vJk zk4|B~8uybviB~73U)mUkdh)@nRCuG;T?cPh{e2!N0`XFfkbzrMw5n9iTFNMu9kP_F zm3gz2;1sI*2_)P+wyP&M&(bHZKuF~}Nb=8YZx1^cBeIk_M>0pIEJm07J?S>HO3W2T z{UVzjQF-G$Rj8rj+h`s@AZI}D^9T~t2PIb8uBQ}=eL^fr=YpODhPg)`3p-;)-|g91 zfg`TPKB|8^%xT2&BksU4P_zQMyl-AgI>6?3T7=&k1e*K%FwcGri6x;^m7rkpy&+jb zR>cs9X)~WB^uI?v>Bozh5WB0I8ft_{1Lqdm8!UxgerOB1NK0+6l^>^FbR|b~zn5G8 zu+Z>*##_O;KlpEK+}}BFCMFK1zgZjO|7!sg<3F?F|Egg6%j5k|SleGS|Atuq&oxZH zSsM#JT;|{-d^upTFDJ!j_hRt-bb3ubN%?EVl#5zXyW!JF5fwGv})#U!L`8BO*-V#ZFn)oI-=|P8 zhB7ds=0g_zAlw_Qv3@K)r0N(;Pb1yW$JCg{=w!}~?vUTiB-ts92Fce>C)qYD^aeNS zQkKXBICvue`SUqj8h{=-LvQkkG~YUcQ3M&|GU65p03vyiqtg*K>V?BR7r?#5Y(Ekt zLc*{FVL9F6;CR&Z^`=4T#gKt2c|c|-++BL+zVTZuJ(_=wU8)EmiH1#vdBQ~%_Qi$9 z(xLfF&*~PguhIw4sOOu?4e9wzu0V9I#+LQ@54tW?fJb7@ES!e!6cvC*8!-NZogZ>n z_yD({0F9g3cetU}x`~6k-^ie&&d;r#0Q0P9sgBv+@&fI~dqO#$GA1tHRJlsf_yUL| zP?$_a4oXT(q=Y9Yzrm)>7=Jv$f9>^hSWJx#P-o}$qTPfXt0){NLk35fcZ6^UsK?V9 zLk0US?)agzl7VCR=2#({yXXP?*IX~8omK#0O}CE{v-(gQ3&8UQFREc8)3nCbz(pS0;>eVBA{}S%y`%$thN~O1c!ZK zu&{iCbad2VjJ+_8=p92r`o(mkbX94zLsnCGrhu2gV#CF{5_Pg4e6EnJ2w5>S zgR1&*`dYPVb-*hI=UmvZBfS{A*!I4y=-Q07EY%<_aLdsh{$~R)JES-6os^r%SAlSS zPS=Ls1l>@6*nYTMad^W$Wbu%iAXb5J1gZGM*2K#2m=H%0AHfLy%nAWza?XUI@E(KO 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 ZPr09t{=DI>>2>3`!p6(P!^h8`mfwWxdB*?% literal 0 HcmV?d00001 diff --git a/tools/polybond/README b/tools/polybond/README new file mode 100644 index 0000000000..7149103d12 --- /dev/null +++ b/tools/polybond/README @@ -0,0 +1,12 @@ +25 July 2013 + +This directory contains a Python-based tool for performing +"programmable polymer bonding". The Python file lmpsdata.py provides a +"Lmpsdata" class with various methods which can be invoked by a +user-written Python script to create data files with complex bonding +topologies. + +See the Manual.pdf for details and example scripts. + +This tool and the manual was written by Zachary Kraus at Georgia Tech. +He can be contacted at this email address: zachkraus at gatech.edu. diff --git a/tools/polybond/lmpsdata.py b/tools/polybond/lmpsdata.py new file mode 100644 index 0000000000..36cd6a0aa3 --- /dev/null +++ b/tools/polybond/lmpsdata.py @@ -0,0 +1,1945 @@ +# +# +# lmpsdata.py +# +# For reading, writing and manipulating lammps data files +# For calculation of certain properities using lammps data files +# For creating VMD input text files using lammps data files +# All x,y,z calculations assume the information includes image flags + +class Lmpsdata: + def __init__(self,file,atomtype): + """initiates lammps data structures""" + self.atomtype=atomtype + self.keywords=[] + self.atoms=[] + self.angles=[] + self.bonds=[] + self.dihedrals=[] + self.dipoles=[] + self.impropers=[] + self.masses=[] + self.shapes=[] + self.velocities=[] + self.anglecoef=[] + self.bondcoef=[] + self.dihedralcoef=[] + self.impropercoef=[] + self.paircoef=[] + self.read(file) + + def read(self,file): + """Reads in lammps data file + Skips the first line of the file (Comment line) + First reads the header portion of the file (sectflag=1) + blank lines are skipped + header keywords delineate an assignment + if no header keyword is found, body portion begins + Second reads the body portion of the file (sectflag=2) + first line of a section has only a keyword + next line is skipped + remaining lines contain values + a blank line signifies the end of that section + if no value is listed on a line than a keyword must be used + File is read until the end + The keywords read in are stored in self.keywords""" + if file=='': + print 'no file is given. Will have to build keywords and class structures manually' + return + sectflag=1 + f=open(file,'r') + f.readline() + for line in f: + row=line.split() + if sectflag==1: + if len(row)==0: + #skip line the line is blank + pass + elif len(row)==1: + # Set Sectflag to 2, assume line is a keyword + sectflag=2 + checkkey=1 #ensures keyword will be checked in body portion + keyword=row + elif len(row)==2: + if row[1]=='atoms': + self.atomnum=row[0] + self.keywords.append('atoms') + elif row[1]=='bonds': + self.bondnum=row[0] + self.keywords.append('bonds') + elif row[1]=='angles': + self.anglenum=row[0] + self.keywords.append('angles') + elif row[1]=='dihedrals': + self.dihedralnum=row[0] + self.keywords.append('dihedrals') + elif row[1]=='impropers': + self.impropernum=row[0] + self.keywords.append('impropers') + else: + # Set Sectflag to 2, assume line is a keyword + sectflag=2 + checkkey=1 #ensures keyword will be checked in body portion + keyword=row + elif len(row)==3: + if row[1]=='atom' and row[2]=='types': + self.atomtypenum=row[0] + self.keywords.append('atom types') + elif row[1]=='bond' and row[2]=='types': + self.bondtypenum=row[0] + self.keywords.append('bond types') + elif row[1]=='angle' and row[2]=='types': + self.angletypenum=row[0] + self.keywords.append('angle types') + elif row[1]=='dihedral' and row[2]=='types': + self.dihedraltypenum=row[0] + self.keywords.append('dihedral types') + elif row[1]=='improper' and row[2]=='types': + self.impropertypenum=row[0] + self.keywords.append('improper types') + else: + # Set Sectflag to 2, assume line is a keyword + sectflag=2 + checkkey=1 #ensures keyword will be checked in body portion + keyword=row + elif len(row)==4: + if row[2]=='xlo' and row[3]=='xhi': + self.xdimension=[row[0], row[1]] + self.keywords.append('xlo xhi') + elif row[2]=='ylo' and row[3]=='yhi': + self.ydimension=[row[0], row[1]] + self.keywords.append('ylo yhi') + elif row[2]=='zlo' and row[3]=='zhi': + self.zdimension=[row[0], row[1]] + self.keywords.append('zlo zhi') + else: + # Set Sectflag to 2, assume line is a keyword + sectflag=2 + checkkey=1 #ensures keyword will be checked in body portion + keyword=row + elif len(row)==5: + if row[1]=='extra' and row[2]=='bond' and row[3]=='per' and row[4]=='atom': + self.extrabonds=row[0] + self.keywords.append('extra bond per atom') + else: + # Set Sectflag to 2, assume line is a keyword + sectflag=2 + checkkey=1 #ensures keyword will be checked in body portion + keyword=row + elif len(row)==6: + if row[3]=='xy' and row[4]=='xz' and row[5]=='yz': + self.tilt=[row[0], row[1], row[2]] + self.keywords.append('xy xz yz') + else: + # Set Sectflag to 2, assume line is a keyword + sectflag=2 + checkkey=1 #ensures keyword will be checked in body portion + keyword=row + else: + # set sectflag to 2, assume line is a keyword + sectflag=2 + checkkey=1 #ensures keyword will be checked in body portion + keyword=row + elif sectflag==2: + if checkkey==1: + if len(keyword)==1: + if keyword[0]=='Atoms' or keyword[0]=='Velocities' or keyword[0]=='Masses' or\ + keyword[0]=='Shapes' or keyword[0]=='Dipoles' or keyword[0]=='Bonds' or\ + keyword[0]=='Angles' or keyword[0]=='Dihedrals' or keyword[0]=='Impropers': + bodyflag=1 + blanknum=0 + self.keywords.append(keyword[0]) + checkkey=0 + else: + bodyflag=0 + checkkey=0 + elif len(keyword)==2: + if row[1]=='Coeffs' and (row[0]=='Pair' or row[0]=='Bond' or row[0]=='Angle' or\ + row[0]=='Dihedral' or row[0]=='Improper'):#class 2 force field keywords not included + bodyflag=1 + blanknum=0 + self.keywords.append('{0} {1}'.format(keyword[0],keyword[1])) + checkkey=0 + else: + bodyflag=0 + checkkey=0 + else: + #egnore line and set bodyflag=0 + bodyflag=0 + checkkey=0 + if bodyflag==0: #bodyflag 0 means no body keyword has been found + if len(row)==1: + if row[0]=='Atoms' or row[0]=='Velocities' or row[0]=='Masses' or\ + row[0]=='Shapes' or row[0]=='Dipoles' or row[0]=='Bonds' or\ + row[0]=='Angles' or row[0]=='Dihedrals' or row[0]=='Impropers': + bodyflag=1 + blanknum=0 + keyword=row + self.keywords.append(keyword[0]) + else: + #egnore line + pass + elif len(row)==2: + if row[1]=='Coeffs' and (row[0]=='Pair' or row[0]=='Bond' or row[0]=='Angle' or\ + row[0]=='Dihedral' or row[0]=='Improper'):#class 2 force field keywords not included + bodyflag=1 + blanknum=0 + keyword=row + self.keywords.append('{0} {1}'.format(keyword[0],keyword[1])) + else: + #egnore line + pass + else: + #egnore line + pass + elif bodyflag==1: #currently assumes 1 or more blank lines are between body data keywords + if len(row)==0: + blanknum+=1 + if blanknum>1: + bodyflag=0 + elif len(keyword)==1: + if keyword[0]=='Atoms': + try: + int(row[0]) + except ValueError: + keyword=row + checkkey=1 + else: + self.atoms.append(row) + elif keyword[0]=='Velocities': + try: + int(row[0]) + except ValueError: + keyword=row + checkkey=1 + else: + self.velocities.append(row) + elif keyword[0]=='Masses': + try: + int(row[0]) + except ValueError: + keyword=row + checkkey=1 + else: + self.masses.append(row) + elif keyword[0]=='Shapes': + try: + int(row[0]) + except ValueError: + keyword=row + checkkey=1 + else: + self.shapes.append(row) + elif keyword[0]=='Dipoles': + try: + int(row[0]) + except ValueError: + keyword=row + checkkey=1 + else: + self.dipoles.append(row) + elif keyword[0]=='Bonds': + try: + int(row[0]) + except ValueError: + keyword=row + checkkey=1 + else: + self.bonds.append(row) + elif keyword[0]=='Angles': + try: + int(row[0]) + except ValueError: + keyword=row + checkkey=1 + else: + self.angles.append(row) + elif keyword[0]=='Dihedrals': + try: + int(row[0]) + except ValueError: + keyword=row + checkkey=1 + else: + self.dihedrals.append(row) + elif keyword[0]=='Impropers': + try: + int(row[0]) + except ValueError: + keyword=row + checkkey=1 + else: + self.impropers.append(row) + else: + #egnore line and change bodyflag to 0 + bodyflag=0 + elif len(keyword)==2: + if keyword[0]=='Pair' and keyword[1]=='Coeffs': + try: + int(row[0]) + except ValueError: + keyword=row + checkkey=1 + else: + self.paircoef.append(row) + elif keyword[0]=='Bond' and keyword[1]=='Coeffs': + try: + int(row[0]) + except ValueError: + keyword=row + checkkey=1 + else: + self.bondcoef.append(row) + elif keyword[0]=='Angle' and keyword[1]=='Coeffs': + try: + int(row[0]) + except ValueError: + keyword=row + checkkey=1 + else: + self.anglecoef.append(row) + elif keyword[0]=='Dihedral' and keyword[1]=='Coeffs': + try: + int(row[0]) + except ValueError: + keyword=row + checkkey=1 + else: + self.dihedralcoef.append(row) + elif keyword[0]=='Improper' and keyword[1]=='Coeffs': + try: + int(row[0]) + except ValueError: + keyword=row + checkkey=1 + else: + self.impropercoef.append(row) + else: + #egnore line and change bodyflag to 0 + bodyflag=0 + else: + #egnore line and change bodyflag to 0 + bodyflag=0 + f.close() + + def write(self,file,modflag): + """Write lammps data files using the lammps keywords and lammpsdata structures + writes first line of the file as a Comment line + if no modifications to any of the lammpsdata structures (modflag=0) + Use Keywords to write lammpsdata structures directly + if modifications to any of the lammpsdata structures (modflag=1) + Key Lammpsdata structures like atom numbers, coeficient numbers + need to be modified to match the other modified lammpsdata structures + This section will still use the keywords to write lammpsdata structures. + For all modflags, the code will write data to the file until all of the + keyword's data structures have been finished writing. + The keywords are stored in self.keywords""" + f=open(file,'w') + f.write('polymer data file\n') + for row in self.keywords: + if row=='atoms': + if modflag==0: + f.write('{0} {1}\n'.format(self.atomnum,row)) + elif modflag==1: + f.write('{0} {1}\n'.format(len(self.atoms),row)) + elif row=='bonds': + if modflag==0: + f.write('{0} {1}\n'.format(self.bondnum,row)) + elif modflag==1: + f.write('{0} {1}\n'.format(len(self.bonds),row)) + elif row=='angles': + if modflag==0: + f.write('{0} {1}\n'.format(self.anglenum,row)) + elif modflag==1: + f.write('{0} {1}\n'.format(len(self.angles),row)) + elif row=='dihedrals': + if modflag==0: + f.write('{0} {1}\n'.format(self.dihedralnum,row)) + elif modflag==1: + f.write('{0} {1}\n'.format(len(self.dihedrals),row)) + elif row=='impropers': + if modflag==0: + f.write('{0} {1}\n'.format(self.impropernum,row)) + elif modflag==1: + f.write('{0} {1}\n'.format(len(self.impropers),row)) + elif row=='atom types': + if modflag==0: + f.write('{0} {1}\n'.format(self.atomtypenum,row)) + elif modflag==1: + f.write('{0} {1}\n'.format(len(self.masses),row)) + elif row=='bond types': + if modflag==0: + f.write('{0} {1}\n'.format(self.bondtypenum,row)) + elif modflag==1: + f.write('{0} {1}\n'.format(len(self.bondcoef),row)) + elif row=='angle types': + if modflag==0: + f.write('{0} {1}\n'.format(self.angletypenum,row)) + elif modflag==1: + f.write('{0} {1}\n'.format(len(self.anglecoef),row)) + elif row=='dihedral types': + if modflag==0: + f.write('{0} {1}\n'.format(self.dihedraltypenum,row)) + elif modflag==1: + f.write('{0} {1}\n'.format(len(self.dihedralcoef),row)) + elif row=='improper types': + if modflag==0: + f.write('{0} {1}\n'.format(self.impropertypenum,row)) + elif modflag==1: + f.write('{0} {1}\n'.format(len(self.impropercoef),row)) + elif row=='xlo xhi': + f.write('{0} {1} {2}\n'.format(self.xdimension[0],self.xdimension[1],row)) + elif row=='ylo yhi': + f.write('{0} {1} {2}\n'.format(self.ydimension[0],self.ydimension[1],row)) + elif row=='zlo zhi': + f.write('{0} {1} {2}\n'.format(self.zdimension[0],self.zdimension[1],row)) + elif row=='extra bond per atom': + f.write('{0} {1}\n'.format(self.extrabonds,row)) + elif row=='xy xz yz': + f.write('{0} {1} {2} {3}\n'.format(self.tilt[0],self.tilt[1],self.tilt[2],row)) + elif row=='Atoms': + f.write('\n{0}'.format(row)) #new line between header and body portion or two body keywords + f.write('\n') #new line between body keyword and body data + for line in self.atoms: + f.write('\n') #creates a new line for adding body data + for item in line: + f.write(' {0}'.format(item)) #adds in each peice of body data with space imbetween + f.write('\n') #allows space to be added between the end of body data and a new keyword + elif row=='Velocities': + f.write('\n{0}'.format(row)) #new line between header and body portion or two body keywords + f.write('\n') #new line between body keyword and body data + for line in self.velocities: + f.write('\n') #creates a new line for adding body data + for item in line: + f.write(' {0}'.format(item)) #adds in each peice of body data with space imbetween + f.write('\n') #allows space to be added between the end of body data and a new keyword + elif row=='Masses': + f.write('\n{0}'.format(row)) #new line between header and body portion or two body keywords + f.write('\n') #new line between body keyword and body data + for line in self.masses: + f.write('\n') #creates a new line for adding body data + for item in line: + f.write(' {0}'.format(item)) #adds in each peice of body data with space imbetween + f.write('\n') #allows space to be added between the end of body data and a new keyword + elif row=='Shapes': + f.write('\n{0}'.format(row)) #new line between header and body portion or two body keywords + f.write('\n') #new line between body keyword and body data + for line in self.shapes: + f.write('\n') #creates a new line for adding body data + for item in line: + f.write(' {0}'.format(item)) #adds in each peice of body data with space imbetween + f.write('\n') #allows space to be added between the end of body data and a new keyword + elif row=='Dipoles': + f.write('\n{0}'.format(row)) #new line between header and body portion or two body keywords + f.write('\n') #new line between body keyword and body data + for line in self.dipoles: + f.write('\n') #creates a new line for adding body data + for item in line: + f.write(' {0}'.format(item)) #adds in each peice of body data with space imbetween + f.write('\n') #allows space to be added between the end of body data and a new keyword + elif row=='Bonds': + f.write('\n{0}'.format(row)) #new line between header and body portion or two body keywords + f.write('\n') #new line between body keyword and body data + for line in self.bonds: + f.write('\n') #creates a new line for adding body data + for item in line: + f.write(' {0}'.format(item)) #adds in each peice of body data with space imbetween + f.write('\n') #allows space to be added between the end of body data and a new keyword + elif row=='Angles': + f.write('\n{0}'.format(row)) #new line between header and body portion or two body keywords + f.write('\n') #new line between body keyword and body data + for line in self.angles: + f.write('\n') #creates a new line for adding body data + for item in line: + f.write(' {0}'.format(item)) #adds in each peice of body data with space imbetween + f.write('\n') #allows space to be added between the end of body data and a new keyword + elif row=='Dihedrals': + f.write('\n{0}'.format(row)) #new line between header and body portion or two body keywords + f.write('\n') #new line between body keyword and body data + for line in self.dihedrals: + f.write('\n') #creates a new line for adding body data + for item in line: + f.write(' {0}'.format(item)) #adds in each peice of body data with space imbetween + elif row=='Impropers': + f.write('\n{0}'.format(row)) #new line between header and body portion or two body keywords + f.write('\n') #new line between body keyword and body data + for line in self.impropers: + f.write('\n') #creates a new line for adding body data + for item in line: + f.write(' {0}'.format(item)) #adds in each peice of body data with space imbetween + f.write('\n') #allows space to be added between the end of body data and a new keyword + elif row=='Pair Coeffs': + f.write('\n{0}'.format(row)) #new line between header and body portion or two body keywords + f.write('\n') #new line between body keyword and body data + for line in self.paircoef: + f.write('\n') #creates a new line for adding body data + for item in line: + f.write(' {0}'.format(item)) #adds in each peice of body data with space imbetween + f.write('\n') #allows space to be added between the end of body data and a new keyword + elif row=='Bond Coeffs': + f.write('\n{0}'.format(row)) #new line between header and body portion or two body keywords + f.write('\n') #new line between body keyword and body data + for line in self.bondcoef: + f.write('\n') #creates a new line for adding body data + for item in line: + f.write(' {0}'.format(item)) #adds in each peice of body data with space imbetween + f.write('\n') #allows space to be added between the end of body data and a new keyword + elif row=='Angle Coeffs': + f.write('\n{0}'.format(row)) #new line between header and body portion or two body keywords + f.write('\n') #new line between body keyword and body data + for line in self.anglecoef: + f.write('\n') #creates a new line for adding body data + for item in line: + f.write(' {0}'.format(item)) #adds in each peice of body data with space imbetween + f.write('\n') #allows space to be added between the end of body data and a new keyword + elif row=='Dihedral Coeffs': + f.write('\n{0}'.format(row)) #new line between header and body portion or two body keywords + f.write('\n') #new line between body keyword and body data + for line in self.dihedralcoef: + f.write('\n') #creates a new line for adding body data + for item in line: + f.write(' {0}'.format(item)) #adds in each peice of body data with space imbetween + f.write('\n') #allows space to be added between the end of body data and a new keyword + elif row=='Improper Coeffs': + f.write('\n{0}'.format(row)) #new line between header and body portion or two body keywords + f.write('\n') #new line between body keyword and body data + for line in self.impropercoef: + f.write('\n') #creates a new line for adding body data + for item in line: + f.write(' {0}'.format(item)) #adds in each peice of body data with space imbetween + f.write('\n') #allows space to be added between the end of body data and a new keyword + else: + pass + f.close() + + def atomorder(self): + """Takes self.atoms and organizes the atom id from least to greatest. + If the atom ids are allready ordered this algorithm will do nothing.""" + current=range(len(self.atoms[0])) # initialize current [assumes self.atoms coloumn #'s does not change] + for i in range(1,len(self.atoms)): #when i=0, self.atoms will not change; therefore its skipped + for k in range(len(self.atoms[i])): + current[k]=self.atoms[i][k] + for j in range(i-1,-1,-1): + for k in range(len(self.atoms[j])): + self.atoms[j+1][k]=self.atoms[j][k] + if int(current[0]) > int(self.atoms[j][0]): + for k in range(len(current)): + self.atoms[j+1][k]=current[k] + break + elif j==0: + for k in range(len(current)): + self.atoms[j][k]=current[k] + #dont need a break here because this is the last j value in the for loop + + def addatoms(self, atoms, retlist=False): + """Appends atoms to self.atoms. Assumes atoms are written in the correct atomtype format. + Change added atom numbers in self.atoms so self.atoms will be in increasing sequential order. + If retlist is True return a list of the modified atom numbers otherwise exit.""" + initpos=len(self.atoms) #Store index of first appended atoms + for item in atoms: + self.atoms.append(range(len(item))) #initializing spots for the new atoms to go + numberchange=[] #initiate number change where the changed atom numbers will be stored + count=0 + for i in range(initpos,len(self.atoms)): + for j in range(len(self.atoms[i])): + if j==0: + self.atoms[i][0]=str(i+1) + numberchange.append(str(i+1)) + else: + self.atoms[i][j]=atoms[count][j] + count+=1 + if retlist: return numberchange + else: return #need to redo this algorithm so their is no referencing going on. + + def adddata(self,data,keyword): + """Adds data to a keyword's existing data structure + All body keywords are viable except Atoms which has its own method + All header keywords will do nothing in this algrorithm because they have their own algorithm + changes the body id number so id numbers will be in sequential order""" + if keyword=='Velocities': + pos=len(self.velocities) + for item in data: + self.velocities.append(item) #adds data to existing data structure + self.velocities[pos][0]=str(pos+1) #changing body id number + pos+=1 + elif keyword=='Masses': + pos=len(self.masses) + for item in data: + self.masses.append(item) #adds data to existing data structure + self.masses[pos][0]=str(pos+1) #changing body id number + pos+=1 + elif keyword=='Shapes': + pos=len(self.shapes) + for item in data: + self.shapes.append(item) #adds data to existing data structure + self.shapes[pos][0]=str(pos+1) #changing body id number + pos+=1 + elif keyword=='Dipoles': + pos=len(self.dipoles) + for item in data: + self.dipoles.append(item) #adds data to existing data structure + self.dipoles[pos][0]=str(pos+1) #changing body id number + pos+=1 + elif keyword=='Bonds': + pos=len(self.bonds) + for item in data: + self.bonds.append(item) #adds data to existing data structure + self.bonds[pos][0]=str(pos+1) + pos+=1 + elif keyword=='Angles': + pos=len(self.angles) + for item in data: + self.angles.append(item) #adds data to existing data structure + self.angles[pos][0]=str(pos+1) #changing body id number + pos+=1 + elif keyword=='Dihedrals': + pos=len(self.dihedrals) + for item in data: + self.dihedrals.append(item) #adds data to existing data structure + self.dihedrals[pos][0]=str(pos+1) #changing body id number + pos+=1 + elif keyword=='Impropers': + pos=len(self.impropers) + for item in data: + self.impropers.append(item) #adds data to existing data structure + self.impropers[pos][0]=str(pos+1) #changing body id number + pos+=1 + elif keyword=='Pair Coeffs': + pos=len(self.paircoef) + for item in data: + self.paircoef.append(item) #adds data to existing data structure + self.paircoef[pos][0]=str(pos+1) #changing body id number + pos+=1 + elif keyword=='Bond Coeffs': + pos=len(self.bondcoef) + for item in data: + self.bondcoef.append(item) #adds data to existing data structure + self.bondcoef[pos][0]=str(pos+1) #changing body id number + pos+=1 + elif keyword=='Angle Coeffs': + pos=len(self.anglecoef) + for item in data: + self.anglecoef.append(item) #adds data to existing data structure + self.anglecoef[pos][0]=str(pos+1) #changing body id number + pos+=1 + elif keyword=='Dihedral Coeffs': + pos=len(self.dihedralcoef) + for item in data: + self.dihedralcoef.append(item) #adds data to existing data structure + self.dihedralcoef[pos][0]=str(pos+1) #changing body id number + pos+=1 + elif keyword=='Improper Coeffs': + pos=len(self.impropercoef) + for item in data: + self.impropercoef.append(item) #adds data to existing data structure + self.impropercoef[pos][0]=str(pos+1) #changing body id number + pos+=1 + +# def modifiydata(data,keyword): Will not implement +# """for modifying header data""" + + def changeatomnum(self,data,originalatoms,atomchanges,vflag=False): + """Takes data which contains atom ids + and alters those ids from the original atom id system in originalatoms + to the new atom id system in atomchanges.""" + #set up boolean array to match the size of data and with all True values +# print atomchanges + array=booleanarray(len(data),len(data[0]),True) +# print originalatoms + if vflag: # for changing atomnumbers for velocities + for i in range(len(originalatoms)): #len of originalatoms should match len of atomchanges + if originalatoms[i][0]==atomchanges[i]: continue + for j in range(len(data)): + for k in range(1): + if data[j][k]==originalatoms[i][0]: #checks if the atom id in data + #matches the atom id in origianalatoms + if array.getelement(j,k): #if the boolean array is true + #set the boolean array to False and + #change the atom id in data to atomchanges + array.setelement(j,k,False) + data[j][k]=atomchanges[i] + break + #the change of boolean array to False insures + #the atom id in data will only be changed once. + else: # for changing atom numbers for everything else + for i in range(len(originalatoms)): #len of originalatoms should match len of atomchanges + if originalatoms[i][0]==atomchanges[i]: continue + for j in range(len(data)): + for k in range(2,len(data[j])): + if data[j][k]==originalatoms[i][0]: #checks if the atom id in data + #matches the atom id in origianalatoms + if array.getelement(j,k): #if the boolean array is true + #set the boolean array to False and + #change the atom id in data to atomchanges + array.setelement(j,k,False) + data[j][k]=atomchanges[i] + break + #the change of boolean array to False insures + #the atom id in data will only be changed once. +# print data + return data + + def deletebodydata(self,keyword): + """Changes a keyword's class data structure to an empty structure""" + if keyword=='Velocities': + self.velocities=[] + elif keyword=='Masses': + self.masses=[] + elif keyword=='Shapes': + self.shapes=[] + elif keyword=='Dipoles': + self.dipoles=[] + elif keyword=='Bonds': + self.bonds=[] + elif keyword=='Angles': + self.angles=[] + elif keyword=='Dihedrals': + self.dihedrals=[] + elif keyword=='Impropers': + self.impropers=[] + elif keyword=='Pair Coeffs': + self.paircoef=[] + elif keyword=='Bond Coeffs': + self.bondcoef=[] + elif keyword=='Angle Coeffs': + self.anglecoef=[] + elif keyword=='Dihedral Coeffs': + self.dihedralcoef=[] + elif keyword=='Improper Coeffs': + self.impropercoef=[] + elif keyword=='Atoms': + self.atoms=[] + + def extractmolecules(self,molecule): + """Takes the variable molecule and + extracts the individual molecules' data back into lmpsdata. + This extraction takes place through a 4 step process. + Step 1: Use a molecule's keywords to alter the lmpsdata data structures to empty. + To acomplish this procedure use the lmpsdata method deletebodydata. + Step 2: Add the molecules' atoms to lmpsdata's atoms using the method addatoms. + Return a list of atom id changes for each molecule. + Step 3: Utilize each molecules list of atom id changes to change their data's atom id numbers. + Uses changeatomnum and returns the altered molecule's data. + Step 4: Add the altered molecules' data to lmpsdata's data using the method adddata""" + #Use molecule index 0's keyword to change the equivalent lmpsdata structures to empty + print 'extracting the molecules back to data' + for keyword in molecule[0].keywords: # step 1 + self.deletebodydata(keyword) + atomchanges=[] #initializing step 2 + for i in range(len(molecule)): # step 2 + atomchanges.append(self.addatoms(molecule[i].atoms,True)) + for i in range(len(molecule)): # step 3 + for keyword in molecule[i].keywords: + if keyword=='Angles': + molecule[i].angles=self.changeatomnum(molecule[i].angles,molecule[i].atoms,atomchanges[i]) + if keyword=='Bonds': + molecule[i].bonds=self.changeatomnum(molecule[i].bonds,molecule[i].atoms,atomchanges[i]) + elif keyword=='Dihedrals': + molecule[i].dihedrals=self.changeatomnum(molecule[i].dihedrals,molecule[i].atoms,atomchanges[i]) + elif keyword=='Velocities': + molecule[i].velocities=self.changeatomnum(molecule[i].velocities,molecule[i].atoms,atomchanges[i],True) + elif keyword=='Impropers': + molecule[i].impropers=self.changeatomnum(molecule[i].impropers,molecule[i].atoms,atomchanges[i]) + for i in range(len(molecule)): # step 4 + for keyword in molecule[i].keywords: + if keyword=='Angles': + self.adddata(molecule[i].angles,keyword) + elif keyword=='Bonds': + self.adddata(molecule[i].bonds,keyword) + elif keyword=='Dihedrals': + self.adddata(molecule[i].dihedrals,keyword) + elif keyword=='Velocities': + self.adddata(molecule[i].velocities,keyword) + elif keyword=='Impropers': + self.adddata(molecule[i].impropers,keyword) + + def density(self,ringsize,init,final,file=' '): + """Create spherical shells from the initial radius to the final radius + The spherical shell's thickness is defined by ringsize + Calculate the mass of particles within each spherical shell + Calculate the volume of each spherical shell + Then calculate the density in each spherical shell + Current code is written with the assumption the spheres are centered around the origin + If a file is listed place the results in the variable file otherwise return the results + The distance calculations are written assuming image flags are in the data file""" + rho=[] + r=[init] #initial radius to examine + radius=init+ringsize + while radius=radius**2: + if self.atomtype=='atomic' or self.atomtype=='charge' or\ + self.atomtype=='colloid' or self.atomtype=='electron' or\ + self.atomtype=='granular' or self.atomtype=='peri': + type=int(row[1])-1 + else: + type=int(row[2])-1 + mass+=float(self.masses[type][1]) + #assumes self.masses is written in atomtype order + elif self.atomtype=='dipole': + l=len(row)-1 + dist=float(row[l-6])**2+float(row[l-7])**2+float(row[l-8])**2 + if dist<(radius+ringsize)**2 and dist>=radius**2: + type=int(row[1])-1 + mass+=float(self.masses[type][1]) + #assumes self.masses is written in atomtype order + elif self.atomtype=='ellipsoid': + l=len(row)-1 + dist=float(row[l-7])**2+float(row[l-8])**2+float(row[l-9])**2 + if dist<(radius+ringsize)**2 and dist>=radius**2: + type=int(row[1]) + mass+=float(self.masses[type][1]) + #assumes self.masses is written in atomtype order + elif self.atomtype=='hybrid': + dist=float(row[2])**2+float(row[3])**2+float(row[4])**2 + if dist<(radius+ringsize)**2 and dist>=radius**2: + type=int(row[1]) + mass+=float(self.masses[type][1]) + #assumes self.masses is written in atomtype order + rho.append(mass/volume) + if file==' ': + return r,rho + else: + f=open(file,'w') + for i in range(len(r)): + f.write('{0} {1}\n'.format(r[i],rho[i])) + f.close() + return + + def createxyz(self,file, routine='mass', values=None): + """Two possible routines one to use the masses from data and the other to use the atom type and values supplied by the user. + The mass version is assessed by setting the routine to 'mass' which is the default method. + The other version is assessed by setting the routine to 'atomtype'. + The other version takes values which is a list containing the value the user wants to assign those atomtypes to. + The atomtypes of the values in the list will start at 1 even if no atoms in molecule use 1. + This makes it easier to find the atomtype and assign the value from the list + All atom data is assumed to have image flags in the data.""" + f=open(file,'w') + f.write('{0}\n'.format(len(self.atoms))) + f.write('atoms\n') + if routine=='mass': + for line in self.atoms: + if self.atomtype=='angle' or self.atomtype=='bond' or self.atomtype=='full' or\ + self.atomtype=='molecular': + type=int(line[2])-1 #3rd position + else: + type=int(line[1])-1 #2nd position + mass=self.masses[type][1] + if mass=='12.0107': elementnum=6 + elif mass=='15.9994': elementnum=8 + elif mass=='26.9815':elementnum=13 + else: + print 'no matching mass value. The method will exit' + return + l=len(line)-1 + if self.atomtype=='angle' or self.atomtype=='atomic' or self.atomtype=='bond' or\ + self.atomtype=='charge' or self.atomtype=='colloid' or self.atomtype=='electron' or\ + self.atomtype=='full' or self.atomtype=='granular' or self.atomtype=='molecular' or\ + self.atomtype=='peri': + f.write('{0} {1} {2} {3}\n'.format(elementnum,line[l-5],line[l-4],line[l-3])) + elif self.atomtype=='dipole': + f.write('{0} {1} {2} {3}\n'.format(elementnum,line[l-8],line[l-7],line[l-6])) + elif self.atomtype=='ellipsoid': + f.write('{0} {1} {2} {3}\n'.format(elementnum,line[l-9],line[l-8],line[l-7])) + elif self.atomtype=='hybrid': + f.write('{0} {1} {2} {3}\n'.format(elementnum,line[2],line[3],line[4])) + f.close() + elif routine=='atomtype': + for line in self.atoms: + if self.atomtype=='angle' or self.atomtype=='bond' or self.atomtype=='full' or\ + self.atomtype=='molecular': + type=int(line[2])-1 #3rd position + else: + type=int(line[1])-1 #2nd position + elementnum=values[type] + l=len(line)-1 + if self.atomtype=='angle' or self.atomtype=='atomic' or self.atomtype=='bond' or\ + self.atomtype=='charge' or self.atomtype=='colloid' or self.atomtype=='electron' or\ + self.atomtype=='full' or self.atomtype=='granular' or self.atomtype=='molecular' or\ + self.atomtype=='peri': + f.write('{0} {1} {2} {3}\n'.format(elementnum,line[l-5],line[l-4],line[l-3])) + elif self.atomtype=='dipole': + f.write('{0} {1} {2} {3}\n'.format(elementnum,line[l-8],line[l-7],line[l-6])) + elif self.atomtype=='ellipsoid': + f.write('{0} {1} {2} {3}\n'.format(elementnum,line[l-9],line[l-8],line[l-7])) + elif self.atomtype=='hybrid': + f.write('{0} {1} {2} {3}\n'.format(elementnum,line[2],line[3],line[4])) + f.close() + + +class booleanarray: + """A class that stores boolean values in a list of lists.""" + def __init__(self,rownum, colnum, initval): + """ initialise a list of lists (array) with + rownum correspondinig to the number of lists in the list and + colnum corresponding to the number of elements in the list's list. + initval is the value the list of lists will be initialized with. + initval should be a boolean value.""" + # initializing self.array + self.array=[] + for i in range(rownum): + self.array.append(range(colnum)) + # setting elements of self.array to initval + for i in range(rownum): + for j in range(colnum): + self.setelement(i,j,initval) + + def setelement(self,rownum, colnum, value): + """Assigns value to the list of lists (array) element at rownum and colnum.""" + self.array[rownum][colnum]=value + + def getelement(self,rownum,colnum): + """Returns element in the list of lists (array) at rownum and colnum.""" + return self.array[rownum][colnum] + +def atomdistance(a,b,atomtype): + """Returns the distance between atom a and b. + Atomtype is the style the atom data is written in. + All atom data is assumed to have image flags in the data. + Atom a and atom b are assumed to be the same atomtype.""" + from math import sqrt + if atomtype=='angle' or atomtype=='atomic' or atomtype=='bond' or\ + atomtype=='charge' or atomtype=='colloid' or atomtype=='electron' or\ + atomtype=='full' or atomtype=='granular' or atomtype=='molecular' or\ + atomtype=='peri': + l=len(a)-1 + dist=sqrt((float(a[l-3])-float(b[l-3]))**2\ + +(float(a[l-4])-float(b[l-4]))**2\ + +(float(a[l-5])-float(b[l-5]))**2) + elif atomtype=='dipole': + l=len(a)-1 + dist=sqrt((float(a[l-6])-float(b[l-6]))**2\ + +(float(a[l-7])-float(b[l-7]))**2\ + +(float(a[l-8])-float(b[l-8]))**2) + elif atomtype=='ellipsoid': + l=len(a)-1 + dist=sqrt((float(a[l-7])-float(b[l-7]))**2\ + +(float(a[l-8])-float(b[l-8]))**2\ + +(float(a[l-9])-float(b[l-9]))**2) + elif atomtype=='hybrid': + dist=sqrt((float(a[2])-float(b[2]))**2\ + +(float(a[3])-float(b[3]))**2\ + +(float(a[4])-float(b[4]))**2) + return dist + +def distance(a,coord,atomtype): + """Returns the distance between atom a and coord. + Atomtype is the style the atom data is written in. + All atom data is assumed to have image flags in the data.""" + from math import sqrt #need to alter this slightly + if atomtype=='angle' or atomtype=='atomic' or atomtype=='bond' or\ + atomtype=='charge' or atomtype=='colloid' or atomtype=='electron' or\ + atomtype=='full' or atomtype=='granular' or atomtype=='molecular' or\ + atomtype=='peri': + l=len(a)-1 + dist=sqrt((float(a[l-3])-coord[2])**2\ + +(float(a[l-4])-coord[1])**2\ + +(float(a[l-5])-coord[0])**2) + elif atomtype=='dipole': + l=len(a)-1 + dist=sqrt((float(a[l-6])-coord[2])**2\ + +(float(a[l-7])-coord[1])**2\ + +(float(a[l-8])-coord[0])**2) + elif atomtype=='ellipsoid': + l=len(a)-1 + dist=sqrt((float(a[l-7])-coord[2])**2\ + +(float(a[l-8])-coord[1])**2\ + +(float(a[l-9])-coord[0])**2) + elif atomtype=='hybrid': + dist=sqrt((float(a[2])-coord[0])**2\ + +(float(a[3])-coord[1])**2\ + +(float(a[4])-coord[0])**2) + return dist + + +class particlesurface: + def __init__(self,particle,cutoff,atomid,atomtype,shape='sphere'): + """Builds a particle surface with a specific shape from a particle + The atoms choosen from the surface will have the specific atomid + atomid will be given in terms of an integer and not a string.""" + self.particle=particle.atoms + self.atomtype=atomtype + self.cutoff=cutoff + self.surface=[] + self.particlelocator=[] #stores surface particle locations with respect to the particle + self.deletedatoms=[] + if shape=='sphere': self.createspheresurf(atomid) + + def createspheresurf(self,atomid): + """Assumes sphere is centered around 0,0,0. + Finds maximum distance particle with atomid. + Than all atoms within cutoff distance of max distance + will be included into self.surface + Also will build a list of where the surface particles are + in relationship to the particle. + Will create a booleanarray to keep track of any changes made to the surface.""" + particledist=[] + for row in self.particle: #Stores all of the distances of the particle in particledist + if self.atomtype=='angle' or self.atomtype=='bond' or self.atomtype=='full' or\ + self.atomtype=='molecular': + type=int(row[2]) #3rd position + else: + type=int(row[1]) #2nd position + if type==atomid: #only calculates the distance if the atom has the correct atomid number + particledist.append(distance(row,[0,0,0],self.atomtype)) + else: + particledist.append(0.0) + self.maxdist=max(particledist)# finds the max dist. + for i in range(len(particledist)): + if particledist[i]>=(self.maxdist-self.cutoff): + self.particlelocator.append(i) + self.surface.append(self.particle[i]) + self.bool=booleanarray(len(self.surface),1,True) + + def getsurfatom(self,rownum): + return self.surface[rownum] + + def getbool(self,rownum): + return self.bool.getelement(rownum,0) + + def setbool(self,rownum,value): + self.bool.setelement(rownum,0,value) + + def removesurfatom(self,rownum): + """note this does not actually remove the surface atom. + Instead this adds a value to the list deletedatoms. + This list is later used in extractparticle to actually delete those atoms""" + self.deletedatoms.append(self.particlelocator[rownum]) + + def extractparticle(self): + """"deletesatoms from particle from the list of deletedatoms + and than returns the particle.""" + self.particle=self.deleteatoms(self.particle,self.deletedatoms) + return self.particle + + def deleteatoms(self,structure,rows): + """delete atoms from particle and shifts the structure down""" + new=[] + #mulitple copying of b to the rows being replaced. + if rows==[]: + for line in structure: + new.append(line) #if no rows need replacing copy structure + return new + for i in range(rows[0]): + new.append(structure[i]) #copy structure to new until the first replaced row + count=0 + for i in range(rows[0],len(structure)-len(rows)):# to replace rows and shift undeleted rows over + for j in range(i+1+count,len(structure)): + for val in rows: + if val==j: + count+=1 + break + if val==j:continue + else: + new.append(structure[j]) + break + return new + + def addatom(self,atomtype,charge=None,moleculenum=None): + """Adds an atom to the particle surface between maxdist and the cutoff distance. + This atom is stored in self.particle and self.surface. + In the current coding the particle is centered at (0,0,0). + This method has a required input of atomtype and optional inputs of charge and moleculenum. + This method currently does not support correctly self.atomtype of dipole, electron, ellipsoid, granular, peri or hybrid + Will use the image flags 0,0,0. + Note: The atomtype here is different from the atomtype attribute as part of this class + Note: If the added atom will be required for bonding later than you will need to extract the particle data + and rebuild the surface, because this method doesn't include updates to the required variables due to some coding issues""" + import math, random + # Checks to make sure self.atomtype is not set to an unsupported value + if self.atomtype=='dipole' or self.atomtype=='electron' or\ + self.atomtype=='ellipsoid' or self.atomtype=='peri' or self.atomtype=='hybrid': + print self.atomtype, 'is not currently supported. But, you can add support by adding the needed functionality and inputs' + return + + print 'adding an atom to the surface' + # Randomly assigns the position of a new atom in a spherical shell between self.cutoff and self.maxdist + foundpoint=False + while (foundpoint==False): + x=random.uniform(-self.maxdist,self.maxdist) + y=random.uniform(-self.maxdist,self.maxdist) + try: + z=random.uniform(math.sqrt(self.cutoff**2-x**2-y**2),math.sqrt(self.maxdist**2-x**2-y**2)) + except ValueError: + continue + distance=math.sqrt(x**2+y**2+z**2) + if distance=self.maxdist-self.cutoff: + foundpoint=True + + # initialize new row in self.particle and self.surface + self.particle.append([]) + self.surface.append([]) + + pposition=len(self.particle)-1 #particle postion + sposition=len(self.surface)-1 #surface position + + # Adds the atom id number to the new row + self.particle[pposition].append(str(pposition+1)) + self.surface[sposition].append(str(pposition+1)) + + # Adds the atomtype and the moleculenum if needed to the new row + if self.atomtype=='angle' or self.atomtype=='bond' or self.atomtype=='full' or\ + self.atomtype=='molecular': + self.particle[pposition].append(str(moleculenum)) + self.surface[sposition].append(str(moleculenum)) + self.particle[pposition].append(str(atomtype)) + self.surface[sposition].append(str(atomtype)) + else: + self.particle[pposition].append(str(atomtype)) + self.surface[sposition].append(str(atomtype)) + + # Adds the charge if needed to the new row + if self.atomtype=='charge' or self.atomtype=='full': + self.particle[pposition].append(str(charge)) + self.surface[sposition].append(str(charge)) + + # Adds the atom's position to the new row + self.particle[pposition].append(str(x)) + self.surface[sposition].append(str(x)) + self.particle[pposition].append(str(y)) + self.surface[sposition].append(str(y)) + self.particle[pposition].append(str(z)) + self.surface[sposition].append(str(z)) + + # Adds the atom's image flags to the new row + self.particle[pposition].append('0') + self.surface[sposition].append('0') + self.particle[pposition].append('0') + self.surface[sposition].append('0') + self.particle[pposition].append('0') + self.surface[sposition].append('0') + + + def createxyz(self,file,data,routine='mass', values=None): + """This shows the particle surface. To show the particle surface after bonding has occured, + you will need to extract the particle than reinsert the particle into the class and use createxyz. + Two possible routines one to use the masses from data and the other to use the atom type and values supplied by the user. + The mass version is assessed by setting the routine to 'mass' which is the default method. + The other version is assessed by setting the routine to 'atomtype'. + The other version takes values which is a list containing the value the user wants to assign those atomtypes to. + The atomtypes of the values in the list will start at 1 even if no atoms in molecule use 1. + This makes it easier to find the atomtype and assign the value from the list + All atom data is assumed to have image flags in the data.""" + f=open(file,'w') + f.write('{0}\n'.format(len(self.surface))) + f.write('atoms\n') + if routine=='mass': + for line in self.surface: + if self.atomtype=='angle' or self.atomtype=='bond' or self.atomtype=='full' or\ + self.atomtype=='molecular': + type=int(line[2])-1 #3rd position + else: + type=int(line[1])-1 #2nd position + mass=data.masses[type][1] + if mass=='12.0107': elementnum=6 + elif mass=='15.9994': elementnum=8 + elif mass=='26.981539':elementnum=13 + else: + print 'no matching mass value. The method will exit' + return + l=len(line)-1 + if self.atomtype=='angle' or self.atomtype=='atomic' or self.atomtype=='bond' or\ + self.atomtype=='charge' or self.atomtype=='colloid' or self.atomtype=='electron' or\ + self.atomtype=='full' or self.atomtype=='granular' or self.atomtype=='molecular' or\ + self.atomtype=='peri': + f.write('{0} {1} {2} {3}\n'.format(elementnum,line[l-5],line[l-4],line[l-3])) + elif self.atomtype=='dipole': + f.write('{0} {1} {2} {3}\n'.format(elementnum,line[l-8],line[l-7],line[l-6])) + elif self.atomtype=='ellipsoid': + f.write('{0} {1} {2} {3}\n'.format(elementnum,line[l-9],line[l-8],line[l-7])) + elif self.atomtype=='hybrid': + f.write('{0} {1} {2} {3}\n'.format(elementnum,line[2],line[3],line[4])) + f.close() + elif routine=='atomtype': + for line in self.surface: + if self.atomtype=='angle' or self.atomtype=='bond' or self.atomtype=='full' or\ + self.atomtype=='molecular': + type=int(line[2])-1 #3rd position + else: + type=int(line[1])-1 #2nd position + elementnum=values[type] + l=len(line)-1 + if self.atomtype=='angle' or self.atomtype=='atomic' or self.atomtype=='bond' or\ + self.atomtype=='charge' or self.atomtype=='colloid' or self.atomtype=='electron' or\ + self.atomtype=='full' or self.atomtype=='granular' or self.atomtype=='molecular' or\ + self.atomtype=='peri': + f.write('{0} {1} {2} {3}\n'.format(elementnum,line[l-5],line[l-4],line[l-3])) + elif self.atomtype=='dipole': + f.write('{0} {1} {2} {3}\n'.format(elementnum,line[l-8],line[l-7],line[l-6])) + elif self.atomtype=='ellipsoid': + f.write('{0} {1} {2} {3}\n'.format(elementnum,line[l-9],line[l-8],line[l-7])) + elif self.atomtype=='hybrid': + f.write('{0} {1} {2} {3}\n'.format(elementnum,line[2],line[3],line[4])) + f.close() + +def molecules(data,init,final,processors, method='all'): + """ There are two ways to initialize the class Lmpsmolecule. + Method controls which way is chosen. + Acceptable values for Method are 'all', 'atom'. + The default method is 'all'""" + molecule=[] + from multiprocessing import Pool + p=Pool(processors) + for i in range(init,final+1): + molecule.append(p.apply_async(Lmpsmolecule,(i,data,method,))) + for i in range(len(molecule)): + molecule[i]=molecule[i].get() + p.close() + p.join() + return molecule + +class Lmpsmolecule: #Technically should be a meta class but written as a seperate class for easier coding. + def __init__(self,moleculenum,data,method): + """initiates lammps molecule structures + and than extract the appropriate molecular structures from the base class data""" +# ***** Lammps Molecule Structures + self.keywords=['Atoms']#include atoms here since this will be in every instance of this class + self.atoms=[] #extract + self.angles=[] #extract if keyword is there + self.bonds=[] #extract if keyword is there + self.dihedrals=[] #extract if keyword is there + self.impropers=[] #extract if keyword is there + self.velocities=[] #extract if keyword is there +# ***** Molecule number + self.moleculenum=moleculenum +# ***** Extracting the moleculue's atom information from data + self.extract(data,method) +# ***** If methods is 'all' then self.extract will extract all of the molecule's information from data + + def extract(self,data,method): + """uses base class data to extract the molecules atoms + and any other molecule data from molecule moleculenum. This extraction is done in a two step process. + Step 1: extract data.atoms with moleculenum and renumber self.atoms beginning from 1 + store extracted atom numbers from data.atoms in a list called temp + Step 2: pass temp and data to method changeatomnum + which extracts the rest of the Lammps Molecule structures from data + and changes the atomnumbers in those structures to match the ones already in self.atoms""" + #checking to make sure atomtype is a valid molecule + #if not a valid molecule print error and exit method + if data.atomtype=='full' or data.atomtype=='molecular': +# ***** Sets the molecule's atomtype + self.atomtype=data.atomtype + else: + print "not a valid molecular structure" + return + #extract the molecule self.moleculenum from data.atom to self.atom + atomnum=1 + temp=[] + for i in range(len(data.atoms)): + if int(data.atoms[i][1])==self.moleculenum: + temp.append(data.atoms[i][0]) #store extracted atomnumbers into temp + self.atoms.append(data.atoms[i]) #store extracted atom into self.atom + self.atoms[atomnum-1][0]=str(atomnum) #change extracted atom to the correct atomnumber + atomnum+=1 #update atomnumber + #extract the rest of the Lammps Molecule structures from data using changeatomnum + if method=='atom': + return + else: + self.changeatomnum(temp,data) + + def changeatomnum(self,temp,data=' '): + """changes the atomnumbers in Lmpsmolecule structures angles, bonds, dihedrals, impropers, + and velocities in a three step process. + If data is defined extract the rest of the keywords used for Lmpsmolecule. + Step 1A:If data is defined copy from data one of the above structures. + If data is not defined skip this step. + Step 1B:If data is defined remove the rows of copy which do not contain any values from temp + Step 2: If data is defined extract from copy to an above Lmpsmolecule structure and remove the extracted rows + If data is not defined skip this step. + Step 3: Use temp and self.atoms to convert the atomnumbers in Lmpsmolecule structures + to the correct values. + temp and self.atoms line up, so temp[i] and self.atoms[i][0] + correspond to the current Lmpsmolecule structures atom numbers + and correct values + will use booleanarray class to ensure that lmpsmolecule's structure values are altered only once.""" + #Step 1 and Step 2 + if data!=' ': + #extract molecular keywords from data.keywords + for keywords in data.keywords: + if keywords=='Angles' or keywords=='Bonds' or keywords=='Dihedrals' or\ + keywords=='Impropers' or keywords=='Velocities': + self.keywords.append(keywords) + for keywords in self.keywords: + if keywords!='Atoms': print 'extracting the data from', keywords + if keywords=='Angles': + copy=self.copy(data.angles) #Step 1A + dnr=[] #dnr means do not remove a 0 means remove and a 1 means keep + for j in range(len(copy)): #Step 1B + dnr.append(0) #adds 0 to all list elements of dnr + for item in temp: #finds copied structure that has temp values + for j in range(len(copy)): + for i in range(2,len(copy[j])): + if copy[j][i]==item: + dnr[j]=1 #changes jth list element of dnr to 1 + break + remove=[] + for j in range(len(dnr)): # finds dnr values that are still 0 + if dnr[j]==0: + remove.append(j) #and appends their index value to the list remove + copy=self.deleterows(copy,remove) #removes all unneeded rows from copy + structnum=1 + print 'the length of data is', len(copy) + for item in temp: #Step 2 + found=[] + for j in range(len(copy)): + for i in range(2,len(copy[j])): + if copy[j][i]==item: + found.append(j) + self.angles.append(copy[j]) + l=len(self.angles)-1 + self.angles[l][0]=str(structnum) + structnum+=1 + break + # print 'the item is', item + # print 'found is', found + copy=self.deleterows(copy,found) + elif keywords=='Bonds': + copy=self.copy(data.bonds) #Step 1A + dnr=[] #dnr means do not remove a 0 means remove and a 1 means keep + for j in range(len(copy)): #Step 1B + dnr.append(0) #adds 0 to all list elements of dnr + for item in temp: #finds copied structure that has temp values + for j in range(len(copy)): + for i in range(2,len(copy[j])): + if copy[j][i]==item: + dnr[j]=1 #changes jth list element of dnr to 1 + break + remove=[] + for j in range(len(dnr)): # finds dnr values that are still 0 + if dnr[j]==0: + remove.append(j) #and appends their index value to the list remove + copy=self.deleterows(copy,remove) #removes all unneeded rows from copy + structnum=1 + print 'the length of data is', len(copy) + for item in temp: #Step 2 + found=[] + for j in range(len(copy)): + for i in range(2,len(copy[j])): + if copy[j][i]==item: + found.append(j) + self.bonds.append(copy[j]) + l=len(self.bonds)-1 + self.bonds[l][0]=str(structnum) + structnum+=1 + break + # print 'the item is', item + # print 'found is', found + copy=self.deleterows(copy,found) + elif keywords=='Dihedrals': + copy=self.copy(data.dihedrals) #Step 1A + dnr=[] #dnr means do not remove a 0 means remove and a 1 means keep + for j in range(len(copy)): #Step 1B + dnr.append(0) #adds 0 to all list elements of dnr + for item in temp: #finds copied structure that has temp values + for j in range(len(copy)): + for i in range(2,len(copy[j])): + if copy[j][i]==item: + dnr[j]=1 #changes jth list element of dnr to 1 + break + remove=[] + for j in range(len(dnr)): # finds dnr values that are still 0 + if dnr[j]==0: + remove.append(j) #and appends their index value to the list remove + copy=self.deleterows(copy,remove) #removes all unneeded rows from copy + structnum=1 + print 'the length of data is', len(copy) + structnum=1 + for item in temp: #Step 2 + found=[] + for j in range(len(copy)): + for i in range(2,len(copy[j])): + if copy[j][i]==item: + found.append(j) + self.dihedrals.append(copy[j]) + l=len(self.dihedrals)-1 + self.dihedrals[l][0]=str(structnum) + structnum+=1 + break + # print 'the item is', item + # print 'found is', found + copy=self.deleterows(copy,found) + elif keywords=='Impropers': + copy=self.copy(data.impropers) #Step 1B + dnr=[] #dnr means do not remove a 0 means remove and a 1 means keep + for j in range(len(copy)): #Step 1B + dnr.append(0) #adds 0 to all list elements of dnr + for item in temp: #finds copied structure that has temp values + for j in range(len(copy)): + for i in range(2,len(copy[j])): + if copy[j][i]==item: + dnr[j]=1 #changes jth list element of dnr to 1 + break + remove=[] + for j in range(len(dnr)): # finds dnr values that are still 0 + if dnr[j]==0: + remove.append(j) #and appends their index value to the list remove + copy=self.deleterows(copy,remove) #removes all unneeded rows from copy + structnum=1 + print 'the length of data is', len(copy) + for item in temp: #Step 2 + found=[] + for j in range(len(copy)): + for i in range(2,len(copy[j])): + if copy[j][i]==item: + found.append(j) + self.impropers.append(copy[j]) + l=len(self.impropers)-1 + self.impropers[l][0]=str(structnum) + structnum+=1 + break + # print 'the item is', item + # print 'found is', found + copy=self.deleterows(copy,found) + elif keywords=='Velocities': + copy=self.copy(data.velocities) #Step 1 + dnr=[] #dnr means do not remove a 0 means remove and a 1 means keep + for j in range(len(copy)): #Step 1B + dnr.append(0) #adds 0 to all list elements of dnr + for item in temp: #finds copied structure that has temp values + for j in range(len(copy)): + for i in range(1): + if copy[j][i]==item: + dnr[j]=1 #changes jth list element of dnr to 1 + break + remove=[] + for j in range(len(dnr)): # finds dnr values that are still 0 + if dnr[j]==0: + remove.append(j) #and appends their index value to the list remove + copy=self.deleterows(copy,remove) #removes all unneeded rows from copy + structnum=1 + print 'the length of data is', len(copy) + for item in temp: #Step 2 + found=[] + for j in range(len(copy)): + for i in range(1): + if copy[j][i]==item: + found.append(j) + self.velocities.append(copy[j]) + l=len(self.velocities)-1 + self.velocities[l][0]=str(structnum) + structnum+=1 + break + # print 'the item is', item + # print 'found is', found + copy=self.deleterows(copy,found) + + #Step 3 + for keywords in self.keywords: + if keywords!='Atoms': print 'altering data structure values for', keywords + if keywords=='Angles': + copy=self.copy(self.angles) + array=booleanarray(len(copy),len(copy[0]),True) #creating a booleanarray with true values + for i in range(len(temp)): + if temp[i]==self.atoms[i][0]: continue + for j in range(len(copy)): + for k in range(2,len(copy[j])): + if copy[j][k]==temp[i]: + if array.getelement(j,k): #if the boolean array is true + self.angles[j][k]=self.atoms[i][0] + array.setelement(j,k,False) + break + elif keywords=='Bonds': + copy=self.copy(self.bonds) + array=booleanarray(len(copy),len(copy[0]),True) #creating a booleanarray with true values + for i in range(len(temp)): + if temp[i]==self.atoms[i][0]: continue + for j in range(len(copy)): + for k in range(2,len(copy[j])): + if copy[j][k]==temp[i]: + if array.getelement(j,k): #if the boolean array is true + self.bonds[j][k]=self.atoms[i][0] + array.setelement(j,k,False) + break + elif keywords=='Dihedrals': + copy=self.copy(self.dihedrals) + array=booleanarray(len(copy),len(copy[0]),True) #creating a booleanarray with true values + for i in range(len(temp)): + if temp[i]==self.atoms[i][0]: continue + for j in range(len(copy)): + for k in range(2,len(copy[j])): + if copy[j][k]==temp[i]: + if array.getelement(j,k): #if the boolean array is true + self.dihedrals[j][k]=self.atoms[i][0] + array.setelement(j,k,False) + break + elif keywords=='Impropers': + copy=self.copy(self.impropers) + array=booleanarray(len(copy),len(copy[0]),True) #creating a booleanarray with true values + for i in range(len(temp)): + if temp[i]==self.atoms[i][0]: continue + for j in range(len(copy)): + for k in range(2,len(copy[j])): + if copy[j][k]==temp[i]: + if array.getelement(j,k): #if the boolean array is true + self.impropers[j][k]=self.atoms[i][0] + array.setelement(j,k,False) + break + elif keywords=='Velocities': + copy=self.copy(self.velocities) + array=booleanarray(len(copy),len(copy[0]),True) #creating a booleanarray with true values + for i in range(len(temp)): + if temp[i]==self.atoms[i][0]: continue + for j in range(len(copy)): + for k in range(1): + if copy[j][k]==temp[i]: + if array.getelement(j,k): #if the boolean array is true + self.velocities[j][k]=self.atoms[i][0] + array.setelement(j,k,False) + break + + def copy(self,structure): + """copies structure to same and returns same.""" + same=[] + for row in structure: + same.append(row) + return same + + def deleterows(self,structure,rows): # run through this {structure is list of strings and rows is list + #of numbers} + """delete rows in a structure and shifts the structure up + rows must be in increasing order for this algorithm to work correctly""" + new=[] + #mulitple copying of b to the rows being replaced. + if rows==[]: + for line in structure: + new.append(line) #if no rows need replacing copy structure + return new + for i in range(rows[0]): + new.append(structure[i]) #copy structure to new until the first replaced row + count=0 + for i in range(rows[0],len(structure)-len(rows)):# to replace rows and shift undeleted rows over + for j in range(i+1+count,len(structure)): + for val in rows: + if val==j: + count+=1 + break + if val==j:continue + else: + new.append(structure[j]) + break + return new + + def modifyatom(self,atomnumber,column,newvalue): + """modifies self.atom[atomnumber-1][column] to newvalue. + *note: newvalue needs to be a string + if column is 0 than changeatomnum method might need runnning for all atoms + that have had their atomnumber(column=0) modified. + changeatomnum method is not ran in this method when column=0""" + self.atoms[atomnumber-1][column]=newvalue + + + def deleteatoms(self,atomnumbers,atomid): + """Algorithm to find all atoms bonded to atomnumbers in the direction of atomid. + Atomid can be a list or a single integer. The single integer corresponds to atom's atomtype value. + The list corresponds to the atom's atomid values. The only difference between both cases is in the top portion of code. + When all bonded atoms have been found; ie: (the modified return values from findbonds yields an empty list); + delete those bonded atoms and all molecule structures which contain those atoms. + Convert the list of bonded atoms into rows and delete those rows from molecule.atoms""" + print 'finding atoms to delete' + bondedatoms=[] + # 1st iteration + nextatoms=self.findbonds(atomnumbers) + try: # tests whether atomid is a list or a single integer + atomid[0] + except TypeError: + #need to remove atoms from nextatoms which dont have the proper atom id + testflag=True + i=0 + while testflag: + if i>len(nextatoms)-1: break #For the case where i becomes greater than the len of nextatoms break loop + if int(self.atoms[nextatoms[i]-1][2])!=atomid:#uses the atom id for the row in atoms and than checks atom id + del nextatoms[i] #delets the atom at i + i-=1 #and than decreases i by 1 so next atom in the list will line up when i is increased + i+=1 #increase i by 1 + else: + #need to remove atoms from nextatoms which dont have the proper atom id + testflag=True + i=0 + while testflag: + if i>len(nextatoms)-1: break #For the case where i becomes greater than the len of nextatoms break loop + keep=False + for id in atomid: + if int(self.atoms[nextatoms[i]-1][0])==id: #checking if atomid is in next atom + keep=True #keep this atom + break + if not keep: + del nextatoms[i] #delets the atom at i + i-=1 #and than decreases i by 1 so next atom in the list will line up when i is increased + i+=1 #increase i by 1 + + #append next atoms into bondedatoms + #copy next atoms into prevatoms + prevatoms=[] + for atom in nextatoms: + bondedatoms.append(atom) + prevatoms.append(atom) + + + #2nd iteration + if prevatoms==[]: + print 'no bonds were found in first iteration that had atomid criteria' + return + + nextatoms=self.findbonds(prevatoms) + #need to remove atoms from nextatoms which are in atomnumbers + for atom in atomnumbers: + for i in range(len(nextatoms)): + if nextatoms[i]==atom: #checking if atom is in next atom + del nextatoms[i] #delete the atom at i + break + if nextatoms==[]: break #all bonds from find bonds have allready been added to bondedatoms + + #append next atoms into bondedatoms + #copy next atoms into prevatoms + prevatoms=[] + for atom in nextatoms: + bondedatoms.append(atom) + prevatoms.append(atom) + + #iterative proccess for finding the rest of the atoms bonded to atomnumbers in the direction of atomid. + while prevatoms!=[]: + nextatoms=self.findbonds(prevatoms) + #need to remove atoms from nextatoms which are in the prevatoms + for atom in bondedatoms: + for i in range(len(nextatoms)): + if nextatoms[i]==atom: #checking if atom is in next atom + del nextatoms[i] #delete the atom at i + break + if nextatoms==[]: break #all bonds from find bonds have allready been added to bondedatoms + + #append next atoms into bondedatoms + #copy next atoms into prevatoms + prevatoms=[] + for atom in nextatoms: + bondedatoms.append(atom) + prevatoms.append(atom) + + print 'the atoms to delete are', bondedatoms + print 'deleting atoms from structures' + #delete bonded atoms from the molecule's structures except the atom structure + for i in range(1,len(self.keywords)): #goes through all keywords except the atom keyword + print self.keywords[i] + if self.keywords[i]=='Angles': + rows=self.findatomnumbers(bondedatoms,self.angles,False) + rows=self.listorder(rows) #to order the rows in increasing order + self.angles=self.deleterows(self.angles,rows) #requires that rows be in increasing order + elif self.keywords[i]=='Bonds': + rows=self.findatomnumbers(bondedatoms,self.bonds,False) + rows=self.listorder(rows) #to order the rows in increasing order + self.bonds=self.deleterows(self.bonds,rows)#requires that rows be in increasing order + elif self.keywords[i]=='Dihedrals': + rows=self.findatomnumbers(bondedatoms,self.dihedrals,False) + rows=self.listorder(rows) #to order the rows in increasing order + self.dihedrals=self.deleterows(self.dihedrals,rows) #requires that rows be in increasing order + elif self.keywords[i]=='Impropers': + rows=self.findatomnumbers(bondedatoms,self.impropers,False) + rows=self.listorder(rows) #to order the rows in increasing order + self.impropers=self.deleterows(self.impropers,rows) #requires that rows be in increasing order + elif self.keywords[i]=='Velocities': + rows=self.findatomnumbers(bondedatoms,self.velocities,True) + rows=self.listorder(rows) #to order the rows in increasing order + self.velocities=self.deleterows(self.velocities,rows) #requires that rows be in increasing order + + print 'Atoms' + #convert bondedatoms from atom numbers to row numbers + for i in range(len(bondedatoms)): + bondedatoms[i]-=1 + bondedatoms=self.listorder(bondedatoms) #to order the row numbers in increasing order + #delete bonded atoms (row numbers) from the atom structure + self.atoms=self.deleterows(self.atoms,bondedatoms) #requires that row numbers be in increasing order + + def findatomnumbers(self,atomnumbers,structure,vflag): #need to read through this algorithm + """Algorithm to find atomnumbers in a molecule structure except. + Atoms structure is not handled in here. + Returns a list of the rows in which the atomnumbers are contained in the molecule structure""" + rows=[] + if vflag: #for handling velocity structure + for atom in atomnumbers: + for i in range(len(structure)): + if int(structure[i][0])==atom: + #duplicate rows for vflag=True are not possible. + rows.append(i) + break + else: #for handling all other structures except atoms + for atom in atomnumbers: + for i in range(len(structure)): + for j in range(2,len(structure[i])): + if int(structure[i][j])==atom: + #need to make sure duplicate value of rows are not being added + if rows==[]: + rows.append(i) #appends the row number + else: + #checking for duplicate values of rows + duplicateflag=0 + for k in range(len(rows)): + if rows[k]==i: + duplicateflag=1 + break + if duplicateflag==0: #if no duplicates adds bond number + rows.append(i) #appends the row number + break + print 'the finished row is', rows + return rows + + def listorder(self,struct): + """Takes struct and organizes the list from least to greatest. + If the list is allready ordered this algorithm will do nothing.""" + if len(struct)==1: return struct #with the length at 1; thier is only one element and theirfore nothing to order + for i in range(1,len(struct)): #when i=0, struct will not change; therefore its skipped + copy=struct[i] + for j in range(i-1,-1,-1): + struct[j+1]=struct[j] + if copy >struct[j]: + struct[j+1]=copy + break + elif j==0: + struct[j]=copy + #dont need a break here because this is the last j value in the for loop + print 'the organized row is', struct + return struct + + + + def findbonds(self,atomnumbers): + """Algorithm to find all atoms bonded to atomnumbers. + Returns a list of atomnumbers""" + #finds the bonds in which the atomnumbers are located + bondids=[] + for i in range(len(atomnumbers)): + for j in range(len(self.bonds)): + for k in range(2,len(self.bonds[j])): + if int(self.bonds[j][k])==atomnumbers[i]: + if bondids==[]: + bondids.append(int(self.bonds[j][0])) #appends the bond number + else: + #checking for duplicates of bondids + duplicateflag=0 + for l in range(len(bondids)): + if bondids[l]==int(self.bonds[j][0]): + duplicateflag=1 + break + if duplicateflag==0: #if no duplicates adds bond number + bondids.append(int(self.bonds[j][0])) + break + + #Using bondids find the atoms bonded to atomnumbers + bondedatoms=[] + from math import fabs + for id in bondids: + for atoms in atomnumbers: + found=False + for i in range(2,len(self.bonds[id-1])): + if int(self.bonds[id-1][i])==atoms: + j=int(fabs(i-5)) #switches the index from the atomnumber location to the other location + bondedatoms.append(int(self.bonds[id-1][j])) #appends the atomnuber at the other location + found=True + break + if found==True: break + return bondedatoms + + def findparticlebondingpoints(self,particle,atomid,cutoffdistance,bondnumber): + """Particle is a particlesurfaceobject, atomid is an int. + Finds atoms with atomid in the molecule which are less than the cutoffdistance from atoms in the particle. + The found atoms and particles locations are stored in a 3 dimensional list called possiblebonds + The first index corresponds with the molecule's atom + The second index correspons with the molecule/particle combination + The third index corresponds with whether the value is the molecule or the particle + Always bonds the two ends of the molecule that meet cutoff requirement. + All other possible bonds are randomly choosen until the required bondnumbers are met. + After every bond is choosen, the particle object's boolean list is updated, and possiblebonds is updated. + The update to possiblebonds involves removing the row from which the bonded molecule's atom is located + and also removing the particle atom and it's corresponding bonded atom from other rows of possiblebonds. + The final bonds are all stored in self.bonding as a 2 dimensional list""" + possiblebonds=[] + for i in range(len(self.atoms)): + if int(self.atoms[i][2])==atomid: + row=[] + for j in range(len(particle.surface)): + #assumes molecule and particle have same atomtype if not true than atomdistance will give bad value + if atomdistance(self.atoms[i],particle.surface[j],self.atomtype)<=cutoffdistance: + if particle.getbool(j): row.append([i,j]) #if not bonded than can form a possible bond + if row!=[]:possiblebonds.append(row) #need to correct this.... + + #initiate section which assigns bonds into bonding information + bonds=0 + self.bondinginformation=[] #initiate new class member + + #Checks to see if no bonds are possible [2 possible cases] + if possiblebonds==[]: + print 'no possible bonds can be formed' + return + if bondnumber==0: + print 'bondnumber is 0; so, no possible bonds can form' + return + + #section which assigns a bond to the first molecule atom which can be bonded. + self.bondinginformation.append(self.particlebondinginfo(particle,possiblebonds,0)) + bonds+=1 + if bonds==bondnumber: return + del possiblebonds[0] #deletes possible bonds to the first molecule which can be bonded + l=len(self.bondinginformation)-1 + possiblebonds=self.updatepossiblebonds(possiblebonds,self.bondinginformation[l][1]) #updates possiblebonds + #by removing any bonds which contain the newly bonded particle. + + if possiblebonds==[]:return + #section which finds the last molecule atom which can be bonded + l=len(possiblebonds)-1 + while possiblebonds[l]==[]:# to find the last molecule atom which can be bonded + if possiblebonds==[]:return + del possiblebonds[l] #since there are no more possible bonds in this location delete + l-=1 #go to next possible spot where the last molecule atom could be bonded + + #section which assigns a bond to the last molecule atom which can be bonded. + self.bondinginformation.append(self.particlebondinginfo(particle,possiblebonds,l)) + bonds+=1 + if bonds==bondnumber: return + del possiblebonds[l] #deletes possible bonds to the last molecule which can be bonded + l=len(self.bondinginformation)-1 + possiblebonds=self.updatepossiblebonds(possiblebonds,self.bondinginformation[l][1]) + + if bondnumber-bonds>=len(possiblebonds): #the rest of the bonds are assigned in order + while possiblebonds!=[]: + if possiblebonds[0]==[]: #if row of possible bonds is empty then delete the row + del possiblebonds[0] + else: #else find a bond in the row of possible bonds + self.bondinginformation.append(self.particlebondinginfo(particle,possiblebonds,0)) + #dont need to update bonds since possiblebonds will become empty at the same time or before bonds + #is equal to bondnumber + del possiblebonds[0] + l=len(self.bondinginformation)-1 + possiblebonds=self.updatepossiblebonds(possiblebonds,self.bondinginformation[l][1]) + return + else: #the rest of the bonds are assigned randomly + from random import randint #use to randomly choose an index to bond. + while bondsx,y,z[molecular] + # for j in range(3,6): + # self.modifyatom(self.bondinginformation[i][0]+1,j,patom[j])#uses the atomid here + #elif self.atomtype=='full':#4-6->x,y,z[full] + # for j in range(4,7): + # self.modifyatom(self.bondinginformation[i][0]+1,j,patom[j])#uses the atomid here + #alters the atomtype to newid of the molecule's atoms bonded to the particle. + self.modifyatom(self.bondinginformation[i][0]+1,2,newid) #uses the atomid here + if self.atomtype=='full': + # Alters the charge of the molecule's atoms bonded to the particle to newcharge + self.modifyatom(self.bondinginformation[i][0]+1,3,newcharge) + + #This is a seperate loop so atom id's and row indexes for previous steps wont get out of sync + #create atomnumbers to begin deletion process. + atomnumbers=[] + for i in range(len(self.bondinginformation)): + atomnumbers.append(self.bondinginformation[i][0]+1)#using actual atomnumbers rather than the row index + + #Call algorithm to find all atoms bonded to atomnumbers in the direction of atomid. + #Than delete those atoms and all molecule structures which contain those atoms. + self.deleteatoms(atomnumbers,atomid) + + print 'begining deletion process of the surface atoms for which the molecule atoms have replaced' + #Goes through the bondinginformation and superficially removes the surfaceatom + for i in range(len(self.bondinginformation)): + particle.removesurfatom(self.bondinginformation[i][1]) #uses the row number + #Allows the particle extract method used outside this class to remove this atom. + + def createxyz(self,file,data,routine='mass', values=None): + """Two possible routines one to use the masses from data and the other to use the atom type and values supplied by the user. + The mass version is assessed by setting the routine to 'mass' which is the default method. + The other version is assessed by setting the routine to 'atomtype'. + The other version takes values which is a list containing the value the user wants to assign those atomtypes to. + The atomtypes of the values in the list will start at 1 even if no atoms in molecule use 1. + This makes it easier to find the atomtype and assign the value from the list + All atom data is assumed to have image flags in the data.""" + f=open(file,'w') + f.write('{0}\n'.format(len(self.atoms))) + f.write('atoms\n') + if routine=='mass': + for line in self.atoms: + type=int(line[2])-1 + mass=data.masses[type][1] + if mass=='12.0107': elementnum=6 + elif mass=='15.9994': elementnum=8 + elif mass=='26.9815':elementnum=13 + else: + print 'no matching mass value. The method will exit' + return + l=len(line)-1 + f.write('{0} {1} {2} {3}\n'.format(elementnum,line[l-5],line[l-4],line[l-3])) + f.close() + elif routine=='atomtype': + for line in self.atoms: + type=int(line[2])-1 + elementnum=values[type] + l=len(line)-1 + f.write('{0} {1} {2} {3}\n'.format(elementnum,line[l-5],line[l-4],line[l-3])) + f.close()